1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-10 05:02:36 +01:00

Merge pull request #9302 from turbo124/v5-develop

v5.8.26
This commit is contained in:
David Bomba 2024-02-18 19:54:07 +11:00 committed by GitHub
commit 8e50ae9691
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
61 changed files with 1010 additions and 321 deletions

View File

@ -1 +1 @@
5.8.25
5.8.26

View File

@ -495,7 +495,10 @@ class CompanySettings extends BaseSettings
public $show_pdfhtml_on_mobile = true;
public $use_unapplied_payment = 'off'; //always, option, off //@implemented
public static $casts = [
'use_unapplied_payment' => 'string',
'show_pdfhtml_on_mobile' => 'bool',
'payment_email_all_contacts' => 'bool',
'statement_design_id' => 'string',

View File

@ -49,6 +49,14 @@ class CompanyFactory
$company->markdown_enabled = false;
$company->tax_data = new TaxModel();
$company->first_month_of_year = 1;
$company->smtp_encryption = 'tls';
$company->smtp_host = '';
$company->smtp_local_domain = '';
$company->smtp_password = '';
$company->smtp_port = '';
$company->smtp_username = '';
$company->smtp_verify_peer = true;
return $company;
}
}

View File

@ -12,8 +12,9 @@
namespace App\Filters;
use App\Models\Payment;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Eloquent\Builder;
/**
* PaymentFilters.
@ -163,7 +164,7 @@ class PaymentFilters extends QueryFilters
{
$sort_col = explode('|', $sort);
if (!is_array($sort_col) || count($sort_col) != 2) {
if (!is_array($sort_col) || count($sort_col) != 2 || !in_array($sort_col, Schema::getColumnListing('payments'))) {
return $this->builder;
}

View File

@ -140,6 +140,7 @@ class BaseController extends Controller
'company.quotes.invitations.company',
'company.quotes.documents',
'company.tasks.documents',
// 'company.tasks.project',
'company.subscriptions',
'company.tax_rates',
'company.tokens_hashed',
@ -458,7 +459,7 @@ class BaseController extends Controller
}
},
'company.tasks' => function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at)->with('documents');
$query->where('updated_at', '>=', $updated_at)->with('project','documents');
if (! $user->hasPermission('view_task')) {
$query->whereNested(function ($query) use ($user) {
@ -796,7 +797,7 @@ class BaseController extends Controller
}
},
'company.tasks' => function ($query) use ($created_at, $user) {
$query->where('created_at', '>=', $created_at)->with('documents');
$query->where('created_at', '>=', $created_at)->with('project.documents','documents');
if (! $user->hasPermission('view_task')) {
$query->whereNested(function ($query) use ($user) {

View File

@ -169,13 +169,16 @@ class ConnectedAccountController extends BaseController
'email_verified_at' => now(),
];
auth()->user()->update($connected_account);
auth()->user()->email_verified_at = now();
auth()->user()->save();
/** @var \App\Models\User $logged_in_user */
$logged_in_user = auth()->user();
$this->setLoginCache(auth()->user());
$logged_in_user->update($connected_account);
$logged_in_user->email_verified_at = now();
$logged_in_user->save();
return $this->itemResponse(auth()->user());
$this->setLoginCache($logged_in_user);
return $this->itemResponse($logged_in_user);
}
return response()
@ -214,20 +217,22 @@ class ConnectedAccountController extends BaseController
// 'email_verified_at' =>now(),
];
if (auth()->user()->email != $google->harvestEmail($user)) {
/** @var \App\Models\User $logged_in_user */
$logged_in_user = auth()->user();
if ($logged_in_user->email != $google->harvestEmail($user)) {
return response()->json(['message' => 'Primary Email differs to OAuth email. Emails must match.'], 400);
}
auth()->user()->update($connected_account);
auth()->user()->email_verified_at = now();
auth()->user()->oauth_user_token = $token;
auth()->user()->oauth_user_refresh_token = $refresh_token;
$logged_in_user->update($connected_account);
$logged_in_user->email_verified_at = now();
$logged_in_user->oauth_user_token = $token;
$logged_in_user->oauth_user_refresh_token = $refresh_token;
$logged_in_user->save();
auth()->user()->save();
$this->activateGmail($logged_in_user);
$this->activateGmail(auth()->user());
return $this->itemResponse(auth()->user());
return $this->itemResponse($logged_in_user);
}
return response()

View File

@ -0,0 +1,64 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\Controllers;
use App\Http\Requests\Smtp\CheckSmtpRequest;
use App\Mail\TestMailServer;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
class SmtpController extends BaseController
{
public function __construct()
{
parent::__construct();
}
public function check(CheckSmtpRequest $request)
{
/** @var \App\Models\User $user */
$user = auth()->user();
$company = $user->company();
config([
'mail.mailers.smtp' => [
'transport' => 'smtp',
'host' => $request->input('smtp_host', $company->smtp_host),
'port' => $request->input('smtp_port', $company->smtp_port),
'username' => $request->input('smtp_username', $company->smtp_username),
'password' => $request->input('smtp_password', $company->smtp_password),
'encryption' => $request->input('smtp_encryption', $company->smtp_encryption ?? 'tls'),
'local_domain' => $request->input('smtp_local_domain', strlen($company->smtp_local_domain) > 2 ? $company->smtp_local_domain : null),
'verify_peer' => $request->input('verify_peer', $company->smtp_verify_peer ?? true),
'timeout' => 5,
],
]);
(new \Illuminate\Mail\MailServiceProvider(app()))->register();
try {
Mail::to($user->email, $user->present()->name())->send(new TestMailServer('Email Server Works!', strlen($company->settings->custom_sending_email) > 1 ? $company->settings->custom_sending_email : $user->email));
} catch (\Exception $e) {
app('mail.manager')->forgetMailers();
return response()->json(['message' => $e->getMessage()], 400);
}
app('mail.manager')->forgetMailers();
return response()->json(['message' => 'Ok'], 200);
}
}

View File

@ -49,6 +49,9 @@ class StoreClientRequest extends Request
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
}
else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {
$rules['file.*'] = $this->file_validation;

View File

@ -53,6 +53,8 @@ class UpdateClientRequest extends Request
$rules['file.*'] = $this->file_validation;
} elseif ($this->file('file')) {
$rules['file'] = $this->file_validation;
} else {
$rules['documents'] = 'bail|sometimes|array';
}
$rules['company_logo'] = 'mimes:jpeg,jpg,png,gif|max:10000';

View File

@ -56,6 +56,15 @@ class StoreCompanyRequest extends Request
}
}
$rules['smtp_host'] = 'sometimes|string|nullable';
$rules['smtp_port'] = 'sometimes|string|nullable';
$rules['smtp_encryption'] = 'sometimes|string';
$rules['smtp_local_domain'] = 'sometimes|string|nullable';
$rules['smtp_encryption'] = 'sometimes|string|nullable';
$rules['smtp_local_domain'] = 'sometimes|string|nullable';
// $rules['smtp_verify_peer'] = 'sometimes|in:true,false';
return $rules;
}
@ -67,11 +76,11 @@ class StoreCompanyRequest extends Request
$input['name'] = 'Untitled Company';
}
if (array_key_exists('google_analytics_url', $input)) {
if (isset($input['google_analytics_url'])) {
$input['google_analytics_key'] = $input['google_analytics_url'];
}
if (array_key_exists('portal_domain', $input)) {
if (isset($input['portal_domain'])) {
$input['portal_domain'] = rtrim(strtolower($input['portal_domain']), "/");
}
@ -79,6 +88,17 @@ class StoreCompanyRequest extends Request
$input['subdomain'] = MultiDB::randomSubdomainGenerator();
}
if(isset($input['smtp_username']) && strlen(str_replace("*", "", $input['smtp_username'])) < 2) {
unset($input['smtp_username']);
}
if(isset($input['smtp_password']) && strlen(str_replace("*", "", $input['smtp_password'])) < 2) {
unset($input['smtp_password']);
}
if(isset($input['smtp_verify_peer']) && is_string($input['smtp_verify_peer']))
$input['smtp_verify_peer'] == 'true' ? true : false;
$this->replace($input);
}
}

View File

