Merge pull request #8576 from turbo124/preview

Preview
This commit is contained in:
David Bomba 2023-06-25 13:19:25 +10:00 committed by GitHub
commit db80be3d69
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
163 changed files with 457367 additions and 248390 deletions

View File

@ -1 +1 @@
5.6.1 5.6.4

View File

@ -53,8 +53,10 @@ class TranslationsExport extends Command
'fr_CA', 'fr_CA',
'he', 'he',
'hr', 'hr',
'hu',
'it', 'it',
'ja', 'ja',
'km_KH',
'lt', 'lt',
'lv_LV', 'lv_LV',
'mk_MK', 'mk_MK',
@ -131,10 +133,11 @@ class TranslationsExport extends Command
Storage::disk('local')->makeDirectory('lang'); Storage::disk('local')->makeDirectory('lang');
foreach ($this->langs as $lang) { foreach ($this->langs as $lang) {
nlog($lang);
Storage::disk('local')->makeDirectory("lang/{$lang}"); Storage::disk('local')->makeDirectory("lang/{$lang}");
$translations = Lang::getLoader()->load($lang, 'texts'); $translations = Lang::getLoader()->load($lang, 'texts');
nlog($translations);
Storage::disk('local')->put("lang/{$lang}/{$lang}.json", json_encode(Arr::dot($translations), JSON_UNESCAPED_UNICODE)); Storage::disk('local')->put("lang/{$lang}/{$lang}.json", json_encode(Arr::dot($translations), JSON_UNESCAPED_UNICODE));
} }
} }

View File

@ -41,6 +41,7 @@ class EmailStatement
public const LAST_QUARTER = "last_quarter"; public const LAST_QUARTER = "last_quarter";
public const THIS_YEAR = "this_year"; public const THIS_YEAR = "this_year";
public const LAST_YEAR = "last_year"; public const LAST_YEAR = "last_year";
public const ALL_TIME = "all_time";
public const CUSTOM_RANGE = "custom"; public const CUSTOM_RANGE = "custom";

View File

@ -162,14 +162,16 @@ class Rule extends BaseRule implements RuleInterface
if($this->tax_data?->stateSalesTax == 0) { if($this->tax_data?->stateSalesTax == 0) {
$this->tax_rate1 = $this->invoice->client->company->tax_data->regions->{$this->client_region}->subregions->{$this->client_subregion}->tax_rate; $this->tax_rate1 = 0;
$this->tax_name1 = "Sales Tax"; $this->tax_name1 = '';
// $this->tax_rate1 = $this->invoice->client->company->tax_data->regions->{$this->client_region}->subregions->{$this->client_subregion}->tax_rate;
// $this->tax_name1 = "Sales Tax";
return $this; return $this;
} }
$this->tax_rate1 = $this->tax_data->taxSales * 100; $this->tax_rate1 = $this->tax_data->taxSales * 100;
// $this->tax_name1 = "{$this->tax_data->geoState} Sales Tax";
$this->tax_name1 = "Sales Tax"; $this->tax_name1 = "Sales Tax";
return $this; return $this;

View File

@ -13,9 +13,10 @@ namespace App\Events\Payment;
use App\Models\Company; use App\Models\Company;
use App\Models\Payment; use App\Models\Payment;
use Illuminate\Broadcasting\InteractsWithSockets; use App\Models\ClientContact;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
/** /**
* Class PaymentWasEmailed. * Class PaymentWasEmailed.
@ -24,26 +25,15 @@ class PaymentWasEmailed
{ {
use Dispatchable, InteractsWithSockets, SerializesModels; use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* @var Payment
*/
public $payment;
public $company;
public $event_vars;
/** /**
* Create a new event instance. * Create a new event instance.
* *
* @param Payment $payment * @param Payment $payment
* @param Company $company * @param Company $company
* @param ClientContact $contact
* @param array $event_vars * @param array $event_vars
*/ */
public function __construct(Payment $payment, Company $company, array $event_vars) public function __construct(public Payment $payment, public Company $company, public ClientContact $contact, public array $event_vars)
{ {
$this->payment = $payment;
$this->company = $company;
$this->event_vars = $event_vars;
} }
} }

View File

@ -115,6 +115,11 @@ class CreditFilters extends QueryFilters
return $this->builder; return $this->builder;
} }
if ($sort_col[0] == 'client_id') {
return $this->builder->orderBy(\App\Models\Client::select('name')
->whereColumn('clients.id', 'credits.client_id'), $sort_col[1]);
}
return $this->builder->orderBy($sort_col[0], $sort_col[1]); return $this->builder->orderBy($sort_col[0], $sort_col[1]);
} }

View File

@ -61,11 +61,11 @@ class DesignFilters extends QueryFilters
*/ */
public function entityFilter(): Builder public function entityFilter(): Builder
{ {
//19-03-2023 change the scope for the design filters /** @var \App\Models\User $user */
return $this->builder->where(function ($query) { $user = auth()->user();
$query->where('company_id', auth()->user()->company()->id)->orWhere('company_id', null)->orderBy('id', 'asc');
// return $this->builder->where('company_id', auth()->user()->company()->id)->orWhere('company_id', null)->orderBy('id', 'asc');
return $this->builder->where(function ($query) use($user){
$query->where('company_id', $user->company()->id)->orWhere('company_id', null)->orderBy('id', 'asc');
}); });
} }

View File

