Merge pull request #8649 from turbo124/v5-develop

v5.6.19
This commit is contained in:
David Bomba 2023-07-16 12:45:00 +10:00 committed by GitHub
commit a322dc2511
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
72 changed files with 2136 additions and 593 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

@ -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 () {

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}"));
}
}