@ -57,8 +57,14 @@ class UpdateCompanyRequest extends Request
$rules['matomo_id'] = 'nullable|integer';
$rules['e_invoice_certificate_passphrase'] = 'sometimes|nullable';
$rules['e_invoice_certificate'] = 'sometimes|nullable|file|mimes:p12,pfx,pem,cer,crt,der,txt,p7b,spc,bin';
// $rules['client_registration_fields'] = 'array';
$rules['smtp_host'] = 'sometimes|string|nullable';
$rules['smtp_port'] = 'sometimes|string|nullable';
$rules['smtp_encryption'] = 'sometimes|string|nullable';
$rules['smtp_local_domain'] = 'sometimes|string|nullable';
// $rules['smtp_verify_peer'] = 'sometimes|string';
if (isset($input['portal_mode']) && ($input['portal_mode'] == 'domain' || $input['portal_mode'] == 'iframe')) {
$rules['portal_domain'] = 'bail|nullable|sometimes|url';
}
@ -74,23 +80,35 @@ class UpdateCompanyRequest extends Request
{
$input = $this->all();
if (array_key_exists('portal_domain', $input) && strlen($input['portal_domain']) > 1) {
if (isset($input['portal_domain']) && strlen($input['portal_domain']) > 1) {
$input['portal_domain'] = $this->addScheme($input['portal_domain']);
$input['portal_domain'] = rtrim(strtolower($input['portal_domain']), "/");
}
if (array_key_exists('settings', $input)) {
if (isset($input['settings'])) {
$input['settings'] = (array)$this->filterSaveableSettings($input['settings']);
}
if(array_key_exists('subdomain', $input) && $this->company->subdomain == $input['subdomain']) {
if(isset($input['subdomain']) && $this->company->subdomain == $input['subdomain']) {
unset($input['subdomain']);
}
if(array_key_exists('e_invoice_certificate_passphrase', $input) && empty($input['e_invoice_certificate_passphrase'])) {
if(isset($input['e_invoice_certificate_passphrase']) && empty($input['e_invoice_certificate_passphrase'])) {
unset($input['e_invoice_certificate_passphrase']);
}
if(isset($input['smtp_username']) && strlen(str_replace("*","", $input['smtp_username'])) < 2) {
unset($input['smtp_username']);
}
if(isset($input['smtp_password']) && strlen(str_replace("*", "", $input['smtp_password'])) < 2) {
unset($input['smtp_password']);
}
if(isset($input['smtp_verify_peer']) && is_string($input['smtp_verify_peer'])) {
$input['smtp_verify_peer'] == 'true' ? true : false;
}
$this->replace($input);
}

View File

@ -50,6 +50,8 @@ class StoreCreditRequest extends Request
$rules['documents.*'] = $this->file_validation;
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
}else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {

View File

@ -52,6 +52,8 @@ class UpdateCreditRequest extends Request
$rules['documents.*'] = $this->file_validation;
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
}else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {

View File

@ -52,6 +52,7 @@ class StoreExpenseRequest extends Request
$rules['category_id'] = 'bail|nullable|sometimes|exists:expense_categories,id,company_id,'.$user->company()->id.',is_deleted,0';
$rules['payment_date'] = 'bail|nullable|sometimes|date:Y-m-d';
$rules['date'] = 'bail|sometimes|date:Y-m-d';
$rules['documents'] = 'bail|sometimes|array';
return $this->globalRules($rules);
}

View File

@ -29,25 +29,32 @@ class UpdateExpenseRequest extends Request
*/
public function authorize(): bool
{
return auth()->user()->can('edit', $this->expense);
/** @var \App\Models\User $user */
$user = auth()->user();
return $user->can('edit', $this->expense);
}
public function rules()
{
/** @var \App\Models\User $user */
$user = auth()->user();
/* Ensure we have a client name, and that all emails are unique*/
$rules = [];
if (isset($this->number)) {
$rules['number'] = Rule::unique('expenses')->where('company_id', auth()->user()->company()->id)->ignore($this->expense->id);
$rules['number'] = Rule::unique('expenses')->where('company_id', $user->company()->id)->ignore($this->expense->id);
}
if ($this->client_id) {
$rules['client_id'] = 'bail|sometimes|exists:clients,id,company_id,'.auth()->user()->company()->id;
$rules['client_id'] = 'bail|sometimes|exists:clients,id,company_id,'.$user->company()->id;
}
$rules['category_id'] = 'bail|sometimes|nullable|exists:expense_categories,id,company_id,'.auth()->user()->company()->id.',is_deleted,0';
$rules['transaction_id'] = 'bail|sometimes|nullable|exists:bank_transactions,id,company_id,'.auth()->user()->company()->id;
$rules['invoice_id'] = 'bail|sometimes|nullable|exists:invoices,id,company_id,'.auth()->user()->company()->id;
$rules['category_id'] = 'bail|sometimes|nullable|exists:expense_categories,id,company_id,'.$user->company()->id.',is_deleted,0';
$rules['transaction_id'] = 'bail|sometimes|nullable|exists:bank_transactions,id,company_id,'.$user->company()->id;
$rules['invoice_id'] = 'bail|sometimes|nullable|exists:invoices,id,company_id,'.$user->company()->id;
$rules['documents'] = 'bail|sometimes|array';
return $this->globalRules($rules);
@ -55,6 +62,10 @@ class UpdateExpenseRequest extends Request
public function prepareForValidation()
{
/** @var \App\Models\User $user */
$user = auth()->user();
$input = $this->all();
$input = $this->decodePrimaryKeys($input);
@ -64,7 +75,7 @@ class UpdateExpenseRequest extends Request
}
if (! array_key_exists('currency_id', $input) || strlen($input['currency_id']) == 0) {
$input['currency_id'] = (string) auth()->user()->company()->settings->currency_id;
$input['currency_id'] = (string) $user->company()->settings->currency_id;
}
/* Ensure the project is related */

View File

@ -47,6 +47,8 @@ class StoreInvoiceRequest extends Request
$rules['documents.*'] = $this->file_validation;
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
}else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {
@ -76,6 +78,7 @@ class StoreInvoiceRequest extends Request
$rules['partial'] = 'bail|sometimes|nullable|numeric|gte:0';
$rules['partial_due_date'] = ['bail', 'sometimes', 'exclude_if:partial,0', Rule::requiredIf(fn () => $this->partial > 0), 'date'];
return $rules;
}

View File

@ -49,6 +49,8 @@ class UpdateInvoiceRequest extends Request
$rules['documents.*'] = $this->file_validation;
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
}else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {
@ -77,6 +79,7 @@ class UpdateInvoiceRequest extends Request
$rules['partial'] = 'bail|sometimes|nullable|numeric';
$rules['partial_due_date'] = ['bail', 'sometimes', 'exclude_if:partial,0', Rule::requiredIf(fn () => $this->partial > 0), 'date', 'before:due_date'];
return $rules;
}

View File

@ -126,6 +126,8 @@ class StorePaymentRequest extends Request
$rules['documents.*'] = $this->file_validation;
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
}else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {

View File

@ -55,6 +55,8 @@ class UpdatePaymentRequest extends Request
$rules['documents.*'] = $this->file_validation;
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
}else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {

View File

@ -35,6 +35,8 @@ class StoreProductRequest extends Request
$rules['documents.*'] = $this->file_validation;
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
}else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {

View File

@ -25,7 +25,11 @@ class UpdateProductRequest extends Request
*/
public function authorize(): bool
{
return auth()->user()->can('edit', $this->product);
/** @var \App\Models\User $user */
$user = auth()->user();
return $user->can('edit', $this->product);
}
public function rules()
@ -34,6 +38,8 @@ class UpdateProductRequest extends Request
$rules['documents.*'] = $this->file_validation;
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
}else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {

View File

@ -53,6 +53,8 @@ class StoreProjectRequest extends Request
$rules['documents.*'] = $this->file_validation;
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
}else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {

View File

@ -49,6 +49,8 @@ class UpdateProjectRequest extends Request
$rules['documents.*'] = $this->file_validation;
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
}else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {

View File

@ -57,6 +57,8 @@ class StorePurchaseOrderRequest extends Request
$rules['documents.*'] = $this->file_validation;
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
} else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {

View File

@ -59,6 +59,8 @@ class UpdatePurchaseOrderRequest extends Request
$rules['documents.*'] = $this->file_validation;
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
}else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {

View File

@ -49,6 +49,8 @@ class StoreQuoteRequest extends Request
$rules['documents.*'] = $this->file_validation;
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
}else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {
@ -59,11 +61,8 @@ class StoreQuoteRequest extends Request
$rules['number'] = ['nullable', Rule::unique('quotes')->where('company_id', $user->company()->id)];
$rules['discount'] = 'sometimes|numeric';
$rules['is_amount_discount'] = ['boolean'];
$rules['exchange_rate'] = 'bail|sometimes|numeric';
// $rules['number'] = new UniqueQuoteNumberRule($this->all());
$rules['line_items'] = 'array';
return $rules;

View File

@ -46,6 +46,8 @@ class UpdateQuoteRequest extends Request
$rules['documents.*'] = $this->file_validation;
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
}else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {

View File

@ -49,6 +49,8 @@ class StoreRecurringInvoiceRequest extends Request
$rules['documents.*'] = $this->file_validation;
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
}else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {

View File

@ -48,6 +48,8 @@ class UpdateRecurringInvoiceRequest extends Request
$rules['documents.*'] = $this->file_validation;
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
}else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {

View File

@ -0,0 +1,54 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\Requests\Smtp;
use App\Http\Requests\Request;
class CheckSmtpRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
/** @var \App\Models\User $user */
$user = auth()->user();
return $user->isAdmin();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
];
}
public function prepareForValidation()
{
$input = $this->input();
if(isset($input['smtp_username']) && $input['smtp_username'] == '********')
unset($input['smtp_username']);
if(isset($input['smtp_password'])&& $input['smtp_password'] == '********')
unset($input['smtp_password']);
$this->replace($input);
}
}

View File

