Merge branch 'v5-develop' into v5-2805-tracking-campaign-source

This commit is contained in:
Benjamin Beganović 2021-06-01 13:38:36 +02:00 committed by GitHub
commit bd97b916e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
69 changed files with 102665 additions and 100364 deletions

View File

@ -1 +1 @@
5.1.65
5.1.70

View File

@ -12,12 +12,12 @@
namespace App\Console\Commands;
use App\Jobs\Ninja\SendReminders;
use App\Jobs\Util\WebHookHandler;
use App\Libraries\MultiDB;
use App\Models\Invoice;
use App\Models\Quote;
use App\Models\Webhook;
use Illuminate\Console\Command;
use App\Jobs\Util\WebhookHandler;
class SendRemindersCron extends Command
{
@ -54,8 +54,8 @@ class SendRemindersCron extends Command
{
SendReminders::dispatchNow();
$this->webHookOverdueInvoices();
$this->webHookExpiredQuotes();
$this->webHookOverdueInvoices();
$this->webHookExpiredQuotes();
}
private function webHookOverdueInvoices()
@ -90,6 +90,7 @@ class SendRemindersCron extends Command
$invoices->each(function ($invoice) {
WebHookHandler::dispatch(Webhook::EVENT_LATE_INVOICE, $invoice, $invoice->company);
});
$quotes = Quote::where('is_deleted', 0)

View File

@ -68,7 +68,7 @@ class CompanySettings extends BaseSettings
public $inclusive_taxes = false; //@implemented
public $quote_footer = ''; //@implmented
public $translations; //@TODO not used anywhere
public $translations;
public $counter_number_applied = 'when_saved'; // when_saved , when_sent //@implemented
public $quote_number_applied = 'when_saved'; // when_saved , when_sent //@implemented
@ -245,8 +245,8 @@ class CompanySettings extends BaseSettings
public $hide_paid_to_date = false; //@TODO where?
public $embed_documents = false; //@TODO where?
public $all_pages_header = false; //@implemented
public $all_pages_footer = false; //@implemented
public $all_pages_header = false; //@deprecated 31-05-2021
public $all_pages_footer = false; //@deprecated 31-05-2021
public $pdf_variables = ''; //@implemented
public $portal_custom_head = ''; //@TODO @BEN

View File

@ -132,8 +132,11 @@ class ForgotPasswordController extends Controller
: $this->sendResetLinkFailedResponse($request, $response);
}
public function showLinkRequestForm()
public function showLinkRequestForm(Request $request)
{
return $this->render('auth.passwords.request', ['root' => 'themes']);
$account_id = $request->get('account_id');
$account = Account::find($account_id);
return $this->render('auth.passwords.request', ['root' => 'themes', 'account' => $account]);
}
}

View File

@ -38,6 +38,7 @@ use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
use Laravel\Socialite\Facades\Socialite;
use PragmaRX\Google2FA\Google2FA;
use Turbo124\Beacon\Facades\LightLogs;
@ -489,49 +490,37 @@ class LoginController extends BaseController
if (request()->has('code')) {
return $this->handleProviderCallback($provider);
} else {
return Socialite::driver($provider)->scopes($scopes)->redirect();
return Socialite::driver($provider)->with(['redirect_uri' => config('ninja.app_url')."/auth/google"])->scopes($scopes)->redirect();
}
}
public function handleProviderCallback(string $provider)
{
$socialite_user = Socialite::driver($provider)
->stateless()
->user();
// if($user = OAuth::handleAuth($socialite_user, $provider))
// {
// Auth::login($user, true);
if($user = OAuth::handleAuth($socialite_user, $provider))
{
// return redirect($this->redirectTo);
// }
// else if(MultiDB::checkUserEmailExists($socialite_user->getEmail()))
// {
// Session::flash('error', 'User exists in system, but not with this authentication method'); //todo add translations
nlog('found user and updating their user record');
// return view('auth.login');
// }
// else {
// //todo
// $name = OAuth::splitName($socialite_user->getName());
$update_user = [
'first_name' => $name[0],
'last_name' => $name[1],
'password' => '',
'email' => $socialite_user->getEmail(),
'oauth_user_id' => $socialite_user->getId(),
'oauth_provider_id' => $provider,
'oauth_user_token' => $socialite_user->refreshToken,
];
// $new_account = [
// 'first_name' => $name[0],
// 'last_name' => $name[1],
// 'password' => '',
// 'email' => $socialite_user->getEmail(),
// 'oauth_user_id' => $socialite_user->getId(),
// 'oauth_provider_id' => $provider
// ];
$user->update($update_user);
// $account = CreateAccount::dispatchNow($new_account);
// Auth::login($account->default_company->owner(), true);
// $cookie = cookie('db', $account->default_company->db);
// return redirect($this->redirectTo)->withCookie($cookie);
// }
}
else {
nlog("user not found for oauth");
}
return redirect('/#/');
}
}

View File

@ -12,6 +12,7 @@
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\Account;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
@ -52,7 +53,10 @@ class ResetPasswordController extends Controller
public function showResetForm(Request $request, $token = null)
{
return $this->render('auth.passwords.reset', ['root' => 'themes', 'token' => $token]);
$account_id = $request->get('account_id');
$account = Account::find($account_id);
return $this->render('auth.passwords.reset', ['root' => 'themes', 'token' => $token, 'account' => $account]);
}
/**

View File

@ -15,6 +15,7 @@ use App\Events\Client\ClientWasCreated;
use App\Events\Client\ClientWasUpdated;
use App\Factory\ClientFactory;
use App\Filters\ClientFilters;
use App\Http\Requests\Client\AdjustClientLedgerRequest;
use App\Http\Requests\Client\CreateClientRequest;
use App\Http\Requests\Client\DestroyClientRequest;
use App\Http\Requests\Client\EditClientRequest;
@ -585,4 +586,64 @@ class ClientController extends BaseController
}
/**
* Update the specified resource in storage.
*
* @param UploadClientRequest $request
* @param Client $client
* @return Response
*
*
*
* @OA\Put(
* path="/api/v1/clients/{id}/adjust_ledger",
* operationId="adjustLedger",
* tags={"clients"},
* summary="Adjust the client ledger to rebalance",
* description="Adjust the client ledger to rebalance",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(
* name="id",
* in="path",
* description="The Client Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Returns the client object",
* @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\JsonContent(ref="#/components/schemas/Client"),
* ),
* @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"),
* ),
* )
*/
//@deprecated - not available
public function adjustLedger(AdjustClientLedgerRequest $request, Client $client)
{
// $adjustment = $request->input('adjustment');
// $notes = $request->input('notes');
// $client->service()->updateBalance
}
}

View File

@ -0,0 +1,42 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Controllers\Gateways;
use App\Http\Controllers\Controller;
use App\Http\Requests\Gateways\Checkout3ds\Checkout3dsRequest;
class Checkout3dsController extends Controller
{
public function index(Checkout3dsRequest $request, string $company_key, string $company_gateway_id, string $hash)
{
if (!$request->getCompany()) {
return response()->json(['message' => 'Company record not found.', 'company_key' => $company_key]);
}
if (!$request->getCompanyGateway()) {
return response()->json(['message' => 'Company gateway record not found.', 'company_gateway_id' => $company_gateway_id]);
}
if (!$request->getPaymentHash()) {
return response()->json(['message' => 'Hash record not found.', 'hash' => $hash]);
}
if (!$request->getClient()) {
return response()->json(['message' => 'Client record not found.']);
}
return $request->getCompanyGateway()
->driver($request->getClient())
->process3dsConfirmation($request);
}
}

View File

