mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
Merge pull request #8218 from turbo124/v5-develop
Additional docs for mailers
This commit is contained in:
commit
8706d5a382
3
.github/ISSUE_TEMPLATE/bug_report.md
vendored
3
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -15,9 +15,10 @@ https://invoiceninja.github.io/docs/self-host-troubleshooting/ -->
|
||||
- Environment: <!-- Docker/Shared Hosting/ZIP/Other -->
|
||||
|
||||
## Checklist
|
||||
- Can you replicate the issue on our v5 demo site https://demo.invoiceninja.com pr https://react.invoicing.co/demo?
|
||||
- Can you replicate the issue on our v5 demo site https://demo.invoiceninja.com or https://react.invoicing.co/demo?
|
||||
- Have you searched existing issues?
|
||||
- Have you reported this to Slack/forum before posting?
|
||||
- Have you inspected the logs in storage/logs/laravel.log for any errors?
|
||||
|
||||
## Describe the bug
|
||||
<!-- A clear and concise description of the bug. -->
|
||||
|
@ -1 +1 @@
|
||||
5.5.63
|
||||
5.5.64
|
@ -126,6 +126,7 @@ class CheckData extends Command
|
||||
$this->checkVendorSettings();
|
||||
$this->checkClientSettings();
|
||||
$this->checkCompanyTokens();
|
||||
$this->checkUserState();
|
||||
|
||||
if(Ninja::isHosted()){
|
||||
$this->checkAccountStatuses();
|
||||
@ -414,6 +415,16 @@ class CheckData extends Command
|
||||
}
|
||||
}
|
||||
|
||||
private function checkUserState()
|
||||
{
|
||||
User::withTrashed()
|
||||
->where('deleted_at', '0000-00-00 00:00:00.000000')
|
||||
->cursor()
|
||||
->each(function ($user){
|
||||
$user->restore();
|
||||
});
|
||||
}
|
||||
|
||||
private function checkEntityInvitations()
|
||||
{
|
||||
|
||||
|
@ -229,7 +229,7 @@ class CompanySettings extends BaseSettings
|
||||
public $require_quote_signature = false; //@TODO ben to confirm
|
||||
|
||||
//email settings
|
||||
public $email_sending_method = 'default'; //enum 'default','gmail','office365' //@implemented
|
||||
public $email_sending_method = 'default'; //enum 'default','gmail','office365' 'client_postmark', 'client_mailgun'//@implemented
|
||||
|
||||
public $gmail_sending_user_id = '0'; //@implemented
|
||||
|
||||
@ -453,9 +453,15 @@ class CompanySettings extends BaseSettings
|
||||
|
||||
public $show_email_footer = true;
|
||||
|
||||
public $company_logo_size = '65%';
|
||||
public $company_logo_size = '';
|
||||
|
||||
public $show_paid_stamp = false;
|
||||
|
||||
public $show_shipping_address = false;
|
||||
|
||||
public static $casts = [
|
||||
'show_paid_stamp' => 'bool',
|
||||
'show_shipping_address' => 'bool',
|
||||
'company_logo_size' => 'string',
|
||||
'show_email_footer' => 'bool',
|
||||
'email_alignment' => 'string',
|
||||
|
@ -267,7 +267,7 @@ class BaseController extends Controller
|
||||
|
||||
$updated_at = request()->has('updated_at') ? request()->input('updated_at') : 0;
|
||||
|
||||
if ($user->getCompany()->is_large && $updated_at == 0) {
|
||||
if ($user->getCompany()->is_large && $updated_at == 0 && $this->complexPermissionsUser()) {
|
||||
$updated_at = time();
|
||||
}
|
||||
|
||||
@ -613,11 +613,27 @@ class BaseController extends Controller
|
||||
return $this->response($this->manager->createData($resource)->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* In case a user is not an admin and is
|
||||
* able to access multiple companies, then we
|
||||
* need to pass back the mini load only
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function complexPermissionsUser(): bool
|
||||
{
|
||||
//if the user is attached to more than one company AND they are not an admin across all companies
|
||||
if(auth()->user()->company_users()->count() > 1 && (auth()->user()->company_users()->where('is_admin',1)->count() != auth()->user()->company_users()->count()))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function timeConstrainedResponse($query)
|
||||
{
|
||||
$user = auth()->user();
|
||||
|
||||
if ($user->getCompany()->is_large) {
|
||||
if ($user->getCompany()->is_large || $this->complexPermissionsUser()) {
|
||||
$this->manager->parseIncludes($this->mini_load);
|
||||
|
||||
return $this->miniLoadResponse($query);
|
||||
|
@ -218,7 +218,7 @@ class NinjaPlanController extends Controller
|
||||
|
||||
if ($account) {
|
||||
//offer the option to have a free trial
|
||||
if (!$account->is_trial) {
|
||||
if (!$account->plan && !$account->is_trial) {
|
||||
return $this->trial();
|
||||
}
|
||||
|
||||
|
@ -16,12 +16,14 @@ use App\Factory\CreditFactory;
|
||||
use App\Factory\InvoiceFactory;
|
||||
use App\Factory\QuoteFactory;
|
||||
use App\Factory\RecurringInvoiceFactory;
|
||||
use App\Http\Requests\Preview\DesignPreviewRequest;
|
||||
use App\Http\Requests\Preview\PreviewInvoiceRequest;
|
||||
use App\Jobs\Util\PreviewPdf;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Client;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\Credit;
|
||||
use App\Models\GroupSetting;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\InvoiceInvitation;
|
||||
use App\Models\Quote;
|
||||
@ -30,9 +32,9 @@ use App\Repositories\CreditRepository;
|
||||
use App\Repositories\InvoiceRepository;
|
||||
use App\Repositories\QuoteRepository;
|
||||
use App\Repositories\RecurringInvoiceRepository;
|
||||
use App\Services\PdfMaker\Design;
|
||||
use App\Services\PdfMaker\Design as PdfDesignModel;
|
||||
use App\Services\PdfMaker\Design as PdfMakerDesign;
|
||||
use App\Services\PdfMaker\Design;
|
||||
use App\Services\PdfMaker\PdfMaker;
|
||||
use App\Utils\HostedPDF\NinjaPdf;
|
||||
use App\Utils\HtmlEngine;
|
||||
@ -173,8 +175,178 @@ class PreviewController extends BaseController
|
||||
return $this->blankEntity();
|
||||
}
|
||||
|
||||
public function design(DesignPreviewRequest $request)
|
||||
{
|
||||
if(Ninja::isHosted() && $request->getHost() != 'preview.invoicing.co')
|
||||
return response()->json(['message' => 'This server cannot handle this request.'], 400);
|
||||
|
||||
$company = auth()->user()->company();
|
||||
|
||||
MultiDB::setDb($company->db);
|
||||
|
||||
if ($request->input('entity') == 'quote') {
|
||||
$repo = new QuoteRepository();
|
||||
$entity_obj = QuoteFactory::create($company->id, auth()->user()->id);
|
||||
$class = Quote::class;
|
||||
} elseif ($request->input('entity') == 'credit') {
|
||||
$repo = new CreditRepository();
|
||||
$entity_obj = CreditFactory::create($company->id, auth()->user()->id);
|
||||
$class = Credit::class;
|
||||
} elseif ($request->input('entity') == 'recurring_invoice') {
|
||||
$repo = new RecurringInvoiceRepository();
|
||||
$entity_obj = RecurringInvoiceFactory::create($company->id, auth()->user()->id);
|
||||
$class = RecurringInvoice::class;
|
||||
} else { //assume it is either an invoice or a null object
|
||||
$repo = new InvoiceRepository();
|
||||
$entity_obj = InvoiceFactory::create($company->id, auth()->user()->id);
|
||||
$class = Invoice::class;
|
||||
}
|
||||
|
||||
try {
|
||||
DB::connection(config('database.default'))->beginTransaction();
|
||||
|
||||
if ($request->has('entity_id')) {
|
||||
$entity_obj = $class::on(config('database.default'))
|
||||
->with('client.company')
|
||||
->where('id', $this->decodePrimaryKey($request->input('entity_id')))
|
||||
->where('company_id', $company->id)
|
||||
->withTrashed()
|
||||
->first();
|
||||
}
|
||||
|
||||
if($request->has('client_id')) {
|
||||
$client = Client::withTrashed()->find($this->decodePrimaryKey($request->client_id));
|
||||
if($request->settings_type == 'client'){
|
||||
$client->settings = $request->settings;
|
||||
$client->save();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if($request->has('group_id')) {
|
||||
$group = GroupSetting::withTrashed()->find($this->decodePrimaryKey($request->group_id));
|
||||
if($request->settings_type == 'group'){
|
||||
$group->settings = $request->settings;
|
||||
$group->save();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if($request->settings_type == 'company'){
|
||||
$company->settings = $request->settings;
|
||||
$company->save();
|
||||
}
|
||||
|
||||
if($request->has('footer') && !$request->filled('footer') && $request->input('entity') == 'recurring_invoice')
|
||||
$request->merge(['footer' => $company->settings->invoice_footer]);
|
||||
|
||||
if($request->has('terms') && !$request->filled('terms') && $request->input('entity') == 'recurring_invoice')
|
||||
$request->merge(['terms' => $company->settings->invoice_terms]);
|
||||
|
||||
$entity_obj = $repo->save($request->all(), $entity_obj);
|
||||
|
||||
if (! $request->has('entity_id')) {
|
||||
$entity_obj->service()->fillDefaults()->save();
|
||||
}
|
||||
|
||||
App::forgetInstance('translator');
|
||||
$t = app('translator');
|
||||
App::setLocale($entity_obj->client->locale());
|
||||
$t->replace(Ninja::transformTranslations($entity_obj->client->getMergedSettings()));
|
||||
|
||||
$html = new HtmlEngine($entity_obj->invitations()->first());
|
||||
|
||||
$design = \App\Models\Design::find($entity_obj->design_id);
|
||||
|
||||
/* Catch all in case migration doesn't pass back a valid design */
|
||||
if (! $design) {
|
||||
$design = \App\Models\Design::find(2);
|
||||
}
|
||||
|
||||
if ($design->is_custom) {
|
||||
$options = [
|
||||
'custom_partials' => json_decode(json_encode($design->design), true),
|
||||
];
|
||||
$template = new PdfMakerDesign(PdfDesignModel::CUSTOM, $options);
|
||||
} else {
|
||||
$template = new PdfMakerDesign(strtolower($design->name));
|
||||
}
|
||||
|
||||
$variables = $html->generateLabelsAndValues();
|
||||
|
||||
$state = [
|
||||
'template' => $template->elements([
|
||||
'client' => $entity_obj->client,
|
||||
'entity' => $entity_obj,
|
||||
'pdf_variables' => (array) $entity_obj->company->settings->pdf_variables,
|
||||
'$product' => $design->design->product,
|
||||
'variables' => $variables,
|
||||
]),
|
||||
'variables' => $variables,
|
||||
'options' => [
|
||||
'all_pages_header' => $entity_obj->client->getSetting('all_pages_header'),
|
||||
'all_pages_footer' => $entity_obj->client->getSetting('all_pages_footer'),
|
||||
],
|
||||
'process_markdown' => $entity_obj->client->company->markdown_enabled,
|
||||
];
|
||||
|
||||
$maker = new PdfMaker($state);
|
||||
|
||||
$maker
|
||||
->design($template)
|
||||
->build();
|
||||
|
||||
DB::connection(config('database.default'))->rollBack();
|
||||
|
||||
if (request()->query('html') == 'true') {
|
||||
nlog($maker->getCompiledHTML());
|
||||
return $maker->getCompiledHTML();
|
||||
}
|
||||
|
||||
}
|
||||
catch(\Exception $e){
|
||||
nlog($e->getMessage());
|
||||
DB::connection(config('database.default'))->rollBack();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//if phantom js...... inject here..
|
||||
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
|
||||
return (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true));
|
||||
}
|
||||
|
||||
if(config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja'){
|
||||
$pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true));
|
||||
|
||||
$numbered_pdf = $this->pageNumbering($pdf, auth()->user()->company());
|
||||
|
||||
|
||||
$numbered_pdf = $this->pageNumbering($pdf, auth()->user()->company());
|
||||
|
||||
if ($numbered_pdf) {
|
||||
$pdf = $numbered_pdf;
|
||||
}
|
||||
|
||||
return $pdf;
|
||||
}
|
||||
|
||||
$file_path = (new PreviewPdf($maker->getCompiledHTML(true), $company))->handle();
|
||||
|
||||
$response = Response::make($file_path, 200);
|
||||
$response->header('Content-Type', 'application/pdf');
|
||||
|
||||
return $response;
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function live(PreviewInvoiceRequest $request)
|
||||
{
|
||||
if(Ninja::isHosted() && $request->getHost() != 'preview.invoicing.co')
|
||||
return response()->json(['message' => 'This server cannot handle this request.'], 400);
|
||||
|
||||
$company = auth()->user()->company();
|
||||
|
||||
MultiDB::setDb($company->db);
|
||||
|
@ -72,9 +72,7 @@ class ProfitAndLossController extends BaseController
|
||||
// expect a list of visible fields, or use the default
|
||||
|
||||
$pnl = new ProfitLoss(auth()->user()->company(), $request->all());
|
||||
$pnl->build();
|
||||
|
||||
$csv = $pnl->getCsv();
|
||||
$csv = $pnl->run();
|
||||
|
||||
$headers = [
|
||||
'Content-Disposition' => 'attachment',
|
||||
|
71
app/Http/Requests/Preview/DesignPreviewRequest.php
Normal file
71
app/Http/Requests/Preview/DesignPreviewRequest.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\Preview;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use App\Http\ValidationRules\Project\ValidProjectForClient;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\Quote;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Utils\Traits\CleanLineItems;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class DesignPreviewRequest extends Request
|
||||
{
|
||||
use MakesHash;
|
||||
use CleanLineItems;
|
||||
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->user()->can('create', Invoice::class) ||
|
||||
auth()->user()->can('create', Quote::class) ||
|
||||
auth()->user()->can('create', RecurringInvoice::class) ||
|
||||
auth()->user()->can('create', Credit::class) ||
|
||||
auth()->user()->can('create', PurchaseOrder::class);
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
$rules = [
|
||||
'entity' => 'bail|sometimes|string',
|
||||
'entity_id' => 'bail|sometimes|string',
|
||||
'settings_type' => 'bail|required|in:company,group,client',
|
||||
'settings' => 'sometimes',
|
||||
'group_id' => 'sometimes',
|
||||
'client_id' => 'sometimes',
|
||||
];
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
public function prepareForValidation()
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
$input = $this->decodePrimaryKeys($input);
|
||||
|
||||
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
|
||||
$input['amount'] = 0;
|
||||
$input['balance'] = 0;
|
||||
$input['number'] = ctrans('texts.live_preview').' #'.rand(0, 1000);
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
}
|
@ -28,8 +28,10 @@ class StoreWebhookRequest extends Request
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'target_url' => 'required|url',
|
||||
'event_id' => 'required',
|
||||
'target_url' => 'bail|required|url',
|
||||
'event_id' => 'bail|required',
|
||||
'headers' => 'bail|sometimes|json',
|
||||
'rest_method' => 'required|in:post,put'
|
||||
];
|
||||
}
|
||||
|
||||
@ -37,6 +39,9 @@ class StoreWebhookRequest extends Request
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
if(isset($input['headers']) && count($input['headers']) == 0)
|
||||
$input['headers'] = null;
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
}
|
||||
|
@ -27,13 +27,16 @@ class UpdateWebhookRequest extends Request
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->user()->isAdmin();
|
||||
return auth()->user()->can('edit', $this->webhook);
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'target_url' => 'url',
|
||||
'target_url' => 'bail|required|url',
|
||||
'event_id' => 'bail|required',
|
||||
'rest_method' => 'required|in:post,put',
|
||||
'headers' => 'bail|sometimes|json',
|
||||
];
|
||||
}
|
||||
|
||||
@ -41,6 +44,9 @@ class UpdateWebhookRequest extends Request
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
if(isset($input['headers']) && count($input['headers']) == 0)
|
||||
$input['headers'] = null;
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
}
|
||||
|
@ -508,7 +508,7 @@ class CompanyImport implements ShouldQueue
|
||||
|
||||
if(Ninja::isHosted())
|
||||
{
|
||||
$this->company->portal_mode = 'sub_domain';
|
||||
$this->company->portal_mode = 'subdomain';
|
||||
$this->company->portal_domain = '';
|
||||
}
|
||||
|
||||
|
@ -11,9 +11,22 @@
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\User;
|
||||
|
||||
/**
|
||||
* Class WebhookPolicy.
|
||||
*/
|
||||
class WebhookPolicy extends EntityPolicy
|
||||
{
|
||||
/**
|
||||
* Checks if the user has create permissions.
|
||||
*
|
||||
* @param User $user
|
||||
* @return bool
|
||||
*/
|
||||
public function create(User $user) : bool
|
||||
{
|
||||
return $user->isAdmin();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -94,6 +94,11 @@ class ProfitLoss
|
||||
$this->setBillingReportType();
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
return $this->build()->getCsv();
|
||||
}
|
||||
|
||||
public function build()
|
||||
{
|
||||
MultiDB::setDb($this->company->db);
|
||||
|
@ -24,6 +24,7 @@ use App\Utils\Ninja;
|
||||
use App\Utils\Number;
|
||||
use App\Utils\Traits\AppSetup;
|
||||
use App\Utils\Traits\MakesDates;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
@ -32,6 +33,7 @@ class HtmlEngine
|
||||
{
|
||||
use MakesDates;
|
||||
use AppSetup;
|
||||
use MakesHash;
|
||||
|
||||
public $entity;
|
||||
|
||||
@ -98,6 +100,56 @@ class HtmlEngine
|
||||
}
|
||||
}
|
||||
|
||||
private function resolveCompanyLogoSize()
|
||||
{
|
||||
$design_map = [
|
||||
"VolejRejNm" => "65%", // "Plain",
|
||||
"Wpmbk5ezJn" => "65%", //"Clean",
|
||||
"Opnel5aKBz" => "65%", //"Bold",
|
||||
"wMvbmOeYAl" => "55%", //Modern",
|
||||
"4openRe7Az" => "65%", //"Business",
|
||||
"WJxbojagwO" => "65%", //"Creative",
|
||||
"k8mep2bMyJ" => "55%", //"Elegant",
|
||||
"l4zbq2dprO" => "65%", //"Hipster",
|
||||
"yMYerEdOBQ" => "65%", //"Playful",
|
||||
"gl9avmeG1v" => "65%", //"Tech",
|
||||
"7LDdwRb1YK" => "65%", //"Calm",
|
||||
"APdRoy0eGy" => "65%", //"Calm-DB2",
|
||||
"y1aK83rbQG" => "65%", //"Calm-DB1",
|
||||
];
|
||||
|
||||
$design_int_map = [
|
||||
"1" => "65%", // "Plain",
|
||||
"2" => "65%", //"Clean",
|
||||
"3" => "65%", //"Bold",
|
||||
"4" => "55%", //Modern",
|
||||
"5" => "65%", //"Business",
|
||||
"6" => "65%", //"Creative",
|
||||
"7" => "55%", //"Elegant",
|
||||
"8" => "65%", //"Hipster",
|
||||
"9" => "65%", //"Playful",
|
||||
"10" => "65%", //"Tech",
|
||||
"11" => "65%", //"Calm",
|
||||
"6972" => "65%", //"C-DB2"
|
||||
"11221" => "65%", //"C-DB1"
|
||||
];
|
||||
|
||||
if(isset($this->settings->company_logo_size) && strlen($this->settings->company_logo_size) > 1)
|
||||
return $this->settings->company_logo_size;
|
||||
|
||||
if($this->entity->design_id && array_key_exists($this->entity->design_id, $design_int_map))
|
||||
return $design_int_map[$this->entity->design_id];
|
||||
|
||||
$default_design_id = $this->entity_string."_design_id";
|
||||
$design_id = $this->settings->{$default_design_id};
|
||||
|
||||
if(array_key_exists($design_id, $design_map))
|
||||
return $design_map[$design_id];
|
||||
|
||||
return '65%';
|
||||
|
||||
}
|
||||
|
||||
public function buildEntityDataArray() :array
|
||||
{
|
||||
if (! $this->client->currency()) {
|
||||
@ -111,8 +163,9 @@ class HtmlEngine
|
||||
$t->replace(Ninja::transformTranslations($this->settings));
|
||||
|
||||
$data = [];
|
||||
//$data['<html>'] = ['value' => '<html dir="rtl">', 'label' => ''];
|
||||
|
||||
$data['$global_margin'] = ['value' => '6.35mm', 'label' => ''];
|
||||
$data['$company_logo_size'] = ['value' => $this->resolveCompanyLogoSize(), 'label' => ''];
|
||||
$data['$tax'] = ['value' => '', 'label' => ctrans('texts.tax')];
|
||||
$data['$app_url'] = ['value' => $this->generateAppUrl(), 'label' => ''];
|
||||
$data['$from'] = ['value' => '', 'label' => ctrans('texts.from')];
|
||||
|
@ -584,6 +584,12 @@ trait GeneratesCounter
|
||||
$settings->invoice_number_counter = 1;
|
||||
$settings->quote_number_counter = 1;
|
||||
$settings->credit_number_counter = 1;
|
||||
$settings->ticket_number_counter = 1;
|
||||
$settings->payment_number_counter = 1;
|
||||
$settings->project_number_counter = 1;
|
||||
$settings->task_number_counter = 1;
|
||||
$settings->expense_number_counter = 1;
|
||||
$settings->recurring_expense_number_counter = 1;
|
||||
$settings->purchase_order_number_counter = 1;
|
||||
|
||||
$client->company->settings = $settings;
|
||||
@ -600,48 +606,67 @@ trait GeneratesCounter
|
||||
return false;
|
||||
}
|
||||
|
||||
switch ($company->reset_counter_frequency_id) {
|
||||
$settings = $company->settings;
|
||||
|
||||
$reset_counter_frequency = (int) $settings->reset_counter_frequency_id;
|
||||
|
||||
if ($reset_counter_frequency == 0) {
|
||||
|
||||
if($settings->reset_counter_date){
|
||||
|
||||
$settings->reset_counter_date = "";
|
||||
$company->settings = $settings;
|
||||
$company->save();
|
||||
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
switch ($reset_counter_frequency) {
|
||||
case RecurringInvoice::FREQUENCY_DAILY:
|
||||
$reset_date->addDay();
|
||||
$new_reset_date = $reset_date->addDay();
|
||||
break;
|
||||
case RecurringInvoice::FREQUENCY_WEEKLY:
|
||||
$reset_date->addWeek();
|
||||
$new_reset_date = $reset_date->addWeek();
|
||||
break;
|
||||
case RecurringInvoice::FREQUENCY_TWO_WEEKS:
|
||||
$reset_date->addWeeks(2);
|
||||
$new_reset_date = $reset_date->addWeeks(2);
|
||||
break;
|
||||
case RecurringInvoice::FREQUENCY_FOUR_WEEKS:
|
||||
$reset_date->addWeeks(4);
|
||||
$new_reset_date = $reset_date->addWeeks(4);
|
||||
break;
|
||||
case RecurringInvoice::FREQUENCY_MONTHLY:
|
||||
$reset_date->addMonth();
|
||||
$new_reset_date = $reset_date->addMonth();
|
||||
break;
|
||||
case RecurringInvoice::FREQUENCY_TWO_MONTHS:
|
||||
$reset_date->addMonths(2);
|
||||
$new_reset_date = $reset_date->addMonths(2);
|
||||
break;
|
||||
case RecurringInvoice::FREQUENCY_THREE_MONTHS:
|
||||
$reset_date->addMonths(3);
|
||||
$new_reset_date = $reset_date->addMonths(3);
|
||||
break;
|
||||
case RecurringInvoice::FREQUENCY_FOUR_MONTHS:
|
||||
$reset_date->addMonths(4);
|
||||
$new_reset_date = $reset_date->addMonths(4);
|
||||
break;
|
||||
case RecurringInvoice::FREQUENCY_SIX_MONTHS:
|
||||
$reset_date->addMonths(6);
|
||||
$new_reset_date = $reset_date->addMonths(6);
|
||||
break;
|
||||
case RecurringInvoice::FREQUENCY_ANNUALLY:
|
||||
$reset_date->addYear();
|
||||
$new_reset_date = $reset_date->addYear();
|
||||
break;
|
||||
case RecurringInvoice::FREQUENCY_TWO_YEARS:
|
||||
$reset_date->addYears(2);
|
||||
$new_reset_date = $reset_date->addYears(2);
|
||||
break;
|
||||
|
||||
default:
|
||||
$new_reset_date = $reset_date->addYear();
|
||||
break;
|
||||
}
|
||||
|
||||
$settings = $company->settings;
|
||||
$settings->reset_counter_date = $reset_date->format('Y-m-d');
|
||||
$settings->reset_counter_date = $new_reset_date->format('Y-m-d');
|
||||
$settings->invoice_number_counter = 1;
|
||||
$settings->quote_number_counter = 1;
|
||||
$settings->credit_number_counter = 1;
|
||||
$settings->vendor_number_counter = 1;
|
||||
$settings->ticket_number_counter = 1;
|
||||
$settings->payment_number_counter = 1;
|
||||
$settings->project_number_counter = 1;
|
||||
|
@ -14,8 +14,8 @@ return [
|
||||
'require_https' => env('REQUIRE_HTTPS', true),
|
||||
'app_url' => rtrim(env('APP_URL', ''), '/'),
|
||||
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
|
||||
'app_version' => '5.5.63',
|
||||
'app_tag' => '5.5.63',
|
||||
'app_version' => '5.5.64',
|
||||
'app_tag' => '5.5.64',
|
||||
'minimum_client_version' => '5.0.16',
|
||||
'terms_version' => '1.0.1',
|
||||
'api_secret' => env('API_SECRET', ''),
|
||||
|
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
\Illuminate\Support\Facades\Artisan::call('ninja:design-update');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
};
|
@ -4951,6 +4951,7 @@ $LANG = array(
|
||||
'notify_vendor_when_paid_help' => 'Send an email to the vendor when the expense is marked as paid',
|
||||
'update_payment' => 'Update Payment',
|
||||
'markup' => 'Markup',
|
||||
'unlock_pro' => 'Unlock Pro',
|
||||
);
|
||||
|
||||
|
||||
|
@ -59,6 +59,7 @@
|
||||
.company-logo {
|
||||
height: 100%;
|
||||
max-width: 100%;
|
||||
/* max-width: $company_logo_size;*/
|
||||
object-fit: contain;
|
||||
object-position: left center;
|
||||
}
|
||||
|
@ -41,6 +41,7 @@
|
||||
|
||||
.company-logo {
|
||||
max-width: 65%;
|
||||
/* max-width: $company_logo_size;*/
|
||||
}
|
||||
|
||||
.header-container > span {
|
||||
|
@ -47,6 +47,7 @@
|
||||
|
||||
.company-logo {
|
||||
max-width: 65%;
|
||||
/* max-width: $company_logo_size;*/
|
||||
}
|
||||
|
||||
.client-and-entity-wrapper {
|
||||
|
@ -23,7 +23,7 @@
|
||||
@page {
|
||||
margin-left: $global_margin;
|
||||
margin-right: $global_margin;
|
||||
margin-top: 0;
|
||||
margin-top: 5;
|
||||
margin-bottom: 0;
|
||||
size: $page_size $page_layout;
|
||||
}
|
||||
@ -53,6 +53,7 @@
|
||||
|
||||
.company-logo {
|
||||
max-width: 65%;
|
||||
/* max-width: $company_logo_size;*/
|
||||
}
|
||||
|
||||
#company-details {
|
||||
@ -164,7 +165,7 @@
|
||||
padding-top: .5rem;
|
||||
padding-right: 1rem;
|
||||
gap: 80px;
|
||||
page-break-inside:auto;
|
||||
page-break-inside:avoid;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
|
@ -42,6 +42,7 @@
|
||||
|
||||
.company-logo {
|
||||
max-width: 65%;
|
||||
/* max-width: $company_logo_size;*/
|
||||
}
|
||||
|
||||
#entity-details p { margin-top: 5px; }
|
||||
|
@ -32,6 +32,7 @@
|
||||
|
||||
.company-logo {
|
||||
max-width: 55%;
|
||||
/* max-width: $company_logo_size;*/
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
display: block;
|
||||
|
@ -81,6 +81,7 @@
|
||||
|
||||
.company-logo {
|
||||
max-width: 65%;
|
||||
/* max-width: $company_logo_size;*/
|
||||
}
|
||||
|
||||
.entity-label {
|
||||
|
@ -85,6 +85,7 @@
|
||||
|
||||
.company-logo {
|
||||
max-width: 55%;
|
||||
/* max-width: $company_logo_size;*/
|
||||
}
|
||||
|
||||
#client-details {
|
||||
|
@ -42,6 +42,7 @@
|
||||
|
||||
.company-logo {
|
||||
max-width: 65%;
|
||||
/* max-width: $company_logo_size;*/
|
||||
}
|
||||
|
||||
.header-wrapper #company-address {
|
||||
|
@ -63,6 +63,7 @@
|
||||
|
||||
.company-logo {
|
||||
max-width: 65%;
|
||||
/* max-width: $company_logo_size;*/
|
||||
}
|
||||
|
||||
.contacts-wrapper {
|
||||
|
@ -63,10 +63,12 @@
|
||||
|
||||
.company-logo-wrapper {
|
||||
padding-bottom: 60px;
|
||||
height: 5rem;
|
||||
}
|
||||
|
||||
.company-logo-wrapper {
|
||||
height: 5rem;
|
||||
.company-logo {
|
||||
max-width: 65%;
|
||||
/* max-width: $company_logo_size;*/
|
||||
}
|
||||
|
||||
.header-invoice-number {
|
||||
|
@ -103,7 +103,7 @@ Route::group(['middleware' => ['throttle:300,1', 'api_secret_check']], function
|
||||
Route::post('api/v1/oauth_login', [LoginController::class, 'oauthApiLogin']);
|
||||
});
|
||||
|
||||
Route::group(['middleware' => ['throttle:10,1','api_secret_check','email_db']], function () {
|
||||
Route::group(['middleware' => ['throttle:50,1','api_secret_check','email_db']], function () {
|
||||
Route::post('api/v1/login', [LoginController::class, 'apiLogin'])->name('login.submit')->middleware('throttle:20,1');
|
||||
Route::post('api/v1/reset_password', [ForgotPasswordController::class, 'sendResetLinkEmail']);
|
||||
});
|
||||
@ -228,6 +228,7 @@ Route::group(['middleware' => ['throttle:300,1', 'api_db', 'token_auth', 'locale
|
||||
|
||||
Route::post('preview', [PreviewController::class, 'show'])->name('preview.show');
|
||||
Route::post('live_preview', [PreviewController::class, 'live'])->name('preview.live');
|
||||
Route::post('live_design', [PreviewController::class, 'design'])->name('preview.design');
|
||||
|
||||
Route::post('preview/purchase_order', [PreviewPurchaseOrderController::class, 'show'])->name('preview_purchase_order.show');
|
||||
Route::post('live_preview/purchase_order', [PreviewPurchaseOrderController::class, 'live'])->name('preview_purchase_order.live');
|
||||
|
@ -46,6 +46,7 @@ class DesignApiTest extends TestCase
|
||||
|
||||
public function testDesignPost()
|
||||
{
|
||||
|
||||
$design = [
|
||||
'body' => 'body',
|
||||
'includes' => 'includes',
|
||||
|
62
tests/Feature/LiveDesignTest.php
Normal file
62
tests/Feature/LiveDesignTest.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?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://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||
use Tests\MockAccountData;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @covers App\Http\Controllers\PreviewController
|
||||
*/
|
||||
class LiveDesignTest extends TestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
use MockAccountData;
|
||||
|
||||
protected function setUp() :void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->makeTestData();
|
||||
|
||||
$this->withoutMiddleware(
|
||||
ThrottleRequests::class
|
||||
);
|
||||
|
||||
if (config('ninja.testvars.travis') !== false) {
|
||||
$this->markTestSkipped('Skip test for Travis');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function testDesignRoute200()
|
||||
{
|
||||
$data = [
|
||||
'entity' => 'invoice',
|
||||
'entity_id' => $this->invoice->hashed_id,
|
||||
'settings_type' => 'company',
|
||||
'settings' => (array)$this->company->settings,
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/live_design/', $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -308,13 +308,15 @@ class RecurringInvoiceTest extends TestCase
|
||||
public function testRecurringDatePassesToInvoice()
|
||||
{
|
||||
$noteText = "Hello this is for :MONTH_AFTER";
|
||||
$recurringDate = \Carbon\Carbon::now()->subDays(10);
|
||||
$recurringDate = \Carbon\Carbon::now()->timezone($this->client->timezone()->name)->subDays(10);
|
||||
|
||||
$item = InvoiceItemFactory::create();
|
||||
$item->cost = 10;
|
||||
$item->notes = $noteText;
|
||||
|
||||
$recurring_invoice = InvoiceToRecurringInvoiceFactory::create($this->invoice);
|
||||
|
||||
|
||||
$recurring_invoice->user_id = $this->user->id;
|
||||
$recurring_invoice->next_send_date = $recurringDate;
|
||||
$recurring_invoice->status_id = RecurringInvoice::STATUS_ACTIVE;
|
||||
|
@ -70,6 +70,7 @@ class WebhookAPITest extends TestCase
|
||||
$data = [
|
||||
'target_url' => 'http://hook.com',
|
||||
'event_id' => 1,
|
||||
'rest_method' => 'post',
|
||||
'format' => 'JSON',
|
||||
];
|
||||
|
||||
@ -85,7 +86,10 @@ class WebhookAPITest extends TestCase
|
||||
$this->assertEquals(1, $arr['data']['event_id']);
|
||||
|
||||
$data = [
|
||||
'target_url' => 'http://hook.com',
|
||||
'event_id' => 2,
|
||||
'rest_method' => 'post',
|
||||
'format' => 'JSON',
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
|
2
tests/cypress/screenshots/.gitignore
vendored
Normal file
2
tests/cypress/screenshots/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
2
tests/cypress/videos/.gitignore
vendored
Normal file
2
tests/cypress/videos/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user