@ -82,6 +82,8 @@ class StoreTaskRequest extends Request
$rules['documents.*'] = $this->file_validation;
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
}else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {

View File

@ -88,6 +88,8 @@ class UpdateTaskRequest extends Request
$rules['documents.*'] = $this->file_validation;
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
}else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {

View File

@ -64,6 +64,8 @@ class StoreVendorRequest extends Request
$rules['documents.*'] = $this->file_validation;
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
}else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {

View File

@ -65,6 +65,8 @@ class UpdateVendorRequest extends Request
$rules['documents.*'] = $this->file_validation;
} elseif ($this->file('documents')) {
$rules['documents'] = $this->file_validation;
}else {
$rules['documents'] = 'bail|sometimes|array';
}
if ($this->file('file') && is_array($this->file('file'))) {

View File

@ -315,14 +315,11 @@ class BaseTransformer
public function getFloat($data, $field)
{
if (array_key_exists($field, $data)) {
//$number = preg_replace('/[^0-9-.]+/', '', $data[$field]);
return Number::parseFloat($data[$field]);
} else {
//$number = 0;
return 0;
}
// return Number::parseFloat($number);
}
return 0;
}
/**

View File

@ -112,6 +112,13 @@ use Laracasts\Presenter\PresentableTrait;
* @property int $notify_vendor_when_paid
* @property int $invoice_task_hours
* @property int $deleted_at
* @property string $smtp_username
* @property string $smtp_password
* @property string $smtp_host
* @property string $smtp_port
* @property string $smtp_encryption
* @property string $smtp_local_domain
* @property boolean $smtp_verify_peer
* @property-read \App\Models\Account $account
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read int|null $activities_count
@ -352,12 +359,19 @@ class Company extends BaseModel
'calculate_taxes',
'tax_data',
'e_invoice_certificate_passphrase',
'smtp_host',
'smtp_port',
'smtp_encryption',
'smtp_local_domain',
'smtp_verify_peer',
];
protected $hidden = [
'id',
'db',
'ip',
'smtp_username',
'smtp_password',
];
protected $casts = [
@ -372,6 +386,8 @@ class Company extends BaseModel
'tax_data' => 'object',
'origin_tax_data' => 'object',
'e_invoice_certificate_passphrase' => EncryptedCast::class,
'smtp_username' => 'encrypted',
'smtp_password' => 'encrypted',
];
protected $with = [];

View File

@ -176,7 +176,11 @@ class Document extends BaseModel
public function generateRoute($absolute = false)
{
try{
return route('api.documents.show', ['document' => $this->hashed_id]).'/download';
}catch(\Exception $e){
return '';
}
}
public function deleteFile()

View File

@ -17,7 +17,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* App\Models\License
*
* @property int $id
* @property int|null $created_at
* @property \Carbon\Carbon $created_at
* @property int|null $updated_at
* @property int|null $deleted_at
* @property string|null $first_name
@ -28,6 +28,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @property string|null $transaction_reference
* @property int|null $product_id
* @property int|null $recurring_invoice_id
* @property-read \App\Models\RecurringInvoice $recurring_invoice
* @method static \Illuminate\Database\Eloquent\Builder|StaticModel company()
* @method static \Illuminate\Database\Eloquent\Builder|StaticModel exclude($columns)
* @method static \Illuminate\Database\Eloquent\Builder|License newModelQuery()
@ -53,4 +54,24 @@ use Illuminate\Database\Eloquent\SoftDeletes;
class License extends StaticModel
{
use SoftDeletes;
}
protected $casts = [
'created_at' => 'date',
];
public function expiry(): string
{
return $this->created_at->addYear()->format('Y-m-d');
}
public function recurring_invoice()
{
return $this->belongsTo(RecurringInvoice::class);
}
public function url()
{
$contact = $this->recurring_invoice->client->contacts()->where('email', $this->email)->first();
}
}

View File

@ -75,6 +75,10 @@ class Project extends BaseModel
'number',
];
protected $with = [
'documents',
];
public function getEntityType()
{
return self::class;

View File

@ -131,6 +131,10 @@ class Task extends BaseModel
'deleted_at' => 'timestamp',
];
protected $with = [
// 'project',
];
protected $touches = [];
public function getEntityType()

View File

@ -39,6 +39,7 @@ class CompanyRepository extends BaseRepository
$company->fill($data);
// nlog($data);
/** Only required to handle v4 migration workloads */
if(Ninja::isHosted() && $company->isDirty('is_disabled') && !$company->is_disabled) {
Ninja::triggerForwarding($company->company_key, $company->owner()->email);
@ -48,6 +49,14 @@ class CompanyRepository extends BaseRepository
$company->saveSettings($data['settings'], $company);
}
if(isset($data['smtp_username'])) {
$company->smtp_username = $data['smtp_username'];
}
if(isset($data['smtp_password'])) {
$company->smtp_password = $data['smtp_password'];
}
$company->save();
return $company;

View File

