mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-10 21:22:58 +01:00
Merge pull request #8168 from turbo124/v5-develop
Improve caching on seutp.
This commit is contained in:
commit
86f343a59d
119
CONTRIBUTING.md
Normal file
119
CONTRIBUTING.md
Normal file
@ -0,0 +1,119 @@
|
||||
# Contributing to CONTRIBUTING.md
|
||||
|
||||
First off, thanks for taking the time to contribute!
|
||||
|
||||
All types of contributions are encouraged and valued. See the [Table of Contents](#table-of-contents) for different ways to help and details about how this project handles them. Please make sure to read the relevant section before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions.
|
||||
|
||||
> And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about:
|
||||
> - Star the project
|
||||
> - Tweet about it
|
||||
> - Refer this project in your project's readme
|
||||
> - Mention the project at local meetups and tell your friends/colleagues
|
||||
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Code of Conduct](#code-of-conduct)
|
||||
- [I Have a Question](#i-have-a-question)
|
||||
- [I Want To Contribute](#i-want-to-contribute)
|
||||
- [Reporting Bugs](#reporting-bugs)
|
||||
- [Suggesting Enhancements](#suggesting-enhancements)
|
||||
- [Your First Code Contribution](#your-first-code-contribution)
|
||||
- [Improving The Documentation](#improving-the-documentation)
|
||||
- [Styleguides](#styleguides)
|
||||
- [Commit Messages](#commit-messages)
|
||||
- [Join The Project Team](#join-the-project-team)
|
||||
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project and everyone participating in it is governed by the
|
||||
[CONTRIBUTING.md Code of Conduct](blob/v5-stable/CODE_OF_CONDUCT.md).
|
||||
By participating, you are expected to uphold this code. Please report unacceptable behavior
|
||||
to <>.
|
||||
|
||||
|
||||
## I Have a Question
|
||||
|
||||
> If you want to ask a question, we assume that you have read the available [Documentation](https://invoiceninja.github.io), Searched the [Forum](https://forum.invoiceninja.com), or tried chatting with us on [Slack](https://invoiceninja.slack.com) [generate a slack invite here](http://slack.invoiceninja.com)
|
||||
|
||||
Please reserve issues for bugs, general questions posted in the issues section will be closed and redirected to other support venues.
|
||||
|
||||
## I Want To Contribute
|
||||
|
||||
|
||||
> ### Legal Notice
|
||||
> When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project license. Please note that you'll need to sign the CLA for any PRs to be accepted
|
||||
|
||||
### Reporting Bugs
|
||||
|
||||
|
||||
#### Before Submitting a Bug Report
|
||||
|
||||
A good bug report shouldn't leave others needing to chase you up for more information. Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. Please complete the following steps in advance to help us fix any potential bug as fast as possible.
|
||||
|
||||
- Make sure that you are using the latest version.
|
||||
- Determine if your bug is really a bug and not an error on your side e.g. using incompatible environment components/versions (Make sure that you have read the [documentation](https://invoiceninja.github.io). If you are looking for support, you might want to check [this section](#i-have-a-question)).
|
||||
- To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error in the [bug tracker](issues?q=label%3Abug).
|
||||
- Also make sure to search the internet (including Stack Overflow) to see if users outside of the GitHub community have discussed the issue.
|
||||
- Collect information about the bug:
|
||||
- Stack trace (Traceback)
|
||||
- OS, Platform and Version (Windows, Linux, macOS, x86, ARM)
|
||||
- Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant.
|
||||
- Possibly your input and the output
|
||||
- Can you reliably reproduce the issue? And can you also reproduce it with older versions?
|
||||
|
||||
|
||||
#### How Do I Submit a Good Bug Report?
|
||||
|
||||
> You must never report security related issues, vulnerabilities or bugs including sensitive information to the issue tracker, or elsewhere in public. Instead sensitive bugs must be sent by email to <>.
|
||||
|
||||
|
||||
We use GitHub issues to track bugs and errors. If you run into an issue with the project:
|
||||
|
||||
- Open an [Issue](/issues/new). (Since we can't be sure at this point whether it is a bug or not, we ask you not to talk about a bug yet and not to label the issue.)
|
||||
- Explain the behavior you would expect and the actual behavior.
|
||||
- Please provide as much context as possible and describe the *reproduction steps* that someone else can follow to recreate the issue on their own. This usually includes your code. For good bug reports you should isolate the problem and create a reduced test case.
|
||||
- Provide the information you collected in the previous section.
|
||||
|
||||
Once it's filed:
|
||||
|
||||
- The project team will label the issue accordingly.
|
||||
- A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for those steps and mark the issue as `needs-repro`. Bugs with the `needs-repro` tag will not be addressed until they are reproduced.
|
||||
- If the team is able to reproduce the issue, it will be marked `needs-fix`, as well as possibly other tags (such as `critical`), and the issue will be left to be [implemented by someone](#your-first-code-contribution).
|
||||
|
||||
|
||||
|
||||
|
||||
### Suggesting Enhancements
|
||||
|
||||
This section guides you through submitting an enhancement suggestion for CONTRIBUTING.md, **including completely new features and minor improvements to existing functionality**. Following these guidelines will help maintainers and the community to understand your suggestion and find related suggestions.
|
||||
|
||||
|
||||
#### Before Submitting an Enhancement
|
||||
|
||||
- Make sure that you are using the latest version.
|
||||
- Read the [documentation]() carefully and find out if the functionality is already covered, maybe by an individual configuration.
|
||||
- Perform a [search](/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
|
||||
- Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on/plugin library.
|
||||
|
||||
|
||||
#### How Do I Submit a Good Enhancement Suggestion?
|
||||
|
||||
Enhancement suggestions are tracked as [GitHub issues](/issues).
|
||||
|
||||
- Use a **clear and descriptive title** for the issue to identify the suggestion.
|
||||
- Provide a **step-by-step description of the suggested enhancement** in as many details as possible.
|
||||
- **Describe the current behavior** and **explain which behavior you expected to see instead** and why. At this point you can also tell which alternatives do not work for you.
|
||||
- You may want to **include screenshots and animated GIFs** which help you demonstrate the steps or point out the part which the suggestion is related to. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux.
|
||||
- **Explain why this enhancement would be useful** to most CONTRIBUTING.md users. You may also want to point out the other projects that solved it better and which could serve as inspiration.
|
||||
|
||||
|
||||
|
||||
### Your First Code Contribution
|
||||
|
||||
All PRs should be created against the v5-develop branch.
|
||||
|
||||
|
||||
## Attribution
|
||||
This guide is based on the **contributing.md**. [Make your own](https://contributing.md/)!
|
@ -1 +1 @@
|
||||
5.5.55
|
||||
5.5.56
|
@ -134,6 +134,9 @@ class CreateAccount extends Command
|
||||
(new CreateCompanyPaymentTerms($company, $user))->handle();
|
||||
(new CreateCompanyTaskStatuses($company, $user))->handle();
|
||||
(new VersionCheck())->handle();
|
||||
|
||||
$this->warmCache();
|
||||
|
||||
}
|
||||
|
||||
private function warmCache()
|
||||
@ -142,11 +145,6 @@ class CreateAccount extends Command
|
||||
$cached_tables = config('ninja.cached_tables');
|
||||
|
||||
foreach ($cached_tables as $name => $class) {
|
||||
if (! Cache::has($name)) {
|
||||
// check that the table exists in case the migration is pending
|
||||
if (! Schema::hasTable((new $class())->getTable())) {
|
||||
continue;
|
||||
}
|
||||
if ($name == 'payment_terms') {
|
||||
$orderBy = 'num_days';
|
||||
} elseif ($name == 'fonts') {
|
||||
@ -160,7 +158,7 @@ class CreateAccount extends Command
|
||||
if ($tableData->count()) {
|
||||
Cache::forever($name, $tableData);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,15 +12,16 @@
|
||||
namespace App\DataMapper;
|
||||
|
||||
/**
|
||||
* ClientSettings.
|
||||
* BaseSettings.
|
||||
*/
|
||||
class BaseSettings
|
||||
{
|
||||
//@deprecated
|
||||
public function __construct($obj)
|
||||
{
|
||||
foreach ($obj as $key => $value) {
|
||||
$obj->{$key} = $value;
|
||||
}
|
||||
// foreach ($obj as $key => $value) {
|
||||
// $obj->{$key} = $value;
|
||||
// }
|
||||
}
|
||||
|
||||
public static function setCasts($obj, $casts)
|
||||
@ -57,7 +58,4 @@ class BaseSettings
|
||||
}
|
||||
}
|
||||
|
||||
public static function castSingleAttribute($key, $data)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -451,7 +451,11 @@ class CompanySettings extends BaseSettings
|
||||
|
||||
public $email_alignment = 'center'; // center , left, right
|
||||
|
||||
public $show_email_footer = true;
|
||||
|
||||
|
||||
public static $casts = [
|
||||
'show_email_footer' => 'bool',
|
||||
'email_alignment' => 'string',
|
||||
'auto_bill_standard_invoices' => 'bool',
|
||||
'postmark_secret' => 'string',
|
||||
@ -730,8 +734,9 @@ class CompanySettings extends BaseSettings
|
||||
* and always ensure an up to date class is returned.
|
||||
*
|
||||
* @param $obj
|
||||
* @deprecated
|
||||
*/
|
||||
public function __construct($obj)
|
||||
public function __construct()
|
||||
{
|
||||
// parent::__construct($obj);
|
||||
}
|
||||
|
@ -16,12 +16,10 @@ use App\Exceptions\InternalPDFFailure;
|
||||
use App\Exceptions\PhantomPDFFailure;
|
||||
use App\Exceptions\StripeConnectFailure;
|
||||
use App\Utils\Ninja;
|
||||
use Exception;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Auth\AuthenticationException;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundException;
|
||||
use Illuminate\Database\Eloquent\RelationNotFoundException;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||
use Illuminate\Http\Exceptions\ThrottleRequestsException;
|
||||
use Illuminate\Http\Request;
|
||||
@ -34,7 +32,6 @@ use Illuminate\Validation\ValidationException;
|
||||
use PDOException;
|
||||
use Sentry\Laravel\Integration;
|
||||
use Sentry\State\Scope;
|
||||
use Swift_TransportException;
|
||||
use Symfony\Component\Console\Exception\CommandNotFoundException;
|
||||
use Symfony\Component\Debug\Exception\FatalThrowableError;
|
||||
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
|
||||
@ -50,7 +47,6 @@ class Handler extends ExceptionHandler
|
||||
*/
|
||||
protected $dontReport = [
|
||||
PDOException::class,
|
||||
//Swift_TransportException::class,
|
||||
MaxAttemptsExceededException::class,
|
||||
CommandNotFoundException::class,
|
||||
ValidationException::class,
|
||||
@ -80,11 +76,10 @@ class Handler extends ExceptionHandler
|
||||
{
|
||||
if (! Schema::hasTable('accounts')) {
|
||||
info('account table not found');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (Ninja::isHosted() && ! ($exception instanceof ValidationException)) {
|
||||
if (Ninja::isHosted()) {
|
||||
Integration::configureScope(function (Scope $scope): void {
|
||||
$name = 'hosted@invoiceninja.com';
|
||||
|
||||
@ -105,7 +100,10 @@ class Handler extends ExceptionHandler
|
||||
]);
|
||||
});
|
||||
|
||||
if ($this->validException($exception)) {
|
||||
Integration::captureUnhandledException($exception);
|
||||
}
|
||||
|
||||
} elseif (app()->bound('sentry') && $this->shouldReport($exception)) {
|
||||
Integration::configureScope(function (Scope $scope): void {
|
||||
if (auth()->guard('contact') && auth()->guard('contact')->user() && auth()->guard('contact')->user()->company->account->report_errors) {
|
||||
@ -180,8 +178,8 @@ class Handler extends ExceptionHandler
|
||||
return response()->json(['message' => $exception->getMessage()], 500);
|
||||
} elseif ($exception instanceof ThrottleRequestsException && $request->expectsJson()) {
|
||||
return response()->json(['message'=>'Too many requests'], 429);
|
||||
} elseif ($exception instanceof FatalThrowableError && $request->expectsJson()) {
|
||||
return response()->json(['message'=>'Fatal error'], 500);
|
||||
// } elseif ($exception instanceof FatalThrowableError && $request->expectsJson()) {
|
||||
// return response()->json(['message'=>'Fatal error'], 500); //@deprecated
|
||||
} elseif ($exception instanceof AuthorizationException) {
|
||||
return response()->json(['message'=> $exception->getMessage()], 401);
|
||||
} elseif ($exception instanceof TokenMismatchException) {
|
||||
@ -208,13 +206,6 @@ class Handler extends ExceptionHandler
|
||||
return response()->json(['message' => $exception->getMessage()], 400);
|
||||
}
|
||||
|
||||
|
||||
// elseif ($exception instanceof QueryException) {
|
||||
// return response()->json(['message' => 'We had a problem executing this query. Please retry.'], 500);
|
||||
// }
|
||||
|
||||
|
||||
|
||||
return parent::render($request, $exception);
|
||||
}
|
||||
|
||||
|
@ -109,6 +109,7 @@ class BaseController extends Controller
|
||||
'company.bank_integrations',
|
||||
'company.bank_transactions',
|
||||
'company.bank_transaction_rules',
|
||||
'company.task_schedulers',
|
||||
];
|
||||
|
||||
private $mini_load = [
|
||||
@ -128,6 +129,7 @@ class BaseController extends Controller
|
||||
'company.subscriptions',
|
||||
'company.bank_integrations',
|
||||
'company.bank_transaction_rules',
|
||||
'company.task_schedulers',
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
@ -465,6 +467,13 @@ class BaseController extends Controller
|
||||
$query->where('bank_transaction_rules.user_id', $user->id);
|
||||
}
|
||||
},
|
||||
'company.task_schedulers'=> function ($query) use ($updated_at, $user) {
|
||||
$query->where('updated_at', '>=', $updated_at);
|
||||
|
||||
if (! $user->isAdmin()) {
|
||||
$query->where('schedulers.user_id', $user->id);
|
||||
}
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
@ -545,6 +554,12 @@ class BaseController extends Controller
|
||||
$query->where('bank_transaction_rules.user_id', $user->id);
|
||||
}
|
||||
},
|
||||
'company.task_schedulers'=> function ($query) use ($user) {
|
||||
|
||||
if (! $user->isAdmin()) {
|
||||
$query->where('schedulers.user_id', $user->id);
|
||||
}
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
@ -803,6 +818,13 @@ class BaseController extends Controller
|
||||
$query->where('bank_transactions.user_id', $user->id);
|
||||
}
|
||||
},
|
||||
'company.task_schedulers'=> function ($query) use ($created_at, $user) {
|
||||
$query->where('created_at', '>=', $created_at);
|
||||
|
||||
if (! $user->isAdmin()) {
|
||||
$query->where('schedulers.user_id', $user->id);
|
||||
}
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -109,16 +109,24 @@ class ClientStatementController extends BaseController
|
||||
*/
|
||||
public function statement(CreateStatementRequest $request)
|
||||
{
|
||||
$send_email = false;
|
||||
|
||||
if($request->has('send_email') && $request->send_email == 'true')
|
||||
$send_email = true;
|
||||
|
||||
$pdf = $request->client()->service()->statement(
|
||||
$request->only(['start_date', 'end_date', 'show_payments_table', 'show_aging_table', 'status'])
|
||||
$request->only(['start_date', 'end_date', 'show_payments_table', 'show_aging_table', 'status']), $send_email
|
||||
);
|
||||
|
||||
if($send_email)
|
||||
return response()->json(['message' => ctrans('texts.email_queued')], 200);
|
||||
|
||||
if ($pdf) {
|
||||
return response()->streamDownload(function () use ($pdf) {
|
||||
echo $pdf;
|
||||
}, ctrans('texts.statement').'.pdf', ['Content-Type' => 'application/pdf']);
|
||||
}
|
||||
|
||||
return response()->json(['message' => 'Something went wrong. Please check logs.']);
|
||||
return response()->json(['message' => ctrans('texts.error_title')], 500);
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ use App\Factory\CreditFactory;
|
||||
use App\Factory\InvoiceFactory;
|
||||
use App\Factory\QuoteFactory;
|
||||
use App\Factory\RecurringInvoiceFactory;
|
||||
use App\Http\Requests\Invoice\StoreInvoiceRequest;
|
||||
use App\Http\Requests\Preview\PreviewInvoiceRequest;
|
||||
use App\Jobs\Util\PreviewPdf;
|
||||
use App\Libraries\MultiDB;
|
||||
@ -44,7 +43,6 @@ use App\Utils\Traits\MakesInvoiceHtml;
|
||||
use App\Utils\Traits\Pdf\PageNumbering;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Lang;
|
||||
use Illuminate\Support\Facades\Response;
|
||||
use Turbo124\Beacon\Facades\LightLogs;
|
||||
|
||||
@ -109,8 +107,6 @@ class PreviewController extends BaseController
|
||||
|
||||
$class = "App\Models\\$entity";
|
||||
|
||||
$pdf_class = "App\Jobs\\$entity\\Create{$entity}Pdf";
|
||||
|
||||
$entity_obj = $class::whereId($this->decodePrimaryKey(request()->input('entity_id')))->company()->first();
|
||||
|
||||
if (! $entity_obj) {
|
||||
@ -214,6 +210,12 @@ class PreviewController extends BaseController
|
||||
->first();
|
||||
}
|
||||
|
||||
if($request->has('footer') && !$request->filled('footer') && $request->input('entity') == 'recurring_invoice')
|
||||
$request->merge(['footer' => $company->settings->invoice_footer]);
|
||||
|
||||
if($request->has('terms') && !$request->filled('terms') && $request->input('entity') == 'recurring_invoice')
|
||||
$request->merge(['terms' => $company->settings->invoice_terms]);
|
||||
|
||||
$entity_obj = $repo->save($request->all(), $entity_obj);
|
||||
|
||||
if (! $request->has('entity_id')) {
|
||||
@ -404,8 +406,8 @@ class PreviewController extends BaseController
|
||||
'user_id' => auth()->user()->id,
|
||||
'company_id' => auth()->user()->company()->id,
|
||||
'client_id' => $client->id,
|
||||
'terms' => 'Sample Terms',
|
||||
'footer' => 'Sample Footer',
|
||||
'terms' => auth()->user()->company()->settings->invoice_terms,
|
||||
'footer' => auth()->user()->company()->settings->invoice_footer,
|
||||
'public_notes' => 'Sample Public Notes',
|
||||
]);
|
||||
|
||||
|
@ -16,7 +16,6 @@ use App\Models\Client;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\AppSetup;
|
||||
use App\Utils\Traits\ClientGroupSettingsSaver;
|
||||
use Beganovich\Snappdf\Snappdf;
|
||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
@ -108,6 +107,8 @@ class SelfUpdateController extends BaseController
|
||||
|
||||
$zipFile->openFile($file);
|
||||
|
||||
$zipFile->deleteFromName(".htaccess");
|
||||
|
||||
$zipFile->extractTo(base_path());
|
||||
|
||||
$zipFile->close();
|
||||
|
@ -156,7 +156,11 @@ class PaymentEmailEngine extends BaseEmailEngine
|
||||
$data['$payment2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'payment2', $this->payment->custom_value2, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'payment2')];
|
||||
$data['$payment3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'payment3', $this->payment->custom_value3, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'payment3')];
|
||||
$data['$payment4'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'payment4', $this->payment->custom_value4, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'payment4')];
|
||||
// $data['$type'] = ['value' => $this->payment->type->name ?: '', 'label' => ctrans('texts.payment_type')];
|
||||
|
||||
$data['$custom1'] = &$data['$payment1'];
|
||||
$data['$custom2'] = &$data['$payment2'];
|
||||
$data['$custom3'] = &$data['$payment3'];
|
||||
$data['$custom4'] = &$data['$payment4'];
|
||||
|
||||
$data['$client1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'client1', $this->client->custom_value1, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'client1')];
|
||||
$data['$client2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'client2', $this->client->custom_value2, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'client2')];
|
||||
|
@ -12,15 +12,12 @@
|
||||
namespace App\Models;
|
||||
|
||||
use App\DataMapper\ClientSettings;
|
||||
use App\DataMapper\CompanySettings;
|
||||
use App\Jobs\Entity\CreateEntityPdf;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Utils\Traits\UserSessionAttributes;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundException;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
|
||||
@ -115,17 +112,17 @@ class BaseModel extends Model
|
||||
* reference to the parent class.
|
||||
*
|
||||
* @param $key The key of property
|
||||
* @return
|
||||
* @deprecated
|
||||
*/
|
||||
public function getSettingsByKey($key)
|
||||
{
|
||||
/* Does Setting Exist @ client level */
|
||||
if (isset($this->getSettings()->{$key})) {
|
||||
return $this->getSettings()->{$key};
|
||||
} else {
|
||||
return (new CompanySettings($this->company->settings))->{$key};
|
||||
}
|
||||
}
|
||||
// public function getSettingsByKey($key)
|
||||
// {
|
||||
// /* Does Setting Exist @ client level */
|
||||
// if (isset($this->getSettings()->{$key})) {
|
||||
// return $this->getSettings()->{$key};
|
||||
// } else {
|
||||
// return (new CompanySettings($this->company->settings))->{$key};
|
||||
// }
|
||||
// }
|
||||
|
||||
public function setSettingsByEntity($entity, $settings)
|
||||
{
|
||||
|
@ -172,6 +172,16 @@ class Company extends BaseModel
|
||||
return $this->morphMany(Document::class, 'documentable');
|
||||
}
|
||||
|
||||
public function schedulers()
|
||||
{
|
||||
return $this->hasMany(Scheduler::class);
|
||||
}
|
||||
|
||||
public function task_schedulers() //alias for schedulers
|
||||
{
|
||||
return $this->hasMany(Scheduler::class);
|
||||
}
|
||||
|
||||
public function all_documents()
|
||||
{
|
||||
return $this->hasMany(Document::class);
|
||||
|
@ -15,23 +15,28 @@ use App\Models\Client;
|
||||
use App\Models\Credit;
|
||||
use App\Services\Client\Merge;
|
||||
use App\Services\Client\PaymentMethod;
|
||||
use App\Services\Email\EmailObject;
|
||||
use App\Services\Email\EmailService;
|
||||
use App\Utils\Number;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use App\Utils\Traits\MakesDates;
|
||||
use Illuminate\Mail\Mailables\Address;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ClientService
|
||||
{
|
||||
private $client;
|
||||
use MakesDates;
|
||||
|
||||
public function __construct(Client $client)
|
||||
{
|
||||
$this->client = $client;
|
||||
}
|
||||
private string $client_start_date;
|
||||
|
||||
private string $client_end_date;
|
||||
|
||||
public function __construct(private Client $client){}
|
||||
|
||||
public function updateBalance(float $amount)
|
||||
{
|
||||
|
||||
try {
|
||||
\DB::connection(config('database.default'))->transaction(function () use($amount) {
|
||||
DB::connection(config('database.default'))->transaction(function () use($amount) {
|
||||
|
||||
$this->client = Client::withTrashed()->where('id', $this->client->id)->lockForUpdate()->first();
|
||||
$this->client->balance += $amount;
|
||||
@ -51,7 +56,7 @@ class ClientService
|
||||
{
|
||||
|
||||
try {
|
||||
\DB::connection(config('database.default'))->transaction(function () use($balance, $paid_to_date) {
|
||||
DB::connection(config('database.default'))->transaction(function () use($balance, $paid_to_date) {
|
||||
|
||||
$this->client = Client::withTrashed()->where('id', $this->client->id)->lockForUpdate()->first();
|
||||
$this->client->balance += $balance;
|
||||
@ -64,16 +69,14 @@ class ClientService
|
||||
nlog("DB ERROR " . $throwable->getMessage());
|
||||
}
|
||||
|
||||
|
||||
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
||||
public function updatePaidToDate(float $amount)
|
||||
{
|
||||
// $this->client->paid_to_date += $amount;
|
||||
|
||||
\DB::connection(config('database.default'))->transaction(function () use($amount) {
|
||||
DB::connection(config('database.default'))->transaction(function () use($amount) {
|
||||
|
||||
$this->client = Client::withTrashed()->where('id', $this->client->id)->lockForUpdate()->first();
|
||||
$this->client->paid_to_date += $amount;
|
||||
@ -82,17 +85,21 @@ class ClientService
|
||||
}, 1);
|
||||
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
||||
public function adjustCreditBalance(float $amount)
|
||||
{
|
||||
|
||||
$this->client->credit_balance += $amount;
|
||||
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
||||
public function getCreditBalance() :float
|
||||
{
|
||||
|
||||
$credits = Credit::withTrashed()->where('client_id', $this->client->id)
|
||||
->where('is_deleted', false)
|
||||
->where(function ($query) {
|
||||
@ -102,6 +109,7 @@ class ClientService
|
||||
->orderBy('created_at', 'ASC');
|
||||
|
||||
return Number::roundValue($credits->sum('balance'), $this->client->currency()->precision);
|
||||
|
||||
}
|
||||
|
||||
public function getCredits()
|
||||
@ -132,12 +140,71 @@ class ClientService
|
||||
* Generate the client statement.
|
||||
*
|
||||
* @param array $options
|
||||
* @param bool $send_email determines if we should send this statement direct to the client
|
||||
*/
|
||||
public function statement(array $options = [])
|
||||
public function statement(array $options = [], bool $send_email = false)
|
||||
{
|
||||
return (new Statement($this->client, $options))->run();
|
||||
$statement = (new Statement($this->client, $options));
|
||||
|
||||
$pdf = $statement->run();
|
||||
|
||||
if($send_email)
|
||||
return $this->emailStatement($pdf, $statement->options);
|
||||
|
||||
return $pdf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emails the statement to the client
|
||||
*
|
||||
* @param mixed $pdf The pdf blob
|
||||
* @param array $options The statement options array
|
||||
* @return void
|
||||
*/
|
||||
private function emailStatement($pdf, array $options): void
|
||||
{
|
||||
|
||||
$this->client_start_date = $this->translateDate($options['start_date'], $this->client->date_format(), $this->client->locale());
|
||||
$this->client_end_date = $this->translateDate($options['end_date'], $this->client->date_format(), $this->client->locale());
|
||||
|
||||
$email_service = new EmailService($this->buildStatementMailableData($pdf), $this->client->company);
|
||||
|
||||
$email_service->send();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and returns an EmailObject for Client Statements
|
||||
*
|
||||
* @param mixed $pdf The PDF to send
|
||||
* @return EmailObject The EmailObject to send
|
||||
*/
|
||||
public function buildStatementMailableData($pdf) :EmailObject
|
||||
{
|
||||
|
||||
$email_object = new EmailObject;
|
||||
$email_object->to = [new Address($this->client->present()->email(), $this->client->present()->name())];
|
||||
$email_object->attachments = [['file' => base64_encode($pdf), 'name' => ctrans('texts.statement') . ".pdf"]];
|
||||
$email_object->settings = $this->client->getMergedSettings();
|
||||
$email_object->company = $this->client->company;
|
||||
$email_object->client = $this->client;
|
||||
$email_object->email_template_subject = 'email_subject_statement';
|
||||
$email_object->email_template_body = 'email_template_statement';
|
||||
$email_object->variables = [
|
||||
'$client' => $this->client->present()->name(),
|
||||
'$start_date' => $this->client_start_date,
|
||||
'$end_date' => $this->client_end_date,
|
||||
];
|
||||
|
||||
return $email_object;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the client instance
|
||||
*
|
||||
* @return Client The Client Model
|
||||
*/
|
||||
public function save() :Client
|
||||
{
|
||||
$this->client->save();
|
||||
|
@ -32,23 +32,14 @@ class Statement
|
||||
{
|
||||
use PdfMakerTrait;
|
||||
|
||||
protected Client $client;
|
||||
|
||||
/**
|
||||
* @var Invoice|Payment|null
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
protected array $options;
|
||||
|
||||
protected bool $rollback = false;
|
||||
|
||||
public function __construct(Client $client, array $options)
|
||||
{
|
||||
$this->client = $client;
|
||||
|
||||
$this->options = $options;
|
||||
}
|
||||
public function __construct(protected Client $client, public array $options){}
|
||||
|
||||
public function run() :?string
|
||||
{
|
||||
|
@ -89,24 +89,27 @@ class EmailService
|
||||
if($this->company->is_disabled && !$this->override)
|
||||
return true;
|
||||
|
||||
if(Ninja::isSelfHost())
|
||||
return false;
|
||||
|
||||
/* To handle spam users we drop all emails from flagged accounts */
|
||||
if(Ninja::isHosted() && $this->company->account && $this->company->account->is_flagged)
|
||||
if($this->company->account && $this->company->account->is_flagged)
|
||||
return true;
|
||||
|
||||
/* On the hosted platform we set default contacts a @example.com email address - we shouldn't send emails to these types of addresses */
|
||||
if(Ninja::isHosted() && $this->hasValidEmails())
|
||||
if($this->hasInValidEmails())
|
||||
return true;
|
||||
|
||||
/* GMail users are uncapped */
|
||||
if(Ninja::isHosted() && in_array($this->email_object->settings->email_sending_method, ['gmail', 'office365', 'client_postmark', 'client_mailgun']))
|
||||
if(in_array($this->email_object->settings->email_sending_method, ['gmail', 'office365', 'client_postmark', 'client_mailgun']))
|
||||
return false;
|
||||
|
||||
/* On the hosted platform, if the user is over the email quotas, we do not send the email. */
|
||||
if(Ninja::isHosted() && $this->company->account && $this->company->account->emailQuotaExceeded())
|
||||
if($this->company->account && $this->company->account->emailQuotaExceeded())
|
||||
return true;
|
||||
|
||||
/* If the account is verified, we allow emails to flow */
|
||||
if(Ninja::isHosted() && $this->company->account && $this->company->account->is_verified_account) {
|
||||
if($this->company->account && $this->company->account->is_verified_account) {
|
||||
|
||||
//11-01-2022
|
||||
|
||||
@ -118,7 +121,7 @@ class EmailService
|
||||
}
|
||||
|
||||
/* On the hosted platform if the user has not verified their account we fail here - but still check what they are trying to send! */
|
||||
if(Ninja::isHosted() && $this->company->account && !$this->company->account->account_sms_verified){
|
||||
if($this->company->account && !$this->company->account->account_sms_verified){
|
||||
|
||||
if(class_exists(\Modules\Admin\Jobs\Account\EmailFilter::class))
|
||||
return (new \Modules\Admin\Jobs\Account\EmailFilter($this->email_object, $this->company))->run();
|
||||
@ -133,7 +136,7 @@ class EmailService
|
||||
return false;
|
||||
}
|
||||
|
||||
private function hasValidEmails(): bool
|
||||
private function hasInValidEmails(): bool
|
||||
{
|
||||
|
||||
foreach($this->email_object->to as $address_object)
|
||||
@ -145,6 +148,8 @@ class EmailService
|
||||
if(!str_contains($address_object->address, "@"))
|
||||
return true;
|
||||
|
||||
if($address_object->address == " ")
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
@ -11,18 +11,10 @@
|
||||
|
||||
namespace App\Services\Scheduler;
|
||||
|
||||
use App\DataMapper\EmailTemplateDefaults;
|
||||
use App\Mail\Client\ClientStatement;
|
||||
use App\Models\Client;
|
||||
use App\Models\Scheduler;
|
||||
use App\Services\Email\EmailMailable;
|
||||
use App\Services\Email\EmailObject;
|
||||
use App\Services\Email\EmailService;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\MakesDates;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Mail\Mailables\Address;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class SchedulerService
|
||||
{
|
||||
@ -54,7 +46,6 @@ class SchedulerService
|
||||
if(count($this->scheduler->parameters['clients']) >= 1)
|
||||
$query->where('id', $this->transformKeys($this->scheduler->parameters['clients']));
|
||||
|
||||
|
||||
$query->cursor()
|
||||
->each(function ($_client){
|
||||
|
||||
@ -62,10 +53,7 @@ class SchedulerService
|
||||
$statement_properties = $this->calculateStatementProperties();
|
||||
|
||||
//work out the date range
|
||||
$pdf = $_client->service()->statement($statement_properties);
|
||||
|
||||
$email_service = new EmailService($this->buildMailableData($pdf), $_client->company);
|
||||
$email_service->send();
|
||||
$pdf = $_client->service()->statement($statement_properties,true);
|
||||
|
||||
//calculate next run dates;
|
||||
|
||||
@ -77,9 +65,6 @@ class SchedulerService
|
||||
{
|
||||
$start_end = $this->calculateStartAndEndDates();
|
||||
|
||||
$this->client_start_date = $this->translateDate($start_end[0], $this->client->date_format(), $this->client->locale());
|
||||
$this->client_end_date = $this->translateDate($start_end[1], $this->client->date_format(), $this->client->locale());
|
||||
|
||||
return [
|
||||
'start_date' =>$start_end[0],
|
||||
'end_date' =>$start_end[1],
|
||||
@ -104,26 +89,7 @@ class SchedulerService
|
||||
};
|
||||
}
|
||||
|
||||
private function buildMailableData($pdf)
|
||||
{
|
||||
|
||||
$email_object = new EmailObject;
|
||||
$email_object->to = [new Address($this->client->present()->email(), $this->client->present()->name())];
|
||||
$email_object->attachments = [['file' => base64_encode($pdf), 'name' => ctrans('texts.statement') . ".pdf"]];
|
||||
$email_object->settings = $this->client->getMergedSettings();
|
||||
$email_object->company = $this->client->company;
|
||||
$email_object->client = $this->client;
|
||||
$email_object->email_template_subject = 'email_subject_statement';
|
||||
$email_object->email_template_body = 'email_template_statement';
|
||||
$email_object->variables = [
|
||||
'$client' => $this->client->present()->name(),
|
||||
'$start_date' => $this->client_start_date,
|
||||
'$end_date' => $this->client_end_date,
|
||||
];
|
||||
|
||||
return $email_object;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -35,6 +35,7 @@ use App\Models\PurchaseOrder;
|
||||
use App\Models\Quote;
|
||||
use App\Models\RecurringExpense;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Models\Scheduler;
|
||||
use App\Models\Subscription;
|
||||
use App\Models\SystemLog;
|
||||
use App\Models\Task;
|
||||
@ -106,6 +107,8 @@ class CompanyTransformer extends EntityTransformer
|
||||
'bank_integrations',
|
||||
'bank_transactions',
|
||||
'bank_transaction_rules',
|
||||
'task_schedulers',
|
||||
'schedulers',
|
||||
];
|
||||
|
||||
/**
|
||||
@ -233,6 +236,7 @@ class CompanyTransformer extends EntityTransformer
|
||||
return $this->includeCollection($company->tokens, $transformer, CompanyToken::class);
|
||||
}
|
||||
|
||||
|
||||
public function includeBankTransactions(Company $company)
|
||||
{
|
||||
$transformer = new BankTransactionTransformer($this->serializer);
|
||||
@ -241,6 +245,20 @@ class CompanyTransformer extends EntityTransformer
|
||||
}
|
||||
|
||||
|
||||
public function includeTaskSchedulers(Company $company)
|
||||
{
|
||||
$transformer = new SchedulerTransformer($this->serializer);
|
||||
|
||||
return $this->includeCollection($company->schedulers, $transformer, Scheduler::class);
|
||||
}
|
||||
|
||||
public function includeSchedulers(Company $company)
|
||||
{
|
||||
$transformer = new SchedulerTransformer($this->serializer);
|
||||
|
||||
return $this->includeCollection($company->schedulers, $transformer, Scheduler::class);
|
||||
}
|
||||
|
||||
public function includeBankTransactionRules(Company $company)
|
||||
{
|
||||
$transformer = new BankTransactionRuleTransformer($this->serializer);
|
||||
|
@ -20,12 +20,10 @@ use App\Models\GatewayType;
|
||||
use App\Models\InvoiceInvitation;
|
||||
use App\Models\QuoteInvitation;
|
||||
use App\Models\RecurringInvoiceInvitation;
|
||||
use App\Services\PdfMaker\Designs\Utilities\DesignHelpers;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Number;
|
||||
use App\Utils\Traits\AppSetup;
|
||||
use App\Utils\Traits\MakesDates;
|
||||
use App\Utils\transformTranslations;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
@ -165,6 +163,16 @@ class HtmlEngine
|
||||
$data['$view_url'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_invoice')];
|
||||
$data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->client->date_format(), $this->client->locale()) ?: ' ', 'label' => ctrans('texts.invoice_date')];
|
||||
|
||||
$data['$invoice.custom1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice1', $this->entity->custom_value1, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice1')];
|
||||
$data['$invoice.custom2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice2', $this->entity->custom_value2, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice2')];
|
||||
$data['$invoice.custom3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice3', $this->entity->custom_value3, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice3')];
|
||||
$data['$invoice.custom4'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice4', $this->entity->custom_value4, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice4')];
|
||||
|
||||
$data['$custom1'] = &$data['$invoice.custom1'];
|
||||
$data['$custom2'] = &$data['$invoice.custom2'];
|
||||
$data['$custom3'] = &$data['$invoice.custom3'];
|
||||
$data['$custom4'] = &$data['$invoice.custom4'];
|
||||
|
||||
if($this->entity->project) {
|
||||
$data['$project.name'] = ['value' => $this->entity->project->name, 'label' => ctrans('texts.project')];
|
||||
$data['$invoice.project'] = &$data['$project.name'];
|
||||
@ -204,6 +212,16 @@ class HtmlEngine
|
||||
$data['$view_url'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_quote')];
|
||||
$data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->client->date_format(), $this->client->locale()) ?: ' ', 'label' => ctrans('texts.quote_date')];
|
||||
|
||||
$data['$quote.custom1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice1', $this->entity->custom_value1, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice1')];
|
||||
$data['$quote.custom2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice2', $this->entity->custom_value2, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice2')];
|
||||
$data['$quote.custom3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice3', $this->entity->custom_value3, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice3')];
|
||||
$data['$quote.custom4'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice4', $this->entity->custom_value4, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice4')];
|
||||
|
||||
$data['$custom1'] = &$data['$quote.custom1'];
|
||||
$data['$custom2'] = &$data['$quote.custom2'];
|
||||
$data['$custom3'] = &$data['$quote.custom3'];
|
||||
$data['$custom4'] = &$data['$quote.custom4'];
|
||||
|
||||
if($this->entity->project) {
|
||||
$data['$project.name'] = ['value' => $this->entity->project->name, 'label' => ctrans('texts.project_name')];
|
||||
$data['$invoice.project'] = &$data['$project.name'];
|
||||
@ -234,6 +252,11 @@ class HtmlEngine
|
||||
$data['$credit.custom3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'credit3', $this->entity->custom_value3, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice3')];
|
||||
$data['$credit.custom4'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'credit4', $this->entity->custom_value4, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice4')];
|
||||
|
||||
$data['$custom1'] = &$data['$credit.custom1'];
|
||||
$data['$custom2'] = &$data['$credit.custom2'];
|
||||
$data['$custom3'] = &$data['$credit.custom3'];
|
||||
$data['$custom4'] = &$data['$credit.custom4'];
|
||||
|
||||
}
|
||||
|
||||
$data['$portal_url'] = ['value' => $this->invitation->getPortalLink(), 'label' =>''];
|
||||
@ -251,6 +274,7 @@ class HtmlEngine
|
||||
|
||||
$data['$invoice.subtotal'] = &$data['$subtotal'];
|
||||
|
||||
/* Do not change the order of these */
|
||||
if ($this->entity->partial > 0) {
|
||||
$data['$balance_due'] = ['value' => Number::formatMoney($this->entity->partial, $this->client) ?: ' ', 'label' => ctrans('texts.partial_due')];
|
||||
$data['$balance_due_raw'] = ['value' => $this->entity->partial, 'label' => ctrans('texts.partial_due')];
|
||||
@ -287,10 +311,8 @@ class HtmlEngine
|
||||
$data['$amount_raw'] = ['value' => $this->entity->amount, 'label' => ctrans('texts.amount')];
|
||||
}
|
||||
|
||||
// $data['$amount_in_words'] = ['value' => (new \NumberFormatter($this->client->locale(), \NumberFormatter::SPELLOUT))->format($this->entity->amount), 'label' => ''];
|
||||
// $data['$balance_in_words'] = ['value' => (new \NumberFormatter($this->client->locale(), \NumberFormatter::SPELLOUT))->format($this->entity->balance), 'label' => ''];
|
||||
/* Do not change the order of these */
|
||||
|
||||
// $data['$balance_due'] = $data['$balance_due'];
|
||||
$data['$outstanding'] = &$data['$balance_due'];
|
||||
$data['$partial_due'] = ['value' => Number::formatMoney($this->entity->partial, $this->client) ?: ' ', 'label' => ctrans('texts.partial_due')];
|
||||
$data['$partial'] = &$data['$partial_due'];
|
||||
@ -323,10 +345,7 @@ class HtmlEngine
|
||||
|
||||
$data['$user_iban'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'company1', $this->settings->custom_value1, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'company1')];
|
||||
|
||||
$data['$invoice.custom1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice1', $this->entity->custom_value1, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice1')];
|
||||
$data['$invoice.custom2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice2', $this->entity->custom_value2, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice2')];
|
||||
$data['$invoice.custom3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice3', $this->entity->custom_value3, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice3')];
|
||||
$data['$invoice.custom4'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice4', $this->entity->custom_value4, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice4')];
|
||||
|
||||
$data['$invoice.public_notes'] = ['value' => Helpers::processReservedKeywords(\nl2br($this->entity->public_notes ?: ''), $this->client) ?: '', 'label' => ctrans('texts.public_notes')];
|
||||
$data['$entity.public_notes'] = &$data['$invoice.public_notes'];
|
||||
$data['$public_notes'] = &$data['$invoice.public_notes'];
|
||||
@ -345,10 +364,7 @@ class HtmlEngine
|
||||
$data['$valid_until'] = &$data['$quote.valid_until'];
|
||||
$data['$credit_amount'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->client) ?: ' ', 'label' => ctrans('texts.credit_amount')];
|
||||
$data['$credit_balance'] = ['value' => Number::formatMoney($this->entity->balance, $this->client) ?: ' ', 'label' => ctrans('texts.credit_balance')];
|
||||
$data['$quote.custom1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice1', $this->entity->custom_value1, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice1')];
|
||||
$data['$quote.custom2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice2', $this->entity->custom_value2, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice2')];
|
||||
$data['$quote.custom3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice3', $this->entity->custom_value3, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice3')];
|
||||
$data['$quote.custom4'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice4', $this->entity->custom_value4, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice4')];
|
||||
|
||||
|
||||
$data['$credit_number'] = &$data['$number'];
|
||||
$data['$credit_no'] = &$data['$number'];
|
||||
|
@ -51,7 +51,12 @@ trait SubscriptionHooker
|
||||
|
||||
$error = json_decode($e->getResponse()->getBody()->getContents());
|
||||
|
||||
if(property_exists($error, 'message'))
|
||||
if(is_null($error)){
|
||||
nlog("empty response");
|
||||
nlog($e->getMessage());
|
||||
}
|
||||
|
||||
if($error && property_exists($error, 'message'))
|
||||
$message = $error->message;
|
||||
|
||||
return array_merge($body, ['message' => $message, 'status_code' => 500]);
|
||||
|
@ -228,6 +228,11 @@ class VendorHtmlEngine
|
||||
$data['$purchase_order.custom3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice3', $this->entity->custom_value3, $this->company) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice3')];
|
||||
$data['$purchase_order.custom4'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice4', $this->entity->custom_value4, $this->company) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice4')];
|
||||
|
||||
$data['$custom1'] = &$data['$purchase_order.custom1'];
|
||||
$data['$custom2'] = &$data['$purchase_order.custom2'];
|
||||
$data['$custom3'] = &$data['$purchase_order.custom3'];
|
||||
$data['$custom4'] = &$data['$purchase_order.custom4'];
|
||||
|
||||
$data['$vendor1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'vendor1', $this->vendor->custom_value1, $this->company) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'vendor1')];
|
||||
$data['$vendor2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'vendor2', $this->vendor->custom_value2, $this->company) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'vendor2')];
|
||||
$data['$vendor3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'vendor3', $this->vendor->custom_value3, $this->company) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'vendor3')];
|
||||
|
@ -14,8 +14,8 @@ return [
|
||||
'require_https' => env('REQUIRE_HTTPS', true),
|
||||
'app_url' => rtrim(env('APP_URL', ''), '/'),
|
||||
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
|
||||
'app_version' => '5.5.55',
|
||||
'app_tag' => '5.5.55',
|
||||
'app_version' => '5.5.56',
|
||||
'app_tag' => '5.5.56',
|
||||
'minimum_client_version' => '5.0.16',
|
||||
'terms_version' => '1.0.1',
|
||||
'api_secret' => env('API_SECRET', ''),
|
||||
|
@ -4924,7 +4924,8 @@ $LANG = array(
|
||||
'action_add_to_invoice' => 'Add To Invoice',
|
||||
'danger_zone' => 'Danger Zone',
|
||||
'import_completed' => 'Import completed',
|
||||
'client_statement_body' => 'Your statement from :start_date to :end_date is attached.'
|
||||
'client_statement_body' => 'Your statement from :start_date to :end_date is attached.',
|
||||
'email_queued' => 'Email queued',
|
||||
);
|
||||
|
||||
|
||||
|
@ -180,7 +180,7 @@
|
||||
</p>
|
||||
@endisset
|
||||
|
||||
@if(isset($company) && $company instanceof \App\Models\Company)
|
||||
@if(isset($company) && $company instanceof \App\Models\Company && $company->getSetting('show_email_footer'))
|
||||
<p style="font-size: 15px; color: #2e2e2e; font-family: 'roboto', Arial, Helvetica, sans-serif; font-weight: 500; margin-bottom:0;">
|
||||
{{ $company->present()->name() }}</p>
|
||||
<p style="font-size: 15px; color: #2e2e2e; font-family: 'roboto', Arial, Helvetica, sans-serif; font-weight: 400; margin-top: 5px;">
|
||||
|
@ -53,6 +53,71 @@ class ClientApiTest extends TestCase
|
||||
Model::reguard();
|
||||
}
|
||||
|
||||
public function testClientStatement()
|
||||
{
|
||||
|
||||
$response = null;
|
||||
|
||||
$data = [
|
||||
'client_id' => $this->client->hashed_id,
|
||||
'start_date' => '2000-01-01',
|
||||
'end_date' => '2023-01-01',
|
||||
'show_aging_table' => true,
|
||||
'show_payments_table' => true,
|
||||
'status' => 'paid',
|
||||
];
|
||||
|
||||
|
||||
try {
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->postJson('/api/v1/client_statement', $data);
|
||||
} catch (ValidationException $e) {
|
||||
$message = json_decode($e->validator->getMessageBag(), 1);
|
||||
nlog($message);
|
||||
}
|
||||
|
||||
$this->assertTrue($response->headers->get('content-type') == 'application/pdf');
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
}
|
||||
|
||||
public function testClientStatementEmail()
|
||||
{
|
||||
|
||||
$response = null;
|
||||
|
||||
$data = [
|
||||
'client_id' => $this->client->hashed_id,
|
||||
'start_date' => '2000-01-01',
|
||||
'end_date' => '2023-01-01',
|
||||
'show_aging_table' => true,
|
||||
'show_payments_table' => true,
|
||||
'status' => 'paid',
|
||||
];
|
||||
|
||||
try {
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->postJson('/api/v1/client_statement?send_email=true', $data);
|
||||
} catch (ValidationException $e) {
|
||||
$message = json_decode($e->validator->getMessageBag(), 1);
|
||||
nlog($message);
|
||||
}
|
||||
|
||||
$response->assertJson([
|
||||
'message' => ctrans('texts.email_queued'),
|
||||
]);
|
||||
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function testCsvImportRepositoryPersistance()
|
||||
{
|
||||
Client::unguard();
|
||||
|
84
tests/Feature/Email/EmailTest.php
Normal file
84
tests/Feature/Email/EmailTest.php
Normal file
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Services\Email\EmailObject;
|
||||
use App\Services\Email\EmailService;
|
||||
use App\Utils\Traits\GeneratesCounter;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||
use Tests\MockAccountData;
|
||||
use Tests\TestCase;
|
||||
use Illuminate\Mail\Mailables\Address;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @covers App\Services\Email\EmailService
|
||||
*/
|
||||
class EmailTest extends TestCase
|
||||
{
|
||||
use MakesHash;
|
||||
use GeneratesCounter;
|
||||
use MockAccountData;
|
||||
|
||||
public EmailService $email_service;
|
||||
|
||||
public EmailObject $email_object;
|
||||
|
||||
protected function setUp() :void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
if(!class_exists(\Modules\Admin\Jobs\Account\EmailFilter::class))
|
||||
$this->markTestSkipped('Skip test not needed in this environment');
|
||||
|
||||
$this->makeTestData();
|
||||
|
||||
$this->email_object = new EmailObject();
|
||||
$this->email_object->to = [new Address("testing@gmail.com", "Cool Name")];
|
||||
$this->email_object->attachments = [];
|
||||
$this->email_object->settings = $this->client->getMergedSettings();
|
||||
$this->email_object->company = $this->client->company;
|
||||
$this->email_object->client = $this->client;
|
||||
$this->email_object->email_template_subject = 'email_subject_statement';
|
||||
$this->email_object->email_template_body = 'email_template_statement';
|
||||
$this->email_object->variables = [
|
||||
'$client' => $this->client->present()->name(),
|
||||
'$start_date' => '2022-01-01',
|
||||
'$end_date' => '2023-01-01',
|
||||
];
|
||||
|
||||
$this->email_service = new EmailService($this->email_object, $this->company);
|
||||
|
||||
}
|
||||
|
||||
public function testPreFlightChecksHosted()
|
||||
{
|
||||
|
||||
config(['ninja.environment' => 'hosted']);
|
||||
|
||||
$this->assertFalse($this->email_service->preFlightChecksFail());
|
||||
|
||||
}
|
||||
|
||||
public function testPreFlightChecksSelfHost()
|
||||
{
|
||||
|
||||
config(['ninja.environment' => 'selfhost']);
|
||||
|
||||
$this->assertFalse($this->email_service->preFlightChecksFail());
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user