Merge pull request #4526 from turbo124/v5-stable

Version bump
This commit is contained in:
David Bomba 2020-12-19 11:56:32 +11:00 committed by GitHub
commit 0763829018
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
259 changed files with 121601 additions and 117455 deletions

View File

@ -26,7 +26,7 @@ BROADCAST_DRIVER=log
LOG_CHANNEL=stack LOG_CHANNEL=stack
CACHE_DRIVER=file CACHE_DRIVER=file
QUEUE_CONNECTION=database QUEUE_CONNECTION=database
SESSION_DRIVER=cookie SESSION_DRIVER=file
SESSION_LIFETIME=120 SESSION_LIFETIME=120
REDIS_HOST=127.0.0.1 REDIS_HOST=127.0.0.1

View File

@ -8,11 +8,14 @@ on:
name: phpunit name: phpunit
jobs: jobs:
phpunit: run:
runs-on: ubuntu-latest runs-on: ${{ matrix.operating-system }}
strategy: strategy:
matrix: matrix:
php-versions: ['7.3', '7.4', '8.0'] operating-system: ['ubuntu-18.04', 'ubuntu-20.04']
php-versions: ['7.3', '7.4']
phpunit-versions: ['latest']
env: env:
DB_DATABASE1: ninja DB_DATABASE1: ninja
DB_USERNAME1: root DB_USERNAME1: root
@ -60,6 +63,12 @@ jobs:
sleep 1 sleep 1
done done
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
extensions: mysql, mysqlnd, sqlite3, bcmath, gmp, gd, curl, zip, openssl, mbstring, xml
- uses: actions/checkout@v1 - uses: actions/checkout@v1
with: with:
ref: v5-develop ref: v5-develop

2
.gitignore vendored
View File

@ -27,3 +27,5 @@ storage/migrations
nbproject nbproject
.php_cs.cache .php_cs.cache
public/test.pdf
public/storage/test.pdf

View File

@ -2,12 +2,12 @@
<img src="https://raw.githubusercontent.com/hillelcoren/invoice-ninja/master/public/images/round_logo.png" alt="Sublime's custom image"/> <img src="https://raw.githubusercontent.com/hillelcoren/invoice-ninja/master/public/images/round_logo.png" alt="Sublime's custom image"/>
</p> </p>
![phpunit](https://github.com/turbo124/invoiceninja/workflows/phpunit/badge.svg?branch=v2) ![v5-develop phpunit](https://github.com/invoiceninja/invoiceninja/workflows/phpunit/badge.svg?branch=v5-develop)
[![Build Status](https://travis-ci.org/invoiceninja/invoiceninja.svg?branch=v2)](https://travis-ci.org/invoiceninja/invoiceninja) ![v5-stable phpunit](https://github.com/invoiceninja/invoiceninja/workflows/phpunit/badge.svg?branch=v5-stable)
[![codecov](https://codecov.io/gh/invoiceninja/invoiceninja/branch/v2/graph/badge.svg)](https://codecov.io/gh/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) [![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)
# Invoice Ninja version 5 is coming! # Invoice Ninja version 5 is in Beta!
We will be using the lessons learnt in Invoice Ninja 4.0 to build a bigger better platform to work from. If you would like to contribute to the project we will gladly accept contributions for code, user guides, bug tracking and feedback! Please consider the following guidelines prior to submitting a pull request: We will be using the lessons learnt in Invoice Ninja 4.0 to build a bigger better platform to work from. If you would like to contribute to the project we will gladly accept contributions for code, user guides, bug tracking and feedback! Please consider the following guidelines prior to submitting a pull request:

View File

@ -1 +1 @@
5.0.35 5.0.36

View File

@ -15,6 +15,7 @@ use App\DataMapper\CompanySettings;
use App\Events\Invoice\InvoiceWasCreated; use App\Events\Invoice\InvoiceWasCreated;
use App\Factory\InvoiceFactory; use App\Factory\InvoiceFactory;
use App\Factory\InvoiceItemFactory; use App\Factory\InvoiceItemFactory;
use App\Factory\RecurringInvoiceFactory;
use App\Helpers\Invoice\InvoiceSum; use App\Helpers\Invoice\InvoiceSum;
use App\Jobs\Company\CreateCompanyPaymentTerms; use App\Jobs\Company\CreateCompanyPaymentTerms;
use App\Jobs\Company\CreateCompanyTaskStatuses; use App\Jobs\Company\CreateCompanyTaskStatuses;
@ -30,6 +31,7 @@ use App\Models\Expense;
use App\Models\Product; use App\Models\Product;
use App\Models\Project; use App\Models\Project;
use App\Models\Quote; use App\Models\Quote;
use App\Models\RecurringInvoice;
use App\Models\Task; use App\Models\Task;
use App\Models\User; use App\Models\User;
use App\Models\Vendor; use App\Models\Vendor;
@ -238,43 +240,25 @@ class DemoMode extends Command
$this->info('creating entities for client #'.$client->id); $this->info('creating entities for client #'.$client->id);
$this->createInvoice($client, $u2->id); $this->createInvoice($client, $u2->id);
// for($y=0; $y<($this->count); $y++){ $this->createRecurringInvoice($client, $u2->id);
// $this->info("creating invoice #{$y} for client #".$client->id);
// }
$client = $company->clients->random(); $client = $company->clients->random();
$this->createCredit($client, $u2->id); $this->createCredit($client, $u2->id);
// for($y=0; $y<($this->count); $y++){
// $this->info("creating credit #{$y} for client #".$client->id);
// }
$client = $company->clients->random(); $client = $company->clients->random();
$this->createQuote($client, $u2->id); $this->createQuote($client, $u2->id);
// for($y=0; $y<($this->count); $y++){
// $this->info("creating quote #{$y} for client #".$client->id);
// }
$client = $company->clients->random(); $client = $company->clients->random();
$this->createExpense($client); $this->createExpense($client);
//$this->info("creating expense for client #".$client->id);
$client = $company->clients->random(); $client = $company->clients->random();
$this->createVendor($client, $u2->id); $this->createVendor($client, $u2->id);
// $this->info("creating vendor for client #".$client->id);
$client = $company->clients->random(); $client = $company->clients->random();
$this->createTask($client, $u2->id); $this->createTask($client, $u2->id);
// $this->info("creating task for client #".$client->id);
$client = $company->clients->random(); $client = $company->clients->random();
$this->createProject($client, $u2->id); $this->createProject($client, $u2->id);
// $this->info("creating project for client #".$client->id);
} }
} }
@ -352,6 +336,7 @@ class DemoMode extends Command
$vendor = Task::factory()->create([ $vendor = Task::factory()->create([
'user_id' => $client->user->id, 'user_id' => $client->user->id,
'company_id' => $client->company_id, 'company_id' => $client->company_id,
'client_id' => $client->id
]); ]);
} }
@ -431,6 +416,59 @@ class DemoMode extends Command
event(new InvoiceWasCreated($invoice, $invoice->company, Ninja::eventVars())); event(new InvoiceWasCreated($invoice, $invoice->company, Ninja::eventVars()));
} }
private function createRecurringInvoice($client, $assigned_user_id = null)
{
$faker = \Faker\Factory::create();
$invoice = RecurringInvoiceFactory::create($client->company->id, $client->user->id); //stub the company and user_id
$invoice->client_id = $client->id;
$invoice->frequency_id = RecurringInvoice::FREQUENCY_MONTHLY;
$invoice->last_sent_date = now()->subMonth();
$invoice->next_send_date = now()->addMonthNoOverflow();
if ((bool) rand(0, 1)) {
$dateable = Carbon::now()->subDays(rand(0, 90));
} else {
$dateable = Carbon::now()->addDays(rand(0, 90));
}
$invoice->date = $dateable;
$invoice->line_items = $this->buildLineItems(rand(1, 10));
$invoice->uses_inclusive_taxes = false;
if (rand(0, 1)) {
$invoice->tax_name1 = 'GST';
$invoice->tax_rate1 = 10.00;
}
if (rand(0, 1)) {
$invoice->tax_name2 = 'VAT';
$invoice->tax_rate2 = 17.50;
}
if (rand(0, 1)) {
$invoice->tax_name3 = 'CA Sales Tax';
$invoice->tax_rate3 = 5;
}
// $invoice->custom_value1 = $faker->date;
// $invoice->custom_value2 = rand(0, 1) ? 'yes' : 'no';
$invoice->save();
$invoice_calc = new InvoiceSum($invoice);
$invoice_calc->build();
$invoice = $invoice_calc->getInvoice();
if (rand(0, 1)) {
$invoice->assigned_user_id = $assigned_user_id;
}
$invoice->save();
}
private function createCredit($client, $assigned_user_id = null) private function createCredit($client, $assigned_user_id = null)
{ {
$faker = \Faker\Factory::create(); $faker = \Faker\Factory::create();

View File

@ -12,10 +12,8 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Jobs\Util\VersionCheck; use App\Jobs\Util\VersionCheck;
use Composer\Console\Application;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Artisan;
use Symfony\Component\Console\Input\ArrayInput;
class PostUpdate extends Command class PostUpdate extends Command
{ {
@ -52,23 +50,35 @@ class PostUpdate extends Command
info("I wasn't able to migrate the data."); info("I wasn't able to migrate the data.");
} }
info("finished migrating");
exec('vendor/bin/composer install --no-dev:');
info("finished running composer install ");
try { try {
Artisan::call('optimize'); Artisan::call('optimize');
} catch (\Exception $e) { } catch (\Exception $e) {
info("I wasn't able to optimize."); info("I wasn't able to optimize.");
} }
info("optimized");
try {
Artisan::call('view:clear');
} catch (\Exception $e) {
info("I wasn't able to clear the views.");
}
info("view cleared");
/* For the following to work, the web user (www-data) must own all the directories */ /* For the following to work, the web user (www-data) must own all the directories */
putenv('COMPOSER_HOME=' . __DIR__ . '/vendor/bin/composer');
$input = new ArrayInput(['command' => 'install', '--no-dev' => 'true']);
$application = new Application();
$application->setAutoExit(false);
$application->run($input);
VersionCheck::dispatch(); VersionCheck::dispatch();
echo "Done."; info("sent for version check");
// echo "Done.";
} }
} }

View File

@ -173,7 +173,6 @@ class EmailTemplateDefaults
public static function emailReminder1Subject() public static function emailReminder1Subject()
{ {
info("reminder 1 subject");
return ctrans('texts.reminder_subject', ['invoice'=>'$invoice.number', 'account'=>'$company.name']); return ctrans('texts.reminder_subject', ['invoice'=>'$invoice.number', 'account'=>'$company.name']);
} }

View File

@ -11,6 +11,7 @@
namespace App\Events\Product; namespace App\Events\Product;
use App\Models\Company;
use App\Models\Product; use App\Models\Product;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;

View File