@ -0,0 +1,96 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Controllers;
use App\Http\Requests\Import\ImportJsonRequest;
use App\Jobs\Company\CompanyExport;
use App\Jobs\Company\CompanyImport;
use App\Utils\Traits\MakesHash;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
class ImportJsonController extends BaseController
{
use MakesHash;
public function __construct()
{
parent::__construct();
}
/**
* @OA\Post(
* path="/api/v1/import_json",
* operationId="getImportJson",
* tags={"import"},
* summary="Import data from the system",
* description="Import data from the system",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Response(
* response=200,
* description="success",
* @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 index(ImportJsonRequest $request)
{
$import_file = $request->file('files');
$contents = $this->unzipFile($import_file->getPathname());
$hash = Str::random(32);
Cache::put( $hash, base64_encode( $contents ), 3600 );
CompanyImport::dispatch(auth()->user()->getCompany(), auth()->user(), $hash, $request->all());
return response()->json(['message' => 'Processing'], 200);
}
private function unzipFile($file_contents)
{
$zip = new ZipArchive();
$archive = $zip->open($file_contents);
$filename = pathinfo($file_contents, PATHINFO_FILENAME);
$zip->extractTo(public_path("storage/backups/{$filename}"));
$zip->close();
$file_location = public_path("storage/backups/$filename/backup.json");
if (! file_exists($file_location))
throw new NonExistingMigrationFile('Backup file does not exist, or is corrupted.');
$data = json_decode(file_get_contents($file_location));
unlink($file_contents);
unlink($file_location);
return $data
}
}

View File

@ -845,13 +845,11 @@ class InvoiceController extends BaseController
*/
public function deliveryNote(ShowInvoiceRequest $request, Invoice $invoice)
{
$file = $invoice->service()->getInvoiceDeliveryNote($invoice, $invoice->invitations->first()->contact);
try {
return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
} catch (\Exception $e) {
return response(['message' => 'Oops, something went wrong. Make sure you have symlink to storage/ in public/ directory.'], 500);
}
return response()->download($file, basename($file), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true);
}
/**

View File

@ -99,9 +99,10 @@ class PreviewController extends BaseController
$entity_obj->load('client');
App::setLocale($entity_obj->client->primary_contact()->preferredLocale());
App::forgetInstance('translator');
Lang::replace(Ninja::transformTranslations($entity_obj->client->getMergedSettings()));
$t = app('translator');
App::setLocale($entity_obj->client->primary_contact()->preferredLocale());
$t->replace(Ninja::transformTranslations($entity_obj->client->getMergedSettings()));
$html = new HtmlEngine($entity_obj->invitations()->first());
@ -151,7 +152,8 @@ class PreviewController extends BaseController
private function blankEntity()
{
App::forgetInstance('translator');
Lang::replace(Ninja::transformTranslations(auth()->user()->company()->settings));
$t = app('translator');
$t->replace(Ninja::transformTranslations(auth()->user()->company()->settings));
DB::beginTransaction();

View File

@ -163,7 +163,7 @@ class SetupController extends Controller
/* Create the first account. */
if (Account::count() == 0) {
CreateAccount::dispatchNow($request->all());
CreateAccount::dispatchNow($request->all(), $request->getClientIp());
}
VersionCheck::dispatchNow();

View File

@ -112,6 +112,7 @@ class Kernel extends HttpKernel
VerifyCsrfToken::class,
SubstituteBindings::class,
QueryLogging::class,
Cors::class,
],
'shop' => [
'throttle:120,1',

View File

@ -16,7 +16,7 @@ class Cors
// ALLOW OPTIONS METHOD
$headers = [
'Access-Control-Allow-Methods'=> 'POST, GET, OPTIONS, PUT, DELETE',
'Access-Control-Allow-Headers'=> 'X-API-COMPANY-KEY,X-CLIENT-VERSION,X-API-SECRET,X-API-TOKEN,X-API-PASSWORD,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range',
'Access-Control-Allow-Headers'=> 'X-API-COMPANY-KEY,X-CLIENT-VERSION,X-API-SECRET,X-API-TOKEN,X-API-PASSWORD,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,X-CSRF-TOKEN,X-LIVEWIRE',
];
return Response::make('OK', 200, $headers);
@ -27,7 +27,7 @@ class Cors
$response->headers->set('Access-Control-Allow-Origin', '*');
$response->headers->set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
$response->headers->set('Access-Control-Allow-Headers', 'X-API-COMPANY-KEY,X-API-SECRET,X-API-TOKEN,X-API-PASSWORD,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range');
$response->headers->set('Access-Control-Expose-Headers', 'X-APP-VERSION,X-MINIMUM-CLIENT-VERSION');
$response->headers->set('Access-Control-Expose-Headers', 'X-APP-VERSION,X-MINIMUM-CLIENT-VERSION,X-CSRF-TOKEN,X-LIVEWIRE');
$response->headers->set('X-APP-VERSION', config('ninja.app_version'));
$response->headers->set('X-MINIMUM-CLIENT-VERSION', config('ninja.minimum_client_version'));

View File

@ -0,0 +1,55 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Requests\Client;
use App\Http\Requests\Request;
use App\Utils\Traits\MakesHash;
use Illuminate\Validation\Rule;
class AdjustClientLedgerRequest extends Request
{
use MakesHash;
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->can('edit', $this->client);
}
public function rules()
{
/* Ensure we have a client name, and that all emails are unique*/
$rules = [];
return $rules;
}
public function messages()
{
return [
];
}
protected function prepareForValidation()
{
$input = $this->all();
$this->replace($input);
}
}

View File

@ -0,0 +1,57 @@
<?php
namespace App\Http\Requests\Gateways\Checkout3ds;
use App\Models\Client;
use App\Models\Company;
use App\Models\CompanyGateway;
use App\Models\PaymentHash;
use App\Utils\Traits\MakesHash;
use Illuminate\Foundation\Http\FormRequest;
class Checkout3dsRequest extends FormRequest
{
use MakesHash;
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
//
];
}
public function getCompany()
{
return Company::where('company_key', $this->company_key)->first();
}
public function getCompanyGateway()
{
return CompanyGateway::find($this->decodePrimaryKey($this->company_gateway_id));
}
public function getPaymentHash()
{
return PaymentHash::where('hash', $this->hash)->first();
}
public function getClient()
{
return Client::find($this->getPaymentHash()->data->client_id);
}
}

View File

@ -0,0 +1,39 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Requests\Import;
use App\Http\Requests\Request;
class ImportJsonRequest 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 [
// 'import_type' => 'required',
// 'files' => 'required_without:hash|array|min:1|max:6',
// 'hash' => 'nullable|string',
// 'column_map' => 'required_with:hash|array',
// 'skip_header' => 'required_with:hash|boolean',
// 'files.*' => 'file|mimes:csv,txt',
];
}
}

View File

@ -100,14 +100,15 @@ class PaymentWebhookRequest extends Request
/**
* Resolve client from payment hash.
*
* @return null|\App\Models\Client
* @return null|\App\Models\Client|bool
*/
public function getClient()
{
$hash = $this->getPaymentHash();
if($hash)
if($hash) {
return Client::find($hash->data->client_id)->firstOrFail();
}
return false;
}

View File

@ -58,7 +58,7 @@ class AttachableUser implements Rule
if(!$user)
return true;
$user_already_attached = CompanyUser::query()
$user_already_attached = CompanyUser::query()
->where('user_id', $user->id)
->where('account_id',$user->account_id)
->where('company_id', auth()->user()->company()->id)

View File

@ -13,6 +13,7 @@ namespace App\Http\ViewComposers;
use App\Utils\Ninja;
use App\Utils\TranslationHelper;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Lang;
use Illuminate\View\View;
@ -34,7 +35,9 @@ class PortalComposer
$view->with($this->portalData());
if (auth()->user()) {
Lang::replace(Ninja::transformTranslations(auth()->user()->client->getMergedSettings()));
App::forgetInstance('translator');
$t = app('translator');
$t->replace(Ninja::transformTranslations(auth()->user()->client->getMergedSettings()));
}
}

View File

@ -25,6 +25,7 @@ use App\Models\RecurringInvoice;
use App\Models\RecurringInvoiceInvitation;
use App\Models\User;
use App\Models\VendorContact;
use App\Utils\Ninja;
use App\Utils\Traits\MakesHash;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
@ -121,7 +122,7 @@ class CompanyExport implements ShouldQueue
$user->account_id = $this->encodePrimaryKey($user->account_id);
// $user->id = $this->encodePrimaryKey($user->id);
return $user;
return $user->makeVisible(['id']);
})->all();
@ -205,26 +206,27 @@ class CompanyExport implements ShouldQueue
$credit = $this->transformBasicEntities($credit);
$credit = $this->transformArrayOfKeys($credit, ['recurring_id','client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id','invoice_id']);
return $credit;
return $credit->makeVisible(['id']);
})->all();
$this->export_data['credit_invitations'] = CreditInvitation::where('company_id', $this->company->id)->withTrashed()->cursor()->map(function ($credit){
$credit = $this->transformArrayOfKeys($credit, ['company_id', 'user_id', 'client_contact_id', 'recurring_invoice_id']);
$credit = $this->transformArrayOfKeys($credit, ['company_id', 'user_id', 'client_contact_id', 'credit_id']);
return $credit;
return $credit->makeVisible(['id']);
})->all();
$this->export_data['designs'] = $this->company->user_designs->makeHidden(['id'])->all();
$this->export_data['documents'] = $this->company->documents->map(function ($document){
$this->export_data['documents'] = $this->company->all_documents->map(function ($document){
$document = $this->transformArrayOfKeys($document, ['user_id', 'assigned_user_id', 'company_id', 'project_id', 'vendor_id']);
$document = $this->transformArrayOfKeys($document, ['user_id', 'assigned_user_id', 'company_id', 'project_id', 'vendor_id','documentable_id']);
$document->hashed_id = $this->encodePrimaryKey($document->id);
return $document;
return $document->makeVisible(['id']);
})->all();
@ -232,7 +234,7 @@ class CompanyExport implements ShouldQueue
$expense_category = $this->transformArrayOfKeys($expense_category, ['user_id', 'company_id']);
return $expense_category;
return $expense_category->makeVisible(['id']);
})->all();
@ -242,7 +244,7 @@ class CompanyExport implements ShouldQueue
$expense = $this->transformBasicEntities($expense);
$expense = $this->transformArrayOfKeys($expense, ['vendor_id', 'invoice_id', 'client_id', 'category_id', 'recurring_expense_id','project_id']);
return $expense;
return $expense->makeVisible(['id']);
})->all();
@ -250,7 +252,7 @@ class CompanyExport implements ShouldQueue
$gs = $this->transformArrayOfKeys($gs, ['user_id', 'company_id']);
return $gs;
return $gs->makeVisible(['id']);
})->all();
@ -260,16 +262,20 @@ class CompanyExport implements ShouldQueue
$invoice = $this->transformBasicEntities($invoice);
$invoice = $this->transformArrayOfKeys($invoice, ['recurring_id','client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id']);
return $invoice;
return $invoice->makeVisible(['id',
'private_notes',
'user_id',
'client_id',
'company_id',]);
})->all();
$this->export_data['invoice_invitations'] = InvoiceInvitation::where('company_id', $this->company->id)->withTrashed()->cursor()->map(function ($invoice){
$invoice = $this->transformArrayOfKeys($invoice, ['company_id', 'user_id', 'client_contact_id', 'recurring_invoice_id']);
$invoice = $this->transformArrayOfKeys($invoice, ['company_id', 'user_id', 'client_contact_id', 'invoice_id']);
return $invoice;
return $invoice->makeVisible(['id']);
})->all();
@ -281,19 +287,14 @@ class CompanyExport implements ShouldQueue
})->makeHidden(['id'])->all();
$this->export_data['paymentables'] = $this->company->payments()->with('paymentables')->cursor()->map(function ($paymentable){
$paymentable = $this->transformArrayOfKeys($paymentable, ['payment_id','paymentable_id']);
return $paymentable;
})->all();
$this->export_data['payments'] = $this->company->payments->map(function ($payment){
$payment = $this->transformBasicEntities($payment);
$payment = $this->transformArrayOfKeys($payment, ['client_id','project_id', 'vendor_id', 'client_contact_id', 'invitation_id', 'company_gateway_id']);
$payment->paymentables = $this->transformPaymentable($payment);
return $payment->makeVisible(['id']);
})->all();
@ -312,7 +313,7 @@ class CompanyExport implements ShouldQueue
$project = $this->transformBasicEntities($project);
$project = $this->transformArrayOfKeys($project, ['client_id']);
return $project;
return $project->makeVisible(['id']);
})->all();
@ -321,25 +322,26 @@ class CompanyExport implements ShouldQueue
$quote = $this->transformBasicEntities($quote);
$quote = $this->transformArrayOfKeys($quote, ['invoice_id','recurring_id','client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id']);
return $quote;
return $quote->makeVisible(['id']);
})->all();
$this->export_data['quote_invitations'] = QuoteInvitation::where('company_id', $this->company->id)->withTrashed()->cursor()->map(function ($quote){
$quote = $this->transformArrayOfKeys($quote, ['company_id', 'user_id', 'client_contact_id', 'recurring_invoice_id']);
$quote = $this->transformArrayOfKeys($quote, ['company_id', 'user_id', 'client_contact_id', 'quote_id']);
return $quote;
return $quote->makeVisible(['id']);
})->all();
$this->export_data['recurring_invoices'] = $this->company->recurring_invoices->map(function ($ri){
$this->export_data['recurring_invoices'] = $this->company->recurring_invoices->makeVisible(['id'])->map(function ($ri){
$ri = $this->transformBasicEntities($ri);
$ri = $this->transformArrayOfKeys($ri, ['client_id', 'vendor_id', 'project_id', 'design_id', 'subscription_id']);
return $ri;
return $ri->makeVisible(['id']);
})->all();
@ -357,7 +359,13 @@ class CompanyExport implements ShouldQueue
$subscription = $this->transformBasicEntities($subscription);
$subscription->group_id = $this->encodePrimaryKey($subscription->group_id);
return $subscription;
return $subscription->makeVisible([ 'id',
'user_id',
'assigned_user_id',
'company_id',
'product_ids',
'recurring_product_ids',
'group_id']);
})->all();
@ -376,7 +384,7 @@ class CompanyExport implements ShouldQueue
$task = $this->transformBasicEntities($task);
$task = $this->transformArrayOfKeys($task, ['client_id', 'invoice_id', 'project_id', 'status_id']);
return $task;
return $task->makeVisible(['id']);
})->all();
@ -401,7 +409,7 @@ class CompanyExport implements ShouldQueue
$this->export_data['vendors'] = $this->company->vendors->map(function ($vendor){
return $this->transformBasicEntities($vendor);
return $this->transformBasicEntities($vendor)->makeVisible(['id']);
})->all();
@ -411,7 +419,7 @@ class CompanyExport implements ShouldQueue
$vendor = $this->transformBasicEntities($vendor);
$vendor->vendor_id = $this->encodePrimaryKey($vendor->vendor_id);
return $vendor;
return $vendor->makeVisible(['id']);
})->all();
@ -425,7 +433,7 @@ class CompanyExport implements ShouldQueue
})->makeHidden(['id'])->all();
//write to tmp and email to owner();
$this->zipAndSend();
return true;
@ -434,7 +442,7 @@ class CompanyExport implements ShouldQueue
private function transformBasicEntities($model)
{
return $this->transformArrayOfKeys($model, ['id', 'user_id', 'assigned_user_id', 'company_id']);
return $this->transformArrayOfKeys($model, ['user_id', 'assigned_user_id', 'company_id']);
}
@ -449,6 +457,24 @@ class CompanyExport implements ShouldQueue
}
private function transformPaymentable($payment)
{
$new_arr = [];
foreach($payment->paymentables as $paymentable)
{
$paymentable->payment_id = $this->encodePrimaryKey($paymentable->payment_id);
$paymentable->paymentable_id = $this->encodePrimaryKey($paymentable->paymentable_id);
$new_arr[] = $paymentable;
}
return $new_arr;
}
private function zipAndSend()
{
@ -464,6 +490,10 @@ class CompanyExport implements ShouldQueue
$zip->addFromString("backup.json", json_encode($this->export_data));
$zip->close();
if(Ninja::isHosted()) {
Storage::disk(config('filesystems.default'))->put('backups/'.$file_name, file_get_contents($zip_path));
}
$nmo = new NinjaMailerObject;
$nmo->mailable = new DownloadBackup(Storage::disk(config('filesystems.default'))->url('backups/'.$file_name), $this->company);
$nmo->to_user = $this->user;
@ -473,6 +503,7 @@ class CompanyExport implements ShouldQueue
NinjaMailerJob::dispatch($nmo);
UnlinkFile::dispatch(config('filesystems.default'), 'backups/'.$file_name)->delay(now()->addHours(1));
UnlinkFile::dispatch('public', 'backups/'.$file_name)->delay(now()->addHours(1));
}
}