@ -22,6 +22,8 @@ use App\Models\Invoice;
use App\Models\Payment;
use App\Models\PaymentHash;
use App\Models\PaymentType;
use App\Repositories\CreditRepository;
use App\Repositories\PaymentRepository;
use App\Services\AbstractService;
use App\Utils\Ninja;
use Illuminate\Support\Str;
@ -31,7 +33,7 @@ class AutoBillInvoice extends AbstractService
private Client $client;
private array $used_credit = [];
/*Specific variable for partial payments */
private bool $is_partial_amount = false;
@ -66,8 +68,12 @@ class AutoBillInvoice extends AbstractService
$this->applyCreditPayment();
}
if($this->client->getSetting('use_unapplied_payment') != 'off') {
$this->applyUnappliedPayment();
}
//If this returns true, it means a partial invoice amount was paid as a credit and there is no further balance payable
if ($this->is_partial_amount && $this->invoice->partial == 0) {
if (($this->is_partial_amount && $this->invoice->partial == 0) || (int)$this->invoice->balance == 0) {
return;
}
@ -176,9 +182,6 @@ class AutoBillInvoice extends AbstractService
$payment->amount = 0;
$payment->applied = 0;
// $payment->amount = $amount;
// $payment->applied = $amount;
$payment->client_id = $this->invoice->client_id;
$payment->currency_id = $this->invoice->client->getSetting('currency_id');
$payment->date = now()->addSeconds($this->invoice->company->utc_offset())->format('Y-m-d');
@ -217,8 +220,6 @@ class AutoBillInvoice extends AbstractService
->client
->service()
->updateBalanceAndPaidToDate($amount * -1, $amount)
// ->updateBalance($amount * -1)
// ->updatePaidToDate($amount)
->adjustCreditBalance($amount * -1)
->save();
@ -233,9 +234,7 @@ class AutoBillInvoice extends AbstractService
//if we have paid the invoice in full using credits, then we need to fire the event
if($this->invoice->balance == 0) {
event(new InvoiceWasPaid($this->invoice, $payment, $payment->company, Ninja::eventVars()));
}
return $this->invoice
@ -243,6 +242,84 @@ class AutoBillInvoice extends AbstractService
->setCalculatedStatus()
->save();
}
/**
* If the client has unapplied payments on file
* we will use these prior to charging a
* payment method on file.
*
* This needs to be wrapped in a transaction.
*
* @return self
*/
private function applyUnappliedPayment(): self
{
$unapplied_payments = Payment::query()
->where('client_id', $this->client->id)
->where('status_id', Payment::STATUS_COMPLETED)
->where('is_deleted', false)
->whereColumn('amount', '>', 'applied')
->where('amount', '>', 0)
->orderBy('created_at')
->get();
$available_unapplied_balance = $unapplied_payments->sum('amount') - $unapplied_payments->sum('applied');
nlog("available unapplied balance = {$available_unapplied_balance}");
if ((int) $available_unapplied_balance == 0) {
return $this;
}
if ($this->invoice->partial > 0) {
$this->is_partial_amount = true;
}
$payment_repo = new PaymentRepository(new CreditRepository());
foreach ($unapplied_payments as $key => $payment) {
$payment_balance = $payment->amount - $payment->applied;
if ($this->is_partial_amount) {
//more than needed
if ($payment_balance > $this->invoice->partial) {
$payload = ['client_id' => $this->invoice->client_id, 'invoices' => [['invoice_id' => $this->invoice->id,'amount' => $this->invoice->partial]]];
$payment_repo->save($payload, $payment);
$this->invoice = $this->invoice->fresh();
return $this;
} else {
$payload = ['client_id' => $this->invoice->client_id, 'invoices' => [['invoice_id' => $this->invoice->id,'amount' => $payment_balance]]];
$payment_repo->save($payload, $payment);
}
} else {
//more than needed
if ($payment_balance > $this->invoice->balance) {
$payload = ['client_id' => $this->invoice->client_id, 'invoices' => [['invoice_id' => $this->invoice->id,'amount' => $this->invoice->balance]]];
$payment_repo->save($payload, $payment);
$this->invoice = $this->invoice->fresh();
return $this;
} else {
$payload = ['client_id' => $this->invoice->client_id, 'invoices' => [['invoice_id' => $this->invoice->id,'amount' => $payment_balance]]];
$payment_repo->save($payload, $payment);
}
}
if((int)$this->invoice->balance == 0) {
event(new InvoiceWasPaid($this->invoice, $payment, $payment->company, Ninja::eventVars()));
return $this;
}
}
return $this;
}
/**
* Applies credits to a payment prior to push
@ -260,7 +337,7 @@ class AutoBillInvoice extends AbstractService
$available_credit_balance = $available_credits->sum('balance');
info("available credit balance = {$available_credit_balance}");
nlog("available credit balance = {$available_credit_balance}");
if ((int) $available_credit_balance == 0) {
return $this;
@ -332,14 +409,6 @@ class AutoBillInvoice extends AbstractService
})->orderBy('is_default', 'DESC')
->get();
// $gateway_tokens = $this->client
// ->gateway_tokens()
// ->whereHas('gateway', function ($query) {
// $query->where('is_deleted', 0)
// ->where('deleted_at', null);
// })->orderBy('is_default', 'DESC')
// ->get();
$filtered_gateways = $gateway_tokens->filter(function ($gateway_token) use ($amount) {
$company_gateway = $gateway_token->gateway;

View File

@ -204,9 +204,9 @@ class PdfMock
[
'$client.shipping_postal_code' => '46420',
'$client.billing_postal_code' => '11243',
'$company.city_state_postal' => 'Beveley Hills, CA, 90210',
'$company.postal_city_state' => 'CA',
'$company.postal_city' => '90210, CA',
'$company.city_state_postal' => "{$this->settings->city}, {$this->settings->state}, {$this->settings->postal_code}",
'$company.postal_city_state' => "{$this->settings->postal_code}, {$this->settings->city}, {$this->settings->state}",
'$company.postal_city' => "{$this->settings->postal_code}, {$this->settings->state}",
'$product.gross_line_total' => '100',
'$client.classification' => 'Individual',
'$company.classification' => 'Business',
@ -262,9 +262,9 @@ class PdfMock
'$company.id_number' => $this->settings->id_number,
'$invoice.po_number' => 'PO12345',
'$invoice_total_raw' => 0.0,
'$postal_city_state' => '11243 Aufderharchester, North Carolina',
'$postal_city_state' => "{$this->settings->postal_code}, {$this->settings->city}, {$this->settings->state}",
'$client.vat_number' => '975977515',
'$city_state_postal' => 'Aufderharchester, North Carolina 11243',
'$city_state_postal' => "{$this->settings->city}, {$this->settings->state}, {$this->settings->postal_code}",
'$contact.full_name' => 'Benedict Eichmann',
'$contact.last_name' => 'Eichmann',
'$company.country_2' => 'US',
@ -275,7 +275,7 @@ class PdfMock
'$statement_amount' => '',
'$task.description' => '',
'$product.discount' => '',
'$entity_issued_to' => 'Bob JOnes',
'$entity_issued_to' => 'Bob Jones',
'$assigned_to_user' => '',
'$product.quantity' => '',
'$total_tax_labels' => '',
@ -303,10 +303,10 @@ class PdfMock
'$invoice.custom2' => 'custom value',
'$invoice.custom3' => 'custom value',
'$invoice.custom4' => 'custom value',
'$company.custom1' => 'custom value',
'$company.custom2' => 'custom value',
'$company.custom3' => 'custom value',
'$company.custom4' => 'custom value',
'$company.custom1' => $this->company->custom_value1,
'$company.custom2' => $this->company->custom_value2,
'$company.custom3' => $this->company->custom_value3,
'$company.custom4' => $this->company->custom_value4,
'$quote.po_number' => 'PO12345',
'$company.website' => $this->settings->website,
'$balance_due_raw' => '0.00',
@ -317,8 +317,8 @@ class PdfMock
'$user.first_name' => 'Derrick Monahan DDS',
'$created_by_user' => 'Derrick Monahan DDS Erna Wunsch',
'$client.currency' => 'USD',
'$company.country' => 'United States',
'$company.address' => 'Christiansen Garden<br/>70218 Lori Station Suite 529<br/>New Loy, Delaware 29359<br/>United States<br/>Phone: 1-240-886-2233<br/>Email: immanuel53@example.net<br/>',
'$company.country' => $this->company->country()?->name ?? 'USA',
'$company.address' => $this->company->present()->address(),
'$tech_hero_image' => 'http://ninja.test:8000/images/pdf-designs/tech-hero-image.jpg',
'$task.tax_name1' => '',
'$task.tax_name2' => '',
@ -450,15 +450,15 @@ class PdfMock
'$task.tax' => '',
'$discount' => '$0.00',
'$subtotal' => '$0.00',
'$company1' => 'custom value',
'$company2' => 'custom value',
'$company3' => 'custom value',
'$company4' => 'custom value',
'$company1' => $this->company->custom_value1,
'$company2' => $this->company->custom_value2,
'$company3' => $this->company->custom_value3,
'$company4' => $this->company->custom_value4,
'$due_date' => '2022-01-01',
'$poNumber' => 'PO-123456',
'$quote_no' => '0029',
'$address2' => '63993 Aiyana View',
'$address1' => '8447',
'$address2' => $this->settings->address2,
'$address1' => $this->settings->address1,
'$viewLink' => '<a class="button" href="http://ninja.test:8000/client/invoice/UAUY8vIPuno72igmXbbpldwo5BDDKIqs">View Invoice</a>',
'$autoBill' => 'This invoice will automatically be billed to your credit card on file on the due date.',
'$view_url' => 'http://ninja.test:8000/client/invoice/UAUY8vIPuno72igmXbbpldwo5BDDKIqs',
@ -477,7 +477,7 @@ class PdfMock
'$country' => 'United States',
'$contact' => 'Benedict Eichmann',
'$app_url' => 'http://ninja.test:8000',
'$website' => 'http://www.parisian.org/',
'$website' => $this->settings->website,
'$entity' => '',
'$thanks' => 'Thanks!',
'$amount' => '$30.00',

View File

@ -47,6 +47,7 @@ use App\Utils\Traits\SubscriptionHooker;
use Carbon\Carbon;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Support\Str;
use Illuminate\Mail\Mailables\Address;
class SubscriptionService
{
@ -208,7 +209,7 @@ class SubscriptionService
$invitation = $invoice->invitations()->first();
$email_object = new EmailObject();
$email_object->to = [$contact->email];
$email_object->to = [new Address($contact->email, $contact->present()->name())];
$email_object->subject = ctrans('texts.white_label_link') . " " .ctrans('texts.payment_subject');
$email_object->body = ctrans('texts.white_label_body', ['license_key' => $license_key]);
$email_object->client_id = $invoice->client_id;

View File

@ -711,7 +711,10 @@ class TemplateService
private function getPaymentRefundActivity(Payment $payment): array
{
return collect($payment->refund_meta ?? [])
if(!is_array($payment->refund_meta))
return [];
return collect($payment->refund_meta)
->map(function ($refund) use ($payment) {
$date = \Carbon\Carbon::parse($refund['date'])->addSeconds($payment->client->timezone_offset());

View File

@ -204,6 +204,13 @@ class CompanyTransformer extends EntityTransformer
'invoice_task_project_header' => (bool) $company->invoice_task_project_header,
'invoice_task_item_description' => (bool) $company->invoice_task_item_description,
'origin_tax_data' => $company->origin_tax_data ?: new \stdClass(),
'smtp_host' => (string)$company->smtp_host ?? '',
'smtp_port' => (int)$company->smtp_port ?? 25,
'smtp_encryption' => (string)$company->smtp_encryption ?? 'tls',
'smtp_username' => $company->smtp_username ? '********' : '',
'smtp_password' => $company->smtp_password ? '********' : '',
'smtp_local_domain' => (string)$company->smtp_local_domain ?? '',
'smtp_verify_peer' => (bool)$company->smtp_verify_peer,
];
}

View File

@ -41,7 +41,10 @@ class ProjectTransformer extends EntityTransformer
{
$transformer = new DocumentTransformer($this->serializer);
return $this->includeCollection($project->documents, $transformer, Document::class);
if($project->documents)
return $this->includeCollection($project->documents, $transformer, Document::class);
return null;
}
public function includeClient(Project $project): \League\Fractal\Resource\Item

View File

@ -100,11 +100,10 @@ class TaskTransformer extends EntityTransformer
{
$transformer = new ProjectTransformer($this->serializer);
if (!$task->project) {
return null;
}
if ($task->project)
return $this->includeItem($task->project, $transformer, Project::class);
return $this->includeItem($task->project, $transformer, Project::class);
return null;
}
public function transform(Task $task)

View File

@ -95,6 +95,14 @@ class Number
*/
public static function parseFloat($value)
{
if(!$value)
return 0;
$multiplier = false;
if(substr($value, 0,1) == '-')
$multiplier = -1;
// convert "," to "."
$s = str_replace(',', '.', $value);
@ -108,7 +116,9 @@ class Number
// remove all separators from first part and keep the end
$s = str_replace('.', '', substr($s, 0, -3)).substr($s, -3);
// return float
if($multiplier)
$s = floatval($s)*-1;
return (float) $s;
}

View File

@ -75,11 +75,11 @@ trait CleanLineItems
}
if(isset($item['notes'])) {
$item['notes'] = str_replace("</", "<-", $item['notes']);
$item['notes'] = str_replace("</sc", "<-", $item['notes']);
}
if(isset($item['product_key'])) {
$item['product_key'] = str_replace("</", "<-", $item['product_key']);
$item['product_key'] = str_replace("</sc", "<-", $item['product_key']);
}
}

View File