@ -11,12 +11,14 @@
namespace App\Filters; namespace App\Filters;
use RuntimeException;
use App\Models\Client;
use App\Models\Invoice; use App\Models\Invoice;
use App\Filters\QueryFilters;
use InvalidArgumentException;
use Illuminate\Support\Carbon;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
use InvalidArgumentException;
use RuntimeException;
/** /**
* InvoiceFilters. * InvoiceFilters.
@ -204,6 +206,13 @@ class InvoiceFilters extends QueryFilters
return $this->builder; return $this->builder;
} }
if ($sort_col[0] == 'client_id') {
return $this->builder->orderBy(\App\Models\Client::select('name')
->whereColumn('clients.id', 'invoices.client_id'), $sort_col[1]);
}
return $this->builder->orderBy($sort_col[0], $sort_col[1]); return $this->builder->orderBy($sort_col[0], $sort_col[1]);
} }

View File

@ -40,7 +40,10 @@ class PaymentFilters extends QueryFilters
->orWhere('custom_value1', 'like', '%'.$filter.'%') ->orWhere('custom_value1', 'like', '%'.$filter.'%')
->orWhere('custom_value2', 'like', '%'.$filter.'%') ->orWhere('custom_value2', 'like', '%'.$filter.'%')
->orWhere('custom_value3', 'like', '%'.$filter.'%') ->orWhere('custom_value3', 'like', '%'.$filter.'%')
->orWhere('custom_value4', 'like', '%'.$filter.'%'); ->orWhere('custom_value4', 'like', '%'.$filter.'%')
->orWhereHas('client', function ($q) use ($filter) {
$q->where('name', 'like', '%'.$filter.'%');
});
}); });
} }
@ -155,6 +158,13 @@ class PaymentFilters extends QueryFilters
return $this->builder; return $this->builder;
} }
if ($sort_col[0] == 'client_id') {
return $this->builder->orderBy(\App\Models\Client::select('name')
->whereColumn('clients.id', 'payments.client_id'), $sort_col[1]);
}
return $this->builder->orderBy($sort_col[0], $sort_col[1]); return $this->builder->orderBy($sort_col[0], $sort_col[1]);
} }

View File

@ -135,6 +135,13 @@ class QuoteFilters extends QueryFilters
return $this->builder; return $this->builder;
} }
if($sort_col[0] == 'client_id'){
return $this->builder->orderBy(\App\Models\Client::select('name')
->whereColumn('clients.id', 'quotes.client_id'), $sort_col[1]);
}
if ($sort_col[0] == 'valid_until') { if ($sort_col[0] == 'valid_until') {
$sort_col[0] = 'due_date'; $sort_col[0] = 'due_date';
} }

View File

@ -34,10 +34,15 @@ class RecurringInvoiceFilters extends QueryFilters
} }
return $this->builder->where(function ($query) use ($filter) { return $this->builder->where(function ($query) use ($filter) {
$query->where('recurring_invoices.custom_value1', 'like', '%'.$filter.'%') $query->where('date', 'like', '%'.$filter.'%')
->orWhere('recurring_invoices.custom_value2', 'like', '%'.$filter.'%') ->orWhere('amount', 'like', '%'.$filter.'%')
->orWhere('recurring_invoices.custom_value3', 'like', '%'.$filter.'%') ->orWhere('custom_value1', 'like', '%'.$filter.'%')
->orWhere('recurring_invoices.custom_value4', 'like', '%'.$filter.'%'); ->orWhere('custom_value2', 'like', '%'.$filter.'%')
->orWhere('custom_value3', 'like', '%'.$filter.'%')
->orWhere('custom_value4', 'like', '%'.$filter.'%')
->orWhereHas('client', function ($q) use ($filter) {
$q->where('name', 'like', '%'.$filter.'%');
});
}); });
} }
@ -110,6 +115,13 @@ class RecurringInvoiceFilters extends QueryFilters
return $this->builder; return $this->builder;
} }
if ($sort_col[0] == 'client_id') {
return $this->builder->orderBy(\App\Models\Client::select('name')
->whereColumn('clients.id', 'recurring_invoices.client_id'), $sort_col[1]);
}
return $this->builder->orderBy($sort_col[0], $sort_col[1]); return $this->builder->orderBy($sort_col[0], $sort_col[1]);
} }

View File

@ -39,7 +39,13 @@ class TaskFilters extends QueryFilters
->orWhere('custom_value1', 'like', '%'.$filter.'%') ->orWhere('custom_value1', 'like', '%'.$filter.'%')
->orWhere('custom_value2', 'like', '%'.$filter.'%') ->orWhere('custom_value2', 'like', '%'.$filter.'%')
->orWhere('custom_value3', 'like', '%'.$filter.'%') ->orWhere('custom_value3', 'like', '%'.$filter.'%')
->orWhere('custom_value4', 'like', '%'.$filter.'%'); ->orWhere('custom_value4', 'like', '%'.$filter.'%')
->orWhereHas('project', function ($q) use ($filter) {
$q->where('name', 'like', '%'.$filter.'%');
})
->orWhereHas('client', function ($q) use ($filter) {
$q->where('name', 'like', '%'.$filter.'%');
});
}); });
} }
@ -104,6 +110,16 @@ class TaskFilters extends QueryFilters
return $this->builder; return $this->builder;
} }
if ($sort_col[0] == 'client_id') {
return $this->builder->orderBy(\App\Models\Client::select('name')
->whereColumn('clients.id', 'tasks.client_id'), $sort_col[1]);
}
if ($sort_col[0] == 'user_id') {
return $this->builder->orderBy(\App\Models\User::select('first_name')
->whereColumn('users.id', 'tasks.user_id'), $sort_col[1]);
}
return $this->builder->orderBy($sort_col[0], $sort_col[1]); return $this->builder->orderBy($sort_col[0], $sort_col[1]);
} }

View File

@ -165,7 +165,6 @@ class InvoiceSum
{ {
if (! isset($this->invoice->id) && isset($this->invoice->partial)) { if (! isset($this->invoice->id) && isset($this->invoice->partial)) {
$this->invoice->partial = max(0, min(Number::roundValue($this->invoice->partial, 2), $this->invoice->balance)); $this->invoice->partial = max(0, min(Number::roundValue($this->invoice->partial, 2), $this->invoice->balance));
// $this->invoice->partial = max(0, min($this->formatValue($this->invoice->partial, 2), $this->invoice->balance));
} }
return $this; return $this;

View File

@ -85,7 +85,7 @@ class ActivityController extends BaseController
*/ */
public function index(Request $request) public function index(Request $request)
{ {
$default_activities = $request->has('rows') ? $request->input('rows') : 50; $default_activities = $request->has('rows') ? $request->input('rows') : 75;
$activities = Activity::with('user') $activities = Activity::with('user')
->orderBy('created_at', 'DESC') ->orderBy('created_at', 'DESC')
@ -93,18 +93,22 @@ class ActivityController extends BaseController
->take($default_activities); ->take($default_activities);
if ($request->has('react')) { if ($request->has('react')) {
if (!auth()->user()->isAdmin()) {
/** @var \App\Models\User auth()->user() */
$user = auth()->user();
if (!$user->isAdmin()) {
$activities->where('user_id', auth()->user()->id); $activities->where('user_id', auth()->user()->id);
} }
// return response()->json(['data' => []], 200);
$system = ctrans('texts.system'); $system = ctrans('texts.system');
$data = $activities->cursor()->map(function ($activity) use ($system) { $data = $activities->cursor()->map(function ($activity) {
$arr = $arr =
[ [
'client' => $activity->client ? $activity->client : '', 'client' => $activity->client ? $activity->client : '',
'contact' => $activity->contact ? $activity->contact : '', 'contact' => $activity->client ? $activity->contact : '',
'quote' => $activity->quote ? $activity->quote : '', 'quote' => $activity->quote ? $activity->quote : '',
'user' => $activity->user ? $activity->user : '', 'user' => $activity->user ? $activity->user : '',
'expense' => $activity->expense ? $activity->expense : '', 'expense' => $activity->expense ? $activity->expense : '',
@ -120,7 +124,28 @@ class ActivityController extends BaseController
'recurring_expense' => $activity->recurring_expense ? $activity->recurring_expense : '', 'recurring_expense' => $activity->recurring_expense ? $activity->recurring_expense : '',
]; ];
return array_merge($arr, $activity->toArray()); $activity_array = $activity->toArray();
return array_merge($arr, $activity_array);
});
return response()->json(['data' => $data->toArray()], 200);
}
elseif($request->has('reactv2')) {
/** @var \App\Models\User auth()->user() */
$user = auth()->user();
if (!$user->isAdmin()) {
$activities->where('user_id', auth()->user()->id);
}
$system = ctrans('texts.system');
$data = $activities->cursor()->map(function ($activity) use ($system) {
return $activity->activity_string();
}); });
return response()->json(['data' => $data->toArray()], 200); return response()->json(['data' => $data->toArray()], 200);

View File

@ -58,7 +58,7 @@ class DocumentController extends Controller
$document = Document::where('hash', $document_hash)->firstOrFail(); $document = Document::where('hash', $document_hash)->firstOrFail();
$headers = []; $headers = ['Cache-Control:' => 'no-cache'];
if (request()->input('inline') == 'true') { if (request()->input('inline') == 'true') {
$headers = array_merge($headers, ['Content-Disposition' => 'inline']); $headers = array_merge($headers, ['Content-Disposition' => 'inline']);

View File

@ -87,7 +87,7 @@ class CompanyController extends BaseController
* summary="Gets a list of companies", * summary="Gets a list of companies",
* description="Lists companies, search and filters allow fine grained lists to be generated. * description="Lists companies, search and filters allow fine grained lists to be generated.
Query parameters can be added to performed more fine grained filtering of the companies, these are handled by the CompanyFilters class which defines the methods available", * Query parameters can be added to performed more fine grained filtering of the companies, these are handled by the CompanyFilters class which defines the methods available",
* @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"), * @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"), * @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"), * @OA\Parameter(ref="#/components/parameters/include"),
@ -114,7 +114,10 @@ class CompanyController extends BaseController
*/ */
public function index() public function index()
{ {
$companies = Company::whereAccountId(auth()->user()->company()->account->id); /** @var \App\Models\User $user */
$user = auth()->user();
$companies = Company::whereAccountId($user->company()->account->id);
return $this->listResponse($companies); return $this->listResponse($companies);
} }
@ -159,8 +162,12 @@ class CompanyController extends BaseController
*/ */
public function create(CreateCompanyRequest $request) public function create(CreateCompanyRequest $request)
{ {
$cf = new \App\Factory\CompanyFactory; /** @var \App\Models\User $user */
$company = $cf->create(auth()->user()->company()->account->id); $user = auth()->user();
$company_factory = new \App\Factory\CompanyFactory;
$company = $company_factory->create($user->company()->account->id);
return $this->itemResponse($company); return $this->itemResponse($company);
} }
@ -206,15 +213,18 @@ class CompanyController extends BaseController
{ {
$this->forced_includes = ['company_user']; $this->forced_includes = ['company_user'];
$company = (new CreateCompany($request->all(), auth()->user()->company()->account))->handle(); /** @var \App\Models\User $user */
(new CreateCompanyPaymentTerms($company, auth()->user()))->handle(); $user = auth()->user();
(new CreateCompanyTaskStatuses($company, auth()->user()))->handle();
$company = (new CreateCompany($request->all(), $user->company()->account))->handle();
(new CreateCompanyPaymentTerms($company, $user))->handle();
(new CreateCompanyTaskStatuses($company, $user))->handle();
$company = $this->company_repo->save($request->all(), $company); $company = $this->company_repo->save($request->all(), $company);
$this->uploadLogo($request->file('company_logo'), $company, $company); $this->uploadLogo($request->file('company_logo'), $company, $company);
auth()->user()->companies()->attach($company->id, [ $user->companies()->attach($company->id, [
'account_id' => $company->account->id, 'account_id' => $company->account->id,
'is_owner' => 1, 'is_owner' => 1,
'is_admin' => 1, 'is_admin' => 1,
@ -231,7 +241,7 @@ class CompanyController extends BaseController
/* /*
* Required dependencies * Required dependencies
*/ */
auth()->user()->setCompany($company); $user->setCompany($company);
/* /*
* Create token * Create token
@ -412,9 +422,6 @@ class CompanyController extends BaseController
$company = $this->company_repo->save($request->all(), $company); $company = $this->company_repo->save($request->all(), $company);
/** We save the settings in the repository - this is duplicated */
// $company->saveSettings($request->input('settings'), $company);
if ($request->has('documents')) { if ($request->has('documents')) {
$this->saveDocuments($request->input('documents'), $company, false); $this->saveDocuments($request->input('documents'), $company, false);
} }

View File

@ -11,12 +11,13 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Http\Requests\CompanyUser\UpdateCompanyUserRequest;
use App\Models\CompanyUser;
use App\Models\User; use App\Models\User;
use App\Models\CompanyUser;
use Illuminate\Http\Response;
use App\Transformers\CompanyUserTransformer; use App\Transformers\CompanyUserTransformer;
use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\Response; use App\Http\Requests\CompanyUser\UpdateCompanyUserRequest;
use App\Http\Requests\CompanyUser\UpdateCompanyUserPreferencesRequest;
class CompanyUserController extends BaseController class CompanyUserController extends BaseController
{ {
@ -131,6 +132,24 @@ class CompanyUserController extends BaseController
return $this->itemResponse($company_user->fresh()); return $this->itemResponse($company_user->fresh());
} }
public function updatePreferences(UpdateCompanyUserPreferencesRequest $request, User $user)
{
$company = auth()->user()->company();
$company_user = CompanyUser::whereUserId($user->id)->whereCompanyId($company->id)->first();
if (! $company_user) {
throw new ModelNotFoundException(ctrans('texts.company_user_not_found'));
return;
}
$company_user->react_settings = $request->react_settings;
$company_user->save();
return $this->itemResponse($company_user->fresh());
}
/** /**
* Remove the specified resource from storage. * Remove the specified resource from storage.
* *

View File

@ -818,10 +818,11 @@ class InvoiceController extends BaseController
return response()->json(['message' => 'no record found'], 400); return response()->json(['message' => 'no record found'], 400);
} }
$contact = $invitation->contact;
$invoice = $invitation->invoice; $invoice = $invitation->invoice;
$file = $invoice->service()->getInvoicePdf($contact); $file_name = $invoice->numberFormatter().'.pdf';
$file = (new \App\Jobs\Entity\CreateRawPdf($invitation, $invitation->company->db))->handle();
$headers = ['Content-Type' => 'application/pdf']; $headers = ['Content-Type' => 'application/pdf'];
@ -830,8 +831,8 @@ class InvoiceController extends BaseController
} }
return response()->streamDownload(function () use ($file) { return response()->streamDownload(function () use ($file) {
echo Storage::get($file); echo $file;
}, basename($file), $headers); }, $file_name, $headers);
} }
/** /**

View File

@ -565,10 +565,11 @@ class RecurringInvoiceController extends BaseController
return response()->json(['message' => 'no record found'], 400); return response()->json(['message' => 'no record found'], 400);
} }
$contact = $invitation->contact;
$invoice = $invitation->recurring_invoice; $invoice = $invitation->recurring_invoice;
$file = $invoice->service()->getInvoicePdf($contact); $file_name = $invoice->numberFormatter().'.pdf';
$file = (new \App\Jobs\Entity\CreateRawPdf($invitation, $invitation->company->db))->handle();
$headers = ['Content-Type' => 'application/pdf']; $headers = ['Content-Type' => 'application/pdf'];
@ -577,8 +578,9 @@ class RecurringInvoiceController extends BaseController
} }
return response()->streamDownload(function () use ($file) { return response()->streamDownload(function () use ($file) {
echo Storage::get($file); echo $file;
}, basename($file), $headers); }, $file_name, $headers);
} }
} }

View File

@ -61,7 +61,6 @@ class SelfUpdateController extends BaseController
nlog('copying release file'); nlog('copying release file');
// if (copy($this->getDownloadUrl(), storage_path('app/invoiceninja.zip'))) {
if (copy($this->getDownloadUrl(), storage_path("app/{$this->filename}"))) { if (copy($this->getDownloadUrl(), storage_path("app/{$this->filename}"))) {
nlog('Copied file from URL'); nlog('Copied file from URL');
} else { } else {
@ -70,21 +69,16 @@ class SelfUpdateController extends BaseController
nlog('Finished copying'); nlog('Finished copying');
// if($this->use_zip) { $file = Storage::disk('local')->path($this->filename);
$file = Storage::disk('local')->path($this->filename);
nlog('Extracting tar'); nlog('Extracting tar');
$phar = new \PharData($file); $phar = new \PharData($file);
$phar->extractTo(base_path(), null, true); $phar->extractTo(base_path(), null, true);
nlog('Finished extracting files'); nlog('Finished extracting files');
unlink($file); unlink($file);
// }
// else {
// $this->extractUsingZip();
// }
nlog('Deleted release zip file'); nlog('Deleted release zip file');
@ -110,31 +104,13 @@ class SelfUpdateController extends BaseController
return response()->json(['message' => 'Update completed'], 200); return response()->json(['message' => 'Update completed'], 200);
} }
// private function extractUsingZip()
// {
// $file = Storage::disk('local')->path($this->filename);
// nlog('Extracting zip');
// $zipFile = new \PhpZip\ZipFile();
// $zipFile->openFile($file);
// $zipFile->deleteFromName(".htaccess");
// $zipFile->rewrite();
// $zipFile->extractTo(base_path());
// $zipFile->close();
// $zipFile = null;
// unlink($file);
// }
private function clearCacheDir() private function clearCacheDir()
{ {
$directoryIterator = new \RecursiveDirectoryIterator(base_path('bootstrap/cache'), \RecursiveDirectoryIterator::SKIP_DOTS); $directoryIterator = new \RecursiveDirectoryIterator(base_path('bootstrap/cache'), \RecursiveDirectoryIterator::SKIP_DOTS);
foreach (new \RecursiveIteratorIterator($directoryIterator) as $file) { foreach (new \RecursiveIteratorIterator($directoryIterator) as $file) {
unlink(base_path('bootstrap/cache/').$file->getFileName()); unlink(base_path('bootstrap/cache/').$file->getFileName());
$file = null;
} }
$directoryIterator = null; $directoryIterator = null;
@ -155,6 +131,8 @@ class SelfUpdateController extends BaseController
throw new FilePermissionsFailure("Cannot update system because {$file->getFileName()} is not writable"); throw new FilePermissionsFailure("Cannot update system because {$file->getFileName()} is not writable");
} }
$file = null;
} }
$directoryIterator = null; $directoryIterator = null;
@ -169,10 +147,8 @@ class SelfUpdateController extends BaseController
private function getDownloadUrl() private function getDownloadUrl()
{ {
$version = $this->checkVersion();
// if(request()->has('zip')) $version = $this->checkVersion();
// return "https://github.com/invoiceninja/invoiceninja/releases/download/v{$version}/invoiceninja.zip";
return "https://github.com/invoiceninja/invoiceninja/releases/download/v{$version}/invoiceninja.tar"; return "https://github.com/invoiceninja/invoiceninja/releases/download/v{$version}/invoiceninja.tar";

View File

@ -58,7 +58,11 @@ class PasswordProtection
Cache::put(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in', Str::random(64), $timeout); Cache::put(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in', Str::random(64), $timeout);
return $next($request); return $next($request);
} elseif ($request->header('X-API-OAUTH-PASSWORD') && strlen($request->header('X-API-OAUTH-PASSWORD')) >=1) { }
elseif(strlen(auth()->user()->oauth_provider_id) > 2 && !auth()->user()->company()->oauth_password_required){
return $next($request);
}
elseif ($request->header('X-API-OAUTH-PASSWORD') && strlen($request->header('X-API-OAUTH-PASSWORD')) >=1) {
//user is attempting to reauth with OAuth - check the token value //user is attempting to reauth with OAuth - check the token value
//todo expand this to include all OAuth providers //todo expand this to include all OAuth providers
if (auth()->user()->oauth_provider_id == 'google') { if (auth()->user()->oauth_provider_id == 'google') {

View File

@ -82,7 +82,7 @@ class UpdateCompanyRequest extends Request
$input['settings'] = (array)$this->filterSaveableSettings($input['settings']); $input['settings'] = (array)$this->filterSaveableSettings($input['settings']);
} }
if(array_key_exists('subdomain', $input) && $this->subdomain == $input['subdomain']) { if(array_key_exists('subdomain', $input) && $this->company->subdomain == $input['subdomain']) {
unset($input['subdomain']); unset($input['subdomain']);
} }

View File

@ -0,0 +1,37 @@
<?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\CompanyUser;
use App\Http\Requests\Request;
use App\Utils\Traits\MakesHash;
class UpdateCompanyUserPreferencesRequest extends Request
{
use MakesHash;
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->id == $this->user->id;
}
public function rules()
{
return [
'react_settings' => 'required'
];
}
}

View File

@ -88,7 +88,7 @@ class StorePaymentRequest extends Request
// $input['is_manual'] = true; // $input['is_manual'] = true;
if (! isset($input['date'])) { if (! isset($input['date'])) {
$input['date'] = now()->format('Y-m-d'); $input['date'] = now()->addSeconds(auth()->user()->company()->timezone()->utc_offset)->format('Y-m-d');
} }
$this->replace($input); $this->replace($input);

View File

@ -59,6 +59,8 @@ class StorePurchaseOrderRequest extends Request
$rules['file'] = $this->file_validation; $rules['file'] = $this->file_validation;
} }
$rules['status_id'] = 'nullable|integer|in:1,2,3,4,5';
return $rules; return $rules;
} }

View File

@ -62,6 +62,8 @@ class UpdatePurchaseOrderRequest extends Request
$rules['file'] = $this->file_validation; $rules['file'] = $this->file_validation;
} }
$rules['status_id'] = 'sometimes|integer|in:1,2,3,4,5';
return $rules; return $rules;
} }

View File

@ -95,6 +95,14 @@ class StoreTaskRequest extends Request
} }
} }
if (isset($input['project_id']) && isset($input['client_id'])) {
$search_project_with_client = Project::withTrashed()->where('id', $input['project_id'])->where('client_id', $input['client_id'])->company()->doesntExist();
if ($search_project_with_client) {
unset($input['project_id']);
}
}
$this->replace($input); $this->replace($input);
} }
} }

View File

@ -104,6 +104,15 @@ class UpdateTaskRequest extends Request
$input['color'] = ''; $input['color'] = '';
} }
if(isset($input['project_id']) && isset($input['client_id'])){
$search_project_with_client = Project::withTrashed()->where('id', $input['project_id'])->where('client_id', $input['client_id'])->company()->doesntExist();
if($search_project_with_client){
unset($input['project_id']);
}
}
$this->replace($input); $this->replace($input);
} }

View File

@ -40,7 +40,7 @@ class StoreSchedulerRequest extends Request
'template' => 'bail|required|string', 'template' => 'bail|required|string',
'parameters' => 'bail|array', 'parameters' => 'bail|array',
'parameters.clients' => ['bail','sometimes', 'array', new ValidClientIds()], 'parameters.clients' => ['bail','sometimes', 'array', new ValidClientIds()],
'parameters.date_range' => 'bail|sometimes|string|in:last7_days,last30_days,last365_days,this_month,last_month,this_quarter,last_quarter,this_year,last_year,custom', 'parameters.date_range' => 'bail|sometimes|string|in:last7_days,last30_days,last365_days,this_month,last_month,this_quarter,last_quarter,this_year,last_year,all_time,custom',
'parameters.start_date' => ['bail', 'sometimes', 'date:Y-m-d', 'required_if:parameters.date_rate,custom'], 'parameters.start_date' => ['bail', 'sometimes', 'date:Y-m-d', 'required_if:parameters.date_rate,custom'],
'parameters.end_date' => ['bail', 'sometimes', 'date:Y-m-d', 'required_if:parameters.date_rate,custom', 'after_or_equal:parameters.start_date'], 'parameters.end_date' => ['bail', 'sometimes', 'date:Y-m-d', 'required_if:parameters.date_rate,custom', 'after_or_equal:parameters.start_date'],
'parameters.entity' => ['bail', 'sometimes', 'string', 'in:invoice,credit,quote,purchase_order'], 'parameters.entity' => ['bail', 'sometimes', 'string', 'in:invoice,credit,quote,purchase_order'],
@ -57,7 +57,7 @@ class StoreSchedulerRequest extends Request
$input = $this->all(); $input = $this->all();
if (array_key_exists('next_run', $input) && is_string($input['next_run'])) { if (array_key_exists('next_run', $input) && is_string($input['next_run'])) {
$this->merge(['next_run_client' => $input['next_run']]); $input['next_run_client'] = $input['next_run'];
} }
if($input['template'] == 'email_record'){ if($input['template'] == 'email_record'){

View File

@ -37,7 +37,7 @@ class UpdateSchedulerRequest extends Request
'template' => 'bail|required|string', 'template' => 'bail|required|string',
'parameters' => 'bail|array', 'parameters' => 'bail|array',
'parameters.clients' => ['bail','sometimes', 'array', new ValidClientIds()], 'parameters.clients' => ['bail','sometimes', 'array', new ValidClientIds()],
'parameters.date_range' => 'bail|sometimes|string|in:last7_days,last30_days,last365_days,this_month,last_month,this_quarter,last_quarter,this_year,last_year,custom', 'parameters.date_range' => 'bail|sometimes|string|in:last7_days,last30_days,last365_days,this_month,last_month,this_quarter,last_quarter,this_year,last_year,all_time,custom',
'parameters.start_date' => ['bail', 'sometimes', 'date:Y-m-d', 'required_if:parameters.date_rate,custom'], 'parameters.start_date' => ['bail', 'sometimes', 'date:Y-m-d', 'required_if:parameters.date_rate,custom'],
'parameters.end_date' => ['bail', 'sometimes', 'date:Y-m-d', 'required_if:parameters.date_rate,custom', 'after_or_equal:parameters.start_date'], 'parameters.end_date' => ['bail', 'sometimes', 'date:Y-m-d', 'required_if:parameters.date_rate,custom', 'after_or_equal:parameters.start_date'],
'parameters.entity' => ['bail', 'sometimes', 'string', 'in:invoice,credit,quote,purchase_order'], 'parameters.entity' => ['bail', 'sometimes', 'string', 'in:invoice,credit,quote,purchase_order'],
@ -54,7 +54,7 @@ class UpdateSchedulerRequest extends Request
$input = $this->all(); $input = $this->all();
if (array_key_exists('next_run', $input) && is_string($input['next_run'])) { if (array_key_exists('next_run', $input) && is_string($input['next_run'])) {
$this->merge(['next_run_client' => $input['next_run']]); $input['next_run_client'] = $input['next_run'];
} }
if($input['template'] == 'email_record') { if($input['template'] == 'email_record') {

View File

@ -20,12 +20,14 @@ class BankTransactionMap
1 => 'transaction.amount', 1 => 'transaction.amount',
2 => 'transaction.currency', 2 => 'transaction.currency',
3 => 'transaction.account_type', 3 => 'transaction.account_type',
4 => 'transaction.category_id', 4 => 'transaction.category',
5 => 'transaction.category_type', 5 => 'transaction.category_type',
6 => 'transaction.date', 6 => 'transaction.date',
7 => 'transaction.bank_account_id', 7 => 'transaction.bank_account',
8 => 'transaction.description', 8 => 'transaction.description',
9 => 'transaction.base_type', 9 => 'transaction.base_type',
10 => 'transaction.payment_type_Credit',
11 => 'transaction.payment_type_Debit',
]; ];
} }
@ -42,6 +44,8 @@ class BankTransactionMap
7 => 'texts.bank_account_id', 7 => 'texts.bank_account_id',
8 => 'texts.description', 8 => 'texts.description',
9 => 'texts.type', 9 => 'texts.type',
10 => 'transaction.credit',
11 => 'transaction.debit',
]; ];
} }
} }

View File

@ -30,7 +30,7 @@ class BankTransformer extends BaseTransformer
$transformed = [ $transformed = [
'bank_integration_id' => $transaction['transaction.bank_integration_id'], 'bank_integration_id' => $transaction['transaction.bank_integration_id'],
'transaction_id' => $this->getNumber($transaction, 'transaction.transaction_id'), 'transaction_id' => $this->getNumber($transaction, 'transaction.transaction_id'),
'amount' => abs($this->getFloat($transaction, 'transaction.amount')), 'amount' => $this->calculateAmount($transaction),
'currency_id' => $this->getCurrencyByCode($transaction, 'transaction.currency'), 'currency_id' => $this->getCurrencyByCode($transaction, 'transaction.currency'),
'account_type' => strlen($this->getString($transaction, 'transaction.account_type')) > 1 ? $this->getString($transaction, 'transaction.account_type') : 'bank', 'account_type' => strlen($this->getString($transaction, 'transaction.account_type')) > 1 ? $this->getString($transaction, 'transaction.account_type') : 'bank',
'category_id' => $this->getNumber($transaction, 'transaction.category_id') > 0 ? $this->getNumber($transaction, 'transaction.category_id') : null, 'category_id' => $this->getNumber($transaction, 'transaction.category_id') > 0 ? $this->getNumber($transaction, 'transaction.category_id') : null,
@ -49,13 +49,35 @@ class BankTransformer extends BaseTransformer
return $transformed; return $transformed;
} }
private function calculateAmount(array $transaction):float
{
if (array_key_exists('transaction.amount', $transaction) && is_numeric($transaction['transaction.amount'])) {
return abs($this->getFloat($transaction, 'transaction.amount'));
}
if (array_key_exists('transaction.payment_type_Credit', $transaction) && is_numeric($transaction['transaction.payment_type_Credit'])) {
return abs($this->getFloat($transaction, 'transaction.payment_type_Credit'));
}
if (array_key_exists('transaction.payment_type_Debit', $transaction) && is_numeric($transaction['transaction.payment_type_Debit'])) {
return abs($this->getFloat($transaction, 'transaction.payment_type_Debit'));
}
return 0;
}
private function calculateType($transaction) private function calculateType($transaction)
{ {
if (array_key_exists('transaction.base_type', $transaction) && (($transaction['transaction.base_type'] == 'CREDIT') || strtolower($transaction['transaction.base_type']) == 'deposit')) {
if (array_key_exists('transaction.payment_type_Credit', $transaction) && is_numeric($transaction['transaction.payment_type_Credit'])) {
return 'CREDIT'; return 'CREDIT';
} }
if (array_key_exists('transaction.transaction.payment_type_Debit', $transaction) && is_numeric($transaction['transaction.payment_type_Debit'])) {
return 'DEBIT';
}
if (array_key_exists('transaction.base_type', $transaction) && (($transaction['transaction.base_type'] == 'DEBIT') || strtolower($transaction['transaction.base_type']) == 'withdrawal')) { if (array_key_exists('transaction.base_type', $transaction) && (($transaction['transaction.base_type'] == 'DEBIT') || strtolower($transaction['transaction.base_type']) == 'withdrawal')) {
return 'DEBIT'; return 'DEBIT';
} }

View File

@ -246,6 +246,15 @@ class BaseTransformer
->exists(); ->exists();
} }
public function hasClientIdNumber($id_number)
{
return Client::where('company_id', $this->company->id)
->where('is_deleted', false)
->where('id_number', trim($id_number))
->exists();
}
/** /**
* @param $name * @param $name
* *

View File

@ -27,7 +27,12 @@ class ClientTransformer extends BaseTransformer
*/ */
public function transform($data) public function transform($data)
{ {
if (isset($data['Company Name']) && $this->hasClient($data['Company Name'])) { $client_id_proxy = array_key_exists('Customer ID', $data) ? 'Customer ID' : 'Primary Contact ID';
if(isset($data[$client_id_proxy]) && $this->hasClientIdNumber($data[$client_id_proxy])) {
throw new ImportException('Client ID already exists => '. $data[$client_id_proxy]);
}
elseif (isset($data['Company Name']) && $this->hasClient($data['Company Name'])) {
throw new ImportException('Client already exists => '. $data['Company Name']); throw new ImportException('Client already exists => '. $data['Company Name']);
} }
@ -38,8 +43,6 @@ class ClientTransformer extends BaseTransformer
$settings->payment_terms = $data['Payment Terms']; $settings->payment_terms = $data['Payment Terms'];
} }
$client_id_proxy = array_key_exists('Customer ID', $data) ? 'Customer ID' : 'Primary Contact ID';
$data = [ $data = [
'company_id' => $this->company->id, 'company_id' => $this->company->id,
'name' => $this->getString($data, 'Display Name'), 'name' => $this->getString($data, 'Display Name'),

View File

@ -123,7 +123,6 @@ class InvoiceTransformer extends BaseTransformer
return $client_id_search->first()->id; return $client_id_search->first()->id;
} }
$client_repository = app()->make(\App\Repositories\ClientRepository::class); $client_repository = app()->make(\App\Repositories\ClientRepository::class);
$client_repository->import_mode = true; $client_repository->import_mode = true;

View File

@ -19,7 +19,7 @@ use App\Models\Company;
use App\Services\Bank\BankMatchingService; use App\Services\Bank\BankMatchingService;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;

View File

@ -73,12 +73,11 @@ class UpdateTaxData implements ShouldQueue
nlog("problem getting tax data => ".$e->getMessage()); nlog("problem getting tax data => ".$e->getMessage());
} }
/** Set static tax information */ /*
if(!$tax_provider->updatedTaxStatus() && $this->client->country_id == 840){ if(!$tax_provider->updatedTaxStatus() && $this->client->country_id == 840){
$calculated_state = false; $calculated_state = false;
/** State must be calculated else default to the company state for taxes */
if(array_key_exists($this->client->shipping_state, USStates::get())) { if(array_key_exists($this->client->shipping_state, USStates::get())) {
$calculated_state = $this->client->shipping_state; $calculated_state = $this->client->shipping_state;
$calculated_postal_code = $this->client->shipping_postal_code; $calculated_postal_code = $this->client->shipping_postal_code;
@ -136,7 +135,7 @@ class UpdateTaxData implements ShouldQueue
$this->client->saveQuietly(); $this->client->saveQuietly();
} }
*/
} }
public function middleware() public function middleware()
@ -147,6 +146,8 @@ class UpdateTaxData implements ShouldQueue
public function failed($exception) public function failed($exception)
{ {
nlog("UpdateTaxData failed => ".$exception->getMessage()); nlog("UpdateTaxData failed => ".$exception->getMessage());
config(['queue.failed.driver' => null]);
} }
} }

View File

@ -67,6 +67,9 @@ class AutoBill implements ShouldQueue
if ($invitation->contact && ! $invitation->contact->trashed() && strlen($invitation->contact->email) >= 1 && $invoice->client->getSetting('auto_email_invoice')) { if ($invitation->contact && ! $invitation->contact->trashed() && strlen($invitation->contact->email) >= 1 && $invoice->client->getSetting('auto_email_invoice')) {
try { try {
EmailEntity::dispatch($invitation, $invoice->company)->delay(rand(1, 2)); EmailEntity::dispatch($invitation, $invoice->company)->delay(rand(1, 2));
$invoice->entityEmailEvent($invitation, 'invoice', 'email_template_invoice');
} catch (\Exception $e) { } catch (\Exception $e) {
nlog($e->getMessage()); nlog($e->getMessage());
} }

View File

@ -65,7 +65,7 @@ class EmailPayment implements ShouldQueue
public function handle() public function handle()
{ {
if ($this->company->is_disabled) { if ($this->company->is_disabled) {
return true; return;
} }
if ($this->contact->email) { if ($this->contact->email) {
@ -96,7 +96,7 @@ class EmailPayment implements ShouldQueue
(new NinjaMailerJob($nmo))->handle(); (new NinjaMailerJob($nmo))->handle();
event(new PaymentWasEmailed($this->payment, $this->payment->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); event(new PaymentWasEmailed($this->payment, $this->payment->company, $this->contact, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
} }
} }
} }

View File

@ -101,7 +101,7 @@ class EmailRefundPayment implements ShouldQueue
(new NinjaMailerJob($nmo))->handle(); (new NinjaMailerJob($nmo))->handle();
event(new PaymentWasEmailed($this->payment, $this->payment->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); event(new PaymentWasEmailed($this->payment, $this->payment->company, $this->contact, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
} }
} }
} }

View File

@ -122,7 +122,7 @@ class WebhookSingle implements ShouldQueue
$client = new Client(['headers' => array_merge($base_headers, $headers)]); $client = new Client(['headers' => array_merge($base_headers, $headers)]);
try { try {
$response = $client->post($subscription->target_url, [ $response = $client->{$subscription->rest_method}($subscription->target_url, [
RequestOptions::JSON => $data, // or 'json' => [...] RequestOptions::JSON => $data, // or 'json' => [...]
]); ]);

View File

@ -58,7 +58,6 @@ class InvoiceEmailFailedActivity implements ShouldQueue
$fields->client_contact_id = $event->invitation->client_contact_id; $fields->client_contact_id = $event->invitation->client_contact_id;
$fields->company_id = $event->invitation->invoice->company_id; $fields->company_id = $event->invitation->invoice->company_id;
$fields->activity_type_id = Activity::EMAIL_INVOICE_FAILED; $fields->activity_type_id = Activity::EMAIL_INVOICE_FAILED;
$fields->notes = $event->message;
$this->activity_repo->save($fields, $event->invitation->invoice, $event->event_vars); $this->activity_repo->save($fields, $event->invitation->invoice, $event->event_vars);
} }

View File

@ -45,6 +45,7 @@ class PaymentEmailedActivity implements ShouldQueue
$fields->user_id = $user_id; $fields->user_id = $user_id;
$fields->client_id = $event->payment->client_id; $fields->client_id = $event->payment->client_id;
$fields->client_contact_id = $event->contact->id;
$fields->company_id = $event->payment->company_id; $fields->company_id = $event->payment->company_id;
$fields->activity_type_id = Activity::PAYMENT_EMAILED; $fields->activity_type_id = Activity::PAYMENT_EMAILED;
$fields->payment_id = $event->payment->id; $fields->payment_id = $event->payment->id;

View File

@ -46,7 +46,6 @@ class ArchivedUserActivity implements ShouldQueue
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->creating_user->id; $user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->creating_user->id;
$fields->user_id = $user_id; $fields->user_id = $user_id;
$fields->notes = $event->creating_user->present()->name.' Archived User '.$event->user->present()->name();
$fields->company_id = $event->company->id; $fields->company_id = $event->company->id;
$fields->activity_type_id = Activity::ARCHIVE_USER; $fields->activity_type_id = Activity::ARCHIVE_USER;

View File

@ -46,7 +46,6 @@ class CreatedUserActivity implements ShouldQueue
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->creating_user->id; $user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->creating_user->id;
$fields->user_id = $user_id; $fields->user_id = $user_id;
$fields->notes = $event->creating_user->present()->name().' Created the user '.$event->user->present()->name();
$fields->company_id = $event->company->id; $fields->company_id = $event->company->id;
$fields->activity_type_id = Activity::CREATE_USER; $fields->activity_type_id = Activity::CREATE_USER;

View File

@ -51,8 +51,6 @@ class DeletedUserActivity implements ShouldQueue
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->creating_user->id; $user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->creating_user->id;
$fields->user_id = $user_id; $fields->user_id = $user_id;
$fields->notes = $event->creating_user->present()->name().' Deleted the user '.$event->user->present()->name();
$fields->company_id = $event->company->id; $fields->company_id = $event->company->id;
$fields->activity_type_id = Activity::DELETE_USER; $fields->activity_type_id = Activity::DELETE_USER;

View File

@ -45,7 +45,6 @@ class RestoredUserActivity implements ShouldQueue
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->creating_user->id; $user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->creating_user->id;
$fields->user_id = $user_id; $fields->user_id = $user_id;
$fields->notes = $event->creating_user->present()->name().' Restored user '.$event->user->present()->name();
$fields->company_id = $event->company->id; $fields->company_id = $event->company->id;
$fields->activity_type_id = Activity::RESTORE_USER; $fields->activity_type_id = Activity::RESTORE_USER;

View File

@ -45,7 +45,6 @@ class UpdatedUserActivity implements ShouldQueue
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->creating_user->id; $user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->creating_user->id;
$fields->user_id = $user_id; $fields->user_id = $user_id;
$fields->notes = $event->creating_user->present()->name().' Updated user '.$event->user->present()->name();
$fields->company_id = $event->company->id; $fields->company_id = $event->company->id;
$fields->activity_type_id = Activity::UPDATE_USER; $fields->activity_type_id = Activity::UPDATE_USER;

View File

@ -77,7 +77,6 @@ class EntityFailedSendObject
private function setTemplate() private function setTemplate()
{ {
// nlog($this->template);
switch ($this->template) { switch ($this->template) {
case 'invoice': case 'invoice':

View File

@ -104,7 +104,6 @@ class EntitySentObject
private function setTemplate() private function setTemplate()
{ {
// nlog($this->template);
switch ($this->template) { switch ($this->template) {
case 'invoice': case 'invoice':

View File

@ -356,6 +356,11 @@ class PaymentEmailEngine extends BaseEmailEngine
} }
if(strlen($invoice_list) < 4){
$invoice_list = Number::formatMoney($this->payment->amount, $this->client) ?: '&nbsp;';
}
return $invoice_list; return $invoice_list;
} }

View File

@ -72,7 +72,7 @@ class ImportCompleted extends Mailable
'client_gateway_token_count' => $this->company->client_gateway_tokens()->count(), 'client_gateway_token_count' => $this->company->client_gateway_tokens()->count(),
'tax_rate_count' => $this->company->tax_rates()->count(), 'tax_rate_count' => $this->company->tax_rates()->count(),
'document_count' => $this->company->documents()->count(), 'document_count' => $this->company->documents()->count(),
'url' => Ninja::isHosted() ? config('ninja.react_url') : config('ninja.app_url'),
]); ]);
return $this return $this

View File

@ -527,11 +527,11 @@ class Account extends BaseModel
public function emailsSent() public function emailsSent()
{ {
if (is_null(Cache::get($this->key))) { if (is_null(Cache::get("email_quota".$this->key))) {
return 0; return 0;
} }
return Cache::get($this->key); return Cache::get("email_quota".$this->key);
} }
public function emailQuotaExceeded() :bool public function emailQuotaExceeded() :bool

View File

@ -11,6 +11,7 @@
namespace App\Models; namespace App\Models;
use App\Utils\Number;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
/** /**
@ -346,7 +347,7 @@ class Activity extends StaticModel
*/ */
public function contact() public function contact()
{ {
return $this->belongsTo(ClientContact::class)->withTrashed(); return $this->belongsTo(ClientContact::class, 'client_contact_id', 'id')->withTrashed();
} }
/** /**
@ -460,4 +461,90 @@ class Activity extends StaticModel
{ {
return $this->belongsTo(Company::class); return $this->belongsTo(Company::class);
} }
public function activity_string()
{
$intersect = [
':invoice',
':client',
':contact',
':user',
':vendor',
':quote',
':credit',
':payment',
':task',
':expense',
':purchase_order',
':subscription',
':recurring_invoice',
':recurring_expense',
':amount',
':balance',
':number',
':payment_amount',
':gateway',
':adjustment'
];
$found_variables = array_intersect(explode(" ",trans("texts.activity_{$this->activity_type_id}")), $intersect);
$replacements = [];
foreach($found_variables as $var)
$replacements = array_merge($replacements, $this->matchVar($var));
if($this->client)
$replacements['client'] = ['label' => $this?->client?->present()->name() ?? '', 'hashed_id' => $this->client->hashed_id ?? ''];
if($this->vendor)
$replacements['vendor'] = ['label' => $this?->vendor?->present()->name() ?? '', 'hashed_id' => $this->vendor->hashed_id ?? ''];
$replacements['activity_type_id'] = $this->activity_type_id;
$replacements['id'] = $this->id;
$replacements['hashed_id'] = $this->hashed_id;
$replacements['notes'] = $this->notes ?? '';
$replacements['created_at'] = $this->created_at ?? '';
$replacements['ip'] = $this->ip ?? '';
return $replacements;
}
private function matchVar(string $variable)
{
$system = ctrans('texts.system');
return match($variable) {
':invoice' => $translation = [substr($variable, 1) => [ 'label' => $this?->invoice?->number ?? '', 'hashed_id' => $this->invoice?->hashed_id ?? '']],
':contact' => $translation = $this->resolveContact(),
':user' => $translation = [substr($variable, 1) => [ 'label' => $this?->user?->present()->name() ?? $system, 'hashed_id' => $this->user->hashed_id ?? '']],
':quote' => $translation = [substr($variable, 1) => [ 'label' => $this?->quote?->number ?? '', 'hashed_id' => $this->quote->hashed_id ?? '']],
':credit' => $translation = [substr($variable, 1) => [ 'label' => $this?->credit?->number ?? '', 'hashed_id' => $this->credit->hashed_id ?? '']],
':payment' => $translation = [substr($variable, 1) => [ 'label' => $this?->payment?->number ?? '', 'hashed_id' => $this->payment->hashed_id ?? '']],
':task' => $translation = [substr($variable, 1) => [ 'label' => $this?->task?->number ?? '', 'hashed_id' => $this->task->hashed_id ?? '']],
':expense' => $translation = [substr($variable, 1) => [ 'label' => $this?->expense?->number ?? '', 'hashed_id' => $this->expense->hashed_id ?? '']],
':purchase_order' => $translation = [substr($variable, 1) => [ 'label' => $this?->purchase_order?->number ?? '', 'hashed_id' => $this->purchase_order->hashed_id ?? '']],
':subscription' => $translation = [substr($variable, 1) => [ 'label' => $this?->subscription?->number ?? '', 'hashed_id' => $this->subscription->hashed_id ?? '' ]],
':recurring_invoice' => $translation = [substr($variable, 1) =>[ 'label' => $this?->recurring_invoice?->number ??'', 'hashed_id' => $this->recurring_invoice->hashed_id ?? '']],
':recurring_expense' => $translation = [substr($variable, 1) => [ 'label' => $this?->recurring_expense?->number ??'', 'hashed_id' => $this->recurring_expense->hashed_id ?? '']],
':payment_amount' => $translation = [substr($variable, 1) =>[ 'label' => Number::formatMoney($this?->payment?->amount, $this?->payment?->client) ?? '', 'hashed_id' => '']],
':adjustment' => $translation = [substr($variable, 1) =>[ 'label' => Number::formatMoney($this?->payment?->refunded, $this?->payment?->client) ?? '', 'hashed_id' => '']],
':ip' => $translation = [ 'ip' => $this->ip ?? ''],
default => $translation = [],
};
return $translation;
}
private function resolveContact() : array
{
$contact = $this->contact ? $this->contact : $this->vendor_contact;
$entity = $this->contact ? $this->client : $this->vendor;
$contact_entity = $this->contact ? 'clients' : 'vendors';
return ['contact' => [ 'label' => $contact?->present()->name() ?? '', 'hashed_id' => $entity->hashed_id ?? '', 'contact_entity' => $contact_entity]];
}
} }

View File

@ -45,6 +45,9 @@ use Illuminate\Support\Str;
* @method static \Illuminate\Database\Eloquent\Builder|BaseModel count() * @method static \Illuminate\Database\Eloquent\Builder|BaseModel count()
* @method static \Illuminate\Database\Eloquent\Builder|BaseModel create() * @method static \Illuminate\Database\Eloquent\Builder|BaseModel create()
* @method static \Illuminate\Database\Eloquent\Builder|BaseModel insert() * @method static \Illuminate\Database\Eloquent\Builder|BaseModel insert()
* @method static \Illuminate\Database\Eloquent\Builder|BaseModel whereHas()
* @method static \Illuminate\Database\Eloquent\Builder|BaseModel withTrashed()
*
* @method \App\Models\Company company() * @method \App\Models\Company company()
* @method int companyId() * @method int companyId()
* @method Builder|static exclude($columns) * @method Builder|static exclude($columns)

View File

@ -46,8 +46,8 @@ use Laracasts\Presenter\PresentableTrait;
* @property string|null $email * @property string|null $email
* @property string|null $email_verified_at * @property string|null $email_verified_at
* @property string|null $confirmation_code * @property string|null $confirmation_code
* @property int $is_primary * @property bool $is_primary
* @property int $confirmed * @property bool $confirmed
* @property int|null $last_login * @property int|null $last_login
* @property int|null $failed_logins * @property int|null $failed_logins
* @property string|null $oauth_user_id * @property string|null $oauth_user_id
@ -59,8 +59,8 @@ use Laracasts\Presenter\PresentableTrait;
* @property string|null $avatar_size * @property string|null $avatar_size
* @property string $password * @property string $password
* @property string|null $token * @property string|null $token
* @property int $is_locked * @property bool $is_locked
* @property int $send_email * @property bool $send_email
* @property string|null $contact_key * @property string|null $contact_key
* @property string|null $remember_token * @property string|null $remember_token
* @property int|null $created_at * @property int|null $created_at

View File

@ -76,6 +76,7 @@ class PaymentType extends StaticModel
const BACS = 49; const BACS = 49;
const STRIPE_BANK_TRANSFER = 50; const STRIPE_BANK_TRANSFER = 50;
const CASH_APP = 51; const CASH_APP = 51;
const VENMO = 24;
public array $type_names = [ public array $type_names = [
self::CREDIT => 'payment_type_Credit', self::CREDIT => 'payment_type_Credit',
@ -119,6 +120,7 @@ class PaymentType extends StaticModel
self::Interac_E_Transfer => 'payment_type_Interac E Transfer', self::Interac_E_Transfer => 'payment_type_Interac E Transfer',
self::STRIPE_BANK_TRANSFER => 'bank_transfer', self::STRIPE_BANK_TRANSFER => 'bank_transfer',
self::CASH_APP => 'payment_type_Cash App', self::CASH_APP => 'payment_type_Cash App',
self::VENMO => 'payment_type_Venmo',
]; ];
public static function parseCardType($cardName) public static function parseCardType($cardName)

View File

@ -103,9 +103,9 @@ use Laracasts\Presenter\PresentableTrait;
* @property-read mixed $valid_until * @property-read mixed $valid_until
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Backup> $history * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Backup> $history
* @property-read int|null $history_count * @property-read int|null $history_count
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\QuoteInvitation> $invitations
* @property-read int|null $invitations_count * @property-read int|null $invitations_count
* @property-read \App\Models\Invoice|null $invoice * @property-read \App\Models\Invoice|null $invoice
* @property-read \App\Models\QuoteInvitation|null $invitations
* @property-read \App\Models\Project|null $project * @property-read \App\Models\Project|null $project
* @property-read \App\Models\User $user * @property-read \App\Models\User $user
* @property-read \App\Models\Vendor|null $vendor * @property-read \App\Models\Vendor|null $vendor
@ -185,50 +185,6 @@ use Laracasts\Presenter\PresentableTrait;
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Backup> $history * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Backup> $history
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\QuoteInvitation> $invitations * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\QuoteInvitation> $invitations
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Backup> $history
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\QuoteInvitation> $invitations
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Backup> $history
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\QuoteInvitation> $invitations
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Backup> $history
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\QuoteInvitation> $invitations
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Backup> $history
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\QuoteInvitation> $invitations
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Backup> $history
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\QuoteInvitation> $invitations
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Backup> $history
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\QuoteInvitation> $invitations
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Backup> $history
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\QuoteInvitation> $invitations
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Backup> $history
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\QuoteInvitation> $invitations
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Backup> $history
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\QuoteInvitation> $invitations
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Backup> $history
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\QuoteInvitation> $invitations
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Backup> $history
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\QuoteInvitation> $invitations
* @mixin \Eloquent * @mixin \Eloquent
*/ */
class Quote extends BaseModel class Quote extends BaseModel

View File

@ -202,5 +202,16 @@ class Scheduler extends BaseModel
$this->save(); $this->save();
} }
public function adjustOffset(): void
{
if (! $this->next_run) {
return;
}
$offset = $this->company->timezone_offset();
$this->next_run = $this->next_run->copy()->addSeconds($offset);
$this->save();
}
} }

View File

@ -26,6 +26,8 @@ use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundExceptio
* @method static \Illuminate\Database\Eloquent\Builder|StaticModel newQuery() * @method static \Illuminate\Database\Eloquent\Builder|StaticModel newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|StaticModel query() * @method static \Illuminate\Database\Eloquent\Builder|StaticModel query()
* @method static \Illuminate\Database\Eloquent\Builder|StaticModel find() * @method static \Illuminate\Database\Eloquent\Builder|StaticModel find()
* @method static \Illuminate\Database\Eloquent\Builder|StaticModel with()
* @method static \Illuminate\Database\Eloquent\Builder|StaticModel withTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|StaticModel findOrFail() * @method static \Illuminate\Database\Eloquent\Builder|StaticModel findOrFail()
* @mixin \Eloquent * @mixin \Eloquent
*/ */

View File

@ -320,7 +320,7 @@ class BaseDriver extends AbstractPaymentDriver
$payment->company_gateway_id = $this->company_gateway->id; $payment->company_gateway_id = $this->company_gateway->id;
$payment->status_id = $status; $payment->status_id = $status;
$payment->currency_id = $this->client->getSetting('currency_id'); $payment->currency_id = $this->client->getSetting('currency_id');
$payment->date = Carbon::now(); $payment->date = Carbon::now()->addSeconds($this->client->company->timezone()->utc_offset)->format('Y-m-d');
$payment->gateway_type_id = $data['gateway_type_id']; $payment->gateway_type_id = $data['gateway_type_id'];
$client_contact = $this->getContact(); $client_contact = $this->getContact();

View File

@ -12,6 +12,7 @@
namespace App\Repositories; namespace App\Repositories;
use App\Models\Company; use App\Models\Company;
use App\Utils\Ninja;
/** /**
* CompanyRepository. * CompanyRepository.
@ -31,12 +32,18 @@ class CompanyRepository extends BaseRepository
*/ */
public function save(array $data, Company $company) : ?Company public function save(array $data, Company $company) : ?Company
{ {
if (isset($data['custom_fields']) && is_array($data['custom_fields'])) { if (isset($data['custom_fields']) && is_array($data['custom_fields'])) {
$data['custom_fields'] = $this->parseCustomFields($data['custom_fields']); $data['custom_fields'] = $this->parseCustomFields($data['custom_fields']);
} }
$company->fill($data); $company->fill($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);
}
if (array_key_exists('settings', $data)) { if (array_key_exists('settings', $data)) {
$company->saveSettings($data['settings'], $company); $company->saveSettings($data['settings'], $company);
} }
@ -46,6 +53,12 @@ class CompanyRepository extends BaseRepository
return $company; return $company;
} }
/**
* parseCustomFields
*
* @param array $fields
* @return array
*/
private function parseCustomFields($fields) :array private function parseCustomFields($fields) :array
{ {
foreach ($fields as &$value) { foreach ($fields as &$value) {

View File

@ -122,7 +122,7 @@ class PaymentMigrationRepository extends BaseRepository
$invoices = Invoice::whereIn('id', array_column($data['invoices'], 'invoice_id'))->withTrashed()->get(); $invoices = Invoice::whereIn('id', array_column($data['invoices'], 'invoice_id'))->withTrashed()->get();
$payment->invoices()->saveMany($invoices); $payment->invoices()->saveMany($invoices); // 1:1 relationship so this is ok
$payment->invoices->each(function ($inv) use ($invoice_totals, $refund_totals, $payment) { $payment->invoices->each(function ($inv) use ($invoice_totals, $refund_totals, $payment) {
if ($payment->status_id != Payment::STATUS_CANCELLED || ! $payment->is_deleted) { if ($payment->status_id != Payment::STATUS_CANCELLED || ! $payment->is_deleted) {

View File

@ -11,19 +11,20 @@
namespace App\Repositories; namespace App\Repositories;
use App\Events\Payment\PaymentWasCreated; use App\Utils\Ninja;
use App\Events\Payment\PaymentWasDeleted;
use App\Jobs\Credit\ApplyCreditPayment;
use App\Libraries\Currency\Conversion\CurrencyApi;
use App\Models\Client; use App\Models\Client;
use App\Models\Credit; use App\Models\Credit;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\Payment; use App\Models\Payment;
use App\Utils\Ninja; use App\Models\Paymentable;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\SavesDocuments;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\SavesDocuments;
use App\Jobs\Credit\ApplyCreditPayment;
use App\Events\Payment\PaymentWasCreated;
use App\Events\Payment\PaymentWasDeleted;
use App\Libraries\Currency\Conversion\CurrencyApi;
/** /**
* PaymentRepository. * PaymentRepository.
@ -138,7 +139,7 @@ class PaymentRepository extends BaseRepository
$invoices = Invoice::withTrashed()->whereIn('id', array_column($data['invoices'], 'invoice_id'))->get(); $invoices = Invoice::withTrashed()->whereIn('id', array_column($data['invoices'], 'invoice_id'))->get();
$payment->invoices()->saveMany($invoices); // $payment->invoices()->saveMany($invoices); //25-06-2023
//todo optimize this into a single query //todo optimize this into a single query
foreach ($data['invoices'] as $paid_invoice) { foreach ($data['invoices'] as $paid_invoice) {
@ -146,6 +147,16 @@ class PaymentRepository extends BaseRepository
$invoice = $invoices->firstWhere('id', $paid_invoice['invoice_id']); $invoice = $invoices->firstWhere('id', $paid_invoice['invoice_id']);
if ($invoice) { if ($invoice) {
//25-06-2023
$paymentable = new Paymentable();
$paymentable->payment_id = $payment->id;
$paymentable->paymentable_id = $invoice->id;
$paymentable->paymentable_type = 'invoices';
$paymentable->amount = $paid_invoice['amount'];
$paymentable->save();
$invoice = $invoice->service() $invoice = $invoice->service()
->markSent() ->markSent()
->applyPayment($payment, $paid_invoice['amount']) ->applyPayment($payment, $paid_invoice['amount'])
@ -153,26 +164,30 @@ class PaymentRepository extends BaseRepository
} }
} }
} else { } else {
//payment is made, but not to any invoice, therefore we are applying the payment to the clients paid_to_date only
//01-07-2020 i think we were duplicating the paid to date here.
//$payment->client->service()->updatePaidToDate($payment->amount)->save();
} }
if (array_key_exists('credits', $data) && is_array($data['credits'])) { if (array_key_exists('credits', $data) && is_array($data['credits'])) {
$credit_totals = array_sum(array_column($data['credits'], 'amount')); $credit_totals = array_sum(array_column($data['credits'], 'amount'));
// $credits = Credit::whereIn('id', $this->transformKeys(array_column($data['credits'], 'credit_id')))->get();
$credits = Credit::whereIn('id', array_column($data['credits'], 'credit_id'))->get(); $credits = Credit::whereIn('id', array_column($data['credits'], 'credit_id'))->get();
$payment->credits()->saveMany($credits); // $payment->credits()->saveMany($credits);
//todo optimize into a single query //todo optimize into a single query
foreach ($data['credits'] as $paid_credit) { foreach ($data['credits'] as $paid_credit) {
// $credit = Credit::withTrashed()->find($paid_credit['credit_id']);
$credit = $credits->firstWhere('id', $paid_credit['credit_id']); $credit = $credits->firstWhere('id', $paid_credit['credit_id']);
if ($credit) { if ($credit) {
$paymentable = new Paymentable();
$paymentable->payment_id = $payment->id;
$paymentable->paymentable_id = $credit->id;
$paymentable->paymentable_type = Credit::class;
$paymentable->amount = $paid_invoice['amount'];
$paymentable->save();
$credit = $credit->service()->markSent()->save(); $credit = $credit->service()->markSent()->save();
(new ApplyCreditPayment($credit, $payment, $paid_credit['amount'], $credit->company))->handle(); (new ApplyCreditPayment($credit, $payment, $paid_credit['amount'], $credit->company))->handle();
} }

View File

@ -30,8 +30,7 @@ class SchedulerRepository extends BaseRepository
$scheduler->save(); $scheduler->save();
/** 18-5-2023 set client specific send times. */ $scheduler->adjustOffset();
$scheduler->calculateNextRun();
return $scheduler->fresh(); return $scheduler->fresh();
} }

View File

@ -18,6 +18,7 @@ use App\Services\Email\Email;
use App\Services\Email\EmailObject; use App\Services\Email\EmailObject;
use App\Utils\Number; use App\Utils\Number;
use App\Utils\Traits\MakesDates; use App\Utils\Traits\MakesDates;
use Carbon\Carbon;
use Illuminate\Mail\Mailables\Address; use Illuminate\Mail\Mailables\Address;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
@ -149,6 +150,11 @@ class ClientService
$pdf = $statement->run(); $pdf = $statement->run();
if ($send_email) { if ($send_email) {
// If selected, ignore clients that don't have any invoices to put on the statement.
if (!empty($options['only_clients_with_invoices']) && $statement->getInvoices()->count() == 0) {
return false;
}
return $this->emailStatement($pdf, $statement->options); return $this->emailStatement($pdf, $statement->options);
} }

View File

@ -211,6 +211,9 @@ class Statement
$this->options['show_credits_table'] = false; $this->options['show_credits_table'] = false;
} }
if (!\array_key_exists('only_clients_with_invoices', $this->options)) {
$this->options['only_clients_with_invoices'] = false;
}
return $this; return $this;
} }
@ -220,7 +223,7 @@ class Statement
* *
* @return Invoice[]|\Illuminate\Support\LazyCollection * @return Invoice[]|\Illuminate\Support\LazyCollection
*/ */
protected function getInvoices(): \Illuminate\Support\LazyCollection public function getInvoices(): \Illuminate\Support\LazyCollection
{ {
return Invoice::withTrashed() return Invoice::withTrashed()
->with('payments.type') ->with('payments.type')

View File

@ -119,7 +119,7 @@ class CreditService
$payment->type_id = PaymentType::CREDIT; $payment->type_id = PaymentType::CREDIT;
$payment->is_manual = true; $payment->is_manual = true;
$payment->currency_id = $this->credit->client->getSetting('currency_id'); $payment->currency_id = $this->credit->client->getSetting('currency_id');
$payment->date = now(); $payment->date = now()->addSeconds($this->credit->company->timezone()->utc_offset)->format('Y-m-d');
$payment->saveQuietly(); $payment->saveQuietly();
$payment->number = $payment->client->getNextPaymentNumber($payment->client, $payment); $payment->number = $payment->client->getNextPaymentNumber($payment->client, $payment);
@ -243,7 +243,7 @@ class CreditService
public function triggeredActions($request) public function triggeredActions($request)
{ {
$this->invoice = (new TriggeredActions($this->credit, $request))->run(); $this->credit = (new TriggeredActions($this->credit, $request))->run();
return $this; return $this;
} }

View File

@ -61,6 +61,10 @@ class TriggeredActions extends AbstractService
$company->save(); $company->save();
} }
if ($this->request->has('mark_paid') && $this->request->input('mark_paid') == 'true') {
$this->credit->service()->markPaid()->save();
}
return $this->credit; return $this->credit;
} }

View File

@ -84,9 +84,9 @@ class ApplyPayment extends AbstractService
/* Update Pivot Record amount */ /* Update Pivot Record amount */
$this->payment->invoices->each(function ($inv) use ($amount_paid) { $this->payment->invoices->each(function ($inv) use ($amount_paid) {
if ($inv->id == $this->invoice->id) { if ($inv->id == $this->invoice->id) {
$inv->pivot->amount = ($amount_paid * -1); // $inv->pivot->amount = ($amount_paid * -1);
$inv->pivot->save(); // $inv->pivot->save();
//25-06-2023
$inv->paid_to_date += floatval($amount_paid * -1); $inv->paid_to_date += floatval($amount_paid * -1);
$inv->save(); $inv->save();
} }

View File

@ -179,7 +179,7 @@ class AutoBillInvoice extends AbstractService
$payment->applied = $amount; $payment->applied = $amount;
$payment->client_id = $this->invoice->client_id; $payment->client_id = $this->invoice->client_id;
$payment->currency_id = $this->invoice->client->getSetting('currency_id'); $payment->currency_id = $this->invoice->client->getSetting('currency_id');
$payment->date = now(); $payment->date = now()->addSeconds($this->invoice->company->timezone()->utc_offset)->format('Y-m-d');
$payment->status_id = Payment::STATUS_COMPLETED; $payment->status_id = Payment::STATUS_COMPLETED;
$payment->type_id = PaymentType::CREDIT; $payment->type_id = PaymentType::CREDIT;
$payment->service()->applyNumber()->save(); $payment->service()->applyNumber()->save();

View File

@ -33,14 +33,7 @@ class SendEmail
$this->contact = $this->payment->client->contacts()->first(); $this->contact = $this->payment->client->contacts()->first();
} }
// $this->payment->invoices->sortByDesc('id')->first(function ($invoice) {
// $invoice->invitations->each(function ($invitation) {
// if (!$invitation->contact->trashed() && $invitation->contact->email) {
EmailPayment::dispatch($this->payment, $this->payment->company, $this->contact); EmailPayment::dispatch($this->payment, $this->payment->company, $this->contact);
// event(new PaymentWasEmailed($this->payment, $this->payment->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
// }
// });
// });
} }
} }