File diff suppressed because it is too large Load Diff

View File

@ -93,17 +93,19 @@ class CreateEntityPdf implements ShouldQueue
public function handle()
{
/* Set the locale*/
App::setLocale($this->contact->preferredLocale());
/* Forget the singleton*/
App::forgetInstance('translator');
/* Init a new copy of the translator*/
$t = app('translator');
/* Set the locale*/
App::setLocale($this->contact->preferredLocale());
// nlog($this->entity->client->getMergedSettings());
/* Set customized translations _NOW_ */
Lang::replace(Ninja::transformTranslations($this->entity->client->getMergedSettings()));
$t->replace(Ninja::transformTranslations($this->entity->client->getMergedSettings()));
$this->entity->service()->deletePdf();

View File

@ -154,9 +154,9 @@ class NinjaMailerJob implements ShouldQueue
App::forgetInstance('mail.manager'); //singletons must be destroyed!
App::forgetInstance('mailer');
App::forgetInstance('laravelgmail');
$t = app('translator');
/* Inject custom translations if any exist */
Lang::replace(Ninja::transformTranslations($this->nmo->settings));
$t->replace(Ninja::transformTranslations($this->nmo->settings));
switch ($this->nmo->settings->email_sending_method) {
case 'default':

View File

@ -35,9 +35,9 @@ class WebhookHandler implements ShouldQueue
private $company;
public $tries = 5; //number of retries
public $tries = 3; //number of retries
public $backoff = 5; //seconds to wait until retry
public $backoff = 10; //seconds to wait until retry
public $deleteWhenMissingModels = true;

View File

@ -44,7 +44,8 @@ class CreditEmailEngine extends BaseEmailEngine
public function build()
{
App::forgetInstance('translator');
Lang::replace(Ninja::transformTranslations($this->client->getMergedSettings()));
$t = app('translator');
$t->replace(Ninja::transformTranslations($this->client->getMergedSettings()));
if (is_array($this->template_data) && array_key_exists('body', $this->template_data) && strlen($this->template_data['body']) > 0) {
$body_template = $this->template_data['body'];

View File

@ -47,7 +47,8 @@ class InvoiceEmailEngine extends BaseEmailEngine
{
App::forgetInstance('translator');
Lang::replace(Ninja::transformTranslations($this->client->getMergedSettings()));
$t = app('translator');
$t->replace(Ninja::transformTranslations($this->client->getMergedSettings()));
if (is_array($this->template_data) && array_key_exists('body', $this->template_data) && strlen($this->template_data['body']) > 0) {
$body_template = $this->template_data['body'];

View File

@ -45,7 +45,8 @@ class QuoteEmailEngine extends BaseEmailEngine
public function build()
{
App::forgetInstance('translator');
Lang::replace(Ninja::transformTranslations($this->client->getMergedSettings()));
$t = app('translator');
$t->replace(Ninja::transformTranslations($this->client->getMergedSettings()));
if (is_array($this->template_data) && array_key_exists('body', $this->template_data) && strlen($this->template_data['body']) > 0) {
$body_template = $this->template_data['body'];

View File

@ -41,8 +41,8 @@ class MigrationCompleted extends Mailable
$result = $this->from(config('mail.from.address'), config('mail.from.name'))
->view('email.import.completed', $data);
if($this->company->invoices->count() >=1)
$result->attach($this->company->invoices->first()->pdf_file_path());
// if($this->company->invoices->count() >=1)
// $result->attach($this->company->invoices->first()->pdf_file_path());
return $result;
}

View File

@ -103,6 +103,15 @@ class Activity extends StaticModel
'deleted_at' => 'timestamp',
];
protected $appends = [
'hashed_id',
];
public function getHashedIdAttribute()
{
return $this->encodePrimaryKey($this->id);
}
public function getEntityType()
{
return self::class;

View File

@ -131,6 +131,11 @@ class Company extends BaseModel
return $this->morphMany(Document::class, 'documentable');
}
public function all_documents()
{
return $this->HasMany(Document::class);
}
public function getEntityType()
{
return self::class;

View File

@ -71,10 +71,6 @@ class Quote extends BaseModel
'custom_surcharge2',
'custom_surcharge3',
'custom_surcharge4',
// 'custom_surcharge_tax1',
// 'custom_surcharge_tax2',
// 'custom_surcharge_tax3',
// 'custom_surcharge_tax4',
'design_id',
'assigned_user_id',
'exchange_rate',

View File

@ -144,7 +144,7 @@ class CreditCard
if ($this->checkout->client->currency()->code == 'EUR' || $this->checkout->company_gateway->getConfigField('threeds')) {
$payment->{'3ds'} = ['enabled' => true];
$payment->{'success_url'} = route('payment_webhook', [
$payment->{'success_url'} = route('checkout.3ds_redirect', [
'company_key' => $this->checkout->client->company->company_key,
'company_gateway_id' => $this->checkout->company_gateway->hashed_id,
'hash' => $this->checkout->payment_hash->hash,

View File

@ -13,6 +13,7 @@
namespace App\PaymentDrivers;
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
use App\Http\Requests\Gateways\Checkout3ds\Checkout3dsRequest;
use App\Http\Requests\Payments\PaymentWebhookRequest;
use App\Jobs\Mail\PaymentFailureMailer;
use App\Jobs\Util\SystemLogger;
@ -287,6 +288,11 @@ class CheckoutComPaymentDriver extends BaseDriver
}
public function processWebhookRequest(PaymentWebhookRequest $request, Payment $payment = null)
{
return true;
}
public function process3dsConfirmation(Checkout3dsRequest $request)
{
$this->init();
$this->setPaymentHash($request->getPaymentHash());

View File

@ -193,7 +193,8 @@ class ACH
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_SUCCESS,
SystemLog::TYPE_STRIPE,
$this->stripe->client
$this->stripe->client,
$this->stripe->client->company,
);
return redirect()->route('client.payments.show', ['payment' => $this->stripe->encodePrimaryKey($payment->id)]);

View File

@ -390,6 +390,13 @@ class StripePaymentDriver extends BaseDriver
$payment->save();
}
if ($request->type == 'charge.succeeded') {
$payment->status_id = Payment::STATUS_COMPLETED;
$payment->save();
}
// charge.failed, charge.refunded
return response([], 200);
}

View File

@ -188,7 +188,7 @@ class PaymentMethod
$fee_label = $gateway->calcGatewayFeeLabel($this->amount, $this->client, $gateway_type_id);
if(!$gateway_type_id){
if(!$gateway_type_id || (GatewayType::CUSTOM == $gateway_type_id)){
$this->payment_urls[] = [
'label' => $gateway->getConfigField('name') . $fee_label,

View File

@ -346,6 +346,8 @@ class Design extends BaseDesign
$items = $this->transformLineItems($this->entity->line_items, $type);
$this->processMarkdownOnLineItems($items);
if (count($items) == 0) {
return [];
}

View File

@ -321,10 +321,27 @@ document.addEventListener('DOMContentLoaded', function() {
public static function parseMarkdownToHtml(string $markdown): ?string
{
// Use setting to determinate if parsing should be done.
// 'parse_markdown_on_pdfs'
$converter = new CommonMarkConverter([
'allow_unsafe_links' => false,
]);
return $converter->convertToHtml($markdown);
}
public function processMarkdownOnLineItems(array &$items)
{
// Use setting to determinate if parsing should be done.
// 'parse_markdown_on_pdfs'
foreach ($items as $key => $item) {
foreach ($item as $variable => $value) {
$item[$variable] = DesignHelpers::parseMarkdownToHtml($value ?? '');
}
$items[$key] = $item;
}
}
}

View File

@ -92,7 +92,10 @@ trait PdfMakerUtilities
$contains_html = false;
if (isset($child['content'])) {
$child['content'] = nl2br($child['content']);
// Commented cause it keeps adding <br> at the end, if markdown parsing is turned on.
// Should update with 'parse_markdown_on_pdfs' setting.
// $child['content'] = nl2br($child['content']);
}
// "/\/[a-z]*>/i" -> checks for HTML-like tags:

View File

@ -485,8 +485,9 @@ class SubscriptionService
/**
* Handle a plan change where no payment is required
*
*
* @param array $data
* @deprecated - no usage found
*/
public function handlePlanChangeNoPayment($data)
{
@ -654,7 +655,7 @@ class SubscriptionService
$status = $response->getStatusCode();
//$response_body = $response->getReasonPhrase();
$body = array_merge($body, ['status' => $status, 'response_body' => $response_body]);
//$body = array_merge($body, ['status' => $status, 'response_body' => $response_body]);
}

View File

@ -17,6 +17,7 @@ use App\Models\CreditInvitation;
use App\Models\InvoiceInvitation;
use App\Models\QuoteInvitation;
use App\Models\RecurringInvoiceInvitation;
use App\Services\PdfMaker\Designs\Utilities\DesignHelpers;
use App\Utils\Traits\MakesDates;
use Exception;
use Illuminate\Support\Facades\App;
@ -121,7 +122,7 @@ class HtmlEngine
if ($this->entity_string == 'invoice' || $this->entity_string == 'recurring_invoice') {
$data['$entity'] = ['value' => '', 'label' => ctrans('texts.invoice')];
$data['$number'] = ['value' => $this->entity->number ?: '&nbsp;', 'label' => ctrans('texts.invoice_number')];
$data['$entity.terms'] = ['value' => $this->entity->terms ?: '', 'label' => ctrans('texts.invoice_terms')];
$data['$entity.terms'] = ['value' => DesignHelpers::parseMarkdownToHtml($this->entity->terms ?: '') ?: '', 'label' => ctrans('texts.invoice_terms')];
$data['$terms'] = &$data['$entity.terms'];
$data['$view_link'] = ['value' => '<a class="button" href="'.$this->invitation->getLink().'">'.ctrans('texts.view_invoice').'</a>', 'label' => ctrans('texts.view_invoice')];
$data['$view_url'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_invoice')];
@ -200,7 +201,7 @@ class HtmlEngine
$data['$invoice.custom2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice2', $this->entity->custom_value2, $this->client) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice2')];
$data['$invoice.custom3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice3', $this->entity->custom_value3, $this->client) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice3')];
$data['$invoice.custom4'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice4', $this->entity->custom_value4, $this->client) ?: '&nbsp;', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice4')];
$data['$invoice.public_notes'] = ['value' => $this->entity->public_notes ?: '', 'label' => ctrans('texts.public_notes')];
$data['$invoice.public_notes'] = ['value' => DesignHelpers::parseMarkdownToHtml($this->entity->public_notes ?: '') ?: '', 'label' => ctrans('texts.public_notes')];
$data['$entity.public_notes'] = &$data['$invoice.public_notes'];
$data['$public_notes'] = &$data['$invoice.public_notes'];
@ -374,7 +375,7 @@ class HtmlEngine
$data['$description'] = ['value' => '', 'label' => ctrans('texts.description')];
//$data['$entity_footer'] = ['value' => $this->client->getSetting("{$this->entity_string}_footer"), 'label' => ''];
$data['$entity_footer'] = ['value' => $this->entity->footer, 'label' => ''];
$data['$entity_footer'] = ['value' => DesignHelpers::parseMarkdownToHtml($this->entity->footer ?: ''), 'label' => ''];
$data['$page_size'] = ['value' => $this->settings->page_size, 'label' => ''];
$data['$page_layout'] = ['value' => property_exists($this->settings, 'page_layout') ? $this->settings->page_layout : 'Portrait', 'label' => ''];

View File

@ -25,7 +25,7 @@ use Illuminate\Support\Facades\Queue;
class SystemHealth
{
private static $extensions = [
'mysqli',
// 'mysqli',
'gd',
'curl',
'zip',
@ -34,7 +34,7 @@ class SystemHealth
'mbstring',
'xml',
'bcmath',
'mysqlnd',
// 'mysqlnd',
//'intl', //todo double check whether we need this for email dns validation
];

View File

@ -106,7 +106,8 @@ class TemplateEngine
}
App::forgetInstance('translator');
Lang::replace(Ninja::transformTranslations($this->settings));
$t = app('translator');
$t->replace(Ninja::transformTranslations($this->settings));
return $this;
}

View File

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

View File

@ -0,0 +1,43 @@
<?php
use App\Libraries\MultiDB;
use App\Models\Document;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class MakeDocumentsAssignedUserNullable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('documents', function (Blueprint $table){
$table->unsignedInteger('assigned_user_id')->nullable()->change();
});
Document::where('assigned_user_id', 0)->update(['assigned_user_id' => null]);
if(config('ninja.db.multi_db_enabled')){
foreach (MultiDB::$dbs as $db) {
Document::on($db)->where('assigned_user_id', 0)->update(['assigned_user_id' => null]);
}
}
else{
Document::where('assigned_user_id', 0)->update(['assigned_user_id' => null]);
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}

2
public/css/app.css vendored

File diff suppressed because one or more lines are too long

View File

@ -5,7 +5,7 @@ const CACHE_NAME = 'flutter-app-cache';
const RESOURCES = {
"version.json": "9fe5b22a16f39b766c8fdc35a24b3efa",
"favicon.ico": "51636d3a390451561744c42188ccd628",
"main.dart.js": "99c1cdf29ca54a505582ef971ac4f97f",
"main.dart.js": "4ed39edfd85c4ac1e202b7c83715f20d",
"/": "23224b5e03519aaa87594403d54412cf",
"assets/packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf": "174c02fc4609e8fc4389f5d21f16a296",
"assets/AssetManifest.json": "659dcf9d1baf3aed3ab1b9c42112bf8f",

Binary file not shown.

After

Width:  |  Height:  |  Size: 622 KiB

100701
public/main.dart.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

99471
public/main.foss.dart.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
{
"/js/app.js": "/js/app.js?id=696e8203d5e8e7cf5ff5",
"/css/app.css": "/css/app.css?id=8bf94fe817c8702670c9",
"/css/app.css": "/css/app.css?id=14a824656f32eec8c2b1",
"/js/clients/invoices/action-selectors.js": "/js/clients/invoices/action-selectors.js?id=a09bb529b8e1826f13b4",
"/js/clients/invoices/payment.js": "/js/clients/invoices/payment.js?id=8ce8955ba775ea5f47d1",
"/js/clients/linkify-urls.js": "/js/clients/linkify-urls.js?id=0dc8c34010d09195d2f7",
@ -19,7 +19,5 @@
"/js/clients/shared/multiple-downloads.js": "/js/clients/shared/multiple-downloads.js?id=5c35d28cf0a3286e7c45",
"/js/clients/shared/pdf.js": "/js/clients/shared/pdf.js?id=fc3055d6a099f523ea98",
"/js/setup/setup.js": "/js/setup/setup.js?id=8d454e7090f119552a6c",
"/css/card-js.min.css": "/css/card-js.min.css?id=62afeb675235451543ad",
"/js/admin.js": "/js/admin.js?id=003930085af69b13a86a",
"/css/admin.css": "/css/admin.css?id=0f5a9ba733ae0e1f9cbf"
"/css/card-js.min.css": "/css/card-js.min.css?id=62afeb675235451543ad"
}

View File

@ -159,6 +159,10 @@
#footer {
margin-top: 30px;
}
[data-ref="totals_table-outstanding"] {
color: var(--primary-color)
}
</style>
<div id="header"></div>

View File

@ -10,52 +10,53 @@
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6 sm:col-span-4">
<label for="address1" class="input-label">{{ ctrans('texts.address1') }}</label>
<input id="address1" class="input w-full" name="address1" />
<input id="address1" class="input w-full" name="address1"/>
@error('address1')
<div class="validation validation-fail">
{{ $message }}
</div>
<div class="validation validation-fail">
{{ $message }}
</div>
@enderror
</div>
<div class="col-span-6 sm:col-span-3">
<label for="address2" class="input-label">{{ ctrans('texts.address2') }}</label>
<input id="address2" class="input w-full" name="address2" />
<input id="address2" class="input w-full" name="address2"/>
@error('address2')
<div class="validation validation-fail">
{{ $message }}
</div>
<div class="validation validation-fail">
{{ $message }}
</div>
@enderror
</div>
<div class="col-span-6 sm:col-span-3">
<label for="city" class="input-label">{{ ctrans('texts.city') }}</label>
<input id="city" class="input w-full" name="city" />
<input id="city" class="input w-full" name="city"/>
@error('city')
<div class="validation validation-fail">
{{ $message }}
</div>
<div class="validation validation-fail">
{{ $message }}
</div>
@enderror
</div>
<div class="col-span-6 sm:col-span-2">
<label for="state" class="input-label">{{ ctrans('texts.state') }}</label>
<input id="state" class="input w-full" name="state" />
<input id="state" class="input w-full" name="state"/>
@error('state')
<div class="validation validation-fail">
{{ $message }}
</div>
<div class="validation validation-fail">
{{ $message }}
</div>
@enderror
</div>
<div class="col-span-6 sm:col-span-2">
<label for="postal_code" class="input-label">{{ ctrans('texts.postal_code') }}</label>
<input id="postal_code" class="input w-full" name="postal_code" />
<input id="postal_code" class="input w-full" name="postal_code"/>
@error('postal_code')
<div class="validation validation-fail">
{{ $message }}
</div>
<div class="validation validation-fail">
{{ $message }}
</div>
@enderror
</div>
<div class="col-span-6 sm:col-span-2">
<label for="country" class="input-label">{{ ctrans('texts.country') }}</label>
<select id="country" class="input w-full form-select" name="country">
<option value="none"></option>
@foreach(App\Utils\TranslationHelper::getCountries() as $country)
<option value="{{ $country->id }}">
{{ $country->iso_3166_2 }} ({{ $country->name }})
@ -63,11 +64,11 @@
@endforeach
</select>
@error('country')
<div class="validation validation-fail">
{{ $message }}
</div>
<div class="validation validation-fail">
{{ $message }}
</div>
@enderror
</div>
</div>
</div>
</div>
</div>

View File

@ -1,74 +1,88 @@
<!-- Client shipping address -->
<h3 class="text-lg font-medium leading-6 text-gray-900 mt-8">{{ ctrans('texts.shipping_address') }}</h3>
<!-- Client shipping address -->
<h3 class="text-lg font-medium leading-6 text-gray-900 mt-8">{{ ctrans('texts.shipping_address') }}</h3>
<p class="mt-1 text-sm leading-5 text-gray-500">
{{ ctrans('texts.enter_your_shipping_address') }}
</p>
<p class="mt-1 text-sm leading-5 text-gray-500">
{{ ctrans('texts.enter_your_shipping_address') }}
</p>
<div class="shadow overflow-hidden rounded mt-4">
<div class="px-4 py-5 bg-white sm:p-6">
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6 sm:col-span-4">
<label for="shipping_address1" class="input-label">{{ ctrans('texts.shipping_address1') }}</label>
<input id="shipping_address1" class="input w-full {{ in_array('shipping_address1', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}" name="shipping_address1" />
@error('shipping_address1')
<div class="validation validation-fail">
{{ $message }}
</div>
@enderror
</div>
<div class="col-span-6 sm:col-span-3">
<label for="shipping_address2" class="input-label">{{ ctrans('texts.shipping_address2') }}</label>
<input id="shipping_address2 {{ in_array('shipping_address2', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}" class="input w-full" name="shipping_address2" />
@error('shipping_address2')
<div class="validation validation-fail">
{{ $message }}
</div>
@enderror
</div>
<div class="col-span-6 sm:col-span-3">
<label for="shipping_city" class="input-label">{{ ctrans('texts.shipping_city') }}</label>
<input id="shipping_city" class="input w-full {{ in_array('shipping_city', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}" name="shipping_city" />
@error('shipping_city')
<div class="validation validation-fail">
{{ $message }}
</div>
@enderror
</div>
<div class="col-span-6 sm:col-span-2">
<label for="shipping_state" class="input-label">{{ ctrans('texts.shipping_state') }}</label>
<input id="shipping_state" class="input w-ful {{ in_array('shipping_state', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}l" name="shipping_state" />
@error('shipping_state')
<div class="validation validation-fail">
{{ $message }}
</div>
@enderror
</div>
<div class="col-span-6 sm:col-span-2">
<label for="shipping_postal_code" class="input-label">{{ ctrans('texts.shipping_postal_code') }}</label>
<input id="shipping_postal_code" class="input w-full {{ in_array('shipping_postal_code', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}" name="shipping_postal_code" />
@error('shipping_postal_code')
<div class="validation validation-fail">
{{ $message }}
</div>
@enderror
</div>
<div class="col-span-4 sm:col-span-2">
<label for="shipping_country" class="input-label">{{ ctrans('texts.shipping_country') }}</label>
<select id="shipping_country" class="input w-full form-select {{ in_array('shipping_country', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}" name="shipping_country">
@foreach(App\Utils\TranslationHelper::getCountries() as $country)
<option {{ $country == isset(auth()->user()->client->shipping_country->id) ? 'selected' : null }} value="{{ $country->id }}">
<div class="shadow overflow-hidden rounded mt-4">
<div class="px-4 py-5 bg-white sm:p-6">
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6 sm:col-span-4">
<label for="shipping_address1" class="input-label">{{ ctrans('texts.shipping_address1') }}</label>
<input id="shipping_address1"
class="input w-full {{ in_array('shipping_address1', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}"
name="shipping_address1"/>
@error('shipping_address1')
<div class="validation validation-fail">
{{ $message }}
</div>
@enderror
</div>
<div class="col-span-6 sm:col-span-3">
<label for="shipping_address2" class="input-label">{{ ctrans('texts.shipping_address2') }}</label>
<input
id="shipping_address2 {{ in_array('shipping_address2', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}"
class="input w-full" name="shipping_address2"/>
@error('shipping_address2')
<div class="validation validation-fail">
{{ $message }}
</div>
@enderror
</div>
<div class="col-span-6 sm:col-span-3">
<label for="shipping_city" class="input-label">{{ ctrans('texts.shipping_city') }}</label>
<input id="shipping_city"
class="input w-full {{ in_array('shipping_city', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}"
name="shipping_city"/>
@error('shipping_city')
<div class="validation validation-fail">
{{ $message }}
</div>
@enderror
</div>
<div class="col-span-6 sm:col-span-2">
<label for="shipping_state" class="input-label">{{ ctrans('texts.shipping_state') }}</label>
<input id="shipping_state"
class="input w-ful {{ in_array('shipping_state', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}l"
name="shipping_state"/>
@error('shipping_state')
<div class="validation validation-fail">
{{ $message }}
</div>
@enderror
</div>
<div class="col-span-6 sm:col-span-2">
<label for="shipping_postal_code" class="input-label">{{ ctrans('texts.shipping_postal_code') }}</label>
<input id="shipping_postal_code"
class="input w-full {{ in_array('shipping_postal_code', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}"
name="shipping_postal_code"/>
@error('shipping_postal_code')
<div class="validation validation-fail">
{{ $message }}
</div>
@enderror
</div>
<div class="col-span-4 sm:col-span-2">
<label for="shipping_country" class="input-label">{{ ctrans('texts.shipping_country') }}</label>
<select id="shipping_country"
class="input w-full form-select {{ in_array('shipping_country', (array) session('missing_required_fields')) ? 'border border-red-400' : '' }}"
name="shipping_country">
@foreach(App\Utils\TranslationHelper::getCountries() as $country)
<option value="none"></option>
<option
{{ $country == isset(auth()->user()->client->shipping_country->id) ? 'selected' : null }} value="{{ $country->id }}">
{{ $country->iso_3166_2 }}
({{ $country->name }})
</option>
@endforeach
</select>
@error('country')
<div class="validation validation-fail">
{{ $message }}
</div>
@enderror
</div>
</div>
</div>
</div>
@endforeach
</select>
@error('country')
<div class="validation validation-fail">
{{ $message }}
</div>
@enderror
</div>
</div>
</div>
</div>

View File

@ -10,14 +10,14 @@
<div class="grid lg:grid-cols-3">
@if($account && !$account->isPaid())
<div class="hidden lg:block col-span-1 bg-red-100 h-screen">
<img src="{{ asset('images/bg-home2018b.jpg') }}"
<img src="{{ asset('images/client-portal-new-image.jpg') }}"
class="w-full h-screen object-cover"
alt="Background image">
</div>
@endif
<div class="col-span-2 h-screen flex">
<div class="m-auto md:w-1/2 lg:w-1/4">
@if($account && !$account->isPaid())
<div>
<img src="{{ asset('images/invoiceninja-black-logo-2.png') }}" class="border-b border-gray-100 h-18 pb-4" alt="Invoice Ninja logo">

View File

@ -3,7 +3,7 @@
@section('body')
<div class="grid lg:grid-cols-12 py-8">
<div class="lg:col-span-4 lg:col-start-5 px-6">
<div class="col-span-12 lg:col-span-6 lg:col-start-4 xl:col-span-4 xl:col-start-5 px-6">
<div class="flex justify-center">
<img class="h-32 w-auto" src="{{ $company->present()->logo() }}" alt="{{ ctrans('texts.logo') }}">
</div>

View File

@ -30,7 +30,7 @@
{{ ctrans('texts.credit_card') }}
@endcomponent
@include('portal.ninja2020.gateways.stripe.includes.card_widget')
@include('portal.ninja2020.gateways.stripe.includes.card_widget', ['show_save_method' => false])
@component('portal.ninja2020.gateways.includes.pay_now', ['id' => 'authorize-card'])
{{ ctrans('texts.add_payment_method') }}
@ -40,4 +40,4 @@
@section('gateway_footer')
<script src="https://js.stripe.com/v3/"></script>
<script src="{{ asset('js/clients/payments/stripe-credit-card.js') }}"></script>
@endsection
@endsection

View File

@ -12,4 +12,6 @@
@endunless
</div>
@include('portal.ninja2020.gateways.includes.save_card')
@unless(isset($show_save_method) && $show_save_method == false)
@include('portal.ninja2020.gateways.includes.save_card')
@endunless

View File

@ -43,7 +43,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="@yield('meta_description')"/>
<!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}">

View File

@ -34,6 +34,7 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a
Route::post('claim_license', 'LicenseController@index')->name('license.index');
Route::resource('clients', 'ClientController'); // name = (clients. index / create / show / update / destroy / edit
Route::put('clients/{client}/adjust_ledger', 'ClientController@adjustLedger')->name('clients.adjust_ledger');
Route::put('clients/{client}/upload', 'ClientController@upload')->name('clients.upload');
Route::post('clients/bulk', 'ClientController@bulk')->name('clients.bulk');
@ -84,6 +85,7 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a
Route::post('group_settings/bulk', 'GroupSettingController@bulk');
Route::post('import', 'ImportController@import')->name('import.import');
Route::post('import_json', 'ImportJsonController@import')->name('import.import_json');
Route::post('preimport', 'ImportController@preimport')->name('import.preimport');
Route::resource('invoices', 'InvoiceController'); // name = (invoices. index / create / show / update / destroy / edit

View File

@ -15,9 +15,9 @@ Route::post('setup/check_db', 'SetupController@checkDB')->middleware('guest');
Route::post('setup/check_mail', 'SetupController@checkMail')->middleware('guest');
Route::post('setup/check_pdf', 'SetupController@checkPdf')->middleware('guest');
Route::get('password/reset', 'Auth\ForgotPasswordController@showLinkRequestForm')->name('password.request');
Route::get('password/reset', 'Auth\ForgotPasswordController@showLinkRequestForm')->middleware('domain_db')->name('password.request');
Route::post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail')->name('password.email');
Route::get('password/reset/{token}', 'Auth\ResetPasswordController@showResetForm')->middleware('email_db')->name('password.reset');
Route::get('password/reset/{token}', 'Auth\ResetPasswordController@showResetForm')->middleware(['domain_db','email_db'])->name('password.reset');
Route::post('password/reset', 'Auth\ResetPasswordController@reset')->middleware('email_db')->name('password.update');
/*
@ -36,4 +36,6 @@ Route::group(['middleware' => ['url_db']], function () {
});
Route::get('stripe/signup/{token}', 'StripeConnectController@initialize')->name('stripe_connect.initialization');
Route::get('stripe/completed', 'StripeConnectController@completed')->name('stripe_connect.return');
Route::get('stripe/completed', 'StripeConnectController@completed')->name('stripe_connect.return');
Route::get('checkout/3ds_redirect/{company_key}/{company_gateway_id}/{hash}', 'Gateways\Checkout3dsController@index')->name('checkout.3ds_redirect');

View File

@ -12,24 +12,43 @@ namespace Tests\Feature\Import;
use App\Jobs\Import\CSVImport;
use App\Models\Account;
use App\Models\Activity;
use App\Models\Backup;
use App\Models\Client;
use App\Models\ClientContact;
use App\Models\ClientGatewayToken;
use App\Models\Company;
use App\Models\CompanyGateway;
use App\Models\CompanyLedger;
use App\Models\CompanyToken;
use App\Models\CompanyUser;
use App\Models\Credit;
use App\Models\CreditInvitation;
use App\Models\Design;
use App\Models\Document;
use App\Models\Expense;
use App\Models\ExpenseCategory;
use App\Models\GroupSetting;
use App\Models\Invoice;
use App\Models\InvoiceInvitation;
use App\Models\Payment;
use App\Models\PaymentTerm;
use App\Models\Paymentable;
use App\Models\Product;
use App\Models\Vendor;
use App\Models\VendorContact;
use App\Models\Project;
use App\Models\Quote;
use App\Models\QuoteInvitation;
use App\Models\RecurringInvoice;
use App\Models\RecurringInvoiceInvitation;
use App\Models\Subscription;
use App\Models\Task;
use App\Models\TaskStatus;
use App\Models\TaxRate;
use App\Models\User;
use App\Models\Vendor;
use App\Models\VendorContact;
use App\Utils\Traits\MakesHash;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Routing\Middleware\ThrottleRequests;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
@ -45,6 +64,7 @@ use Tests\TestCase;
class ImportCompanyTest extends TestCase
{
use MakesHash;
use DatabaseTransactions;
public $account;
public $company;
@ -81,6 +101,13 @@ class ImportCompanyTest extends TestCase
$this->backup_json_object = json_decode(file_get_contents($backup_json_file));
Credit::all()->each(function($credit){
$credit->forceDelete();
});
CreditInvitation::all()->each(function($credit){
$credit->forceDelete();
});
}
public function testBackupJsonRead()
@ -256,7 +283,6 @@ class ImportCompanyTest extends TestCase
$obj_array = (array)$obj;
unset($obj_array['user_id']);
unset($obj_array['company_id']);
unset($obj_array['account_id']);
unset($obj_array['hashed_id']);
unset($obj_array['id']);
unset($obj_array['tax_rate_id']);
@ -288,10 +314,8 @@ class ImportCompanyTest extends TestCase
$obj_array = (array)$obj;
unset($obj_array['user_id']);
unset($obj_array['company_id']);
unset($obj_array['account_id']);
unset($obj_array['hashed_id']);
unset($obj_array['id']);
unset($obj_array['tax_rate_id']);
$new_obj = ExpenseCategory::firstOrNew(
['name' => $obj->name, 'company_id' => $this->company->id],
@ -321,10 +345,8 @@ class ImportCompanyTest extends TestCase
$obj_array = (array)$obj;
unset($obj_array['user_id']);
unset($obj_array['company_id']);
unset($obj_array['account_id']);
unset($obj_array['hashed_id']);
unset($obj_array['id']);
unset($obj_array['tax_rate_id']);
$new_obj = TaskStatus::firstOrNew(
['name' => $obj->name, 'company_id' => $this->company->id],
@ -416,79 +438,605 @@ class ImportCompanyTest extends TestCase
$this->assertEquals(1, ClientContact::count());
/***************************** Client Contacts *****************************/
/* Generic */
/* Generic */
//vendors!
/* Generic */
$this->assertEquals(1, count($this->backup_json_object->vendors));
$this->genericImport(Vendor::class,
['user_id', 'assigned_user_id', 'company_id', 'id', 'hashed_id'],
[['users' => 'user_id'], ['users' =>'assigned_user_id']],
'vendors',
'number');
$this->assertEquals(1, Vendor::count());
/* Generic */
$this->assertEquals(1, count($this->backup_json_object->projects));
//$class, $unset, $transforms, $object_property, $match_key
$this->genericImport(Project::class,
['user_id', 'assigned_user_id', 'company_id', 'id', 'hashed_id','client_id'],
[['users' => 'user_id'], ['users' =>'assigned_user_id'], ['clients' => 'client_id']],
'projects',
'number');
$this->assertEquals(1, Project::count());
//projects!
/***************************** Products *****************************/
// Product::unguard();
//products!
// $this->assertEquals(1, count($this->backup_json_object->products));
$this->assertEquals(1, count($this->backup_json_object->products));
// foreach($this->backup_json_object->products as $obj)
// {
$this->genericNewClassImport(Product::class,
['user_id', 'company_id', 'hashed_id', 'id'],
[['users' => 'user_id'], ['users' =>'assigned_user_id'], ['vendors' => 'vendor_id'], ['projects' => 'project_id']],
'products'
);
$this->assertEquals(1, Product::count());
// $user_id = $this->transformId('users', $obj->user_id);
// $assigned_user_id = $this->transformId('users', $obj->assigned_user_id);
// $vendor_id = $this->transformId('vendors', $obj->vendor_id);
// $project_id = $this->transformId('projects', $obj->project_id);
//company gateways
// $obj_array = (array)$obj;
// unset($obj_array['user_id']);
// unset($obj_array['company_id']);
// unset($obj_array['account_id']);
// unset($obj_array['hashed_id']);
// unset($obj_array['id']);
$this->assertEquals(1, count($this->backup_json_object->company_gateways));
// $new_obj = new Product();
// $new_obj->company_id = $this->company->id;
// $new_obj->user_id = $user_id;
// $new_obj->assigned_user_id = $assigned_user_id;
// $new_obj->vendor_id = $vendor_id;
// $new_obj->project_id = $project_id;
// $new_obj->fill($obj_array);
$this->genericNewClassImport(CompanyGateway::class,
['user_id', 'company_id', 'hashed_id', 'id'],
[['users' => 'user_id']],
'company_gateways'
);
// $new_obj->save(['timestamps' => false]);
// $this->ids['products']["{$obj->hashed_id}"] = $new_obj->id;
$this->assertEquals(1, CompanyGateway::count());
// }
//company gateways
// Product::reguard();
// $this->assertEquals(1, Product::count());
/***************************** Products *****************************/
//client gateway tokens
$this->genericNewClassImport(ClientGatewayToken::class,
['company_id', 'id', 'hashed_id','client_id'],
[['clients' => 'client_id']],
'client_gateway_tokens');
//client gateway tokens
//Group Settings
$this->genericImport(GroupSetting::class,
['user_id', 'company_id', 'id', 'hashed_id',],
[['users' => 'user_id']],
'group_settings',
'name');
//Group Settings
//Subscriptions
$this->assertEquals(1, count($this->backup_json_object->subscriptions));
$this->genericImport(Subscription::class,
['user_id', 'assigned_user_id', 'company_id', 'id', 'hashed_id',],
[['group_settings' => 'group_id'], ['users' => 'user_id'], ['users' => 'assigned_user_id']],
'subscriptions',
'name');
$this->assertEquals(1, Subscription::count());
//Subscriptions
// Recurring Invoices
$this->assertEquals(2, count($this->backup_json_object->recurring_invoices));
$this->genericImport(RecurringInvoice::class,
['user_id', 'assigned_user_id', 'company_id', 'id', 'hashed_id', 'client_id','subscription_id','project_id','vendor_id','status'],
[
['subscriptions' => 'subscription_id'],
['users' => 'user_id'],
['users' => 'assigned_user_id'],
['clients' => 'client_id'],
['projects' => 'project_id'],
['vendors' => 'vendor_id'],
['clients' => 'client_id'],
],
'recurring_invoices',
'number');
$this->assertEquals(2, RecurringInvoice::count());
// Recurring Invoices
// Recurring Invoice Invitations
$this->assertEquals(2, count($this->backup_json_object->recurring_invoice_invitations));
$this->genericImport(RecurringInvoiceInvitation::class,
['user_id', 'client_contact_id', 'company_id', 'id', 'hashed_id', 'recurring_invoice_id'],
[
['users' => 'user_id'],
['recurring_invoices' => 'recurring_invoice_id'],
['client_contacts' => 'client_contact_id'],
],
'recurring_invoice_invitations',
'key');
$this->assertEquals(2, RecurringInvoiceInvitation::count());
// Recurring Invoice Invitations
// Invoices
$this->assertEquals(2, count($this->backup_json_object->invoices));
$this->genericImport(Invoice::class,
['user_id', 'client_id', 'company_id', 'id', 'hashed_id', 'recurring_id','status'],
[
['users' => 'user_id'],
['users' => 'assigned_user_id'],
['recurring_invoices' => 'recurring_id'],
['clients' => 'client_id'],
['subscriptions' => 'subscription_id'],
['projects' => 'project_id'],
['vendors' => 'vendor_id'],
],
'invoices',
'number');
$this->assertEquals(2, Invoice::count());
// Invoices
// Invoice Invitations
$this->assertEquals(2, count($this->backup_json_object->invoice_invitations));
$this->genericImport(InvoiceInvitation::class,
['user_id', 'client_contact_id', 'company_id', 'id', 'hashed_id', 'invoice_id'],
[
['users' => 'user_id'],
['invoices' => 'invoice_id'],
['client_contacts' => 'client_contact_id'],
],
'invoice_invitations',
'key');
$this->assertEquals(2, InvoiceInvitation::count());
// Invoice Invitations
// Quotes
$this->assertEquals(2, count($this->backup_json_object->quotes));
$this->genericImport(Quote::class,
['user_id', 'client_id', 'company_id', 'id', 'hashed_id', 'recurring_id','status'],
[
['users' => 'user_id'],
['users' => 'assigned_user_id'],
['recurring_invoices' => 'recurring_id'],
['clients' => 'client_id'],
['subscriptions' => 'subscription_id'],
['projects' => 'project_id'],
['vendors' => 'vendor_id'],
],
'quotes',
'number');
$this->assertEquals(2, Quote::count());
// Quotes
// Quotes Invitations
$this->assertEquals(2, count($this->backup_json_object->quote_invitations));
$this->genericImport(QuoteInvitation::class,
['user_id', 'client_contact_id', 'company_id', 'id', 'hashed_id', 'quote_id'],
[
['users' => 'user_id'],
['quotes' => 'quote_id'],
['client_contacts' => 'client_contact_id'],
],
'quote_invitations',
'key');
$this->assertEquals(2, QuoteInvitation::count());
// Quotes Invitations
// Credits
$this->assertEquals(2, count($this->backup_json_object->credits));
$this->genericImport(Credit::class,
['user_id', 'client_id', 'company_id', 'id', 'hashed_id', 'recurring_id','status'],
[
['users' => 'user_id'],
['users' => 'assigned_user_id'],
['recurring_invoices' => 'recurring_id'],
['clients' => 'client_id'],
['subscriptions' => 'subscription_id'],
['projects' => 'project_id'],
['vendors' => 'vendor_id'],
],
'credits',
'number');
$this->assertEquals(2, Credit::count());
// Credits
// Credits Invitations
$this->assertEquals(2, count($this->backup_json_object->credit_invitations));
$this->genericImport(CreditInvitation::class,
['user_id', 'client_contact_id', 'company_id', 'id', 'hashed_id', 'credit_id'],
[
['users' => 'user_id'],
['credits' => 'credit_id'],
['client_contacts' => 'client_contact_id'],
],
'credit_invitations',
'key');
$this->assertEquals(2, CreditInvitation::count());
// Credits Invitations
// Expenses
$this->assertEquals(2, count($this->backup_json_object->expenses));
$this->genericImport(Expense::class,
['assigned_user_id', 'user_id', 'client_id', 'company_id', 'id', 'hashed_id', 'project_id','vendor_id'],
[
['users' => 'user_id'],
['users' => 'assigned_user_id'],
['clients' => 'client_id'],
['projects' => 'project_id'],
['vendors' => 'vendor_id'],
],
'expenses',
'number');
$this->assertEquals(2, Expense::count());
// Expenses
// Tasks
$this->assertEquals(3, count($this->backup_json_object->tasks));
$this->genericImport(Task::class,
['assigned_user_id', 'user_id', 'client_id', 'company_id', 'id', 'hashed_id', 'invoice_id','project_id'],
[
['users' => 'user_id'],
['users' => 'assigned_user_id'],
['clients' => 'client_id'],
['projects' => 'project_id'],
['invoices' => 'invoice_id'],
],
'tasks',
'number');
$this->assertEquals(3, Task::count());
// Tasks
// Payments
$this->assertEquals(2, count($this->backup_json_object->payments));
$this->genericImport(Payment::class,
['assigned_user_id', 'user_id', 'client_id', 'company_id', 'id', 'hashed_id', 'client_contact_id','invitation_id','vendor_id','paymentables'],
[
['users' => 'user_id'],
['users' => 'assigned_user_id'],
['clients' => 'client_id'],
['client_contacts' => 'client_contact_id'],
['vendors' => 'vendor_id'],
['invoice_invitations' => 'invitation_id'],
['company_gateways' => 'company_gateway_id'],
],
'payments',
'number');
$this->assertEquals(2, Payment::count());
// Payments
// Paymentables
$this->paymentablesImport();
$this->assertEquals(1, Paymentable::count());
// Paymentables
// Activities
$activities = [];
foreach($this->backup_json_object->activities as $activity)
{
$activity->account_id = $this->company->account_id;
$activities[] = $activity;
}
$this->assertEquals(25, count($this->backup_json_object->activities));
$this->backup_json_object->activities = $activities;
$this->genericImport(Activity::class,
[
'user_id',
'company_id',
'client_id',
'client_contact_id',
'project_id',
'vendor_id',
'payment_id',
'invoice_id',
'credit_id',
'invitation_id',
'task_id',
'expense_id',
'token_id',
'quote_id',
'subscription_id',
'recurring_invoice_id',
'hashed_id',
'company_id',
],
[
['users' => 'user_id'],
['clients' => 'client_id'],
['client_contacts' => 'client_contact_id'],
['projects' => 'project_id'],
['vendors' => 'vendor_id'],
['payments' => 'payment_id'],
['invoices' => 'invoice_id'],
['credits' => 'credit_id'],
['tasks' => 'task_id'],
['expenses' => 'expense_id'],
['quotes' => 'quote_id'],
['subscriptions' => 'subscription_id'],
['recurring_invoices' => 'recurring_invoice_id'],
['invitations' => 'invitation_id'],
],
'activities',
'created_at');
$this->assertEquals(25, Activity::count());
// Activities
// Backup
$this->assertEquals(25, count($this->backup_json_object->backups));
$this->genericImportWithoutCompany(Backup::class,
['activity_id','hashed_id'],
[
['activities' => 'activity_id'],
],
'backups',
'created_at');
$this->assertEquals(25, Backup::count());
// Backup
// Company Ledger
$this->assertEquals(3, count($this->backup_json_object->company_ledger));
$this->genericImport(CompanyLedger::class,
['company_id', 'user_id', 'client_id', 'activity_id', 'id','account_id'],
[
['users' => 'user_id'],
['clients' => 'client_id'],
['activities' => 'activity_id'],
],
'company_ledger',
'created_at');
$this->assertEquals(3, CompanyLedger::count());
// Company Ledger
// Designs
$this->genericImport(Design::class,
['company_id', 'user_id'],
[
['users' => 'user_id'],
],
'designs',
'name');
// Designs
// Documents
$this->assertEquals(2, count($this->backup_json_object->documents));
$this->documentsImport();
$this->assertEquals(2, Document::count());
// Documents
}
private function genericImport($class, $unset, $transform, $object_property, $match_key)
private function documentsImport()
{
foreach($this->backup_json_object->documents as $document)
{
$new_document = new Document();
$new_document->user_id = $this->transformId('users', $document->user_id);
$new_document->assigned_user_id = $this->transformId('users', $document->assigned_user_id);
$new_document->company_id = $this->company->id;
$new_document->project_id = $this->transformId('projects', $document->project_id);
$new_document->vendor_id = $this->transformId('vendors', $document->vendor_id);
$new_document->url = $document->url;
$new_document->preview = $document->preview;
$new_document->name = $document->name;
$new_document->type = $document->type;
$new_document->disk = $document->disk;
$new_document->hash = $document->hash;
$new_document->size = $document->size;
$new_document->width = $document->width;
$new_document->height = $document->height;
$new_document->is_default = $document->is_default;
$new_document->custom_value1 = $document->custom_value1;
$new_document->custom_value2 = $document->custom_value2;
$new_document->custom_value3 = $document->custom_value3;
$new_document->custom_value4 = $document->custom_value4;
$new_document->deleted_at = $document->deleted_at;
$new_document->documentable_id = $this->transformDocumentId($document->documentable_id, $document->documentable_type);
$new_document->documentable_type = $document->documentable_type;
$new_document->save(['timestamps' => false]);
}
}
private function transformDocumentId($id, $type)
{
switch ($type) {
case Company::class:
return $this->company->id;
break;
case Client::class:
return $this->transformId('clients', $id);
break;
case ClientContact::class:
return $this->transformId('client_contacts', $id);
break;
case Credit::class:
return $this->transformId('credits', $id);
break;
case Expense::class:
return $this->transformId('expenses', $id);
break;
case 'invoices':
return $this->transformId('invoices', $id);
break;
case Payment::class:
return $this->transformId('payments', $id);
break;
case Product::class:
return $this->transformId('products', $id);
break;
case Quote::class:
return $this->transformId('quotes', $id);
break;
case RecurringInvoice::class:
return $this->transformId('recurring_invoices', $id);
break;
case Company::class:
return $this->transformId('clients', $id);
break;
default:
# code...
break;
}
}
private function paymentablesImport()
{
foreach($this->backup_json_object->payments as $payment)
{
foreach($payment->paymentables as $paymentable_obj)
{
$paymentable = new Paymentable();
$paymentable->payment_id = $this->transformId('payments', $paymentable_obj->payment_id);
$paymentable->paymentable_type = $paymentable_obj->paymentable_type;
$paymentable->amount = $paymentable_obj->amount;
$paymentable->refunded = $paymentable_obj->refunded;
$paymentable->created_at = $paymentable_obj->created_at;
$paymentable->deleted_at = $paymentable_obj->deleted_at;
$paymentable->updated_at = $paymentable_obj->updated_at;
$paymentable->paymentable_id = $this->convertPaymentableId($paymentable_obj->paymentable_type, $paymentable_obj->paymentable_id);
$paymentable->paymentable_type = $paymentable_obj->paymentable_type;
$paymentable->save(['timestamps' => false]);
}
}
}
private function convertPaymentableId($type, $id)
{
switch ($type) {
case 'invoices':
return $this->transformId('invoices', $id);
break;
case Credit::class:
return $this->transformId('credits', $id);
break;
case Payment::class:
return $this->transformId('payments', $id);
default:
# code...
break;
}
}
private function genericNewClassImport($class, $unset, $transforms, $object_property)
{
$class::unguard();
foreach($this->backup_json_object->{$object_property} as $obj)
{
/* Remove unwanted keys*/
$obj_array = (array)$obj;
foreach($unset as $un){
unset($obj_array[$un]);
}
foreach($trans as $key => $value)
{
$obj_array["{$value}"] = $this->transformId($object_property, $obj->{$value});
$activity_invitation_key = false;
if($class instanceof Activity){
if(isset($obj->invitation_id)){
if(isset($obj->invoice_id))
$activity_invitation_key = 'invoice_invitations';
elseif(isset($obj->quote_id))
$activity_invitation_key = 'quote_invitations';
elseif($isset($obj->credit_id))
$activity_invitation_key = 'credit_invitations';
}
}
$new_obj = $class::firstOrNew(
[$match_key => $obj->{$match_key}, 'company_id' => $this->company->id],
$obj_array,
);
/* Transform old keys to new keys */
foreach($transforms as $transform)
{
foreach($transform as $key => $value)
{
if($class instanceof Activity && $activity_invitation_key)
$key = $activity_invitation_key;
$obj_array["{$value}"] = $this->transformId($key, $obj->{$value});
}
}
if($class instanceof CompanyGateway) {
$obj_array['config'] = encrypt($obj_array['config']);
}
$new_obj = new $class();
$new_obj->company_id = $this->company->id;
$new_obj->fill($obj_array);
$new_obj->save(['timestamps' => false]);
@ -497,10 +1045,119 @@ class ImportCompanyTest extends TestCase
}
$class::reguard();
}
private function genericImportWithoutCompany($class, $unset, $transforms, $object_property, $match_key)
{
$class::unguard();
foreach($this->backup_json_object->{$object_property} as $obj)
{
/* Remove unwanted keys*/
$obj_array = (array)$obj;
foreach($unset as $un){
unset($obj_array[$un]);
}
/* Transform old keys to new keys */
foreach($transforms as $transform)
{
foreach($transform as $key => $value)
{
$obj_array["{$value}"] = $this->transformId($key, $obj->{$value});
}
}
/* New to convert product ids from old hashes to new hashes*/
if($class instanceof Subscription){
$obj_array['product_ids'] = $this->recordProductIds($obj_array['product_ids']);
$obj_array['recurring_product_ids'] = $this->recordProductIds($obj_array['recurring_product_ids']);
}
$new_obj = $class::firstOrNew(
[$match_key => $obj->{$match_key}],
$obj_array,
);
$new_obj->save(['timestamps' => false]);
if($new_obj instanceof CompanyLedger){
}
else
$this->ids["{$object_property}"]["{$obj->hashed_id}"] = $new_obj->id;
}
$class::reguard();
}
private function genericImport($class, $unset, $transforms, $object_property, $match_key)
{
$class::unguard();
foreach($this->backup_json_object->{$object_property} as $obj)
{
/* Remove unwanted keys*/
$obj_array = (array)$obj;
foreach($unset as $un){
unset($obj_array[$un]);
}
/* Transform old keys to new keys */
foreach($transforms as $transform)
{
foreach($transform as $key => $value)
{
$obj_array["{$value}"] = $this->transformId($key, $obj->{$value});
}
}
/* New to convert product ids from old hashes to new hashes*/
if($class instanceof Subscription){
$obj_array['product_ids'] = $this->recordProductIds($obj_array['product_ids']);
$obj_array['recurring_product_ids'] = $this->recordProductIds($obj_array['recurring_product_ids']);
}
$new_obj = $class::firstOrNew(
[$match_key => $obj->{$match_key}, 'company_id' => $this->company->id],
$obj_array,
);
$new_obj->save(['timestamps' => false]);
if($new_obj instanceof CompanyLedger){
}
else
$this->ids["{$object_property}"]["{$obj->hashed_id}"] = $new_obj->id;
}
$class::reguard();
}
private function recordProductIds($ids)
{
$id_array = explode(",", $ids);
$tmp_arr = [];
foreach($id_array as $id) {
$tmp_arr[] = $this->encodePrimaryKey($this->transformId('products', $id));
}
return implode(",", $tmp_arr);
}
private function transformId(string $resource, ?string $old): ?int
{
if(empty($old))
@ -511,7 +1168,7 @@ class ImportCompanyTest extends TestCase
}
if (! array_key_exists("{$old}", $this->ids[$resource])) {
throw new \Exception("Missing resource key: {$old}");
throw new \Exception("Missing {$resource} key: {$old}");
}
return $this->ids[$resource]["{$old}"];

Binary file not shown.

View File

@ -54,6 +54,10 @@ class UserTest extends TestCase
ThrottleRequests::class,
PasswordProtection::class
);
// if (config('ninja.testvars.travis') !== false) {
// $this->markTestSkipped('Skip test for Travis');
// }
}
public function testUserList()
@ -95,54 +99,58 @@ class UserTest extends TestCase
$this->assertNotNull($arr['data']['company_user']);
}
// public function testUserAttachAndDetach()
// {
// $this->withoutMiddleware(PasswordProtection::class);
public function testUserAttachAndDetach()
{
// $user = UserFactory::create($this->account->id);
// $user->first_name = 'Test';
// $user->last_name = 'Palloni';
// $user->email = $this->default_email;
// $user->save();
$this->withoutMiddleware(PasswordProtection::class);
// $data = $user->toArray();
$data = [
'first_name' => 'Test',
'last_name' => 'Palloni',
'email' => $this->default_email,
];
// $response = false;
$response = false;
// try {
// $response = $this->withHeaders([
// 'X-API-SECRET' => config('ninja.api_secret'),
// 'X-API-TOKEN' => $this->token,
// 'X-API-PASSWORD' => 'ALongAndBriliantPassword',
// ])->post('/api/v1/users?include=company_user', $data);
try {
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
'X-API-PASSWORD' => 'ALongAndBriliantPassword',
])->post('/api/v1/users?include=company_user', $data);
// } catch (ValidationException $e) {
// $message = json_decode($e->validator->getMessageBag(), 1);
// nlog($message);
// var_dump($message);
// $this->assertNotNull($message);
// }
} catch (ValidationException $e) {
$message = json_decode($e->validator->getMessageBag(), 1);
nlog($message);
var_dump($message);
$this->assertNotNull($message);
}
// $response->assertStatus(200);
$response->assertStatus(200);
// // $this->assertNotNull($user->company_user);
// // $this->assertEquals($user->company_user->company_id, $this->company->id);
$arr = $response->json();
// $response = $this->withHeaders([
// 'X-API-SECRET' => config('ninja.api_secret'),
// 'X-API-TOKEN' => $this->token,
// 'X-API-PASSWORD' => 'ALongAndBriliantPassword',
// ])->delete('/api/v1/users/'.$this->encodePrimaryKey($user->id).'/detach_from_company?include=company_user');
// $this->assertNotNull($user->company_user);
// $this->assertEquals($user->company_user->company_id, $this->company->id);
// $response->assertStatus(200);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
'X-API-PASSWORD' => 'ALongAndBriliantPassword',
])->delete('/api/v1/users/'.$arr['data']['id'].'/detach_from_company?include=company_user');
// $cu = CompanyUser::whereUserId($user->id)->whereCompanyId($this->company->id)->first();
// $ct = CompanyToken::whereUserId($user->id)->whereCompanyId($this->company->id)->first();
$response->assertStatus(200);
// $this->assertNull($cu);
// $this->assertNull($ct);
// $this->assertNotNull($user);
// }
$user_id = $this->decodePrimaryKey($arr['data']['id']);
$cu = CompanyUser::whereUserId($user_id)->whereCompanyId($this->company->id)->first();
$ct = CompanyToken::whereUserId($user_id)->whereCompanyId($this->company->id)->first();
$user = User::find($user_id);
$this->assertNull($cu);
$this->assertNull($ct);
$this->assertNotNull($user);
}
public function testAttachUserToMultipleCompanies()
{
@ -168,14 +176,12 @@ class UserTest extends TestCase
$cu->save();
/*Create New Blank User and Attach to Company 2*/
$new_user = UserFactory::create($this->account->id);
$new_user->first_name = 'Test';
$new_user->last_name = 'Palloni';
$new_user->email = $this->default_email;
$new_user->save();
$data = $new_user->toArray();
$data = [
'first_name' => 'Test',
'last_name' => 'Palloni',
'email' => $this->default_email,
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $company_token->token,

View File

@ -34,9 +34,9 @@ class SystemHealthTest extends TestCase
$this->assertTrue((bool) $results['system_health']);
$this->assertTrue($results['extensions'][0]['mysqli']);
$this->assertTrue($results['extensions'][1]['gd']);
$this->assertTrue($results['extensions'][2]['curl']);
$this->assertTrue($results['extensions'][3]['zip']);
// $this->assertTrue($results['extensions'][0]['mysqli']);
$this->assertTrue($results['extensions'][0]['gd']);
$this->assertTrue($results['extensions'][1]['curl']);
$this->assertTrue($results['extensions'][2]['zip']);
}
}