@ -11,7 +11,6 @@
namespace App\Exceptions; namespace App\Exceptions;
use App\Models\Account;
use Exception; use Exception;
use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Auth\AuthenticationException; use Illuminate\Auth\AuthenticationException;
@ -72,10 +71,6 @@ class Handler extends ExceptionHandler
info('account table not found'); info('account table not found');
return; return;
} }
// if(Account::count() == 0){
// info("handler - account table not found");
// return;
// }
if (app()->bound('sentry') && $this->shouldReport($exception)) { if (app()->bound('sentry') && $this->shouldReport($exception)) {
app('sentry')->configureScope(function (Scope $scope): void { app('sentry')->configureScope(function (Scope $scope): void {
@ -94,13 +89,31 @@ class Handler extends ExceptionHandler
} }
}); });
// app('sentry')->setRelease(config('ninja.app_version')); if ($this->validException($exception)) {
app('sentry')->captureException($exception); app('sentry')->captureException($exception);
} }
}
parent::report($exception); parent::report($exception);
} }
private function validException($exception)
{
if (strpos($exception->getMessage(), 'file_put_contents') !== false) {
return false;
}
if (strpos($exception->getMessage(), 'Permission denied') !== false) {
return false;
}
if (strpos($exception->getMessage(), 'flock()') !== false) {
return false;
}
return true;
}
/** /**
* Render an exception into an HTTP response. * Render an exception into an HTTP response.
* *

View File

@ -0,0 +1,38 @@
<?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\Export\CSV;
use App\Models\Company;
use Excel;
class InvoiceExport
{
private $company;
public function __construct(Company $company)
{
$this->company = $company;
}
public function export()
{
// $fileName = 'test.csv';
// $data = $this->company->invoices->get();
// return Excel::create($fileName, function ($excel) use ($data) {
// $excel->sheet('', function ($sheet) use ($data) {
// $sheet->loadView('export', $data);
// });
// })->download('csv');
}
}

View File

@ -22,6 +22,18 @@ use Illuminate\Support\Facades\Gate;
*/ */
class ClientFilters extends QueryFilters class ClientFilters extends QueryFilters
{ {
/**
* Filter by name.
*
* @param string $name
* @return Builder
*/
public function name(string $name): Builder
{
return $this->builder->where('name', 'like', '%'.$name.'%');
}
/** /**
* Filter by balance. * Filter by balance.
* *
@ -190,7 +202,6 @@ class ClientFilters extends QueryFilters
* limit the user to only the invoices they have created * limit the user to only the invoices they have created
*/ */
if (Gate::denies('view-list', Client::class)) { if (Gate::denies('view-list', Client::class)) {
info('the gate!');
$query->where('clients.user_id', '=', $user->id); $query->where('clients.user_id', '=', $user->id);
} }

View File

@ -104,11 +104,11 @@ abstract class QueryFilters
* @param string $value * @param string $value
* @return stdClass * @return stdClass
*/ */
public function split($value) : stdClass public function split($value) : \stdClass
{ {
$exploded_array = explode(':', $value); $exploded_array = explode(':', $value);
$parts = new stdClass; $parts = new \stdClass;
$parts->value = $exploded_array[0]; $parts->value = $exploded_array[0];
$parts->operator = $this->operatorConvertor($exploded_array[1]); $parts->operator = $this->operatorConvertor($exploded_array[1]);

View File

@ -53,6 +53,13 @@ class DocumentController extends Controller
return Storage::disk($document->disk)->download($document->url, $document->name); return Storage::disk($document->disk)->download($document->url, $document->name);
} }
public function publicDownload(string $document_hash)
{
$document = Document::where('hash', $document_hash)->firstOrFail();
return Storage::disk($document->disk)->download($document->url, $document->name);
}
public function downloadMultiple(DownloadMultipleDocumentsRequest $request) public function downloadMultiple(DownloadMultipleDocumentsRequest $request)
{ {
$documents = Document::whereIn('id', $this->transformKeys($request->file_hash)) $documents = Document::whereIn('id', $this->transformKeys($request->file_hash))

View File

@ -232,6 +232,7 @@ class PaymentController extends Controller
public function response(PaymentResponseRequest $request) public function response(PaymentResponseRequest $request)
{ {
$gateway = CompanyGateway::find($request->input('company_gateway_id'))->firstOrFail(); $gateway = CompanyGateway::find($request->input('company_gateway_id'))->firstOrFail();
$payment_hash = PaymentHash::whereRaw('BINARY `hash`= ?', [$request->payment_hash])->first(); $payment_hash = PaymentHash::whereRaw('BINARY `hash`= ?', [$request->payment_hash])->first();
return $gateway return $gateway

View File

@ -132,8 +132,6 @@ class EmailController extends BaseController
} }
}); });
$entity_obj->service()->markSent()->save();
$entity_obj->last_sent_date = now(); $entity_obj->last_sent_date = now();
$entity_obj->save(); $entity_obj->save();
@ -145,35 +143,34 @@ class EmailController extends BaseController
$this->entity_type = Invoice::class; $this->entity_type = Invoice::class;
$this->entity_transformer = InvoiceTransformer::class; $this->entity_transformer = InvoiceTransformer::class;
if($entity_obj->invitations->count() >= 1) if ($entity_obj->invitations->count() >= 1) {
$entity_obj->entityEmailEvent($entity_obj->invitations->first(), 'invoice'); $entity_obj->entityEmailEvent($entity_obj->invitations->first(), 'invoice');
}
} }
if ($entity_obj instanceof Quote) { if ($entity_obj instanceof Quote) {
$this->entity_type = Quote::class; $this->entity_type = Quote::class;
$this->entity_transformer = QuoteTransformer::class; $this->entity_transformer = QuoteTransformer::class;
if($entity_obj->invitations->count() >= 1) if ($entity_obj->invitations->count() >= 1) {
event(new QuoteWasEmailed($entity_obj->invitations->first(), $entity_obj->company, Ninja::eventVars())); event(new QuoteWasEmailed($entity_obj->invitations->first(), $entity_obj->company, Ninja::eventVars()));
}
} }
if ($entity_obj instanceof Credit) { if ($entity_obj instanceof Credit) {
$this->entity_type = Credit::class; $this->entity_type = Credit::class;
$this->entity_transformer = CreditTransformer::class; $this->entity_transformer = CreditTransformer::class;
if($entity_obj->invitations->count() >= 1) if ($entity_obj->invitations->count() >= 1) {
event(new CreditWasEmailed($entity_obj->invitations->first(), $entity_obj->company, Ninja::eventVars())); event(new CreditWasEmailed($entity_obj->invitations->first(), $entity_obj->company, Ninja::eventVars()));
} }
}
if ($entity_obj instanceof RecurringInvoice) { if ($entity_obj instanceof RecurringInvoice) {
$this->entity_type = RecurringInvoice::class; $this->entity_type = RecurringInvoice::class;
$this->entity_transformer = RecurringInvoiceTransformer::class; $this->entity_transformer = RecurringInvoiceTransformer::class;
} }
$entity_obj->service()->markSent()->save(); return $this->itemResponse($entity_obj->fresh());
return $this->itemResponse($entity_obj);
} }
} }

View File

@ -0,0 +1,133 @@
<?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\Controllers;
use App\Http\Requests\Import\ImportRequest;
use App\Http\Requests\Import\PreImportRequest;
use App\Jobs\Import\CSVImport;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
use League\Csv\Reader;
use League\Csv\Statement;
class ImportController extends Controller
{
/**
* Store a newly created resource in storage.
*
* @param StoreImportRequest $request
* @return Response
*
* @OA\Post(
* path="/api/v1/preimport",
* operationId="preimport",
* tags={"imports"},
* summary="Pre Import checks - returns a reference to the job and the headers of the CSV",
* description="Pre Import checks - returns a reference to the job and the headers of the CSV",
* @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 CSV file",
* required=true,
* @OA\MediaType(
* mediaType="multipart/form-data",
* @OA\Schema(
* type="string",
* format="binary"
* )
* )
* ),
* @OA\Response(
* response=200,
* description="Returns a reference to the file",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-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 preimport(PreImportRequest $request)
{
//create a reference
$hash = Str::random(32);
//store the csv in cache with an expiry of 10 minutes
Cache::put($hash, base64_encode(file_get_contents($request->file('file')->getPathname())), 3600);
//parse CSV
$csv_array = $this->getCsvData(file_get_contents($request->file('file')->getPathname()));
$class_map = $this->getEntityMap($request->input('entity_type'));
$data = [
'hash' => $hash,
'available' => $class_map::importable(),
'headers' => array_slice($csv_array, 0, 2)
];
return response()->json($data);
}
public function import(ImportRequest $request)
{
CSVImport::dispatch($request->all(), auth()->user()->company());
return response()->json(['message' => 'Importing data, email will be sent on completion'], 200);
}
private function getEntityMap($entity_type)
{
return sprintf('App\\Import\\Definitions\%sMap', ucfirst($entity_type));
}
private function getCsvData($csvfile)
{
if (! ini_get('auto_detect_line_endings')) {
ini_set('auto_detect_line_endings', '1');
}
$csv = Reader::createFromString($csvfile);
$stmt = new Statement();
$data = iterator_to_array($stmt->process($csv));
if (count($data) > 0) {
$headers = $data[0];
// Remove Invoice Ninja headers
if (count($headers) && count($data) > 4) {
$firstCell = $headers[0];
if (strstr($firstCell, (string)config('ninja.app_name'))) {
array_shift($data); // Invoice Ninja...
array_shift($data); // <blank line>
array_shift($data); // Enitty Type Header
}
}
}
return $data;
}
}

View File

@ -12,9 +12,7 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Events\Invoice\InvoiceReminderWasEmailed;
use App\Events\Invoice\InvoiceWasCreated; use App\Events\Invoice\InvoiceWasCreated;
use App\Events\Invoice\InvoiceWasEmailed;
use App\Events\Invoice\InvoiceWasUpdated; use App\Events\Invoice\InvoiceWasUpdated;
use App\Factory\CloneInvoiceFactory; use App\Factory\CloneInvoiceFactory;
use App\Factory\CloneInvoiceToQuoteFactory; use App\Factory\CloneInvoiceToQuoteFactory;
@ -31,7 +29,6 @@ use App\Jobs\Entity\EmailEntity;
use App\Jobs\Invoice\StoreInvoice; use App\Jobs\Invoice\StoreInvoice;
use App\Jobs\Invoice\ZipInvoices; use App\Jobs\Invoice\ZipInvoices;
use App\Jobs\Util\UnlinkFile; use App\Jobs\Util\UnlinkFile;
use App\Models\Activity;
use App\Models\Client; use App\Models\Client;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\Quote; use App\Models\Quote;
@ -730,8 +727,9 @@ class InvoiceController extends BaseController
EmailEntity::dispatch($invitation, $invoice->company, $this->reminder_template); EmailEntity::dispatch($invitation, $invoice->company, $this->reminder_template);
}); });
if($invoice->invitations->count() >= 1) if ($invoice->invitations->count() >= 1) {
$invoice->entityEmailEvent($invoice->invitations->first(), $this->reminder_template); $invoice->entityEmailEvent($invoice->invitations->first(), $this->reminder_template);
}
if (! $bulk) { if (! $bulk) {
return response()->json(['message' => 'email sent'], 200); return response()->json(['message' => 'email sent'], 200);

View File

@ -21,10 +21,13 @@ class PaymentWebhookController extends Controller
$this->middleware('guest'); $this->middleware('guest');
} }
public function __invoke(PaymentWebhookRequest $request, string $gateway_key, string $company_key) public function __invoke(PaymentWebhookRequest $request, string $company_key, string $company_gateway_id)
{ {
$payment = $request->getPayment();
$client = is_null($payment) ? $request->getClient() : $payment->client;
return $request->getCompanyGateway() return $request->getCompanyGateway()
->driver($request->getClient()) ->driver($client)
->processWebhookRequest($request, $request->getPayment()); ->processWebhookRequest($request, $payment);
} }
} }

View File

@ -11,7 +11,6 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Designs\Designer;
use App\Jobs\Util\PreviewPdf; use App\Jobs\Util\PreviewPdf;
use App\Models\Client; use App\Models\Client;
use App\Models\ClientContact; use App\Models\ClientContact;
@ -20,10 +19,13 @@ use App\Models\InvoiceInvitation;
use App\Services\PdfMaker\Design; use App\Services\PdfMaker\Design;
use App\Services\PdfMaker\PdfMaker; use App\Services\PdfMaker\PdfMaker;
use App\Utils\HtmlEngine; use App\Utils\HtmlEngine;
use App\Utils\Ninja;
use App\Utils\PhantomJS\Phantom; use App\Utils\PhantomJS\Phantom;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use App\Utils\Traits\MakesInvoiceHtml; use App\Utils\Traits\MakesInvoiceHtml;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Lang;
use Illuminate\Support\Facades\Response; use Illuminate\Support\Facades\Response;
class PreviewController extends BaseController class PreviewController extends BaseController
@ -96,15 +98,16 @@ class PreviewController extends BaseController
$entity_obj->load('client'); $entity_obj->load('client');
App::setLocale($entity_obj->client->primary_contact()->preferredLocale());
App::forgetInstance('translator');
Lang::replace(Ninja::transformTranslations($entity_obj->client->getMergedSettings()));
$html = new HtmlEngine($entity_obj->invitations()->first()); $html = new HtmlEngine($entity_obj->invitations()->first());
$design_namespace = 'App\Services\PdfMaker\Designs\\'.request()->design['name']; $design_namespace = 'App\Services\PdfMaker\Designs\\'.request()->design['name'];
$design_class = new $design_namespace(); $design_class = new $design_namespace();
// $designer = new Designer($entity_obj, $design_object, $entity_obj->client->getSetting('pdf_variables'), lcfirst($entity));
// $html = $this->generateEntityHtml($designer, $entity_obj);
$state = [ $state = [
'template' => $design_class->elements([ 'template' => $design_class->elements([
'client' => $entity_obj->client, 'client' => $entity_obj->client,
@ -122,6 +125,10 @@ class PreviewController extends BaseController
->design($design) ->design($design)
->build(); ->build();
if (request()->has('html') && request()->input('html') == true) {
return $maker->getCompiledHTML;
}
//if phantom js...... inject here.. //if phantom js...... inject here..
if (config('ninja.phantomjs_pdf_generation')) { if (config('ninja.phantomjs_pdf_generation')) {
return (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true)); return (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true));
@ -138,6 +145,9 @@ class PreviewController extends BaseController
private function blankEntity() private function blankEntity()
{ {
App::forgetInstance('translator');
Lang::replace(Ninja::transformTranslations(auth()->user()->company()->settings));
DB::beginTransaction(); DB::beginTransaction();
$client = Client::factory()->create([ $client = Client::factory()->create([

View File

@ -73,9 +73,10 @@ class SelfUpdateController extends BaseController
info($e->getMessage()); info($e->getMessage());
return response()->json(['message'=>$e->getMessage()], 500); return response()->json(['message'=>$e->getMessage()], 500);
} }
info('Are there any changes to pull? '.$repo->hasChanges());
dispatch(function () {
Artisan::call('ninja:post-update'); Artisan::call('ninja:post-update');
});
return response()->json(['message' => ''], 200); return response()->json(['message' => ''], 200);
} }

View File

@ -21,6 +21,7 @@ use App\Models\Account;
use App\Utils\CurlUtils; use App\Utils\CurlUtils;
use App\Utils\SystemHealth; use App\Utils\SystemHealth;
use App\Utils\Traits\AppSetup; use App\Utils\Traits\AppSetup;
use Beganovich\ChromiumPdf\ChromiumPdf;
use DB; use DB;
use Exception; use Exception;
use Illuminate\Contracts\Foundation\Application; use Illuminate\Contracts\Foundation\Application;
@ -97,14 +98,8 @@ class SetupController extends Controller
$mail_driver = $request->input('mail_driver'); $mail_driver = $request->input('mail_driver');
$url = $request->input('url');
if (substr($url, -1) != '/') {
$url = $url . '/';
}
$env_values = [ $env_values = [
'APP_URL' => $url, 'APP_URL' => $request->input('url'),
'REQUIRE_HTTPS' => $request->input('https') ? 'true' : 'false', 'REQUIRE_HTTPS' => $request->input('https') ? 'true' : 'false',
'APP_DEBUG' => $request->input('debug') ? 'true' : 'false', 'APP_DEBUG' => $request->input('debug') ? 'true' : 'false',
@ -164,12 +159,12 @@ class SetupController extends Controller
} }
/** /**
* Return status based on check of database connection. * Return status based on database check.
* *
* @param CheckDatabaseRequest $request * @param CheckDatabaseRequest $request
* @return Response * @return Application|ResponseFactory|JsonResponse|Response
*/ */
public function checkDB(CheckDatabaseRequest $request): Response public function checkDB(CheckDatabaseRequest $request)
{ {
try { try {
$status = SystemHealth::dbCheck($request); $status = SystemHealth::dbCheck($request);
@ -227,15 +222,26 @@ class SetupController extends Controller
return $this->testPhantom(); return $this->testPhantom();
} }
if (config('ninja.experimental_pdf_engine')) {
$chromium_pdf = new ChromiumPdf();
$pdf = $chromium_pdf
->setChromiumPath(config('ninja.experimental_pdf_engine_chromium_path'))
->setHtml('GENERATING PDFs WORKS! Thank you for using Invoice Ninja!')
->generate();
Storage::put('public/test.pdf', $pdf);
} else {
Browsershot::html('GENERATING PDFs WORKS! Thank you for using Invoice Ninja!') Browsershot::html('GENERATING PDFs WORKS! Thank you for using Invoice Ninja!')
->setNodeBinary(config('ninja.system.node_path')) ->setNodeBinary(config('ninja.system.node_path'))
->setNpmBinary(config('ninja.system.npm_path')) ->setNpmBinary(config('ninja.system.npm_path'))
->noSandbox() ->noSandbox()
->savePdf( ->savePdf(
public_path('test.pdf') public_path('storage/test.pdf')
); );
}
return response(['url' => asset('test.pdf')], 200); return response(['url' => asset('storage/test.pdf')], 200);
} catch (Exception $e) { } catch (Exception $e) {
info($e->getMessage()); info($e->getMessage());

View File

@ -35,9 +35,12 @@ class StoreExpenseRequest extends Request
{ {
$rules = []; $rules = [];
if (isset($this->number)) {
$rules['number'] = Rule::unique('expenses')->where('company_id', auth()->user()->company()->id); $rules['number'] = Rule::unique('expenses')->where('company_id', auth()->user()->company()->id);
}
// $rules['number'] = 'unique:expenses,number,'.$this->id.',id,company_id,'.auth()->user()->company()->id; // $rules['number'] = 'unique:expenses,number,'.$this->id.',id,company_id,'.auth()->user()->company()->id;
$rules['contacts.*.email'] = 'nullable|distinct'; // $rules['contacts.*.email'] = 'nullable|distinct';
//$rules['number'] = new UniqueExpenseNumberRule($this->all()); //$rules['number'] = new UniqueExpenseNumberRule($this->all());
$rules['client_id'] = 'bail|sometimes|exists:clients,id,company_id,'.auth()->user()->company()->id; $rules['client_id'] = 'bail|sometimes|exists:clients,id,company_id,'.auth()->user()->company()->id;
@ -55,6 +58,10 @@ class StoreExpenseRequest extends Request
$input['category_id'] = $this->decodePrimaryKey($input['category_id']); $input['category_id'] = $this->decodePrimaryKey($input['category_id']);
} }
if (! array_key_exists('currency_id', $input) || strlen($input['currency_id']) == 0) {
$input['currency_id'] = (string)auth()->user()->company()->settings->currency_id;
}
$this->replace($input); $this->replace($input);
} }
@ -62,8 +69,6 @@ class StoreExpenseRequest extends Request
{ {
return [ return [
'unique' => ctrans('validation.unique', ['attribute' => 'email']), 'unique' => ctrans('validation.unique', ['attribute' => 'email']),
//'required' => trans('validation.required', ['attribute' => 'email']),
'contacts.*.email.required' => ctrans('validation.email', ['attribute' => 'email']),
]; ];
} }
} }