@ -101,7 +101,8 @@
"twilio/sdk": "^6.40",
"webpatser/laravel-countries": "dev-master#75992ad",
"wepay/php-sdk": "^0.3",
"wildbit/postmark-php": "^4.0"
"wildbit/postmark-php": "^4.0",
"hyvor/php-json-exporter": "^0.0.3"
},
"require-dev": {
"php": "^8.1|^8.2",
@ -112,7 +113,6 @@
"fakerphp/faker": "^1.14",
"filp/whoops": "^2.7",
"friendsofphp/php-cs-fixer": "^3.14",
"hyvor/php-json-exporter": "^0.0.3",
"laracasts/cypress": "^3.0",
"larastan/larastan": "^2",
"mockery/mockery": "^1.4.4",

284
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "7a990a24b596bd1ab52a5f829fcc7e27",
"content-hash": "dc9142e4af116b98de0ac6310297dba6",
"packages": [
{
"name": "afosto/yaac",
@ -141,16 +141,16 @@
},
{
"name": "amphp/byte-stream",
"version": "v2.1.0",
"version": "v2.1.1",
"source": {
"type": "git",
"url": "https://github.com/amphp/byte-stream.git",
"reference": "0a4b0e80dad92c75e6131f8ad253919211540338"
"reference": "daa00f2efdbd71565bf64ffefa89e37542addf93"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/amphp/byte-stream/zipball/0a4b0e80dad92c75e6131f8ad253919211540338",
"reference": "0a4b0e80dad92c75e6131f8ad253919211540338",
"url": "https://api.github.com/repos/amphp/byte-stream/zipball/daa00f2efdbd71565bf64ffefa89e37542addf93",
"reference": "daa00f2efdbd71565bf64ffefa89e37542addf93",
"shasum": ""
},
"require": {
@ -166,7 +166,7 @@
"amphp/php-cs-fixer-config": "^2",
"amphp/phpunit-util": "^3",
"phpunit/phpunit": "^9",
"psalm/phar": "^5.4"
"psalm/phar": "5.22.1"
},
"type": "library",
"autoload": {
@ -204,7 +204,7 @@
],
"support": {
"issues": "https://github.com/amphp/byte-stream/issues",
"source": "https://github.com/amphp/byte-stream/tree/v2.1.0"
"source": "https://github.com/amphp/byte-stream/tree/v2.1.1"
},
"funding": [
{
@ -212,7 +212,7 @@
"type": "github"
}
],
"time": "2023-11-19T14:34:16+00:00"
"time": "2024-02-17T04:49:38+00:00"
},
{
"name": "amphp/cache",
@ -581,16 +581,16 @@
},
{
"name": "amphp/process",
"version": "v2.0.1",
"version": "v2.0.2",
"source": {
"type": "git",
"url": "https://github.com/amphp/process.git",
"reference": "a65d3bc1f36ef12d44df42a68f0f0643183f1052"
"reference": "a79dc87100be857db2c4bbfd5369585a6d1e658c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/amphp/process/zipball/a65d3bc1f36ef12d44df42a68f0f0643183f1052",
"reference": "a65d3bc1f36ef12d44df42a68f0f0643183f1052",
"url": "https://api.github.com/repos/amphp/process/zipball/a79dc87100be857db2c4bbfd5369585a6d1e658c",
"reference": "a79dc87100be857db2c4bbfd5369585a6d1e658c",
"shasum": ""
},
"require": {
@ -637,7 +637,7 @@
"homepage": "https://amphp.org/process",
"support": {
"issues": "https://github.com/amphp/process/issues",
"source": "https://github.com/amphp/process/tree/v2.0.1"
"source": "https://github.com/amphp/process/tree/v2.0.2"
},
"funding": [
{
@ -645,7 +645,7 @@
"type": "github"
}
],
"time": "2023-01-15T16:00:57+00:00"
"time": "2024-02-13T20:38:21+00:00"
},
{
"name": "amphp/serialization",
@ -1343,16 +1343,16 @@
},
{
"name": "aws/aws-sdk-php",
"version": "3.298.9",
"version": "3.299.1",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
"reference": "db225c3a1c5dabfbb5071349cfb7e4c396c3d9ec"
"reference": "a0f87b8e8bfb9afd0ffd702fcda556b465eee457"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/db225c3a1c5dabfbb5071349cfb7e4c396c3d9ec",
"reference": "db225c3a1c5dabfbb5071349cfb7e4c396c3d9ec",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/a0f87b8e8bfb9afd0ffd702fcda556b465eee457",
"reference": "a0f87b8e8bfb9afd0ffd702fcda556b465eee457",
"shasum": ""
},
"require": {
@ -1432,9 +1432,9 @@
"support": {
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
"issues": "https://github.com/aws/aws-sdk-php/issues",
"source": "https://github.com/aws/aws-sdk-php/tree/3.298.9"
"source": "https://github.com/aws/aws-sdk-php/tree/3.299.1"
},
"time": "2024-02-13T19:08:16+00:00"
"time": "2024-02-16T19:08:34+00:00"
},
{
"name": "bacon/bacon-qr-code",
@ -2265,16 +2265,16 @@
},
{
"name": "doctrine/dbal",
"version": "3.8.1",
"version": "3.8.2",
"source": {
"type": "git",
"url": "https://github.com/doctrine/dbal.git",
"reference": "c9ea252cdce4da324ede3d6c5913dd89f769afd2"
"reference": "a19a1d05ca211f41089dffcc387733a6875196cb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/dbal/zipball/c9ea252cdce4da324ede3d6c5913dd89f769afd2",
"reference": "c9ea252cdce4da324ede3d6c5913dd89f769afd2",
"url": "https://api.github.com/repos/doctrine/dbal/zipball/a19a1d05ca211f41089dffcc387733a6875196cb",
"reference": "a19a1d05ca211f41089dffcc387733a6875196cb",
"shasum": ""
},
"require": {
@ -2358,7 +2358,7 @@
],
"support": {
"issues": "https://github.com/doctrine/dbal/issues",
"source": "https://github.com/doctrine/dbal/tree/3.8.1"
"source": "https://github.com/doctrine/dbal/tree/3.8.2"
},
"funding": [
{
@ -2374,7 +2374,7 @@
"type": "tidelift"
}
],
"time": "2024-02-03T17:33:49+00:00"
"time": "2024-02-12T18:36:36+00:00"
},
{
"name": "doctrine/deprecations",
@ -4500,6 +4500,50 @@
},
"time": "2021-07-21T13:50:14+00:00"
},
{
"name": "hyvor/php-json-exporter",
"version": "0.0.3",
"source": {
"type": "git",
"url": "https://github.com/hyvor/php-json-exporter.git",
"reference": "9fade1856135deaa2d0fc9b3065949019703471d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/hyvor/php-json-exporter/zipball/9fade1856135deaa2d0fc9b3065949019703471d",
"reference": "9fade1856135deaa2d0fc9b3065949019703471d",
"shasum": ""
},
"require": {
"php": "^8.1"
},
"require-dev": {
"pestphp/pest": "^1.22",
"phpstan/phpstan": "^1.8"
},
"type": "library",
"autoload": {
"psr-4": {
"Hyvor\\JsonExporter\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Supun",
"email": "supun@hyvor.com"
}
],
"description": "Export large datasets to a JSON file without memory exhaustion",
"support": {
"issues": "https://github.com/hyvor/php-json-exporter/issues",
"source": "https://github.com/hyvor/php-json-exporter/tree/0.0.3"
},
"time": "2023-04-11T15:12:18+00:00"
},
{
"name": "imdhemy/appstore-iap",
"version": "1.6.0",
@ -4606,16 +4650,16 @@
},
{
"name": "imdhemy/laravel-purchases",
"version": "1.9.1",
"version": "1.10.0",
"source": {
"type": "git",
"url": "https://github.com/imdhemy/laravel-in-app-purchases.git",
"reference": "b74e09b78fb3e0f1b1630dbcfd23d9f6fe251b90"
"reference": "014c189795704bea88260a4054b02d80b9bbc3ac"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/imdhemy/laravel-in-app-purchases/zipball/b74e09b78fb3e0f1b1630dbcfd23d9f6fe251b90",
"reference": "b74e09b78fb3e0f1b1630dbcfd23d9f6fe251b90",
"url": "https://api.github.com/repos/imdhemy/laravel-in-app-purchases/zipball/014c189795704bea88260a4054b02d80b9bbc3ac",
"reference": "014c189795704bea88260a4054b02d80b9bbc3ac",
"shasum": ""
},
"require": {
@ -4671,7 +4715,7 @@
],
"support": {
"issues": "https://github.com/imdhemy/laravel-in-app-purchases/issues",
"source": "https://github.com/imdhemy/laravel-in-app-purchases/tree/1.9.1"
"source": "https://github.com/imdhemy/laravel-in-app-purchases/tree/1.10.0"
},
"funding": [
{
@ -4679,7 +4723,7 @@
"type": "github"
}
],
"time": "2023-12-15T10:35:56+00:00"
"time": "2024-02-17T05:58:30+00:00"
},
{
"name": "intervention/image",
@ -4829,7 +4873,7 @@
},
{
"name": "invoiceninja/ubl_invoice",
"version": "v2.0.1",
"version": "v2.0.2",
"source": {
"type": "git",
"url": "https://github.com/invoiceninja/UBL_invoice.git",
@ -4887,7 +4931,7 @@
"xml invoice"
],
"support": {
"source": "https://github.com/invoiceninja/UBL_invoice/tree/v2.0.1"
"source": "https://github.com/invoiceninja/UBL_invoice/tree/v2.0.2"
},
"time": "2024-02-05T02:16:14+00:00"
},
@ -5710,16 +5754,16 @@
},
{
"name": "laravel/socialite",
"version": "v5.12.0",
"version": "v5.12.1",
"source": {
"type": "git",
"url": "https://github.com/laravel/socialite.git",
"reference": "ffeeb2cdf723b4c88b25479968e2d3a61a83dbe5"
"reference": "7dae1b072573809f32ab6dcf4aebb57c8b3e8acf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/socialite/zipball/ffeeb2cdf723b4c88b25479968e2d3a61a83dbe5",
"reference": "ffeeb2cdf723b4c88b25479968e2d3a61a83dbe5",
"url": "https://api.github.com/repos/laravel/socialite/zipball/7dae1b072573809f32ab6dcf4aebb57c8b3e8acf",
"reference": "7dae1b072573809f32ab6dcf4aebb57c8b3e8acf",
"shasum": ""
},
"require": {
@ -5776,7 +5820,7 @@
"issues": "https://github.com/laravel/socialite/issues",
"source": "https://github.com/laravel/socialite"
},
"time": "2024-02-11T18:29:55+00:00"
"time": "2024-02-16T08:58:20+00:00"
},
{
"name": "laravel/tinker",
@ -7384,41 +7428,41 @@
},
{
"name": "moneyphp/money",
"version": "v4.4.0",
"version": "v4.5.0",
"source": {
"type": "git",
"url": "https://github.com/moneyphp/money.git",
"reference": "5e60aebf09f709dd4ea16bf85e66d65301c0d172"
"reference": "a1daa7daf159b4044e3d0c34c41fe2be5860e850"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/moneyphp/money/zipball/5e60aebf09f709dd4ea16bf85e66d65301c0d172",
"reference": "5e60aebf09f709dd4ea16bf85e66d65301c0d172",
"url": "https://api.github.com/repos/moneyphp/money/zipball/a1daa7daf159b4044e3d0c34c41fe2be5860e850",
"reference": "a1daa7daf159b4044e3d0c34c41fe2be5860e850",
"shasum": ""
},
"require": {
"ext-bcmath": "*",
"ext-filter": "*",
"ext-json": "*",
"php": "~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0"
"php": "~8.1.0 || ~8.2.0 || ~8.3.0"
},
"require-dev": {
"cache/taggable-cache": "^1.1.0",
"doctrine/coding-standard": "^9.0",
"doctrine/instantiator": "^1.4.0",
"doctrine/coding-standard": "^12.0",
"doctrine/instantiator": "^1.5.0 || ^2.0",
"ext-gmp": "*",
"ext-intl": "*",
"florianv/exchanger": "^2.6.3",
"florianv/exchanger": "^2.8.1",
"florianv/swap": "^4.3.0",
"moneyphp/crypto-currencies": "^1.0.0",
"moneyphp/iso-currencies": "^3.2.1",
"php-http/message": "^1.11.0",
"php-http/mock-client": "^1.4.1",
"moneyphp/crypto-currencies": "^1.1.0",
"moneyphp/iso-currencies": "^3.4",
"php-http/message": "^1.16.0",
"php-http/mock-client": "^1.6.0",
"phpbench/phpbench": "^1.2.5",
"phpunit/phpunit": "^9.5.4",
"phpunit/phpunit": "^10.5.9",
"psalm/plugin-phpunit": "^0.18.4",
"psr/cache": "^1.0.1 || ^2.0 || ^3.0",
"vimeo/psalm": "~5.15.0"
"vimeo/psalm": "~5.20.0"
},
"suggest": {
"ext-gmp": "Calculate without integer limits",
@ -7466,9 +7510,9 @@
],
"support": {
"issues": "https://github.com/moneyphp/money/issues",
"source": "https://github.com/moneyphp/money/tree/v4.4.0"
"source": "https://github.com/moneyphp/money/tree/v4.5.0"
},
"time": "2024-01-24T08:29:16+00:00"
"time": "2024-02-15T19:47:21+00:00"
},
{
"name": "monolog/monolog",
@ -11801,38 +11845,38 @@
},
{
"name": "spatie/php-structure-discoverer",
"version": "2.0.1",
"version": "2.1.0",
"source": {
"type": "git",
"url": "https://github.com/spatie/php-structure-discoverer.git",
"reference": "d2e4e6cba962ce2a058ea415a123dd84b37765ac"
"reference": "f5b3c935dda89d6c382b27e3caf348fa80bcfa88"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/php-structure-discoverer/zipball/d2e4e6cba962ce2a058ea415a123dd84b37765ac",
"reference": "d2e4e6cba962ce2a058ea415a123dd84b37765ac",
"url": "https://api.github.com/repos/spatie/php-structure-discoverer/zipball/f5b3c935dda89d6c382b27e3caf348fa80bcfa88",
"reference": "f5b3c935dda89d6c382b27e3caf348fa80bcfa88",
"shasum": ""
},
"require": {
"amphp/amp": "^v3.0",
"amphp/parallel": "^2.2",
"illuminate/collections": "^9.30|^10.0",
"illuminate/collections": "^10.0|^11.0",
"php": "^8.1",
"spatie/laravel-package-tools": "^1.4.3",
"symfony/finder": "^6.0|^7.0"
},
"require-dev": {
"illuminate/console": "^9.30|^10.0",
"illuminate/console": "^10.0|^11.0",
"laravel/pint": "^1.0",
"nunomaduro/collision": "^6.0",
"nunomaduro/collision": "^7.0|^8.0",
"nunomaduro/larastan": "^2.0.1",
"orchestra/testbench": "^7.0|^8.0",
"pestphp/pest": "^1.21",
"pestphp/pest-plugin-laravel": "^1.1",
"orchestra/testbench": "^7.0|^8.0|^9.0",
"pestphp/pest": "^2.0",
"pestphp/pest-plugin-laravel": "^2.0",
"phpstan/extension-installer": "^1.1",
"phpstan/phpstan-deprecation-rules": "^1.0",
"phpstan/phpstan-phpunit": "^1.0",
"phpunit/phpunit": "^9.5",
"phpunit/phpunit": "^9.5|^10.0",
"spatie/laravel-ray": "^1.26"
},
"type": "library",
@ -11869,7 +11913,7 @@
],
"support": {
"issues": "https://github.com/spatie/php-structure-discoverer/issues",
"source": "https://github.com/spatie/php-structure-discoverer/tree/2.0.1"
"source": "https://github.com/spatie/php-structure-discoverer/tree/2.1.0"
},
"funding": [
{
@ -11877,7 +11921,7 @@
"type": "github"
}
],
"time": "2024-01-08T21:01:26+00:00"
"time": "2024-02-16T12:42:24+00:00"
},
{
"name": "sprain/swiss-qr-bill",
@ -15832,16 +15876,16 @@
"packages-dev": [
{
"name": "barryvdh/laravel-debugbar",
"version": "v3.10.4",
"version": "v3.10.5",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-debugbar.git",
"reference": "09d3dc77d7dc1b063e3728a6029c39ee0fbebf1d"
"reference": "d1a48965f2b25a6cec2eea07d719b568a37c9a88"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/09d3dc77d7dc1b063e3728a6029c39ee0fbebf1d",
"reference": "09d3dc77d7dc1b063e3728a6029c39ee0fbebf1d",
"url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/d1a48965f2b25a6cec2eea07d719b568a37c9a88",
"reference": "d1a48965f2b25a6cec2eea07d719b568a37c9a88",
"shasum": ""
},
"require": {
@ -15900,7 +15944,7 @@
],
"support": {
"issues": "https://github.com/barryvdh/laravel-debugbar/issues",
"source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.10.4"
"source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.10.5"
},
"funding": [
{
@ -15912,43 +15956,43 @@
"type": "github"
}
],
"time": "2024-02-14T08:52:12+00:00"
"time": "2024-02-15T10:45:45+00:00"
},
{
"name": "barryvdh/laravel-ide-helper",
"version": "v2.14.0",
"version": "v2.15.1",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-ide-helper.git",
"reference": "485c756f6cff408d6b273274c5e86112c3973d98"
"reference": "77831852bb7bc54f287246d32eb91274eaf87f8b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/485c756f6cff408d6b273274c5e86112c3973d98",
"reference": "485c756f6cff408d6b273274c5e86112c3973d98",
"url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/77831852bb7bc54f287246d32eb91274eaf87f8b",
"reference": "77831852bb7bc54f287246d32eb91274eaf87f8b",
"shasum": ""
},
"require": {
"barryvdh/reflection-docblock": "^2.0.6",
"composer/class-map-generator": "^1.0",
"doctrine/dbal": "^2.6 || ^3",
"doctrine/dbal": "^2.6 || ^3.1.4",
"ext-json": "*",
"illuminate/console": "^8 || ^9 || ^10",
"illuminate/filesystem": "^8 || ^9 || ^10",
"illuminate/support": "^8 || ^9 || ^10",
"illuminate/console": "^9 || ^10",
"illuminate/filesystem": "^9 || ^10",
"illuminate/support": "^9 || ^10",
"nikic/php-parser": "^4.18 || ^5",
"php": "^7.3 || ^8.0",
"php": "^8.0",
"phpdocumentor/type-resolver": "^1.1.0"
},
"require-dev": {
"ext-pdo_sqlite": "*",
"friendsofphp/php-cs-fixer": "^2",
"illuminate/config": "^8 || ^9 || ^10",
"illuminate/view": "^8 || ^9 || ^10",
"friendsofphp/php-cs-fixer": "^3",
"illuminate/config": "^9 || ^10",
"illuminate/view": "^9 || ^10",
"mockery/mockery": "^1.4",
"orchestra/testbench": "^6 || ^7 || ^8",
"phpunit/phpunit": "^8.5 || ^9",
"spatie/phpunit-snapshot-assertions": "^3 || ^4",
"orchestra/testbench": "^7 || ^8",
"phpunit/phpunit": "^9",
"spatie/phpunit-snapshot-assertions": "^4",
"vimeo/psalm": "^5.4"
},
"suggest": {
@ -15957,7 +16001,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.14-dev"
"dev-master": "2.15-dev"
},
"laravel": {
"providers": [
@ -15994,7 +16038,7 @@
],
"support": {
"issues": "https://github.com/barryvdh/laravel-ide-helper/issues",
"source": "https://github.com/barryvdh/laravel-ide-helper/tree/v2.14.0"
"source": "https://github.com/barryvdh/laravel-ide-helper/tree/v2.15.1"
},
"funding": [
{
@ -16006,7 +16050,7 @@
"type": "github"
}
],
"time": "2024-02-05T08:16:36+00:00"
"time": "2024-02-15T14:23:20+00:00"
},
{
"name": "barryvdh/reflection-docblock",
@ -16780,50 +16824,6 @@
},
"time": "2020-07-09T08:09:16+00:00"
},
{
"name": "hyvor/php-json-exporter",
"version": "0.0.3",
"source": {
"type": "git",
"url": "https://github.com/hyvor/php-json-exporter.git",
"reference": "9fade1856135deaa2d0fc9b3065949019703471d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/hyvor/php-json-exporter/zipball/9fade1856135deaa2d0fc9b3065949019703471d",
"reference": "9fade1856135deaa2d0fc9b3065949019703471d",
"shasum": ""
},
"require": {
"php": "^8.1"
},
"require-dev": {
"pestphp/pest": "^1.22",
"phpstan/phpstan": "^1.8"
},
"type": "library",
"autoload": {
"psr-4": {
"Hyvor\\JsonExporter\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Supun",
"email": "supun@hyvor.com"
}
],
"description": "Export large datasets to a JSON file without memory exhaustion",
"support": {
"issues": "https://github.com/hyvor/php-json-exporter/issues",
"source": "https://github.com/hyvor/php-json-exporter/tree/0.0.3"
},
"time": "2023-04-11T15:12:18+00:00"
},
{
"name": "laracasts/cypress",
"version": "3.0.1",
@ -16986,16 +16986,16 @@
},
{
"name": "maximebf/debugbar",
"version": "v1.20.1",
"version": "v1.20.2",
"source": {
"type": "git",
"url": "https://github.com/maximebf/php-debugbar.git",
"reference": "06ebf922ccedfa4cc43015825697ee8c1fb80f7e"
"reference": "484625c23a4fa4f303617f29fcacd42951c9c01d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/06ebf922ccedfa4cc43015825697ee8c1fb80f7e",
"reference": "06ebf922ccedfa4cc43015825697ee8c1fb80f7e",
"url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/484625c23a4fa4f303617f29fcacd42951c9c01d",
"reference": "484625c23a4fa4f303617f29fcacd42951c9c01d",
"shasum": ""
},
"require": {
@ -17046,9 +17046,9 @@
],
"support": {
"issues": "https://github.com/maximebf/php-debugbar/issues",
"source": "https://github.com/maximebf/php-debugbar/tree/v1.20.1"
"source": "https://github.com/maximebf/php-debugbar/tree/v1.20.2"
},
"time": "2024-02-13T19:03:14+00:00"
"time": "2024-02-15T10:49:09+00:00"
},
{
"name": "mockery/mockery",

View File

@ -17,8 +17,8 @@ return [
'require_https' => env('REQUIRE_HTTPS', true),
'app_url' => rtrim(env('APP_URL', ''), '/'),
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
'app_version' => env('APP_VERSION', '5.8.25'),
'app_tag' => env('APP_TAG', '5.8.25'),
'app_version' => env('APP_VERSION', '5.8.26'),
'app_tag' => env('APP_TAG', '5.8.26'),
'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', false),

View File

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('companies', function (Blueprint $table) {
$table->string('smtp_host')->nullable();
$table->unsignedInteger('smtp_port')->nullable();
$table->string('smtp_encryption')->nullable();
$table->text('smtp_username')->nullable();
$table->text('smtp_password')->nullable();
$table->string('smtp_local_domain')->nullable();
$table->boolean('smtp_verify_peer')->default(0);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
//
}
};

View File

@ -4925,7 +4925,7 @@ $lang = array(
'no_assigned_tasks' => 'No billable tasks for this project',
'authorization_failure' => 'Insufficient permissions to perform this action',
'authorization_sms_failure' => 'Please verify your account to send emails.',
'white_label_body' => 'Thank you for purchasing a white label license. <br><br> Your license key is: <br><br> :license_key',
'white_label_body' => 'Thank you for purchasing a white label license. <br><br> Your license key is: <br><br> :license_key <br><br> You can manage your license here: https://invoiceninja.invoicing.co/client/login',
'payment_type_Klarna' => 'Klarna',
'payment_type_Interac E Transfer' => 'Interac E Transfer',
'xinvoice_payable' => 'Payable within :payeddue days net until :paydate',

View File

@ -1,4 +1,4 @@
@component('email.template.admin', ['settings' => $settings, 'logo' => $logo ?? 'https://www.invoiceninja.com/wp-content/uploads/2015/10/logo-white-horizontal-1.png'])
@component('email.template.admin', ['settings' => $settings, 'logo' => $logo ?? 'https://pdf.invoicing.co/favicon-v2.png'])
{{-- Body --}}
{!! $support_message !!}

View File

@ -10,113 +10,114 @@
| is assigned the "api" middleware group. Enjoy building your API!
|
*/
use App\Http\Controllers\AccountController;
use App\Http\Controllers\ActivityController;
use App\Http\Controllers\Auth\ForgotPasswordController;
use App\Http\Controllers\Auth\LoginController;
use App\Http\Controllers\Auth\PasswordTimeoutController;
use App\Http\Controllers\Bank\NordigenController;
use App\Http\Controllers\Bank\YodleeController;
use App\Http\Controllers\BankIntegrationController;
use App\Http\Controllers\BankTransactionController;
use App\Http\Controllers\BankTransactionRuleController;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\BaseController;
use App\Http\Controllers\PingController;
use App\Http\Controllers\SmtpController;
use App\Http\Controllers\TaskController;
use App\Http\Controllers\UserController;
use App\Http\Controllers\ChartController;
use App\Http\Controllers\EmailController;
use App\Http\Controllers\QuoteController;
use App\Http\Controllers\TokenController;
use App\Http\Controllers\ClientController;
use App\Http\Controllers\ClientGatewayTokenController;
use App\Http\Controllers\ClientStatementController;
use App\Http\Controllers\CompanyController;
use App\Http\Controllers\CompanyGatewayController;
use App\Http\Controllers\CompanyLedgerController;
use App\Http\Controllers\CompanyUserController;
use App\Http\Controllers\ConnectedAccountController;
use App\Http\Controllers\CreditController;
use App\Http\Controllers\DesignController;
use App\Http\Controllers\DocumentController;
use App\Http\Controllers\EmailController;
use App\Http\Controllers\EmailHistoryController;
use App\Http\Controllers\ExpenseCategoryController;
use App\Http\Controllers\ExpenseController;
use App\Http\Controllers\ExportController;
use App\Http\Controllers\FilterController;
use App\Http\Controllers\GroupSettingController;
use App\Http\Controllers\HostedMigrationController;
use App\Http\Controllers\ImportController;
use App\Http\Controllers\ImportJsonController;
use App\Http\Controllers\InAppPurchase\AppleController;
use App\Http\Controllers\LogoutController;
use App\Http\Controllers\SearchController;
use App\Http\Controllers\StaticController;
use App\Http\Controllers\StripeController;
use App\Http\Controllers\TwilioController;
use App\Http\Controllers\VendorController;
use App\Http\Controllers\AccountController;
use App\Http\Controllers\CompanyController;
use App\Http\Controllers\ExpenseController;
use App\Http\Controllers\InvoiceController;
use App\Http\Controllers\LicenseController;
use App\Http\Controllers\LogoutController;
use App\Http\Controllers\MailgunWebhookController;
use App\Http\Controllers\MigrationController;
use App\Http\Controllers\OneTimeTokenController;
use App\Http\Controllers\PaymentController;
use App\Http\Controllers\PaymentNotificationWebhookController;
use App\Http\Controllers\PaymentTermController;
use App\Http\Controllers\PaymentWebhookController;
use App\Http\Controllers\PingController;
use App\Http\Controllers\PostMarkController;
use App\Http\Controllers\PreviewController;
use App\Http\Controllers\PreviewPurchaseOrderController;
use App\Http\Controllers\ProductController;
use App\Http\Controllers\ProjectController;
use App\Http\Controllers\ProtectedDownloadController;
use App\Http\Controllers\TaxRateController;
use App\Http\Controllers\WebCronController;
use App\Http\Controllers\WebhookController;
use App\Http\Controllers\ActivityController;
use App\Http\Controllers\DocumentController;
use App\Http\Controllers\PostMarkController;
use App\Http\Controllers\TemplateController;
use App\Http\Controllers\MigrationController;
use App\Http\Controllers\SchedulerController;
use App\Http\Controllers\SubdomainController;
use App\Http\Controllers\SystemLogController;
use App\Http\Controllers\TwoFactorController;
use App\Http\Controllers\Auth\LoginController;
use App\Http\Controllers\ImportJsonController;
use App\Http\Controllers\SelfUpdateController;
use App\Http\Controllers\TaskStatusController;
use App\Http\Controllers\Bank\YodleeController;
use App\Http\Controllers\CompanyUserController;
use App\Http\Controllers\PaymentTermController;
use App\PaymentDrivers\PayPalPPCPPaymentDriver;
use App\Http\Controllers\EmailHistoryController;
use App\Http\Controllers\GroupSettingController;
use App\Http\Controllers\OneTimeTokenController;
use App\Http\Controllers\SubscriptionController;
use App\Http\Controllers\Bank\NordigenController;
use App\Http\Controllers\CompanyLedgerController;
use App\Http\Controllers\PurchaseOrderController;
use App\Http\Controllers\QuoteController;
use App\Http\Controllers\TaskSchedulerController;
use App\Http\Controllers\CompanyGatewayController;
use App\Http\Controllers\MailgunWebhookController;
use App\Http\Controllers\PaymentWebhookController;
use App\Http\Controllers\RecurringQuoteController;
use App\Http\Controllers\BankIntegrationController;
use App\Http\Controllers\BankTransactionController;
use App\Http\Controllers\ClientStatementController;
use App\Http\Controllers\ExpenseCategoryController;
use App\Http\Controllers\HostedMigrationController;
use App\Http\Controllers\TemplatePreviewController;
use App\Http\Controllers\ConnectedAccountController;
use App\Http\Controllers\RecurringExpenseController;
use App\Http\Controllers\RecurringInvoiceController;
use App\Http\Controllers\RecurringQuoteController;
use App\Http\Controllers\Reports\ActivityReportController;
use App\Http\Controllers\Reports\ARDetailReportController;
use App\Http\Controllers\Reports\ARSummaryReportController;
use App\Http\Controllers\Reports\ClientBalanceReportController;
use App\Http\Controllers\Reports\ClientContactReportController;
use App\Http\Controllers\ProtectedDownloadController;
use App\Http\Controllers\ClientGatewayTokenController;
use App\Http\Controllers\Reports\TaskReportController;
use App\Http\Controllers\Auth\ForgotPasswordController;
use App\Http\Controllers\BankTransactionRuleController;
use App\Http\Controllers\InAppPurchase\AppleController;
use App\Http\Controllers\Reports\QuoteReportController;
use App\Http\Controllers\Auth\PasswordTimeoutController;
use App\Http\Controllers\PreviewPurchaseOrderController;
use App\Http\Controllers\Reports\ClientReportController;
use App\Http\Controllers\Reports\ClientSalesReportController;
use App\Http\Controllers\Reports\CreditReportController;
use App\Http\Controllers\Reports\DocumentReportController;
use App\Http\Controllers\Reports\ReportExportController;
use App\Http\Controllers\Reports\VendorReportController;
use App\Http\Controllers\Reports\ExpenseReportController;
use App\Http\Controllers\Reports\InvoiceItemReportController;
use App\Http\Controllers\Reports\InvoiceReportController;
use App\Http\Controllers\Reports\PaymentReportController;
use App\Http\Controllers\Reports\ProductReportController;
use App\Http\Controllers\Reports\ProductSalesReportController;
use App\Http\Controllers\Reports\ProfitAndLossController;
use App\Http\Controllers\Reports\PurchaseOrderItemReportController;
use App\Http\Controllers\Reports\PurchaseOrderReportController;
use App\Http\Controllers\Reports\QuoteItemReportController;
use App\Http\Controllers\Reports\QuoteReportController;
use App\Http\Controllers\Reports\RecurringInvoiceReportController;
use App\Http\Controllers\Reports\ReportExportController;
use App\Http\Controllers\Reports\ReportPreviewController;
use App\Http\Controllers\Reports\TaskReportController;
use App\Http\Controllers\Reports\TaxSummaryReportController;
use App\Http\Controllers\Reports\ActivityReportController;
use App\Http\Controllers\Reports\ARDetailReportController;
use App\Http\Controllers\Reports\DocumentReportController;
use App\Http\Controllers\Reports\ARSummaryReportController;
use App\Http\Controllers\Reports\QuoteItemReportController;
use App\Http\Controllers\Reports\UserSalesReportController;
use App\Http\Controllers\Reports\VendorReportController;
use App\Http\Controllers\SchedulerController;
use App\Http\Controllers\SearchController;
use App\Http\Controllers\SelfUpdateController;
use App\Http\Controllers\StaticController;
use App\Http\Controllers\StripeController;
use App\Http\Controllers\SubdomainController;
use App\Http\Controllers\SubscriptionController;
use App\Http\Controllers\Reports\TaxSummaryReportController;
use App\Http\Controllers\Support\Messages\SendingController;
use App\Http\Controllers\SystemLogController;
use App\Http\Controllers\TaskController;
use App\Http\Controllers\TaskSchedulerController;
use App\Http\Controllers\TaskStatusController;
use App\Http\Controllers\TaxRateController;
use App\Http\Controllers\TemplateController;
use App\Http\Controllers\TemplatePreviewController;
use App\Http\Controllers\TokenController;
use App\Http\Controllers\TwilioController;
use App\Http\Controllers\TwoFactorController;
use App\Http\Controllers\UserController;
use App\Http\Controllers\VendorController;
use App\Http\Controllers\WebCronController;
use App\Http\Controllers\WebhookController;
use App\PaymentDrivers\PayPalPPCPPaymentDriver;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Reports\ClientSalesReportController;
use App\Http\Controllers\Reports\InvoiceItemReportController;
use App\Http\Controllers\PaymentNotificationWebhookController;
use App\Http\Controllers\Reports\ProductSalesReportController;
use App\Http\Controllers\Reports\ClientBalanceReportController;
use App\Http\Controllers\Reports\ClientContactReportController;
use App\Http\Controllers\Reports\PurchaseOrderReportController;
use App\Http\Controllers\Reports\RecurringInvoiceReportController;
use App\Http\Controllers\Reports\PurchaseOrderItemReportController;
Route::group(['middleware' => ['throttle:api', 'api_secret_check']], function () {
Route::post('api/v1/signup', [AccountController::class, 'store'])->name('signup.submit');
@ -392,6 +393,8 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale']
// Route::post('hooks', [SubscriptionController::class, 'subscribe'])->name('hooks.subscribe');
// Route::delete('hooks/{subscription_id}', [SubscriptionController::class, 'unsubscribe'])->name('hooks.unsubscribe');
Route::post('smtp/check', [SmtpController::class, 'check'])->name('smtp.check')->middleware('throttle:10,1');
Route::post('stripe/update_payment_methods', [StripeController::class, 'update'])->middleware('password_protected')->name('stripe.update');
Route::post('stripe/import_customers', [StripeController::class, 'import'])->middleware('password_protected')->name('stripe.import');

View File

@ -59,6 +59,78 @@ class ClientApiTest extends TestCase
Model::reguard();
}
public function testDocumentValidation()
{
$data = [
'name' => 'name of client',
'documents' => [],
];
$response = $this->withHeaders([
'X-API-TOKEN' => $this->token,
])->postJson("/api/v1/clients",$data)
->assertStatus(200);
}
public function testDocumentValidationFails()
{
$data = [
'name' => 'name of client',
'documents' => 'wut',
];
$response = $this->withHeaders([
'X-API-TOKEN' => $this->token,
])->postJson("/api/v1/clients", $data)
->assertStatus(422);
$data = [
'name' => 'name of client',
'documents' => null,
];
$response = $this->withHeaders([
'X-API-TOKEN' => $this->token,
])->postJson("/api/v1/clients", $data)
->assertStatus(422);
}
public function testDocumentValidationPutFails()
{
$data = [
'name' => 'name of client',
'documents' => 'wut',
];
$response = $this->withHeaders([
'X-API-TOKEN' => $this->token,
])->putJson("/api/v1/clients/{$this->client->hashed_id}", $data)
->assertStatus(422);
$data = [
'name' => 'name of client',
'documents' => null,
];
$response = $this->withHeaders([
'X-API-TOKEN' => $this->token,
])->putJson("/api/v1/clients/{$this->client->hashed_id}", $data)
->assertStatus(422);
$data = [
'name' => 'name of client',
'documents' => [],
];
$response = $this->withHeaders([
'X-API-TOKEN' => $this->token,
])->putJson("/api/v1/clients/{$this->client->hashed_id}", $data)
->assertStatus(200);
}
public function testClientDocumentQuery()
{

View File

@ -50,6 +50,15 @@ class CompanyTest extends TestCase
$this->makeTestData();
}
public function testEnsureStrReplace()
{
$x = '**********';
$new_string = str_replace("*", "", $x);
$this->assertEquals(0, strlen($new_string));
}
public function testCompanyTaxInit()
{
TaxRate::query()->delete();

View File

@ -0,0 +1,180 @@
<?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\Payments;
use App\DataMapper\ClientSettings;
use App\Factory\InvoiceFactory;
use App\Helpers\Invoice\InvoiceSum;
use App\Models\Client;
use App\Models\Credit;
use App\Models\Invoice;
use App\Models\Payment;
use App\Utils\Traits\MakesHash;
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\MockUnitData;
use Tests\TestCase;
/**
* @test
*/
class AutoUnappliedPaymentTest extends TestCase
{
use MakesHash;
use DatabaseTransactions;
use MockUnitData;
protected function setUp() :void
{
parent::setUp();
Session::start();
$this->faker = \Faker\Factory::create();
Model::reguard();
$this->makeTestData();
// $this->withoutExceptionHandling();
$this->withoutMiddleware(
ThrottleRequests::class
);
}
public function testUnappliedPaymentsAreEnabled()
{
$settings = ClientSettings::defaults();
$settings->use_unapplied_payment = 'always';
$client = Client::factory()->create([
'company_id' => $this->company->id,
'user_id' => $this->user->id,
'settings' => $settings,
]);
$this->assertEquals('always', $client->settings->use_unapplied_payment);
$invoice = Invoice::factory()->for($client)->create([
'company_id' => $this->company->id,
'user_id' => $this->user->id,
'auto_bill_enabled' => true,
'client_id' => $client->id,
]);
$invoice = $invoice->calc()->getInvoice();
$payment = Payment::factory()->for($client)->create([
'company_id' => $this->company->id,
'user_id' => $this->user->id,
'client_id' => $client->id,
'amount' => 100,
'applied' => 0,
'refunded' => 0,
'status_id' => Payment::STATUS_COMPLETED,
'is_deleted' => 0,
]);
$invoice->service()->markSent()->save();
$this->assertGreaterThan(0, $invoice->balance);
nlog($invoice->balance);
try{
$invoice->service()->autoBill()->save();
}
catch(\Exception $e){
}
$invoice = $invoice->fresh();
$payment = $payment->fresh();
nlog($invoice->toArray());
nlog($payment->toArray());
$this->assertEquals($payment->applied, $invoice->paid_to_date);
$this->assertGreaterThan(2, $invoice->status_id);
$this->assertGreaterThan(0, $payment->applied);
// $this->assertEquals(Invoice::STATUS_PAID, $invoice->status_id);
// $this->assertEquals(0, $invoice->balance);
}
public function testUnappliedPaymentsAreDisabled()
{
$settings = ClientSettings::defaults();
$settings->use_unapplied_payment = 'off';
$client = Client::factory()->create([
'company_id' => $this->company->id,
'user_id' => $this->user->id,
'settings' => $settings,
]);
$this->assertEquals('off', $client->settings->use_unapplied_payment);
$invoice = Invoice::factory()->create([
'company_id' => $this->company->id,
'user_id' => $this->user->id,
'client_id' => $client->id,
'auto_bill_enabled' => true,
'status_id' => 2
]);
$invoice = $invoice->calc()->getInvoice();
$invoice_balance = $invoice->balance;
$payment = Payment::factory()->create([
'company_id' => $this->company->id,
'user_id' => $this->user->id,
'client_id' => $client->id,
'amount' => 100,
'applied' => 0,
'refunded' => 0,
'status_id' => Payment::STATUS_COMPLETED
]);
$invoice->service()->markSent()->save();
$this->assertGreaterThan(0, $invoice->balance);
try {
$invoice->service()->autoBill()->save();
}
catch(\Exception $e) {
}
$invoice = $invoice->fresh();
$payment = $payment->fresh();
$this->assertEquals($invoice_balance, $invoice->balance);
$this->assertEquals(0, $payment->applied);
$this->assertEquals(2, $invoice->status_id);
$this->assertEquals(0, $invoice->paid_to_date);
$this->assertEquals($invoice->amount, $invoice->balance);
// $this->assertEquals($payment->applied, $invoice->paid_to_date);
// $this->assertEquals(2, $invoice->status_id);
}
}

View File

@ -2,9 +2,19 @@
namespace Tests;
use App\Utils\Traits\AppSetup;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase
{
use CreatesApplication;
use AppSetup;
protected function setUp() :void
{
parent::setUp();
$this->buildCache(true);
}
}