mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-06-23 20:00:33 -04:00
Merge branch 'v5-develop' into bank_rules
This commit is contained in:
commit
f6f659521f
61
README.md
61
README.md
@ -18,18 +18,33 @@ Just make sure to add the `invoice-ninja` tag to your question.
|
|||||||
|
|
||||||
Version 5 of Invoice Ninja is here! We've taken the best parts of version 4 and bolted on all of the most requested features to produce a invoicing application like no other.
|
Version 5 of Invoice Ninja is here! We've taken the best parts of version 4 and bolted on all of the most requested features to produce a invoicing application like no other.
|
||||||
|
|
||||||
The new interface has a lot more functionality so it isn't a carbon copy of v4, but once you get used to the new layout and functionality we are sure you will love it!
|
All Pro and Enterprise features from the hosted app are included in the open-code. We offer a $30 per year white-label license to remove the Invoice Ninja branding from client facing parts of the app.
|
||||||
|
|
||||||
## Referral Program
|
* [Videos](https://www.youtube.com/@appinvoiceninja)
|
||||||
* Earn 50% of Pro & Enterprise Plans up to 4 years - [Learn more](https://www.invoiceninja.com/referral-program/)
|
* [API Documentation](https://app.swaggerhub.com/apis/invoiceninja/invoiceninja)
|
||||||
|
* [APP Documentation](https://invoiceninja.github.io/)
|
||||||
|
* [Support Forum](https://forum.invoiceninja.com)
|
||||||
|
* [StackOverflow](https://stackoverflow.com/tags/invoice-ninja/)
|
||||||
|
|
||||||
|
## Mobile Apps
|
||||||
|
* [iPhone](https://apps.apple.com/app/id1503970375?platform=iphone)
|
||||||
|
* [Android](https://play.google.com/store/apps/details?id=com.invoiceninja.app)
|
||||||
|
|
||||||
|
## Desktop Apps
|
||||||
|
* [macOS](https://apps.apple.com/app/id1503970375?platform=mac)
|
||||||
|
* [Windows](https://microsoft.com/en-us/p/invoice-ninja/9n3f2bbcfdr6)
|
||||||
|
* [Linux](https://snapcraft.io/invoiceninja)
|
||||||
|
|
||||||
|
|
||||||
|
## Installation Options
|
||||||
|
* [Docker File](https://hub.docker.com/r/invoiceninja/invoiceninja/)
|
||||||
|
* [Cloudron](https://cloudron.io/store/com.invoiceninja.cloudronapp.html)
|
||||||
|
* [Softaculous](https://www.softaculous.com/apps/ecommerce/Invoice_Ninja)
|
||||||
|
|
||||||
## Recommended Providers
|
## Recommended Providers
|
||||||
* [Stripe](https://stripe.com/)
|
* [Stripe](https://stripe.com/)
|
||||||
* [Postmark](https://postmarkapp.com/)
|
* [Postmark](https://postmarkapp.com/)
|
||||||
|
|
||||||
## Development
|
|
||||||
* [API Documentation](https://app.swaggerhub.com/apis/invoiceninja/invoiceninja)
|
|
||||||
* [APP Documentation](https://invoiceninja.github.io/)
|
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
@ -67,43 +82,11 @@ user: user@example.com
|
|||||||
pass: password
|
pass: password
|
||||||
```
|
```
|
||||||
|
|
||||||
## Contribution guide.
|
|
||||||
|
|
||||||
Code Style to follow [PSR-2](https://www.php-fig.org/psr/psr-2/) standards.
|
|
||||||
|
|
||||||
All methods names to be in CamelCase
|
|
||||||
|
|
||||||
All variables names to be in snake_case
|
|
||||||
|
|
||||||
Where practical code should be strongly typed, ie your methods must return a type ie
|
|
||||||
|
|
||||||
`public function doThis() : void`
|
|
||||||
|
|
||||||
PHP >= 7.3 allows the return type Nullable so there should be no circumstance a type cannot be return by using the following:
|
|
||||||
|
|
||||||
`public function doThat() ?:string`
|
|
||||||
|
|
||||||
To improve chances of PRs being merged please include tests to ensure your code works well and integrates with the rest of the project.
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
API documentation is hosted using Swagger and can be found [HERE](https://app.swaggerhub.com/apis/invoiceninja/invoiceninja)
|
|
||||||
|
|
||||||
Installation, Configuration and Troubleshooting documentation can be found [HERE] (https://invoiceninja.github.io)
|
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
* [Hillel Coren](https://hillelcoren.com/)
|
* [Hillel Coren](https://hillelcoren.com/)
|
||||||
* [David Bomba](https://github.com/turbo124)
|
* [David Bomba](https://github.com/turbo124)
|
||||||
* [All contributors](https://github.com/invoiceninja/invoiceninja/graphs/contributors)
|
|
||||||
|
|
||||||
**Special thanks to:**
|
|
||||||
* [Holger Lösken](https://github.com/codedge) - [codedge](http://codedge.de)
|
|
||||||
* [Samuel Laulhau](https://github.com/lalop) - [Lalop](http://lalop.co/)
|
|
||||||
* [Alexander Vanderveen](https://blog.technicallycomputers.ca/) - [Technically Computers](https://www.technicallycomputers.ca/)
|
|
||||||
* [Efthymios Sarmpanis](https://github.com/esarbanis)
|
|
||||||
* [Gianfranco Gasbarri](https://github.com/gincos)
|
|
||||||
* [Clemens Mol](https://github.com/clemensmol)
|
|
||||||
* [Benjamin Beganović](https://github.com/beganovich)
|
* [Benjamin Beganović](https://github.com/beganovich)
|
||||||
|
* [All contributors](https://github.com/invoiceninja/invoiceninja/graphs/contributors)
|
||||||
|
|
||||||
## Security
|
## Security
|
||||||
|
|
||||||
|
@ -1 +1 @@
|
|||||||
5.5.38
|
5.5.41
|
@ -175,7 +175,7 @@ class SendRemindersCron extends Command
|
|||||||
/**Refresh Invoice values*/
|
/**Refresh Invoice values*/
|
||||||
$invoice->calc()->getInvoice()->save();
|
$invoice->calc()->getInvoice()->save();
|
||||||
$invoice->fresh();
|
$invoice->fresh();
|
||||||
$invoice->service()->deletePdf();
|
$invoice->service()->deletePdf()->save();
|
||||||
|
|
||||||
/* Refresh the client here to ensure the balance is fresh */
|
/* Refresh the client here to ensure the balance is fresh */
|
||||||
$client = $invoice->client;
|
$client = $invoice->client;
|
||||||
|
@ -22,6 +22,7 @@ use App\Jobs\Ninja\CompanySizeCheck;
|
|||||||
use App\Jobs\Ninja\QueueSize;
|
use App\Jobs\Ninja\QueueSize;
|
||||||
use App\Jobs\Ninja\SystemMaintenance;
|
use App\Jobs\Ninja\SystemMaintenance;
|
||||||
use App\Jobs\Ninja\TaskScheduler;
|
use App\Jobs\Ninja\TaskScheduler;
|
||||||
|
use App\Jobs\Quote\QuoteCheckExpired;
|
||||||
use App\Jobs\Util\DiskCleanup;
|
use App\Jobs\Util\DiskCleanup;
|
||||||
use App\Jobs\Util\ReminderJob;
|
use App\Jobs\Util\ReminderJob;
|
||||||
use App\Jobs\Util\SchedulerCheck;
|
use App\Jobs\Util\SchedulerCheck;
|
||||||
@ -70,6 +71,9 @@ class Kernel extends ConsoleKernel
|
|||||||
/* Sends recurring invoices*/
|
/* Sends recurring invoices*/
|
||||||
$schedule->job(new RecurringExpensesCron)->dailyAt('00:10')->withoutOverlapping();
|
$schedule->job(new RecurringExpensesCron)->dailyAt('00:10')->withoutOverlapping();
|
||||||
|
|
||||||
|
/* Fires notifications for expired Quotes */
|
||||||
|
$schedule->job(new QuoteCheckExpired)->dailyAt('05:00')->withoutOverlapping();
|
||||||
|
|
||||||
/* Performs auto billing */
|
/* Performs auto billing */
|
||||||
$schedule->job(new AutoBillCron)->dailyAt('06:00')->withoutOverlapping();
|
$schedule->job(new AutoBillCron)->dailyAt('06:00')->withoutOverlapping();
|
||||||
|
|
||||||
|
@ -55,6 +55,55 @@ class BankTransactionFilters extends QueryFilters
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter based on client status.
|
||||||
|
*
|
||||||
|
* Statuses we need to handle
|
||||||
|
* - all
|
||||||
|
* - unmatched
|
||||||
|
* - matched
|
||||||
|
* - converted
|
||||||
|
* - deposits
|
||||||
|
* - withdrawals
|
||||||
|
*
|
||||||
|
* @return Builder
|
||||||
|
*/
|
||||||
|
public function client_status(string $value = '') :Builder
|
||||||
|
{
|
||||||
|
if (strlen($value) == 0) {
|
||||||
|
return $this->builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
$status_parameters = explode(',', $value);
|
||||||
|
|
||||||
|
if (in_array('all', $status_parameters)) {
|
||||||
|
return $this->builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array('unmatched', $status_parameters)) {
|
||||||
|
$this->builder->where('status_id', BankTransaction::STATUS_UNMATCHED);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array('matched', $status_parameters)) {
|
||||||
|
$this->builder->where('status_id', BankTransaction::STATUS_MATCHED);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array('converted', $status_parameters)) {
|
||||||
|
$this->builder->where('status_id', BankTransaction::STATUS_CONVERTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array('deposits', $status_parameters)) {
|
||||||
|
$this->builder->where('base_type', 'CREDIT');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array('withdrawals', $status_parameters)) {
|
||||||
|
$this->builder->where('base_type', 'DEBIT');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->builder;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filters the list based on the status
|
* Filters the list based on the status
|
||||||
* archived, active, deleted.
|
* archived, active, deleted.
|
||||||
|
@ -44,6 +44,55 @@ class ExpenseFilters extends QueryFilters
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter based on client status.
|
||||||
|
*
|
||||||
|
* Statuses we need to handle
|
||||||
|
* - all
|
||||||
|
* - logged
|
||||||
|
* - pending
|
||||||
|
* - invoiced
|
||||||
|
* - paid
|
||||||
|
* - unpaid
|
||||||
|
*
|
||||||
|
* @return Builder
|
||||||
|
*/
|
||||||
|
public function client_status(string $value = '') :Builder
|
||||||
|
{
|
||||||
|
if (strlen($value) == 0) {
|
||||||
|
return $this->builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
$status_parameters = explode(',', $value);
|
||||||
|
|
||||||
|
if (in_array('all', $status_parameters)) {
|
||||||
|
return $this->builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array('logged', $status_parameters)) {
|
||||||
|
$this->builder->where('amount', '>', 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array('pending', $status_parameters)) {
|
||||||
|
$this->builder->whereNull('invoice_id')->whereNotNull('payment_date');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array('invoiced', $status_parameters)) {
|
||||||
|
$this->builder->whereNotNull('invoice_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array('paid', $status_parameters)) {
|
||||||
|
$this->builder->whereNotNull('payment_date');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array('unpaid', $status_parameters)) {
|
||||||
|
$this->builder->whereNull('payment_date');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filters the list based on the status
|
* Filters the list based on the status
|
||||||
* archived, active, deleted.
|
* archived, active, deleted.
|
||||||
|
@ -17,19 +17,20 @@ use Illuminate\Database\Eloquent\Builder;
|
|||||||
|
|
||||||
class PurchaseOrderFilters extends QueryFilters
|
class PurchaseOrderFilters extends QueryFilters
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter based on client status.
|
* Filter based on client status.
|
||||||
*
|
*
|
||||||
* Statuses we need to handle
|
* Statuses we need to handle
|
||||||
* - all
|
* - all
|
||||||
* - paid
|
* - draft
|
||||||
* - unpaid
|
* - sent
|
||||||
* - overdue
|
* - accepted
|
||||||
* - reversed
|
* - cancelled
|
||||||
*
|
*
|
||||||
* @return Builder
|
* @return Builder
|
||||||
*/
|
*/
|
||||||
public function credit_status(string $value = '') :Builder
|
public function client_status(string $value = '') :Builder
|
||||||
{
|
{
|
||||||
if (strlen($value) == 0) {
|
if (strlen($value) == 0) {
|
||||||
return $this->builder;
|
return $this->builder;
|
||||||
@ -45,16 +46,17 @@ class PurchaseOrderFilters extends QueryFilters
|
|||||||
$this->builder->where('status_id', PurchaseOrder::STATUS_DRAFT);
|
$this->builder->where('status_id', PurchaseOrder::STATUS_DRAFT);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (in_array('partial', $status_parameters)) {
|
if (in_array('sent', $status_parameters)) {
|
||||||
$this->builder->where('status_id', PurchaseOrder::STATUS_PARTIAL);
|
$this->builder->where('status_id', PurchaseOrder::STATUS_SENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (in_array('applied', $status_parameters)) {
|
if (in_array('accepted', $status_parameters)) {
|
||||||
$this->builder->where('status_id', PurchaseOrder::STATUS_APPLIED);
|
$this->builder->where('status_id', PurchaseOrder::STATUS_ACCEPTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
//->where('due_date', '>', Carbon::now())
|
if (in_array('cancelled', $status_parameters)) {
|
||||||
//->orWhere('partial_due_date', '>', Carbon::now());
|
$this->builder->where('status_id', PurchaseOrder::STATUS_CANCELLED);
|
||||||
|
}
|
||||||
|
|
||||||
return $this->builder;
|
return $this->builder;
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
namespace App\Filters;
|
namespace App\Filters;
|
||||||
|
|
||||||
|
use App\Models\Quote;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
|
||||||
@ -41,6 +42,51 @@ class QuoteFilters extends QueryFilters
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter based on client status.
|
||||||
|
*
|
||||||
|
* Statuses we need to handle
|
||||||
|
* - all
|
||||||
|
* - active
|
||||||
|
* - paused
|
||||||
|
* - completed
|
||||||
|
*
|
||||||
|
* @param string client_status The invoice status as seen by the client
|
||||||
|
* @return Builder
|
||||||
|
*/
|
||||||
|
public function client_status(string $value = '') :Builder
|
||||||
|
{
|
||||||
|
if (strlen($value) == 0) {
|
||||||
|
return $this->builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
$status_parameters = explode(',', $value);
|
||||||
|
|
||||||
|
if (in_array('all', $status_parameters)) {
|
||||||
|
return $this->builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array('draft', $status_parameters)) {
|
||||||
|
$this->builder->where('status_id', Quote::STATUS_DRAFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array('sent', $status_parameters)) {
|
||||||
|
$this->builder->where('status_id', Quote::STATUS_SENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array('approved', $status_parameters)) {
|
||||||
|
$this->builder->where('status_id', Quote::STATUS_APPROVED);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array('expired', $status_parameters)) {
|
||||||
|
$this->builder->where('status_id', Quote::STATUS_SENT)
|
||||||
|
->where('due_date', '<=', now()->toDateString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filters the list based on the status
|
* Filters the list based on the status
|
||||||
* archived, active, deleted.
|
* archived, active, deleted.
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
namespace App\Filters;
|
namespace App\Filters;
|
||||||
|
|
||||||
|
use App\Models\RecurringInvoice;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
|
||||||
@ -40,6 +41,46 @@ class RecurringInvoiceFilters extends QueryFilters
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter based on client status.
|
||||||
|
*
|
||||||
|
* Statuses we need to handle
|
||||||
|
* - all
|
||||||
|
* - active
|
||||||
|
* - paused
|
||||||
|
* - completed
|
||||||
|
*
|
||||||
|
* @param string client_status The invoice status as seen by the client
|
||||||
|
* @return Builder
|
||||||
|
*/
|
||||||
|
public function client_status(string $value = '') :Builder
|
||||||
|
{
|
||||||
|
if (strlen($value) == 0) {
|
||||||
|
return $this->builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
$status_parameters = explode(',', $value);
|
||||||
|
|
||||||
|
if (in_array('all', $status_parameters)) {
|
||||||
|
return $this->builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array('active', $status_parameters)) {
|
||||||
|
$this->builder->where('status_id', RecurringInvoice::STATUS_ACTIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array('paused', $status_parameters)) {
|
||||||
|
$this->builder->where('status_id', RecurringInvoice::STATUS_PAUSED);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array('completed', $status_parameters)) {
|
||||||
|
$this->builder->where('status_id', RecurringInvoice::STATUS_COMPLETED);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filters the list based on the status
|
* Filters the list based on the status
|
||||||
* archived, active, deleted.
|
* archived, active, deleted.
|
||||||
|
@ -41,6 +41,37 @@ class TaskFilters extends QueryFilters
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter based on client status.
|
||||||
|
*
|
||||||
|
* Statuses we need to handle
|
||||||
|
* - all
|
||||||
|
* - invoiced
|
||||||
|
*
|
||||||
|
* @param string client_status The invoice status as seen by the client
|
||||||
|
* @return Builder
|
||||||
|
*/
|
||||||
|
public function client_status(string $value = '') :Builder
|
||||||
|
{
|
||||||
|
if (strlen($value) == 0) {
|
||||||
|
return $this->builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
$status_parameters = explode(',', $value);
|
||||||
|
|
||||||
|
if (in_array('all', $status_parameters)) {
|
||||||
|
return $this->builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array('invoiced', $status_parameters)) {
|
||||||
|
$this->builder->whereNotNull('invoice_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filters the list based on the status
|
* Filters the list based on the status
|
||||||
* archived, active, deleted.
|
* archived, active, deleted.
|
||||||
|
@ -112,7 +112,35 @@ class SwissQrGenerator
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$invoice_number = iconv("UTF-8", "ASCII", $this->invoice->number);
|
$tempInvoiceNumber = $this->invoice->number;
|
||||||
|
$tempInvoiceNumber = preg_replace('/[^A-Za-z0-9]/', '', $tempInvoiceNumber);
|
||||||
|
$tempInvoiceNumber = substr($tempInvoiceNumber, 1);
|
||||||
|
|
||||||
|
$calcInvoiceNumber = "";
|
||||||
|
$array = str_split($tempInvoiceNumber);
|
||||||
|
foreach($array as $char)
|
||||||
|
{
|
||||||
|
if (is_numeric($char))
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if ($char)
|
||||||
|
{
|
||||||
|
$char = strtolower($char);
|
||||||
|
$char = ord($char) - 96;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$calcInvoiceNumber .= $char;
|
||||||
|
}
|
||||||
|
|
||||||
|
$invoice_number = $calcInvoiceNumber;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(strlen($this->company->present()->besr_id()) > 1)
|
if(strlen($this->company->present()->besr_id()) > 1)
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Models\Account;
|
use App\Models\Account;
|
||||||
|
use App\Models\BankTransaction;
|
||||||
use App\Models\Company;
|
use App\Models\Company;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Transformers\ArraySerializer;
|
use App\Transformers\ArraySerializer;
|
||||||
@ -819,12 +820,15 @@ class BaseController extends Controller
|
|||||||
// 10-01-2022 need to ensure we snake case properly here to ensure permissions work as expected
|
// 10-01-2022 need to ensure we snake case properly here to ensure permissions work as expected
|
||||||
// 28-03-2022 this is definitely correct here, do not append _ to the view, it resolved correctly when snake cased
|
// 28-03-2022 this is definitely correct here, do not append _ to the view, it resolved correctly when snake cased
|
||||||
if (auth()->user() && ! auth()->user()->hasPermission('view'.lcfirst(class_basename(Str::snake($this->entity_type))))) {
|
if (auth()->user() && ! auth()->user()->hasPermission('view'.lcfirst(class_basename(Str::snake($this->entity_type))))) {
|
||||||
|
|
||||||
//06-10-2022 - some entities do not have assigned_user_id - this becomes an issue when we have a large company and low permission users
|
//06-10-2022 - some entities do not have assigned_user_id - this becomes an issue when we have a large company and low permission users
|
||||||
if(lcfirst(class_basename(Str::snake($this->entity_type))) == 'user')
|
if(lcfirst(class_basename(Str::snake($this->entity_type))) == 'user')
|
||||||
$query->where('id', auth()->user()->id);
|
$query->where('id', auth()->user()->id);
|
||||||
elseif(in_array(lcfirst(class_basename(Str::snake($this->entity_type))),['design','group_setting','payment_term','bank_transaction'])){
|
elseif($this->entity_type == BankTransaction::class){ //table without assigned_user_id
|
||||||
|
$query->where('user_id', '=', auth()->user()->id);
|
||||||
|
}
|
||||||
|
elseif(in_array(lcfirst(class_basename(Str::snake($this->entity_type))),['design','group_setting','payment_term'])){
|
||||||
//need to pass these back regardless
|
//need to pass these back regardless
|
||||||
|
nlog($this->entity_type);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
$query->where('user_id', '=', auth()->user()->id)->orWhere('assigned_user_id', auth()->user()->id);
|
$query->where('user_id', '=', auth()->user()->id)->orWhere('assigned_user_id', auth()->user()->id);
|
||||||
@ -996,42 +1000,6 @@ class BaseController extends Controller
|
|||||||
return redirect('/setup');
|
return redirect('/setup');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function reactCatch()
|
|
||||||
{
|
|
||||||
|
|
||||||
if ((bool) $this->checkAppSetup() !== false && $account = Account::first()) {
|
|
||||||
if (config('ninja.require_https') && ! request()->isSecure()) {
|
|
||||||
return redirect()->secure(request()->getRequestUri());
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = [];
|
|
||||||
|
|
||||||
//pass report errors bool to front end
|
|
||||||
$data['report_errors'] = Ninja::isSelfHost() ? $account->report_errors : true;
|
|
||||||
|
|
||||||
//pass referral code to front end
|
|
||||||
$data['rc'] = request()->has('rc') ? request()->input('rc') : '';
|
|
||||||
$data['build'] = request()->has('build') ? request()->input('build') : '';
|
|
||||||
$data['login'] = request()->has('login') ? request()->input('login') : 'false';
|
|
||||||
$data['signup'] = request()->has('signup') ? request()->input('signup') : 'false';
|
|
||||||
|
|
||||||
$data['user_agent'] = request()->server('HTTP_USER_AGENT');
|
|
||||||
|
|
||||||
$data['path'] = $this->setBuild();
|
|
||||||
|
|
||||||
$this->buildCache();
|
|
||||||
|
|
||||||
if (Ninja::isSelfHost() && $account->set_react_as_default_ap) {
|
|
||||||
return view('react.index', $data);
|
|
||||||
} else {
|
|
||||||
abort('page not found', 404);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return redirect('/setup');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private function setBuild()
|
private function setBuild()
|
||||||
{
|
{
|
||||||
$build = '';
|
$build = '';
|
||||||
|
@ -165,8 +165,11 @@ class TwilioController extends BaseController
|
|||||||
|
|
||||||
if($verification_check->status == 'approved'){
|
if($verification_check->status == 'approved'){
|
||||||
|
|
||||||
if($request->query('validate_only') == 'true')
|
if($request->query('validate_only') == 'true'){
|
||||||
|
$user->verified_phone_number = true;
|
||||||
|
$user->save();
|
||||||
return response()->json(['message' => 'SMS verified'], 200);
|
return response()->json(['message' => 'SMS verified'], 200);
|
||||||
|
}
|
||||||
|
|
||||||
$user->google_2fa_secret = '';
|
$user->google_2fa_secret = '';
|
||||||
$user->sms_verification_code = '';
|
$user->sms_verification_code = '';
|
||||||
|
@ -26,7 +26,7 @@ class UpdateAccountRequest extends Request
|
|||||||
*/
|
*/
|
||||||
public function authorize()
|
public function authorize()
|
||||||
{
|
{
|
||||||
return (auth()->user()->isAdmin() || auth()->user()->isOwner()) && ($this->account->id == auth()->user()->account_id);
|
return (auth()->user()->isAdmin() || auth()->user()->isOwner()) && ($this->account->id == auth()->user()->token()->account_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -31,12 +31,12 @@ class MatchBankTransactionRequest extends Request
|
|||||||
|
|
||||||
$rules = [
|
$rules = [
|
||||||
'transactions' => 'bail|array',
|
'transactions' => 'bail|array',
|
||||||
'transactions.*.id' => 'bail|required',
|
|
||||||
'transactions.*.invoice_ids' => 'nullable|string|sometimes',
|
'transactions.*.invoice_ids' => 'nullable|string|sometimes',
|
||||||
];
|
];
|
||||||
|
|
||||||
$rules['transactions.*.ninja_category_id'] = 'bail|nullable|sometimes|exists:expense_categories,id,company_id,'.auth()->user()->company()->id.',is_deleted,0';
|
$rules['transactions.*.ninja_category_id'] = 'bail|nullable|sometimes|exists:expense_categories,id,company_id,'.auth()->user()->company()->id.',is_deleted,0';
|
||||||
$rules['transactions.*.vendor_id'] = 'bail|sometimes|exists:vendors,id,company_id,'.auth()->user()->company()->id.',is_deleted,0';
|
$rules['transactions.*.vendor_id'] = 'bail|sometimes|exists:vendors,id,company_id,'.auth()->user()->company()->id.',is_deleted,0';
|
||||||
|
$rules['transactions.*.id'] = 'bail|required|exists:bank_transactions,id,company_id,'.auth()->user()->company()->id.',is_deleted,0';
|
||||||
|
|
||||||
return $rules;
|
return $rules;
|
||||||
|
|
||||||
|
@ -34,6 +34,9 @@ class StoreBankTransactionRequest extends Request
|
|||||||
|
|
||||||
$rules = [];
|
$rules = [];
|
||||||
|
|
||||||
|
if(isset($this->bank_integration_id))
|
||||||
|
$rules['bank_integration_id'] = 'bail|required|exists:bank_integrations,id,company_id,'.auth()->user()->company()->id.',is_deleted,0';
|
||||||
|
|
||||||
return $rules;
|
return $rules;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,6 +45,9 @@ class UpdateBankTransactionRequest extends Request
|
|||||||
if(isset($this->expense_id))
|
if(isset($this->expense_id))
|
||||||
$rules['expense_id'] = 'bail|required|exists:expenses,id,company_id,'.auth()->user()->company()->id.',is_deleted,0';
|
$rules['expense_id'] = 'bail|required|exists:expenses,id,company_id,'.auth()->user()->company()->id.',is_deleted,0';
|
||||||
|
|
||||||
|
if(isset($this->bank_integration_id))
|
||||||
|
$rules['bank_integration_id'] = 'bail|required|exists:bank_integrations,id,company_id,'.auth()->user()->company()->id.',is_deleted,0';
|
||||||
|
|
||||||
|
|
||||||
return $rules;
|
return $rules;
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,14 @@ class UpdateCompanyRequest extends Request
|
|||||||
{
|
{
|
||||||
use MakesHash;
|
use MakesHash;
|
||||||
|
|
||||||
|
private array $protected_input = [
|
||||||
|
'client_portal_privacy_policy',
|
||||||
|
'client_portal_terms',
|
||||||
|
'portal_custom_footer',
|
||||||
|
'portal_custom_css',
|
||||||
|
'portal_custom_head'
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if the user is authorized to make this request.
|
* Determine if the user is authorized to make this request.
|
||||||
*
|
*
|
||||||
@ -32,6 +40,8 @@ class UpdateCompanyRequest extends Request
|
|||||||
return auth()->user()->can('edit', $this->company);
|
return auth()->user()->can('edit', $this->company);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public function rules()
|
public function rules()
|
||||||
{
|
{
|
||||||
$input = $this->all();
|
$input = $this->all();
|
||||||
@ -90,6 +100,14 @@ class UpdateCompanyRequest extends Request
|
|||||||
{
|
{
|
||||||
$account = $this->company->account;
|
$account = $this->company->account;
|
||||||
|
|
||||||
|
if(Ninja::isHosted())
|
||||||
|
{
|
||||||
|
foreach($this->protected_input as $protected_var)
|
||||||
|
{
|
||||||
|
$settings[$protected_var] = str_replace("script", "", $settings[$protected_var]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (! $account->isFreeHostedClient()) {
|
if (! $account->isFreeHostedClient()) {
|
||||||
return $settings;
|
return $settings;
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,8 @@ class BlackListRule implements Rule
|
|||||||
'superhostforumla.com',
|
'superhostforumla.com',
|
||||||
'wnpop.com',
|
'wnpop.com',
|
||||||
'dataservices.space',
|
'dataservices.space',
|
||||||
|
'karenkey.com',
|
||||||
|
'sharklasers.com',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -87,7 +87,7 @@ class Csv extends BaseImport implements ImportInterface
|
|||||||
|
|
||||||
foreach($data as $key => $value)
|
foreach($data as $key => $value)
|
||||||
{
|
{
|
||||||
$data[$key]['bank.bank_integration_id'] = $this->decodePrimaryKey($this->request['bank_integration_id']);
|
$data[$key]['transaction.bank_integration_id'] = $this->decodePrimaryKey($this->request['bank_integration_id']);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -31,17 +31,17 @@ class BankTransformer extends BaseTransformer
|
|||||||
$now = now();
|
$now = now();
|
||||||
|
|
||||||
$transformed = [
|
$transformed = [
|
||||||
'bank_integration_id' => $transaction['bank.bank_integration_id'],
|
'bank_integration_id' => $transaction['transaction.bank_integration_id'],
|
||||||
'transaction_id' => $this->getNumber($transaction,'bank.transaction_id'),
|
'transaction_id' => $this->getNumber($transaction,'transaction.transaction_id'),
|
||||||
'amount' => abs($this->getFloat($transaction, 'bank.amount')),
|
'amount' => abs($this->getFloat($transaction, 'transaction.amount')),
|
||||||
'currency_id' => $this->getCurrencyByCode($transaction, 'bank.currency'),
|
'currency_id' => $this->getCurrencyByCode($transaction, 'transaction.currency'),
|
||||||
'account_type' => strlen($this->getString($transaction, 'bank.account_type')) > 1 ? $this->getString($transaction, 'bank.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, 'bank.category_id') > 0 ? $this->getNumber($transaction, 'bank.category_id') : null,
|
'category_id' => $this->getNumber($transaction, 'transaction.category_id') > 0 ? $this->getNumber($transaction, 'transaction.category_id') : null,
|
||||||
'category_type' => $this->getString($transaction, 'bank.category_type'),
|
'category_type' => $this->getString($transaction, 'transaction.category_type'),
|
||||||
'date' => array_key_exists('bank.date', $transaction) ? $this->parseDate($transaction['bank.date'])
|
'date' => array_key_exists('transaction.date', $transaction) ? $this->parseDate($transaction['transaction.date'])
|
||||||
: now()->format('Y-m-d'),
|
: now()->format('Y-m-d'),
|
||||||
'bank_account_id' => array_key_exists('bank.bank_account_id', $transaction) ? $transaction['bank.bank_account_id'] : 0,
|
'bank_account_id' => array_key_exists('transaction.bank_account_id', $transaction) ? $transaction['transaction.bank_account_id'] : 0,
|
||||||
'description' => array_key_exists('bank.description', $transaction) ? $transaction['bank.description'] : '',
|
'description' => array_key_exists('transaction.description', $transaction) ? $transaction['transaction.description'] : '',
|
||||||
'base_type' => $this->calculateType($transaction),
|
'base_type' => $this->calculateType($transaction),
|
||||||
'created_at' => $now,
|
'created_at' => $now,
|
||||||
'updated_at' => $now,
|
'updated_at' => $now,
|
||||||
@ -56,22 +56,22 @@ class BankTransformer extends BaseTransformer
|
|||||||
private function calculateType($transaction)
|
private function calculateType($transaction)
|
||||||
{
|
{
|
||||||
|
|
||||||
if(array_key_exists('bank.base_type', $transaction) && ($transaction['bank.base_type'] == 'CREDIT') || strtolower($transaction['bank.base_type']) == 'deposit')
|
if(array_key_exists('transaction.base_type', $transaction) && (($transaction['transaction.base_type'] == 'CREDIT') || strtolower($transaction['transaction.base_type']) == 'deposit'))
|
||||||
return 'CREDIT';
|
return 'CREDIT';
|
||||||
|
|
||||||
if(array_key_exists('bank.base_type', $transaction) && ($transaction['bank.base_type'] == 'DEBIT') || strtolower($transaction['bank.bank_type']) == 'withdrawal')
|
if(array_key_exists('transaction.base_type', $transaction) && (($transaction['transaction.base_type'] == 'DEBIT') || strtolower($transaction['transaction.bank_type']) == 'withdrawal'))
|
||||||
return 'DEBIT';
|
return 'DEBIT';
|
||||||
|
|
||||||
if(array_key_exists('bank.category_id', $transaction))
|
if(array_key_exists('transaction.category_id', $transaction))
|
||||||
return 'DEBIT';
|
return 'DEBIT';
|
||||||
|
|
||||||
if(array_key_exists('bank.category_type', $transaction) && $transaction['bank.category_type'] == 'Income')
|
if(array_key_exists('transaction.category_type', $transaction) && $transaction['transaction.category_type'] == 'Income')
|
||||||
return 'CREDIT';
|
return 'CREDIT';
|
||||||
|
|
||||||
if(array_key_exists('bank.category_type', $transaction))
|
if(array_key_exists('transaction.category_type', $transaction))
|
||||||
return 'DEBIT';
|
return 'DEBIT';
|
||||||
|
|
||||||
if(array_key_exists('bank.amount', $transaction) && is_numeric($transaction['bank.amount']) && $transaction['bank.amount'] > 0)
|
if(array_key_exists('transaction.amount', $transaction) && is_numeric($transaction['transaction.amount']) && $transaction['transaction.amount'] > 0)
|
||||||
return 'CREDIT';
|
return 'CREDIT';
|
||||||
|
|
||||||
return 'DEBIT';
|
return 'DEBIT';
|
||||||
|
@ -160,6 +160,9 @@ class MatchBankTransactions implements ShouldQueue
|
|||||||
{
|
{
|
||||||
$this->bt = BankTransaction::find($input['id']);
|
$this->bt = BankTransaction::find($input['id']);
|
||||||
|
|
||||||
|
if(!$this->bt || $this->bt->status_id == BankTransaction::STATUS_CONVERTED)
|
||||||
|
return $this;
|
||||||
|
|
||||||
$_invoices = Invoice::withTrashed()->find($this->getInvoices($input['invoice_ids']));
|
$_invoices = Invoice::withTrashed()->find($this->getInvoices($input['invoice_ids']));
|
||||||
|
|
||||||
$amount = $this->bt->amount;
|
$amount = $this->bt->amount;
|
||||||
@ -180,6 +183,10 @@ class MatchBankTransactions implements ShouldQueue
|
|||||||
//if there is a category id, pull it from Yodlee and insert - or just reuse!!
|
//if there is a category id, pull it from Yodlee and insert - or just reuse!!
|
||||||
$this->bt = BankTransaction::find($input['id']);
|
$this->bt = BankTransaction::find($input['id']);
|
||||||
|
|
||||||
|
if(!$this->bt || $this->bt->status_id == BankTransaction::STATUS_CONVERTED)
|
||||||
|
return $this;
|
||||||
|
|
||||||
|
|
||||||
$expense = ExpenseFactory::create($this->bt->company_id, $this->bt->user_id);
|
$expense = ExpenseFactory::create($this->bt->company_id, $this->bt->user_id);
|
||||||
$expense->category_id = $this->resolveCategory($input);
|
$expense->category_id = $this->resolveCategory($input);
|
||||||
$expense->amount = $this->bt->amount;
|
$expense->amount = $this->bt->amount;
|
||||||
|
@ -251,6 +251,13 @@ class NinjaMailerJob implements ShouldQueue
|
|||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if(env($this->company->id . '_MAIL_FROM_ADDRESS'))
|
||||||
|
{
|
||||||
|
$this->nmo
|
||||||
|
->mailable
|
||||||
|
->from(env($this->company->id . '_MAIL_FROM_ADDRESS', env('MAIL_FROM_ADDRESS')), env($this->company->id . '_MAIL_FROM_NAME', env('MAIL_FROM_NAME')));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
112
app/Jobs/Quote/QuoteCheckExpired.php
Normal file
112
app/Jobs/Quote/QuoteCheckExpired.php
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Jobs\Quote;
|
||||||
|
|
||||||
|
use App\Jobs\Mail\NinjaMailer;
|
||||||
|
use App\Jobs\Mail\NinjaMailerJob;
|
||||||
|
use App\Jobs\Mail\NinjaMailerObject;
|
||||||
|
use App\Libraries\MultiDB;
|
||||||
|
use App\Mail\Admin\QuoteExpiredObject;
|
||||||
|
use App\Models\Quote;
|
||||||
|
use App\Repositories\BaseRepository;
|
||||||
|
use App\Utils\Traits\Notifications\UserNotifies;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class QuoteCheckExpired implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, UserNotifies;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*/
|
||||||
|
public function __construct() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
if (! config('ninja.db.multi_db_enabled'))
|
||||||
|
return $this->checkForExpiredQuotes();
|
||||||
|
|
||||||
|
foreach (MultiDB::$dbs as $db) {
|
||||||
|
|
||||||
|
MultiDB::setDB($db);
|
||||||
|
|
||||||
|
$this->checkForExpiredQuotes();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function checkForExpiredQuotes()
|
||||||
|
{
|
||||||
|
Quote::query()
|
||||||
|
->where('status_id', Quote::STATUS_SENT)
|
||||||
|
->where('is_deleted', false)
|
||||||
|
->whereNull('deleted_at')
|
||||||
|
->whereNotNull('due_date')
|
||||||
|
->whereHas('client', function ($query) {
|
||||||
|
$query->where('is_deleted', 0)
|
||||||
|
->where('deleted_at', null);
|
||||||
|
})
|
||||||
|
->whereHas('company', function ($query) {
|
||||||
|
$query->where('is_disabled', 0);
|
||||||
|
})
|
||||||
|
// ->where('due_date', '<='. now()->toDateTimeString())
|
||||||
|
->whereBetween('due_date', [now()->subDay()->startOfDay(), now()->startOfDay()->subSecond()])
|
||||||
|
->cursor()
|
||||||
|
->each(function ($quote){
|
||||||
|
$this->queueExpiredQuoteNotification($quote);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private function queueExpiredQuoteNotification(Quote $quote)
|
||||||
|
{
|
||||||
|
$nmo = new NinjaMailerObject;
|
||||||
|
$nmo->mailable = new NinjaMailer((new QuoteExpiredObject($quote, $quote->company))->build());
|
||||||
|
$nmo->company = $quote->company;
|
||||||
|
$nmo->settings = $quote->company->settings;
|
||||||
|
|
||||||
|
/* We loop through each user and determine whether they need to be notified */
|
||||||
|
foreach ($quote->company->company_users as $company_user) {
|
||||||
|
|
||||||
|
/* The User */
|
||||||
|
$user = $company_user->user;
|
||||||
|
|
||||||
|
if (! $user) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Returns an array of notification methods */
|
||||||
|
$methods = $this->findUserNotificationTypes($quote->invitations()->first(), $company_user, 'quote', ['all_notifications', 'quote_expired', 'quote_expired_all']);
|
||||||
|
|
||||||
|
/* If one of the methods is email then we fire the EntitySentMailer */
|
||||||
|
if (($key = array_search('mail', $methods)) !== false) {
|
||||||
|
unset($methods[$key]);
|
||||||
|
|
||||||
|
$nmo->to_user = $user;
|
||||||
|
|
||||||
|
NinjaMailerJob::dispatch($nmo);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -21,8 +21,6 @@ class InvoiceEmailFailedActivity implements ShouldQueue
|
|||||||
{
|
{
|
||||||
protected $activity_repo;
|
protected $activity_repo;
|
||||||
|
|
||||||
public $delay = 5;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the event listener.
|
* Create the event listener.
|
||||||
*
|
*
|
||||||
|
@ -21,8 +21,6 @@ class InvoicePaidActivity implements ShouldQueue
|
|||||||
{
|
{
|
||||||
protected $activity_repo;
|
protected $activity_repo;
|
||||||
|
|
||||||
public $delay = 10;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the event listener.
|
* Create the event listener.
|
||||||
*
|
*
|
||||||
|
@ -21,8 +21,6 @@ class UpdateInvoiceActivity implements ShouldQueue
|
|||||||
{
|
{
|
||||||
protected $activity_repo;
|
protected $activity_repo;
|
||||||
|
|
||||||
public $delay = 5;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the event listener.
|
* Create the event listener.
|
||||||
*
|
*
|
||||||
|
103
app/Mail/Admin/QuoteExpiredObject.php
Normal file
103
app/Mail/Admin/QuoteExpiredObject.php
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://www.elastic.co/licensing/elastic-license
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Mail\Admin;
|
||||||
|
|
||||||
|
use App\Libraries\MultiDB;
|
||||||
|
use App\Models\Company;
|
||||||
|
use App\Models\Quote;
|
||||||
|
use App\Utils\Ninja;
|
||||||
|
use App\Utils\Number;
|
||||||
|
use Illuminate\Support\Facades\App;
|
||||||
|
use stdClass;
|
||||||
|
|
||||||
|
class QuoteExpiredObject
|
||||||
|
{
|
||||||
|
public $quote;
|
||||||
|
|
||||||
|
public $company;
|
||||||
|
|
||||||
|
public $settings;
|
||||||
|
|
||||||
|
public function __construct(Quote $quote, Company $company)
|
||||||
|
{
|
||||||
|
$this->quote = $quote;
|
||||||
|
$this->company = $company;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function build()
|
||||||
|
{
|
||||||
|
MultiDB::setDb($this->company->db);
|
||||||
|
|
||||||
|
if (! $this->quote) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
App::forgetInstance('translator');
|
||||||
|
/* Init a new copy of the translator*/
|
||||||
|
$t = app('translator');
|
||||||
|
/* Set the locale*/
|
||||||
|
App::setLocale($this->company->getLocale());
|
||||||
|
/* Set customized translations _NOW_ */
|
||||||
|
$t->replace(Ninja::transformTranslations($this->company->settings));
|
||||||
|
|
||||||
|
$mail_obj = new stdClass;
|
||||||
|
$mail_obj->amount = $this->getAmount();
|
||||||
|
$mail_obj->subject = $this->getSubject();
|
||||||
|
$mail_obj->data = $this->getData();
|
||||||
|
$mail_obj->markdown = 'email.admin.generic';
|
||||||
|
$mail_obj->tag = $this->company->company_key;
|
||||||
|
|
||||||
|
return $mail_obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getAmount()
|
||||||
|
{
|
||||||
|
return Number::formatMoney($this->quote->amount, $this->quote->client);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getSubject()
|
||||||
|
{
|
||||||
|
return
|
||||||
|
ctrans(
|
||||||
|
'texts.notification_quote_expired_subject',
|
||||||
|
[
|
||||||
|
'client' => $this->quote->client->present()->name(),
|
||||||
|
'invoice' => $this->quote->number,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getData()
|
||||||
|
{
|
||||||
|
$settings = $this->quote->client->getMergedSettings();
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'title' => $this->getSubject(),
|
||||||
|
'message' => ctrans(
|
||||||
|
'texts.notification_quote_expired',
|
||||||
|
[
|
||||||
|
'amount' => $this->getAmount(),
|
||||||
|
'client' => $this->quote->client->present()->name(),
|
||||||
|
'invoice' => $this->quote->number,
|
||||||
|
]
|
||||||
|
),
|
||||||
|
'url' => $this->quote->invitations->first()->getAdminLink(),
|
||||||
|
'button' => ctrans('texts.view_quote'),
|
||||||
|
'signature' => $settings->email_signature,
|
||||||
|
'logo' => $this->company->present()->logo(),
|
||||||
|
'settings' => $settings,
|
||||||
|
'whitelabel' => $this->company->account->isPaid() ? true : false,
|
||||||
|
];
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
}
|
@ -243,6 +243,14 @@ class PaymentEmailEngine extends BaseEmailEngine
|
|||||||
$data['$invoices.due_date'] = ['value' => $this->formatInvoiceField('due_date'), 'label' => ctrans('texts.invoices')];
|
$data['$invoices.due_date'] = ['value' => $this->formatInvoiceField('due_date'), 'label' => ctrans('texts.invoices')];
|
||||||
$data['$invoices.po_number'] = ['value' => $this->formatInvoiceField('po_number'), 'label' => ctrans('texts.invoices')];
|
$data['$invoices.po_number'] = ['value' => $this->formatInvoiceField('po_number'), 'label' => ctrans('texts.invoices')];
|
||||||
|
|
||||||
|
|
||||||
|
if($this->payment->status_id == 4) {
|
||||||
|
$data['$status_logo'] = ['value' => '<div class="stamp is-paid"> ' . ctrans('texts.paid') .'</div>', 'label' => ''];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
$data['$status_logo'] = ['value' => '', 'label' => ''];
|
||||||
|
|
||||||
|
|
||||||
$arrKeysLength = array_map('strlen', array_keys($data));
|
$arrKeysLength = array_map('strlen', array_keys($data));
|
||||||
array_multisort($arrKeysLength, SORT_DESC, $data);
|
array_multisort($arrKeysLength, SORT_DESC, $data);
|
||||||
|
|
||||||
|
@ -42,8 +42,10 @@ class CompanyPresenter extends EntityPresenter
|
|||||||
return $settings->company_logo;
|
return $settings->company_logo;
|
||||||
else if(strlen($settings->company_logo) >= 1)
|
else if(strlen($settings->company_logo) >= 1)
|
||||||
return url('') . $settings->company_logo;
|
return url('') . $settings->company_logo;
|
||||||
else
|
else{
|
||||||
return asset('images/new_logo.png');
|
return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=";
|
||||||
|
//return asset('images/new_logo.png');
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,8 +90,10 @@ class CompanyPresenter extends EntityPresenter
|
|||||||
return "data:image/png;base64, ". base64_encode(@file_get_contents($settings->company_logo, false, stream_context_create($context_options)));
|
return "data:image/png;base64, ". base64_encode(@file_get_contents($settings->company_logo, false, stream_context_create($context_options)));
|
||||||
else if(strlen($settings->company_logo) >= 1)
|
else if(strlen($settings->company_logo) >= 1)
|
||||||
return "data:image/png;base64, ". base64_encode(@file_get_contents(url('') . $settings->company_logo, false, stream_context_create($context_options)));
|
return "data:image/png;base64, ". base64_encode(@file_get_contents(url('') . $settings->company_logo, false, stream_context_create($context_options)));
|
||||||
else
|
else{
|
||||||
return "data:image/png;base64, ". base64_encode(@file_get_contents(asset('images/new_logo.png'), false, stream_context_create($context_options)));
|
return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=";
|
||||||
|
//return "data:image/png;base64, ". base64_encode(@file_get_contents(asset('images/new_logo.png'), false, stream_context_create($context_options)));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,6 +124,7 @@ class RecurringInvoice extends BaseModel
|
|||||||
'exchange_rate',
|
'exchange_rate',
|
||||||
'vendor_id',
|
'vendor_id',
|
||||||
'next_send_date_client',
|
'next_send_date_client',
|
||||||
|
'uses_inclusive_taxes',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
|
@ -136,3 +136,36 @@ class AuthorizeCreateCustomer
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// $request = new net\authorize\api\contract\v1\GetCustomerProfileIdsRequest();
|
||||||
|
// $request->setMerchantAuthentication($auth->merchant_authentication);
|
||||||
|
// $controller = new net\authorize\api\controller\GetCustomerProfileIdsController($request);
|
||||||
|
// $response = $controller->executeWithApiResponse($auth->mode());
|
||||||
|
|
||||||
|
// // $customer_profile_id = end($response->getIds());
|
||||||
|
|
||||||
|
// foreach($response->getIds() as $customer_profile_id)
|
||||||
|
// {
|
||||||
|
// $request = new net\authorize\api\contract\v1\GetCustomerProfileRequest();
|
||||||
|
// $request->setMerchantAuthentication($auth->merchant_authentication);
|
||||||
|
// $request->setCustomerProfileId($customer_profile_id);
|
||||||
|
// $controller = new net\authorize\api\controller\GetCustomerProfileController($request);
|
||||||
|
// $response = $controller->executeWithApiResponse($auth->mode());
|
||||||
|
|
||||||
|
// $profileSelected = $response->getProfile();
|
||||||
|
|
||||||
|
// if($profileSelected->getEmail() == 'katnandan@gmail.com')
|
||||||
|
// {
|
||||||
|
|
||||||
|
// $profileSelected;
|
||||||
|
// break;
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -92,12 +92,14 @@ class CreditCard
|
|||||||
$payment_hash = PaymentHash::where('hash', $request->input('payment_hash'))->firstOrFail();
|
$payment_hash = PaymentHash::where('hash', $request->input('payment_hash'))->firstOrFail();
|
||||||
$amount_with_fee = $payment_hash->data->total->amount_with_fee;
|
$amount_with_fee = $payment_hash->data->total->amount_with_fee;
|
||||||
$invoice_totals = $payment_hash->data->total->invoice_totals;
|
$invoice_totals = $payment_hash->data->total->invoice_totals;
|
||||||
$fee_total = 0;
|
$fee_total = null;
|
||||||
|
|
||||||
$fees_and_limits = $this->forte->company_gateway->getFeesAndLimits(GatewayType::CREDIT_CARD);
|
$fees_and_limits = $this->forte->company_gateway->getFeesAndLimits(GatewayType::CREDIT_CARD);
|
||||||
|
|
||||||
if(property_exists($fees_and_limits, 'fee_percent') && $fees_and_limits->fee_percent > 0)
|
if(property_exists($fees_and_limits, 'fee_percent') && $fees_and_limits->fee_percent > 0)
|
||||||
{
|
{
|
||||||
|
$fee_total = 0;
|
||||||
|
|
||||||
for ($i = ($invoice_totals * 100) ; $i < ($amount_with_fee * 100); $i++) {
|
for ($i = ($invoice_totals * 100) ; $i < ($amount_with_fee * 100); $i++) {
|
||||||
$calculated_fee = ( 3 * $i) / 100;
|
$calculated_fee = ( 3 * $i) / 100;
|
||||||
$calculated_amount_with_fee = round(($i + $calculated_fee) / 100,2);
|
$calculated_amount_with_fee = round(($i + $calculated_fee) / 100,2);
|
||||||
|
@ -98,6 +98,8 @@ class InstantBankPay implements MethodInterface
|
|||||||
$request->getPaymentHash()
|
$request->getPaymentHash()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$this->go_cardless->init();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$billing_request = $this->go_cardless->gateway->billingRequests()->get(
|
$billing_request = $this->go_cardless->gateway->billingRequests()->get(
|
||||||
$this->go_cardless->payment_hash->data->billing_request
|
$this->go_cardless->payment_hash->data->billing_request
|
||||||
|
@ -291,13 +291,13 @@ class GoCardlessPaymentDriver extends BaseDriver
|
|||||||
return response()->json([], 200);
|
return response()->json([], 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->go_cardless->setPaymentHash($hash);
|
$this->setPaymentHash($hash);
|
||||||
|
|
||||||
$billing_request = $this->go_cardless->gateway->billingRequests()->get(
|
$billing_request = $this->gateway->billingRequests()->get(
|
||||||
$event['links']['billing_request']
|
$event['links']['billing_request']
|
||||||
);
|
);
|
||||||
|
|
||||||
$payment = $this->go_cardless->gateway->payments()->get(
|
$payment = $this->gateway->payments()->get(
|
||||||
$billing_request->payment_request->links->payment
|
$billing_request->payment_request->links->payment
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -305,12 +305,12 @@ class GoCardlessPaymentDriver extends BaseDriver
|
|||||||
|
|
||||||
$invoices = Invoice::whereIn('id', $this->transformKeys(array_column($hash->invoices(), 'invoice_id')))->withTrashed()->get();
|
$invoices = Invoice::whereIn('id', $this->transformKeys(array_column($hash->invoices(), 'invoice_id')))->withTrashed()->get();
|
||||||
|
|
||||||
$this->go_cardless->client = $invoices->first()->client;
|
$this->client = $invoices->first()->client;
|
||||||
|
|
||||||
$invoices->each(function ($invoice){
|
$invoices->each(function ($invoice){
|
||||||
|
|
||||||
//if payments exist already, they just need to be confirmed.
|
//if payments exist already, they just need to be confirmed.
|
||||||
if($invoice->payments()->exists){
|
if($invoice->payments()->exists()){
|
||||||
|
|
||||||
$invoice->payments()->where('status_id', 1)->cursor()->each(function ($payment){
|
$invoice->payments()->where('status_id', 1)->cursor()->each(function ($payment){
|
||||||
$payment->status_id = 4;
|
$payment->status_id = 4;
|
||||||
@ -347,12 +347,12 @@ class GoCardlessPaymentDriver extends BaseDriver
|
|||||||
$data = [
|
$data = [
|
||||||
'payment_method' => $payment->links->mandate,
|
'payment_method' => $payment->links->mandate,
|
||||||
'payment_type' => PaymentType::INSTANT_BANK_PAY,
|
'payment_type' => PaymentType::INSTANT_BANK_PAY,
|
||||||
'amount' => $this->go_cardless->payment_hash->data->amount_with_fee,
|
'amount' => $this->payment_hash->data->amount_with_fee,
|
||||||
'transaction_reference' => $payment->id,
|
'transaction_reference' => $payment->id,
|
||||||
'gateway_type_id' => GatewayType::INSTANT_BANK_PAY,
|
'gateway_type_id' => GatewayType::INSTANT_BANK_PAY,
|
||||||
];
|
];
|
||||||
|
|
||||||
$payment = $this->go_cardless->createPayment($data, Payment::STATUS_COMPLETED);
|
$payment = $this->createPayment($data, Payment::STATUS_COMPLETED);
|
||||||
$payment->status_id = Payment::STATUS_COMPLETED;
|
$payment->status_id = Payment::STATUS_COMPLETED;
|
||||||
$payment->save();
|
$payment->save();
|
||||||
|
|
||||||
@ -361,8 +361,8 @@ class GoCardlessPaymentDriver extends BaseDriver
|
|||||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||||
SystemLog::EVENT_GATEWAY_SUCCESS,
|
SystemLog::EVENT_GATEWAY_SUCCESS,
|
||||||
SystemLog::TYPE_GOCARDLESS,
|
SystemLog::TYPE_GOCARDLESS,
|
||||||
$this->go_cardless->client,
|
$this->client,
|
||||||
$this->go_cardless->client->company,
|
$this->client->company,
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ class BankTransactionRepository extends BaseRepository
|
|||||||
public function save($data, BankTransaction $bank_transaction)
|
public function save($data, BankTransaction $bank_transaction)
|
||||||
{
|
{
|
||||||
|
|
||||||
if(!isset($bank_transaction->bank_integration_id) && array_key_exists('bank_integration_id', $data))
|
if(array_key_exists('bank_integration_id', $data))
|
||||||
$bank_transaction->bank_integration_id = $data['bank_integration_id'];
|
$bank_transaction->bank_integration_id = $data['bank_integration_id'];
|
||||||
|
|
||||||
$bank_transaction->fill($data);
|
$bank_transaction->fill($data);
|
||||||
|
@ -187,7 +187,7 @@ class BaseRepository
|
|||||||
if(!$model->id){
|
if(!$model->id){
|
||||||
$this->new_model = true;
|
$this->new_model = true;
|
||||||
|
|
||||||
if(is_array($model->line_items))
|
if(is_array($model->line_items) && !($model instanceof RecurringInvoice))
|
||||||
{
|
{
|
||||||
$model->line_items = (collect($model->line_items))->map(function ($item) use($model,$client) {
|
$model->line_items = (collect($model->line_items))->map(function ($item) use($model,$client) {
|
||||||
|
|
||||||
|
@ -45,6 +45,22 @@ class TriggeredActions extends AbstractService
|
|||||||
$this->credit = $this->credit->service()->markSent()->save();
|
$this->credit = $this->credit->service()->markSent()->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if($this->request->has('save_default_footer') && $this->request->input('save_default_footer') == 'true') {
|
||||||
|
$company = $this->credit->company;
|
||||||
|
$settings = $company->settings;
|
||||||
|
$settings->credit_footer = $this->credit->footer;
|
||||||
|
$company->settings = $settings;
|
||||||
|
$company->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
if($this->request->has('save_default_terms') && $this->request->input('save_default_terms') == 'true') {
|
||||||
|
$company = $this->credit->company;
|
||||||
|
$settings = $company->settings;
|
||||||
|
$settings->credit_terms = $this->credit->terms;
|
||||||
|
$company->settings = $settings;
|
||||||
|
$company->save();
|
||||||
|
}
|
||||||
|
|
||||||
return $this->credit;
|
return $this->credit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,6 +57,8 @@ class HandleCancellation extends AbstractService
|
|||||||
|
|
||||||
event(new InvoiceWasCancelled($this->invoice, $this->invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
event(new InvoiceWasCancelled($this->invoice, $this->invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||||
|
|
||||||
|
event('eloquent.updated: App\Models\Invoice', $this->invoice);
|
||||||
|
|
||||||
$transaction = [
|
$transaction = [
|
||||||
'invoice' => $this->invoice->transaction_event(),
|
'invoice' => $this->invoice->transaction_event(),
|
||||||
'payment' => [],
|
'payment' => [],
|
||||||
|
@ -67,6 +67,22 @@ class TriggeredActions extends AbstractService
|
|||||||
$this->updated = false;
|
$this->updated = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if($this->request->has('save_default_footer') && $this->request->input('save_default_footer') == 'true') {
|
||||||
|
$company = $this->invoice->company;
|
||||||
|
$settings = $company->settings;
|
||||||
|
$settings->invoice_footer = $this->invoice->footer;
|
||||||
|
$company->settings = $settings;
|
||||||
|
$company->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
if($this->request->has('save_default_terms') && $this->request->input('save_default_terms') == 'true') {
|
||||||
|
$company = $this->invoice->company;
|
||||||
|
$settings = $company->settings;
|
||||||
|
$settings->invoice_terms = $this->invoice->terms;
|
||||||
|
$company->settings = $settings;
|
||||||
|
$company->save();
|
||||||
|
}
|
||||||
|
|
||||||
if($this->updated)
|
if($this->updated)
|
||||||
event('eloquent.updated: App\Models\Invoice', $this->invoice);
|
event('eloquent.updated: App\Models\Invoice', $this->invoice);
|
||||||
|
|
||||||
|
@ -73,6 +73,7 @@ class Design extends BaseDesign
|
|||||||
const PLAIN = 'plain';
|
const PLAIN = 'plain';
|
||||||
const PLAYFUL = 'playful';
|
const PLAYFUL = 'playful';
|
||||||
const CUSTOM = 'custom';
|
const CUSTOM = 'custom';
|
||||||
|
const CALM = 'calm';
|
||||||
|
|
||||||
const DELIVERY_NOTE = 'delivery_note';
|
const DELIVERY_NOTE = 'delivery_note';
|
||||||
const STATEMENT = 'statement';
|
const STATEMENT = 'statement';
|
||||||
|
@ -52,6 +52,22 @@ class TriggeredActions extends AbstractService
|
|||||||
// $this->purchase_order = $this->purchase_order->service()->handleCancellation()->save();
|
// $this->purchase_order = $this->purchase_order->service()->handleCancellation()->save();
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
if($this->request->has('save_default_footer') && $this->request->input('save_default_footer') == 'true') {
|
||||||
|
$company = $this->purchase_order->company;
|
||||||
|
$settings = $company->settings;
|
||||||
|
$settings->purchase_order_footer = $this->purchase_order->footer;
|
||||||
|
$company->settings = $settings;
|
||||||
|
$company->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
if($this->request->has('save_default_terms') && $this->request->input('save_default_terms') == 'true') {
|
||||||
|
$company = $this->purchase_order->company;
|
||||||
|
$settings = $company->settings;
|
||||||
|
$settings->purchase_order_terms = $this->purchase_order->terms;
|
||||||
|
$company->settings = $settings;
|
||||||
|
$company->save();
|
||||||
|
}
|
||||||
|
|
||||||
return $this->purchase_order;
|
return $this->purchase_order;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ class Helpers
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 04-10-2022 Return Early if no reserved keywords are present, this is a very expenseive process
|
// 04-10-2022 Return Early if no reserved keywords are present, this is a very expensive process
|
||||||
$string_hit = false;
|
$string_hit = false;
|
||||||
|
|
||||||
foreach ( [':MONTH',':YEAR',':QUARTER',':WEEK'] as $string )
|
foreach ( [':MONTH',':YEAR',':QUARTER',':WEEK'] as $string )
|
||||||
@ -146,19 +146,19 @@ class Helpers
|
|||||||
'%s %s %s',
|
'%s %s %s',
|
||||||
Carbon::now()->subDays(7)->translatedFormat($entity->date_format()),
|
Carbon::now()->subDays(7)->translatedFormat($entity->date_format()),
|
||||||
ctrans('texts.to'),
|
ctrans('texts.to'),
|
||||||
Carbon::now()->translatedFormat($entity->date_format())
|
Carbon::now()->subDays(1)->translatedFormat($entity->date_format())
|
||||||
),
|
),
|
||||||
':WEEK_AHEAD' => \sprintf(
|
':WEEK_AHEAD' => \sprintf(
|
||||||
'%s %s %s',
|
'%s %s %s',
|
||||||
Carbon::now()->addDays(7)->translatedFormat($entity->date_format()),
|
Carbon::now()->addDays(6)->translatedFormat($entity->date_format()),
|
||||||
ctrans('texts.to'),
|
ctrans('texts.to'),
|
||||||
Carbon::now()->addDays(14)->translatedFormat($entity->date_format())
|
Carbon::now()->addDays(13)->translatedFormat($entity->date_format())
|
||||||
),
|
),
|
||||||
':WEEK' => \sprintf(
|
':WEEK' => \sprintf(
|
||||||
'%s %s %s',
|
'%s %s %s',
|
||||||
Carbon::now()->translatedFormat($entity->date_format()),
|
Carbon::now()->subDays(7)->translatedFormat($entity->date_format()),
|
||||||
ctrans('texts.to'),
|
ctrans('texts.to'),
|
||||||
Carbon::now()->addDays(7)->translatedFormat($entity->date_format())
|
Carbon::now()->addDays(13)->translatedFormat($entity->date_format())
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
'raw' => [
|
'raw' => [
|
||||||
|
@ -124,6 +124,7 @@ class HtmlEngine
|
|||||||
$data['$line_tax_labels'] = ['value' => $this->lineTaxLabels(), 'label' => ctrans('texts.taxes')];
|
$data['$line_tax_labels'] = ['value' => $this->lineTaxLabels(), 'label' => ctrans('texts.taxes')];
|
||||||
$data['$line_tax_values'] = ['value' => $this->lineTaxValues(), 'label' => ctrans('texts.taxes')];
|
$data['$line_tax_values'] = ['value' => $this->lineTaxValues(), 'label' => ctrans('texts.taxes')];
|
||||||
$data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->client->date_format(), $this->client->locale()) ?: ' ', 'label' => ctrans('texts.date')];
|
$data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->client->date_format(), $this->client->locale()) ?: ' ', 'label' => ctrans('texts.date')];
|
||||||
|
$data['$status_logo'] = ['value' => '', 'label' => ''];
|
||||||
|
|
||||||
$data['$invoice.date'] = &$data['$date'];
|
$data['$invoice.date'] = &$data['$date'];
|
||||||
$data['$invoiceDate'] = &$data['$date'];
|
$data['$invoiceDate'] = &$data['$date'];
|
||||||
@ -167,6 +168,10 @@ class HtmlEngine
|
|||||||
$data['$invoice.project'] = &$data['$project.name'];
|
$data['$invoice.project'] = &$data['$project.name'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if($this->entity->status_id == 4) {
|
||||||
|
$data['$status_logo'] = ['value' => '<div class="stamp is-paid"> ' . ctrans('texts.paid') .'</div>', 'label' => ''];
|
||||||
|
}
|
||||||
|
|
||||||
if($this->entity->vendor) {
|
if($this->entity->vendor) {
|
||||||
$data['$invoice.vendor'] = ['value' => $this->entity->vendor->present()->name(), 'label' => ctrans('texts.vendor_name')];
|
$data['$invoice.vendor'] = ['value' => $this->entity->vendor->present()->name(), 'label' => ctrans('texts.vendor_name')];
|
||||||
}
|
}
|
||||||
|
@ -14,8 +14,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.5.38',
|
'app_version' => '5.5.41',
|
||||||
'app_tag' => '5.5.38',
|
'app_tag' => '5.5.41',
|
||||||
'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', ''),
|
||||||
|
53
database/migrations/2022_11_16_093535_calmness_design.php
Normal file
53
database/migrations/2022_11_16_093535_calmness_design.php
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\Design;
|
||||||
|
use App\Services\PdfMaker\Design as PdfMakerDesign;
|
||||||
|
use App\Utils\Ninja;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
|
||||||
|
if (Ninja::isHosted()) {
|
||||||
|
$design = new Design();
|
||||||
|
|
||||||
|
$design->name = 'Calm';
|
||||||
|
$design->is_custom = false;
|
||||||
|
$design->design = '';
|
||||||
|
$design->is_active = true;
|
||||||
|
|
||||||
|
$design->save();
|
||||||
|
} elseif (Design::count() !== 0) {
|
||||||
|
$design = new Design();
|
||||||
|
|
||||||
|
$design->name = 'Calm';
|
||||||
|
$design->is_custom = false;
|
||||||
|
$design->design = '';
|
||||||
|
$design->is_active = true;
|
||||||
|
|
||||||
|
$design->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
\Illuminate\Support\Facades\Artisan::call('ninja:design-update');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
};
|
@ -38,6 +38,7 @@ class DesignSeeder extends Seeder
|
|||||||
['id' => 8, 'name' => 'Hipster', 'user_id' => null, 'company_id' => null, 'is_custom' => false, 'design' => '', 'is_active' => true],
|
['id' => 8, 'name' => 'Hipster', 'user_id' => null, 'company_id' => null, 'is_custom' => false, 'design' => '', 'is_active' => true],
|
||||||
['id' => 9, 'name' => 'Playful', 'user_id' => null, 'company_id' => null, 'is_custom' => false, 'design' => '', 'is_active' => true],
|
['id' => 9, 'name' => 'Playful', 'user_id' => null, 'company_id' => null, 'is_custom' => false, 'design' => '', 'is_active' => true],
|
||||||
['id' => 10, 'name' => 'Tech', 'user_id' => null, 'company_id' => null, 'is_custom' => false, 'design' => '', 'is_active' => true],
|
['id' => 10, 'name' => 'Tech', 'user_id' => null, 'company_id' => null, 'is_custom' => false, 'design' => '', 'is_active' => true],
|
||||||
|
['id' => 11, 'name' => 'Calm', 'user_id' => null, 'company_id' => null, 'is_custom' => false, 'design' => '', 'is_active' => true],
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach ($designs as $design) {
|
foreach ($designs as $design) {
|
||||||
|
@ -4837,7 +4837,12 @@ $LANG = array(
|
|||||||
'enable_applying_payments_later' => 'Enable Applying Payments Later',
|
'enable_applying_payments_later' => 'Enable Applying Payments Later',
|
||||||
'line_item_tax_rates' => 'Line Item Tax Rates',
|
'line_item_tax_rates' => 'Line Item Tax Rates',
|
||||||
'show_tasks_in_client_portal' => 'Show Tasks in Client Portal',
|
'show_tasks_in_client_portal' => 'Show Tasks in Client Portal',
|
||||||
|
'notification_quote_expired_subject' => 'Quote :invoice has expired for :client',
|
||||||
|
'notification_quote_expired' => 'The following Quote :invoice for client :client and :amount has now expired.',
|
||||||
|
'auto_sync' => 'Auto Sync',
|
||||||
|
'refresh_accounts' => 'Refresh Accounts',
|
||||||
|
'upgrade_to_connect_bank_account' => 'Upgrade to Enterprise to connect your bank account',
|
||||||
|
'click_here_to_connect_bank_account' => 'Click here to connect your bank account',
|
||||||
);
|
);
|
||||||
|
|
||||||
return $LANG;
|
return $LANG;
|
||||||
|
4
public/flutter_service_worker.js
vendored
4
public/flutter_service_worker.js
vendored
@ -11,9 +11,9 @@ const RESOURCES = {
|
|||||||
"favicon.png": "dca91c54388f52eded692718d5a98b8b",
|
"favicon.png": "dca91c54388f52eded692718d5a98b8b",
|
||||||
"favicon.ico": "51636d3a390451561744c42188ccd628",
|
"favicon.ico": "51636d3a390451561744c42188ccd628",
|
||||||
"flutter.js": "f85e6fb278b0fd20c349186fb46ae36d",
|
"flutter.js": "f85e6fb278b0fd20c349186fb46ae36d",
|
||||||
"/": "d2b918382ed83045e8e3854cae1edc55",
|
"/": "112f22769207bffb3936c08dec3ffa4d",
|
||||||
"manifest.json": "ef43d90e57aa7682d7e2cfba2f484a40",
|
"manifest.json": "ef43d90e57aa7682d7e2cfba2f484a40",
|
||||||
"main.dart.js": "f6e5cc85c8e2f5aca83137308c219666",
|
"main.dart.js": "bddfba2a7d482fece1e7a9ff84429256",
|
||||||
"assets/AssetManifest.json": "759f9ef9973f7e26c2a51450b55bb9fa",
|
"assets/AssetManifest.json": "759f9ef9973f7e26c2a51450b55bb9fa",
|
||||||
"assets/FontManifest.json": "087fb858dc3cbfbf6baf6a30004922f1",
|
"assets/FontManifest.json": "087fb858dc3cbfbf6baf6a30004922f1",
|
||||||
"assets/NOTICES": "1a34e70168d56fad075adfb4bdbb20eb",
|
"assets/NOTICES": "1a34e70168d56fad075adfb4bdbb20eb",
|
||||||
|
260857
public/main.dart.js
vendored
260857
public/main.dart.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
257225
public/main.foss.dart.js
vendored
257225
public/main.foss.dart.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
13776
public/main.profile.dart.js
vendored
13776
public/main.profile.dart.js
vendored
File diff suppressed because one or more lines are too long
@ -101,6 +101,36 @@
|
|||||||
#content .center {
|
#content .center {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stamp {
|
||||||
|
transform: rotate(12deg);
|
||||||
|
color: #555;
|
||||||
|
font-size: 3rem;
|
||||||
|
font-weight: 700;
|
||||||
|
border: 0.25rem solid #555;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.25rem 1rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
border-radius: 1rem;
|
||||||
|
font-family: 'Courier';
|
||||||
|
mix-blend-mode: multiply;
|
||||||
|
z-index:200 !important;
|
||||||
|
position: fixed;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-paid {
|
||||||
|
color: #D23;
|
||||||
|
border: 1rem double #D23;
|
||||||
|
transform: rotate(-5deg);
|
||||||
|
font-size: 6rem;
|
||||||
|
font-family: "Open sans", Helvetica, Arial, sans-serif;
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 0.5rem;
|
||||||
|
opacity: 0.2;
|
||||||
|
z-index:200 !important;
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
@ -80,6 +80,33 @@
|
|||||||
#content .left {
|
#content .left {
|
||||||
text-align: left !important;
|
text-align: left !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stamp {
|
||||||
|
transform: rotate(12deg);
|
||||||
|
color: #555;
|
||||||
|
font-size: 3rem;
|
||||||
|
font-weight: 700;
|
||||||
|
border: 0.25rem solid #555;
|
||||||
|
text-transform: uppercase;
|
||||||
|
border-radius: 1rem;
|
||||||
|
font-family: 'Courier';
|
||||||
|
mix-blend-mode: multiply;
|
||||||
|
z-index:200 !important;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-paid {
|
||||||
|
color: #D23;
|
||||||
|
border: 1rem double #D23;
|
||||||
|
transform: rotate(-5deg);
|
||||||
|
font-size: 6rem;
|
||||||
|
font-family: "Open sans", Helvetica, Arial, sans-serif;
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 0.5rem;
|
||||||
|
opacity: 0.2;
|
||||||
|
z-index:200 !important;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!--[if gte mso 9]>
|
<!--[if gte mso 9]>
|
||||||
|
@ -143,6 +143,36 @@
|
|||||||
color: {{ $design == 'dark' ? '#ffffff' : '#000000' }} !important;
|
color: {{ $design == 'dark' ? '#ffffff' : '#000000' }} !important;
|
||||||
opacity: {{ $design == 'dark' ? '87%': '100%' }} !important;
|
opacity: {{ $design == 'dark' ? '87%': '100%' }} !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stamp {
|
||||||
|
transform: rotate(12deg);
|
||||||
|
color: #555;
|
||||||
|
font-size: 3rem;
|
||||||
|
font-weight: 700;
|
||||||
|
border: 0.25rem solid #555;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.25rem 1rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
border-radius: 1rem;
|
||||||
|
font-family: 'Courier';
|
||||||
|
mix-blend-mode: multiply;
|
||||||
|
z-index:200 !important;
|
||||||
|
position: fixed;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-paid {
|
||||||
|
color: #D23;
|
||||||
|
border: 1rem double #D23;
|
||||||
|
transform: rotate(-5deg);
|
||||||
|
font-size: 6rem;
|
||||||
|
font-family: "Open sans", Helvetica, Arial, sans-serif;
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 0.5rem;
|
||||||
|
opacity: 0.2;
|
||||||
|
z-index:200 !important;
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
@ -47,6 +47,7 @@
|
|||||||
line-height: var(--line-height);
|
line-height: var(--line-height);
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -224,7 +225,7 @@
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr 1fr;
|
grid-template-columns: 1fr;
|
||||||
gap: 15px;
|
gap: 15px;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
@ -239,6 +240,11 @@
|
|||||||
padding-top: 0.5rem
|
padding-top: 0.5rem
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-ref="footer_content"]{
|
||||||
|
padding-right: 2rem;
|
||||||
|
margin-right: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
@ -285,6 +291,36 @@
|
|||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stamp {
|
||||||
|
transform: rotate(12deg);
|
||||||
|
color: #555;
|
||||||
|
font-size: 3rem;
|
||||||
|
font-weight: 700;
|
||||||
|
border: 0.25rem solid #555;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.25rem 1rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
border-radius: 1rem;
|
||||||
|
font-family: 'Courier';
|
||||||
|
mix-blend-mode: multiply;
|
||||||
|
z-index:200 !important;
|
||||||
|
position: fixed;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-paid {
|
||||||
|
color: #D23;
|
||||||
|
border: 1rem double #D23;
|
||||||
|
transform: rotate(-5deg);
|
||||||
|
font-size: 6rem;
|
||||||
|
font-family: "Open sans", Helvetica, Arial, sans-serif;
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 0.5rem;
|
||||||
|
opacity: 0.2;
|
||||||
|
z-index:200 !important;
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
/** Useful snippets, uncomment to enable. **/
|
/** Useful snippets, uncomment to enable. **/
|
||||||
|
|
||||||
/** Hide company logo **/
|
/** Hide company logo **/
|
||||||
@ -380,7 +416,7 @@ $entity_images
|
|||||||
|
|
||||||
<div id="footer">
|
<div id="footer">
|
||||||
<div style="width: 100%;">
|
<div style="width: 100%;">
|
||||||
<p data-ref="total_table-footer">$entity_footer</p>
|
<p data-ref="footer_content">$entity_footer</p>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Clear up space a bit, if [product-table, tasks-table, delivery-note-table] isn't present.
|
// Clear up space a bit, if [product-table, tasks-table, delivery-note-table] isn't present.
|
||||||
@ -402,6 +438,4 @@ $entity_images
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</div>
|
</div>
|
||||||
<div> <!-- #2 column --> </div>
|
|
||||||
<div> <!-- #3 column --> </div>
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -276,6 +276,36 @@
|
|||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stamp {
|
||||||
|
transform: rotate(12deg);
|
||||||
|
color: #555;
|
||||||
|
font-size: 3rem;
|
||||||
|
font-weight: 700;
|
||||||
|
border: 0.25rem solid #555;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.25rem 1rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
border-radius: 1rem;
|
||||||
|
font-family: 'Courier';
|
||||||
|
mix-blend-mode: multiply;
|
||||||
|
z-index:200 !important;
|
||||||
|
position: fixed;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-paid {
|
||||||
|
color: #D23;
|
||||||
|
border: 1rem double #D23;
|
||||||
|
transform: rotate(-5deg);
|
||||||
|
font-size: 6rem;
|
||||||
|
font-family: "Open sans", Helvetica, Arial, sans-serif;
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 0.5rem;
|
||||||
|
opacity: 0.2;
|
||||||
|
z-index:200 !important;
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
/** Useful snippets, uncomment to enable. **/
|
/** Useful snippets, uncomment to enable. **/
|
||||||
|
|
||||||
/** Hide company logo **/
|
/** Hide company logo **/
|
||||||
|
425
resources/views/pdf-designs/calm.html
Normal file
425
resources/views/pdf-designs/calm.html
Normal file
@ -0,0 +1,425 @@
|
|||||||
|
<style id="style">
|
||||||
|
@import url($font_url);
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--primary-color: $primary_color;
|
||||||
|
--secondary-color: $secondary_color;
|
||||||
|
--line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
font-family: $font_name, Helvetica, sans-serif;
|
||||||
|
font-size: "$font_size";
|
||||||
|
zoom: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
margin: 0;
|
||||||
|
padding-top: 1rem;
|
||||||
|
padding-left: 4rem;
|
||||||
|
padding-right: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@page {
|
||||||
|
margin: 0 !important;
|
||||||
|
size: $page_size $page_layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-wrapper {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 0.5fr;
|
||||||
|
line-height: var(--line-height);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-wrapper2 {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 0.5fr;
|
||||||
|
margin-top: 2rem;
|
||||||
|
min-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.company-logo {
|
||||||
|
max-width: 65%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.client-and-entity-wrapper {
|
||||||
|
display: flex;
|
||||||
|
padding: 1rem;
|
||||||
|
border-top: 1px solid #d8d8d8;
|
||||||
|
border-bottom: 1px solid #d8d8d8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-wrapper #company-address {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
line-height: var(--line-height);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-wrapper #entity-details {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
text-align: left;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-wrapper #entity-details > tr,
|
||||||
|
.header-wrapper #entity-details th {
|
||||||
|
font-weight: normal;
|
||||||
|
padding-left: 0.9rem;
|
||||||
|
padding-top: 0.3rem;
|
||||||
|
padding-bottom: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-wrapper
|
||||||
|
#entity-details
|
||||||
|
[data-element='entity-balance-due-label'],
|
||||||
|
.header-wrapper
|
||||||
|
#entity-details
|
||||||
|
[data-element='entity-balance-due'] {
|
||||||
|
background-color: #e6e6e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
#client-details {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
line-height: var(--line-height);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-ref="table"] {
|
||||||
|
margin-top: 2rem;
|
||||||
|
min-width: 100%;
|
||||||
|
table-layout: fixed;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-time-details {
|
||||||
|
display: block;
|
||||||
|
margin-top: 5px;
|
||||||
|
color: grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-ref="table"] > thead {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-ref="table"] > thead > tr > th {
|
||||||
|
padding: 1rem;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-ref="table"] > thead > tr > th:last-child {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-ref="table"] > tbody > tr > td {
|
||||||
|
border-bottom: 1px solid #e6e6e6;
|
||||||
|
padding: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-ref="table"] > tbody > tr > td:last-child {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-ref="table"] > tbody > tr:nth-child(even) {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
#table-totals {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1.5fr 1fr;
|
||||||
|
padding-top: .5rem;
|
||||||
|
gap: 80px;
|
||||||
|
page-break-inside:auto;
|
||||||
|
overflow: visible !important;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: var(--line-height);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#table-totals .totals-table-right-side>* {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
#table-totals>.totals-table-right-side>*> :nth-child(1) {
|
||||||
|
text-align: left;
|
||||||
|
margin-top: .25rem;
|
||||||
|
padding-left: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#table-totals>.totals-table-right-side> * > :not([hidden]) ~ :not([hidden]) {
|
||||||
|
--tw-space-y-reverse: 0;
|
||||||
|
margin-top: calc(.25rem * calc(1 - var(--tw-space-y-reverse)));
|
||||||
|
margin-bottom: calc(.25rem * var(--tw-space-y-reverse));
|
||||||
|
}
|
||||||
|
|
||||||
|
#table-totals>.totals-table-right-side>*> :nth-child(2) {
|
||||||
|
text-align: right;
|
||||||
|
padding-right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#entity-details {
|
||||||
|
text-align: left;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#entity-details th {
|
||||||
|
font-weight:normal;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#table-totals
|
||||||
|
> *
|
||||||
|
[data-element='total-table-balance-due-label'],
|
||||||
|
#table-totals
|
||||||
|
> *
|
||||||
|
[data-element='total-table-balance-due'] {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#table-totals > * > :last-child {
|
||||||
|
text-align: right;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-ref="total_table-footer"] {
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-ref="totals_table-outstanding"] {
|
||||||
|
color: var(--primary-color)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Markdown-specific styles. **/
|
||||||
|
[data-ref="table"] h3 {
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-ref="totals_table-outstanding-label"],
|
||||||
|
[data-ref="totals_table-outstanding"] {
|
||||||
|
background-color: #e6e6e6;
|
||||||
|
color: black;
|
||||||
|
padding-top: 7px;
|
||||||
|
padding-bottom: 7px;
|
||||||
|
padding-right: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-ref="statement-totals"] {
|
||||||
|
margin-top: 1rem;
|
||||||
|
text-align: right;
|
||||||
|
margin-right: .75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-ref*=".line_total-td"] {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repeating-footer,
|
||||||
|
.repeating-footer-space {
|
||||||
|
height: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repeating-header {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repeating-footer {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#header {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#footer {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
border-top: 1px solid #000;
|
||||||
|
width: 82%;
|
||||||
|
min-height:100px;
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
margin-top: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-element='product_table-product.description-td'], td {
|
||||||
|
min-width:100%;
|
||||||
|
max-width: 300px;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-ref="total_table-public_notes"] { font-weight: normal; }
|
||||||
|
[data-ref="total_table-terms"] { font-weight: normal; }
|
||||||
|
|
||||||
|
.stamp {
|
||||||
|
transform: rotate(12deg);
|
||||||
|
color: #555;
|
||||||
|
font-size: 3rem;
|
||||||
|
font-weight: 700;
|
||||||
|
border: 0.25rem solid #555;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.25rem 1rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
border-radius: 1rem;
|
||||||
|
font-family: 'Courier';
|
||||||
|
mix-blend-mode: multiply;
|
||||||
|
z-index:200 !important;
|
||||||
|
position: fixed;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-paid {
|
||||||
|
color: #D23;
|
||||||
|
border: 1rem double #D23;
|
||||||
|
transform: rotate(-5deg);
|
||||||
|
font-size: 6rem;
|
||||||
|
font-family: "Open sans", Helvetica, Arial, sans-serif;
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 0.5rem;
|
||||||
|
opacity: 0.2;
|
||||||
|
z-index:200 !important;
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
|
/** Useful snippets, uncomment to enable. **/
|
||||||
|
|
||||||
|
/** Hide company logo **/
|
||||||
|
/* .company-logo { display: none } */
|
||||||
|
|
||||||
|
/* Hide company details */
|
||||||
|
/* # > * { display: none } */
|
||||||
|
|
||||||
|
/* Hide company address */
|
||||||
|
/* #company-address > * { display: none } */
|
||||||
|
|
||||||
|
/* Hide terms label */
|
||||||
|
/* [data-ref="total_table-terms-label"] { display: none } */
|
||||||
|
|
||||||
|
/* Hide totals table */
|
||||||
|
/* #table-totals { display: none } */
|
||||||
|
|
||||||
|
/* Hide totals table left side */
|
||||||
|
/* #table-totals div:first-child > * { display: none !important } */
|
||||||
|
|
||||||
|
/* Hide totals table right side */
|
||||||
|
/* .totals-table-right-side { display: none } */
|
||||||
|
|
||||||
|
/** For more info, please check our docs: https://invoiceninja.github.io **/
|
||||||
|
/** To find out selectors on your own: https://invoiceninja.github.io/docs/custom-fields/#snippets **/
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div id="body">
|
||||||
|
<table style="min-width: 100%">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<div class="repeating-header-space"> </div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<div id="">
|
||||||
|
<div class="header-wrapper">
|
||||||
|
<div>
|
||||||
|
<img class="company-logo" src="$company.logo" alt="$company.name logo">
|
||||||
|
</div>
|
||||||
|
<div style="float:right; width:100%;">
|
||||||
|
<div id="company-details"></div>
|
||||||
|
<div id="company-address" style="margin-top:10px;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="header-wrapper2">
|
||||||
|
<div id="client-details"></div>
|
||||||
|
<div id="vendor-details"></div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p class="entity-label" style="font-size:32px; font-weight: bold; color:$primary_color;">$entity_label</p>
|
||||||
|
<table id="entity-details" cellspacing="0" dir="ltr"></table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table id="product-table" cellspacing="0" data-ref="table"></table>
|
||||||
|
<table id="task-table" cellspacing="0" data-ref="table"></table>
|
||||||
|
<table id="delivery-note-table" cellspacing="0" data-ref="table"></table>
|
||||||
|
<table id="statement-invoice-table" cellspacing="0" data-ref="table"></table>
|
||||||
|
<div id="statement-invoice-table-totals" data-ref="statement-totals"></div>
|
||||||
|
<table id="statement-payment-table" cellspacing="0" data-ref="table"></table>
|
||||||
|
<div id="statement-payment-table-totals" data-ref="statement-totals"></div>
|
||||||
|
<table id="statement-aging-table" cellspacing="0" data-ref="table"></table>
|
||||||
|
<div id="statement-aging-table-totals" data-ref="statement-totals"></div>
|
||||||
|
<div id="table-totals" cellspacing="0"></div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<div class="repeating-footer-space"> </div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="repeating-header" id="header"></div>
|
||||||
|
|
||||||
|
<div id="footer" style="">
|
||||||
|
<div style="width: 100%;">
|
||||||
|
<p data-ref="total_table-footer">$entity_footer</p>
|
||||||
|
<script>
|
||||||
|
// Clear up space a bit, if [product-table, tasks-table, delivery-note-table] isn't present.
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
let tables = [
|
||||||
|
'product-table', 'task-table', 'delivery-note-table',
|
||||||
|
'statement-invoice-table', 'statement-payment-table', 'statement-aging-table-totals',
|
||||||
|
'statement-invoice-table-totals', 'statement-payment-table-totals', 'statement-aging-table',
|
||||||
|
'vendor-details', 'client-details'
|
||||||
|
];
|
||||||
|
|
||||||
|
tables.forEach((tableIdentifier) => {
|
||||||
|
console.log(document.getElementById(tableIdentifier));
|
||||||
|
|
||||||
|
document.getElementById(tableIdentifier)?.childElementCount === 0
|
||||||
|
? document.getElementById(tableIdentifier).style.setProperty('display', 'none', 'important')
|
||||||
|
: '';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Clear up space a bit, if [product-table, tasks-table, delivery-note-table] isn't present.
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
let tables = [
|
||||||
|
'product-table', 'task-table', 'delivery-note-table',
|
||||||
|
'statement-invoice-table', 'statement-payment-table', 'statement-aging-table-totals',
|
||||||
|
'statement-invoice-table-totals', 'statement-payment-table-totals', 'statement-aging-table',
|
||||||
|
'client-details','vendor-details', 'swiss-qr'
|
||||||
|
];
|
||||||
|
|
||||||
|
tables.forEach((tableIdentifier) => {
|
||||||
|
console.log(document.getElementById(tableIdentifier));
|
||||||
|
|
||||||
|
document.getElementById(tableIdentifier)?.childElementCount === 0
|
||||||
|
? document.getElementById(tableIdentifier).style.setProperty('display', 'none', 'important')
|
||||||
|
: '';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
@ -257,6 +257,36 @@
|
|||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stamp {
|
||||||
|
transform: rotate(12deg);
|
||||||
|
color: #555;
|
||||||
|
font-size: 3rem;
|
||||||
|
font-weight: 700;
|
||||||
|
border: 0.25rem solid #555;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.25rem 1rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
border-radius: 1rem;
|
||||||
|
font-family: 'Courier';
|
||||||
|
mix-blend-mode: multiply;
|
||||||
|
z-index:200 !important;
|
||||||
|
position: fixed;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-paid {
|
||||||
|
color: #D23;
|
||||||
|
border: 1rem double #D23;
|
||||||
|
transform: rotate(-5deg);
|
||||||
|
font-size: 6rem;
|
||||||
|
font-family: "Open sans", Helvetica, Arial, sans-serif;
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 0.5rem;
|
||||||
|
opacity: 0.2;
|
||||||
|
z-index:200 !important;
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
/** Useful snippets, uncomment to enable. **/
|
/** Useful snippets, uncomment to enable. **/
|
||||||
|
|
||||||
/** Hide company logo **/
|
/** Hide company logo **/
|
||||||
|
@ -229,6 +229,35 @@
|
|||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stamp {
|
||||||
|
transform: rotate(12deg);
|
||||||
|
color: #555;
|
||||||
|
font-size: 3rem;
|
||||||
|
font-weight: 700;
|
||||||
|
border: 0.25rem solid #555;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.25rem 1rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
border-radius: 1rem;
|
||||||
|
font-family: 'Courier';
|
||||||
|
mix-blend-mode: multiply;
|
||||||
|
z-index:200 !important;
|
||||||
|
position: fixed;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-paid {
|
||||||
|
color: #D23;
|
||||||
|
border: 1rem double #D23;
|
||||||
|
transform: rotate(-5deg);
|
||||||
|
font-size: 6rem;
|
||||||
|
font-family: "Open sans", Helvetica, Arial, sans-serif;
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 0.5rem;
|
||||||
|
opacity: 0.2;
|
||||||
|
z-index:200 !important;
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
/** Useful snippets, uncomment to enable. **/
|
/** Useful snippets, uncomment to enable. **/
|
||||||
|
|
||||||
/** Hide company logo **/
|
/** Hide company logo **/
|
||||||
|
@ -234,6 +234,35 @@
|
|||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stamp {
|
||||||
|
transform: rotate(12deg);
|
||||||
|
color: #555;
|
||||||
|
font-size: 3rem;
|
||||||
|
font-weight: 700;
|
||||||
|
border: 0.25rem solid #555;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.25rem 1rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
border-radius: 1rem;
|
||||||
|
font-family: 'Courier';
|
||||||
|
mix-blend-mode: multiply;
|
||||||
|
z-index:200 !important;
|
||||||
|
position: fixed;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-paid {
|
||||||
|
color: #D23;
|
||||||
|
border: 1rem double #D23;
|
||||||
|
transform: rotate(-5deg);
|
||||||
|
font-size: 6rem;
|
||||||
|
font-family: "Open sans", Helvetica, Arial, sans-serif;
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 0.5rem;
|
||||||
|
opacity: 0.2;
|
||||||
|
z-index:200 !important;
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
/** Useful snippets, uncomment to enable. **/
|
/** Useful snippets, uncomment to enable. **/
|
||||||
|
|
||||||
/** Hide company logo **/
|
/** Hide company logo **/
|
||||||
|
@ -251,6 +251,35 @@
|
|||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stamp {
|
||||||
|
transform: rotate(12deg);
|
||||||
|
color: #555;
|
||||||
|
font-size: 3rem;
|
||||||
|
font-weight: 700;
|
||||||
|
border: 0.25rem solid #555;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.25rem 1rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
border-radius: 1rem;
|
||||||
|
font-family: 'Courier';
|
||||||
|
mix-blend-mode: multiply;
|
||||||
|
z-index:200 !important;
|
||||||
|
position: fixed;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-paid {
|
||||||
|
color: #D23;
|
||||||
|
border: 1rem double #D23;
|
||||||
|
transform: rotate(-5deg);
|
||||||
|
font-size: 6rem;
|
||||||
|
font-family: "Open sans", Helvetica, Arial, sans-serif;
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 0.5rem;
|
||||||
|
opacity: 0.2;
|
||||||
|
z-index:200 !important;
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
/** Useful snippets, uncomment to enable. **/
|
/** Useful snippets, uncomment to enable. **/
|
||||||
|
|
||||||
/** Hide company logo **/
|
/** Hide company logo **/
|
||||||
|
@ -278,6 +278,35 @@
|
|||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stamp {
|
||||||
|
transform: rotate(12deg);
|
||||||
|
color: #555;
|
||||||
|
font-size: 3rem;
|
||||||
|
font-weight: 700;
|
||||||
|
border: 0.25rem solid #555;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.25rem 1rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
border-radius: 1rem;
|
||||||
|
font-family: 'Courier';
|
||||||
|
mix-blend-mode: multiply;
|
||||||
|
z-index:200 !important;
|
||||||
|
position: fixed;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-paid {
|
||||||
|
color: #D23;
|
||||||
|
border: 1rem double #D23;
|
||||||
|
transform: rotate(-5deg);
|
||||||
|
font-size: 6rem;
|
||||||
|
font-family: "Open sans", Helvetica, Arial, sans-serif;
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 0.5rem;
|
||||||
|
opacity: 0.2;
|
||||||
|
z-index:200 !important;
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
/** Useful snippets, uncomment to enable. **/
|
/** Useful snippets, uncomment to enable. **/
|
||||||
|
|
||||||
/** Hide company logo **/
|
/** Hide company logo **/
|
||||||
|
@ -221,6 +221,35 @@
|
|||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stamp {
|
||||||
|
transform: rotate(12deg);
|
||||||
|
color: #555;
|
||||||
|
font-size: 3rem;
|
||||||
|
font-weight: 700;
|
||||||
|
border: 0.25rem solid #555;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.25rem 1rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
border-radius: 1rem;
|
||||||
|
font-family: 'Courier';
|
||||||
|
mix-blend-mode: multiply;
|
||||||
|
z-index:200 !important;
|
||||||
|
position: fixed;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-paid {
|
||||||
|
color: #D23;
|
||||||
|
border: 1rem double #D23;
|
||||||
|
transform: rotate(-5deg);
|
||||||
|
font-size: 6rem;
|
||||||
|
font-family: "Open sans", Helvetica, Arial, sans-serif;
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 0.5rem;
|
||||||
|
opacity: 0.2;
|
||||||
|
z-index:200 !important;
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
/** Useful snippets, uncomment to enable. **/
|
/** Useful snippets, uncomment to enable. **/
|
||||||
|
|
||||||
/** Hide company logo **/
|
/** Hide company logo **/
|
||||||
|
@ -294,6 +294,36 @@
|
|||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stamp {
|
||||||
|
transform: rotate(12deg);
|
||||||
|
color: #555;
|
||||||
|
font-size: 3rem;
|
||||||
|
font-weight: 700;
|
||||||
|
border: 0.25rem solid #555;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.25rem 1rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
border-radius: 1rem;
|
||||||
|
font-family: 'Courier';
|
||||||
|
mix-blend-mode: multiply;
|
||||||
|
z-index:200 !important;
|
||||||
|
position: fixed;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-paid {
|
||||||
|
color: #D23;
|
||||||
|
border: 1rem double #D23;
|
||||||
|
transform: rotate(-5deg);
|
||||||
|
font-size: 6rem;
|
||||||
|
font-family: "Open sans", Helvetica, Arial, sans-serif;
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 0.5rem;
|
||||||
|
opacity: 0.2;
|
||||||
|
z-index:200 !important;
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
/** Useful snippets, uncomment to enable. **/
|
/** Useful snippets, uncomment to enable. **/
|
||||||
|
|
||||||
/** Hide company logo **/
|
/** Hide company logo **/
|
||||||
@ -323,7 +353,7 @@
|
|||||||
/** For more info, please check our docs: https://invoiceninja.github.io **/
|
/** For more info, please check our docs: https://invoiceninja.github.io **/
|
||||||
/** To find out selectors on your own: https://invoiceninja.github.io/docs/custom-fields/#snippets **/
|
/** To find out selectors on your own: https://invoiceninja.github.io/docs/custom-fields/#snippets **/
|
||||||
</style>
|
</style>
|
||||||
|
<div id="body">
|
||||||
<table style="min-width: 100%">
|
<table style="min-width: 100%">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@ -335,7 +365,6 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<div id="body">
|
|
||||||
<div class="header-wrapper">
|
<div class="header-wrapper">
|
||||||
<div>
|
<div>
|
||||||
<img class="company-logo" src="$company.logo" alt="$company.name logo">
|
<img class="company-logo" src="$company.logo" alt="$company.name logo">
|
||||||
@ -368,7 +397,6 @@
|
|||||||
<table id="statement-aging-table" cellspacing="0" data-ref="table"></table>
|
<table id="statement-aging-table" cellspacing="0" data-ref="table"></table>
|
||||||
<div id="statement-aging-table-totals" data-ref="statement-totals"></div>
|
<div id="statement-aging-table-totals" data-ref="statement-totals"></div>
|
||||||
<div id="table-totals" cellspacing="0"></div>
|
<div id="table-totals" cellspacing="0"></div>
|
||||||
</div>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -380,7 +408,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
<div class="repeating-header">
|
<div class="repeating-header">
|
||||||
<div id="header">
|
<div id="header">
|
||||||
<div style="background-color: #00968B"><!-- 1 --></div>
|
<div style="background-color: #00968B"><!-- 1 --></div>
|
||||||
|
@ -258,6 +258,35 @@
|
|||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stamp {
|
||||||
|
transform: rotate(12deg);
|
||||||
|
color: #555;
|
||||||
|
font-size: 3rem;
|
||||||
|
font-weight: 700;
|
||||||
|
border: 0.25rem solid #555;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.25rem 1rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
border-radius: 1rem;
|
||||||
|
font-family: 'Courier';
|
||||||
|
mix-blend-mode: multiply;
|
||||||
|
z-index:200 !important;
|
||||||
|
position: fixed;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-paid {
|
||||||
|
color: #D23;
|
||||||
|
border: 1rem double #D23;
|
||||||
|
transform: rotate(-5deg);
|
||||||
|
font-size: 6rem;
|
||||||
|
font-family: "Open sans", Helvetica, Arial, sans-serif;
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 0.5rem;
|
||||||
|
opacity: 0.2;
|
||||||
|
z-index:200 !important;
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
/** Useful snippets, uncomment to enable. **/
|
/** Useful snippets, uncomment to enable. **/
|
||||||
|
|
||||||
/** Hide company logo **/
|
/** Hide company logo **/
|
||||||
|
@ -163,7 +163,7 @@
|
|||||||
@yield('footer')
|
@yield('footer')
|
||||||
@stack('footer')
|
@stack('footer')
|
||||||
|
|
||||||
@if((bool) \App\Utils\Ninja::isSelfHost() && !empty($client->getSetting('portal_custom_footer')))
|
@if($company && $company->account->isPaid() && !empty($client->getSetting('portal_custom_footer')))
|
||||||
<div class="py-1 text-sm text-center text-white bg-primary">
|
<div class="py-1 text-sm text-center text-white bg-primary">
|
||||||
{!! $client->getSetting('portal_custom_footer') !!}
|
{!! $client->getSetting('portal_custom_footer') !!}
|
||||||
</div>
|
</div>
|
||||||
|
@ -164,7 +164,7 @@
|
|||||||
@yield('footer')
|
@yield('footer')
|
||||||
@stack('footer')
|
@stack('footer')
|
||||||
|
|
||||||
@if((bool) \App\Utils\Ninja::isSelfHost() && !empty($settings->portal_custom_footer))
|
@if($company && $company->account->isPaid() && !empty($settings->portal_custom_footer))
|
||||||
<div class="py-1 text-sm text-center text-white bg-primary">
|
<div class="py-1 text-sm text-center text-white bg-primary">
|
||||||
{!! $settings->portal_custom_footer !!}
|
{!! $settings->portal_custom_footer !!}
|
||||||
</div>
|
</div>
|
||||||
|
@ -59,5 +59,3 @@ Route::get('checkout/3ds_redirect/{company_key}/{company_gateway_id}/{hash}', [C
|
|||||||
Route::get('mollie/3ds_redirect/{company_key}/{company_gateway_id}/{hash}', [Mollie3dsController::class, 'index'])->middleware('domain_db')->name('mollie.3ds_redirect');
|
Route::get('mollie/3ds_redirect/{company_key}/{company_gateway_id}/{hash}', [Mollie3dsController::class, 'index'])->middleware('domain_db')->name('mollie.3ds_redirect');
|
||||||
Route::get('gocardless/ibp_redirect/{company_key}/{company_gateway_id}/{hash}', [GoCardlessController::class, 'ibpRedirect'])->middleware('domain_db')->name('gocardless.ibp_redirect');
|
Route::get('gocardless/ibp_redirect/{company_key}/{company_gateway_id}/{hash}', [GoCardlessController::class, 'ibpRedirect'])->middleware('domain_db')->name('gocardless.ibp_redirect');
|
||||||
Route::get('.well-known/apple-developer-merchantid-domain-association', [ApplePayDomainController::class, 'showAppleMerchantId']);
|
Route::get('.well-known/apple-developer-merchantid-domain-association', [ApplePayDomainController::class, 'showAppleMerchantId']);
|
||||||
|
|
||||||
Route::fallback([BaseController::class, 'reactCatch']);
|
|
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
namespace Tests\Feature;
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use App\Factory\InvoiceItemFactory;
|
||||||
use App\Factory\InvoiceToRecurringInvoiceFactory;
|
use App\Factory\InvoiceToRecurringInvoiceFactory;
|
||||||
use App\Factory\RecurringInvoiceToInvoiceFactory;
|
use App\Factory\RecurringInvoiceToInvoiceFactory;
|
||||||
use App\Models\Client;
|
use App\Models\Client;
|
||||||
@ -51,6 +52,53 @@ class RecurringInvoiceTest extends TestCase
|
|||||||
$this->makeTestData();
|
$this->makeTestData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testPostRecurringInvoiceWithPlaceholderVariables()
|
||||||
|
{
|
||||||
|
$line_items = [];
|
||||||
|
|
||||||
|
$item = InvoiceItemFactory::create();
|
||||||
|
$item->quantity = 1;
|
||||||
|
$item->cost = 10;
|
||||||
|
$item->task_id = $this->encodePrimaryKey($this->task->id);
|
||||||
|
$item->expense_id = $this->encodePrimaryKey($this->expense->id);
|
||||||
|
$item->notes = "Hello this is the month of :MONTH";
|
||||||
|
|
||||||
|
$line_items[] = $item;
|
||||||
|
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'frequency_id' => 1,
|
||||||
|
'status_id' => 1,
|
||||||
|
'discount' => 0,
|
||||||
|
'is_amount_discount' => 1,
|
||||||
|
'po_number' => '3434343',
|
||||||
|
'public_notes' => 'notes',
|
||||||
|
'is_deleted' => 0,
|
||||||
|
'custom_value1' => 0,
|
||||||
|
'custom_value2' => 0,
|
||||||
|
'custom_value3' => 0,
|
||||||
|
'custom_value4' => 0,
|
||||||
|
'status' => 1,
|
||||||
|
'client_id' => $this->encodePrimaryKey($this->client->id),
|
||||||
|
'line_items' => $line_items,
|
||||||
|
'remaining_cycles' => -1,
|
||||||
|
];
|
||||||
|
|
||||||
|
$response = $this->withHeaders([
|
||||||
|
'X-API-SECRET' => config('ninja.api_secret'),
|
||||||
|
'X-API-TOKEN' => $this->token,
|
||||||
|
])->post('/api/v1/recurring_invoices/', $data)
|
||||||
|
->assertStatus(200);
|
||||||
|
|
||||||
|
$arr = $response->json();
|
||||||
|
$this->assertEquals(RecurringInvoice::STATUS_DRAFT, $arr['data']['status_id']);
|
||||||
|
|
||||||
|
$notes = end($arr['data']['line_items'])['notes'];
|
||||||
|
|
||||||
|
$this->assertTrue(str_contains($notes, ':MONTH'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public function testPostRecurringInvoice()
|
public function testPostRecurringInvoice()
|
||||||
{
|
{
|
||||||
$data = [
|
$data = [
|
||||||
|
@ -48,27 +48,4 @@ class AutoBillInvoiceTest extends TestCase
|
|||||||
$this->assertEquals($this->client->fresh()->credit_balance, 0);
|
$this->assertEquals($this->client->fresh()->credit_balance, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// public function testAutoBillSetOffFunctionality()
|
|
||||||
// {
|
|
||||||
|
|
||||||
// $settings = $this->company->settings;
|
|
||||||
// $settings->use_credits_payment = 'off';
|
|
||||||
|
|
||||||
// $this->company->settings = $settings;
|
|
||||||
// $this->company->save();
|
|
||||||
|
|
||||||
// $this->assertEquals($this->client->balance, 10);
|
|
||||||
// $this->assertEquals($this->client->paid_to_date, 0);
|
|
||||||
// $this->assertEquals($this->client->credit_balance, 10);
|
|
||||||
|
|
||||||
// $this->invoice->service()->markSent()->autoBill()->save();
|
|
||||||
|
|
||||||
// $this->assertNotNull($this->invoice->payments());
|
|
||||||
// $this->assertEquals(0, $this->invoice->payments()->sum('payments.amount'));
|
|
||||||
|
|
||||||
// $this->assertEquals($this->client->balance, 10);
|
|
||||||
// $this->assertEquals($this->client->paid_to_date, 0);
|
|
||||||
// $this->assertEquals($this->client->credit_balance, 10);
|
|
||||||
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
86
tests/Unit/LateFeeTest.php
Normal file
86
tests/Unit/LateFeeTest.php
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
<?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\Unit;
|
||||||
|
|
||||||
|
use App\DataMapper\InvoiceItem;
|
||||||
|
use App\Models\Invoice;
|
||||||
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||||
|
use Tests\MockAccountData;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
class LateFeeTest extends TestCase
|
||||||
|
{
|
||||||
|
use DatabaseTransactions;
|
||||||
|
use MockAccountData;
|
||||||
|
|
||||||
|
protected function setUp() :void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->makeTestData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLateFeeBalances()
|
||||||
|
{
|
||||||
|
|
||||||
|
$this->assertEquals(10, $this->client->balance);
|
||||||
|
$this->assertEquals(10, $this->invoice->balance);
|
||||||
|
|
||||||
|
$this->invoice = $this->setLateFee($this->invoice, 5, 0);
|
||||||
|
|
||||||
|
$this->assertEquals(15, $this->client->fresh()->balance);
|
||||||
|
$this->assertEquals(15, $this->invoice->fresh()->balance);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function setLateFee($invoice, $amount, $percent) :Invoice
|
||||||
|
{
|
||||||
|
|
||||||
|
$temp_invoice_balance = $invoice->balance;
|
||||||
|
|
||||||
|
if ($amount <= 0 && $percent <= 0) {
|
||||||
|
return $invoice;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fee = $amount;
|
||||||
|
|
||||||
|
if ($invoice->partial > 0) {
|
||||||
|
$fee += round($invoice->partial * $percent / 100, 2);
|
||||||
|
} else {
|
||||||
|
$fee += round($invoice->balance * $percent / 100, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
$invoice_item = new InvoiceItem;
|
||||||
|
$invoice_item->type_id = '5';
|
||||||
|
$invoice_item->product_key = trans('texts.fee');
|
||||||
|
$invoice_item->notes = ctrans('texts.late_fee_added', ['date' => now()]);
|
||||||
|
$invoice_item->quantity = 1;
|
||||||
|
$invoice_item->cost = $fee;
|
||||||
|
|
||||||
|
$invoice_items = $invoice->line_items;
|
||||||
|
$invoice_items[] = $invoice_item;
|
||||||
|
|
||||||
|
$invoice->line_items = $invoice_items;
|
||||||
|
|
||||||
|
/**Refresh Invoice values*/
|
||||||
|
$invoice = $invoice->calc()->getInvoice();
|
||||||
|
|
||||||
|
$invoice->client->service()->updateBalance($invoice->balance - $temp_invoice_balance)->save();
|
||||||
|
$invoice->ledger()->updateInvoiceBalance($invoice->balance - $temp_invoice_balance, "Late Fee Adjustment for invoice {$invoice->number}");
|
||||||
|
|
||||||
|
return $invoice;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user