mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-08 06:24:30 -04:00
commit
00e1b48f63
@ -1 +1 @@
|
||||
5.1.13
|
||||
5.1.14
|
@ -614,8 +614,8 @@ class CompanySettings extends BaseSettings
|
||||
'$invoice.po_number',
|
||||
'$invoice.date',
|
||||
'$invoice.due_date',
|
||||
'$invoice.balance_due',
|
||||
'$invoice.total',
|
||||
'$invoice.balance_due',
|
||||
],
|
||||
'quote_details' => [
|
||||
'$quote.number',
|
||||
|
107
app/Http/Controllers/OneTimeTokenController.php
Normal file
107
app/Http/Controllers/OneTimeTokenController.php
Normal file
@ -0,0 +1,107 @@
|
||||
<?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\OneTimeToken\OneTimeRouterRequest;
|
||||
use App\Http\Requests\OneTimeToken\OneTimeTokenRequest;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class OneTimeTokenController extends BaseController
|
||||
{
|
||||
|
||||
private $contexts = [
|
||||
'stripe_connect_test' => 'https://connect.stripe.com/oauth/authorize?response_type=code&client_id=ca_J2FhIhcf9GT5BlWUNeQ1FhnZACaYZrOI&scope=read_write',
|
||||
'stripe_connect' => 'https://connect.stripe.com/oauth/authorize?response_type=code&client_id=ca_J2Fh2tZfMlaaItUfbUwBBx4JPss8jCz9&scope=read_write'
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @param CreateOneTimeTokenRequest $request
|
||||
* @return Response
|
||||
*
|
||||
* @OA\Post(
|
||||
* path="/api/v1/one_time_token",
|
||||
* operationId="oneTimeToken",
|
||||
* tags={"one_time_token"},
|
||||
* summary="Attempts to create a one time token",
|
||||
* description="Attempts to create a one time token",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="The Company User response",
|
||||
* @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 create(OneTimeTokenRequest $request)
|
||||
{
|
||||
|
||||
$hash = Str::random(64);
|
||||
|
||||
$data = [
|
||||
'user_id' => auth()->user()->id,
|
||||
'company_key'=> auth()->company()->company_key,
|
||||
'context' => $requst->input('context'),
|
||||
];
|
||||
|
||||
Cache::put( $hash, $data, 3600 );
|
||||
|
||||
return response()->json(['hash' => $hash], 200);
|
||||
|
||||
}
|
||||
|
||||
public function router(OneTimeRouterRequest $request)
|
||||
{
|
||||
$data = Cache::get($request->input('hash'));
|
||||
|
||||
MultiDB::findAndSetDbByCompanyKey($data['company_key']);
|
||||
|
||||
$user = User::findOrFail($data['user_id']);
|
||||
|
||||
Auth::login($user, true);
|
||||
|
||||
// Cache::forget($request->input('hash'));
|
||||
|
||||
$this->sendTo($data['context']);
|
||||
|
||||
}
|
||||
|
||||
/* We need to merge all contexts here and redirect to the correct location */
|
||||
private function sendTo($context)
|
||||
{
|
||||
|
||||
return redirect();
|
||||
}
|
||||
}
|
@ -467,7 +467,7 @@ class UserController extends BaseController
|
||||
public function destroy(DestroyUserRequest $request, User $user)
|
||||
{
|
||||
/* If the user passes the company user we archive the company user */
|
||||
$user = $this->user_repo->destroy($request->all(), $user);
|
||||
$user = $this->user_repo->delete($request->all(), $user);
|
||||
|
||||
event(new UserWasDeleted($user, auth()->user(), auth()->user()->company, Ninja::eventVars()));
|
||||
|
||||
@ -554,79 +554,6 @@ class UserController extends BaseController
|
||||
return $this->listResponse(User::withTrashed()->whereIn('id', $return_user_collection));
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach an existing user to a company.
|
||||
*
|
||||
* @OA\Post(
|
||||
* path="/api/v1/users/{user}/attach_to_company",
|
||||
* operationId="attachUser",
|
||||
* tags={"users"},
|
||||
* summary="Attach an existing user to a company",
|
||||
* description="Attach an existing user to a company",
|
||||
* @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="user",
|
||||
* in="path",
|
||||
* description="The user hashed_id",
|
||||
* example="FD767dfd7",
|
||||
* required=true,
|
||||
* @OA\Schema(
|
||||
* type="string",
|
||||
* format="string",
|
||||
* ),
|
||||
* ),
|
||||
* @OA\RequestBody(
|
||||
* description="The company user object",
|
||||
* required=true,
|
||||
* @OA\JsonContent(ref="#/components/schemas/CompanyUser"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Returns the saved User 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/CompanyUser"),
|
||||
* ),
|
||||
* @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"),
|
||||
* ),
|
||||
* )
|
||||
* @param AttachCompanyUserRequest $request
|
||||
* @param User $user
|
||||
* @return Response|mixed
|
||||
*/
|
||||
public function attach(AttachCompanyUserRequest $request, User $user)
|
||||
{
|
||||
$company = auth()->user()->company();
|
||||
|
||||
$user->companies()->attach(
|
||||
$company->id,
|
||||
array_merge(
|
||||
$request->all(),
|
||||
[
|
||||
'account_id' => $company->account->id,
|
||||
'notifications' => CompanySettings::notificationDefaults(),
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$ct = CreateCompanyToken::dispatchNow($company, $user, 'User token created by'.auth()->user()->present()->name());
|
||||
|
||||
return $this->itemResponse($user->fresh());
|
||||
}
|
||||
|
||||
/**
|
||||
* Detach an existing user to a company.
|
||||
*
|
||||
|
45
app/Http/Requests/OneTimeToken/OneTimeRouterRequest.php
Normal file
45
app/Http/Requests/OneTimeToken/OneTimeRouterRequest.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?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\OneTimeToken;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
|
||||
class OneTimeRouterRequest extends Request
|
||||
{
|
||||
/**
|
||||
* 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 [
|
||||
'hash' => 'required',
|
||||
];
|
||||
}
|
||||
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
// $input = $this->all();
|
||||
// $this->replace($input);
|
||||
}
|
||||
}
|
45
app/Http/Requests/OneTimeToken/OneTimeTokenRequest.php
Normal file
45
app/Http/Requests/OneTimeToken/OneTimeTokenRequest.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?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\OneTimeToken;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
|
||||
class OneTimeTokenRequest extends Request
|
||||
{
|
||||
/**
|
||||
* 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 [
|
||||
'context' => 'required',
|
||||
];
|
||||
}
|
||||
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
// $input = $this->all();
|
||||
// $this->replace($input);
|
||||
}
|
||||
}
|
@ -14,6 +14,7 @@ namespace App\Http\Requests\User;
|
||||
use App\DataMapper\DefaultSettings;
|
||||
use App\Factory\UserFactory;
|
||||
use App\Http\Requests\Request;
|
||||
use App\Http\ValidationRules\User\AttachableUser;
|
||||
use App\Http\ValidationRules\ValidUserForCompany;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\User;
|
||||
@ -39,9 +40,9 @@ class StoreUserRequest extends Request
|
||||
$rules['last_name'] = 'required|string|max:100';
|
||||
|
||||
if (config('ninja.db.multi_db_enabled')) {
|
||||
$rules['email'] = ['email', new ValidUserForCompany(), Rule::unique('users')->ignore(auth()->user()->company()->account_id, 'account_id')];
|
||||
$rules['email'] = ['email', new ValidUserForCompany(), new AttachableUser()];
|
||||
} else {
|
||||
$rules['email'] = ['email',Rule::unique('users')->ignore(auth()->user()->company()->account_id, 'account_id')];
|
||||
$rules['email'] = ['email', new AttachableUser()];
|
||||
}
|
||||
|
||||
|
||||
|
72
app/Http/ValidationRules/User/AttachableUser.php
Normal file
72
app/Http/ValidationRules/User/AttachableUser.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?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\ValidationRules\User;
|
||||
|
||||
use App\Models\CompanyUser;
|
||||
use App\Models\User;
|
||||
use Illuminate\Contracts\Validation\Rule;
|
||||
|
||||
/**
|
||||
* Class AttachableUser.
|
||||
*/
|
||||
class AttachableUser implements Rule
|
||||
{
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
/**
|
||||
* @param string $attribute
|
||||
* @param mixed $value
|
||||
* @return bool
|
||||
*/
|
||||
public function passes($attribute, $value)
|
||||
{
|
||||
return $this->checkUserIsAttachable($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function message()
|
||||
{
|
||||
return "Cannot add the same user to the same company";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $user_id
|
||||
* @return bool
|
||||
*/
|
||||
private function checkUserIsAttachable($email) : bool
|
||||
{
|
||||
if (empty($email)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$user = User::where('email', $email)->first();
|
||||
|
||||
if(!$user)
|
||||
return true;
|
||||
|
||||
$user_already_attached = CompanyUser::query()
|
||||
->where('user_id', $user->id)
|
||||
->where('account_id',$user->account_id)
|
||||
->where('company_id', auth()->user()->company()->id)
|
||||
->exists();
|
||||
|
||||
if($user_already_attached)
|
||||
return false;
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -49,7 +49,7 @@ class CompanyUser extends Pivot
|
||||
'shop_restricted',
|
||||
];
|
||||
|
||||
protected $touches = [];
|
||||
protected $touches = ['user'];
|
||||
|
||||
public function getEntityType()
|
||||
{
|
||||
|
@ -141,9 +141,9 @@ class UserRepository extends BaseRepository
|
||||
|
||||
event(new UserWasDeleted($user, auth()->user(), $company, Ninja::eventVars()));
|
||||
|
||||
$user->is_deleted = true;
|
||||
$user->save();
|
||||
$user->delete();
|
||||
// $user->is_deleted = true;
|
||||
// $user->save();
|
||||
// $user->delete();
|
||||
|
||||
|
||||
return $user->fresh();
|
||||
|
@ -39,11 +39,11 @@ class ApplyNumber extends AbstractService
|
||||
|
||||
switch ($this->client->getSetting('counter_number_applied')) {
|
||||
case 'when_saved':
|
||||
$this->invoice->number = $this->getNextInvoiceNumber($this->client, $this->invoice);
|
||||
$this->invoice->number = $this->getNextInvoiceNumber($this->client, $this->invoice, $this->invoice->recurring_id);
|
||||
break;
|
||||
case 'when_sent':
|
||||
if ($this->invoice->status_id == Invoice::STATUS_SENT) {
|
||||
$this->invoice->number = $this->getNextInvoiceNumber($this->client, $this->invoice);
|
||||
$this->invoice->number = $this->getNextInvoiceNumber($this->client, $this->invoice, $this->invoice->recurring_id);
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -445,7 +445,7 @@ class Design extends BaseDesign
|
||||
]],
|
||||
['element' => 'img', 'properties' => ['hidden' => $this->client->getSetting('signature_on_pdf'), 'style' => 'max-width: 50%; height: auto;', 'src' => '$contact.signature']],
|
||||
['element' => 'div', 'properties' => ['style' => 'margin-top: 1.5rem; display: flex; align-items: flex-start;'], 'elements' => [
|
||||
['element' => 'img', 'properties' => ['src' => '$invoiceninja.whitelabel', 'style' => 'height: 4rem;', 'hidden' => $this->entity->user->account->isPaid() ? 'true' : 'false', 'id' => 'invoiceninja-whitelabel-logo']],
|
||||
['element' => 'img', 'properties' => ['src' => '$invoiceninja.whitelabel', 'style' => 'height: 2.5rem;', 'hidden' => $this->entity->user->account->isPaid() ? 'true' : 'false', 'id' => 'invoiceninja-whitelabel-logo']],
|
||||
]],
|
||||
]],
|
||||
['element' => 'div', 'properties' => ['class' => 'totals-table-right-side'], 'elements' => []],
|
||||
|
@ -33,26 +33,11 @@ class ApplyNumber extends AbstractService
|
||||
/* Recurring numbers are set when saved */
|
||||
public function run()
|
||||
{
|
||||
if ($this->recurring_entity->number != '') {
|
||||
if ($this->recurring_entity->number != '')
|
||||
return $this->recurring_entity;
|
||||
}
|
||||
|
||||
|
||||
$this->recurring_entity->number = $this->getNextRecurringInvoiceNumber($this->client);
|
||||
|
||||
|
||||
// switch ($this->client->getSetting('counter_number_applied')) {
|
||||
// case 'when_saved':
|
||||
// $this->recurring_entity->number = $this->getNextRecurringInvoiceNumber($this->client);
|
||||
// break;
|
||||
// case 'when_sent':
|
||||
// break;
|
||||
|
||||
// default:
|
||||
// $this->recurring_entity->number = $this->getNextRecurringInvoiceNumber($this->client);
|
||||
// break;
|
||||
// }
|
||||
|
||||
return $this->recurring_entity;
|
||||
}
|
||||
}
|
||||
|
@ -125,10 +125,12 @@ class HtmlEngine
|
||||
$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')];
|
||||
$data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->entity->client->date_format(), $this->entity->client->locale()) ?: ' ', 'label' => ctrans('texts.invoice_date')];
|
||||
|
||||
if($this->entity->project()->exists())
|
||||
if($this->entity->project()->exists()) {
|
||||
$data['$project.name'] = ['value' => $this->entity->project->name, 'label' => ctrans('texts.project_name')];
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->entity_string == 'quote') {
|
||||
$data['$entity'] = ['value' => '', 'label' => ctrans('texts.quote')];
|
||||
@ -137,6 +139,7 @@ class HtmlEngine
|
||||
$data['$terms'] = &$data['$entity.terms'];
|
||||
$data['$view_link'] = ['value' => '<a class="button" href="'.$this->invitation->getLink().'">'.ctrans('texts.view_quote').'</a>', 'label' => ctrans('texts.view_quote')];
|
||||
$data['$view_url'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_quote')];
|
||||
$data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->entity->client->date_format(), $this->entity->client->locale()) ?: ' ', 'label' => ctrans('texts.quote_date')];
|
||||
}
|
||||
|
||||
if ($this->entity_string == 'credit') {
|
||||
@ -147,6 +150,7 @@ class HtmlEngine
|
||||
$data['$view_link'] = ['value' => '<a class="button" href="'.$this->invitation->getLink().'">'.ctrans('texts.view_credit').'</a>', 'label' => ctrans('texts.view_credit')];
|
||||
$data['$view_url'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_credit')];
|
||||
// $data['$view_link'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_credit')];
|
||||
$data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->entity->client->date_format(), $this->entity->client->locale()) ?: ' ', 'label' => ctrans('texts.credit_date')];
|
||||
}
|
||||
|
||||
$data['$entity_number'] = &$data['$number'];
|
||||
@ -249,7 +253,7 @@ class HtmlEngine
|
||||
|
||||
$data['$client.balance'] = ['value' => Number::formatMoney($this->client->balance, $this->client), 'label' => ctrans('texts.account_balance')];
|
||||
$data['$client_balance'] = ['value' => Number::formatMoney($this->client->balance, $this->client), 'label' => ctrans('texts.account_balance')];
|
||||
$data['$paid_to_date'] = ['value' => Number::formatMoney($this->client->paid_to_date, $this->client), 'label' => ctrans('texts.paid_to_date')];
|
||||
$data['$paid_to_date'] = ['value' => Number::formatMoney($this->entity->paid_to_date, $this->client), 'label' => ctrans('texts.paid_to_date')];
|
||||
|
||||
$data['$contact.full_name'] = ['value' => $this->contact->present()->name(), 'label' => ctrans('texts.name')];
|
||||
$data['$contact.email'] = ['value' => $this->contact->email, 'label' => ctrans('texts.email')];
|
||||
@ -346,7 +350,7 @@ class HtmlEngine
|
||||
|
||||
$data['$font_size'] = ['value' => $this->settings->font_size . 'px', 'label' => ''];
|
||||
|
||||
$data['$invoiceninja.whitelabel'] = ['value' => 'https://raw.githubusercontent.com/invoiceninja/invoiceninja/v5-develop/public/images/created-by-invoiceninja-new.png', 'label' => ''];
|
||||
$data['$invoiceninja.whitelabel'] = ['value' => 'https://raw.githubusercontent.com/invoiceninja/invoiceninja/v5-develop/public/images/new_logo.png', 'label' => ''];
|
||||
|
||||
$data['$primary_color'] = ['value' => $this->settings->primary_color, 'label' => ''];
|
||||
$data['$secondary_color'] = ['value' => $this->settings->secondary_color, 'label' => ''];
|
||||
|
@ -35,7 +35,7 @@ trait GeneratesCounter
|
||||
|
||||
|
||||
|
||||
private function getNextEntityNumber($entity, Client $client)
|
||||
private function getNextEntityNumber($entity, Client $client, $is_recurring = false)
|
||||
{
|
||||
$prefix = '';
|
||||
|
||||
@ -75,7 +75,7 @@ trait GeneratesCounter
|
||||
|
||||
$padding = $client->getSetting('counter_padding');
|
||||
|
||||
if($entity instanceof Invoice && $entity && $entity->recurring_id)
|
||||
if($is_recurring)
|
||||
$prefix = $client->getSetting('recurring_number_prefix');
|
||||
|
||||
$entity_number = $this->checkEntityNumber($entity, $client, $counter, $padding, $pattern, $prefix);
|
||||
@ -154,9 +154,9 @@ trait GeneratesCounter
|
||||
* @param Invoice|null $invoice
|
||||
* @return string The next invoice number.
|
||||
*/
|
||||
public function getNextInvoiceNumber(Client $client, ?Invoice $invoice) :string
|
||||
public function getNextInvoiceNumber(Client $client, ?Invoice $invoice, $is_recurring = false) :string
|
||||
{
|
||||
return $this->getNextEntityNumber(Invoice::class, $client);
|
||||
return $this->getNextEntityNumber(Invoice::class, $client, $is_recurring);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -13,7 +13,7 @@ return [
|
||||
'require_https' => env('REQUIRE_HTTPS', true),
|
||||
'app_url' => rtrim(env('APP_URL', ''), '/'),
|
||||
'app_domain' => env('APP_DOMAIN', ''),
|
||||
'app_version' => '5.1.13',
|
||||
'app_version' => '5.1.14',
|
||||
'minimum_client_version' => '5.0.16',
|
||||
'terms_version' => '1.0.1',
|
||||
'api_secret' => env('API_SECRET', false),
|
||||
|
44
package-lock.json
generated
44
package-lock.json
generated
@ -2857,6 +2857,24 @@
|
||||
"sha.js": "^2.4.8"
|
||||
}
|
||||
},
|
||||
"create-html-element": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/create-html-element/-/create-html-element-2.1.0.tgz",
|
||||
"integrity": "sha512-ofbOpJh3GSDsyINuqppupKRUcQHnXSyvwvk0F5DlEtwKwb+thdFoJAtYczy7bIZWdsQjZfADUc38pF4gVd0o+Q==",
|
||||
"requires": {
|
||||
"escape-goat": "^1.3.0",
|
||||
"html-tags": "^2.0.0",
|
||||
"stringify-attributes": "^1.0.0",
|
||||
"type-fest": "^0.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"html-tags": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/html-tags/-/html-tags-2.0.0.tgz",
|
||||
"integrity": "sha1-ELMKOGCF9Dzt41PMj6fLDe7qZos="
|
||||
}
|
||||
}
|
||||
},
|
||||
"credit-card-type": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/credit-card-type/-/credit-card-type-8.3.0.tgz",
|
||||
@ -3730,6 +3748,11 @@
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
|
||||
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw=="
|
||||
},
|
||||
"escape-goat": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-1.3.0.tgz",
|
||||
"integrity": "sha512-E2nU1Y39N5UgfLU8qwMlK0vZrZprIwWLeVmDYN8wd/e37hMtGzu2w1DBiREts0XHfgyZEQlj/hYr0H0izF0HDQ=="
|
||||
},
|
||||
"escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
@ -5721,6 +5744,14 @@
|
||||
"integrity": "sha1-eZllXoZGwX8In90YfRUNMyTVRRM=",
|
||||
"dev": true
|
||||
},
|
||||
"linkify-urls": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/linkify-urls/-/linkify-urls-3.1.1.tgz",
|
||||
"integrity": "sha512-sRxMSunCnLFtZ4iVkMqHhZKSJ3MC/nRAvej8Ou3pEEEPBL0iVN91mZvdFREKcGv3VNcakbT4qsfOnnWMEbA59w==",
|
||||
"requires": {
|
||||
"create-html-element": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"listr": {
|
||||
"version": "0.14.3",
|
||||
"resolved": "https://registry.npmjs.org/listr/-/listr-0.14.3.tgz",
|
||||
@ -8882,6 +8913,14 @@
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"stringify-attributes": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/stringify-attributes/-/stringify-attributes-1.0.0.tgz",
|
||||
"integrity": "sha1-nosvmpRn57SAk8shJOvBwX5jgsU=",
|
||||
"requires": {
|
||||
"escape-goat": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||
@ -9283,6 +9322,11 @@
|
||||
"resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
|
||||
"integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg=="
|
||||
},
|
||||
"type-fest": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz",
|
||||
"integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ=="
|
||||
},
|
||||
"type-is": {
|
||||
"version": "1.6.18",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||
|
@ -26,6 +26,7 @@
|
||||
"cross-env": "^7.0.3",
|
||||
"jsignature": "^2.1.3",
|
||||
"laravel-mix": "^5.0.9",
|
||||
"linkify-urls": "^3.1.1",
|
||||
"lodash": "^4.17.20",
|
||||
"resolve-url-loader": "^3.1.2",
|
||||
"sass": "^1.32.7",
|
||||
|
2
public/css/app.css
vendored
2
public/css/app.css
vendored
File diff suppressed because one or more lines are too long
4
public/flutter_service_worker.js
vendored
4
public/flutter_service_worker.js
vendored
@ -3,7 +3,7 @@ const MANIFEST = 'flutter-app-manifest';
|
||||
const TEMP = 'flutter-temp-cache';
|
||||
const CACHE_NAME = 'flutter-app-cache';
|
||||
const RESOURCES = {
|
||||
"main.dart.js": "cf51da53acb137a76d1c8466fac1c0c1",
|
||||
"main.dart.js": "9642a825d6e3e0331a7b7a8095477684",
|
||||
"icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed",
|
||||
"icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35",
|
||||
"favicon.ico": "51636d3a390451561744c42188ccd628",
|
||||
@ -29,7 +29,7 @@ const RESOURCES = {
|
||||
"assets/assets/images/google-icon.png": "0f118259ce403274f407f5e982e681c3",
|
||||
"assets/assets/images/logo.png": "090f69e23311a4b6d851b3880ae52541",
|
||||
"assets/packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf": "3e722fd57a6db80ee119f0e2c230ccff",
|
||||
"version.json": "c71c432fdc63e809b2f63fcc64edd8cd",
|
||||
"version.json": "b7c8971e1ab5b627fd2a4317c52b843e",
|
||||
"manifest.json": "77215c1737c7639764e64a192be2f7b8",
|
||||
"favicon.png": "dca91c54388f52eded692718d5a98b8b",
|
||||
"/": "23224b5e03519aaa87594403d54412cf"
|
||||
|
BIN
public/images/new_logo.png
Normal file
BIN
public/images/new_logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.2 KiB |
2
public/js/clients/linkify-urls.js
vendored
Normal file
2
public/js/clients/linkify-urls.js
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/*! For license information please see linkify-urls.js.LICENSE.txt */
|
||||
!function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="/",r(r.s=16)}({16:function(e,t,r){e.exports=r("cN42")},Ievl:function(e,t,r){"use strict";t.escape=e=>e.replace(/&/g,"&").replace(/"/g,""").replace(/'/g,"'").replace(/</g,"<").replace(/>/g,">"),t.unescape=e=>e.replace(/>/g,">").replace(/</g,"<").replace(/'/g,"'").replace(/"/g,'"').replace(/&/g,"&"),t.escapeTag=function(e){let r=e[0];for(let n=1;n<arguments.length;n++)r=r+t.escape(arguments[n])+e[n];return r},t.unescapeTag=function(e){let r=e[0];for(let n=1;n<arguments.length;n++)r=r+t.unescape(arguments[n])+e[n];return r}},PoD1:function(e,t,r){"use strict";e.exports=r("sW1H")},YIIW:function(e,t,r){"use strict";const n=r("dBjz"),o=r("PoD1"),a=r("Ievl"),u=new Set(o);e.exports=e=>{if((e=Object.assign({name:"div",attributes:{},html:""},e)).html&&e.text)throw new Error("The `html` and `text` options are mutually exclusive");const t=e.text?a.escape(e.text):e.html;let r=`<${e.name}${n(e.attributes)}>`;return u.has(e.name)||(r+=`${t}</${e.name}>`),r}},cN42:function(e,t,r){var n=r("jG5F");document.querySelectorAll("[data-ref=entity-terms]").forEach((function(e){e.innerHTML=n(e.innerText,{attributes:{target:"_blank",class:"text-primary"}})}))},dBjz:function(e,t,r){"use strict";const n=r("Ievl");e.exports=e=>{const t=[];for(const r of Object.keys(e)){let o=e[r];if(!1===o)continue;Array.isArray(o)&&(o=o.join(" "));let a=n.escape(r);!0!==o&&(a+=`="${n.escape(String(o))}"`),t.push(a)}return t.length>0?" "+t.join(" "):""}},jG5F:function(e,t,r){"use strict";const n=r("YIIW"),o=(e,t)=>n({name:"a",attributes:{href:"",...t.attributes,href:e},text:void 0===t.value?e:void 0,html:void 0===t.value?void 0:"function"==typeof t.value?t.value(e):t.value});e.exports=(e,t)=>{if("string"===(t={attributes:{},type:"string",...t}).type)return((e,t)=>e.replace(/((?<!\+)(?:https?(?::\/\/))(?:www\.)?(?:[a-zA-Z\d-_.]+(?:(?:\.|@)[a-zA-Z\d]{2,})|localhost)(?:(?:[-a-zA-Z\d:%_+.~#*$!?&//=@]*)(?:[,](?![\s]))*)*)/g,e=>o(e,t)))(e,t);if("dom"===t.type)return((e,t)=>{const r=document.createDocumentFragment();for(const[a,u]of Object.entries(e.split(/((?<!\+)(?:https?(?::\/\/))(?:www\.)?(?:[a-zA-Z\d-_.]+(?:(?:\.|@)[a-zA-Z\d]{2,})|localhost)(?:(?:[-a-zA-Z\d:%_+.~#*$!?&//=@]*)(?:[,](?![\s]))*)*)/g)))a%2?r.append((n=o(u,t),document.createRange().createContextualFragment(n))):u.length>0&&r.append(u);var n;return r})(e,t);throw new Error("The type option must be either `dom` or `string`")}},sW1H:function(e){e.exports=JSON.parse('["area","base","br","col","embed","hr","img","input","link","menuitem","meta","param","source","track","wbr"]')}});
|
9
public/js/clients/linkify-urls.js.LICENSE.txt
Normal file
9
public/js/clients/linkify-urls.js.LICENSE.txt
Normal file
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
2
public/js/clients/shared/pdf.js
vendored
2
public/js/clients/shared/pdf.js
vendored
File diff suppressed because one or more lines are too long
141112
public/main.dart.js
vendored
141112
public/main.dart.js
vendored
File diff suppressed because one or more lines are too long
@ -1,8 +1,9 @@
|
||||
{
|
||||
"/js/app.js": "/js/app.js?id=696e8203d5e8e7cf5ff5",
|
||||
"/css/app.css": "/css/app.css?id=58736e43b16ddde82ba9",
|
||||
"/css/app.css": "/css/app.css?id=745170b7d7a4dc7469f2",
|
||||
"/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",
|
||||
"/js/clients/payment_methods/authorize-authorize-card.js": "/js/clients/payment_methods/authorize-authorize-card.js?id=206d7de4ac97612980ff",
|
||||
"/js/clients/payments/authorize-credit-card-payment.js": "/js/clients/payments/authorize-credit-card-payment.js?id=a376eff2227da398b0ba",
|
||||
"/js/clients/payments/card-js.min.js": "/js/clients/payments/card-js.min.js?id=5469146cd629ea1b5c20",
|
||||
@ -14,7 +15,7 @@
|
||||
"/js/clients/quotes/action-selectors.js": "/js/clients/quotes/action-selectors.js?id=1b8f9325aa6e8595e7fa",
|
||||
"/js/clients/quotes/approve.js": "/js/clients/quotes/approve.js?id=85bcae0a646882e56b12",
|
||||
"/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=fa54bb4229aba6b0817c",
|
||||
"/js/clients/shared/pdf.js": "/js/clients/shared/pdf.js?id=fc3055d6a099f523ea98",
|
||||
"/js/setup/setup.js": "/js/setup/setup.js?id=44bc4a71fc1d3606fc8e",
|
||||
"/css/card-js.min.css": "/css/card-js.min.css?id=62afeb675235451543ad"
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
{"app_name":"invoiceninja_flutter","version":"5.0.43","build_number":"43"}
|
||||
{"app_name":"invoiceninja_flutter","version":"5.0.44","build_number":"44"}
|
19
resources/js/clients/linkify-urls.js
vendored
Normal file
19
resources/js/clients/linkify-urls.js
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
const linkifyUrls = require('linkify-urls');
|
||||
|
||||
document
|
||||
.querySelectorAll('[data-ref=entity-terms]')
|
||||
.forEach((text) => {
|
||||
text.innerHTML = linkifyUrls(text.innerText, {
|
||||
attributes: {target: '_blank', class: 'text-primary'}
|
||||
});
|
||||
});
|
4
resources/js/clients/shared/pdf.js
vendored
4
resources/js/clients/shared/pdf.js
vendored
@ -15,7 +15,7 @@ class PDF {
|
||||
this.context = canvas.getContext('2d');
|
||||
this.currentPage = 1;
|
||||
this.maxPages = 1;
|
||||
this.currentScale = 1.75;
|
||||
this.currentScale = 0.75;
|
||||
this.currentScaleText = document.getElementById('zoom-level');
|
||||
|
||||
if (matchMedia('only screen and (max-width: 480px)').matches) {
|
||||
@ -125,7 +125,7 @@ class PDF {
|
||||
}
|
||||
}
|
||||
|
||||
const url = document.querySelector("meta[name='pdf-url'").content;
|
||||
const url = document.querySelector("meta[name='pdf-url']").content;
|
||||
const canvas = document.getElementById('pdf-placeholder');
|
||||
|
||||
new PDF(url, canvas).prepare().handle();
|
||||
|
@ -3968,8 +3968,8 @@ $LANG = array(
|
||||
'list_of_recurring_invoices' => 'List of recurring invoices',
|
||||
'details_of_recurring_invoice' => 'Here are some details about recurring invoice',
|
||||
'cancellation' => 'Cancellation',
|
||||
'about_cancellation' => 'In case you want to stop the recurring invoice,\n please click the request the cancellation.',
|
||||
'cancellation_warning' => 'Warning! You are requesting a cancellation of this service.\n Your service may be cancelled with no further notification to you.',
|
||||
'about_cancellation' => 'In case you want to stop the recurring invoice, please click the request the cancellation.',
|
||||
'cancellation_warning' => 'Warning! You are requesting a cancellation of this service. Your service may be cancelled with no further notification to you.',
|
||||
'cancellation_pending' => 'Cancellation pending, we\'ll be in touch!',
|
||||
'list_of_payments' => 'List of payments',
|
||||
'payment_details' => 'Details of the payment',
|
||||
@ -4143,6 +4143,11 @@ $LANG = array(
|
||||
'hello' => 'Hello',
|
||||
'group_documents' => 'Group documents',
|
||||
'quote_approval_confirmation_label' => 'Are you sure you want to approve this quote?',
|
||||
|
||||
'click_agree_to_accept_terms' => 'Click "Agree" to Accept Terms.',
|
||||
'agree' => 'Agree',
|
||||
|
||||
'pending_approval' => 'Pending Approval',
|
||||
);
|
||||
|
||||
return $LANG;
|
||||
|
@ -41,10 +41,6 @@
|
||||
color: #AAA9A9;
|
||||
}
|
||||
|
||||
#company-details > * {
|
||||
margin-bottom: 0.8rem;
|
||||
}
|
||||
|
||||
#company-address {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -72,10 +68,6 @@
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
#client-details > * {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
#client-details > p:nth-child(1) {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
@ -65,8 +65,13 @@
|
||||
margin-top: 0.8rem;
|
||||
}
|
||||
|
||||
.header-right-side-wrapper-right {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.header-wrapper .company-logo {
|
||||
height: 5rem;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.entity-label {
|
||||
@ -199,11 +204,12 @@
|
||||
</div>
|
||||
|
||||
<div class="header-right-side-wrapper">
|
||||
<div>
|
||||
<div class="header-right-side-wrapper-left">
|
||||
<p class="header-text-label">$to_label:</p>
|
||||
<div id="client-details"></div>
|
||||
</div>
|
||||
|
||||
<div class="header-right-side-wrapper-right">
|
||||
<img
|
||||
class="company-logo"
|
||||
src="$company.logo"
|
||||
@ -211,6 +217,7 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1 class="entity-label">$entity_label</h1>
|
||||
<div class="entity-details-wrapper">
|
||||
|
@ -97,6 +97,12 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#product-table th + th,
|
||||
#delivery-note-table th + th,
|
||||
#task-table th + th {
|
||||
border-left: 2px solid white;
|
||||
}
|
||||
|
||||
#product-table > thead > tr > th,
|
||||
#delivery-note-table > thead > tr > th,
|
||||
#task-table > thead > tr > th {
|
||||
|
@ -8,11 +8,13 @@
|
||||
|
||||
@section('body')
|
||||
<div class="grid lg:grid-cols-3">
|
||||
@if(\App\Models\Account::count() > 0 && !\App\Models\Account::first()->isPaid())
|
||||
<div class="hidden lg:block col-span-1 bg-red-100 h-screen">
|
||||
<img src="https://www.invoiceninja.com/wp-content/uploads/2018/04/bg-home2018b.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">
|
||||
<div class="flex flex-col">
|
||||
|
@ -3,11 +3,13 @@
|
||||
|
||||
@section('body')
|
||||
<div class="grid lg:grid-cols-3">
|
||||
@if(\App\Models\Account::count() > 0 && !\App\Models\Account::first()->isPaid())
|
||||
<div class="hidden lg:block col-span-1 bg-red-100 h-screen">
|
||||
<img src="https://www.invoiceninja.com/wp-content/uploads/2018/04/bg-home2018b.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 w-1/2 md:w-1/3 lg:w-1/4">
|
||||
<div class="flex flex-col">
|
||||
|
@ -3,11 +3,13 @@
|
||||
|
||||
@section('body')
|
||||
<div class="grid lg:grid-cols-3">
|
||||
@if(\App\Models\Account::count() > 0 && !\App\Models\Account::first()->isPaid())
|
||||
<div class="hidden lg:block col-span-1 bg-red-100 h-screen">
|
||||
<img src="https://www.invoiceninja.com/wp-content/uploads/2018/04/bg-home2018b.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 w-1/2 md:w-1/3 lg:w-1/4">
|
||||
<div class="flex flex-col">
|
||||
|
@ -15,6 +15,11 @@
|
||||
<table class="min-w-full shadow rounded border border-gray-200 mt-4 credits-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-white uppercase border-b border-gray-200 bg-primary">
|
||||
<span role="button" wire:click="sortBy('number')" class="cursor-pointer">
|
||||
{{ ctrans('texts.credit_number') }}
|
||||
</span>
|
||||
</th>
|
||||
<th class="px-6 py-3 border-b border-gray-200 bg-primary text-left text-xs leading-4 font-medium text-white uppercase tracking-wider">
|
||||
<span role="button" wire:click="sortBy('amount')" class="cursor-pointer">
|
||||
{{ ctrans('texts.amount') }}
|
||||
@ -32,7 +37,7 @@
|
||||
</th>
|
||||
<th class="px-6 py-3 border-b border-gray-200 bg-primary text-left text-xs leading-4 font-medium text-white uppercase tracking-wider">
|
||||
<span role="button" wire:click="sortBy('public_notes')" class="cursor-pointer">
|
||||
{{ ctrans('texts.public_notes') }}
|
||||
{{ ctrans('texts.notes') }}
|
||||
</span>
|
||||
</th>
|
||||
<th class="px-6 py-3 border-b border-gray-200 bg-primary"></th>
|
||||
@ -41,6 +46,9 @@
|
||||
<tbody>
|
||||
@forelse($credits as $credit)
|
||||
<tr class="bg-white group hover:bg-gray-100">
|
||||
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||
{{ $credit->number }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||
{{ App\Utils\Number::formatMoney($credit->amount, $credit->client) }}
|
||||
</td>
|
||||
|
@ -20,7 +20,7 @@
|
||||
</div>
|
||||
<div class="mr-3">
|
||||
<input wire:model="status" value="overdue" type="checkbox" class="cursor-pointer form-checkbox" id="overdue-checkbox">
|
||||
<label for="overdue-checkbox" class="text-sm cursor-pointer">{{ ctrans('texts.overdue') }}</label>
|
||||
<label for="overdue-checkbox" class="text-sm cursor-pointer">{{ ctrans('texts.past_due') }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -10,10 +10,6 @@
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div class="mr-3">
|
||||
<input wire:model="status" value="{{ App\Models\Quote::STATUS_DRAFT }}" type="checkbox" class="cursor-pointer form-checkbox" id="draft-checkbox">
|
||||
<label for="draft-checkbox" class="text-sm cursor-pointer">{{ ctrans('texts.status_draft') }}</label>
|
||||
</div>
|
||||
<div class="mr-3">
|
||||
<input wire:model="status" value="{{ App\Models\Quote::STATUS_SENT }}" value="sent" type="checkbox" class="cursor-pointer form-checkbox" id="sent-checkbox">
|
||||
<label for="sent-checkbox" class="text-sm cursor-pointer">{{ ctrans('texts.status_pending') }}</label>
|
||||
|
@ -12,23 +12,23 @@
|
||||
<div class="mt-4">
|
||||
@foreach($entities as $entity)
|
||||
<div class="mb-4">
|
||||
<h4 class="leading-6 font-medium text-gray-900">{{ $entity_type }} {{ $entity->number }}:</h4>
|
||||
<p class="text-sm leading-6 font-medium text-gray-500">{{ $entity_type }} {{ $entity->number }}:</p>
|
||||
@if($entity->terms)
|
||||
<p class="text-sm leading-5 text-gray-500">{!! $entity->terms !!}</p>
|
||||
<h5 data-ref="entity-terms" class="text-sm leading-5 text-gray-900">{!! $entity->terms !!}</h5>
|
||||
@else
|
||||
<i class="text-sm leading-5 text-gray-500">{{ ctrans('texts.not_specified') }}</i>
|
||||
@endif
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
<p class="mt-4 block text-sm text-gray-900">{{ ctrans('texts.by_clicking_next_you_accept_terms') }}</p>
|
||||
<p class="mt-4 block text-sm text-gray-900">{{ ctrans('texts.click_agree_to_accept_terms') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||
<div class="flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto">
|
||||
<button type="button" id="accept-terms-button" class="button button-primary bg-primary">
|
||||
{{ ctrans('texts.next_step') }}
|
||||
{{ ctrans('texts.agree') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto">
|
||||
@ -39,3 +39,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@push('footer')
|
||||
<script src="{{ asset('js/clients/linkify-urls.js') }}" defer></script>
|
||||
@endpush
|
||||
|
@ -38,12 +38,6 @@
|
||||
{{ ctrans('texts.invoice_number_placeholder', ['invoice' => $invoice->number])}}
|
||||
- {{ ctrans('texts.unpaid') }}
|
||||
</h3>
|
||||
<div class="mt-2 max-w-xl text-sm leading-5 text-gray-500">
|
||||
<p translate>
|
||||
{{ ctrans('texts.invoice_still_unpaid') }}
|
||||
<!-- This invoice is still not paid. Click the button to complete the payment. -->
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 sm:mt-0 sm:ml-6 sm:flex-shrink-0 sm:flex sm:items-center">
|
||||
<div class="inline-flex rounded-md shadow-sm">
|
||||
@ -180,7 +174,7 @@
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<iframe src="{{ $invoice->pdf_file_path() }}" class="h-screen w-full border-0 sm:hidden lg:block mt-4"></iframe>
|
||||
<iframe src="{{ $invoice->pdf_file_path() }}" class="h-screen w-full border-0 hidden lg:block mt-4"></iframe>
|
||||
|
||||
<div class="flex justify-center">
|
||||
<canvas id="pdf-placeholder" class="shadow rounded-lg bg-white lg:hidden mt-4 p-4"></canvas>
|
||||
|
@ -31,6 +31,10 @@
|
||||
<div>
|
||||
@yield('gateway_content')
|
||||
</div>
|
||||
<span class="block mx-4 mb-4 text-xs inline-flex items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-green-600"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 10 0v4"></path></svg>
|
||||
<span class="ml-1">Secure 256-bit encryption</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
@ -3,9 +3,6 @@
|
||||
<div class="md:col-span-1">
|
||||
<div class="sm:px-0">
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900">{{ ctrans('texts.profile') }}</h3>
|
||||
<p class="mt-1 text-sm leading-5 text-gray-500">
|
||||
{{ ctrans('texts.client_information_text') }}
|
||||
</p>
|
||||
</div>
|
||||
</div> <!-- End of left-side -->
|
||||
|
||||
|
@ -3,9 +3,6 @@
|
||||
<div class="md:col-span-1">
|
||||
<div class="sm:px-0">
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900">{{ ctrans('texts.name_website_logo') }}</h3>
|
||||
<p class="mt-1 text-sm leading-5 text-gray-500">
|
||||
{{ ctrans('texts.make_sure_use_full_link') }}
|
||||
</p>
|
||||
</div>
|
||||
</div> <!-- End of left side -->
|
||||
|
||||
@ -34,7 +31,10 @@
|
||||
@enderror
|
||||
</div>
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<div class="inline-flex items-center">
|
||||
<label for="website" class="input-label">{{ ctrans('texts.website') }}</label>
|
||||
<span class="text-xs ml-2 text-gray-600">E.g. https://invoiceninja.com</span>
|
||||
</div>
|
||||
<input id="website" class="input w-full" name="website" wire:model.defer="website" />
|
||||
@error('website')
|
||||
<div class="validation validation-fail">
|
||||
|
@ -2,10 +2,7 @@
|
||||
<div class="md:grid md:grid-cols-3 md:gap-6">
|
||||
<div class="md:col-span-1">
|
||||
<div class="sm:px-0">
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900">{{ ctrans('texts.personal_address') }}</h3>
|
||||
<p class="mt-1 text-sm leading-5 text-gray-500">
|
||||
{{ ctrans('texts.enter_your_personal_address') }}
|
||||
</p>
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900">{{ ctrans('texts.billing_address') }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 md:mt-0 md:col-span-2">
|
||||
|
@ -3,9 +3,6 @@
|
||||
<div class="md:col-span-1">
|
||||
<div class="sm:px-0">
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900">{{ ctrans('texts.shipping_address') }}</h3>
|
||||
<p class="mt-1 text-sm leading-5 text-gray-500">
|
||||
{{ ctrans('texts.enter_your_shipping_address') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 md:mt-0 md:col-span-2">
|
||||
|
@ -7,16 +7,9 @@
|
||||
<div class="bg-white shadow sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<div class="sm:flex sm:items-start sm:justify-between">
|
||||
<div>
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||
{{ ctrans('texts.waiting_for_approval') }}
|
||||
{{ ctrans('texts.pending_approval') }}
|
||||
</h3>
|
||||
<div class="mt-2 max-w-xl text-sm leading-5 text-gray-500">
|
||||
<p>
|
||||
{{ ctrans('texts.quote_still_not_approved') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-5 sm:mt-0 sm:ml-6 sm:flex-shrink-0 sm:flex sm:items-center">
|
||||
@yield('quote-not-approved-right-side')
|
||||
|
@ -69,11 +69,11 @@
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center lg:hidden">
|
||||
<canvas id="pdf-placeholder" class="shadow rounded-lg bg-white mt-4 p-4"></canvas>
|
||||
<div class="flex justify-center">
|
||||
<canvas id="pdf-placeholder" class="shadow rounded-lg bg-white lg:hidden mt-4 p-4"></canvas>
|
||||
</div>
|
||||
|
||||
<iframe src="{{ $quote->pdf_file_path() }}" class="h-screen w-full border-0 mt-4"></iframe>
|
||||
<iframe src="{{ $quote->pdf_file_path() }}" class="h-screen w-full border-0 hidden lg:block mt-4"></iframe>
|
||||
@endsection
|
||||
|
||||
@section('footer')
|
||||
|
@ -93,6 +93,8 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a
|
||||
Route::post('migration/purge_save_settings/{company}', 'MigrationController@purgeCompanySaveSettings')->middleware('password_protected');
|
||||
Route::post('migration/start', 'MigrationController@startMigration');
|
||||
|
||||
Route::post('one_time_token', 'OneTimeTokenController@create');
|
||||
|
||||
Route::resource('payments', 'PaymentController'); // name = (payments. index / create / show / update / destroy / edit
|
||||
Route::post('payments/refund', 'PaymentController@refund')->name('payments.refund');
|
||||
Route::post('payments/bulk', 'PaymentController@bulk')->name('payments.bulk');
|
||||
@ -158,8 +160,9 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a
|
||||
Route::get('users', 'UserController@index');
|
||||
Route::put('users/{user}', 'UserController@update')->middleware('password_protected');
|
||||
Route::post('users', 'UserController@store')->middleware('password_protected');
|
||||
Route::post('users/{user}/attach_to_company', 'UserController@attach')->middleware('password_protected');
|
||||
//Route::post('users/{user}/attach_to_company', 'UserController@attach')->middleware('password_protected');
|
||||
Route::delete('users/{user}/detach_from_company', 'UserController@detach')->middleware('password_protected');
|
||||
|
||||
Route::post('users/bulk', 'UserController@bulk')->name('users.bulk')->middleware('password_protected');
|
||||
Route::post('/user/{user}/reconfirm', 'UserController@reconfirm')->middleware('password_protected');
|
||||
|
||||
@ -178,5 +181,6 @@ Route::match(['get', 'post'], 'payment_webhook/{company_key}/{company_gateway_id
|
||||
->name('payment_webhook');
|
||||
|
||||
Route::post('api/v1/postmark_webhook', 'PostMarkController@webhook');
|
||||
Route::get('token_hash_router', 'OneTimeTokenController@router');
|
||||
|
||||
Route::fallback('BaseController@notFound');
|
||||
|
@ -21,6 +21,7 @@ use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Tests\MockAccountData;
|
||||
use Tests\TestCase;
|
||||
|
||||
@ -33,6 +34,8 @@ class UserTest extends TestCase
|
||||
use MockAccountData;
|
||||
use DatabaseTransactions;
|
||||
|
||||
private $default_email = 'attach@gmail.com';
|
||||
|
||||
public function setUp() :void
|
||||
{
|
||||
parent::setUp();
|
||||
@ -45,6 +48,8 @@ class UserTest extends TestCase
|
||||
|
||||
Model::reguard();
|
||||
|
||||
$this->withoutExceptionHandling();
|
||||
|
||||
$this->withoutMiddleware(
|
||||
ThrottleRequests::class,
|
||||
PasswordProtection::class
|
||||
@ -97,13 +102,23 @@ class UserTest extends TestCase
|
||||
$user = UserFactory::create($this->account->id);
|
||||
$user->first_name = 'Test';
|
||||
$user->last_name = 'Palloni';
|
||||
$user->email = $this->default_email;
|
||||
$user->save();
|
||||
|
||||
$data = $user->toArray();
|
||||
|
||||
try {
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
'X-API-PASSWORD' => 'ALongAndBriliantPassword',
|
||||
])->post('/api/v1/users/'.$this->encodePrimaryKey($user->id).'/attach_to_company?include=company_user');
|
||||
])->post('/api/v1/users?include=company_user', $data);
|
||||
|
||||
} catch (ValidationException $e) {
|
||||
$message = json_decode($e->validator->getMessageBag(), 1);
|
||||
nlog($message);
|
||||
$this->assertNotNull($message);
|
||||
}
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
@ -153,12 +168,15 @@ class UserTest extends TestCase
|
||||
$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();
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $company_token->token,
|
||||
])->post('/api/v1/users/'.$this->encodePrimaryKey($new_user->id).'/attach_to_company?include=company_user');
|
||||
])->post('/api/v1/users?include=company_user', $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
|
4
webpack.mix.js
vendored
4
webpack.mix.js
vendored
@ -61,6 +61,10 @@ mix.js("resources/js/app.js", "public/js")
|
||||
.js(
|
||||
"resources/js/clients/shared/multiple-downloads.js",
|
||||
"public/js/clients/shared/multiple-downloads.js"
|
||||
)
|
||||
.js(
|
||||
"resources/js/clients/linkify-urls.js",
|
||||
"public/js/clients/linkify-urls.js"
|
||||
);
|
||||
|
||||
mix.copyDirectory('node_modules/card-js/card-js.min.css', 'public/css/card-js.min.css');
|
||||
|
Loading…
x
Reference in New Issue
Block a user