Merge pull request #8650 from turbo124/v5-stable

v5.6.19
This commit is contained in:
David Bomba 2023-07-16 12:46:04 +10:00 committed by GitHub
commit 7f9b784b20
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
78 changed files with 148394 additions and 146823 deletions

View File

@ -1 +1 @@
5.6.18
5.6.19

View File

@ -17,7 +17,7 @@ class EncryptedCast implements CastsAttributes
{
public function get($model, string $key, $value, array $attributes)
{
return strlen($value) > 1 ? decrypt($value) : null;
return is_string($value) && strlen($value) > 1 ? decrypt($value) : null;
}
public function set($model, string $key, $value, array $attributes)

View File

@ -13,6 +13,7 @@ namespace App\Export\CSV;
use App\Utils\Number;
use App\Models\Client;
use App\Models\Expense;
use App\Models\Invoice;
use App\Models\GatewayType;
use App\Models\Payment;
@ -42,9 +43,41 @@ class BaseExport
public array $forced_keys = [];
protected array $vendor_report_keys = [
'address1' => 'vendor.address1',
'address2' => 'vendor.address2',
'city' => 'vendor.city',
'country' => 'vendor.country_id',
'custom_value1' => 'vendor.custom_value1',
'custom_value2' => 'vendor.custom_value2',
'custom_value3' => 'vendor.custom_value3',
'custom_value4' => 'vendor.custom_value4',
'id_number' => 'vendor.id_number',
'name' => 'vendor.name',
'number' => 'vendor.number',
'client_phone' => 'vendor.phone',
'postal_code' => 'vendor.postal_code',
'private_notes' => 'vendor.private_notes',
'public_notes' => 'vendor.public_notes',
'state' => 'vendor.state',
'vat_number' => 'vendor.vat_number',
'website' => 'vendor.website',
'currency' => 'vendor.currency',
'first_name' => 'vendor_contact.first_name',
'last_name' => 'vendor_contact.last_name',
'contact_phone' => 'vendor_contact.phone',
'contact_custom_value1' => 'vendor_contact.custom_value1',
'contact_custom_value2' => 'vendor_contact.custom_value2',
'contact_custom_value3' => 'vendor_contact.custom_value3',
'contact_custom_value4' => 'vendor_contact.custom_value4',
'email' => 'vendor_contact.email',
'status' => 'vendor.status',
];
protected array $client_report_keys = [
"name" => "client.name",
"user" => "client.user_id",
"user" => "client.user",
"assigned_user" => "client.assigned_user",
"balance" => "client.balance",
"paid_to_date" => "client.paid_to_date",
"currency" => "client.currency_id",
@ -103,6 +136,42 @@ class BaseExport
"user" => "invoice.user_id",
];
protected array $purchase_order_report_keys = [
'amount' => 'purchase_order.amount',
'balance' => 'purchase_order.balance',
'vendor' => 'purchase_order.vendor_id',
// 'custom_surcharge1' => 'purchase_order.custom_surcharge1',
// 'custom_surcharge2' => 'purchase_order.custom_surcharge2',
// 'custom_surcharge3' => 'purchase_order.custom_surcharge3',
// 'custom_surcharge4' => 'purchase_order.custom_surcharge4',
'custom_value1' => 'purchase_order.custom_value1',
'custom_value2' => 'purchase_order.custom_value2',
'custom_value3' => 'purchase_order.custom_value3',
'custom_value4' => 'purchase_order.custom_value4',
'date' => 'purchase_order.date',
'discount' => 'purchase_order.discount',
'due_date' => 'purchase_order.due_date',
'exchange_rate' => 'purchase_order.exchange_rate',
'footer' => 'purchase_order.footer',
'number' => 'purchase_order.number',
'paid_to_date' => 'purchase_order.paid_to_date',
'partial' => 'purchase_order.partial',
'partial_due_date' => 'purchase_order.partial_due_date',
'po_number' => 'purchase_order.po_number',
'private_notes' => 'purchase_order.private_notes',
'public_notes' => 'purchase_order.public_notes',
'status' => 'purchase_order.status_id',
'tax_name1' => 'purchase_order.tax_name1',
'tax_name2' => 'purchase_order.tax_name2',
'tax_name3' => 'purchase_order.tax_name3',
'tax_rate1' => 'purchase_order.tax_rate1',
'tax_rate2' => 'purchase_order.tax_rate2',
'tax_rate3' => 'purchase_order.tax_rate3',
'terms' => 'purchase_order.terms',
'total_taxes' => 'purchase_order.total_taxes',
'currency_id' => 'purchase_order.currency_id',
];
protected array $item_report_keys = [
"quantity" => "item.quantity",
"cost" => "item.cost",
@ -197,6 +266,58 @@ class BaseExport
"assigned_user" => "payment.assigned_user_id",
];
protected array $expense_report_keys = [
'amount' => 'expense.amount',
'category' => 'expense.category_id',
'client' => 'expense.client_id',
'custom_value1' => 'expense.custom_value1',
'custom_value2' => 'expense.custom_value2',
'custom_value3' => 'expense.custom_value3',
'custom_value4' => 'expense.custom_value4',
'currency' => 'expense.currency_id',
'date' => 'expense.date',
'exchange_rate' => 'expense.exchange_rate',
'converted_amount' => 'expense.foreign_amount',
'invoice_currency_id' => 'expense.invoice_currency_id',
'payment_date' => 'expense.payment_date',
'number' => 'expense.number',
'payment_type_id' => 'expense.payment_type_id',
'private_notes' => 'expense.private_notes',
'project' => 'expense.project_id',
'public_notes' => 'expense.public_notes',
'tax_amount1' => 'expense.tax_amount1',
'tax_amount2' => 'expense.tax_amount2',
'tax_amount3' => 'expense.tax_amount3',
'tax_name1' => 'expense.tax_name1',
'tax_name2' => 'expense.tax_name2',
'tax_name3' => 'expense.tax_name3',
'tax_rate1' => 'expense.tax_rate1',
'tax_rate2' => 'expense.tax_rate2',
'tax_rate3' => 'expense.tax_rate3',
'transaction_reference' => 'expense.transaction_reference',
'vendor' => 'expense.vendor_id',
'invoice' => 'expense.invoice_id',
'user' => 'expense.user',
'assigned_user' => 'expense.assigned_user',
];
protected array $task_report_keys = [
'start_date' => 'task.start_date',
'end_date' => 'task.end_date',
'duration' => 'task.duration',
'rate' => 'task.rate',
'number' => 'task.number',
'description' => 'task.description',
'custom_value1' => 'task.custom_value1',
'custom_value2' => 'task.custom_value2',
'custom_value3' => 'task.custom_value3',
'custom_value4' => 'task.custom_value4',
'status' => 'task.status_id',
'project' => 'task.project_id',
'invoice' => 'task.invoice_id',
'client' => 'task.client_id',
];
protected function filterByClients($query)
{
if (isset($this->input['client_id']) && $this->input['client_id'] != 'all') {
@ -222,7 +343,11 @@ class BaseExport
match($parts[0]) {
'contact' => $value = $this->resolveClientContactKey($parts[1], $entity, $transformer),
'client' => $value = $this->resolveClientKey($parts[1], $entity, $transformer),
'expense' => $value = $this->resolveExpenseKey($parts[1], $entity, $transformer),
'vendor' => $value = $this->resolveVendorKey($parts[1], $entity, $transformer),
'vendor_contact' => $value = $this->resolveVendorContactKey($parts[1], $entity, $transformer),
'invoice' => $value = $this->resolveInvoiceKey($parts[1], $entity, $transformer),
'purchase_order' => $value = $this->resolvePurchaseOrderKey($parts[1], $entity, $transformer),
'payment' => $value = $this->resolvePaymentKey($parts[1], $entity, $transformer),
default => $value = ''
};
@ -233,14 +358,106 @@ class BaseExport
private function resolveClientContactKey($column, $entity, $transformer)
{
if(!$entity->client) {
return "";
}
$primary_contact = $entity->client->primary_contact()->first() ?? $entity->client->contacts()->first();
return $primary_contact?->{$column} ?? '';
return $primary_contact ? $primary_contact?->{$column} ?? '' : '';
}
private function resolveVendorContactKey($column, $entity, $transformer)
{
if(!$entity->vendor)
return "";
$primary_contact = $entity->vendor->primary_contact()->first() ?? $entity->vendor->contacts()->first();
return $primary_contact ? $primary_contact?->{$column} ?? '' : '';
}
private function resolveExpenseKey($column, $entity, $transformer)
{
if($column == 'user' && $entity?->expense?->user)
return $entity->expense->user->present()->name() ?? ' ';
if($column == 'assigned_user' && $entity?->expense?->assigned_user)
return $entity->expense->assigned_user->present()->name() ?? ' ';
if($column == 'category' && $entity->expense) {
return $entity->expense->category?->name ?? ' ';
}
if($entity instanceof Expense)
return '';
$transformed_entity = $transformer->includeExpense($entity);
$manager = new Manager();
$manager->setSerializer(new ArraySerializer());
$transformed_entity = $manager->createData($transformed_entity)->toArray();
if(array_key_exists($column, $transformed_entity))
return $transformed_entity[$column];
if(property_exists($entity, $column))
return $entity?->{$column} ?? '';
nlog("export: Could not resolve expense key: {$column}");
return '';
}
private function resolveVendorKey($column, $entity, $transformer)
{
if(!$entity->vendor)
return '';
$transformed_entity = $transformer->includeVendor($entity);
$manager = new Manager();
$manager->setSerializer(new ArraySerializer());
$transformed_entity = $manager->createData($transformed_entity)->toArray();
if($column == 'name')
return $entity->vendor->present()->name() ?: '';
if($column == 'user_id')
return $entity->vendor->user->present()->name() ?: '';
if($column == 'country_id')
return $entity->vendor->country ? ctrans("texts.country_{$entity->vendor->country->name}") : '';
if ($column == 'currency_id') {
return $entity->vendor->currency() ? $entity->vendor->currency()->code : $entity->company->currency()->code;
}
if($column == 'status')
return $entity->stringStatus($entity->status_id) ?: '';
if(array_key_exists($column, $transformed_entity))
return $transformed_entity[$column];
nlog("export: Could not resolve vendor key: {$column}");
return '';
}
private function resolveClientKey($column, $entity, $transformer)
{
if(!$entity->client)
return '';
$transformed_client = $transformer->includeClient($entity);
$manager = new Manager();
@ -282,6 +499,18 @@ class BaseExport
}
private function resolvePurchaseOrderKey($column, $entity, $transformer)
{
nlog("searching for {$column}");
$transformed_entity = $transformer->transform($entity);
if($column == 'status')
return $entity->stringStatus($entity->status_id);
return '';
}
private function resolveInvoiceKey($column, $entity, $transformer)
{
nlog("searching for {$column}");
@ -496,6 +725,8 @@ class BaseExport
{
$header = [];
// nlog($this->input['report_keys']);
foreach (array_merge($this->input['report_keys'], $this->forced_keys) as $value) {
$key = array_search($value, $this->entity_keys);
@ -503,48 +734,75 @@ class BaseExport
$prefix = '';
if(!$key) {
$prefix = stripos($value, 'client.') !== false ? ctrans('texts.client') : ctrans('texts.contact');
$prefix = stripos($value, 'client.') !== false ? ctrans('texts.client')." " : ctrans('texts.contact')." ";
$key = array_search($value, $this->client_report_keys);
}
if(!$key) {
$prefix = ctrans('texts.invoice');
$prefix = ctrans('texts.invoice')." ";
$key = array_search($value, $this->invoice_report_keys);
}
if(!$key) {
$prefix = ctrans('texts.payment');
$prefix = ctrans('texts.payment')." ";
$key = array_search($value, $this->payment_report_keys);
}
if(!$key) {
$prefix = ctrans('texts.quote');
$prefix = ctrans('texts.quote')." ";
$key = array_search($value, $this->quote_report_keys);
}
if(!$key) {
$prefix = ctrans('texts.credit');
$prefix = ctrans('texts.credit')." ";
$key = array_search($value, $this->credit_report_keys);
}
if(!$key) {
$prefix = ctrans('texts.item');
$prefix = ctrans('texts.item')." ";
$key = array_search($value, $this->item_report_keys);
}
if(!$key) {
$prefix = ctrans('texts.expense')." ";
$key = array_search($value, $this->expense_report_keys);
}
if(!$key) {
$prefix = ctrans('texts.task')." ";
$key = array_search($value, $this->task_report_keys);
}
if(!$key) {
$prefix = ctrans('texts.vendor')." ";
$key = array_search($value, $this->vendor_report_keys);
}
if(!$key) {
$prefix = ctrans('texts.purchase_order')." ";
$key = array_search($value, $this->purchase_order_report_keys);
}
if(!$key) {
$prefix = '';
}
$key = str_replace('item.', '', $key);
$key = str_replace('recurring_invoice.', '', $key);
$key = str_replace('invoice.', '', $key);
$key = str_replace('quote.', '', $key);
$key = str_replace('credit.', '', $key);
$key = str_replace('task.', '', $key);
$key = str_replace('client.', '', $key);
$key = str_replace('vendor.', '', $key);
$key = str_replace('contact.', '', $key);
$key = str_replace('payment.', '', $key);
$key = str_replace('expense.', '', $key);
$header[] = "{$prefix} " . ctrans("texts.{$key}");
$header[] = "{$prefix}" . ctrans("texts.{$key}");
}
// nlog($header);
return $header;
}

View File

@ -82,7 +82,6 @@ class ClientExport extends BaseExport
];
public array $forced_keys = [
'status',
];
public function __construct(Company $company, array $input)
@ -132,6 +131,8 @@ class ClientExport extends BaseExport
$transformed_client = $this->client_transformer->transform($client);
$transformed_contact = [];
if ($contact = $client->contacts()->first()) {
$transformed_contact = $this->contact_transformer->transform($contact);
}
@ -140,15 +141,13 @@ class ClientExport extends BaseExport
foreach (array_values($this->input['report_keys']) as $key) {
$parts = explode('.', $key);
$keyval = array_search($key, $this->entity_keys);
if (is_array($parts) && $parts[0] == 'client' && array_key_exists($parts[1], $transformed_client)) {
$entity[$keyval] = $transformed_client[$parts[1]];
$entity[$key] = $transformed_client[$parts[1]];
} elseif (is_array($parts) && $parts[0] == 'contact' && array_key_exists($parts[1], $transformed_contact)) {
$entity[$keyval] = $transformed_contact[$parts[1]];
$entity[$key] = $transformed_contact[$parts[1]];
} else {
$entity[$keyval] = '';
$entity[$key] = '';
}
}
@ -157,6 +156,14 @@ class ClientExport extends BaseExport
private function decorateAdvancedFields(Client $client, array $entity) :array
{
if (in_array('client.user', $this->input['report_keys'])) {
$entity['client.user'] = $client->user->present()->name();
}
if (in_array('client.assigned_user', $this->input['report_keys'])) {
$entity['client.assigned_user'] = $client->assigned_user ? $client->user->present()->name() : '';
}
if (in_array('client.country_id', $this->input['report_keys'])) {
$entity['country'] = $client->country ? ctrans("texts.country_{$client->country->name}") : '';
}
@ -173,8 +180,6 @@ class ClientExport extends BaseExport
$entity['industry_id'] = $client->industry ? ctrans("texts.industry_{$client->industry->name}") : '';
}
$entity['status'] = $this->calculateStatus($client);
return $entity;
}
@ -185,7 +190,7 @@ class ClientExport extends BaseExport
}
if ($client->deleted_at) {
return ctrans('texts.arcvived');
return ctrans('texts.archived');
}
return ctrans('texts.active');

View File

@ -30,36 +30,38 @@ class ExpenseExport extends BaseExport
public Writer $csv;
public array $entity_keys = [
'amount' => 'amount',
'category' => 'category_id',
'client' => 'client_id',
'custom_value1' => 'custom_value1',
'custom_value2' => 'custom_value2',
'custom_value3' => 'custom_value3',
'custom_value4' => 'custom_value4',
'currency' => 'currency_id',
'date' => 'date',
'exchange_rate' => 'exchange_rate',
'converted_amount' => 'foreign_amount',
'invoice_currency_id' => 'invoice_currency_id',
'payment_date' => 'payment_date',
'number' => 'number',
'payment_type_id' => 'payment_type_id',
'private_notes' => 'private_notes',
'project' => 'project_id',
'public_notes' => 'public_notes',
'tax_amount1' => 'tax_amount1',
'tax_amount2' => 'tax_amount2',
'tax_amount3' => 'tax_amount3',
'tax_name1' => 'tax_name1',
'tax_name2' => 'tax_name2',
'tax_name3' => 'tax_name3',
'tax_rate1' => 'tax_rate1',
'tax_rate2' => 'tax_rate2',
'tax_rate3' => 'tax_rate3',
'transaction_reference' => 'transaction_reference',
'vendor' => 'vendor_id',
'invoice' => 'invoice_id',
'amount' => 'expense.amount',
'category' => 'expense.category',
'client' => 'expense.client_id',
'custom_value1' => 'expense.custom_value1',
'custom_value2' => 'expense.custom_value2',
'custom_value3' => 'expense.custom_value3',
'custom_value4' => 'expense.custom_value4',
'currency' => 'expense.currency_id',
'date' => 'expense.date',
'exchange_rate' => 'expense.exchange_rate',
'converted_amount' => 'expense.foreign_amount',
'invoice_currency_id' => 'expense.invoice_currency_id',
'payment_date' => 'expense.payment_date',
'number' => 'expense.number',
'payment_type_id' => 'expense.payment_type_id',
'private_notes' => 'expense.private_notes',
'project' => 'expense.project_id',
'public_notes' => 'expense.public_notes',
'tax_amount1' => 'expense.tax_amount1',
'tax_amount2' => 'expense.tax_amount2',
'tax_amount3' => 'expense.tax_amount3',
'tax_name1' => 'expense.tax_name1',
'tax_name2' => 'expense.tax_name2',
'tax_name3' => 'expense.tax_name3',
'tax_rate1' => 'expense.tax_rate1',
'tax_rate2' => 'expense.tax_rate2',
'tax_rate3' => 'expense.tax_rate3',
'transaction_reference' => 'expense.transaction_reference',
'vendor' => 'expense.vendor_id',
'invoice' => 'expense.invoice_id',
'user' => 'expense.user',
'assigned_user' => 'expense.assigned_user',
];
private array $decorate_keys = [
@ -120,13 +122,17 @@ class ExpenseExport extends BaseExport
$entity = [];
foreach (array_values($this->input['report_keys']) as $key) {
$parts = explode('.', $key);
$keyval = array_search($key, $this->entity_keys);
if (array_key_exists($key, $transformed_expense)) {
$entity[$keyval] = $transformed_expense[$key];
if (is_array($parts) && $parts[0] == 'expense' && array_key_exists($parts[1], $transformed_expense)) {
$entity[$key] = $transformed_expense[$parts[1]];
} elseif (array_key_exists($key, $transformed_expense)) {
$entity[$key] = $transformed_expense[$key];
} else {
$entity[$keyval] = '';
$entity[$key] = $this->resolveKey($key, $expense, $this->expense_transformer);
}
}
return $this->decorateAdvancedFields($expense, $entity);
@ -134,32 +140,40 @@ class ExpenseExport extends BaseExport
private function decorateAdvancedFields(Expense $expense, array $entity) :array
{
if (in_array('currency_id', $this->input['report_keys'])) {
$entity['currency'] = $expense->currency ? $expense->currency->code : '';
if (in_array('expense.currency_id', $this->input['report_keys'])) {
$entity['expense.currency_id'] = $expense->currency ? $expense->currency->code : '';
}
if (in_array('client_id', $this->input['report_keys'])) {
$entity['client'] = $expense->client ? $expense->client->present()->name() : '';
if (in_array('expense.client_id', $this->input['report_keys'])) {
$entity['expense.client'] = $expense->client ? $expense->client->present()->name() : '';
}
if (in_array('invoice_id', $this->input['report_keys'])) {
$entity['invoice'] = $expense->invoice ? $expense->invoice->number : '';
if (in_array('expense.invoice_id', $this->input['report_keys'])) {
$entity['expense.invoice_id'] = $expense->invoice ? $expense->invoice->number : '';
}
if (in_array('category_id', $this->input['report_keys'])) {
$entity['category'] = $expense->category ? $expense->category->name : '';
if (in_array('expense.category', $this->input['report_keys'])) {
$entity['expense.category'] = $expense->category ? $expense->category->name : '';
}
if (in_array('vendor_id', $this->input['report_keys'])) {
$entity['vendor'] = $expense->vendor ? $expense->vendor->name : '';
if (in_array('expense.vendor_id', $this->input['report_keys'])) {
$entity['expense.vendor'] = $expense->vendor ? $expense->vendor->name : '';
}
if (in_array('payment_type_id', $this->input['report_keys'])) {
$entity['payment_type'] = $expense->payment_type ? $expense->payment_type->name : '';
if (in_array('expense.payment_type_id', $this->input['report_keys'])) {
$entity['expense.payment_type_id'] = $expense->payment_type ? $expense->payment_type->name : '';
}
if (in_array('project_id', $this->input['report_keys'])) {
$entity['project'] = $expense->project ? $expense->project->name : '';
if (in_array('expense.project_id', $this->input['report_keys'])) {
$entity['expense.project_id'] = $expense->project ? $expense->project->name : '';
}
if (in_array('expense.user', $this->input['report_keys'])) {
$entity['expense.user'] = $expense->user ? $expense->user->present()->name() : '';
}
if (in_array('expense.assigned_user', $this->input['report_keys'])) {
$entity['expense.assigned_user'] = $expense->assigned_user ? $expense->assigned_user->present()->name() : '';
}
return $entity;

View File

@ -0,0 +1,172 @@
<?php
/**
* PurchaseOrder Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. PurchaseOrder Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Export\CSV;
use App\Libraries\MultiDB;
use App\Models\Company;
use App\Models\PurchaseOrder;
use App\Transformers\PurchaseOrderTransformer;
use App\Utils\Ninja;
use App\Utils\Number;
use Illuminate\Support\Facades\App;
use League\Csv\Writer;
class PurchaseOrderExport extends BaseExport
{
private Company $company;
private $purchase_order_transformer;
public string $date_key = 'date';
public Writer $csv;
public array $entity_keys = [
'amount' => 'amount',
'balance' => 'balance',
'vendor' => 'vendor_id',
'custom_surcharge1' => 'custom_surcharge1',
'custom_surcharge2' => 'custom_surcharge2',
'custom_surcharge3' => 'custom_surcharge3',
'custom_surcharge4' => 'custom_surcharge4',
'custom_value1' => 'custom_value1',
'custom_value2' => 'custom_value2',
'custom_value3' => 'custom_value3',
'custom_value4' => 'custom_value4',
'date' => 'date',
'discount' => 'discount',
'due_date' => 'due_date',
'exchange_rate' => 'exchange_rate',
'footer' => 'footer',
'number' => 'number',
'paid_to_date' => 'paid_to_date',
'partial' => 'partial',
'partial_due_date' => 'partial_due_date',
'po_number' => 'po_number',
'private_notes' => 'private_notes',
'public_notes' => 'public_notes',
'status' => 'status_id',
'tax_name1' => 'tax_name1',
'tax_name2' => 'tax_name2',
'tax_name3' => 'tax_name3',
'tax_rate1' => 'tax_rate1',
'tax_rate2' => 'tax_rate2',
'tax_rate3' => 'tax_rate3',
'terms' => 'terms',
'total_taxes' => 'total_taxes',
'currency_id' => 'currency_id',
];
private array $decorate_keys = [
'country',
'currency_id',
'status',
'vendor',
'project',
];
public function __construct(Company $company, array $input)
{
$this->company = $company;
$this->input = $input;
$this->purchase_order_transformer = new PurchaseOrderTransformer();
}
public function run()
{
MultiDB::setDb($this->company->db);
App::forgetInstance('translator');
App::setLocale($this->company->locale());
$t = app('translator');
$t->replace(Ninja::transformTranslations($this->company->settings));
//load the CSV document from a string
$this->csv = Writer::createFromString();
if (count($this->input['report_keys']) == 0) {
$this->input['report_keys'] = array_values($this->entity_keys);
}
//insert the header
$this->csv->insertOne($this->buildHeader());
$query = PurchaseOrder::query()
->withTrashed()
->with('vendor')
->where('company_id', $this->company->id)
->where('is_deleted', 0);
$query = $this->addDateRange($query);
// if(isset($this->input['status'])) {
// $query = $this->addPurchaseOrderStatusFilter($query, $this->input['status']);
// }
$query->cursor()
->each(function ($purchase_order) {
$this->csv->insertOne($this->buildRow($purchase_order));
});
return $this->csv->toString();
}
private function buildRow(PurchaseOrder $purchase_order) :array
{
$transformed_purchase_order = $this->purchase_order_transformer->transform($purchase_order);
$entity = [];
foreach (array_values($this->input['report_keys']) as $key) {
$keyval = array_search($key, $this->entity_keys);
if(!$keyval) {
$keyval = array_search(str_replace("invoice.", "", $key), $this->entity_keys) ?? $key;
}
if(!$keyval) {
$keyval = $key;
}
if (array_key_exists($key, $transformed_purchase_order)) {
$entity[$keyval] = $transformed_purchase_order[$key];
} elseif (array_key_exists($keyval, $transformed_purchase_order)) {
$entity[$keyval] = $transformed_purchase_order[$keyval];
} else {
$entity[$keyval] = $this->resolveKey($keyval, $purchase_order, $this->purchase_order_transformer);
}
}
return $this->decorateAdvancedFields($purchase_order, $entity);
}
private function decorateAdvancedFields(PurchaseOrder $purchase_order, array $entity) :array
{
if (in_array('country_id', $this->input['report_keys'])) {
$entity['country'] = $purchase_order->vendor->country ? ctrans("texts.country_{$purchase_order->vendor->country->name}") : '';
}
if (in_array('currency_id', $this->input['report_keys'])) {
$entity['currency_id'] = $purchase_order->vendor->currency() ? $purchase_order->vendor->currency()->code : $purchase_order->company->currency()->code;
}
if (in_array('vendor_id', $this->input['report_keys'])) {
$entity['vendor'] = $purchase_order->vendor->present()->name();
}
if (in_array('status_id', $this->input['report_keys'])) {
$entity['status'] = $purchase_order->stringStatus($purchase_order->status_id);
}
return $entity;
}
}

View File

@ -0,0 +1,246 @@
<?php
/**
* PurchaseOrder Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. PurchaseOrder Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Export\CSV;
use App\Libraries\MultiDB;
use App\Models\Company;
use App\Models\PurchaseOrder;
use App\Transformers\PurchaseOrderTransformer;
use App\Utils\Ninja;
use Illuminate\Support\Facades\App;
use League\Csv\Writer;
class PurchaseOrderItemExport extends BaseExport
{
private Company $company;
private $purchase_order_transformer;
public string $date_key = 'date';
public Writer $csv;
private bool $force_keys = false;
public array $entity_keys = [
'amount' => 'amount',
'balance' => 'balance',
'vendor' => 'vendor_id',
'vendor_number' => 'vendor.number',
'vendor_id_number' => 'vendor.id_number',
'custom_surcharge1' => 'custom_surcharge1',
'custom_surcharge2' => 'custom_surcharge2',
'custom_surcharge3' => 'custom_surcharge3',
'custom_surcharge4' => 'custom_surcharge4',
// 'custom_value1' => 'custom_value1',
// 'custom_value2' => 'custom_value2',
// 'custom_value3' => 'custom_value3',
// 'custom_value4' => 'custom_value4',
'date' => 'date',
'discount' => 'discount',
'due_date' => 'due_date',
'exchange_rate' => 'exchange_rate',
'footer' => 'footer',
'number' => 'number',
'paid_to_date' => 'paid_to_date',
'partial' => 'partial',
'partial_due_date' => 'partial_due_date',
'po_number' => 'po_number',
'private_notes' => 'private_notes',
'public_notes' => 'public_notes',
'status' => 'status_id',
'tax_name1' => 'tax_name1',
'tax_name2' => 'tax_name2',
'tax_name3' => 'tax_name3',
'tax_rate1' => 'tax_rate1',
'tax_rate2' => 'tax_rate2',
'tax_rate3' => 'tax_rate3',
'terms' => 'terms',
'total_taxes' => 'total_taxes',
'currency' => 'currency_id',
'quantity' => 'item.quantity',
'cost' => 'item.cost',
'product_key' => 'item.product_key',
'buy_price' => 'item.product_cost',
'notes' => 'item.notes',
'discount' => 'item.discount',
'is_amount_discount' => 'item.is_amount_discount',
'tax_rate1' => 'item.tax_rate1',
'tax_rate2' => 'item.tax_rate2',
'tax_rate3' => 'item.tax_rate3',
'tax_name1' => 'item.tax_name1',
'tax_name2' => 'item.tax_name2',
'tax_name3' => 'item.tax_name3',
'line_total' => 'item.line_total',
'gross_line_total' => 'item.gross_line_total',
// 'invoice1' => 'item.custom_value1',
// 'invoice2' => 'item.custom_value2',
// 'invoice3' => 'item.custom_value3',
// 'invoice4' => 'item.custom_value4',
'tax_category' => 'item.tax_id',
'type' => 'item.type_id',
];
private array $decorate_keys = [
'client',
'currency_id',
'status'
];
public function __construct(Company $company, array $input)
{
$this->company = $company;
$this->input = $input;
$this->purchase_order_transformer = new PurchaseOrderTransformer();
}
public function run()
{
MultiDB::setDb($this->company->db);
App::forgetInstance('translator');
App::setLocale($this->company->locale());
$t = app('translator');
$t->replace(Ninja::transformTranslations($this->company->settings));
//load the CSV document from a string
$this->csv = Writer::createFromString();
if (count($this->input['report_keys']) == 0) {
$this->force_keys = true;
$this->input['report_keys'] = array_values($this->entity_keys);
}
//insert the header
$this->csv->insertOne($this->buildHeader());
$query = PurchaseOrder::query()
->withTrashed()
->with('vendor')->where('company_id', $this->company->id)
->where('is_deleted', 0);
$query = $this->addDateRange($query);
$query->cursor()
->each(function ($purchase_order) {
$this->iterateItems($purchase_order);
});
return $this->csv->toString();
}
private function iterateItems(PurchaseOrder $purchase_order)
{
$transformed_invoice = $this->buildRow($purchase_order);
$transformed_items = [];
foreach ($purchase_order->line_items as $item) {
$item_array = [];
foreach (array_values($this->input['report_keys']) as $key) { //items iterator produces item array
if (str_contains($key, "item.")) {
$key = str_replace("item.", "", $key);
$keyval = $key;
$keyval = str_replace("custom_value", "invoice", $key);
if($key == 'type_id') {
$keyval = 'type';
}
if($key == 'tax_id') {
$keyval = 'tax_category';
}
if (property_exists($item, $key)) {
$item_array[$keyval] = $item->{$key};
} else {
$item_array[$keyval] = '';
}
}
}
$entity = [];
foreach (array_values($this->input['report_keys']) as $key) { //create an array of report keys only
$keyval = array_search($key, $this->entity_keys);
if (array_key_exists($key, $transformed_items)) {
$entity[$keyval] = $transformed_items[$key];
} else {
$entity[$keyval] = "";
}
}
$transformed_items = array_merge($transformed_invoice, $item_array);
$entity = $this->decorateAdvancedFields($purchase_order, $transformed_items);
$this->csv->insertOne($entity);
}
}
private function buildRow(PurchaseOrder $purchase_order) :array
{
$transformed_invoice = $this->purchase_order_transformer->transform($purchase_order);
$entity = [];
foreach (array_values($this->input['report_keys']) as $key) {
$keyval = array_search($key, $this->entity_keys);
if(!$keyval) {
$keyval = array_search(str_replace("invoice.", "", $key), $this->entity_keys) ?? $key;
}
if(!$keyval) {
$keyval = $key;
}
if (array_key_exists($key, $transformed_invoice)) {
$entity[$keyval] = $transformed_invoice[$key];
} elseif (array_key_exists($keyval, $transformed_invoice)) {
$entity[$keyval] = $transformed_invoice[$keyval];
} else {
$entity[$keyval] = $this->resolveKey($keyval, $purchase_order, $this->purchase_order_transformer);
}
}
return $this->decorateAdvancedFields($purchase_order, $entity);
}
private function decorateAdvancedFields(PurchaseOrder $purchase_order, array $entity) :array
{
if (in_array('currency_id', $this->input['report_keys'])) {
$entity['currency'] = $purchase_order->vendor->currency() ? $purchase_order->vendor->currency()->code : $purchase_order->company->currency()->code;
}
if(array_key_exists('type', $entity)) {
$entity['type'] = $purchase_order->typeIdString($entity['type']);
}
if(array_key_exists('tax_category', $entity)) {
$entity['tax_category'] = $purchase_order->taxTypeString($entity['tax_category']);
}
if($this->force_keys) {
$entity['vendor'] = $purchase_order->vendor->present()->name();
$entity['vendor_id_number'] = $purchase_order->vendor->id_number;
$entity['vendor_number'] = $purchase_order->vendor->number;
$entity['status'] = $purchase_order->stringStatus($purchase_order->status_id);
}
return $entity;
}
}

View File

@ -0,0 +1,172 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Export\CSV;
use App\Libraries\MultiDB;
use App\Models\Vendor;
use App\Models\Company;
use App\Transformers\VendorContactTransformer;
use App\Transformers\VendorTransformer;
use App\Utils\Ninja;
use Illuminate\Support\Facades\App;
use League\Csv\Writer;
class VendorExport extends BaseExport
{
private $company;
private $vendor_transformer;
private $contact_transformer;
public Writer $csv;
public string $date_key = 'created_at';
public array $entity_keys = [
'address1' => 'vendor.address1',
'address2' => 'vendor.address2',
'city' => 'vendor.city',
'country' => 'vendor.country_id',
'custom_value1' => 'vendor.custom_value1',
'custom_value2' => 'vendor.custom_value2',
'custom_value3' => 'vendor.custom_value3',
'custom_value4' => 'vendor.custom_value4',
'id_number' => 'vendor.id_number',
'name' => 'vendor.name',
'number' => 'vendor.number',
'phone' => 'vendor.phone',
'postal_code' => 'vendor.postal_code',
'private_notes' => 'vendor.private_notes',
'public_notes' => 'vendor.public_notes',
'state' => 'vendor.state',
'vat_number' => 'vendor.vat_number',
'website' => 'vendor.website',
'currency' => 'vendor.currency',
'first_name' => 'vendor_contact.first_name',
'last_name' => 'vendor_contact.last_name',
'contact_phone' => 'vendor_contact.phone',
'contact_custom_value1' => 'vendor_contact.custom_value1',
'contact_custom_value2' => 'vendor_contact.custom_value2',
'contact_custom_value3' => 'vendor_contact.custom_value3',
'contact_custom_value4' => 'vendor_contact.custom_value4',
'email' => 'vendor_contact.email',
'status' => 'vendor.status'
];
private array $decorate_keys = [
'vendor.country_id',
'vendor.currency',
];
public array $forced_keys = [
// 'vendor.status'
];
public function __construct(Company $company, array $input)
{
$this->company = $company;
$this->input = $input;
$this->vendor_transformer = new VendorTransformer();
$this->contact_transformer = new VendorContactTransformer();
}
public function run()
{
MultiDB::setDb($this->company->db);
App::forgetInstance('translator');
App::setLocale($this->company->locale());
$t = app('translator');
$t->replace(Ninja::transformTranslations($this->company->settings));
//load the CSV document from a string
$this->csv = Writer::createFromString();
if (count($this->input['report_keys']) == 0) {
$this->input['report_keys'] = array_values($this->entity_keys);
}
//insert the header
$this->csv->insertOne($this->buildHeader());
$query = Vendor::query()->with('contacts')
->withTrashed()
->where('company_id', $this->company->id)
->where('is_deleted', 0);
$query = $this->addDateRange($query);
$query->cursor()
->each(function ($vendor) {
$this->csv->insertOne($this->buildRow($vendor));
});
return $this->csv->toString();
}
private function buildRow(Vendor $vendor) :array
{
$transformed_contact = false;
$transformed_vendor = $this->vendor_transformer->transform($vendor);
if ($contact = $vendor->contacts()->first()) {
$transformed_contact = $this->contact_transformer->transform($contact);
}
$entity = [];
foreach (array_values($this->input['report_keys']) as $key) {
$parts = explode('.', $key);
$keyval = array_search($key, $this->entity_keys);
if (is_array($parts) && $parts[0] == 'vendor' && array_key_exists($parts[1], $transformed_vendor)) {
$entity[$keyval] = $transformed_vendor[$parts[1]];
} elseif (is_array($parts) && $parts[0] == 'vendor_contact' && array_key_exists($parts[1], $transformed_contact)) {
$entity[$keyval] = $transformed_contact[$parts[1]];
} else {
$entity[$keyval] = '';
}
}
return $this->decorateAdvancedFields($vendor, $entity);
}
private function decorateAdvancedFields(Vendor $vendor, array $entity) :array
{
if (in_array('vendor.country_id', $this->input['report_keys'])) {
$entity['country'] = $vendor->country ? ctrans("texts.country_{$vendor->country->name}") : '';
}
if (in_array('vendor.currency', $this->input['report_keys'])) {
$entity['currency'] = $vendor->currency() ? $vendor->currency()->code : $vendor->company->currency()->code;
}
$entity['status'] = $this->calculateStatus($vendor);
return $entity;
}
private function calculateStatus($vendor)
{
if ($vendor->is_deleted) {
return ctrans('texts.deleted');
}
if ($vendor->deleted_at) {
return ctrans('texts.archived');
}
return ctrans('texts.active');
}
}

View File

@ -55,6 +55,10 @@ class InvoiceFilters extends QueryFilters
$this->builder->where(function ($query) use ($status_parameters) {
$invoice_filters = [];
if (in_array('draft', $status_parameters)) {
$invoice_filters[] = Invoice::STATUS_DRAFT;
}
if (in_array('paid', $status_parameters)) {
$invoice_filters[] = Invoice::STATUS_PAID;
}
@ -131,19 +135,6 @@ class InvoiceFilters extends QueryFilters
}
/**
* @return Builder
* @throws RuntimeException
*/
public function without_deleted_clients(): Builder
{
return $this->builder->whereHas('client', function ($query) {
$query->where('is_deleted', 0);
});
}
/**
* @return Builder
* @return Builder

View File

@ -288,6 +288,33 @@ abstract class QueryFilters
return $this->builder;
}
/**
* @return Builder
* @throws RuntimeException
*/
public function without_deleted_clients(): Builder
{
return $this->builder->where(function ($query) {
$query->whereHas('client', function ($sub_query) {
$sub_query->where('is_deleted', 0);
})->orWhere('client_id', null);
});
}
/**
* @return Builder
* @throws RuntimeException
*/
public function without_deleted_vendors(): Builder
{
return $this->builder->where(function ($query) {
$query->whereHas('vendor', function ($sub_query) {
$sub_query->where('is_deleted', 0);
})->orWhere('vendor_id', null);
});
}
public function with(string $value = ''): Builder
{
if (strlen($value) == 0) {

View File

@ -91,47 +91,7 @@ class ActivityController extends BaseController
->orderBy('created_at', 'DESC')
->company()
->take($default_activities);
// if ($request->has('react')) {
// /** @var \App\Models\User auth()->user() */
// $user = auth()->user();
// if (!$user->isAdmin()) {
// $activities->where('user_id', auth()->user()->id);
// }
// $system = ctrans('texts.system');
// $data = $activities->cursor()->map(function ($activity) {
// $arr =
// [
// 'client' => $activity->client ? $activity->client : '',
// 'contact' => $activity->client ? $activity->contact : '',
// 'quote' => $activity->quote ? $activity->quote : '',
// 'user' => $activity->user ? $activity->user : '',
// 'expense' => $activity->expense ? $activity->expense : '',
// 'invoice' => $activity->invoice ? $activity->invoice : '',
// 'recurring_invoice' => $activity->recurring_invoice ? $activity->recurring_invoice : '',
// 'payment' => $activity->payment ? $activity->payment : '',
// 'credit' => $activity->credit ? $activity->credit : '',
// 'task' => $activity->task ? $activity->task : '',
// 'vendor' => $activity->vendor ? $activity->vendor : '',
// 'purchase_order' => $activity->purchase_order ? $activity->purchase_order : '',
// 'subscription' => $activity->subscription ? $activity->subscription : '',
// 'vendor_contact' => $activity->vendor_contact ? $activity->vendor_contact : '',
// 'recurring_expense' => $activity->recurring_expense ? $activity->recurring_expense : '',
// ];
// $activity_array = $activity->toArray();
// return array_merge($arr, $activity_array);
// });
// return response()->json(['data' => $data->toArray()], 200);
// }
// else
if($request->has('reactv2')) {
/** @var \App\Models\User auth()->user() */

View File

@ -11,22 +11,29 @@
namespace App\Http\Controllers\ClientPortal;
use App\Events\Invoice\InvoiceWasViewed;
use App\Events\Misc\InvitationWasViewed;
use App\Http\Controllers\Controller;
use App\Http\Requests\ClientPortal\Invoices\ProcessInvoicesInBulkRequest;
use App\Http\Requests\ClientPortal\Invoices\ShowInvoiceRequest;
use App\Http\Requests\ClientPortal\Invoices\ShowInvoicesRequest;
use App\Models\Invoice;
use App\Utils\Ninja;
use App\Utils\Number;
use App\Utils\Traits\MakesDates;
use App\Utils\Traits\MakesHash;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use App\Models\Invoice;
use Illuminate\View\View;
use Illuminate\Http\Request;
use App\Models\QuoteInvitation;
use App\Utils\Traits\MakesHash;
use App\Models\CreditInvitation;
use App\Utils\Traits\MakesDates;
use App\Models\InvoiceInvitation;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Cache;
use Illuminate\Contracts\View\Factory;
use App\Models\PurchaseOrderInvitation;
use Illuminate\Support\Facades\Storage;
use App\Events\Invoice\InvoiceWasViewed;
use App\Events\Misc\InvitationWasViewed;
use App\Models\RecurringInvoiceInvitation;
use App\Jobs\Vendor\CreatePurchaseOrderPdf;
use App\Http\Requests\ClientPortal\Invoices\ShowInvoiceRequest;
use App\Http\Requests\ClientPortal\Invoices\ShowInvoicesRequest;
use App\Http\Requests\ClientPortal\Invoices\ProcessInvoicesInBulkRequest;
class InvoiceController extends Controller
{
@ -77,6 +84,31 @@ class InvoiceController extends Controller
return $this->render('invoices.show', $data);
}
public function showBlob($hash)
{
$data = Cache::pull($hash);
match($data['entity_type']){
'invoice' => $invitation = InvoiceInvitation::withTrashed()->find($data['invitation_id']),
'quote' => $invitation = QuoteInvitation::withTrashed()->find($data['invitation_id']),
'credit' => $invitation = CreditInvitation::withTrashed()->find($data['invitation_id']),
'recurring_invoice' => $invitation = RecurringInvoiceInvitation::withTrashed()->find($data['invitation_id']),
};
$file = (new \App\Jobs\Entity\CreateRawPdf($invitation, $invitation->company->db))->handle();
// $headers = ['Content-Type' => 'application/pdf'];
// $entity_string = $data['entity_type'];
// $file_name = $invitation->{$entity_string}->numberFormatter().'.pdf';
// return response()->streamDownload(function () use ($file) {
// echo $file;
// }, $file_name, $headers);
$headers = ['Content-Type' => 'application/pdf'];
return response()->make($file, 200, $headers);
}
/**
* Pay one or more invoices.
*

View File

@ -0,0 +1,53 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\Controllers\Reports;
use App\Export\CSV\PurchaseOrderItemExport;
use App\Http\Controllers\BaseController;
use App\Http\Requests\Report\GenericReportRequest;
use App\Jobs\Report\SendToAdmin;
use App\Utils\Traits\MakesHash;
class PurchaseOrderItemReportController extends BaseController
{
use MakesHash;
private string $filename = 'purchase_order_items.csv';
public function __construct()
{
parent::__construct();
}
public function __invoke(GenericReportRequest $request)
{
if ($request->has('send_email') && $request->get('send_email')) {
SendToAdmin::dispatch(auth()->user()->company(), $request->all(), PurchaseOrderItemExport::class, $this->filename);
return response()->json(['message' => 'working...'], 200);
}
// expect a list of visible fields, or use the default
$export = new PurchaseOrderItemExport(auth()->user()->company(), $request->all());
$csv = $export->run();
$headers = [
'Content-Disposition' => 'attachment',
'Content-Type' => 'text/csv',
];
return response()->streamDownload(function () use ($csv) {
echo $csv;
}, $this->filename, $headers);
}
}

View File

@ -0,0 +1,53 @@
<?php
/**
* PurchaseOrder Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. PurchaseOrder Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\Controllers\Reports;
use App\Export\CSV\PurchaseOrderExport;
use App\Http\Controllers\BaseController;
use App\Http\Requests\Report\GenericReportRequest;
use App\Jobs\Report\SendToAdmin;
use App\Utils\Traits\MakesHash;
class PurchaseOrderReportController extends BaseController
{
use MakesHash;
private string $filename = 'purchase_orders.csv';
public function __construct()
{
parent::__construct();
}
public function __invoke(GenericReportRequest $request)
{
if ($request->has('send_email') && $request->get('send_email')) {
SendToAdmin::dispatch(auth()->user()->company(), $request->all(), PurchaseOrderExport::class, $this->filename);
return response()->json(['message' => 'working...'], 200);
}
// expect a list of visible fields, or use the default
$export = new PurchaseOrderExport(auth()->user()->company(), $request->all());
$csv = $export->run();
$headers = [
'Content-Disposition' => 'attachment',
'Content-Type' => 'text/csv',
];
return response()->streamDownload(function () use ($csv) {
echo $csv;
}, $this->filename, $headers);
}
}

View File

@ -29,37 +29,6 @@ class RecurringInvoiceReportController extends BaseController
parent::__construct();
}
/**
* @OA\Post(
* path="/api/v1/reports/recurring_invoices",
* operationId="getRecurringInvoiceReport",
* tags={"reports"},
* summary="Recurring Invoice reports",
* description="Export recurring invoice reports",
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\RequestBody(
* required=true,
* @OA\JsonContent(ref="#/components/schemas/GenericReportSchema")
* ),
* @OA\Response(
* response=200,
* description="success",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function __invoke(GenericReportRequest $request)
{
if ($request->has('send_email') && $request->get('send_email')) {

View File

@ -0,0 +1,53 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\Controllers\Reports;
use App\Export\CSV\VendorExport;
use App\Http\Controllers\BaseController;
use App\Http\Requests\Report\GenericReportRequest;
use App\Jobs\Report\SendToAdmin;
use App\Utils\Traits\MakesHash;
class VendorReportController extends BaseController
{
use MakesHash;
private string $filename = 'vendors.csv';
public function __construct()
{
parent::__construct();
}
public function __invoke(GenericReportRequest $request)
{
if ($request->has('send_email') && $request->get('send_email')) {
SendToAdmin::dispatch(auth()->user()->company(), $request->all(), VendorExport::class, $this->filename);
return response()->json(['message' => 'working...'], 200);
}
// expect a list of visible fields, or use the default
$export = new VendorExport(auth()->user()->company(), $request->all());
$csv = $export->run();
$headers = [
'Content-Disposition' => 'attachment',
'Content-Type' => 'text/csv',
];
return response()->streamDownload(function () use ($csv) {
echo $csv;
}, $this->filename, $headers);
}
}

View File

@ -11,21 +11,24 @@
namespace App\Http\Controllers\VendorPortal;
use App\Events\Misc\InvitationWasViewed;
use App\Events\PurchaseOrder\PurchaseOrderWasAccepted;
use App\Events\PurchaseOrder\PurchaseOrderWasViewed;
use App\Utils\Ninja;
use Illuminate\View\View;
use App\Models\PurchaseOrder;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\MakesDates;
use App\Http\Controllers\Controller;
use App\Http\Requests\VendorPortal\PurchaseOrders\ProcessPurchaseOrdersInBulkRequest;
use App\Jobs\Invoice\InjectSignature;
use Illuminate\Support\Facades\Cache;
use Illuminate\Contracts\View\Factory;
use App\Models\PurchaseOrderInvitation;
use Illuminate\Support\Facades\Storage;
use App\Events\Misc\InvitationWasViewed;
use App\Jobs\Vendor\CreatePurchaseOrderPdf;
use App\Events\PurchaseOrder\PurchaseOrderWasViewed;
use App\Events\PurchaseOrder\PurchaseOrderWasAccepted;
use App\Http\Requests\VendorPortal\PurchaseOrders\ShowPurchaseOrderRequest;
use App\Http\Requests\VendorPortal\PurchaseOrders\ShowPurchaseOrdersRequest;
use App\Jobs\Invoice\InjectSignature;
use App\Models\PurchaseOrder;
use App\Utils\Ninja;
use App\Utils\Traits\MakesDates;
use App\Utils\Traits\MakesHash;
use Illuminate\Contracts\View\Factory;
use Illuminate\Support\Facades\Storage;
use Illuminate\View\View;
use App\Http\Requests\VendorPortal\PurchaseOrders\ProcessPurchaseOrdersInBulkRequest;
class PurchaseOrderController extends Controller
{
@ -108,6 +111,28 @@ class PurchaseOrderController extends Controller
return $this->render('purchase_orders.show', $data);
}
public function showBlob($hash)
{
$data = Cache::pull($hash);
$invitation = PurchaseOrderInvitation::withTrashed()->find($data['invitation_id']);
$file = (new CreatePurchaseOrderPdf($invitation, $invitation->company->db))->rawPdf();
// $headers = ['Content-Type' => 'application/pdf'];
// $entity_string = $data['entity_type'];
// $file_name = $invitation->{$entity_string}->numberFormatter().'.pdf';
// return response()->streamDownload(function () use ($file) {
// echo $file;
// }, $file_name, $headers);
$headers = ['Content-Type' => 'application/pdf'];
return response()->make($file, 200, $headers);
}
private function sidebarMenu() :array
{
$enabled_modules = auth()->guard('vendor')->user()->company->enabled_modules;

View File

@ -16,6 +16,7 @@ use App\Utils\Number;
use Livewire\Component;
use App\Utils\HtmlEngine;
use App\Libraries\MultiDB;
use Illuminate\Support\Str;
use App\Models\QuoteInvitation;
use App\Utils\VendorHtmlEngine;
use App\Models\CreditInvitation;
@ -23,6 +24,7 @@ use App\Services\Pdf\PdfBuilder;
use App\Services\Pdf\PdfService;
use App\Models\InvoiceInvitation;
use App\Services\Pdf\PdfDesigner;
use Illuminate\Support\Facades\Cache;
use App\Services\Pdf\PdfConfiguration;
use App\Models\PurchaseOrderInvitation;
use App\Models\RecurringInvoiceInvitation;
@ -48,6 +50,12 @@ class PdfSlot extends Component
protected $listeners = ['viewportChanged' => 'getPdf'];
public $show_cost = true;
public $show_quantity = true;
public $route_entity = 'client';
public function mount()
{
MultiDB::setDb($this->db);
@ -55,7 +63,21 @@ class PdfSlot extends Component
public function getPdf()
{
$this->pdf = $this->entity->fullscreenPdfViewer($this->invitation);
// $this->pdf = $this->entity->fullscreenPdfViewer($this->invitation);
$blob = [
'entity_type' => $this->resolveEntityType(),
'entity_id' => $this->entity->id,
'invitation_id' => $this->invitation->id,
'download' => false,
];
$hash = Str::random(64);
Cache::put($hash, $blob, now()->addMinutes(2));
$this->pdf = $hash;
}
public function downloadPdf()
@ -78,10 +100,19 @@ class PdfSlot extends Component
public function render()
{
$this->entity_type = $this->resolveEntityType();
$this->settings = $this->entity->client ? $this->entity->client->getMergedSettings() : $this->entity->company->settings;
$this->show_cost = in_array('$product.unit_cost', $this->settings->pdf_variables->product_columns);
$this->show_quantity = in_array('$product.quantity', $this->settings->pdf_variables->product_columns);
if($this->entity_type == 'quote' && !$this->settings->sync_invoice_quote_columns ){
$this->show_cost = in_array('$product.unit_cost', $this->settings->pdf_variables->product_quote_columns);
$this->show_quantity = in_array('$product.quantity', $this->settings->pdf_variables->product_quote_columns);
}
$this->html_variables = $this->entity->client ?
(new HtmlEngine($this->invitation))->generateLabelsAndValues() :
(new VendorHtmlEngine($this->invitation))->generateLabelsAndValues();
@ -100,7 +131,6 @@ class PdfSlot extends Component
'entity_details' => $this->getEntityDetails(),
'user_details' => $this->getUserDetails(),
'user_name' => $this->getUserName(),
]);
}
@ -243,6 +273,7 @@ class PdfSlot extends Component
} elseif ($this->invitation instanceof RecurringInvoiceInvitation) {
return 'recurring_invoice';
} elseif ($this->invitation instanceof PurchaseOrderInvitation) {
$this->route_entity = 'vendor';
return 'purchase_order';
}

View File

@ -257,7 +257,8 @@ class RequiredClientInfo extends Component
}
if (Str::startsWith($field['name'], 'contact_')) {
if (empty($this->contact->{$_field}) || is_null($this->contact->{$_field})) {
if (empty($this->contact->{$_field}) || is_null($this->contact->{$_field}) || str_contains($this->contact->{$_field}, '@example.com')) {
$this->show_form = true;
} else {
$this->fields[$index]['filled'] = true;

View File

@ -71,6 +71,7 @@ class UpdateInvoiceRequest extends Request
$rules['tax_name1'] = 'bail|sometimes|string|nullable';
$rules['tax_name2'] = 'bail|sometimes|string|nullable';
$rules['tax_name3'] = 'bail|sometimes|string|nullable';
$rules['status_id'] = 'bail|sometimes|not_in:5'; //do not all cancelled invoices to be modfified.
// not needed.
// $rules['partial_due_date'] = 'bail|sometimes|required_unless:partial,0,null';
@ -100,7 +101,8 @@ class UpdateInvoiceRequest extends Request
public function messages()
{
return [
'id' => ctrans('text.locked_invoice'),
'id' => ctrans('texts.locked_invoice'),
'status_id' => ctrans('texts.locked_invoice'),
];
}
}

View File

@ -69,13 +69,16 @@ class ValidCreditsRules implements Rule
if (! $cred) {
$this->error_msg = ctrans('texts.credit_not_found');
return false;
}
if ($cred->client_id != $this->input['client_id']) {
$this->error_msg = ctrans('texts.invoices_dont_match_client');
return false;
}
if($cred->balance < $credit['amount']) {
$this->error_msg = ctrans('texts.insufficient_credit_balance');
return false;
}
}

View File

@ -1173,11 +1173,26 @@ class Import implements ShouldQueue
unset($modified['id']);
$credit = $credit_repository->save(
$modified,
CreditFactory::create($this->company->id, $modified['user_id'])
);
if($credit->status_id == 4)
{
$client = $credit->client;
$client->balance -= $credit->balance;
$client->credit_balance -= $credit->amount;
$client->saveQuietly();
$credit->paid_to_date = $credit->amount;
$credit->balance = 0;
$credit->saveQuietly();
}
//remove credit balance from ledger
if ($credit->balance > 0 && $credit->client->balance > 0) {
$client = $credit->client;

View File

@ -188,7 +188,7 @@ class CreatePurchaseOrderPdf implements ShouldQueue
}
if (config('ninja.log_pdf_html')) {
info($maker->getCompiledHTML());
nlog($maker->getCompiledHTML());
}
$maker = null;

View File

@ -275,4 +275,19 @@ class Expense extends BaseModel
return $this->belongsTo(BankTransaction::class);
}
public function stringStatus()
{
if($this->is_deleted)
return ctrans('texts.deleted');
elseif($this->payment_date)
return ctrans('texts.paid');
elseif($this->invoice_id)
return ctrans('texts.invoiced');
elseif($this->should_be_invoiced)
return ctrans('texts.pending');
elseif($this->trashed())
return ctrans('texts.archived');
return ctrans('texts.logged');
}
}

View File

@ -108,50 +108,6 @@ use Laracasts\Presenter\PresentableTrait;
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\VendorContact> $contacts
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\VendorContact> $primary_contact
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\VendorContact> $contacts
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\VendorContact> $primary_contact
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\VendorContact> $contacts
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\VendorContact> $primary_contact
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\VendorContact> $contacts
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\VendorContact> $primary_contact
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\VendorContact> $contacts
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\VendorContact> $primary_contact
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\VendorContact> $contacts
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\VendorContact> $primary_contact
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\VendorContact> $contacts
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\VendorContact> $primary_contact
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\VendorContact> $contacts
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\VendorContact> $primary_contact
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\VendorContact> $contacts
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\VendorContact> $primary_contact
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\VendorContact> $contacts
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\VendorContact> $primary_contact
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\VendorContact> $contacts
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\VendorContact> $primary_contact
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\VendorContact> $contacts
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\VendorContact> $primary_contact
* @mixin \Eloquent
*/
class Vendor extends BaseModel

View File

@ -57,7 +57,7 @@ class RouteServiceProvider extends ServiceProvider
if (Ninja::isSelfHost()) {
return Limit::none();
} else {
return Limit::perMinute(50)->by($request->ip());
return Limit::perMinute(30)->by($request->ip());
}
});
@ -89,6 +89,11 @@ class RouteServiceProvider extends ServiceProvider
return Limit::perMinute(2)->by($request->ip());
});
RateLimiter::for('portal', function (Request $request) {
return Limit::perMinute(15)->by($request->ip());
});
}
/**

View File

@ -50,6 +50,9 @@ class ActivityRepository extends BaseRepository
$activity->{$key} = $value;
}
if($entity->company)
$activity->account_id = $entity->company->account_id;
if ($token_id = $this->getTokenId($event_vars)) {
$activity->token_id = $token_id;
}

View File

@ -44,7 +44,6 @@ class HandleRestore extends AbstractService
//cannot restore an invoice with a deleted payment
foreach ($this->invoice->payments as $payment) {
if (($this->invoice->paid_to_date == 0) && $payment->is_deleted) {
$this->invoice->delete(); //set it back to deleted so that it can be restored from repository
return $this->invoice;
}
}
@ -81,8 +80,8 @@ class HandleRestore extends AbstractService
Paymentable::query()
->withTrashed()
->where('payment_id', $payment->id)
->where('paymentable_type', '=', 'invoices')
->where('paymentable_id', $this->invoice->id)
// ->where('paymentable_type', '=', 'invoices')
// ->where('paymentable_id', $this->invoice->id)
->update(['deleted_at' => null]);
});
@ -102,6 +101,12 @@ class HandleRestore extends AbstractService
->where('paymentable_type', '=', 'invoices')
->where('paymentable_id', $this->invoice->id)
->sum(DB::raw('refunded'));
//14/07/2023 - do not include credits in the payment amount
$this->adjustment_amount -= $payment->paymentables
->where('paymentable_type', '=', 'App\Models\Credit')
->sum(DB::raw('amount'));
}
$this->total_payments = $this->invoice->payments->sum('amount') - $this->invoice->payments->sum('refunded');
@ -130,11 +135,16 @@ class HandleRestore extends AbstractService
->where('paymentable_id', $this->invoice->id)
->sum(DB::raw('refunded'));
$payment_adjustment -= $payment->paymentables
->where('paymentable_type', '=', 'App\Models\Credit')
->sum(DB::raw('amount'));
$payment->amount += $payment_adjustment;
$payment->applied += $payment_adjustment;
$payment->is_deleted = false;
$payment->restore();
$payment->saveQuietly();
});
return $this;

View File

@ -11,11 +11,12 @@
namespace App\Services\Invoice;
use App\Jobs\Inventory\AdjustProductInventory;
use App\Models\Credit;
use App\Models\Invoice;
use App\Services\AbstractService;
use App\Utils\Traits\GeneratesCounter;
use Illuminate\Support\Facades\DB;
use App\Utils\Traits\GeneratesCounter;
use App\Jobs\Inventory\AdjustProductInventory;
class MarkInvoiceDeleted extends AbstractService
{
@ -94,6 +95,11 @@ class MarkInvoiceDeleted extends AbstractService
->where('paymentable_id', $this->invoice->id)
->sum(DB::raw('refunded'));
//14-07-2023 - Do not include credits in the payment adjustment.
$payment_adjustment -= $payment->paymentables
->where('paymentable_type', '=', 'App\Models\Credit')
->sum(DB::raw('amount'));
$payment->amount -= $payment_adjustment;
$payment->applied -= $payment_adjustment;
$payment->save();

View File

@ -109,6 +109,8 @@ class DeletePayment
$paymentable_invoice->service()
->updatePaidToDate($net_deletable * -1)
->save();
$paymentable_invoice->delete();
}
});
}

View File

@ -274,6 +274,10 @@ trait DesignHelpers
// Some variables don't map 1:1 to table columns. This gives us support for such cases.
$aliases = [
'$quote.balance_due' => 'partial',
'$purchase_order.po_number' => 'number',
'$purchase_order.total' => 'amount',
'$purchase_order.due_date' => 'due_date',
'$purchase_order.balance_due' => 'balance_due',
];
try {

View File

@ -132,7 +132,7 @@ class Ninja
'ip' => $ip,
'token' => request()->header('X-API-TOKEN'),
'is_system' => app()->runningInConsole(),
'user_id' => $user_id,
'user_id' => ($ip == '127.0.0.1') ? null : $user_id,
];
}

View File

@ -133,16 +133,18 @@ class VendorHtmlEngine
$data['$partial_due_date'] = ['value' => $this->translateDate($this->entity->partial_due_date, $this->company->date_format(), $this->company->locale()) ?: '&nbsp;', 'label' => ctrans('texts.'.$this->entity_string.'_due_date')];
$data['$dueDate'] = &$data['$due_date'];
$data['$purchase_order.due_date'] = &$data['$due_date'];
$data['$payment_due'] = ['value' => $this->translateDate($this->entity->due_date, $this->company->date_format(), $this->company->locale()) ?: '&nbsp;', 'label' => ctrans('texts.payment_due')];
$data['$poNumber'] = ['value' => $this->entity->po_number, 'label' => ctrans('texts.po_number')];
$data['$purchase_order.po_number'] = ['value' => $this->entity->number ?: '&nbsp;', 'label' => ctrans('texts.po_number')];
$data['$poNumber'] = &$data['$purchase_order.po_number'];
$data['$entity.datetime'] = ['value' => $this->formatDatetime($this->entity->created_at, $this->company->date_format()), 'label' => ctrans('texts.date')];
$data['$po_number'] = &$data['$poNumber'];
$data['$status_logo'] = ['value' => ' ', 'label' => ' '];
$data['$entity'] = ['value' => '', 'label' => ctrans('texts.purchase_order')];
$data['$number'] = ['value' => $this->entity->number ?: '&nbsp;', 'label' => ctrans('texts.purchase_order_number')];
$data['$number'] = ['value' => $this->entity->number ?: '&nbsp;', 'label' => ctrans('texts.number')];
$data['$number_short'] = ['value' => $this->entity->number ?: '&nbsp;', 'label' => ctrans('texts.purchase_order_number_short')];
$data['$entity.terms'] = ['value' => Helpers::processReservedKeywords(\nl2br($this->entity->terms), $this->company) ?: '', 'label' => ctrans('texts.invoice_terms')];
$data['$terms'] = &$data['$entity.terms'];
@ -155,7 +157,6 @@ class VendorHtmlEngine
$data['$purchase_order.number'] = &$data['$number'];
$data['$purchase_order.date'] = &$data['$date'];
$data['$purchase_order.po_number'] = &$data['$poNumber'];
$data['$purchase_order.due_date'] = &$data['$due_date'];
$data['$entity_issued_to'] = ['value' => '', 'label' => ctrans("texts.purchase_order_issued_to")];
@ -189,8 +190,9 @@ class VendorHtmlEngine
}
}
// $data['$balance_due'] = $data['$balance_due'];
$data['$outstanding'] = &$data['$balance_due'];
$data['$purchase_order.balance_due'] = &$data['$balance_due'];
$data['$partial_due'] = ['value' => Number::formatMoney($this->entity->partial, $this->vendor) ?: '&nbsp;', 'label' => ctrans('texts.partial_due')];
$data['$partial'] = &$data['$partial_due'];

View File

@ -95,7 +95,8 @@
"turbo124/predis": "1.1.11",
"twilio/sdk": "^6.40",
"webpatser/laravel-countries": "dev-master#75992ad",
"wepay/php-sdk": "^0.3"
"wepay/php-sdk": "^0.3",
"psr/http-message": "^1.0"
},
"require-dev": {
"php": "^8.1",

255
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "135eec9ab7a1e8c0ab3820ff27cf1488",
"content-hash": "be16996524279f340c44e2f6c9a0ba53",
"packages": [
{
"name": "adrienrn/php-mimetyper",
@ -424,16 +424,16 @@
},
{
"name": "aws/aws-sdk-php",
"version": "3.269.0",
"version": "3.275.7",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
"reference": "6d759ef9f24f0c7f271baf8014f41fc0cfdfbf78"
"reference": "54dcef3349c81b46c0f5f6e54b5f9bfb5db19903"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/6d759ef9f24f0c7f271baf8014f41fc0cfdfbf78",
"reference": "6d759ef9f24f0c7f271baf8014f41fc0cfdfbf78",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/54dcef3349c81b46c0f5f6e54b5f9bfb5db19903",
"reference": "54dcef3349c81b46c0f5f6e54b5f9bfb5db19903",
"shasum": ""
},
"require": {
@ -445,7 +445,8 @@
"guzzlehttp/promises": "^1.4.0",
"guzzlehttp/psr7": "^1.9.1 || ^2.4.5",
"mtdowling/jmespath.php": "^2.6",
"php": ">=5.5"
"php": ">=5.5",
"psr/http-message": "^1.0"
},
"require-dev": {
"andrewsville/php-token-reflection": "^1.4",
@ -462,7 +463,6 @@
"paragonie/random_compat": ">= 2",
"phpunit/phpunit": "^4.8.35 || ^5.6.3 || ^9.5",
"psr/cache": "^1.0",
"psr/http-message": "^1.0",
"psr/simple-cache": "^1.0",
"sebastian/comparator": "^1.2.3 || ^4.0",
"yoast/phpunit-polyfills": "^1.0"
@ -513,9 +513,9 @@
"support": {
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
"issues": "https://github.com/aws/aws-sdk-php/issues",
"source": "https://github.com/aws/aws-sdk-php/tree/3.269.0"
"source": "https://github.com/aws/aws-sdk-php/tree/3.275.7"
},
"time": "2023-04-26T18:21:04+00:00"
"time": "2023-07-13T18:21:04+00:00"
},
{
"name": "bacon/bacon-qr-code",
@ -2485,16 +2485,16 @@
},
{
"name": "google/apiclient-services",
"version": "v0.307.0",
"version": "v0.308.0",
"source": {
"type": "git",
"url": "https://github.com/googleapis/google-api-php-client-services.git",
"reference": "5f7d451aa912355dda61aa29ac3a4afa8b252467"
"reference": "85cf00383e6bf6eca131bd3261b7859ea418a578"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/5f7d451aa912355dda61aa29ac3a4afa8b252467",
"reference": "5f7d451aa912355dda61aa29ac3a4afa8b252467",
"url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/85cf00383e6bf6eca131bd3261b7859ea418a578",
"reference": "85cf00383e6bf6eca131bd3261b7859ea418a578",
"shasum": ""
},
"require": {
@ -2523,9 +2523,9 @@
],
"support": {
"issues": "https://github.com/googleapis/google-api-php-client-services/issues",
"source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.307.0"
"source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.308.0"
},
"time": "2023-07-01T01:02:13+00:00"
"time": "2023-07-09T01:06:13+00:00"
},
{
"name": "google/auth",
@ -4554,16 +4554,16 @@
},
{
"name": "laravel/socialite",
"version": "v5.6.3",
"version": "v5.7.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/socialite.git",
"reference": "00ea7f8630673ea49304fc8a9fca5a64eb838c7e"
"reference": "f5996f499e14db15407201a6bfbaba3ce6ce736c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/socialite/zipball/00ea7f8630673ea49304fc8a9fca5a64eb838c7e",
"reference": "00ea7f8630673ea49304fc8a9fca5a64eb838c7e",
"url": "https://api.github.com/repos/laravel/socialite/zipball/f5996f499e14db15407201a6bfbaba3ce6ce736c",
"reference": "f5996f499e14db15407201a6bfbaba3ce6ce736c",
"shasum": ""
},
"require": {
@ -4620,7 +4620,7 @@
"issues": "https://github.com/laravel/socialite/issues",
"source": "https://github.com/laravel/socialite"
},
"time": "2023-06-06T13:42:43+00:00"
"time": "2023-07-08T20:51:43+00:00"
},
{
"name": "laravel/tinker",
@ -5768,16 +5768,16 @@
},
{
"name": "mollie/mollie-api-php",
"version": "v2.57.0",
"version": "v2.58.0",
"source": {
"type": "git",
"url": "https://github.com/mollie/mollie-api-php.git",
"reference": "be201657b00a197e5238bbec81fad2a0fc0b83e4"
"reference": "5120e5b3e4622a290b64acf87266ea47d10d7301"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mollie/mollie-api-php/zipball/be201657b00a197e5238bbec81fad2a0fc0b83e4",
"reference": "be201657b00a197e5238bbec81fad2a0fc0b83e4",
"url": "https://api.github.com/repos/mollie/mollie-api-php/zipball/5120e5b3e4622a290b64acf87266ea47d10d7301",
"reference": "5120e5b3e4622a290b64acf87266ea47d10d7301",
"shasum": ""
},
"require": {
@ -5854,9 +5854,9 @@
],
"support": {
"issues": "https://github.com/mollie/mollie-api-php/issues",
"source": "https://github.com/mollie/mollie-api-php/tree/v2.57.0"
"source": "https://github.com/mollie/mollie-api-php/tree/v2.58.0"
},
"time": "2023-06-23T09:09:15+00:00"
"time": "2023-07-11T12:01:27+00:00"
},
{
"name": "moneyphp/money",
@ -7280,16 +7280,16 @@
},
{
"name": "php-http/discovery",
"version": "1.19.0",
"version": "1.19.1",
"source": {
"type": "git",
"url": "https://github.com/php-http/discovery.git",
"reference": "1856a119a0b0ba8da8b5c33c080aa7af8fac25b4"
"reference": "57f3de01d32085fea20865f9b16fb0e69347c39e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-http/discovery/zipball/1856a119a0b0ba8da8b5c33c080aa7af8fac25b4",
"reference": "1856a119a0b0ba8da8b5c33c080aa7af8fac25b4",
"url": "https://api.github.com/repos/php-http/discovery/zipball/57f3de01d32085fea20865f9b16fb0e69347c39e",
"reference": "57f3de01d32085fea20865f9b16fb0e69347c39e",
"shasum": ""
},
"require": {
@ -7352,9 +7352,9 @@
],
"support": {
"issues": "https://github.com/php-http/discovery/issues",
"source": "https://github.com/php-http/discovery/tree/1.19.0"
"source": "https://github.com/php-http/discovery/tree/1.19.1"
},
"time": "2023-06-19T08:45:36+00:00"
"time": "2023-07-11T07:02:26+00:00"
},
{
"name": "php-http/guzzle7-adapter",
@ -7844,16 +7844,16 @@
},
{
"name": "phpseclib/phpseclib",
"version": "3.0.20",
"version": "3.0.21",
"source": {
"type": "git",
"url": "https://github.com/phpseclib/phpseclib.git",
"reference": "543a1da81111a0bfd6ae7bbc2865c5e89ed3fc67"
"reference": "4580645d3fc05c189024eb3b834c6c1e4f0f30a1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/543a1da81111a0bfd6ae7bbc2865c5e89ed3fc67",
"reference": "543a1da81111a0bfd6ae7bbc2865c5e89ed3fc67",
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/4580645d3fc05c189024eb3b834c6c1e4f0f30a1",
"reference": "4580645d3fc05c189024eb3b834c6c1e4f0f30a1",
"shasum": ""
},
"require": {
@ -7934,7 +7934,7 @@
],
"support": {
"issues": "https://github.com/phpseclib/phpseclib/issues",
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.20"
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.21"
},
"funding": [
{
@ -7950,7 +7950,7 @@
"type": "tidelift"
}
],
"time": "2023-06-13T06:30:34+00:00"
"time": "2023-07-09T15:24:48+00:00"
},
{
"name": "phpstan/phpdoc-parser",
@ -8360,16 +8360,16 @@
},
{
"name": "psr/http-message",
"version": "2.0",
"version": "1.1",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-message.git",
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71"
"reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71",
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71",
"url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
"reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
"shasum": ""
},
"require": {
@ -8378,7 +8378,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
"dev-master": "1.1.x-dev"
}
},
"autoload": {
@ -8393,7 +8393,7 @@
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interface for HTTP messages",
@ -8407,9 +8407,9 @@
"response"
],
"support": {
"source": "https://github.com/php-fig/http-message/tree/2.0"
"source": "https://github.com/php-fig/http-message/tree/1.1"
},
"time": "2023-04-04T09:54:51+00:00"
"time": "2023-04-04T09:50:52+00:00"
},
{
"name": "psr/log",
@ -15117,16 +15117,16 @@
},
{
"name": "filp/whoops",
"version": "2.15.2",
"version": "2.15.3",
"source": {
"type": "git",
"url": "https://github.com/filp/whoops.git",
"reference": "aac9304c5ed61bf7b1b7a6064bf9806ab842ce73"
"reference": "c83e88a30524f9360b11f585f71e6b17313b7187"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/filp/whoops/zipball/aac9304c5ed61bf7b1b7a6064bf9806ab842ce73",
"reference": "aac9304c5ed61bf7b1b7a6064bf9806ab842ce73",
"url": "https://api.github.com/repos/filp/whoops/zipball/c83e88a30524f9360b11f585f71e6b17313b7187",
"reference": "c83e88a30524f9360b11f585f71e6b17313b7187",
"shasum": ""
},
"require": {
@ -15176,7 +15176,7 @@
],
"support": {
"issues": "https://github.com/filp/whoops/issues",
"source": "https://github.com/filp/whoops/tree/2.15.2"
"source": "https://github.com/filp/whoops/tree/2.15.3"
},
"funding": [
{
@ -15184,7 +15184,7 @@
"type": "github"
}
],
"time": "2023-04-12T12:00:00+00:00"
"time": "2023-07-13T12:00:00+00:00"
},
{
"name": "friendsofphp/php-cs-fixer",
@ -15392,79 +15392,6 @@
},
"time": "2023-02-16T20:00:16+00:00"
},
{
"name": "laravel/dusk",
"version": "v6.25.2",
"source": {
"type": "git",
"url": "https://github.com/laravel/dusk.git",
"reference": "25a595ac3dc82089a91af10dd23b0d58fd3f6d0b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/dusk/zipball/25a595ac3dc82089a91af10dd23b0d58fd3f6d0b",
"reference": "25a595ac3dc82089a91af10dd23b0d58fd3f6d0b",
"shasum": ""
},
"require": {
"ext-json": "*",
"ext-zip": "*",
"illuminate/console": "^6.0|^7.0|^8.0|^9.0",
"illuminate/support": "^6.0|^7.0|^8.0|^9.0",
"nesbot/carbon": "^2.0",
"php": "^7.2|^8.0",
"php-webdriver/webdriver": "^1.9.0",
"symfony/console": "^4.3|^5.0|^6.0",
"symfony/finder": "^4.3|^5.0|^6.0",
"symfony/process": "^4.3|^5.0|^6.0",
"vlucas/phpdotenv": "^3.0|^4.0|^5.2"
},
"require-dev": {
"mockery/mockery": "^1.0",
"orchestra/testbench": "^4.16|^5.17.1|^6.12.1|^7.0",
"phpunit/phpunit": "^7.5.15|^8.4|^9.0"
},
"suggest": {
"ext-pcntl": "Used to gracefully terminate Dusk when tests are running."
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "6.x-dev"
},
"laravel": {
"providers": [
"Laravel\\Dusk\\DuskServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Laravel\\Dusk\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
}
],
"description": "Laravel Dusk provides simple end-to-end testing and browser automation.",
"keywords": [
"laravel",
"testing",
"webdriver"
],
"support": {
"issues": "https://github.com/laravel/dusk/issues",
"source": "https://github.com/laravel/dusk/tree/v6.25.2"
},
"time": "2022-09-29T09:37:07+00:00"
},
{
"name": "maximebf/debugbar",
"version": "v1.18.2",
@ -16067,72 +15994,6 @@
},
"time": "2022-02-21T01:04:05+00:00"
},
{
"name": "php-webdriver/webdriver",
"version": "1.14.0",
"source": {
"type": "git",
"url": "https://github.com/php-webdriver/php-webdriver.git",
"reference": "3ea4f924afb43056bf9c630509e657d951608563"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/3ea4f924afb43056bf9c630509e657d951608563",
"reference": "3ea4f924afb43056bf9c630509e657d951608563",
"shasum": ""
},
"require": {
"ext-curl": "*",
"ext-json": "*",
"ext-zip": "*",
"php": "^7.3 || ^8.0",
"symfony/polyfill-mbstring": "^1.12",
"symfony/process": "^5.0 || ^6.0"
},
"replace": {
"facebook/webdriver": "*"
},
"require-dev": {
"ergebnis/composer-normalize": "^2.20.0",
"ondram/ci-detector": "^4.0",
"php-coveralls/php-coveralls": "^2.4",
"php-mock/php-mock-phpunit": "^2.0",
"php-parallel-lint/php-parallel-lint": "^1.2",
"phpunit/phpunit": "^9.3",
"squizlabs/php_codesniffer": "^3.5",
"symfony/var-dumper": "^5.0 || ^6.0"
},
"suggest": {
"ext-SimpleXML": "For Firefox profile creation"
},
"type": "library",
"autoload": {
"files": [
"lib/Exception/TimeoutException.php"
],
"psr-4": {
"Facebook\\WebDriver\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "A PHP client for Selenium WebDriver. Previously facebook/webdriver.",
"homepage": "https://github.com/php-webdriver/php-webdriver",
"keywords": [
"Chromedriver",
"geckodriver",
"php",
"selenium",
"webdriver"
],
"support": {
"issues": "https://github.com/php-webdriver/php-webdriver/issues",
"source": "https://github.com/php-webdriver/php-webdriver/tree/1.14.0"
},
"time": "2023-02-09T12:12:19+00:00"
},
{
"name": "phpdocumentor/reflection-docblock",
"version": "5.3.0",
@ -16659,16 +16520,16 @@
},
{
"name": "phpunit/phpunit",
"version": "9.6.9",
"version": "9.6.10",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "a9aceaf20a682aeacf28d582654a1670d8826778"
"reference": "a6d351645c3fe5a30f5e86be6577d946af65a328"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a9aceaf20a682aeacf28d582654a1670d8826778",
"reference": "a9aceaf20a682aeacf28d582654a1670d8826778",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a6d351645c3fe5a30f5e86be6577d946af65a328",
"reference": "a6d351645c3fe5a30f5e86be6577d946af65a328",
"shasum": ""
},
"require": {
@ -16742,7 +16603,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.9"
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.10"
},
"funding": [
{
@ -16758,7 +16619,7 @@
"type": "tidelift"
}
],
"time": "2023-06-11T06:13:56+00:00"
"time": "2023-07-10T04:04:23+00:00"
},
{
"name": "sebastian/cli-parser",

View File

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

View File

@ -0,0 +1,34 @@
<?php
use App\Models\Currency;
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()
{
$tb = Currency::find(21);
if($tb) {
$tb->symbol = '฿';
$tb->save();
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
};

View File

@ -43,7 +43,7 @@ class CurrenciesSeeder extends Seeder
['id' => 18, 'name' => 'Guatemalan Quetzal', 'code' => 'GTQ', 'symbol' => 'Q', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['id' => 19, 'name' => 'Malaysian Ringgit', 'code' => 'MYR', 'symbol' => 'RM', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['id' => 20, 'name' => 'Brazilian Real', 'code' => 'BRL', 'symbol' => 'R$', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','],
['id' => 21, 'name' => 'Thai Baht', 'code' => 'THB', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['id' => 21, 'name' => 'Thai Baht', 'code' => 'THB', 'symbol' => '฿', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['id' => 22, 'name' => 'Nigerian Naira', 'code' => 'NGN', 'symbol' => '', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['id' => 23, 'name' => 'Argentine Peso', 'code' => 'ARS', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','],
['id' => 24, 'name' => 'Bangladeshi Taka', 'code' => 'BDT', 'symbol' => 'Tk', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],

View File

@ -753,7 +753,7 @@ $LANG = array(
'activity_7' => 'Klient :contact zobrazil fakturu :invoice pro :client',
'activity_8' => ':user archivoval fakturu :invoice',
'activity_9' => ':user smazal fakturu :invoice',
'activity_10' => ':contact entered payment :payment for :payment_amount on invoice :invoice for :client',
'activity_10' => ':user entered payment :payment for :payment_amount on invoice :invoice for :client',
'activity_11' => ':user změnil platbu :payment',
'activity_12' => ':user archivoval platbu :payment',
'activity_13' => ':user smazal platbu :payment',

View File

@ -5122,7 +5122,8 @@ $LANG = array(
'lang_French - Swiss' => 'French - Swiss',
'currency_swazi_lilangeni' => 'Swazi Lilangeni',
'income' => 'Income',
'amount_received_help' => 'Enter a value here if the total amount received was MORE than the invoice amount, or when recording a payment with no invoices. Otherwise this field should be left blank.',
'vendor_phone' => 'Vendor Phone',
);

View File

@ -761,7 +761,7 @@ $LANG = array(
'activity_7' => ':contact viewed invoice :invoice for :client',
'activity_8' => ':user archived invoice :invoice',
'activity_9' => ':user deleted invoice :invoice',
'activity_10' => ':contact entered payment :payment for :payment_amount on invoice :invoice for :client',
'activity_10' => ':user entered payment :payment for :payment_amount on invoice :invoice for :client',
'activity_11' => ':user updated payment :payment',
'activity_12' => ':user archived payment :payment',
'activity_13' => ':user deleted payment :payment',

View File

@ -748,7 +748,7 @@ $LANG = array(
'activity_7' => ':contact a visualisé la facture :invoice pour :client',
'activity_8' => ':user a archivé la facture :invoice',
'activity_9' => ':user a supprimé la facture :invoice',
'activity_10' => ':contact a saisi le paiement :payment de :payment_amount de la facture :invoice pour :client',
'activity_10' => ':user a saisi le paiement :payment de :payment_amount de la facture :invoice pour :client',
'activity_11' => ':user a mis à jour le paiement :payment',
'activity_12' => ':user a archivé le paiement :payment',
'activity_13' => ':user a supprimé le paiement :payment',
@ -1433,7 +1433,7 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette
'payment_type_Swish' => 'Swish',
'payment_type_Alipay' => 'Alipay',
'payment_type_Sofort' => 'Sofort',
'payment_type_SEPA' => 'SEPA Prélèvement automatique',
'payment_type_SEPA' => 'Prélèvement automatique SEPA',
'payment_type_Bitcoin' => 'Bitcoin',
'payment_type_GoCardless' => 'GoCardless',
'payment_type_Zelle' => 'Zelle',
@ -2448,7 +2448,7 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette
'alipay' => 'Alipay',
'sofort' => 'Sofort',
'sepa' => 'SEPA Prélèvement automatique',
'sepa' => 'Prélèvement automatique SEPA',
'name_without_special_characters' => 'Veuillez entrer un nom en utilisant seulement les lettres de a à z et des espaces.',
'enable_alipay' => 'Accepter Alipay',
'enable_sofort' => 'Accepter les transferts d\'institutions bancaires de l\'Union européenne',
@ -3130,7 +3130,7 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette
'number_padding' => 'Disposition du numéro',
'general' => 'Général',
'surcharge_field' => 'Champ Surcharge',
'company_value' => 'Valeur de compagnie',
'company_value' => 'Valeur de l\'entreprise',
'credit_field' => 'Champ Crédit',
'payment_field' => 'Champ Paiement',
'group_field' => 'Champ Groupe',
@ -3510,7 +3510,7 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette
'search_expenses' => 'Recherche de dépenses',
'search_payments' => 'Recherche de paiements',
'search_groups' => 'Recherche de groupes',
'search_company' => 'Recherche d\'entreprises',
'search_company' => 'Recherche d\'une entreprise',
'cancelled_invoice' => 'La facture a été annulée',
'cancelled_invoices' => 'Les factures ont été annulées',
'reversed_invoice' => 'La facture a été inversée',
@ -3972,8 +3972,8 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette
'account_balance' => 'Solde de compte',
'thanks' => 'Merci',
'minimum_required_payment' => 'Le paiement minimum requis est :amount',
'under_payments_disabled' => 'La société ne tolère pas le sous-paiement.',
'over_payments_disabled' => 'La société ne tolère pas le sur-paiement.',
'under_payments_disabled' => 'L\'entreprise ne tolère pas le sous-paiement.',
'over_payments_disabled' => 'L\'entreprise ne tolère pas le sur-paiement.',
'saved_at' => 'Enregistré à :time',
'credit_payment' => 'Le crédit a été appliqué à la facture :invoice_number',
'credit_subject' => 'Nouveau crédit :credit de :account',
@ -4109,7 +4109,7 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette
'migration_api_secret_notice' => 'Vous pouvez trouver API_SECRET dans le fichier .env ou Invoice Ninja v5. Si la propriété est manquante, laissez le champ vide.',
'billing_coupon_notice' => 'Votre rabais sera appliqué au moment de régler votre facture.',
'use_last_email' => 'Utiliser le dernier e-mail',
'activate_company' => 'Activer la société',
'activate_company' => 'Activer l\'entreprise',
'activate_company_help' => 'Activez les courriels, les factures récurrentes et les notifications',
'an_error_occurred_try_again' => 'Une erreur s\'est produite, veuillez réessayer',
'please_first_set_a_password' => 'Veuillez d\'abord définir un mot de passe',
@ -4242,9 +4242,9 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette
'giropay_law' => 'En saisissant vos information de client (tel que votre nom et numéro de compte), vous (le client) agréez à donner cette information volontairement.',
'klarna' => 'Klarna',
'eps' => 'EPS',
'becs' => 'BECS Prélèvement automatique',
'bacs' => 'Débit direct BACS',
'payment_type_BACS' => 'Débit direct BACS',
'becs' => 'Prélèvement automatique BECS',
'bacs' => 'Prélèvement automatique BACS',
'payment_type_BACS' => 'Prélèvement automatique BACS',
'missing_payment_method' => 'Veuillez ajouter une méthode de paiement avant de payer.',
'becs_mandate' => 'En fournissant vos coordonnées bancaires, vous acceptez <a class="underline" href="https://stripe.com/au-becs-dd-service-agreement/legal">l\'entente de service de requête de débit direct</a>, et autorisez Stripe Payments Australia Pty Ltd ACN 160 180 343 Direct Debit User ID number 507156 (“Stripe”) à prélever votre compte par le Bulk Electronic Clearing System (BECS) de la part du :company (“Merchant”) pour tout montant séparément communiqué à vous par le Marchand. Vous certifiez que vous êtes propriétaire du compte ou que vous êtes un signataire autorisé sur le compte indiqué plus haut.',
'you_need_to_accept_the_terms_before_proceeding' => 'Vous devez accepter les conditions pour continuer.',
@ -4257,12 +4257,12 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette
'browser_pay' => 'Google Pay, Apple Pay et Microsoft Pay',
'no_available_methods' => 'Cartes de crédit introuvable sur cet appareil. <a href="https://invoiceninja.github.io/docs/payments#apple-pay-google-pay-microsoft-pay" target="_blank" class="underline">Plus d\'information.</a>',
'gocardless_mandate_not_ready' => 'La demande de paiement n\'est pas prête. Veuillez essayer de nouveau plus tard.',
'payment_type_instant_bank_pay' => 'Interac',
'payment_type_instant_bank_pay' => 'Instant Bank Pay',
'payment_type_iDEAL' => 'iDEAL',
'payment_type_Przelewy24' => 'Przelewy24',
'payment_type_Mollie Bank Transfer' => 'Virement bancaire Mollie',
'payment_type_KBC/CBC' => 'KBC/CBC',
'payment_type_Instant Bank Pay' => 'Interac',
'payment_type_Instant Bank Pay' => 'Instant Bank Pay',
'payment_type_Hosted Page' => 'Page hébergée',
'payment_type_GiroPay' => 'GiroPay',
'payment_type_EPS' => 'EPS',
@ -4279,7 +4279,7 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette
'show_pdf_preview_help' => 'Afficher l\'aperçu PDF lors de la rédaction des factures',
'print_pdf' => 'Imprimer le PDF',
'remind_me' => 'Rappel',
'instant_bank_pay' => 'Interac',
'instant_bank_pay' => 'Instant Bank Pay',
'click_selected' => 'Cliquer sur la sélection',
'hide_preview' => 'Cacher l\'aperçu',
'edit_record' => 'Modifier l\'enregistrement',
@ -5111,7 +5111,10 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette
'admin_initiated_payments_help' => 'Permet la saisie d\'un paiement dans le portal d\'administration sans facture',
'paid_date' => 'Date de paiement',
'downloaded_entities' => 'Un courriel sera envoyé avec le ou les PDF',
'lang_French - Swiss' => 'Français - Suisse',
'currency_swazi_lilangeni' => 'Lilangeni Eswatinien',
'income' => 'Revenu',
);

View File

@ -753,7 +753,7 @@ $LANG = array(
'activity_7' => ':contact が :client の請求書 :invoice を閲覧しました',
'activity_8' => ':user は 請求書 :invoice をアーカイブしました。',
'activity_9' => ':user は 請求書 :invoice をアーカイブしました。',
'activity_10' => ':contact は、:client の請求書 :invoice に :payment_amount の支払い :payment を入力しました',
'activity_10' => ':user は、:client の請求書 :invoice に :payment_amount の支払い :payment を入力しました',
'activity_11' => ':user updated payment :payment',
'activity_12' => ':user archived payment :payment',
'activity_13' => ':user deleted payment :payment',

View File

@ -754,7 +754,7 @@ $LANG = array(
'activity_7' => ':contact viewed invoice :invoice for :client',
'activity_8' => ':user archived invoice :invoice',
'activity_9' => ':user deleted invoice :invoice',
'activity_10' => ':contact entered payment :payment for :payment_amount on invoice :invoice for :client',
'activity_10' => ':user entered payment :payment for :payment_amount on invoice :invoice for :client',
'activity_11' => ':user atnaujino mokėjimą :payment',
'activity_12' => ':user archived payment :payment',
'activity_13' => ':user deleted payment :payment',

View File

@ -754,7 +754,7 @@ $LANG = array(
'activity_7' => ':contact har sett fakturaen :invoice for :client',
'activity_8' => ':user arkiverte faktura :invoice',
'activity_9' => ':user slettet faktura :invoice',
'activity_10' => ':contact la inn betaling :payment på :payment_amount',
'activity_10' => ':user la inn betaling :payment på :payment_amount',
'activity_11' => ':user oppdaterte betaling :payment',
'activity_12' => ':user arkiverte betaling :payment',
'activity_13' => ':user slettet betaling :payment',

View File

@ -751,7 +751,7 @@ $LANG = array(
'activity_7' => ':contact viewed invoice :invoice for :client',
'activity_8' => ':user ka arkivuar faturën :invoice',
'activity_9' => ':user ka fshirë faturën :invoice',
'activity_10' => ':contact entered payment :payment for :payment_amount on invoice :invoice for :client',
'activity_10' => ':user entered payment :payment for :payment_amount on invoice :invoice for :client',
'activity_11' => ':user ka perditesuar pagesën :payment',
'activity_12' => ':user ka arkivuar pagesën :payment',
'activity_13' => ':user ka fshirë pagesën :payment',

View File

@ -753,7 +753,7 @@ adresine gönderildi. Müthiş tüm özelliklerin kilidini açmak için lütfen
'activity_7' => ':contact viewed invoice :invoice for :client',
'activity_8' => ':user :invoice nolu faturayı arşivledi',
'activity_9' => ':user :invoice nolu faturayı sildi',
'activity_10' => ':contact entered payment :payment for :payment_amount on invoice :invoice for :client',
'activity_10' => ':user entered payment :payment for :payment_amount on invoice :invoice for :client',
'activity_11' => ':user :payment tutarlı ödemeyi güncelledi',
'activity_12' => ':user :payment tutarlı ödemeyi arşivledi',
'activity_13' => ':user :payment tutarlı ödemeyi sildi',

View File

@ -3,10 +3,10 @@ const MANIFEST = 'flutter-app-manifest';
const TEMP = 'flutter-temp-cache';
const CACHE_NAME = 'flutter-app-cache';
const RESOURCES = {
"/": "faecf4eb3989415f8ac174b178967581",
"/": "0aa10ee6dcb142946702cadae9812704",
"flutter.js": "a85fcf6324d3c4d3ae3be1ae4931e9c5",
"favicon.png": "dca91c54388f52eded692718d5a98b8b",
"main.dart.js": "b0022e7084e6d7ec1e1a04e5d9f20501",
"main.dart.js": "23a2e1cbb7cb043fd5ef1e51de367bb6",
"version.json": "bf49df736fed3f74ade0dbaebf08de11",
"canvaskit/profiling/canvaskit.js": "c21852696bc1cc82e8894d851c01921a",
"canvaskit/profiling/canvaskit.wasm": "371bc4e204443b0d5e774d64a046eb99",

View File

@ -77,6 +77,7 @@ var Payment = /*#__PURE__*/function () {
}, {
key: "displaySignature",
value: function displaySignature() {
document.getElementById("signature-next-step").disabled = true;
var displaySignatureModal = document.getElementById("displaySignatureModal");
displaySignatureModal.removeAttribute("style");
var signaturePad = new SignaturePad(document.getElementById("signature-pad"), {
@ -91,7 +92,6 @@ var Payment = /*#__PURE__*/function () {
key: "handle",
value: function handle() {
var _this2 = this;
document.getElementById("signature-next-step").disabled = true;
document.querySelectorAll(".dropdown-gateway-button").forEach(function (element) {
element.addEventListener("click", function () {
if (!_this2.submitting) {

View File

@ -54,7 +54,12 @@ var Accept = /*#__PURE__*/function () {
value: function handle() {
var _this = this;
document.getElementById("signature-next-step").disabled = true;
document.getElementById("close-button").addEventListener('click', function () {
var approveButton = document.getElementById("approve-button");
if (approveButton) approveButton.disabled = false;
});
document.getElementById('approve-button').addEventListener('click', function () {
console.log("accepted ");
if (_this.shouldDisplaySignature && _this.shouldDisplayTerms) {
_this.displaySignature();
document.getElementById('signature-next-step').addEventListener('click', function () {

View File

@ -61,12 +61,14 @@ var Approve = /*#__PURE__*/function () {
value: function handle() {
var _this = this;
document.getElementById("signature-next-step").disabled = true;
document.getElementById("close_button").addEventListener('click', function () {
document.getElementById("close-button").addEventListener('click', function () {
var approveButton = document.getElementById("approve-button");
console.log('close button');
if (approveButton) approveButton.disabled = false;
});
document.getElementById("hide_close").addEventListener('click', function () {
document.getElementById("close-terms-button").addEventListener('click', function () {
var approveButton = document.getElementById("approve-button");
console.log('close terms-button');
if (approveButton) approveButton.disabled = false;
});
document.getElementById('approve-button').addEventListener('click', function () {

146211
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

137261
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

File diff suppressed because one or more lines are too long

View File

@ -9,13 +9,13 @@
"/js/clients/payments/stripe-bacs.js": "/js/clients/payments/stripe-bacs.js?id=a82927936510bbecc60de3e9742f9739",
"/js/clients/invoices/action-selectors.js": "/js/clients/invoices/action-selectors.js?id=b7601721afa9599518ba894a0f88f947",
"/js/clients/purchase_orders/action-selectors.js": "/js/clients/purchase_orders/action-selectors.js?id=b9c510dffa8420879983f04cd521564c",
"/js/clients/purchase_orders/accept.js": "/js/clients/purchase_orders/accept.js?id=d13b4a7934c9f7ddf2154f8f233843f7",
"/js/clients/invoices/payment.js": "/js/clients/invoices/payment.js?id=1bd5f983e7d9394fe9ac603dea94e901",
"/js/clients/purchase_orders/accept.js": "/js/clients/purchase_orders/accept.js?id=e9040b9f5e9bbf5d04b8a02a284d0481",
"/js/clients/invoices/payment.js": "/js/clients/invoices/payment.js?id=edeab1566d21cb93901d571bdcbb7c30",
"/js/clients/payments/stripe-sofort.js": "/js/clients/payments/stripe-sofort.js?id=f0252b9b140b667794c252ae200cd844",
"/js/clients/payments/stripe-alipay.js": "/js/clients/payments/stripe-alipay.js?id=360b39d82a1a7dd90a8351776fbd9edb",
"/js/clients/payments/checkout-credit-card.js": "/js/clients/payments/checkout-credit-card.js?id=a2168c43060a7de40da20b5fc599bcab",
"/js/clients/quotes/action-selectors.js": "/js/clients/quotes/action-selectors.js?id=4158693089b29ee8e13cb7d9ce4480a9",
"/js/clients/quotes/approve.js": "/js/clients/quotes/approve.js?id=4e596cec23cdd6487534e6ed5499d791",
"/js/clients/quotes/approve.js": "/js/clients/quotes/approve.js?id=4091846cf28ded010380cc2678fbe831",
"/js/clients/payments/stripe-credit-card.js": "/js/clients/payments/stripe-credit-card.js?id=bfa116c1df42c641bc7a3ff4fa8d50dd",
"/js/setup/setup.js": "/js/setup/setup.js?id=086b9e114b0b9ee01f909d686f489162",
"/js/clients/payments/card-js.min.js": "/js/clients/payments/card-js.min.js?id=cf50b5ba1fcd1d184bf0c10d710672c8",

View File

@ -84,6 +84,8 @@ class Payment {
}
displaySignature() {
document.getElementById("signature-next-step").disabled = true;
let displaySignatureModal = document.getElementById(
"displaySignatureModal"
);
@ -104,7 +106,6 @@ class Payment {
}
handle() {
document.getElementById("signature-next-step").disabled = true;
document
.querySelectorAll(".dropdown-gateway-button")

View File

@ -48,9 +48,20 @@ class Accept {
document.getElementById("signature-next-step").disabled = true;
document.getElementById("close-button").addEventListener('click', () => {
const approveButton = document.getElementById("approve-button");
if (approveButton)
approveButton.disabled = false;
});
document
.getElementById('approve-button')
.addEventListener('click', () => {
console.log("accepted ");
if (this.shouldDisplaySignature && this.shouldDisplayTerms) {
this.displaySignature();

View File

@ -55,22 +55,27 @@ class Approve {
document.getElementById("signature-next-step").disabled = true;
document.getElementById("close_button").addEventListener('click', () => {
document.getElementById("close-button").addEventListener('click', () => {
const approveButton = document.getElementById("approve-button");
console.log('close button');
if(approveButton)
approveButton.disabled = false;
});
document.getElementById("hide_close").addEventListener('click', () => {
document.getElementById("close-terms-button").addEventListener('click', () => {
const approveButton = document.getElementById("approve-button");
if(approveButton)
console.log('close terms-button');
if (approveButton)
approveButton.disabled = false;
});
document
.getElementById('approve-button')
.addEventListener('click', () => {

View File

@ -1,4 +1,4 @@
@push('head')
<style>
table, th, td {
@ -26,6 +26,7 @@ span {
}
</style>
@endpush
<div class="w-full bg-white py-3 border-2 shadow sm:rounded-lg">
@ -43,7 +44,6 @@ span {
</div>
<div id="user-details" class="mt-3 px-3 border-b-2 border-fuschia-600 flex flex-col items-end">
<div x-data="{ show_user: false }" class="mb-3">
@ -77,7 +77,15 @@ span {
<td>
<div class="product-information">
<div class="item-details">
<p class="mt-2">{{ $product['quantity'] }} × {{ $product['cost'] }}</p>
<p class="mt-2">
@if($show_quantity)
{{ $product['quantity'] }} x
@endif
@if($show_cost)
{{ $product['cost'] }}
@endif
</p>
<p class="overflow-ellipsis overflow-hidden px-1 mb-2">{{ $product['notes'] }}</p>
</div>
</div>
@ -174,10 +182,10 @@ span {
{{ strip_tags($entity->footer) }}
</div>
</div>
@endif
@push('head')
<script>
document.addEventListener('DOMContentLoaded', () => {
@ -224,3 +232,4 @@ span {
});
</script>
@endpush

View File

@ -1,63 +1,64 @@
<div>
<div class="flex flex-col items-end mb-2">
<button wire:loading.attr="disabled" wire:click="downloadPdf" class="bg-gray-100 hover:bg-gray-200 text-gray-800 font-bold px-2 rounded inline-flex">
<span>{{ ctrans('texts.download_pdf') }}</span>
<div wire:loading wire:target="downloadPdf">
<svg class="animate-spin h-5 w-5 text-blue" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<div class="flex flex-col items-end mb-2">
<button wire:loading.attr="disabled" wire:click="downloadPdf" class="bg-gray-100 hover:bg-gray-200 text-gray-800 font-bold px-2 rounded inline-flex">
<span>{{ ctrans('texts.download_pdf') }}</span>
<div wire:loading wire:target="downloadPdf">
<svg class="animate-spin h-5 w-5 text-blue" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</div>
</button>
</div>
<div class="hidden lg:block">
<div wire:init="getPdf()">
@if($pdf)
<iframe id="pdf-iframe" src="{!! $pdf !!}" class="h-screen w-full border-0 mt-4"></iframe>
@else
<div class="flex mt-4 place-items-center">
<span class="loader m-auto"></span>
<style type="text/css">
.loader {
width: 48px;
height: 48px;
border-radius: 50%;
position: relative;
animation: rotate 1s linear infinite
}
.loader::before , .loader::after {
content: "";
box-sizing: border-box;
position: absolute;
inset: 0px;
border-radius: 50%;
border: 5px solid #454545;
animation: prixClipFix 2s linear infinite ;
}
.loader::after{
border-color: #FF3D00;
animation: prixClipFix 2s linear infinite , rotate 0.5s linear infinite reverse;
inset: 6px;
}
@keyframes rotate {
0% {transform: rotate(0deg)}
100% {transform: rotate(360deg)}
}
@keyframes prixClipFix {
0% {clip-path:polygon(50% 50%,0 0,0 0,0 0,0 0,0 0)}
25% {clip-path:polygon(50% 50%,0 0,100% 0,100% 0,100% 0,100% 0)}
50% {clip-path:polygon(50% 50%,0 0,100% 0,100% 100%,100% 100%,100% 100%)}
75% {clip-path:polygon(50% 50%,0 0,100% 0,100% 100%,0 100%,0 100%)}
100% {clip-path:polygon(50% 50%,0 0,100% 0,100% 100%,0 100%,0 0)}
}
</style>
</div>
@endif
</div>
</button>
</div>
<div class="hidden lg:block">
<div wire:init="getPdf()">
@if($pdf)
<!-- <iframe id="pdf-iframe" src="{!! $pdf !!}" class="h-screen w-full border-0 mt-4"></iframe> -->
<iframe id="pdf-iframe" src="/{{ $route_entity }}/showBlob/{{ $pdf }}" class="h-screen w-full border-0 mt-4"></iframe>
@else
<div class="flex mt-4 place-items-center">
<span class="loader m-auto"></span>
<style type="text/css">
.loader {
width: 48px;
height: 48px;
border-radius: 50%;
position: relative;
animation: rotate 1s linear infinite
}
.loader::before , .loader::after {
content: "";
box-sizing: border-box;
position: absolute;
inset: 0px;
border-radius: 50%;
border: 5px solid #454545;
animation: prixClipFix 2s linear infinite ;
}
.loader::after{
border-color: #FF3D00;
animation: prixClipFix 2s linear infinite , rotate 0.5s linear infinite reverse;
inset: 6px;
}
@keyframes rotate {
0% {transform: rotate(0deg)}
100% {transform: rotate(360deg)}
}
@keyframes prixClipFix {
0% {clip-path:polygon(50% 50%,0 0,0 0,0 0,0 0,0 0)}
25% {clip-path:polygon(50% 50%,0 0,100% 0,100% 0,100% 0,100% 0)}
50% {clip-path:polygon(50% 50%,0 0,100% 0,100% 100%,100% 100%,100% 100%)}
75% {clip-path:polygon(50% 50%,0 0,100% 0,100% 100%,0 100%,0 100%)}
100% {clip-path:polygon(50% 50%,0 0,100% 0,100% 100%,0 100%,0 0)}
}
</style>
</div>
@endif
</div>
</div>
</div>
<div class="block lg:hidden">
@include('portal.ninja2020.components.html-viewer')
</div>
<div class="block lg:hidden">
@include('portal.ninja2020.components.html-viewer')
</div>
</div>

View File

@ -4,8 +4,8 @@
@endphp
@push('head')
<meta name="pdf-url" content="{{ $url ?? $entity->pdf_file_path($invitation, 'url', true) }}?cache_buster={{time()}}">
<script src="{{ asset('js/vendor/pdf.js/pdf.min.js') }}"></script>
<!-- <meta name="pdf-url" content="{{ $url ?? $entity->pdf_file_path($invitation, 'url', true) }}?cache_buster={{time()}}">
<script src="{{ asset('js/vendor/pdf.js/pdf.min.js') }}"></script> -->
@endpush
<div class="flex items-center justify-between mt-4">
@ -56,18 +56,18 @@
</section>
</div>
@if($mobile)
<!-- @if($mobile)
<div class="w-full h-full overflow-auto mt-4">
<canvas id="pdf-placeholder" class="shadow rounded-lg bg-white"></canvas>
</div>
@else
@else -->
@livewire('pdf-slot', ['entity' => $entity, 'invitation' => $invitation, 'db' => $invitation->company->db])
<!-- <iframe id="pdf-iframe" src="{{ $url ?? $entity->pdf_file_path($invitation, 'url', true) }}?cache_buster={{time()}}" class="h-screen w-full border-0 mt-4"></iframe> -->
@endif
<!-- @endif -->
@if($mobile)
@push('footer')
<script src="{{ asset('js/clients/shared/pdf.js') }}" defer></script>
<!-- <script src="{{ asset('js/clients/shared/pdf.js') }}" defer></script> -->
@endpush
@endif

View File

@ -32,8 +32,7 @@
</div>
@include('portal.ninja2020.components.entity-documents', ['entity' => $credit])
@include('portal.ninja2020.components.pdf-viewer', ['entity' => $credit, 'invitation' => $invitation])
@livewire('pdf-slot', ['entity' => $credit, 'invitation' => $invitation, 'db' => $invitation->company->db])
@endsection

View File

@ -1,4 +1,4 @@
<div style="display: none;" id="displaySignatureModal" class="fixed bottom-0 inset-x-0 px-4 pb-4 sm:inset-0 sm:flex sm:items-center sm:justify-center">
<div style="display: none;" id="displaySignatureModal" class="fixed bottom-0 inset-x-0 px-4 pb-4 sm:inset-0 sm:flex sm:items-center sm:justify-center" x-data>
<div x-show="open" x-transition:enter="ease-out duration-300" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="ease-in duration-200" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" class="fixed inset-0 transition-opacity">
<div class="absolute inset-0 bg-gray-500 opacity-75"></div>
</div>
@ -21,14 +21,14 @@
</div>
</div>
</div>
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<div class="flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto">
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse" >
<div class="flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto" x-data>
<button type="button" id="signature-next-step" class="button button-primary bg-primary" @click="document.getElementById('displaySignatureModal').style.display = 'none';">
{{ ctrans('texts.next_step') }}
</button>
</div>
<div class="mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto" id="close_button">
<button @click="document.getElementById('displaySignatureModal').style.display = 'none';" type="button" class="button button-secondary">
<div class="mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto" x-data>
<button @click="document.getElementById('displaySignatureModal').style.display = 'none';" type="button" class="button button-secondary" id="close-button">
{{ ctrans('texts.close') }}
</button>
</div>

View File

@ -1,4 +1,4 @@
<div style="display: none;" id="displayTermsModal" class="fixed bottom-0 inset-x-0 px-4 pb-4 sm:inset-0 sm:flex sm:items-center sm:justify-center">
<div style="display: none;" id="displayTermsModal" class="fixed bottom-0 inset-x-0 px-4 pb-4 sm:inset-0 sm:flex sm:items-center sm:justify-center" x-data>
<div x-show="open" x-transition:enter="ease-out duration-300" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="ease-in duration-200" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" class="fixed inset-0 transition-opacity">
<div class="absolute inset-0 bg-gray-500 opacity-75"></div>
</div>
@ -24,7 +24,7 @@
</div>
</div>
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<div class="flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto">
<div class="flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto" x-data>
<button
type="button"
id="accept-terms-button"
@ -33,8 +33,8 @@
{{ ctrans('texts.i_agree') }}
</button>
</div>
<div class="mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto" id="hide_close">
<button @click="document.getElementById('displayTermsModal').style.display = 'none';" type="button" class="button button-secondary">
<div class="mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto" x-data>
<button @click="document.getElementById('displayTermsModal').style.display = 'none';" type="button" class="button button-secondary" id="close-terms-button">
{{ ctrans('texts.close') }}
</button>
</div>

View File

@ -5,7 +5,6 @@
<meta name="show-invoice-terms" content="{{ $settings->show_accept_invoice_terms ? true : false }}">
<meta name="require-invoice-signature" content="{{ $client->user->account->hasFeature(\App\Models\Account::FEATURE_INVOICE_SETTINGS) && $settings->require_invoice_signature }}">
@include('portal.ninja2020.components.no-cache')
<script src="{{ asset('vendor/signature_pad@2.3.2/signature_pad.min.js') }}"></script>
@endpush
@ -96,18 +95,26 @@
@endif
@include('portal.ninja2020.components.entity-documents', ['entity' => $invoice])
@include('portal.ninja2020.components.pdf-viewer', ['entity' => $invoice, 'invitation' => $invitation])
@include('portal.ninja2020.invoices.includes.terms', ['entities' => [$invoice], 'entity_type' => ctrans('texts.invoice')])
@include('portal.ninja2020.invoices.includes.signature')
@livewire('pdf-slot', ['entity' => $invoice, 'invitation' => $invitation, 'db' => $invitation->company->db])
@endsection
@section('footer')
<script src="{{ asset('js/clients/invoices/payment.js') }}"></script>
<script src="{{ asset('vendor/clipboard.min.js') }}"></script>
@include('portal.ninja2020.invoices.includes.signature')
@include('portal.ninja2020.invoices.includes.terms', ['entities' => [$invoice], 'entity_type' => ctrans('texts.invoice')])
@endsection
@push('head')
<script src="{{ asset('js/clients/invoices/payment.js') }}" defer></script>
<script src="{{ asset('vendor/clipboard.min.js') }}" defer></script>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', () => {
var clipboard = new ClipboardJS('.btn');
});
</script>
@endsection
@endpush

View File

@ -46,19 +46,27 @@
@endif
@include('portal.ninja2020.components.entity-documents', ['entity' => $purchase_order])
@include('portal.ninja2020.components.pdf-viewer', ['entity' => $purchase_order, 'invitation' => $invitation])
@livewire('pdf-slot', ['entity' => $purchase_order, 'invitation' => $invitation, 'db' => $invitation->company->db])
@endsection
@section('footer')
@include('portal.ninja2020.invoices.includes.terms', ['entities' => [$purchase_order], 'entity_type' => ctrans('texts.purchase_order')])
@include('portal.ninja2020.invoices.includes.signature')
@endsection
@section('footer')
<script src="{{ asset('js/clients/purchase_orders/accept.js') }}"></script>
<script src="{{ asset('vendor/clipboard.min.js') }}"></script>
@push('head')
<script src="{{ asset('js/clients/purchase_orders/accept.js') }}" defer></script>
<script src="{{ asset('vendor/clipboard.min.js') }}" defer></script>
<script type="text/javascript">
var clipboard = new ClipboardJS('.btn');
document.addEventListener('DOMContentLoaded', () => {
var clipboard = new ClipboardJS('.btn');
});
</script>
@endsection
@endpush

View File

@ -1,5 +1,5 @@
<div style="display: none;" id="displaySignatureModal"
class="fixed bottom-0 inset-x-0 px-4 pb-4 sm:inset-0 sm:flex sm:items-center sm:justify-center">
class="fixed bottom-0 inset-x-0 px-4 pb-4 sm:inset-0 sm:flex sm:items-center sm:justify-center" x-data>
<div x-show="open" x-transition:enter="ease-out duration-300" x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100" x-transition:leave="ease-in duration-200"
x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0"

View File

@ -106,19 +106,27 @@
@endif
@include('portal.ninja2020.components.entity-documents', ['entity' => $quote])
@include('portal.ninja2020.components.pdf-viewer', ['entity' => $quote, 'invitation' => $invitation])
@include('portal.ninja2020.invoices.includes.terms', ['entities' => [$quote], 'entity_type' => ctrans('texts.quote')])
@include('portal.ninja2020.invoices.includes.signature')
@include('portal.ninja2020.quotes.includes.user-input')
@livewire('pdf-slot', ['entity' => $quote, 'invitation' => $invitation, 'db' => $invitation->company->db])
@endsection
@section('footer')
<script src="{{ asset('js/clients/quotes/approve.js') }}"></script>
<script src="{{ asset('vendor/clipboard.min.js') }}"></script>
@include('portal.ninja2020.quotes.includes.user-input')
@include('portal.ninja2020.invoices.includes.terms', ['entities' => [$quote], 'entity_type' => ctrans('texts.quote')])
@include('portal.ninja2020.invoices.includes.signature')
@endsection
<script type="text/javascript">
@push('head')
<script src="{{ asset('js/clients/quotes/approve.js') }}" defer></script>
<script src="{{ asset('vendor/clipboard.min.js') }}" defer></script>
<script type="text/javascript" defer>
document.addEventListener('DOMContentLoaded', () => {
var clipboard = new ClipboardJS('.btn');
});
</script>
@endsection
@endpush

View File

@ -75,6 +75,7 @@ use App\Http\Controllers\HostedMigrationController;
use App\Http\Controllers\ConnectedAccountController;
use App\Http\Controllers\RecurringExpenseController;
use App\Http\Controllers\RecurringInvoiceController;
use App\Http\Controllers\ProtectedDownloadController;
use App\Http\Controllers\ClientGatewayTokenController;
use App\Http\Controllers\Reports\TaskReportController;
use App\Http\Controllers\Auth\ForgotPasswordController;
@ -85,6 +86,7 @@ use App\Http\Controllers\Auth\PasswordTimeoutController;
use App\Http\Controllers\PreviewPurchaseOrderController;
use App\Http\Controllers\Reports\ClientReportController;
use App\Http\Controllers\Reports\CreditReportController;
use App\Http\Controllers\Reports\VendorReportController;
use App\Http\Controllers\Reports\ExpenseReportController;
use App\Http\Controllers\Reports\InvoiceReportController;
use App\Http\Controllers\Reports\PaymentReportController;
@ -101,11 +103,12 @@ use App\Http\Controllers\Support\Messages\SendingController;
use App\Http\Controllers\Reports\ClientSalesReportController;
use App\Http\Controllers\Reports\InvoiceItemReportController;
use App\Http\Controllers\PaymentNotificationWebhookController;
use App\Http\Controllers\ProtectedDownloadController;
use App\Http\Controllers\Reports\ProductSalesReportController;
use App\Http\Controllers\Reports\ClientBalanceReportController;
use App\Http\Controllers\Reports\ClientContactReportController;
use App\Http\Controllers\Reports\PurchaseOrderReportController;
use App\Http\Controllers\Reports\RecurringInvoiceReportController;
use App\Http\Controllers\Reports\PurchaseOrderItemReportController;
Route::group(['middleware' => ['throttle:api', 'api_secret_check']], function () {
Route::post('api/v1/signup', [AccountController::class, 'store'])->name('signup.submit');
@ -272,7 +275,6 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale']
Route::post('recurring_expenses/bulk', [RecurringExpenseController::class, 'bulk'])->name('recurring_expenses.bulk');
Route::put('recurring_expenses/{recurring_expense}/upload', [RecurringExpenseController::class, 'upload']);
Route::resource('recurring_invoices', RecurringInvoiceController::class); // name = (recurring_invoices. index / create / show / update / destroy / edit
Route::post('recurring_invoices/bulk', [RecurringInvoiceController::class, 'bulk'])->name('recurring_invoices.bulk');
Route::put('recurring_invoices/{recurring_invoice}/upload', [RecurringInvoiceController::class, 'upload']);
@ -291,6 +293,8 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale']
Route::post('reports/expenses', ExpenseReportController::class)->middleware('throttle:20,1');
Route::post('reports/invoices', InvoiceReportController::class)->middleware('throttle:20,1');
Route::post('reports/invoice_items', InvoiceItemReportController::class)->middleware('throttle:20,1');
Route::post('reports/purchase_orders', PurchaseOrderReportController::class)->middleware('throttle:20,1');
Route::post('reports/purchase_order_items', PurchaseOrderItemReportController::class)->middleware('throttle:20,1');
Route::post('reports/quotes', QuoteReportController::class)->middleware('throttle:20,1');
Route::post('reports/quote_items', QuoteItemReportController::class)->middleware('throttle:20,1');
Route::post('reports/recurring_invoices', RecurringInvoiceReportController::class)->middleware('throttle:20,1');
@ -298,7 +302,7 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale']
Route::post('reports/products', ProductReportController::class)->middleware('throttle:20,1');
Route::post('reports/product_sales', ProductSalesReportController::class)->middleware('throttle:20,1');
Route::post('reports/tasks', TaskReportController::class)->middleware('throttle:20,1');
Route::post('reports/vendors', VendorReportController::class)->middleware('throttle:20,1');
Route::post('reports/profitloss', ProfitAndLossController::class);
Route::post('reports/ar_detail_report', ARDetailReportController::class);
Route::post('reports/ar_summary_report', ARSummaryReportController::class);

View File

@ -18,18 +18,18 @@ use App\Http\Controllers\ClientPortal\SubscriptionController;
use App\Http\Controllers\Auth\ContactForgotPasswordController;
use App\Http\Controllers\ClientPortal\PaymentMethodController;
Route::get('client', [ContactLoginController::class, 'showLoginForm'])->name('client.catchall')->middleware(['domain_db', 'contact_account','locale']); //catch all
Route::get('client', [ContactLoginController::class, 'showLoginForm'])->name('client.catchall')->middleware(['domain_db', 'contact_account','locale', 'throttle:portal']); //catch all
Route::get('client/login/{company_key?}', [ContactLoginController::class, 'showLoginForm'])->name('client.login')->middleware(['domain_db', 'contact_account','locale']);
Route::get('client/login/{company_key?}', [ContactLoginController::class, 'showLoginForm'])->name('client.login')->middleware(['domain_db', 'contact_account','locale', 'throttle:portal']);
Route::post('client/login/{company_key?}', [ContactLoginController::class, 'login'])->name('client.login.submit');
Route::get('client/register/{company_key?}', [ContactRegisterController::class, 'showRegisterForm'])->name('client.register')->middleware(['domain_db', 'contact_account', 'contact_register','locale']);
Route::post('client/register/{company_key?}', [ContactRegisterController::class, 'register'])->middleware(['domain_db', 'contact_account', 'contact_register', 'locale', 'throttle:10,1']);
Route::post('client/register/{company_key?}', [ContactRegisterController::class, 'register'])->middleware(['domain_db', 'contact_account', 'contact_register', 'locale', 'throttle:portal']);
Route::get('client/password/reset', [ContactForgotPasswordController::class, 'showLinkRequestForm'])->name('client.password.request')->middleware(['domain_db', 'contact_account','locale']);
Route::post('client/password/email', [ContactForgotPasswordController::class, 'sendResetLinkEmail'])->name('client.password.email')->middleware('locale');
Route::get('client/password/reset/{token}', [ContactResetPasswordController::class, 'showResetForm'])->name('client.password.reset')->middleware(['domain_db', 'contact_account','locale']);
Route::post('client/password/reset', [ContactResetPasswordController::class, 'reset'])->name('client.password.update')->middleware(['domain_db', 'contact_account','locale']);
Route::get('client/password/reset', [ContactForgotPasswordController::class, 'showLinkRequestForm'])->name('client.password.request')->middleware(['domain_db', 'contact_account','locale', 'throttle:portal']);
Route::post('client/password/email', [ContactForgotPasswordController::class, 'sendResetLinkEmail'])->name('client.password.email')->middleware(['locale', 'throttle:portal']);
Route::get('client/password/reset/{token}', [ContactResetPasswordController::class, 'showResetForm'])->name('client.password.reset')->middleware(['domain_db', 'contact_account','locale', 'throttle:portal']);
Route::post('client/password/reset', [ContactResetPasswordController::class, 'reset'])->name('client.password.update')->middleware(['domain_db', 'contact_account','locale', 'throttle:portal']);
Route::get('view/{entity_type}/{invitation_key}', [App\Http\Controllers\ClientPortal\EntityViewController::class, 'index'])->name('client.entity_view');
Route::get('view/{entity_type}/{invitation_key}/password', [App\Http\Controllers\ClientPortal\EntityViewController::class ,'password'])->name('client.entity_view.password');
@ -50,7 +50,8 @@ Route::group(['middleware' => ['auth:contact', 'locale', 'domain_db','check_clie
Route::get('dashboard', [App\Http\Controllers\ClientPortal\DashboardController::class, 'index'])->name('dashboard'); // name = (dashboard. index / create / show / update / destroy / edit
Route::get('plan', [App\Http\Controllers\ClientPortal\NinjaPlanController::class, 'plan'])->name('plan'); // name = (dashboard. index / create / show / update / destroy / edit
Route::get('showBlob/{hash}', [App\Http\Controllers\ClientPortal\InvoiceController::class, 'showBlob'])->name('invoices.showBlob');
Route::get('invoices', [App\Http\Controllers\ClientPortal\InvoiceController::class, 'index'])->name('invoices.index')->middleware('portal_enabled');
Route::post('invoices/payment', [App\Http\Controllers\ClientPortal\InvoiceController::class, 'bulk'])->name('invoices.bulk');
Route::get('invoices/payment', [App\Http\Controllers\ClientPortal\InvoiceController::class, 'catch_bulk'])->name('invoices.catch_bulk');
@ -78,7 +79,7 @@ Route::group(['middleware' => ['auth:contact', 'locale', 'domain_db','check_clie
Route::put('profile/{client_contact}/localization', [App\Http\Controllers\ClientPortal\ProfileController::class, 'updateClientLocalization'])->name('profile.edit_localization');
Route::get('payment_methods/{payment_method}/verification', [App\Http\Controllers\ClientPortal\PaymentMethodController::class, 'verify'])->name('payment_methods.verification');
Route::post('payment_methods/{payment_method}/verification', [App\Http\Controllers\ClientPortal\PaymentMethodController::class, 'processVerification'])->middleware(['throttle:10,1']);
Route::post('payment_methods/{payment_method}/verification', [App\Http\Controllers\ClientPortal\PaymentMethodController::class, 'processVerification'])->middleware(['throttle:portal']);
Route::get('payment_methods/confirm', [App\Http\Controllers\ClientPortal\PaymentMethodController::class, 'store'])->name('payment_methods.confirm');

View File

@ -25,10 +25,6 @@ Route::group(['middleware' => ['invite_db'], 'prefix' => 'vendor', 'as' => 'vend
/*Invitation catches*/
Route::get('purchase_order/{invitation_key}', [InvitationController::class, 'purchaseOrder']);
Route::get('purchase_order/{invitation_key}/download', [InvitationController::class, 'download']);
// Route::get('purchase_order/{invitation_key}/download_pdf', 'PurchaseOrderController@downloadPdf')->name('recurring_invoice.download_invitation_key');
// Route::get('purchase_order/{invitation_key}/download', 'ClientPortal\InvitationController@routerForDownload');
});
Route::group(['middleware' => ['auth:vendor', 'vendor_locale', 'domain_db'], 'prefix' => 'vendor', 'as' => 'vendor.'], function () {
@ -37,6 +33,8 @@ Route::group(['middleware' => ['auth:vendor', 'vendor_locale', 'domain_db'], 'pr
Route::get('purchase_orders', [PurchaseOrderController::class, 'index'])->name('purchase_orders.index');
Route::get('purchase_orders/{purchase_order}', [PurchaseOrderController::class, 'show'])->name('purchase_order.show');
Route::get('showBlob/{hash}', [PurchaseOrderController::class, 'showBlob'])->name('purchase_order.showBlob');
Route::get('profile/{vendor_contact}/edit', [VendorContactController::class, 'edit'])->name('profile.edit');
Route::put('profile/{vendor_contact}/edit', [VendorContactController::class, 'update'])->name('profile.update');

View File

@ -37,6 +37,54 @@ class ClientCsvTest extends TestCase
$this->withoutExceptionHandling();
}
public function testRecurringInvoiceExportCsv()
{
$data = [
'date_range' => 'this_year',
'report_keys' => [],
'send_email' => false,
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/reports/recurring_invoices', $data);
$response->assertStatus(200);
}
public function testVendorExportCsv()
{
$data = [
'date_range' => 'this_year',
'report_keys' => [],
'send_email' => false,
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/reports/vendors', $data);
$response->assertStatus(200);
}
public function testPurchaseOrderExportCsv()
{
$data = [
'date_range' => 'this_year',
'report_keys' => [],
'send_email' => false,
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/reports/purchase_orders', $data);
$response->assertStatus(200);
}
public function testClientExportCsv()
{
$data = [

View File

@ -46,7 +46,7 @@ class ExportCompanyTest extends TestCase
public function testCompanyExport()
{
$res = (new CompanyExport($this->company, $this->company->users->first()))->handle();
$res = (new CompanyExport($this->company, $this->company->users->first(), '123'))->handle();
$this->assertTrue($res);
}

View File

@ -0,0 +1,259 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace Tests\Feature\Export;
use Tests\TestCase;
use App\Models\User;
use App\Models\Client;
use League\Csv\Reader;
use App\Models\Account;
use App\Models\Company;
use App\Models\Invoice;
use Tests\MockAccountData;
use App\Models\CompanyToken;
use App\Models\ClientContact;
use App\Utils\Traits\MakesHash;
use App\DataMapper\CompanySettings;
use App\Factory\CompanyUserFactory;
use App\Factory\InvoiceItemFactory;
use App\Services\Report\ARDetailReport;
use Illuminate\Routing\Middleware\ThrottleRequests;
/**
* @test
*/
class ReportCsvGenerationTest extends TestCase
{
use MakesHash;
public $faker;
protected function setUp() :void
{
parent::setUp();
$this->faker = \Faker\Factory::create();
$this->withoutMiddleware(
ThrottleRequests::class
);
$this->withoutExceptionHandling();
$this->buildData();
}
public $company;
public $user;
public $payload;
public $account;
public $client;
public $token;
public $cu;
/**
* start_date - Y-m-d
end_date - Y-m-d
date_range -
all
last7
last30
this_month
last_month
this_quarter
last_quarter
this_year
custom
is_income_billed - true = Invoiced || false = Payments
expense_billed - true = Expensed || false = Expenses marked as paid
include_tax - true tax_included || false - tax_excluded
*/
private function buildData()
{
$this->account = Account::factory()->create([
'hosted_client_count' => 1000,
'hosted_company_count' => 1000,
]);
$this->account->num_users = 3;
$this->account->save();
$this->user = User::factory()->create([
'account_id' => $this->account->id,
'confirmation_code' => 'xyz123',
'email' => $this->faker->unique()->safeEmail(),
]);
$settings = CompanySettings::defaults();
$settings->client_online_payment_notification = false;
$settings->client_manual_payment_notification = false;
$this->company = Company::factory()->create([
'account_id' => $this->account->id,
'settings' => $settings,
]);
$this->company->settings = $settings;
$this->company->save();
$this->cu = CompanyUserFactory::create($this->user->id, $this->company->id, $this->account->id);
$this->cu->is_owner = true;
$this->cu->is_admin = true;
$this->cu->is_locked = false;
$this->cu->save();
$this->token = \Illuminate\Support\Str::random(64);
$company_token = new CompanyToken;
$company_token->user_id = $this->user->id;
$company_token->company_id = $this->company->id;
$company_token->account_id = $this->account->id;
$company_token->name = 'test token';
$company_token->token = $this->token;
$company_token->is_system = true;
$company_token->save();
$this->payload = [
'start_date' => '2000-01-01',
'end_date' => '2030-01-11',
'date_range' => 'custom',
'is_income_billed' => true,
'include_tax' => false,
];
$this->client = Client::factory()->create([
'user_id' => $this->user->id,
'company_id' => $this->company->id,
'is_deleted' => 0,
'name' => 'bob',
'address1' => '1234'
]);
ClientContact::factory()->create([
'user_id' => $this->user->id,
'client_id' => $this->client->id,
'company_id' => $this->company->id,
'is_primary' => 1,
'first_name' => 'john',
'last_name' => 'doe',
'email' => 'john@doe.com'
]);
}
public function testClientCsvGeneration()
{
$data = [
'date_range' => 'all',
'report_keys' => [],
'send_email' => false,
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/reports/clients', $data);
$csv = $response->streamedContent();
$reader = Reader::createFromString($csv);
$reader->setHeaderOffset(0);
$res = $reader->fetchColumnByName('Street');
$res = iterator_to_array($res, true);
$this->assertEquals('1234', $res[1]);
$res = $reader->fetchColumnByName('Name');
$res = iterator_to_array($res, true);
$this->assertEquals('bob', $res[1]);
}
public function testClientContactCsvGeneration()
{
$data = [
'date_range' => 'all',
'report_keys' => [],
'send_email' => false,
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/reports/contacts', $data);
$csv = $response->streamedContent();
$reader = Reader::createFromString($csv);
$reader->setHeaderOffset(0);
$res = $reader->fetchColumnByName('First Name');
$res = iterator_to_array($res, true);
$this->assertEquals('john', $res[1]);
$res = $reader->fetchColumnByName('Last Name');
$res = iterator_to_array($res, true);
$this->assertEquals('doe', $res[1]);
$res = $reader->fetchColumnByName('Email');
$res = iterator_to_array($res, true);
$this->assertEquals('john@doe.com', $res[1]);
}
public function testCreditCsvGeneration()
{
Credit::factory()->create([
'user_id' => $this->user->id,
'company_id' => $this->company->id,
'client_id' => $this->client->id,
'amount' => 100,
'balance' => 50,
'status_id' => 2,
'discount' => 10,
'po_number' => '1234',
'public_notes' => 'Public',
'private_notes' => 'Private',
'terms' => 'Terms',
]);
$data = [
'date_range' => 'all',
'report_keys' => [],
'send_email' => false,
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/reports/credits', $data);
}

View File

@ -0,0 +1,192 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace Tests\Feature;
use App\Models\Client;
use App\Models\ClientContact;
use App\Models\Credit;
use App\Models\Invoice;
use App\Models\Payment;
use App\Utils\Traits\MakesHash;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Foundation\Testing\WithoutEvents;
use Illuminate\Routing\Middleware\ThrottleRequests;
use Illuminate\Support\Facades\Session;
use Illuminate\Validation\ValidationException;
use Tests\MockAccountData;
use Tests\TestCase;
/**
* @test
* @covers App\Http\Controllers\PaymentController
*/
class PaymentV2Test extends TestCase
{
use MakesHash;
use DatabaseTransactions;
use MockAccountData;
use WithoutEvents;
public $faker;
protected function setUp() :void
{
parent::setUp();
Session::start();
$this->faker = \Faker\Factory::create();
Model::reguard();
$this->makeTestData();
$this->withoutExceptionHandling();
$this->withoutMiddleware(
ThrottleRequests::class
);
}
public function testStorePaymentWithCreditsThenDeletingInvoices()
{
$client = Client::factory()->create(['company_id' =>$this->company->id, 'user_id' => $this->user->id, 'balance' => 20, 'paid_to_date' => 0]);
ClientContact::factory()->create([
'user_id' => $this->user->id,
'client_id' => $client->id,
'company_id' => $this->company->id,
'is_primary' => 1,
]);
$invoice = Invoice::factory()->create([
'company_id' => $this->company->id,
'user_id' => $this->user->id,
'client_id' => $client->id,
'status_id' => Invoice::STATUS_SENT,
'uses_inclusive_taxes' => false,
'amount' => 20,
'balance' => 20,
'discount' => 0,
'number' => uniqid("st", true),
'line_items' => []
]);
$this->assertEquals(20, $client->balance);
$this->assertEquals(0, $client->paid_to_date);
$this->assertEquals(20, $invoice->amount);
$this->assertEquals(20, $invoice->balance);
$credit = Credit::factory()->create([
'company_id' => $this->company->id,
'user_id' => $this->user->id,
'client_id' => $client->id,
'status_id' => Invoice::STATUS_SENT,
'uses_inclusive_taxes' => false,
'amount' => 20,
'balance' => 20,
'discount' => 0,
'number' => uniqid("st", true),
'line_items' => []
]);
$this->assertEquals(20, $credit->amount);
$this->assertEquals(20, $credit->balance);
$data = [
'client_id' => $client->hashed_id,
'invoices' => [
[
'invoice_id' => $invoice->hashed_id,
'amount' => 20,
],
],
'credits' => [
[
'credit_id' => $credit->hashed_id,
'amount' => 20,
],
],
'date' => '2020/12/12',
];
$response = null;
try {
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/payments?include=invoices', $data);
} catch (ValidationException $e) {
$message = json_decode($e->validator->getMessageBag(), 1);
nlog($message);
$this->assertNotNull($message);
}
$arr = $response->json();
$response->assertStatus(200);
$payment_id = $arr['data']['id'];
$payment = Payment::find($this->decodePrimaryKey($payment_id));
$this->assertNotNull($payment);
$this->assertNotNull($payment->invoices());
$this->assertEquals(1, $payment->invoices()->count());
$this->assertEquals(0, $payment->amount);
$this->assertEquals(0, $client->fresh()->balance);
$this->assertEquals(20, $client->fresh()->paid_to_date);
$data = [
'action' => 'delete',
'ids' => [
$invoice->hashed_id,
],
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/invoices/bulk', $data);
$response->assertStatus(200);
$invoice = $invoice->fresh();
$payment = $payment->fresh();
$this->assertEquals(true, $invoice->is_deleted);
$this->assertEquals(0, $payment->amount);
$this->assertEquals(0, $client->fresh()->balance);
$this->assertEquals(0, $client->fresh()->paid_to_date);
$data = [
'action' => 'restore',
'ids' => [
$invoice->hashed_id,
],
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/invoices/bulk', $data);
$invoice = $invoice->fresh();
$this->assertEquals(false, $invoice->is_deleted);
$payment = $payment->fresh();
$this->assertEquals(0, $payment->amount);
$this->assertEquals(20, $client->fresh()->paid_to_date);
}
}

View File

@ -33,7 +33,7 @@ class PaymentTypeTest extends TestCase
$payment_type_class = new PaymentType;
foreach($payment_type_class->type_names as $type)
{nlog($type);
{
$this->assertTrue(Lang::has("texts.{$type}"));
}
}