View File

@ -66,6 +66,10 @@ class UpdateExpenseRequest extends Request
$input['category_id'] = $this->decodePrimaryKey($input['category_id']); $input['category_id'] = $this->decodePrimaryKey($input['category_id']);
} }
if (! array_key_exists('currency_id', $input) || strlen($input['currency_id']) == 0) {
$input['currency_id'] = (string)auth()->user()->company()->settings->currency_id;
}
$this->replace($input); $this->replace($input);
} }
} }

View File

@ -0,0 +1,37 @@
<?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\Import;
use App\Http\Requests\Request;
class ImportRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->isAdmin();
}
public function rules()
{
return [
'hash' => 'required|string',
'entity_type' => 'required|string',
'column_map' => 'required|array',
'skip_header' => 'required|boolean'
];
}
}

View File

@ -0,0 +1,35 @@
<?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\Import;
use App\Http\Requests\Request;
class PreImportRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->isAdmin();
}
public function rules()
{
return [
'file' => 'required|file|mimes:csv,txt',
'entity_type' => 'required',
];
}
}

View File

@ -49,6 +49,7 @@ class UpdateInvoiceRequest extends Request
$rules['id'] = new LockedInvoiceRule($this->invoice); $rules['id'] = new LockedInvoiceRule($this->invoice);
// if ($this->input('number') && strlen($this->input('number')) >= 1) {
if ($this->input('number')) { if ($this->input('number')) {
$rules['number'] = 'unique:invoices,number,'.$this->id.',id,company_id,'.$this->invoice->company_id; $rules['number'] = 'unique:invoices,number,'.$this->id.',id,company_id,'.$this->invoice->company_id;
} }

View File