View File

@ -276,9 +276,8 @@ class Design extends BaseDesign
$header = []; $header = [];
$header[] = ['element' => 'p', 'content' => ctrans('texts.shipping_address'), 'properties' => ['data-ref' => 'shipping_address-label', 'style' => 'font-weight: bold; text-transform: uppercase']]; $header[] = ['element' => 'p', 'content' => ctrans('texts.shipping_address'), 'properties' => ['data-ref' => 'shipping_address-label', 'style' => 'font-weight: bold; text-transform: uppercase']];
return array_merge($header, $elements);
// return $elements; return array_merge($header, $elements);
} }
@ -320,73 +319,6 @@ class Design extends BaseDesign
return $elements; return $elements;
} }
//@deprecated
// public function entityDetailsx(): array
// {
// if ($this->type === 'statement') {
// $s_date = $this->translateDate($this->options['start_date'], $this->client->date_format(), $this->client->locale()) . " - " . $this->translateDate($this->options['end_date'], $this->client->date_format(), $this->client->locale());
// return [
// ['element' => 'p', 'content' => "<h2>".ctrans('texts.statement')."</h2>", 'properties' => ['data-ref' => 'statement-label']],
// ['element' => 'p', 'content' => ctrans('texts.statement_date'), 'properties' => ['data-ref' => 'statement-label'],'elements' =>
// ['element' => 'span', 'content' => "{$s_date} "]
// ],
// ['element' => 'p', 'content' => '$balance_due_label', 'properties' => ['data-ref' => 'statement-label'],'elements' =>
// ['element' => 'span', 'content' => Number::formatMoney($this->invoices->sum('balance'), $this->client)]
// ],
// ];
// }
// $variables = $this->context['pdf_variables']['invoice_details'];
// if ($this->entity instanceof Quote) {
// $variables = $this->context['pdf_variables']['quote_details'];
// if ($this->entity->partial > 0) {
// $variables[] = '$quote.balance_due';
// }
// }
// if ($this->entity instanceof Credit) {
// $variables = $this->context['pdf_variables']['credit_details'];
// }
// if ($this->vendor) {
// $variables = $this->context['pdf_variables']['purchase_order_details'];
// }
// $elements = [];
// // We don't want to show account balance or invoice total on PDF.. or any amount with currency.
// if ($this->type == self::DELIVERY_NOTE) {
// $variables = array_filter($variables, function ($m) {
// return !in_array($m, ['$invoice.balance_due', '$invoice.total']);
// });
// }
// foreach ($variables as $variable) {
// $_variable = explode('.', $variable)[1];
// $_customs = ['custom1', 'custom2', 'custom3', 'custom4'];
// /* 2/7/2022 don't show custom values if they are empty */
// $var = str_replace("custom", "custom_value", $_variable);
// if (in_array($_variable, $_customs) && !empty($this->entity->{$var})) {
// $elements[] = ['element' => 'div', 'properties' => ['style' => "display: table-row; visibility: {$this->entityVariableCheck($_variable)};"],'elements' => [
// ['element' => 'div', 'content' => $variable . '_label', 'properties' => ['class' => 'entity-details-cell', 'data-ref' => 'entity_details-' . substr($variable, 1) . '_label']],
// ['element' => 'div', 'content' => $variable, 'properties' => ['class' => 'entity-details-cell', 'data-ref' => 'entity_details-' . substr($variable, 1)]],
// ]];
// } else {
// $elements[] = ['element' => 'div', 'properties' => ['style' => "display: table-row; visibility: {$this->entityVariableCheck($variable)};"], 'elements' => [
// ['element' => 'div', 'content' => $variable . '_label', 'properties' => ['class' => 'entity-details-cell','data-ref' => 'entity_details-' . substr($variable, 1) . '_label']],
// ['element' => 'div', 'content' => $variable, 'properties' => ['class' => 'entity-details-cell','data-ref' => 'entity_details-' . substr($variable, 1)]],
// ]];
// }
// }
// return $elements;
// }
public function entityDetails(): array public function entityDetails(): array
{ {
if ($this->type === 'statement') { if ($this->type === 'statement') {
@ -912,7 +844,6 @@ class Design extends BaseDesign
$variables = $this->context['pdf_variables']['total_columns']; $variables = $this->context['pdf_variables']['total_columns'];
$elements = [ $elements = [
['element' => 'div', 'properties' => ['style' => 'display: flex; flex-direction: column;'], 'elements' => [ ['element' => 'div', 'properties' => ['style' => 'display: flex; flex-direction: column;'], 'elements' => [
['element' => 'p', 'content' => strtr(str_replace(["labels","values"], ["",""], $_variables['values']['$entity.public_notes']), $_variables), 'properties' => ['data-ref' => 'total_table-public_notes', 'style' => 'text-align: left;']], ['element' => 'p', 'content' => strtr(str_replace(["labels","values"], ["",""], $_variables['values']['$entity.public_notes']), $_variables), 'properties' => ['data-ref' => 'total_table-public_notes', 'style' => 'text-align: left;']],

View File

@ -37,8 +37,6 @@ class SendEmail
*/ */
public function run() public function run()
{ {
nlog($this->reminder_template);
nlog("is there a template");
if (! $this->reminder_template) { if (! $this->reminder_template) {
$this->reminder_template = $this->quote->calculateTemplate('quote'); $this->reminder_template = $this->quote->calculateTemplate('quote');

View File

@ -20,6 +20,8 @@ class GetInvoicePdf extends AbstractService
{ {
public $entity; public $entity;
public $contact;
public function __construct($entity, ClientContact $contact = null) public function __construct($entity, ClientContact $contact = null)
{ {
$this->entity = $entity; $this->entity = $entity;

View File

@ -11,18 +11,19 @@
namespace App\Services\Report; namespace App\Services\Report;
use App\Libraries\Currency\Conversion\CurrencyApi;
use App\Libraries\MultiDB;
use App\Models\Company;
use App\Models\Currency;
use App\Models\Expense;
use App\Models\Payment;
use App\Utils\Ninja; use App\Utils\Ninja;
use App\Utils\Number; use App\Utils\Number;
use League\Csv\Writer;
use App\Models\Company;
use App\Models\Expense;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\Currency;
use App\Libraries\MultiDB;
use Illuminate\Support\Str;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\App;
use Illuminate\Support\Str; use App\Libraries\Currency\Conversion\CurrencyApi;
use League\Csv\Writer;
class ProfitLoss class ProfitLoss
{ {
@ -279,8 +280,6 @@ class ProfitLoss
->with(['company', 'client']) ->with(['company', 'client'])
->cursor() ->cursor()
->each(function ($payment) { ->each(function ($payment) {
$company = $payment->company;
$client = $payment->client;
$map = new \stdClass; $map = new \stdClass;
$amount_payment_paid = 0; $amount_payment_paid = 0;
@ -293,17 +292,20 @@ class ProfitLoss
$tax_amount_credit_converted = $tax_amount_credit_converted = 0; $tax_amount_credit_converted = $tax_amount_credit_converted = 0;
foreach ($payment->paymentables as $pivot) { foreach ($payment->paymentables as $pivot) {
if ($pivot->paymentable instanceof \App\Models\Invoice) { if ($pivot->paymentable_type == 'invoices') {
$invoice = $pivot->paymentable; $invoice = Invoice::withTrashed()->find($pivot->paymentable_id);
$amount_payment_paid += $pivot->amount - $pivot->refunded; $amount_payment_paid += $pivot->amount - $pivot->refunded;
$amount_payment_paid_converted += $amount_payment_paid / ($payment->exchange_rate ?: 1); $amount_payment_paid_converted += $amount_payment_paid / ($payment->exchange_rate ?: 1);
$tax_amount += ($amount_payment_paid / $invoice->amount) * $invoice->total_taxes; if ($invoice->amount > 0) {
$tax_amount_converted += (($amount_payment_paid / $invoice->amount) * $invoice->total_taxes) / $payment->exchange_rate; $tax_amount += ($amount_payment_paid / $invoice->amount) * $invoice->total_taxes;
$tax_amount_converted += (($amount_payment_paid / $invoice->amount) * $invoice->total_taxes) / $payment->exchange_rate;
}
} }
if ($pivot->paymentable instanceof \App\Models\Credit) { if ($pivot->paymentable_type == 'credits') {
$amount_credit_paid += $pivot->amount - $pivot->refunded; $amount_credit_paid += $pivot->amount - $pivot->refunded;
$amount_credit_paid_converted += $amount_payment_paid / ($payment->exchange_rate ?: 1); $amount_credit_paid_converted += $amount_payment_paid / ($payment->exchange_rate ?: 1);
@ -593,52 +595,50 @@ class ProfitLoss
case 'all': case 'all':
$this->start_date = now()->subYears(50); $this->start_date = now()->subYears(50);
$this->end_date = now(); $this->end_date = now();
// return $query; break;
// no break
case 'last7': case 'last7':
$this->start_date = now()->subDays(7); $this->start_date = now()->subDays(7);
$this->end_date = now(); $this->end_date = now();
// return $query->whereBetween($this->date_key, [now()->subDays(7), now()])->orderBy($this->date_key, 'ASC'); break;
// no break
case 'last30': case 'last30':
$this->start_date = now()->subDays(30); $this->start_date = now()->subDays(30);
$this->end_date = now(); $this->end_date = now();
// return $query->whereBetween($this->date_key, [now()->subDays(30), now()])->orderBy($this->date_key, 'ASC'); break;
// no break
case 'this_month': case 'this_month':
$this->start_date = now()->startOfMonth(); $this->start_date = now()->startOfMonth();
$this->end_date = now(); $this->end_date = now();
//return $query->whereBetween($this->date_key, [now()->startOfMonth(), now()])->orderBy($this->date_key, 'ASC'); break;
// no break
case 'last_month': case 'last_month':
$this->start_date = now()->startOfMonth()->subMonth(); $this->start_date = now()->startOfMonth()->subMonth();
$this->end_date = now()->startOfMonth()->subMonth()->endOfMonth(); $this->end_date = now()->startOfMonth()->subMonth()->endOfMonth();
//return $query->whereBetween($this->date_key, [now()->startOfMonth()->subMonth(), now()->startOfMonth()->subMonth()->endOfMonth()])->orderBy($this->date_key, 'ASC'); break;
// no break
case 'this_quarter': case 'this_quarter':
$this->start_date = (new \Carbon\Carbon('-3 months'))->firstOfQuarter(); $this->start_date = (new \Carbon\Carbon('-3 months'))->firstOfQuarter();
$this->end_date = (new \Carbon\Carbon('-3 months'))->lastOfQuarter(); $this->end_date = (new \Carbon\Carbon('-3 months'))->lastOfQuarter();
//return $query->whereBetween($this->date_key, [(new \Carbon\Carbon('-3 months'))->firstOfQuarter(), (new \Carbon\Carbon('-3 months'))->lastOfQuarter()])->orderBy($this->date_key, 'ASC'); break;
// no break
case 'last_quarter': case 'last_quarter':
$this->start_date = (new \Carbon\Carbon('-6 months'))->firstOfQuarter(); $this->start_date = (new \Carbon\Carbon('-6 months'))->firstOfQuarter();
$this->end_date = (new \Carbon\Carbon('-6 months'))->lastOfQuarter(); $this->end_date = (new \Carbon\Carbon('-6 months'))->lastOfQuarter();
//return $query->whereBetween($this->date_key, [(new \Carbon\Carbon('-6 months'))->firstOfQuarter(), (new \Carbon\Carbon('-6 months'))->lastOfQuarter()])->orderBy($this->date_key, 'ASC'); break;
// no break
case 'this_year': case 'this_year':
$this->start_date = now()->startOfYear(); $this->start_date = now()->startOfYear();
$this->end_date = now(); $this->end_date = now();
//return $query->whereBetween($this->date_key, [now()->startOfYear(), now()])->orderBy($this->date_key, 'ASC'); break;
// no break
case 'custom': case 'custom':
$this->start_date = $custom_start_date; $this->start_date = $custom_start_date;
$this->end_date = $custom_end_date; $this->end_date = $custom_end_date;
//return $query->whereBetween($this->date_key, [$custom_start_date, $custom_end_date])->orderBy($this->date_key, 'ASC'); break;
// no break
default: default:
$this->start_date = now()->startOfYear(); $this->start_date = now()->startOfYear();
$this->end_date = now(); $this->end_date = now();
// return $query->whereBetween($this->date_key, [now()->startOfYear(), now()])->orderBy($this->date_key, 'ASC');
} }
return $this; return $this;

View File

@ -11,11 +11,12 @@
namespace App\Services\Scheduler; namespace App\Services\Scheduler;
use App\DataMapper\Schedule\EmailStatement;
use App\Models\Client; use App\Models\Client;
use App\Models\Scheduler; use App\Models\Scheduler;
use App\Utils\Traits\MakesHash;
use App\DataMapper\Schedule\EmailStatement;
use App\Utils\Traits\MakesDates; use App\Utils\Traits\MakesDates;
use App\Utils\Traits\MakesHash;
use Carbon\Carbon;
class EmailStatementService class EmailStatementService
{ {
@ -37,7 +38,7 @@ class EmailStatementService
//Email only the selected clients //Email only the selected clients
if (count($this->scheduler->parameters['clients']) >= 1) { if (count($this->scheduler->parameters['clients']) >= 1) {
$query->whereIn('id', $this->transformKeys($this->scheduler->parameters['clients'])); $query->whereIn('id', $this->transformKeys($this->scheduler->parameters['clients']));
}else { } else {
$query->where('balance', '>', 0); $query->where('balance', '>', 0);
} }
@ -46,7 +47,7 @@ class EmailStatementService
$this->client = $_client; $this->client = $_client;
//work out the date range //work out the date range
$statement_properties = $this->calculateStatementProperties(); $statement_properties = $this->calculateStatementProperties($_client);
$_client->service()->statement($statement_properties, true); $_client->service()->statement($statement_properties, true);
}); });
@ -61,16 +62,17 @@ class EmailStatementService
* *
* @return array The statement options array * @return array The statement options array
*/ */
private function calculateStatementProperties(): array private function calculateStatementProperties(Client $client): array
{ {
$start_end = $this->calculateStartAndEndDates(); $start_end = $this->calculateStartAndEndDates($client);
return [ return [
'start_date' =>$start_end[0], 'start_date' => $start_end[0],
'end_date' =>$start_end[1], 'end_date' => $start_end[1],
'show_payments_table' => $this->scheduler->parameters['show_payments_table'] ?? true, 'show_payments_table' => $this->scheduler->parameters['show_payments_table'] ?? true,
'show_aging_table' => $this->scheduler->parameters['show_aging_table'] ?? true, 'show_aging_table' => $this->scheduler->parameters['show_aging_table'] ?? true,
'show_credits_table' => $this->scheduler->parameters['show_credits_table'] ?? true, 'show_credits_table' => $this->scheduler->parameters['show_credits_table'] ?? true,
'only_clients_with_invoices' => $this->scheduler->parameters['only_clients_with_invoices'] ?? false,
'status' => $this->scheduler->parameters['status'] 'status' => $this->scheduler->parameters['status']
]; ];
} }
@ -80,7 +82,7 @@ class EmailStatementService
* *
* @return array [$start_date, $end_date]; * @return array [$start_date, $end_date];
*/ */
private function calculateStartAndEndDates(): array private function calculateStartAndEndDates(Client $client): array
{ {
return match ($this->scheduler->parameters['date_range']) { return match ($this->scheduler->parameters['date_range']) {
EmailStatement::LAST7 => [now()->startOfDay()->subDays(7)->format('Y-m-d'), now()->startOfDay()->format('Y-m-d')], EmailStatement::LAST7 => [now()->startOfDay()->subDays(7)->format('Y-m-d'), now()->startOfDay()->format('Y-m-d')],
@ -92,6 +94,11 @@ class EmailStatementService
EmailStatement::LAST_QUARTER => [now()->startOfDay()->subQuarterNoOverflow()->firstOfQuarter()->format('Y-m-d'), now()->startOfDay()->subQuarterNoOverflow()->lastOfQuarter()->format('Y-m-d')], EmailStatement::LAST_QUARTER => [now()->startOfDay()->subQuarterNoOverflow()->firstOfQuarter()->format('Y-m-d'), now()->startOfDay()->subQuarterNoOverflow()->lastOfQuarter()->format('Y-m-d')],
EmailStatement::THIS_YEAR => [now()->startOfDay()->firstOfYear()->format('Y-m-d'), now()->startOfDay()->lastOfYear()->format('Y-m-d')], EmailStatement::THIS_YEAR => [now()->startOfDay()->firstOfYear()->format('Y-m-d'), now()->startOfDay()->lastOfYear()->format('Y-m-d')],
EmailStatement::LAST_YEAR => [now()->startOfDay()->subYearNoOverflow()->firstOfYear()->format('Y-m-d'), now()->startOfDay()->subYearNoOverflow()->lastOfYear()->format('Y-m-d')], EmailStatement::LAST_YEAR => [now()->startOfDay()->subYearNoOverflow()->firstOfYear()->format('Y-m-d'), now()->startOfDay()->subYearNoOverflow()->lastOfYear()->format('Y-m-d')],
EmailStatement::ALL_TIME => [
$client->invoices()->selectRaw('MIN(invoices.date) as start_date')->pluck('start_date')->first()
?: Carbon::now()->format('Y-m-d'),
Carbon::now()->format('Y-m-d')
],
EmailStatement::CUSTOM_RANGE => [$this->scheduler->parameters['start_date'], $this->scheduler->parameters['end_date']], EmailStatement::CUSTOM_RANGE => [$this->scheduler->parameters['start_date'], $this->scheduler->parameters['end_date']],
default => [now()->startOfDay()->firstOfMonth()->format('Y-m-d'), now()->startOfDay()->lastOfMonth()->format('Y-m-d')], default => [now()->startOfDay()->firstOfMonth()->format('Y-m-d'), now()->startOfDay()->lastOfMonth()->format('Y-m-d')],
}; };

View File

@ -132,7 +132,7 @@ class TaxProvider
'city' => $this->client->shipping_city, 'city' => $this->client->shipping_city,
'state' => $this->client->shipping_state, 'state' => $this->client->shipping_state,
'postal_code' => $this->client->shipping_postal_code, 'postal_code' => $this->client->shipping_postal_code,
'country' => $this->client->shipping_country->name, 'country' => $this->client->shipping_country()->exists() ? $this->client->shipping_country->name : $this->client->country->name,
]; ];
$taxable_address = $this->taxShippingAddress() ? $shipping_details : $billing_details; $taxable_address = $this->taxShippingAddress() ? $shipping_details : $billing_details;

View File

@ -1,4 +1,5 @@
<?php <?php
/** /**
* Invoice Ninja (https://invoiceninja.com). * Invoice Ninja (https://invoiceninja.com).
* *
@ -11,8 +12,10 @@
namespace App\Transformers; namespace App\Transformers;
use App\Models\Client;
use App\Models\Document; use App\Models\Document;
use App\Models\Project; use App\Models\Project;
use App\Models\Task;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
/** /**
@ -30,6 +33,8 @@ class ProjectTransformer extends EntityTransformer
* @var array * @var array
*/ */
protected $availableIncludes = [ protected $availableIncludes = [
'client',
'tasks',
]; ];
public function includeDocuments(Project $project) public function includeDocuments(Project $project)
@ -39,6 +44,20 @@ class ProjectTransformer extends EntityTransformer
return $this->includeCollection($project->documents, $transformer, Document::class); return $this->includeCollection($project->documents, $transformer, Document::class);
} }
public function includeClient(Project $project): \League\Fractal\Resource\Item
{
$transformer = new ClientTransformer($this->serializer);
return $this->includeItem($project->client, $transformer, Client::class);
}
public function includeTasks(Project $project): \League\Fractal\Resource\Collection
{
$transformer = new TaskTransformer($this->serializer);
return $this->includeCollection($project->tasks, $transformer, Task::class);
}
public function transform(Project $project) public function transform(Project $project)
{ {
return [ return [

View File

@ -36,6 +36,7 @@ class TaskTransformer extends EntityTransformer
'client', 'client',
'status', 'status',
'project', 'project',
'user',
]; ];
public function includeDocuments(Task $task) public function includeDocuments(Task $task)
@ -45,6 +46,18 @@ class TaskTransformer extends EntityTransformer
return $this->includeCollection($task->documents, $transformer, Document::class); return $this->includeCollection($task->documents, $transformer, Document::class);
} }
public function includeUser(Task $task): ?Item
{
$transformer = new UserTransformer($this->serializer);
if (!$task->user) {
return null;
}
return $this->includeItem($task->user, $transformer, User::class);
}
public function includeClient(Task $task): ?Item public function includeClient(Task $task): ?Item
{ {
$transformer = new ClientTransformer($this->serializer); $transformer = new ClientTransformer($this->serializer);

View File

@ -356,10 +356,9 @@ class HtmlEngine
$data['$credit.total'] = &$data['$credit.total']; $data['$credit.total'] = &$data['$credit.total'];
$data['$credit.po_number'] = &$data['$invoice.po_number']; $data['$credit.po_number'] = &$data['$invoice.po_number'];
$data['$credit.date'] = ['value' => $this->translateDate($this->entity->date, $this->client->date_format(), $this->client->locale()), 'label' => ctrans('texts.credit_date')]; $data['$credit.date'] = ['value' => $this->translateDate($this->entity->date, $this->client->date_format(), $this->client->locale()), 'label' => ctrans('texts.credit_date')];
$data['$balance'] = ['value' => Number::formatMoney($this->entity_calc->getBalance(), $this->client) ?: ' ', 'label' => ctrans('texts.balance')]; $data['$balance'] = ['value' => Number::formatMoney($this->getBalance(), $this->client) ?: ' ', 'label' => ctrans('texts.balance')];
$data['$credit.balance'] = ['value' => Number::formatMoney($this->entity_calc->getBalance(), $this->client) ?: ' ', 'label' => ctrans('texts.credit_balance')]; $data['$credit.balance'] = ['value' => Number::formatMoney($this->entity_calc->getBalance(), $this->client) ?: ' ', 'label' => ctrans('texts.credit_balance')];
$data['$invoice.balance'] = &$data['$balance']; $data['$invoice.balance'] = &$data['$balance'];
$data['$taxes'] = ['value' => Number::formatMoney($this->entity_calc->getItemTotalTaxes(), $this->client) ?: ' ', 'label' => ctrans('texts.taxes')]; $data['$taxes'] = ['value' => Number::formatMoney($this->entity_calc->getItemTotalTaxes(), $this->client) ?: ' ', 'label' => ctrans('texts.taxes')];
$data['$invoice.taxes'] = &$data['$taxes']; $data['$invoice.taxes'] = &$data['$taxes'];
@ -675,6 +674,15 @@ class HtmlEngine
return $data; return $data;
} }
private function getBalance()
{
if($this->entity->status_id == 1){
return $this->entity->amount;
}
return $this->entity->balance;
}
public function makeValues() :array public function makeValues() :array
{ {
$data = []; $data = [];

View File

@ -12,6 +12,7 @@
namespace App\Utils; namespace App\Utils;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
/** /**
* Class Ninja. * Class Ninja.
@ -152,6 +153,23 @@ class Ninja
return $translations; return $translations;
} }
public static function triggerForwarding(string $company_key, string $email)
{
try {
$x = Http::withHeaders([
'X-API-HOSTED-SECRET' => config('ninja.ninja_hosted_secret'),
])->post(config('ninja.license_url').'/api/v1/enable_forwarding', [
'account_key' => $company_key,
'email' => $email,
]);
nlog($x->body());
}
catch (\Exception $e) {
nlog("Attempt forwarding for {$email} - {$company_key} Failed");
}
}
public function createLicense($request) public function createLicense($request)
{ {
// $affiliate = Affiliate::where('affiliate_key', '=', SELF_HOST_AFFILIATE_KEY)->first(); // $affiliate = Affiliate::where('affiliate_key', '=', SELF_HOST_AFFILIATE_KEY)->first();

View File

@ -194,8 +194,8 @@ class Number
/** /**
* Formats a given value based on the clients currency AND country. * Formats a given value based on the clients currency AND country.
* *
* @param floatval $value The number to be formatted * @param float $value The number to be formatted
* @param $entity * @param mixed $entity
* @return string The formatted value * @return string The formatted value
*/ */
public static function formatMoneyNoRounding($value, $entity) :string public static function formatMoneyNoRounding($value, $entity) :string

View File

@ -278,9 +278,9 @@ class PaymentHtmlEngine
/** /**
* generateLabelsAndValues * generateLabelsAndValues
* *
* @return void * @return array
*/ */
public function generateLabelsAndValues() public function generateLabelsAndValues(): array
{ {
$data = []; $data = [];

View File

@ -288,11 +288,6 @@ class SystemHealth
return $result; return $result;
} }
private static function checkDbConnection()
{
return DB::connection()->getPdo();
}
public static function testMailServer($request = null) public static function testMailServer($request = null)
{ {
if ($request->driver == 'log') { if ($request->driver == 'log') {

View File

@ -102,7 +102,11 @@ class TemplateEngine
} elseif (stripos($this->template, 'purchase') !== false && $purchase_order = PurchaseOrder::whereHas('invitations')->withTrashed()->company()->first()) { } elseif (stripos($this->template, 'purchase') !== false && $purchase_order = PurchaseOrder::whereHas('invitations')->withTrashed()->company()->first()) {
$this->entity = 'purchase_order'; $this->entity = 'purchase_order';
$this->entity_obj = $purchase_order; $this->entity_obj = $purchase_order;
} elseif ($invoice = Invoice::whereHas('invitations')->withTrashed()->company()->first()) { }elseif (stripos($this->template, 'payment') !== false && $payment = Payment::withTrashed()->company()->first()) {
$this->entity = 'payment';
$this->entity_obj = $payment;
}
elseif ($invoice = Invoice::whereHas('invitations')->withTrashed()->company()->first()) {
$this->entity_obj = $invoice; $this->entity_obj = $invoice;
} else { } else {
$this->mockEntity(); $this->mockEntity();

View File

@ -69,7 +69,7 @@ trait Inviteable
$qr = $writer->writeString($this->getPaymentLink(), 'utf-8'); $qr = $writer->writeString($this->getPaymentLink(), 'utf-8');
return "<svg class='pqrcode' viewBox='0 0 200 200' width='200' height='200' x='0' y='0' xmlns='http://www.w3.org/2000/svg'> return "<svg class='pqrcode' viewBox='0 0 200 200' width='200' height='200' x='0' y='0' xmlns='http://www.w3.org/2000/svg'>
<rect x='0' y='0' width='100%'' height='100%' />{$qr}</svg>"; <rect x='0' y='0' width='100%' height='100%' />{$qr}</svg>";
} }
public function getUnsubscribeLink() public function getUnsubscribeLink()

View File

@ -116,6 +116,8 @@ trait MakesTemplateData
$data['$quote_total'] = ['value' => '$20.00', 'label' => ctrans('texts.quote_total')]; $data['$quote_total'] = ['value' => '$20.00', 'label' => ctrans('texts.quote_total')];
$data['$credit_amount'] = ['value' => '$15.00', 'label' => ctrans('texts.credit_amount')]; $data['$credit_amount'] = ['value' => '$15.00', 'label' => ctrans('texts.credit_amount')];
$data['$credit_balance'] = ['value' => '$12.00', 'label' => ctrans('texts.credit_balance')]; $data['$credit_balance'] = ['value' => '$12.00', 'label' => ctrans('texts.credit_balance')];
$data['$invoice_references'] = ['value' => 'Invoice #2222', 'label' => ctrans('texts.invoices')];
$data['$invoice_references_subject'] = ['value' => 'Invoice #2222', 'label' => ctrans('texts.invoices')];
$data['$credit_number'] = &$data['$number']; $data['$credit_number'] = &$data['$number'];
$data['$credit_no'] = &$data['$number']; $data['$credit_no'] = &$data['$number'];

View File

@ -8,6 +8,8 @@ use Imdhemy\Purchases\Events\GooglePlay\SubscriptionExpired;
use Imdhemy\Purchases\Events\GooglePlay\SubscriptionRenewed; use Imdhemy\Purchases\Events\GooglePlay\SubscriptionRenewed;
use Imdhemy\Purchases\Events\GooglePlay\SubscriptionRevoked; use Imdhemy\Purchases\Events\GooglePlay\SubscriptionRevoked;
use Imdhemy\Purchases\Events\AppStore\DidChangeRenewalStatus; use Imdhemy\Purchases\Events\AppStore\DidChangeRenewalStatus;
use Imdhemy\Purchases\Events\AppStore\InitialBuy;
use Imdhemy\Purchases\Events\AppStore\InteractiveRenewal;
use Imdhemy\Purchases\Events\GooglePlay\SubscriptionCanceled; use Imdhemy\Purchases\Events\GooglePlay\SubscriptionCanceled;
use Imdhemy\Purchases\Events\GooglePlay\SubscriptionPurchased; use Imdhemy\Purchases\Events\GooglePlay\SubscriptionPurchased;
use Imdhemy\Purchases\Events\GooglePlay\SubscriptionRecovered; use Imdhemy\Purchases\Events\GooglePlay\SubscriptionRecovered;
@ -118,6 +120,11 @@ return [
SubscriptionPaused::class => class_exists(\Modules\Admin\Listeners\Subscription\GoogleSubscriptionPaused::class) ? [\Modules\Admin\Listeners\Subscription\GoogleSubscriptionPaused::class] : [], SubscriptionPaused::class => class_exists(\Modules\Admin\Listeners\Subscription\GoogleSubscriptionPaused::class) ? [\Modules\Admin\Listeners\Subscription\GoogleSubscriptionPaused::class] : [],
SubscriptionRevoked::class => class_exists(\Modules\Admin\Listeners\Subscription\GoogleSubscriptionRevoked::class) ? [\Modules\Admin\Listeners\Subscription\GoogleSubscriptionRevoked::class] : [], SubscriptionRevoked::class => class_exists(\Modules\Admin\Listeners\Subscription\GoogleSubscriptionRevoked::class) ? [\Modules\Admin\Listeners\Subscription\GoogleSubscriptionRevoked::class] : [],
SubscriptionExpired::class => class_exists(\Modules\Admin\Listeners\Subscription\GoogleSubscriptionExpired::class) ? [\Modules\Admin\Listeners\Subscription\GoogleSubscriptionExpired::class] : [], SubscriptionExpired::class => class_exists(\Modules\Admin\Listeners\Subscription\GoogleSubscriptionExpired::class) ? [\Modules\Admin\Listeners\Subscription\GoogleSubscriptionExpired::class] : [],
Cancel::class => class_exists(\Modules\Admin\Listeners\Subscription\AppleCancel::class) ? [\Modules\Admin\Listeners\Subscription\AppleCancel::class] : [],
DidRecover::class => class_exists(\Modules\Admin\Listeners\Subscription\AppleRecover::class) ? [\Modules\Admin\Listeners\Subscription\AppleRecover::class] : [],
InitialBuy::class => class_exists(\Modules\Admin\Listeners\Subscription\AppleInitialBuy::class) ? [\Modules\Admin\Listeners\Subscription\AppleInitialBuy::class] : [],
InteractiveRenewal::class => class_exists(\Modules\Admin\Listeners\Subscription\AppleInteractiveRenewal::class) ? [\Modules\Admin\Listeners\Subscription\AppleInteractiveRenewal::class] : [],
], ],
/* /*

View File

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

View File

@ -0,0 +1,35 @@
<?php
use App\Models\Language;
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Language::unguard();
if (!Language::find(39)) {
$hungarian = ['id' => 39, 'name' => 'Hungarian', 'locale' => 'hu'];
Language::create($hungarian);
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
};

View File

@ -63,6 +63,7 @@ class LanguageSeeder extends Seeder
['id' => 36, 'name' => 'Bulgarian', 'locale' => 'bg'], ['id' => 36, 'name' => 'Bulgarian', 'locale' => 'bg'],
['id' => 37, 'name' => 'Hebrew', 'locale' => 'he'], ['id' => 37, 'name' => 'Hebrew', 'locale' => 'he'],
['id' => 38, 'name' => 'Khmer', 'locale' => 'km_KH'], ['id' => 38, 'name' => 'Khmer', 'locale' => 'km_KH'],
['id' => 39, 'name' => 'Hungarian', 'locale' => 'hu'],
]; ];
foreach ($languages as $language) { foreach ($languages as $language) {

View File

@ -754,7 +754,7 @@ $LANG = array(
'activity_7' => ':contact viewed invoice :invoice for :client', 'activity_7' => ':contact viewed invoice :invoice for :client',
'activity_8' => ':user archived invoice :invoice', 'activity_8' => ':user archived invoice :invoice',
'activity_9' => ':user deleted invoice :invoice', 'activity_9' => ':user deleted invoice :invoice',
'activity_10' => ':contact entered payment :payment for :payment_amount on invoice :invoice for :client', 'activity_10' => ':user entered payment :payment for :payment_amount on invoice :invoice for :client',
'activity_11' => ':user updated payment :payment', 'activity_11' => ':user updated payment :payment',
'activity_12' => ':user archived payment :payment', 'activity_12' => ':user archived payment :payment',
'activity_13' => ':user deleted payment :payment', 'activity_13' => ':user deleted payment :payment',
@ -2000,6 +2000,7 @@ $LANG = array(
'current_quarter' => 'Current Quarter', 'current_quarter' => 'Current Quarter',
'last_quarter' => 'Last Quarter', 'last_quarter' => 'Last Quarter',
'last_year' => 'Last Year', 'last_year' => 'Last Year',
'all_time' => 'All Time',
'custom_range' => 'Custom Range', 'custom_range' => 'Custom Range',
'url' => 'URL', 'url' => 'URL',
'debug' => 'Debug', 'debug' => 'Debug',
@ -4907,6 +4908,7 @@ $LANG = array(
'all_clients' => 'All Clients', 'all_clients' => 'All Clients',
'show_aging_table' => 'Show Aging Table', 'show_aging_table' => 'Show Aging Table',
'show_payments_table' => 'Show Payments Table', 'show_payments_table' => 'Show Payments Table',
'only_clients_with_invoices' => 'Only Clients with Invoices',
'email_statement' => 'Email Statement', 'email_statement' => 'Email Statement',
'once' => 'Once', 'once' => 'Once',
'schedules' => 'Schedules', 'schedules' => 'Schedules',
@ -5105,6 +5107,8 @@ $LANG = array(
'gallery' => 'Gallery', 'gallery' => 'Gallery',
'project_location' => 'Project Location', 'project_location' => 'Project Location',
'add_gateway_help_message' => 'Add a payment gateway (ie. Stripe, WePay or PayPal) to accept online payments', 'add_gateway_help_message' => 'Add a payment gateway (ie. Stripe, WePay or PayPal) to accept online payments',
'lang_Hungarian' => 'Hungarian',
'use_mobile_to_manage_plan' => 'Use your phone subscription settings to manage your plan',
); );

View File

@ -5087,6 +5087,16 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette
'upload_certificate' => 'Téléverser le certificat', 'upload_certificate' => 'Téléverser le certificat',
'certificate_passphrase' => 'Passphrase du certificat', 'certificate_passphrase' => 'Passphrase du certificat',
'valid_vat_number' => 'Numéro valide de taxe', 'valid_vat_number' => 'Numéro valide de taxe',
'react_notification_link' => 'Lien de notifications React',
'react_notification_link_help' => 'Les courriels provenant de l\'administration contiennent des liens vers l\'application React',
'show_task_billable' => 'Afficher la facturation de tâche',
'credit_item' => 'Credit Item',
'drop_file_here' => 'Déposer le fichier ici',
'files' => 'Fichiers',
'camera' => 'Caméra',
'gallery' => 'Galerie',
'project_location' => 'Emplacement du projet',
'add_gateway_help_message' => 'Ajouter un passerelle de paiement (Stripe, WePay, ou PayPal) pour accepter les paiements en ligne',
); );

19
lang/hu/auth.php Normal file
View File

@ -0,0 +1,19 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Authentication Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are used during authentication for various
| messages that we need to display to the user. You are free to modify
| these language lines according to your application's requirements.
|
*/
'failed' => 'Ovi podaci ne odgovaraju našima.',
'throttle' => 'Previše pokušaja prijave. Molim Vas pokušajte ponovno za :seconds sekundi.',
];

19
lang/hu/pagination.php Normal file
View File

@ -0,0 +1,19 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Pagination Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are used by the paginator library to build
| the simple pagination links. You are free to change them to anything
| you want to customize your views to better match your application.
|
*/
'previous' => '&laquo; Prethodna',
'next' => 'Sljedeća &raquo;',
];

22
lang/hu/passwords.php Normal file
View File

@ -0,0 +1,22 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Password Reminder Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are the default lines which match reasons
| that are given by the password broker for a password update attempt
| has failed, such as for an invalid token or invalid new password.
|
*/
'password' => 'Lozinke moraju biti duge barem 6 znakova i moraju odgovarati potvrdi.',
'reset' => 'Lozinka je postavljena!',
'sent' => 'Poveznica za ponovono postavljanje lozinke je poslana!',
'token' => 'Oznaka za ponovno postavljanje lozinke više nije važeća.',
'user' => 'Korisnik nije pronađen.',
];

5088
lang/hu/texts.php Normal file

File diff suppressed because it is too large Load Diff

116
lang/hu/validation.php Normal file
View File

@ -0,0 +1,116 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Validation Language Lines
|--------------------------------------------------------------------------
|
| The following language lines contain the default error messages used by
| the validator class. Some of these rules have multiple versions such
| such as the size rules. Feel free to tweak each of these messages.
|
*/
'accepted' => 'Polje :attribute mora biti prihvaćeno.',
'active_url' => 'Polje :attribute nije ispravan URL.',
'after' => 'Polje :attribute mora biti datum nakon :date.',
'alpha' => 'Polje :attribute smije sadržavati samo slova.',
'alpha_dash' => 'Polje :attribute smije sadržavati samo slova, brojeve i crtice.',
'alpha_num' => 'Polje :attribute smije sadržavati samo slova i brojeve.',
'array' => 'Polje :attribute mora biti niz.',
'before' => 'Polje :attribute mora biti datum prije :date.',
'between' => [
'numeric' => 'Polje :attribute mora biti između :min - :max.',
'file' => 'Polje :attribute mora biti između :min - :max kilobajta.',
'string' => 'Polje :attribute mora biti između :min - :max znakova.',
'array' => 'Polje :attribute mora imati između :min - :max stavki.',
],
'boolean' => 'Polje :attribute mora biti false ili true.',
'confirmed' => 'Potvrda polja :attribute se ne podudara.',
'date' => 'Polje :attribute nije ispravan datum.',
'date_format' => 'Polje :attribute ne podudara s formatom :format.',
'different' => 'Polja :attribute i :other moraju biti različita.',
'digits' => 'Polje :attribute mora sadržavati :digits znamenki.',
'digits_between' => 'Polje :attribute mora imati između :min i :max znamenki.',
'dimensions' => 'The :attribute has invalid image dimensions.',
'distinct' => 'The :attribute field has a duplicate value.',
'email' => 'Polje :attribute mora biti ispravna e-mail adresa.',
'exists' => 'Odabrano polje :attribute nije ispravno.',
'filled' => 'The :attribute field is required.',
'image' => 'Polje :attribute mora biti slika.',
'in' => 'Odabrano polje :attribute nije ispravno.',
'in_array' => 'The :attribute field does not exist in :other.',
'integer' => 'Polje :attribute mora biti broj.',
'ip' => 'Polje :attribute mora biti ispravna IP adresa.',
'json' => 'The :attribute must be a valid JSON string.',
'max' => [
'numeric' => 'Polje :attribute mora biti manje od :max.',
'file' => 'Polje :attribute mora biti manje od :max kilobajta.',
'string' => 'Polje :attribute mora sadržavati manje od :max znakova.',
'array' => 'Polje :attribute ne smije imati više od :max stavki.',
],
'mimes' => 'Polje :attribute mora biti datoteka tipa: :values.',
'min' => [
'numeric' => 'Polje :attribute mora biti najmanje :min.',
'file' => 'Polje :attribute mora biti najmanje :min kilobajta.',
'string' => 'Polje :attribute mora sadržavati najmanje :min znakova.',
'array' => 'Polje :attribute mora sadržavati najmanje :min stavki.',
],
'not_in' => 'Odabrano polje :attribute nije ispravno.',
'numeric' => 'Polje :attribute mora biti broj.',
'present' => 'The :attribute field must be present.',
'regex' => 'Polje :attribute se ne podudara s formatom.',
'required' => 'Polje :attribute je obavezno.',
'required_if' => 'Polje :attribute je obavezno kada polje :other sadrži :value.',
'required_unless' => 'The :attribute field is required unless :other is in :values.',
'required_with' => 'Polje :attribute je obavezno kada postoji polje :values.',
'required_with_all' => 'Polje :attribute je obavezno kada postje polja :values.',
'required_without' => 'Polje :attribute je obavezno kada ne postoji polje :values.',
'required_without_all' => 'Polje :attribute je obavezno kada nijedno od polja :values ne postoji.',
'same' => 'Polja :attribute i :other se moraju podudarati.',
'size' => [
'numeric' => 'Polje :attribute mora biti :size.',
'file' => 'Polje :attribute mora biti :size kilobajta.',
'string' => 'Polje :attribute mora biti :size znakova.',
'array' => 'Polje :attribute mora sadržavati :size stavki.',
],
'string' => 'The :attribute must be a string.',
'timezone' => 'Polje :attribute mora biti ispravna vremenska zona.',
'unique' => 'Polje :attribute već postoji.',
'url' => 'Polje :attribute nije ispravnog formata.',
/*
|--------------------------------------------------------------------------
| Custom Validation Language Lines
|--------------------------------------------------------------------------
|
| Here you may specify custom validation messages for attributes using the
| convention "attribute.rule" to name the lines. This makes it quick to
| specify a specific custom language line for a given attribute rule.
|
*/
'custom' => [
'attribute-name' => [
'rule-name' => 'custom-message',
],
],
/*
|--------------------------------------------------------------------------
| Custom Validation Attributes
|--------------------------------------------------------------------------
|
| The following language lines are used to swap attribute place-holders
| with something more reader friendly such as E-Mail Address instead
| of "email". This simply helps us make messages a little cleaner.
|
*/
'attributes' => [
//
],
];

View File

@ -19980,6 +19980,17 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
widget_kit_plugin
Copyright 2023 Gameflow AS
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
win32 win32

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