@ -18,9 +18,12 @@ use App\Models\Company;
use App\Models\CompanyGateway; use App\Models\CompanyGateway;
use App\Models\Payment; use App\Models\Payment;
use App\Models\PaymentHash; use App\Models\PaymentHash;
use App\Utils\Traits\MakesHash;
class PaymentWebhookRequest extends Request class PaymentWebhookRequest extends Request
{ {
use MakesHash;
public function authorize() public function authorize()
{ {
return true; return true;
@ -41,20 +44,22 @@ class PaymentWebhookRequest extends Request
*/ */
public function getCompanyGateway(): ?CompanyGateway public function getCompanyGateway(): ?CompanyGateway
{ {
return CompanyGateway::where('gateway_key', $this->gateway_key)->firstOrFail(); return CompanyGateway::find($this->decodePrimaryKey($this->company_gateway_id))->firstOrFail();
} }
/** /**
* Resolve payment hash. * Resolve payment hash.
* *
* @param string $hash * @param string $hash
* @return null|\App\Http\Requests\Payments\PaymentHash * @return null|\App\Models\PaymentHash
*/ */
public function getPaymentHash(): ?PaymentHash public function getPaymentHash(): ?PaymentHash
{ {
if ($this->query('hash')) { if ($this->query('hash')) {
return PaymentHash::where('hash', $this->query('hash'))->firstOrFail(); return PaymentHash::where('hash', $this->query('hash'))->firstOrFail();
} }
return null;
} }
/** /**
@ -64,9 +69,15 @@ class PaymentWebhookRequest extends Request
*/ */
public function getPayment(): ?Payment public function getPayment(): ?Payment
{ {
$hash = $this->getPaymentHash(); /**
* Some gateways, like Checkout, we can dynamically pass payment hash,
* which we will resolve here and get payment information from it.
*/
if ($this->getPaymentHash()) {
return $this->getPaymentHash()->payment;
}
return $hash->payment; abort(404);
} }
/** /**

View File

@ -15,6 +15,7 @@ use App\Http\Requests\Request;
use App\Models\Client; use App\Models\Client;
use App\Models\Project; use App\Models\Project;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use Illuminate\Validation\Rule;
class StoreProjectRequest extends Request class StoreProjectRequest extends Request
{ {
@ -36,7 +37,10 @@ class StoreProjectRequest extends Request
$rules['name'] = 'required'; $rules['name'] = 'required';
$rules['client_id'] = 'required|exists:clients,id,company_id,'.auth()->user()->company()->id; $rules['client_id'] = 'required|exists:clients,id,company_id,'.auth()->user()->company()->id;
$rules['number'] = 'unique:projects,number,'.$this->id.',id,company_id,'.auth()->user()->company()->id;
if (isset($this->number)) {
$rules['number'] = Rule::unique('projects')->where('company_id', auth()->user()->company()->id);
}
return $this->globalRules($rules); return $this->globalRules($rules);
} }

View File

@ -113,8 +113,9 @@ class StoreRecurringInvoiceRequest extends Request
private function setAutoBillFlag($auto_bill) private function setAutoBillFlag($auto_bill)
{ {
if ($auto_bill == 'always') if ($auto_bill == 'always') {
return true; return true;
}
//if ($auto_bill == 'off' || $auto_bill == 'optin') { //if ($auto_bill == 'off' || $auto_bill == 'optin') {

View File

@ -38,10 +38,11 @@ class StoreUserRequest extends Request
$rules['first_name'] = 'required|string|max:100'; $rules['first_name'] = 'required|string|max:100';
$rules['last_name'] = 'required|string|max:100'; $rules['last_name'] = 'required|string|max:100';
if (config('ninja.db.multi_db_enabled')) if (config('ninja.db.multi_db_enabled')) {
$rules['email'] = [new ValidUserForCompany(), Rule::unique('users')]; $rules['email'] = [new ValidUserForCompany(), Rule::unique('users')];
else } else {
$rules['email'] = Rule::unique('users'); $rules['email'] = Rule::unique('users');
}
if (auth()->user()->company()->account->isFreeHostedClient()) { if (auth()->user()->company()->account->isFreeHostedClient()) {

View File

@ -0,0 +1,101 @@
<?php
/**
* client Ninja (https://clientninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
*
* @copyright Copyright (c) 2020. client Ninja LLC (https://clientninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Import\Definitions;
class ClientMap
{
public static function importable()
{
return [
0 => 'client.name',
1 => 'client.user_id',
2 => 'client.balance',
3 => 'client.paid_to_date',
4 => 'client.currency_id',
5 => 'client.website',
6 => 'client.private_notes',
7 => 'client.industry_id',
8 => 'client.size_id',
9 => 'client.address1',
10 => 'client.address2',
11 => 'client.city',
12 => 'client.state',
13 => 'client.postal_code',
14 => 'client.country_id',
15 => 'client.custom_value1',
16 => 'client.custom_value2',
17 => 'client.custom_value3',
18 => 'client.custom_value4',
19 => 'client.shipping_address1',
20 => 'client.shipping_address2',
21 => 'client.shipping_city',
22 => 'client.shipping_state',
23 => 'client.shipping_postal_code',
24 => 'client.shipping_country_id',
25 => 'client.payment_terms',
26 => 'client.vat_number',
27 => 'client.id_number',
28 => 'client.public_notes',
29 => 'contact.first_name',
30 => 'contact.last_name',
31 => 'contact.email',
32 => 'contact.phone',
33 => 'contact.custom_value1',
34 => 'contact.custom_value2',
35 => 'contact.custom_value3',
36 => 'contact.custom_value4',
];
}
public static function import_keys()
{
return [
0 => 'texts.client_name',
1 => 'texts.user',
2 => 'texts.balance',
3 => 'texts.paid_to_date',
4 => 'texts.currency',
5 => 'texts.website',
6 => 'texts.private_notes',
7 => 'texts.industry',
8 => 'texts.size',
9 => 'texts.address1',
10 => 'texts.address2',
11 => 'texts.city',
12 => 'texts.state',
13 => 'texts.postal_code',
14 => 'texts.country',
15 => 'texts.custom_value',
16 => 'texts.custom_value',
17 => 'texts.custom_value',
18 => 'texts.custom_value',
19 => 'texts.address1',
20 => 'texts.address2',
21 => 'texts.shipping_city',
22 => 'texts.shipping_state',
23 => 'texts.shipping_postal_code',
24 => 'texts.shipping_country',
25 => 'texts.payment_terms',
26 => 'texts.vat_number',
27 => 'texts.id_number',
28 => 'texts.public_notes',
29 => 'texts.first_name',
30 => 'texts.last_name',
31 => 'texts.email',
32 => 'texts.phone',
33 => 'texts.custom_value',
34 => 'texts.custom_value',
35 => 'texts.custom_value',
36 => 'texts.custom_value',
];
}
}

View File

@ -0,0 +1,133 @@
<?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\Import\Definitions;
class InvoiceMap
{
public static function importable()
{
return [
0 => 'invoice.number',
1 => 'invoice.user_id',
2 => 'invoice.amount',
3 => 'invoice.balance',
4 => 'invoice.client_id',
5 => 'invoice.discount',
6 => 'invoice.po_number',
7 => 'invoice.date',
8 => 'invoice.due_date',
9 => 'invoice.terms',
10 => 'invoice.public_notes',
11 => 'invoice.is_sent',
12 => 'invoice.private_notes',
13 => 'invoice.uses_inclusive_taxes',
14 => 'invoice.tax_name1',
15 => 'invoice.tax_rate1',
16 => 'invoice.tax_name2',
17 => 'invoice.tax_rate2',
18 => 'invoice.tax_name3',
19 => 'invoice.tax_rate3',
20 => 'invoice.is_amount_discount',
21 => 'invoice.footer',
22 => 'invoice.partial',
23 => 'invoice.partial_due_date',
24 => 'invoice.custom_value1',
25 => 'invoice.custom_value2',
26 => 'invoice.custom_value3',
27 => 'invoice.custom_value4',
28 => 'invoice.custom_surcharge1',
29 => 'invoice.custom_surcharge2',
30 => 'invoice.custom_surcharge3',
31 => 'invoice.custom_surcharge4',
32 => 'invoice.exchange_rate',
33 => 'payment.date',
34 => 'payment.amount',
35 => 'payment.transaction_reference',
36 => 'item.quantity',
37 => 'item.cost',
38 => 'item.product_key',
39 => 'item.notes',
40 => 'item.discount',
41 => 'item.is_amount_discount',
42 => 'item.tax_name1',
43 => 'item.tax_rate1',
44 => 'item.tax_name2',
45 => 'item.tax_rate2',
46 => 'item.tax_name3',
47 => 'item.tax_rate3',
48 => 'item.custom_value1',
49 => 'item.custom_value2',
50 => 'item.custom_value3',
51 => 'item.custom_value4',
52 => 'item.type_id',
];
}
public static function import_keys()
{
return [
0 => 'texts.invoice_number',
1 => 'texts.user',
2 => 'texts.amount',
3 => 'texts.balance',
4 => 'texts.client',
5 => 'texts.discount',
6 => 'texts.po_number',
7 => 'texts.date',
8 => 'texts.due_date',
9 => 'texts.terms',
10 => 'texts.public_notes',
11 => 'texts.sent',
12 => 'texts.private_notes',
13 => 'texts.uses_inclusive_taxes',
14 => 'texts.tax_name',
15 => 'texts.tax_rate',
16 => 'texts.tax_name',
17 => 'texts.tax_rate',
18 => 'texts.tax_name',
19 => 'texts.tax_rate',
20 => 'texts.is_amount_discount',
21 => 'texts.footer',
22 => 'texts.partial',
23 => 'texts.partial_due_date',
24 => 'texts.custom_value1',
25 => 'texts.custom_value2',
26 => 'texts.custom_value3',
27 => 'texts.custom_value4',
28 => 'texts.surcharge',
29 => 'texts.surcharge',
30 => 'texts.surcharge',
31 => 'texts.surcharge',
32 => 'texts.exchange_rate',
33 => 'texts.payment_date',
34 => 'texts.payment_amount',
35 => 'texts.transaction_reference',
36 => 'texts.quantity',
37 => 'texts.cost',
38 => 'texts.product_key',
39 => 'texts.notes',
40 => 'texts.discount',
41 => 'texts.is_amount_discount',
42 => 'texts.tax_name',
43 => 'texts.tax_rate',
44 => 'texts.tax_name',
45 => 'texts.tax_rate',
46 => 'texts.tax_name',
47 => 'texts.tax_rate',
48 => 'texts.custom_value',
49 => 'texts.custom_value',
50 => 'texts.custom_value',
51 => 'texts.custom_value',
52 => 'texts.type',
];
}
}

View File

@ -0,0 +1,59 @@
<?php
/**
* client Ninja (https://clientninja.com).
*
* @link https://github.com/clientninja/clientninja source repository
*
* @copyright Copyright (c) 2020. client Ninja LLC (https://clientninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Import\Definitions;
class ProductMap
{
public static function importable()
{
return [
0 => 'product.product_key',
1 => 'product.notes',
2 => 'product.cost',
3 => 'product.price',
4 => 'product.quantity',
5 => 'product.tax_name1',
6 => 'product.tax_rate1',
7 => 'product.tax_name2',
8 => 'product.tax_rate2',
9 => 'product.tax_name3',
10 => 'product.tax_rate3',
11 => 'product.custom_value1',
12 => 'product.custom_value2',
13 => 'product.custom_value3',
14 => 'product.custom_value4',
15 => 'product.user_id',
];
}
public static function import_keys()
{
return [
0 => 'texts.item',
1 => 'texts.notes',
2 => 'texts.cost',
3 => 'texts.price',
4 => 'texts.quantity',
5 => 'texts.tax_name',
6 => 'texts.tax_rate',
7 => 'texts.tax_name',
8 => 'texts.tax_rate',
9 => 'texts.tax_name',
10 => 'texts.tax_rate',
11 => 'texts.custom_value',
12 => 'texts.custom_value',
13 => 'texts.custom_value',
14 => 'texts.custom_value',
15 => 'texts.user',
];
}
}

View File

@ -0,0 +1,351 @@
<?php
namespace App\Import\Transformers;
use Carbon;
use Exception;
/**
* Class BaseTransformer.
*/
class BaseTransformer
{
/**
* @var
*/
protected $maps;
/**
* BaseTransformer constructor.
*
* @param $maps
*/
public function __construct($maps)
{
$this->maps = $maps;
}
/**
* @param $data
* @param $field
*
* @return string
*/
public function getString($data, $field)
{
return (isset($data[$field]) && $data[$field]) ? $data[$field] : '';
}
public function getCurrencyByCode($data)
{
$code = array_key_exists('client.currency_id', $data) ? $data['client.currency_id'] : false;
if ($code) {
return $this->maps['currencies']->where('code', $code)->first()->id;
}
return $this->maps['company']->settings->currency_id;
}
/**
* @param $name
*
* @return bool
*/
public function hasClient($name)
{
$name = trim(strtolower($name));
return isset($this->maps[ENTITY_CLIENT][$name]);
}
/**
* @param $name
*
* @return bool
*/
public function hasVendor($name)
{
$name = trim(strtolower($name));
return isset($this->maps[ENTITY_VENDOR][$name]);
}
/**
* @param $key
*
* @return bool
*/
public function hasProduct($key)
{
$key = trim(strtolower($key));
return isset($this->maps[ENTITY_PRODUCT][$key]);
}
/**
* @param $data
* @param $field
*
* @return int
*/
public function getNumber($data, $field)
{
return (isset($data->$field) && $data->$field) ? $data->$field : 0;
}
/**
* @param $data
* @param $field
*
* @return float
*/
public function getFloat($data, $field)
{
return (isset($data->$field) && $data->$field) ? Utils::parseFloat($data->$field) : 0;
}
/**
* @param $name
*
* @return null
*/
public function getClientId($name)
{
$name = strtolower(trim($name));
return isset($this->maps[ENTITY_CLIENT][$name]) ? $this->maps[ENTITY_CLIENT][$name] : null;
}
/**
* @param $name
*
* @return null
*/
public function getProduct($data, $key, $field, $default = false)
{
$productKey = trim(strtolower($data->$key));
if (! isset($this->maps['product'][$productKey])) {
return $default;
}
$product = $this->maps['product'][$productKey];
return $product->$field ?: $default;
}
/**
* @param $name
*
* @return null
*/
public function getContact($email)
{
$email = trim(strtolower($email));
if (! isset($this->maps['contact'][$email])) {
return false;
}
return $this->maps['contact'][$email];
}
/**
* @param $name
*
* @return null
*/
public function getCustomer($key)
{
$key = trim($key);
if (! isset($this->maps['customer'][$key])) {
return false;
}
return $this->maps['customer'][$key];
}
/**
* @param $name
*
* @return null
*/
public function getCountryId($name)
{
$name = strtolower(trim($name));
return isset($this->maps['countries'][$name]) ? $this->maps['countries'][$name] : null;
}
/**
* @param $name
*
* @return null
*/
public function getCountryIdBy2($name)
{
$name = strtolower(trim($name));
return isset($this->maps['countries2'][$name]) ? $this->maps['countries2'][$name] : null;
}
/**
* @param $name
*
* @return null
*/
public function getTaxRate($name)
{
$name = strtolower(trim($name));
return isset($this->maps['tax_rates'][$name]) ? $this->maps['tax_rates'][$name] : 0;
}
/**
* @param $name
*
* @return null
*/
public function getTaxName($name)
{
$name = strtolower(trim($name));
return isset($this->maps['tax_names'][$name]) ? $this->maps['tax_names'][$name] : '';
}
/**
* @param $name
*
* @return mixed
*/
public function getFirstName($name)
{
$name = Utils::splitName($name);
return $name[0];
}
/**
* @param $date
* @param string $format
* @param mixed $data
* @param mixed $field
*
* @return null
*/
public function getDate($data, $field)
{
if ($date = data_get($data, $field)) {
try {
$date = new Carbon($date);
} catch (Exception $e) {
// if we fail to parse return blank
$date = false;
}
}
return $date ? $date->format('Y-m-d') : null;
}
/**
* @param $name
*
* @return mixed
*/
public function getLastName($name)
{
$name = Utils::splitName($name);
return $name[1];
}
/**
* @param $number
*
* @return string
*/
public function getInvoiceNumber($number)
{
return $number ? str_pad(trim($number), 4, '0', STR_PAD_LEFT) : null;
}
/**
* @param $invoiceNumber
*
* @return null
*/
public function getInvoiceId($invoiceNumber)
{
$invoiceNumber = $this->getInvoiceNumber($invoiceNumber);
$invoiceNumber = strtolower($invoiceNumber);
return isset($this->maps[ENTITY_INVOICE][$invoiceNumber]) ? $this->maps[ENTITY_INVOICE][$invoiceNumber] : null;
}
/**
* @param $invoiceNumber
*
* @return null
*/
public function getInvoicePublicId($invoiceNumber)
{
$invoiceNumber = $this->getInvoiceNumber($invoiceNumber);
$invoiceNumber = strtolower($invoiceNumber);
return isset($this->maps['invoices'][$invoiceNumber]) ? $this->maps['invoices'][$invoiceNumber]->public_id : null;
}
/**
* @param $invoiceNumber
*
* @return bool
*/
public function hasInvoice($invoiceNumber)
{
$invoiceNumber = $this->getInvoiceNumber($invoiceNumber);
$invoiceNumber = strtolower($invoiceNumber);
return isset($this->maps[ENTITY_INVOICE][$invoiceNumber]);
}
/**
* @param $invoiceNumber
*
* @return null
*/
public function getInvoiceClientId($invoiceNumber)
{
$invoiceNumber = $this->getInvoiceNumber($invoiceNumber);
$invoiceNumber = strtolower($invoiceNumber);
return isset($this->maps[ENTITY_INVOICE.'_'.ENTITY_CLIENT][$invoiceNumber]) ? $this->maps[ENTITY_INVOICE.'_'.ENTITY_CLIENT][$invoiceNumber] : null;
}
/**
* @param $name
*
* @return null
*/
public function getVendorId($name)
{
$name = strtolower(trim($name));
return isset($this->maps[ENTITY_VENDOR][$name]) ? $this->maps[ENTITY_VENDOR][$name] : null;
}
/**
* @param $name
*
* @return null
*/
public function getExpenseCategoryId($name)
{
$name = strtolower(trim($name));
return isset($this->maps[ENTITY_EXPENSE_CATEGORY][$name]) ? $this->maps[ENTITY_EXPENSE_CATEGORY][$name] : null;
}
}

View File

@ -0,0 +1,69 @@
<?php
namespace App\Import\Transformers;
use Illuminate\Support\Str;
/**
* Class ClientTransformer.
*/
class ClientTransformer extends BaseTransformer
{
/**
* @param $data
*
* @return bool|Item
*/
public function transform($data)
{
if (isset($data->name) && $this->hasClient($data->name)) {
return false;
}
$settings = new \stdClass;
$settings->currency_id = (string)$this->getCurrencyByCode($data);
return [
'company_id' => $this->maps['company']->id,
'name' => $this->getString($data, 'client.name'),
'work_phone' => $this->getString($data, 'client.phone'),
'address1' => $this->getString($data, 'client.address1'),
'address2' => $this->getString($data, 'client.address2'),
'city' => $this->getString($data, 'client.city'),
'state' => $this->getString($data, 'client.state'),
'shipping_address1' => $this->getString($data, 'client.shipping_address1'),
'shipping_address2' => $this->getString($data, 'client.shipping_address2'),
'shipping_city' => $this->getString($data, 'client.shipping_city'),
'shipping_state' => $this->getString($data, 'client.shipping_state'),
'shipping_postal_code' => $this->getString($data, 'client.shipping_postal_code'),
'public_notes' => $this->getString($data, 'client.public_notes'),
'private_notes' => $this->getString($data, 'client.private_notes'),
'website' => $this->getString($data, 'client.website'),
'vat_number' => $this->getString($data, 'client.vat_number'),
'id_number' => $this->getString($data, 'client.id_number'),
'custom_value1' => $this->getString($data, 'client.custom1'),
'custom_value2' => $this->getString($data, 'client.custom2'),
'custom_value3' => $this->getString($data, 'client.custom3'),
'custom_value4' => $this->getString($data, 'client.custom4'),
'balance' => $this->getString($data, 'client.balance'),
'paid_to_date' => $this->getString($data, 'client.paid_to_date'),
'credit_balance' => 0,
'settings' => $settings,
'client_hash' => Str::random(40),
'contacts' => [
[
'first_name' => $this->getString($data, 'contact.first_name'),
'last_name' => $this->getString($data, 'contact.last_name'),
'email' => $this->getString($data, 'contact.email'),
'phone' => $this->getString($data, 'contact.phone'),
'custom_value1' => $this->getString($data, 'contact.custom1'),
'custom_value2' => $this->getString($data, 'contact.custom2'),
'custom_value3' => $this->getString($data, 'contact.custom3'),
'custom_value4' => $this->getString($data, 'contact.custom4'),
],
],
'country_id' => isset($data->country_id) ? $this->getCountryId($data->country_id) : null,
'shipping_country_id' => isset($data->shipping_country_id) ? $this->getCountryId($data->shipping_country_id) : null,
];
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace App\Import\Transformers;
use Illuminate\Support\Str;
/**
* Class ProductTransformer.
*/
class ProductTransformer extends BaseTransformer
{
/**
* @param $data
*
* @return bool|Item
*/
public function transform($data)
{
return [
'company_id' => $this->maps['company']->id,
'product_key' => $this->getString($data, 'product.product_key'),
'notes' => $this->getString($data, 'product.notes'),
'cost' => $this->getString($data, 'product.cost'),
'price' => $this->getString($data, 'product.price'),
'quantity' => $this->getString($data, 'product.quantity'),
'tax_name1' => $this->getString($data, 'product.tax_name1'),
'tax_rate1' => $this->getString($data, 'product.tax_rate1'),
'tax_name2' => $this->getString($data, 'product.tax_name2'),
'tax_rate2' => $this->getString($data, 'product.tax_rate2'),
'tax_name3' => $this->getString($data, 'product.tax_name3'),
'tax_rate3' => $this->getString($data, 'product.tax_rate3'),
'custom_value1' => $this->getString($data, 'product.custom_value1'),
'custom_value2' => $this->getString($data, 'product.custom_value2'),
'custom_value3' => $this->getString($data, 'product.custom_value3'),
'custom_value4' => $this->getString($data, 'product.custom_value4'),
];
}
}

View File

@ -87,7 +87,6 @@ class CreateEntityPdf implements ShouldQueue
public function handle() public function handle()
{ {
App::setLocale($this->contact->preferredLocale()); App::setLocale($this->contact->preferredLocale());
App::forgetInstance('translator'); App::forgetInstance('translator');
Lang::replace(Ninja::transformTranslations($this->entity->client->getMergedSettings())); Lang::replace(Ninja::transformTranslations($this->entity->client->getMergedSettings()));
@ -158,10 +157,19 @@ class CreateEntityPdf implements ShouldQueue
info(print_r($e->getMessage(), 1)); info(print_r($e->getMessage(), 1));
} }
if (config('ninja.log_pdf_html')) {
info($maker->getCompiledHTML());
}
if ($pdf) { if ($pdf) {
$instance = Storage::disk($this->disk)->put($file_path, $pdf); $instance = Storage::disk($this->disk)->put($file_path, $pdf);
} }
return $file_path; return $file_path;
} }
public function failed(\Exception $exception)
{
info("help!");
}
} }

View File

@ -101,7 +101,6 @@ class EmailEntity extends BaseMailerJob implements ShouldQueue
$this->setMailDriver(); $this->setMailDriver();
try { try {
Mail::to($this->invitation->contact->email, $this->invitation->contact->present()->name()) Mail::to($this->invitation->contact->email, $this->invitation->contact->present()->name())
->send( ->send(
new TemplateEmail( new TemplateEmail(

View File

@ -0,0 +1,243 @@
<?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\Import;
use App\Factory\ClientFactory;
use App\Factory\ProductFactory;
use App\Http\Requests\Client\StoreClientRequest;
use App\Http\Requests\Product\StoreProductRequest;
use App\Import\Transformers\ClientTransformer;
use App\Import\Transformers\ProductTransformer;
use App\Libraries\MultiDB;
use App\Models\Client;
use App\Models\Company;
use App\Models\Currency;
use App\Models\User;
use App\Repositories\ClientContactRepository;
use App\Repositories\ClientRepository;
use App\Repositories\ProductRepository;
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\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Validator;
use League\Csv\Reader;
use League\Csv\Statement;
class CSVImport implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $invoice;
public $company;
public $hash;
public $entity_type;
public $skip_header;
public $column_map;
public $import_array;
public $error_array;
public $maps;
public function __construct(array $request, Company $company)
{
$this->company = $company;
$this->hash = $request['hash'];
$this->entity_type = $request['entity_type'];
$this->skip_header = $request['skip_header'];
$this->column_map = $request['column_map'];
}
/**
* Execute the job.
*
*
* @return void
*/
public function handle()
{
MultiDB::setDb($this->company->db);
$this->company->owner()->setCompany($this->company);
Auth::login($this->company->owner(), true);
$this->buildMaps();
//sort the array by key
ksort($this->column_map);
$this->{"import".ucfirst($this->entity_type)}();
info(print_r($this->maps,1));
}
public function failed($exception)
{
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
private function importProduct()
{
info("importing products");
$product_repository = new ProductRepository();
$product_transformer = new ProductTransformer($this->maps);
$records = $this->getCsvData();
if ($this->skip_header)
array_shift($records);
foreach ($records as $record)
{
$keys = $this->column_map;
$values = array_intersect_key($record, $this->column_map);
$product_data = array_combine($keys, $values);
$product = $product_transformer->transform($product_data);
$validator = Validator::make($product, (new StoreProductRequest())->rules());
if ($validator->fails()) {
$this->error_array[] = ['product' => $product, 'error' => json_encode($validator->errors())];
} else {
$product = $product_repository->save($product, ProductFactory::create($this->company->id, $this->setUser($record)));
$product->save();
$this->maps['products'][] = $product->id;
}
}
}
//todo limit client imports for hosted version
private function importClient()
{
//clients
$records = $this->getCsvData();
$contact_repository = new ClientContactRepository();
$client_repository = new ClientRepository($contact_repository);
$client_transformer = new ClientTransformer($this->maps);
if ($this->skip_header)
array_shift($records);
foreach ($records as $record) {
$keys = $this->column_map;
$values = array_intersect_key($record, $this->column_map);
$client_data = array_combine($keys, $values);
$client = $client_transformer->transform($client_data);
$validator = Validator::make($client, (new StoreClientRequest())->rules());
if ($validator->fails()) {
$this->error_array[] = ['client' => $client, 'error' => json_encode($validator->errors())];
} else {
$client = $client_repository->save($client, ClientFactory::create($this->company->id, $this->setUser($record)));
if (array_key_exists('client.balance', $client_data)) {
$client->balance = preg_replace('/[^0-9,.]+/', '', $client_data['client.balance']);
}
if (array_key_exists('client.paid_to_date', $client_data)) {
$client->paid_to_date = preg_replace('/[^0-9,.]+/', '', $client_data['client.paid_to_date']);
}
$client->save();
$this->maps['clients'][] = $client->id;
}
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
private function buildMaps()
{
$this->maps['currencies'] = Currency::all();
$this->maps['users'] = $this->company->users;
$this->maps['company'] = $this->company;
$this->maps['clients'] = [];
$this->maps['products'] = [];
return $this;
}
private function setUser($record)
{
$user_key_exists = array_search('client.user_id', $this->column_map);
if ($user_key_exists) {
return $this->findUser($record[$user_key_exists]);
} else {
return $this->company->owner()->id;
}
}
private function findUser($user_hash)
{
$user = User::where('company_id', $this->company->id)
->where(\DB::raw('CONCAT_WS(" ", first_name, last_name)'), 'like', '%' . $user_hash . '%')
->first();
if ($user) {
return $user->id;
} else {
return $this->company->owner()->id;
}
}
private function getCsvData()
{
$base64_encoded_csv = Cache::get($this->hash);
$csv = base64_decode($base64_encoded_csv);
$csv = Reader::createFromString($csv);
$stmt = new Statement();
$data = iterator_to_array($stmt->process($csv));
if (count($data) > 0) {
$headers = $data[0];
// Remove Invoice Ninja headers
if (count($headers) && count($data) > 4) {
$firstCell = $headers[0];
if (strstr($firstCell, config('ninja.app_name'))) {
array_shift($data); // Invoice Ninja...
array_shift($data); // <blank line>
array_shift($data); // Enitty Type Header
}
}
}
return $data;
}
}

View File

@ -104,6 +104,7 @@ class Import implements ShouldQueue
* @var array * @var array
*/ */
private $available_imports = [ private $available_imports = [
'account',
'company', 'company',
'users', 'users',
'payment_terms', 'payment_terms',
@ -225,6 +226,13 @@ class Import implements ShouldQueue
}); });
} }
private function processAccount(array $data) :void
{
$account = $this->company->account;
$account->fill($data);
$account->save();
}
/** /**
* @param array $data * @param array $data
* @throws Exception * @throws Exception
@ -1048,13 +1056,11 @@ class Import implements ShouldQueue
$cgt = ClientGatewayToken::Create($modified); $cgt = ClientGatewayToken::Create($modified);
$old_user_key = array_key_exists('user_id', $resource) ?? $this->user->id; $key = "client_gateway_tokens_{$resource['id']}";
$this->ids['client_gateway_tokens'] = [ $this->ids['client_gateway_tokens'][$key] = [
"client_gateway_tokens_{$old_user_key}" => [
'old' => $resource['id'], 'old' => $resource['id'],
'new' => $cgt->id, 'new' => $cgt->id,
],
]; ];
} }
@ -1079,13 +1085,11 @@ class Import implements ShouldQueue
$task_status = TaskStatus::Create($modified); $task_status = TaskStatus::Create($modified);
$old_user_key = array_key_exists('user_id', $resource) ?? $this->user->id; $key = "task_statuses_{$resource['id']}";
$this->ids['task_statuses'] = [ $this->ids['task_statuses'][$key] = [
"task_statuses_{$old_user_key}" => [
'old' => $resource['id'], 'old' => $resource['id'],
'new' => $task_status->id, 'new' => $task_status->id,
],
]; ];
} }

View File

@ -35,12 +35,6 @@ class UploadAvatar implements ShouldQueue
public function handle() : ?string public function handle() : ?string
{ {
//make dir
// info("avatar dir creation => ". $this->directory);
// Storage::makeDirectory($this->directory, 0775);
$tmp_file = sha1(time()).'.png'; $tmp_file = sha1(time()).'.png';
$im = imagecreatefromstring(file_get_contents($this->file)); $im = imagecreatefromstring(file_get_contents($this->file));
@ -50,8 +44,8 @@ class UploadAvatar implements ShouldQueue
$path = Storage::putFile($this->directory, new File(sys_get_temp_dir().'/'.$tmp_file)); $path = Storage::putFile($this->directory, new File(sys_get_temp_dir().'/'.$tmp_file));
info($path); // info($path);
info($tmp_file); // info($tmp_file);
$url = Storage::url($path); $url = Storage::url($path);

View File

@ -13,8 +13,6 @@ namespace App\Models;
use App\DataMapper\ClientSettings; use App\DataMapper\ClientSettings;
use App\DataMapper\CompanySettings; use App\DataMapper\CompanySettings;
use App\Models\CompanyGateway;
use App\Models\Gateway;
use App\Models\Presenters\ClientPresenter; use App\Models\Presenters\ClientPresenter;
use App\Services\Client\ClientService; use App\Services\Client\ClientService;
use App\Utils\Traits\GeneratesCounter; use App\Utils\Traits\GeneratesCounter;

View File

@ -79,6 +79,8 @@ class Company extends BaseModel
'invoice_task_timelog', 'invoice_task_timelog',
'auto_start_tasks', 'auto_start_tasks',
'is_disabled', 'is_disabled',
'default_task_is_date_based',
'enable_product_discount',
]; ];
protected $hidden = [ protected $hidden = [

View File

@ -11,7 +11,6 @@
namespace App\Models; namespace App\Models;
use App\Models\GatewayType;
use App\PaymentDrivers\BasePaymentDriver; use App\PaymentDrivers\BasePaymentDriver;
use App\Utils\Number; use App\Utils\Number;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
@ -236,8 +235,9 @@ class CompanyGateway extends BaseModel
return false; return false;
} }
if($gateway_type_id == GatewayType::CUSTOM) if ($gateway_type_id == GatewayType::CUSTOM) {
$gateway_type_id = GatewayType::CREDIT_CARD; $gateway_type_id = GatewayType::CREDIT_CARD;
}
return $this->fees_and_limits->{$gateway_type_id}; return $this->fees_and_limits->{$gateway_type_id};
} }
@ -280,7 +280,7 @@ class CompanyGateway extends BaseModel
if ($fees_and_limits->fee_amount) { if ($fees_and_limits->fee_amount) {
$fee += $fees_and_limits->fee_amount; $fee += $fees_and_limits->fee_amount;
info("fee after adding fee amount = {$fee}"); // info("fee after adding fee amount = {$fee}");
} }
if ($fees_and_limits->fee_percent) { if ($fees_and_limits->fee_percent) {
@ -289,7 +289,7 @@ class CompanyGateway extends BaseModel
} else { } else {
$fee += round(($amount * $fees_and_limits->fee_percent / 100), 2); $fee += round(($amount * $fees_and_limits->fee_percent / 100), 2);
} }
info("fee after adding fee percent = {$fee}"); // info("fee after adding fee percent = {$fee}");
} }
/* Cap fee if we have to here. */ /* Cap fee if we have to here. */
@ -303,17 +303,17 @@ class CompanyGateway extends BaseModel
if ($include_taxes) { if ($include_taxes) {
if ($fees_and_limits->fee_tax_rate1) { if ($fees_and_limits->fee_tax_rate1) {
$fee += round(($pre_tax_fee * $fees_and_limits->fee_tax_rate1 / 100), 2); $fee += round(($pre_tax_fee * $fees_and_limits->fee_tax_rate1 / 100), 2);
info("fee after adding fee tax 1 = {$fee}"); // info("fee after adding fee tax 1 = {$fee}");
} }
if ($fees_and_limits->fee_tax_rate2) { if ($fees_and_limits->fee_tax_rate2) {
$fee += round(($pre_tax_fee * $fees_and_limits->fee_tax_rate2 / 100), 2); $fee += round(($pre_tax_fee * $fees_and_limits->fee_tax_rate2 / 100), 2);
info("fee after adding fee tax 2 = {$fee}"); // info("fee after adding fee tax 2 = {$fee}");
} }
if ($fees_and_limits->fee_tax_rate3) { if ($fees_and_limits->fee_tax_rate3) {
$fee += round(($pre_tax_fee * $fees_and_limits->fee_tax_rate3 / 100), 2); $fee += round(($pre_tax_fee * $fees_and_limits->fee_tax_rate3 / 100), 2);
info("fee after adding fee tax 3 = {$fee}"); // info("fee after adding fee tax 3 = {$fee}");
} }
} }

View File

@ -19,6 +19,8 @@ class Country extends StaticModel
'eea' => 'boolean', 'eea' => 'boolean',
'swap_postal_code' => 'boolean', 'swap_postal_code' => 'boolean',
'swap_currency_symbol' => 'boolean', 'swap_currency_symbol' => 'boolean',
'thousand_separator' => 'string',
'decimal_separator' => 'string',
'updated_at' => 'timestamp', 'updated_at' => 'timestamp',
'created_at' => 'timestamp', 'created_at' => 'timestamp',
'deleted_at' => 'timestamp', 'deleted_at' => 'timestamp',

View File

@ -17,7 +17,6 @@ use App\Events\Invoice\InvoiceWasUpdated;
use App\Helpers\Invoice\InvoiceSum; use App\Helpers\Invoice\InvoiceSum;
use App\Helpers\Invoice\InvoiceSumInclusive; use App\Helpers\Invoice\InvoiceSumInclusive;
use App\Jobs\Entity\CreateEntityPdf; use App\Jobs\Entity\CreateEntityPdf;
use App\Models\Activity;
use App\Models\Presenters\InvoicePresenter; use App\Models\Presenters\InvoicePresenter;
use App\Services\Invoice\InvoiceService; use App\Services\Invoice\InvoiceService;
use App\Services\Ledger\LedgerService; use App\Services\Ledger\LedgerService;
@ -438,7 +437,6 @@ class Invoice extends BaseModel
public function entityEmailEvent($invitation, $reminder_template) public function entityEmailEvent($invitation, $reminder_template)
{ {
switch ($reminder_template) { switch ($reminder_template) {
case 'invoice': case 'invoice':
event(new InvoiceWasEmailed($invitation, $invitation->company, Ninja::eventVars())); event(new InvoiceWasEmailed($invitation, $invitation->company, Ninja::eventVars()));

View File

@ -38,6 +38,7 @@ class Task extends BaseModel
'invoice_documents', 'invoice_documents',
'rate', 'rate',
'number', 'number',
'is_date_based',
]; ];
protected $touches = []; protected $touches = [];

View File

@ -460,4 +460,9 @@ class BaseDriver extends AbstractPaymentDriver
return $this; return $this;
} }
public function getCompanyGatewayId(): int
{
return $this->company_gateway->id;
}
} }

View File

@ -129,8 +129,8 @@ class CreditCard
$payment->{'3ds'} = ['enabled' => true]; $payment->{'3ds'} = ['enabled' => true];
$payment->{'success_url'} = route('payment_webhook', [ $payment->{'success_url'} = route('payment_webhook', [
'gateway_key' => $this->checkout->company_gateway->gateway_key,
'company_key' => $this->checkout->client->company->company_key, 'company_key' => $this->checkout->client->company->company_key,
'company_gateway_id' => $this->checkout->company_gateway->hashed_id,
'hash' => $this->checkout->payment_hash->hash, 'hash' => $this->checkout->payment_hash->hash,
]); ]);
} }

View File

@ -28,27 +28,15 @@ class CustomPaymentDriver extends BaseDriver
/** /**
* Returns the gateway types. * Returns the gateway types.
*/ */
public function gatewayTypes() :array public function gatewayTypes(): array
{ {
$types = [ $types = [
GatewayType::CREDIT_CARD, GatewayType::CUSTOM,
]; ];
return $types; return $types;
} }
public function authorize($payment_method)
{
}
public function purchase($amount, $return_client_response = false)
{
}
public function refund(Payment $payment, $amount, $return_client_response = false)
{
}
public function setPaymentMethod($payment_method_id) public function setPaymentMethod($payment_method_id)
{ {
$this->payment_method = $payment_method_id; $this->payment_method = $payment_method_id;
@ -56,13 +44,33 @@ class CustomPaymentDriver extends BaseDriver
return $this; return $this;
} }
/**
* View for displaying custom content of the driver.
*
* @param array $data
* @return mixed
*/
public function processPaymentView($data) public function processPaymentView($data)
{ {
return render('gateways.custom.landing_page', $data); $data['title'] = $this->company_gateway->getConfigField('name');
$data['instructions'] = $this->company_gateway->getConfigField('text');
$this->payment_hash->data = array_merge((array) $this->payment_hash->data, $data);
$this->payment_hash->save();
$data['gateway'] = $this;
return render('gateways.custom.payment', $data);
} }
/**
* Processing method for payment. Should never be reached with this driver.
*
* @return mixed
*/
public function processPaymentResponse($request) public function processPaymentResponse($request)
{ {
return redirect()->route('client.invoices');
} }
/** /**

View File

@ -60,13 +60,13 @@ class Alipay
$this->stripe->payment_hash->save(); $this->stripe->payment_hash->save();
if ($request->redirect_status == 'succeeded') { if ($request->redirect_status == 'succeeded') {
return $this->processSuccesfulRedirect(); return $this->processSuccesfulRedirect($request->source);
} }
return $this->processUnsuccesfulRedirect(); return $this->processUnsuccesfulRedirect();
} }
public function processSuccesfulRedirect() public function processSuccesfulRedirect(string $source)
{ {
$this->stripe->init(); $this->stripe->init();
@ -74,7 +74,7 @@ class Alipay
'payment_method' => $this->stripe->payment_hash->data->source, 'payment_method' => $this->stripe->payment_hash->data->source,
'payment_type' => PaymentType::ALIPAY, 'payment_type' => PaymentType::ALIPAY,
'amount' => $this->stripe->convertFromStripeAmount($this->stripe->payment_hash->data->stripe_amount, $this->stripe->client->currency()->precision), 'amount' => $this->stripe->convertFromStripeAmount($this->stripe->payment_hash->data->stripe_amount, $this->stripe->client->currency()->precision),
'transaction_reference' => ctrans('texts.n/a'), 'transaction_reference' => $source,
]; ];
$payment = $this->stripe->createPayment($data, \App\Models\Payment::STATUS_PENDING); $payment = $this->stripe->createPayment($data, \App\Models\Payment::STATUS_PENDING);

View File

@ -17,8 +17,6 @@ use App\Http\Requests\Payments\PaymentWebhookRequest;
use App\Http\Requests\Request; use App\Http\Requests\Request;
use App\Jobs\Util\SystemLogger; use App\Jobs\Util\SystemLogger;
use App\Models\ClientGatewayToken; use App\Models\ClientGatewayToken;
use App\Models\Company;
use App\Models\CompanyGateway;
use App\Models\GatewayType; use App\Models\GatewayType;
use App\Models\Payment; use App\Models\Payment;
use App\Models\PaymentHash; use App\Models\PaymentHash;
@ -343,9 +341,9 @@ class StripePaymentDriver extends BaseDriver
return $this->payment_method->processVerification($request, $payment_method); return $this->payment_method->processVerification($request, $payment_method);
} }
public function processWebhookRequest(PaymentWebhookRequest $request, Company $company, CompanyGateway $company_gateway, Payment $payment) public function processWebhookRequest(PaymentWebhookRequest $request, Payment $payment)
{ {
if ($request->type == 'source.chargable') { if ($request->type == 'source.chargeable') {
$payment->status_id = Payment::STATUS_COMPLETED; $payment->status_id = Payment::STATUS_COMPLETED;
$payment->save(); $payment->save();
} }

View File

@ -122,8 +122,8 @@ use App\Listeners\Invoice\InvoiceArchivedActivity;
use App\Listeners\Invoice\InvoiceCancelledActivity; use App\Listeners\Invoice\InvoiceCancelledActivity;
use App\Listeners\Invoice\InvoiceDeletedActivity; use App\Listeners\Invoice\InvoiceDeletedActivity;
use App\Listeners\Invoice\InvoiceEmailActivity; use App\Listeners\Invoice\InvoiceEmailActivity;
use App\Listeners\Invoice\InvoiceEmailFailedActivity;
use App\Listeners\Invoice\InvoiceEmailedNotification; use App\Listeners\Invoice\InvoiceEmailedNotification;
use App\Listeners\Invoice\InvoiceEmailFailedActivity;
use App\Listeners\Invoice\InvoicePaidActivity; use App\Listeners\Invoice\InvoicePaidActivity;
use App\Listeners\Invoice\InvoiceReminderEmailActivity; use App\Listeners\Invoice\InvoiceReminderEmailActivity;
use App\Listeners\Invoice\InvoiceRestoredActivity; use App\Listeners\Invoice\InvoiceRestoredActivity;
@ -131,8 +131,8 @@ use App\Listeners\Invoice\InvoiceReversedActivity;
use App\Listeners\Invoice\InvoiceViewedActivity; use App\Listeners\Invoice\InvoiceViewedActivity;
use App\Listeners\Invoice\UpdateInvoiceActivity; use App\Listeners\Invoice\UpdateInvoiceActivity;
use App\Listeners\Misc\InvitationViewedListener; use App\Listeners\Misc\InvitationViewedListener;
use App\Listeners\Payment\PaymentEmailFailureActivity;
use App\Listeners\Payment\PaymentEmailedActivity; use App\Listeners\Payment\PaymentEmailedActivity;
use App\Listeners\Payment\PaymentEmailFailureActivity;
use App\Listeners\Payment\PaymentNotification; use App\Listeners\Payment\PaymentNotification;
use App\Listeners\Payment\PaymentRestoredActivity; use App\Listeners\Payment\PaymentRestoredActivity;
use App\Listeners\Quote\QuoteApprovedActivity; use App\Listeners\Quote\QuoteApprovedActivity;

View File

@ -36,14 +36,13 @@ class ApplyPayment extends AbstractService
->ledger() ->ledger()
->updatePaymentBalance($this->payment_amount * -1); ->updatePaymentBalance($this->payment_amount * -1);
info("apply payment method - current client balance = {$this->payment->client->balance}"); // info("apply payment method - current client balance = {$this->payment->client->balance}");
info("reducing client balance by payment amount {$this->payment_amount}"); // info("reducing client balance by payment amount {$this->payment_amount}");
$this->invoice->client->service()->updateBalance($this->payment_amount * -1)->save(); $this->invoice->client->service()->updateBalance($this->payment_amount * -1)->save();
// $this->invoice->client->service()->updateBalance($this->payment_amount*-1)->updatePaidToDate($this->payment_amount)->save();
info("post client balance = {$this->invoice->client->balance}"); // info("post client balance = {$this->invoice->client->balance}");
/* Update Pivot Record amount */ /* Update Pivot Record amount */
$this->payment->invoices->each(function ($inv) { $this->payment->invoices->each(function ($inv) {
@ -55,7 +54,7 @@ class ApplyPayment extends AbstractService
$this->invoice->fresh('client'); $this->invoice->fresh('client');
info("1 end of apply payment method the client balance = {$this->invoice->client->balance}"); // info("1 end of apply payment method the client balance = {$this->invoice->client->balance}");
if ($this->invoice->hasPartial()) { if ($this->invoice->hasPartial()) {
//is partial and amount is exactly the partial amount //is partial and amount is exactly the partial amount
@ -71,11 +70,11 @@ class ApplyPayment extends AbstractService
} elseif ($this->payment_amount < $this->invoice->balance) { //partial invoice payment made } elseif ($this->payment_amount < $this->invoice->balance) { //partial invoice payment made
$this->invoice->service()->clearPartial()->setStatus(Invoice::STATUS_PARTIAL)->updateBalance($this->payment_amount * -1); $this->invoice->service()->clearPartial()->setStatus(Invoice::STATUS_PARTIAL)->updateBalance($this->payment_amount * -1);
} }
info("2 end of apply payment method the client balnace = {$this->invoice->client->balance}"); // info("2 end of apply payment method the client balnace = {$this->invoice->client->balance}");
$this->invoice->service()->applyNumber()->save(); $this->invoice->service()->applyNumber()->save();
info("3 end of apply payment method the client balnace = {$this->invoice->client->balance}"); // info("3 end of apply payment method the client balnace = {$this->invoice->client->balance}");
return $this->invoice; return $this->invoice;
} }

View File

@ -88,6 +88,10 @@ class GenerateDeliveryNote
$pdf = $this->makePdf(null, null, $maker->getCompiledHTML()); $pdf = $this->makePdf(null, null, $maker->getCompiledHTML());
if (config('ninja.log_pdf_html')) {
info($maker->getCompiledHTML());
}
Storage::disk($this->disk)->put($file_path, $pdf); Storage::disk($this->disk)->put($file_path, $pdf);
return $file_path; return $file_path;

View File

@ -94,6 +94,9 @@ class HandleRestore extends AbstractService
$new_invoice_number = substr($this->invoice->number, 0, $pos); $new_invoice_number = substr($this->invoice->number, 0, $pos);
if(strlen($new_invoice_number) == 0)
$new_invoice_number = null;
try { try {
$this->invoice->number = $new_invoice_number; $this->invoice->number = $new_invoice_number;
$this->invoice->save(); $this->invoice->save();

View File

@ -134,7 +134,7 @@ class Design extends BaseDesign
$elements = []; $elements = [];
foreach ($variables as $variable) { foreach ($variables as $variable) {
$elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false]; $elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'company_details-' . substr($variable, 1)]];
} }
return $elements; return $elements;
@ -147,7 +147,7 @@ class Design extends BaseDesign
$elements = []; $elements = [];
foreach ($variables as $variable) { foreach ($variables as $variable) {
$elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false]; $elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'company_address-' . substr($variable, 1)]];
} }
return $elements; return $elements;
@ -159,15 +159,19 @@ class Design extends BaseDesign
if ($this->type == 'delivery_note') { if ($this->type == 'delivery_note') {
$elements = [ $elements = [
['element' => 'p', 'content' => $this->entity->client->name, 'show_empty' => false], ['element' => 'p', 'content' => $this->entity->client->name, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-client.name']],
['element' => 'p', 'content' => $this->entity->client->shipping_address1, 'show_empty' => false], ['element' => 'p', 'content' => $this->entity->client->shipping_address1, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-client.shipping_address1']],
['element' => 'p', 'content' => $this->entity->client->shipping_address2, 'show_empty' => false], ['element' => 'p', 'content' => $this->entity->client->shipping_address2, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-client.shipping_address2']],
['element' => 'p', 'content' => "{$this->entity->client->shipping_city} {$this->entity->client->shipping_state} {$this->entity->client->shipping_postal_code}", 'show_empty' => false], ['element' => 'p', 'show_empty' => false, 'elements' => [
['element' => 'span', 'content' => "{$this->entity->client->shipping_city} ", 'properties' => ['ref' => 'delivery_note-client.shipping_city']],
['element' => 'span', 'content' => "{$this->entity->client->shipping_state} ", 'properties' => ['ref' => 'delivery_note-client.shipping_state']],
['element' => 'span', 'content' => "{$this->entity->client->shipping_postal_code} ", 'properties' => ['ref' => 'delivery_note-client.shipping_postal_code']],
]],
['element' => 'p', 'content' => optional($this->entity->client->shipping_country)->name, 'show_empty' => false], ['element' => 'p', 'content' => optional($this->entity->client->shipping_country)->name, 'show_empty' => false],
]; ];
if (!is_null($this->context['contact'])) { if (!is_null($this->context['contact'])) {
$elements[] = ['element' => 'p', 'content' => $this->context['contact']->email, 'show_empty' => false]; $elements[] = ['element' => 'p', 'content' => $this->context['contact']->email, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-contact.email']];
} }
return $elements; return $elements;
@ -176,7 +180,7 @@ class Design extends BaseDesign
$variables = $this->context['pdf_variables']['client_details']; $variables = $this->context['pdf_variables']['client_details'];
foreach ($variables as $variable) { foreach ($variables as $variable) {
$elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false]; $elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'client_details-' . substr($variable, 1)]];
} }
return $elements; return $elements;
@ -205,13 +209,13 @@ class Design extends BaseDesign
if (in_array($_variable, $_customs)) { if (in_array($_variable, $_customs)) {
$elements[] = ['element' => 'tr', 'elements' => [ $elements[] = ['element' => 'tr', 'elements' => [
['element' => 'th', 'content' => $variable . '_label'], ['element' => 'th', 'content' => $variable . '_label', 'properties' => ['data-ref' => 'entity_details-' . substr($variable, 1) . '_label']],
['element' => 'th', 'content' => $variable], ['element' => 'th', 'content' => $variable, 'properties' => ['data-ref' => 'entity_details-' . substr($variable, 1)]],
]]; ]];
} else { } else {
$elements[] = ['element' => 'tr', 'properties' => ['hidden' => $this->entityVariableCheck($variable)], 'elements' => [ $elements[] = ['element' => 'tr', 'properties' => ['hidden' => $this->entityVariableCheck($variable)], 'elements' => [
['element' => 'th', 'content' => $variable . '_label'], ['element' => 'th', 'content' => $variable . '_label', 'properties' => ['data-ref' => 'entity_details-' . substr($variable, 1) . '_label']],
['element' => 'th', 'content' => $variable], ['element' => 'th', 'content' => $variable, 'properties' => ['data-ref' => 'entity_details-' . substr($variable, 1)]],
]]; ]];
} }
} }
@ -225,16 +229,14 @@ class Design extends BaseDesign
return []; return [];
} }
$elements = [ return [
['element' => 'thead', 'elements' => [ ['element' => 'thead', 'elements' => [
['element' => 'th', 'content' => '$item_label'], ['element' => 'th', 'content' => '$item_label', 'properties' => ['data-ref' => 'delivery_note-item_label']],
['element' => 'th', 'content' => '$description_label'], ['element' => 'th', 'content' => '$description_label', 'properties' => ['data-ref' => 'delivery_note-description_label']],
['element' => 'th', 'content' => '$product.quantity_label'], ['element' => 'th', 'content' => '$product.quantity_label', 'properties' => ['data-ref' => 'delivery_note-product.quantity_label']],
]], ]],
['element' => 'tbody', 'elements' => $this->buildTableBody('delivery_note')], ['element' => 'tbody', 'elements' => $this->buildTableBody('delivery_note')],
]; ];
return $elements;
} }
/** /**
@ -306,7 +308,7 @@ class Design extends BaseDesign
if (array_key_exists($column, $aliases)) { if (array_key_exists($column, $aliases)) {
$elements[] = ['element' => 'th', 'content' => $aliases[$column] . '_label']; $elements[] = ['element' => 'th', 'content' => $aliases[$column] . '_label'];
} else { } else {
$elements[] = ['element' => 'th', 'content' => $column . '_label']; $elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($column, 1) . '-th']];
} }
} }
@ -333,9 +335,9 @@ class Design extends BaseDesign
foreach ($items as $row) { foreach ($items as $row) {
$element = ['element' => 'tr', 'elements' => []]; $element = ['element' => 'tr', 'elements' => []];
$element['elements'][] = ['element' => 'td', 'content' => $row['delivery_note.product_key']]; $element['elements'][] = ['element' => 'td', 'content' => $row['delivery_note.product_key'], 'properties' => ['data-ref' => 'delivery_note_table.product_key-td']];
$element['elements'][] = ['element' => 'td', 'content' => $row['delivery_note.notes']]; $element['elements'][] = ['element' => 'td', 'content' => $row['delivery_note.notes'], 'properties' => ['data-ref' => 'delivery_note_table.notes-td']];
$element['elements'][] = ['element' => 'td', 'content' => $row['delivery_note.quantity']]; $element['elements'][] = ['element' => 'td', 'content' => $row['delivery_note.quantity'], 'properties' => ['data-ref' => 'delivery_note_table.quantity-td']];
$elements[] = $element; $elements[] = $element;
} }
@ -378,21 +380,21 @@ class Design extends BaseDesign
// $task.quantity => $task.hours // $task.quantity => $task.hours
if ($cell == '$task.rate') { if ($cell == '$task.rate') {
$element['elements'][] = ['element' => 'td', 'content' => $row['$task.cost']]; $element['elements'][] = ['element' => 'td', 'content' => $row['$task.cost'], 'properties' => ['data-ref' => 'task_table-task.cost-td']];
} elseif ($cell == '$task.hours') { } elseif ($cell == '$task.hours') {
$element['elements'][] = ['element' => 'td', 'content' => $row['$task.quantity']]; $element['elements'][] = ['element' => 'td', 'content' => $row['$task.quantity'], 'properties' => ['data-ref' => 'task_table-task.hours-td']];
} elseif ($cell == '$task.description') { } elseif ($cell == '$task.description') {
$_element = ['element' => 'td', 'content' => '', 'elements' => [ $_element = ['element' => 'td', 'content' => '', 'elements' => [
['element' => 'span', 'content' => $row[$cell]], ['element' => 'span', 'content' => $row[$cell], 'properties' => ['data-ref' => 'task_table-task.description-td']],
]]; ]];
foreach ($this->getTaskTimeLogs($row) as $log) { foreach ($this->getTaskTimeLogs($row) as $log) {
$_element['elements'][] = ['element' => 'span', 'content' => $log, 'properties' => ['class' => 'task-duration']]; $_element['elements'][] = ['element' => 'span', 'content' => $log, 'properties' => ['class' => 'task-duration', 'data-ref' => 'task_table-task.duration']];
} }
$element['elements'][] = $_element; $element['elements'][] = $_element;
} else { } else {
$element['elements'][] = ['element' => 'td', 'content' => $row[$cell]]; $element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['data-ref' => "{$_type}_table-" . substr($cell, 1) . '-td']];
} }
} }
} }
@ -413,7 +415,7 @@ class Design extends BaseDesign
$elements = [ $elements = [
['element' => 'div', 'elements' => [ ['element' => 'div', 'elements' => [
['element' => 'span', 'content' => '$entity.public_notes', 'properties' => ['data-element' => 'total-table-public-notes-label']], ['element' => 'span', 'content' => '$entity.public_notes', 'properties' => ['data-ref' => 'total_table-public_notes-label', 'style' => 'text-align: left;']],
]], ]],
]; ];
@ -465,7 +467,7 @@ class Design extends BaseDesign
} else { } else {
$elements[] = ['element' => 'div', 'elements' => [ $elements[] = ['element' => 'div', 'elements' => [
['element' => 'span', 'content' => 'This is placeholder for the 3rd fraction of element.', 'properties' => ['style' => 'opacity: 0%']], // Placeholder for fraction of element (3fr) ['element' => 'span', 'content' => 'This is placeholder for the 3rd fraction of element.', 'properties' => ['style' => 'opacity: 0%']], // Placeholder for fraction of element (3fr)
['element' => 'span', 'content' => $variable . '_label'], ['element' => 'span', 'content' => $variable . '_label', 'properties' => ['data-ref' => 'totals_table-' . substr($variable, 1)]],
['element' => 'span', 'content' => $variable], ['element' => 'span', 'content' => $variable],
]]; ]];
} }

View File

@ -146,6 +146,8 @@ class CompanyTransformer extends EntityTransformer
'invoice_task_documents' => (bool) $company->invoice_task_documents, 'invoice_task_documents' => (bool) $company->invoice_task_documents,
'show_tasks_table' => (bool) $company->show_tasks_table, 'show_tasks_table' => (bool) $company->show_tasks_table,
'use_credits_payment' => 'always', //todo remove 'use_credits_payment' => 'always', //todo remove
'default_task_is_date_based' => (bool)$company->default_task_is_date_based,
'enable_product_discount' => (bool)$company->enable_product_discount,
]; ];
} }

View File

@ -59,7 +59,7 @@ class ExpenseTransformer extends EntityTransformer
'bank_id' => (string) $expense->bank_id ?: '', 'bank_id' => (string) $expense->bank_id ?: '',
'invoice_currency_id' => (string) $expense->invoice_currency_id ?: '', 'invoice_currency_id' => (string) $expense->invoice_currency_id ?: '',
'expense_currency_id' => '', //todo remove redundant in 5.0.25 'expense_currency_id' => '', //todo remove redundant in 5.0.25
'currency_id' => (string) $expense->expense_currency_id ?: '', 'currency_id' => (string) $expense->currency_id ?: '',
'category_id' => $this->encodePrimaryKey($expense->category_id), 'category_id' => $this->encodePrimaryKey($expense->category_id),
'payment_type_id' => (string) $expense->payment_type_id ?: '', 'payment_type_id' => (string) $expense->payment_type_id ?: '',
'recurring_expense_id' => (string) $expense->recurring_expense_id ?: '', 'recurring_expense_id' => (string) $expense->recurring_expense_id ?: '',

View File

@ -48,7 +48,7 @@ class ProjectTransformer extends EntityTransformer
'assigned_user_id' => (string) $this->encodePrimaryKey($project->assigned_user_id), 'assigned_user_id' => (string) $this->encodePrimaryKey($project->assigned_user_id),
'client_id' => (string) $this->encodePrimaryKey($project->client_id), 'client_id' => (string) $this->encodePrimaryKey($project->client_id),
'name' => $project->name ?: '', 'name' => $project->name ?: '',
'number' => $project->number, 'number' => $project->number ?: '',
'created_at' => (int) $project->created_at, 'created_at' => (int) $project->created_at,
'updated_at' => (int) $project->updated_at, 'updated_at' => (int) $project->updated_at,
'archived_at' => (int) $project->deleted_at, 'archived_at' => (int) $project->deleted_at,

View File

@ -66,6 +66,7 @@ class TaskTransformer extends EntityTransformer
'custom_value4' => $task->custom_value4 ?: '', 'custom_value4' => $task->custom_value4 ?: '',
'status_id' => $this->encodePrimaryKey($task->status_id) ?: '', 'status_id' => $this->encodePrimaryKey($task->status_id) ?: '',
'status_sort_order' => (int) $task->status_sort_order, 'status_sort_order' => (int) $task->status_sort_order,
'is_date_based' => (bool) $task->is_date_based,
]; ];
} }
} }

View File

@ -92,8 +92,7 @@ class HtmlEngine
} }
$data = []; $data = [];
$data['$global-margin'] = ['value' => 'm-8', 'label' => '']; $data['$global_margin'] = ['value' => config('ninja.experimental_pdf_engine') ? '0cm' : '1cm', 'label' => ''];
$data['$global-padding'] = ['value' => 'p-8', 'label' => ''];
$data['$tax'] = ['value' => '', 'label' => ctrans('texts.tax')]; $data['$tax'] = ['value' => '', 'label' => ctrans('texts.tax')];
$data['$app_url'] = ['value' => $this->generateAppUrl(), 'label' => '']; $data['$app_url'] = ['value' => $this->generateAppUrl(), 'label' => ''];
$data['$from'] = ['value' => '', 'label' => ctrans('texts.from')]; $data['$from'] = ['value' => '', 'label' => ctrans('texts.from')];
@ -184,7 +183,7 @@ class HtmlEngine
$data['$invoice.custom2'] = ['value' => $this->formatCustomFieldValue('invoice2', $this->entity->custom_value2) ?: '&nbsp;', 'label' => $this->makeCustomField('invoice2')]; $data['$invoice.custom2'] = ['value' => $this->formatCustomFieldValue('invoice2', $this->entity->custom_value2) ?: '&nbsp;', 'label' => $this->makeCustomField('invoice2')];
$data['$invoice.custom3'] = ['value' => $this->formatCustomFieldValue('invoice3', $this->entity->custom_value3) ?: '&nbsp;', 'label' => $this->makeCustomField('invoice3')]; $data['$invoice.custom3'] = ['value' => $this->formatCustomFieldValue('invoice3', $this->entity->custom_value3) ?: '&nbsp;', 'label' => $this->makeCustomField('invoice3')];
$data['$invoice.custom4'] = ['value' => $this->formatCustomFieldValue('invoice4', $this->entity->custom_value4) ?: '&nbsp;', 'label' => $this->makeCustomField('invoice4')]; $data['$invoice.custom4'] = ['value' => $this->formatCustomFieldValue('invoice4', $this->entity->custom_value4) ?: '&nbsp;', 'label' => $this->makeCustomField('invoice4')];
$data['$invoice.public_notes'] = ['value' => $this->entity->public_notes ?: '&nbsp;', 'label' => ctrans('texts.public_notes')]; $data['$invoice.public_notes'] = ['value' => nl2br($this->entity->public_notes) ?: '&nbsp;', 'label' => ctrans('texts.public_notes')];
$data['$entity.public_notes'] = &$data['$invoice.public_notes']; $data['$entity.public_notes'] = &$data['$invoice.public_notes'];
$data['$entity_issued_to'] = ['value' => '', 'label' => ctrans("texts.{$this->entity_string}_issued_to")]; $data['$entity_issued_to'] = ['value' => '', 'label' => ctrans("texts.{$this->entity_string}_issued_to")];

View File

@ -28,7 +28,7 @@ class SystemHealth
'gd', 'gd',
'curl', 'curl',
'zip', 'zip',
'gmp', // 'gmp',
'openssl', 'openssl',
'mbstring', 'mbstring',
'xml', 'xml',
@ -78,9 +78,29 @@ class SystemHealth
'node_status' => self::checkNode(), 'node_status' => self::checkNode(),
'cache_enabled' => self::checkConfigCache(), 'cache_enabled' => self::checkConfigCache(),
'phantom_enabled' => (bool) config('ninja.phantomjs_pdf_generation'), 'phantom_enabled' => (bool) config('ninja.phantomjs_pdf_generation'),
'exec' => (bool) self::checkExecWorks(),
'open_basedir' => (bool) self::checkOpenBaseDir(),
]; ];
} }
public static function checkOpenBaseDir()
{
if (strlen(ini_get('open_basedir') == 0)) {
return true;
}
return false;
}
public static function checkExecWorks()
{
if (function_exists('exec')) {
return true;
}
return false;
}
public static function checkConfigCache() public static function checkConfigCache()
{ {
if (env('APP_URL')) { if (env('APP_URL')) {
@ -100,7 +120,6 @@ class SystemHealth
} }
return 'Node not found.'; return 'Node not found.';
} catch (Exception $e) { } catch (Exception $e) {
return 'Node not found.'; return 'Node not found.';
} }
@ -116,7 +135,6 @@ class SystemHealth
} }
return 'NPM not found'; return 'NPM not found';
} catch (Exception $e) { } catch (Exception $e) {
return 'NPM not found'; return 'NPM not found';
} }

View File

@ -63,6 +63,15 @@ trait ClientGroupSettingsSaver
$entity_settings->{$key} = $value; $entity_settings->{$key} = $value;
} }
//this pass will handle any null values that are in the translations
foreach ($settings->translations as $key => $value) {
if (is_null($settings->translations[$key])) {
$settings->translations[$key] = '';
}
}
$entity_settings->translations = $settings->translations;
$entity->settings = $entity_settings; $entity->settings = $entity_settings;
$entity->save(); $entity->save();

View File

@ -58,6 +58,15 @@ trait CompanySettingsSaver
} }
} }
//this pass will handle any null values that are in the translations
foreach ($settings->translations as $key => $value) {
if (is_null($settings->translations[$key])) {
$settings->translations[$key] = '';
}
}
$company_settings->translations = $settings->translations;
$entity->settings = $company_settings; $entity->settings = $company_settings;
$entity->save(); $entity->save();

View File

@ -209,7 +209,4 @@ trait MakesReminders
return null; return null;
} }
} }
} }

View File

@ -11,6 +11,7 @@
namespace App\Utils\Traits\Pdf; namespace App\Utils\Traits\Pdf;
//use Beganovich\ChromiumPdf\ChromiumPdf;
use Spatie\Browsershot\Browsershot; use Spatie\Browsershot\Browsershot;
trait PdfMaker trait PdfMaker
@ -26,6 +27,15 @@ trait PdfMaker
*/ */
public function makePdf($header, $footer, $html) public function makePdf($header, $footer, $html)
{ {
if (config('ninja.experimental_pdf_engine')) {
$pdf = new ChromiumPdf();
return $pdf
->setChromiumPath(config('ninja.experimental_pdf_engine_chromium_path'))
->setHtml($html)
->generate();
}
$browser = Browsershot::html($html); $browser = Browsershot::html($html);
if (config('ninja.system.node_path')) { if (config('ninja.system.node_path')) {
@ -44,34 +54,3 @@ trait PdfMaker
->pdf(); ->pdf();
} }
} }
// if($header && $footer){
// $browser = Browsershot::html($html)
// ->headerHtml($header)
// ->footerHtml($footer);
// }
// elseif($header){
// $browser = Browsershot::html($html)
// ->headerHtml($header);
// }
// else if($footer){
// $browser = Browsershot::html($html)
// ->footerHtml($footer);
// }
// else {
// $browser = Browsershot::html($html);
// }
//
//
// // return Browsershot::html($html)
// //->showBrowserHeaderAndFooter()
// //->headerHtml($header)
// //->footerHtml($footer)
// ->deviceScaleFactor(1)
// ->showBackground()
// ->waitUntilNetworkIdle(true) ->pdf();
// //->margins(10,10,10,10)
// //->savePdf('test.pdf');
//
// $browser->format('A4');
// $browser->landscape();

View File

@ -10,7 +10,8 @@
"CRM", "CRM",
"Credit card billing", "Credit card billing",
"projects", "projects",
"tasks" "tasks",
"freelancer"
], ],
"license": "Attribution Assurance License", "license": "Attribution Assurance License",
"authors": [ "authors": [
@ -27,8 +28,10 @@
"require": { "require": {
"php": "^7.3|^7.4", "php": "^7.3|^7.4",
"ext-json": "*", "ext-json": "*",
"ext-libxml": "*",
"asgrim/ofxparser": "^1.2", "asgrim/ofxparser": "^1.2",
"authorizenet/authorizenet": "^2.0", "authorizenet/authorizenet": "^2.0",
"beganovich/chromium-pdf": "dev-master",
"checkout/checkout-sdk-php": "^1.0", "checkout/checkout-sdk-php": "^1.0",
"cleverit/ubl_invoice": "^1.3", "cleverit/ubl_invoice": "^1.3",
"composer/composer": "^2", "composer/composer": "^2",
@ -47,10 +50,11 @@
"laravel/socialite": "^5", "laravel/socialite": "^5",
"laravel/tinker": "^2.0", "laravel/tinker": "^2.0",
"laravel/ui": "^3.0", "laravel/ui": "^3.0",
"league/csv": "^9.6",
"league/flysystem-aws-s3-v3": "~1.0", "league/flysystem-aws-s3-v3": "~1.0",
"league/flysystem-cached-adapter": "^1.1", "league/flysystem-cached-adapter": "^1.1",
"league/fractal": "^0.17.0", "league/fractal": "^0.17.0",
"league/omnipay": "^3.0", "league/omnipay": "^3",
"livewire/livewire": "^2.0", "livewire/livewire": "^2.0",
"maennchen/zipstream-php": "^1.2", "maennchen/zipstream-php": "^1.2",
"nwidart/laravel-modules": "^8.0", "nwidart/laravel-modules": "^8.0",
@ -61,7 +65,8 @@
"stripe/stripe-php": "^7.50", "stripe/stripe-php": "^7.50",
"turbo124/beacon": "^1", "turbo124/beacon": "^1",
"turbo124/laravel-gmail": "^5.0", "turbo124/laravel-gmail": "^5.0",
"webpatser/laravel-countries": "dev-master#75992ad" "webpatser/laravel-countries": "dev-master#75992ad",
"ext-dom": "*"
}, },
"require-dev": { "require-dev": {
"anahkiasen/former": "^4.2", "anahkiasen/former": "^4.2",

450
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -7,12 +7,12 @@ return [
'production' => env('NINJA_PROD', false), 'production' => env('NINJA_PROD', false),
'license' => env('NINJA_LICENSE', ''), 'license' => env('NINJA_LICENSE', ''),
'version_url' => 'https://raw.githubusercontent.com/invoiceninja/invoiceninja/v5-stable/VERSION.txt', 'version_url' => 'https://raw.githubusercontent.com/invoiceninja/invoiceninja/v5-stable/VERSION.txt',
'app_name' => env('APP_NAME'), 'app_name' => env('APP_NAME','Invoice Ninja'),
'app_env' => env('APP_ENV', 'selfhosted'), 'app_env' => env('APP_ENV', 'selfhosted'),
'require_https' => env('REQUIRE_HTTPS', true), 'require_https' => env('REQUIRE_HTTPS', true),
'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_url' => rtrim(env('APP_URL', ''), '/'),
'app_domain' => env('APP_DOMAIN', ''), 'app_domain' => env('APP_DOMAIN', ''),
'app_version' => '5.0.35', 'app_version' => '5.0.36',
'minimum_client_version' => '5.0.16', 'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1', 'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', false), 'api_secret' => env('API_SECRET', false),
@ -135,4 +135,7 @@ return [
'designs' => [ 'designs' => [
'base_path' => resource_path('views/pdf-designs/'), 'base_path' => resource_path('views/pdf-designs/'),
], ],
'experimental_pdf_engine' => env('EXPERIMENTAL_PDF_ENGINE', false),
'experimental_pdf_engine_chromium_path' => env('EXPERIMENTAL_PDF_ENGINE_CHROMIUM_PATH', null),
'log_pdf_html' => env('LOG_PDF_HTML', false),
]; ];

View File

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class TaskFields extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('tasks', function (Blueprint $table) {
$table->boolean('is_date_based')->default(false);
});
Schema::table('companies', function (Blueprint $table) {
$table->boolean('default_task_is_date_based')->default(false);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddEnableProductDiscountFieldToCompaniesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('companies', function (Blueprint $table) {
$table->boolean('enable_product_discount')->default(false);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('companies', function (Blueprint $table) {
//
});
}
}

0
public/.htaccess Normal file → Executable file
View File

0
public/assets/AssetManifest.json Normal file → Executable file
View File

0
public/assets/FontManifest.json Normal file → Executable file
View File

0
public/assets/LICENSE Normal file → Executable file
View File

49
public/assets/NOTICES Normal file → Executable file
View File

@ -6032,6 +6032,31 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
draggable_scrollbar
The MIT License (MIT)
Copyright (c) 2018 Draggable Scrollbar Authors
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
engine engine
gpu gpu
@ -6226,6 +6251,30 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
file_picker
MIT License
Copyright (c) 2018 Miguel Ruivo
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
--------------------------------------------------------------------------------
files files
Copyright (c) 1998, 1999 Thai Open Source Software Center Ltd Copyright (c) 1998, 1999 Thai Open Source Software Center Ltd

0
public/assets/assets/images/google-icon.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

0
public/assets/assets/images/logo.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

0
public/assets/assets/images/payment_types/ach.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

0
public/assets/assets/images/payment_types/amex.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

0
public/assets/assets/images/payment_types/discover.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

0
public/assets/assets/images/payment_types/jcb.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

0
public/assets/assets/images/payment_types/laser.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

0
public/assets/assets/images/payment_types/maestro.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

0
public/assets/assets/images/payment_types/other.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

0
public/assets/assets/images/payment_types/paypal.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

0
public/assets/assets/images/payment_types/solo.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

0
public/assets/assets/images/payment_types/switch.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 938 B

After

Width:  |  Height:  |  Size: 938 B

0
public/assets/assets/images/payment_types/unionpay.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

0
public/assets/assets/images/payment_types/visa.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

0
public/assets/fonts/MaterialIcons-Regular.otf Normal file → Executable file
View File

0
public/assets/fonts/MaterialIcons-Regular.ttf Normal file → Executable file
View File

0
public/assets/fonts/Roboto-Regular.ttf Normal file → Executable file
View File

View File

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

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