Merge pull request #8659 from turbo124/v5-stable

v5.6.20
This commit is contained in:
David Bomba 2023-07-22 11:42:18 +10:00 committed by GitHub
commit 45e9c697f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
102 changed files with 365679 additions and 365037 deletions

View File

@ -1 +1 @@
5.6.19
5.6.20

View File

@ -24,7 +24,6 @@ use App\Transformers\ActivityTransformer;
class ActivityExport extends BaseExport
{
private Company $company;
private $entity_transformer;

View File

@ -13,14 +13,14 @@ namespace App\Export\CSV;
use App\Utils\Number;
use App\Models\Client;
use App\Models\Company;
use App\Models\Expense;
use App\Models\Invoice;
use App\Models\GatewayType;
use App\Models\Payment;
use League\Fractal\Manager;
use Illuminate\Support\Carbon;
use App\Utils\Traits\MakesHash;
use App\Transformers\ClientTransformer;
use App\Transformers\TaskTransformer;
use App\Transformers\PaymentTransformer;
use Illuminate\Database\Eloquent\Builder;
use League\Fractal\Serializer\ArraySerializer;
@ -29,6 +29,8 @@ class BaseExport
{
use MakesHash;
public Company $company;
public array $input;
public string $date_key = '';
@ -136,6 +138,35 @@ class BaseExport
"user" => "invoice.user_id",
];
protected array $recurring_invoice_report_keys = [
"invoice_number" => "recurring_invoice.number",
"amount" => "recurring_invoice.amount",
"balance" => "recurring_invoice.balance",
"paid_to_date" => "recurring_invoice.paid_to_date",
"po_number" => "recurring_invoice.po_number",
"date" => "recurring_invoice.date",
"due_date" => "recurring_invoice.due_date",
"terms" => "recurring_invoice.terms",
"footer" => "recurring_invoice.footer",
"status" => "recurring_invoice.status",
"public_notes" => "recurring_invoice.public_notes",
"private_notes" => "recurring_invoice.private_notes",
"uses_inclusive_taxes" => "recurring_invoice.uses_inclusive_taxes",
"is_amount_discount" => "recurring_invoice.is_amount_discount",
"partial" => "recurring_invoice.partial",
"partial_due_date" => "recurring_invoice.partial_due_date",
"surcharge1" => "recurring_invoice.custom_surcharge1",
"surcharge2" => "recurring_invoice.custom_surcharge2",
"surcharge3" => "recurring_invoice.custom_surcharge3",
"surcharge4" => "recurring_invoice.custom_surcharge4",
"exchange_rate" => "recurring_invoice.exchange_rate",
"tax_amount" => "recurring_invoice.total_taxes",
"assigned_user" => "recurring_invoice.assigned_user_id",
"user" => "recurring_invoice.user_id",
"frequency_id" => "recurring_invoice.frequency_id",
"next_send_date" => "recurring_invoice.next_send_date"
];
protected array $purchase_order_report_keys = [
'amount' => 'purchase_order.amount',
'balance' => 'purchase_order.balance',
@ -193,13 +224,17 @@ class BaseExport
];
protected array $quote_report_keys = [
"quote_number" => "quote.number",
'custom_value1' => 'quote.custom_value1',
'custom_value2' => 'quote.custom_value2',
'custom_value3' => 'quote.custom_value3',
'custom_value4' => 'quote.custom_value4',
"number" => "quote.number",
"amount" => "quote.amount",
"balance" => "quote.balance",
"paid_to_date" => "quote.paid_to_date",
"po_number" => "quote.po_number",
"date" => "quote.date",
"due_date" => "quote.due_date",
"valid_until" => "quote.due_date",
"terms" => "quote.terms",
"footer" => "quote.footer",
"status" => "quote.status",
@ -314,8 +349,6 @@ class BaseExport
'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)
@ -347,8 +380,11 @@ class BaseExport
'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),
'recurring_invoice' => $value = $this->resolveInvoiceKey($parts[1], $entity, $transformer),
'quote' => $value = $this->resolveQuoteKey($parts[1], $entity, $transformer),
'purchase_order' => $value = $this->resolvePurchaseOrderKey($parts[1], $entity, $transformer),
'payment' => $value = $this->resolvePaymentKey($parts[1], $entity, $transformer),
'task' => $value = $this->resolveTaskKey($parts[1], $entity, $transformer),
default => $value = ''
};
@ -414,6 +450,22 @@ class BaseExport
}
private function resolveTaskKey($column, $entity, $transformer)
{
nlog("searching for {$column}");
$transformed_entity = $transformer->transform($entity);
if(array_key_exists($column, $transformed_entity)) {
return $transformed_entity[$column];
}
return '';
}
private function resolveVendorKey($column, $entity, $transformer)
{
@ -511,6 +563,20 @@ class BaseExport
return '';
}
private function resolveQuoteKey($column, $entity, $transformer)
{
nlog("searching for {$column}");
$transformed_entity = $transformer->transform($entity);
if(array_key_exists($column, $transformed_entity)) {
return $transformed_entity[$column];
}
return '';
}
private function resolveInvoiceKey($column, $entity, $transformer)
{
nlog("searching for {$column}");
@ -537,7 +603,23 @@ class BaseExport
}
$transformed_invoice = $transformer->transform($entity);
if($transformer instanceof TaskTransformer) {
$transformed_invoice = $transformer->includeInvoice($entity);
if(!$transformed_invoice)
return '';
$manager = new Manager();
$manager->setSerializer(new ArraySerializer());
$transformed_invoice = $manager->createData($transformed_invoice)->toArray();
}
if(array_key_exists($column, $transformed_invoice)) {
return $transformed_invoice[$column];
} elseif (array_key_exists(str_replace("invoice.", "", $column), $transformed_invoice)) {
return $transformed_invoice[$column];
}
if($column == 'status')
return $entity->stringStatus($entity->status_id);
@ -707,8 +789,27 @@ class BaseExport
$this->end_date = now()->startOfDay()->format('Y-m-d');
return $query->whereBetween($this->date_key, [now()->subDays(365), now()])->orderBy($this->date_key, 'ASC');
case 'this_year':
$this->start_date = now()->startOfYear()->format('Y-m-d');
$this->end_date = now()->format('Y-m-d');
$first_month_of_year = $this->company->getSetting('first_month_of_year') ?? 1;
$fin_year_start = now()->createFromDate(now()->year, $first_month_of_year, 1);
if(now()->lt($fin_year_start))
$fin_year_start->subYearNoOverflow();
$this->start_date = $fin_year_start->format('Y-m-d');
$this->end_date = $fin_year_start->copy()->addYear()->subDay()->format('Y-m-d');
return $query->whereBetween($this->date_key, [now()->startOfYear(), now()])->orderBy($this->date_key, 'ASC');
case 'last_year':
$first_month_of_year = $this->company->getSetting('first_month_of_year') ?? 1;
$fin_year_start = now()->createFromDate(now()->year, $first_month_of_year, 1);
$fin_year_start->subYearNoOverflow();
if(now()->subYear()->lt($fin_year_start))
$fin_year_start->subYearNoOverflow();
$this->start_date = $fin_year_start->format('Y-m-d');
$this->end_date = $fin_year_start->copy()->addYear()->subDay()->format('Y-m-d');
return $query->whereBetween($this->date_key, [now()->startOfYear(), now()])->orderBy($this->date_key, 'ASC');
case 'custom':
$this->start_date = $custom_start_date->format('Y-m-d');
@ -743,6 +844,11 @@ class BaseExport
$key = array_search($value, $this->invoice_report_keys);
}
if(!$key) {
$prefix = ctrans('texts.recurring_invoice')." ";
$key = array_search($value, $this->recurring_invoice_report_keys);
}
if(!$key) {
$prefix = ctrans('texts.payment')." ";
$key = array_search($value, $this->payment_report_keys);
@ -790,6 +896,7 @@ class BaseExport
$key = str_replace('item.', '', $key);
$key = str_replace('recurring_invoice.', '', $key);
$key = str_replace('purchase_order.', '', $key);
$key = str_replace('invoice.', '', $key);
$key = str_replace('quote.', '', $key);
$key = str_replace('credit.', '', $key);
@ -800,9 +907,18 @@ class BaseExport
$key = str_replace('payment.', '', $key);
$key = str_replace('expense.', '', $key);
if(in_array($key, ['quote1','quote2','quote3','quote4','credit1','credit2','credit3','credit4','purchase_order1','purchase_order2','purchase_order3','purchase_order4']))
{
$number = substr($key, -1);
$header[] = ctrans('texts.item') . " ". ctrans("texts.custom_value{$number}");
}
else
{
$header[] = "{$prefix}" . ctrans("texts.{$key}");
}
// nlog($header);
}
// nlog($header);
return $header;
}

View File

@ -22,8 +22,6 @@ use League\Csv\Writer;
class ClientExport extends BaseExport
{
private $company;
private $client_transformer;
private $contact_transformer;
@ -172,8 +170,8 @@ class ClientExport extends BaseExport
$entity['shipping_country'] = $client->shipping_country ? ctrans("texts.country_{$client->shipping_country->name}") : '';
}
if (in_array('client.currency', $this->input['report_keys'])) {
$entity['currency'] = $client->currency() ? $client->currency()->code : $client->company->currency()->code;
if (in_array('client.currency_id', $this->input['report_keys'])) {
$entity['client.currency_id'] = $client->currency() ? $client->currency()->code : $client->company->currency()->code;
}
if (in_array('client.industry_id', $this->input['report_keys'])) {

View File

@ -23,7 +23,6 @@ use League\Csv\Writer;
class ContactExport extends BaseExport
{
private Company $company;
private ClientTransformer $client_transformer;

View File

@ -21,7 +21,6 @@ use League\Csv\Writer;
class CreditExport extends BaseExport
{
private Company $company;
private CreditTransformer $credit_transformer;

View File

@ -21,7 +21,6 @@ use League\Csv\Writer;
class DocumentExport extends BaseExport
{
private Company $company;
private $entity_transformer;

View File

@ -21,7 +21,6 @@ use League\Csv\Writer;
class ExpenseExport extends BaseExport
{
private Company $company;
private $expense_transformer;

View File

@ -23,8 +23,6 @@ use App\Transformers\InvoiceTransformer;
class InvoiceExport extends BaseExport
{
private Company $company;
private $invoice_transformer;
public string $date_key = 'date';

View File

@ -22,7 +22,6 @@ use League\Csv\Writer;
class InvoiceItemExport extends BaseExport
{
private Company $company;
private $invoice_transformer;

View File

@ -21,8 +21,6 @@ use League\Csv\Writer;
class PaymentExport extends BaseExport
{
private Company $company;
private $entity_transformer;
public string $date_key = 'date';

View File

@ -22,8 +22,6 @@ use League\Csv\Writer;
class ProductExport extends BaseExport
{
private Company $company;
private $entity_transformer;
public string $date_key = 'created_at';

View File

@ -23,8 +23,6 @@ use League\Csv\Writer;
class ProductSalesExport extends BaseExport
{
private Company $company;
public string $date_key = 'created_at';
protected Collection $products;

View File

@ -22,7 +22,6 @@ use League\Csv\Writer;
class PurchaseOrderExport extends BaseExport
{
private Company $company;
private $purchase_order_transformer;
@ -31,39 +30,39 @@ class PurchaseOrderExport extends BaseExport
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',
'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',
];
private array $decorate_keys = [
@ -130,7 +129,7 @@ class PurchaseOrderExport extends BaseExport
$keyval = array_search($key, $this->entity_keys);
if(!$keyval) {
$keyval = array_search(str_replace("invoice.", "", $key), $this->entity_keys) ?? $key;
$keyval = array_search(str_replace("purchase_order.", "", $key), $this->entity_keys) ?? $key;
}
if(!$keyval) {

View File

@ -21,7 +21,6 @@ use League\Csv\Writer;
class PurchaseOrderItemExport extends BaseExport
{
private Company $company;
private $purchase_order_transformer;
@ -37,10 +36,10 @@ class PurchaseOrderItemExport extends BaseExport
'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_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',
@ -82,10 +81,10 @@ class PurchaseOrderItemExport extends BaseExport
'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',
'purchase_order1' => 'item.custom_value1',
'purchase_order2' => 'item.custom_value2',
'purchase_order3' => 'item.custom_value3',
'purchase_order4' => 'item.custom_value4',
'tax_category' => 'item.tax_id',
'type' => 'item.type_id',
];
@ -139,7 +138,7 @@ class PurchaseOrderItemExport extends BaseExport
private function iterateItems(PurchaseOrder $purchase_order)
{
$transformed_invoice = $this->buildRow($purchase_order);
$transformed_purchase_order = $this->buildRow($purchase_order);
$transformed_items = [];
@ -154,7 +153,7 @@ class PurchaseOrderItemExport extends BaseExport
$keyval = $key;
$keyval = str_replace("custom_value", "invoice", $key);
$keyval = str_replace("custom_value", "purchase_order", $key);
if($key == 'type_id') {
$keyval = 'type';
@ -184,7 +183,7 @@ class PurchaseOrderItemExport extends BaseExport
}
}
$transformed_items = array_merge($transformed_invoice, $item_array);
$transformed_items = array_merge($transformed_purchase_order, $item_array);
$entity = $this->decorateAdvancedFields($purchase_order, $transformed_items);
$this->csv->insertOne($entity);
@ -193,7 +192,7 @@ class PurchaseOrderItemExport extends BaseExport
private function buildRow(PurchaseOrder $purchase_order) :array
{
$transformed_invoice = $this->purchase_order_transformer->transform($purchase_order);
$transformed_purchase_order = $this->purchase_order_transformer->transform($purchase_order);
$entity = [];
@ -201,17 +200,17 @@ class PurchaseOrderItemExport extends BaseExport
$keyval = array_search($key, $this->entity_keys);
if(!$keyval) {
$keyval = array_search(str_replace("invoice.", "", $key), $this->entity_keys) ?? $key;
$keyval = array_search(str_replace("purchase_order.", "", $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];
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);
}

View File

@ -21,7 +21,6 @@ use League\Csv\Writer;
class QuoteExport extends BaseExport
{
private Company $company;
private $quote_transformer;
@ -43,7 +42,7 @@ class QuoteExport extends BaseExport
'custom_value4' => 'custom_value4',
'date' => 'date',
'discount' => 'discount',
'due_date' => 'due_date',
'valid_until' => 'due_date',
'exchange_rate' => 'exchange_rate',
'footer' => 'footer',
'number' => 'number',
@ -115,17 +114,28 @@ class QuoteExport extends BaseExport
private function buildRow(Quote $quote) :array
{
$transformed_quote = $this->quote_transformer->transform($quote);
$transformed_entity = $this->quote_transformer->transform($quote);
$entity = [];
foreach (array_values($this->input['report_keys']) as $key) {
$keyval = array_search($key, $this->entity_keys);
if (array_key_exists($key, $transformed_quote)) {
$entity[$keyval] = $transformed_quote[$key];
} else {
$entity[$keyval] = '';
if(!$keyval) {
$keyval = array_search(str_replace("invoice.", "", $key), $this->entity_keys) ?? $key;
}
if(!$keyval) {
$keyval = $key;
}
if (array_key_exists($key, $transformed_entity)) {
$entity[$keyval] = $transformed_entity[$key];
} elseif (array_key_exists($keyval, $transformed_entity)) {
$entity[$keyval] = $transformed_entity[$keyval];
}
else {
$entity[$keyval] = $this->resolveKey($keyval, $quote, $this->quote_transformer);
}
}

View File

@ -21,7 +21,6 @@ use League\Csv\Writer;
class QuoteItemExport extends BaseExport
{
private Company $company;
private $quote_transformer;
@ -63,10 +62,11 @@ class QuoteItemExport extends BaseExport
'terms' => 'terms',
'total_taxes' => 'total_taxes',
'currency' => 'currency_id',
'qty' => 'item.quantity',
'unit_cost' => 'item.cost',
'quantity' => 'item.quantity',
'cost' => 'item.cost',
'product_key' => 'item.product_key',
'cost' => 'item.product_cost',
'buy_price' => 'item.product_cost',
'cost' => 'item.cost',
'notes' => 'item.notes',
'discount' => 'item.discount',
'is_amount_discount' => 'item.is_amount_discount',
@ -77,11 +77,13 @@ class QuoteItemExport extends BaseExport
'tax_name2' => 'item.tax_name2',
'tax_name3' => 'item.tax_name3',
'line_total' => 'item.line_total',
// 'gross_line_total' => 'item.gross_line_total',
'custom_value1' => 'item.custom_value1',
'custom_value2' => 'item.custom_value2',
'custom_value3' => 'item.custom_value3',
'custom_value4' => 'item.custom_value4',
'gross_line_total' => 'item.gross_line_total',
'quote1' => 'item.custom_value1',
'quote2' => 'item.custom_value2',
'quote3' => 'item.custom_value3',
'quote4' => 'item.custom_value4',
'tax_category' => 'item.tax_id',
'type' => 'item.type_id',
];
private array $decorate_keys = [
@ -135,25 +137,44 @@ class QuoteItemExport extends BaseExport
$transformed_items = [];
$transformed_items = [];
foreach ($quote->line_items as $item) {
$item_array = [];
foreach (array_values($this->input['report_keys']) as $key) {
if (str_contains($key, 'item.')) {
$key = str_replace('item.', '', $key);
$item_array[$key] = $item->{$key};
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", "quote", $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) {
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] = '';
$entity[$keyval] = "";
}
}
@ -173,16 +194,26 @@ class QuoteItemExport extends BaseExport
foreach (array_values($this->input['report_keys']) as $key) {
$keyval = array_search($key, $this->entity_keys);
if(!$keyval) {
$keyval = array_search(str_replace("quote.", "", $key), $this->entity_keys) ?? $key;
}
if(!$keyval) {
$keyval = $key;
}
if (array_key_exists($key, $transformed_quote)) {
$entity[$keyval] = $transformed_quote[$key];
} else {
$entity[$keyval] = '';
} elseif (array_key_exists($keyval, $transformed_quote)) {
$entity[$keyval] = $transformed_quote[$keyval];
}
else {
$entity[$keyval] = $this->resolveKey($keyval, $quote, $this->quote_transformer);
}
}
return $this->decorateAdvancedFields($quote, $entity);
}
private function decorateAdvancedFields(Quote $quote, array $entity) :array
{
if (in_array('currency_id', $this->input['report_keys'])) {

View File

@ -21,7 +21,6 @@ use League\Csv\Writer;
class RecurringInvoiceExport extends BaseExport
{
private Company $company;
private $invoice_transformer;
@ -33,10 +32,10 @@ class RecurringInvoiceExport extends BaseExport
'amount' => 'amount',
'balance' => 'balance',
'client' => 'client_id',
'custom_surcharge1' => 'custom_surcharge1',
'custom_surcharge2' => 'custom_surcharge2',
'custom_surcharge3' => 'custom_surcharge3',
'custom_surcharge4' => 'custom_surcharge4',
// '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',
@ -66,7 +65,8 @@ class RecurringInvoiceExport extends BaseExport
'currency' => 'currency_id',
'vendor' => 'vendor_id',
'project' => 'project_id',
'frequency' => 'frequency_id'
'frequency_id' => 'frequency_id',
'next_send_date' => 'next_send_date'
];
private array $decorate_keys = [
@ -127,11 +127,22 @@ class RecurringInvoiceExport extends BaseExport
foreach (array_values($this->input['report_keys']) as $key) {
$keyval = array_search($key, $this->entity_keys);
if(!$keyval) {
$keyval = array_search(str_replace("recurring_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] = '';
$entity[$keyval] = $this->resolveKey($keyval, $invoice, $this->invoice_transformer);
}
}
return $this->decorateAdvancedFields($invoice, $entity);
@ -163,7 +174,9 @@ class RecurringInvoiceExport extends BaseExport
$entity['vendor'] = $invoice->vendor ? $invoice->vendor->name : '';
}
$entity['frequency'] = $invoice->frequencyForKey($invoice->frequency_id);
if (in_array('recurring_invoice.frequency_id', $this->input['report_keys']) || in_array('frequency_id', $this->input['report_keys'])) {
$entity['frequency_id'] = $invoice->frequencyForKey($invoice->frequency_id);
}
return $entity;
}

View File

@ -24,7 +24,6 @@ use League\Csv\Writer;
class TaskExport extends BaseExport
{
private Company $company;
private $entity_transformer;
@ -47,8 +46,6 @@ class TaskExport extends BaseExport
'custom_value4' => 'custom_value4',
'status' => 'status_id',
'project' => 'project_id',
'invoice' => 'invoice_id',
'client' => 'client_id',
];
private array $decorate_keys = [
@ -110,14 +107,24 @@ class TaskExport extends BaseExport
$entity = [];
$transformed_entity = $this->entity_transformer->transform($task);
if (is_null($task->time_log) || (is_array(json_decode($task->time_log, 1)) && count(json_decode($task->time_log, 1)) == 0)) {
foreach (array_values($this->input['report_keys']) as $key) {
$keyval = array_search($key, $this->entity_keys);
if(!$keyval) {
$keyval = array_search(str_replace("task.", "", $key), $this->entity_keys) ?? $key;
}
if(!$keyval) {
$keyval = $key;
}
if (array_key_exists($key, $transformed_entity)) {
$entity[$keyval] = $transformed_entity[$key];
} else {
$entity[$keyval] = '';
} elseif (array_key_exists($keyval, $transformed_entity)) {
$entity[$keyval] = $transformed_entity[$keyval];
}
else {
$entity[$keyval] = $this->resolveKey($keyval, $task, $this->entity_transformer);
}
}
@ -125,23 +132,14 @@ class TaskExport extends BaseExport
$entity['end_date'] = '';
$entity['duration'] = '';
$entity = $this->decorateAdvancedFields($task, $entity);
ksort($entity);
if (is_null($task->time_log) || (is_array(json_decode($task->time_log, 1)) && count(json_decode($task->time_log, 1)) == 0)) {
$this->csv->insertOne($entity);
} elseif (is_array(json_decode($task->time_log, 1)) && count(json_decode($task->time_log, 1)) > 0) {
foreach (array_values($this->input['report_keys']) as $key) {
$keyval = array_search($key, $this->entity_keys);
if (array_key_exists($key, $transformed_entity)) {
$entity[$keyval] = $transformed_entity[$key];
} else {
$entity[$keyval] = '';
}
}
$this->iterateLogs($task, $entity);
}
}
private function iterateLogs(Task $task, array $entity)
@ -164,37 +162,24 @@ class TaskExport extends BaseExport
}
foreach ($logs as $key => $item) {
if (in_array('start_date', $this->input['report_keys'])) {
if (in_array('task.start_date', $this->input['report_keys']) || in_array('start_date', $this->input['report_keys'])) {
$entity['start_date'] = Carbon::createFromTimeStamp($item[0])->setTimezone($timezone_name)->format($date_format_default);
}
if (in_array('end_date', $this->input['report_keys']) && $item[1] > 0) {
if ((in_array('task.end_date', $this->input['report_keys']) || in_array('end_date', $this->input['report_keys'])) && $item[1] > 0) {
$entity['end_date'] = Carbon::createFromTimeStamp($item[1])->setTimezone($timezone_name)->format($date_format_default);
}
if (in_array('end_date', $this->input['report_keys']) && $item[1] == 0) {
if ((in_array('task.end_date', $this->input['report_keys']) || in_array('end_date', $this->input['report_keys'])) && $item[1] == 0) {
$entity['end_date'] = ctrans('texts.is_running');
}
if (in_array('duration', $this->input['report_keys'])) {
if (in_array('task.duration', $this->input['report_keys']) || in_array('duration', $this->input['report_keys'])) {
$entity['duration'] = $task->calcDuration();
}
if (! array_key_exists('duration', $entity)) {
$entity['duration'] = '';
}
if (! array_key_exists('start_date', $entity)) {
$entity['start_date'] = '';
}
if (! array_key_exists('end_date', $entity)) {
$entity['end_date'] = '';
}
$entity = $this->decorateAdvancedFields($task, $entity);
ksort($entity);
$this->csv->insertOne($entity);
unset($entity['start_date']);
@ -213,14 +198,6 @@ class TaskExport extends BaseExport
$entity['project'] = $task->project()->exists() ? $task->project->name : '';
}
if (in_array('client_id', $this->input['report_keys'])) {
$entity['client'] = $task->client ? $task->client->present()->name() : '';
}
if (in_array('invoice_id', $this->input['report_keys'])) {
$entity['invoice'] = $task->invoice ? $task->invoice->number : '';
}
return $entity;
}
}

View File

@ -22,7 +22,6 @@ use League\Csv\Writer;
class VendorExport extends BaseExport
{
private $company;
private $vendor_transformer;
@ -115,7 +114,7 @@ class VendorExport extends BaseExport
private function buildRow(Vendor $vendor) :array
{
$transformed_contact = false;
$transformed_contact = [];
$transformed_vendor = $this->vendor_transformer->transform($vendor);

View File

@ -49,7 +49,7 @@ class CreditFactory
$credit->user_id = $user_id;
$credit->company_id = $company_id;
$credit->recurring_id = null;
$credit->exchange_rate = 1;
return $credit;
}
}

View File

@ -49,6 +49,7 @@ class PurchaseOrderFactory
$purchase_order->user_id = $user_id;
$purchase_order->company_id = $company_id;
$purchase_order->recurring_id = null;
$purchase_order->exchange_rate = 1;
return $purchase_order;
}

View File

@ -46,6 +46,7 @@ class QuoteFactory
$quote->user_id = $user_id;
$quote->company_id = $company_id;
$quote->paid_to_date = 0;
$quote->exchange_rate = 1;
return $quote;
}

View File

@ -32,7 +32,8 @@ class ExpenseFilters extends QueryFilters
}
return $this->builder->where(function ($query) use ($filter) {
$query->where('public_notes', 'like', '%'.$filter.'%')
$query->where('number', 'like', '%'.$filter.'%')
->orWhere('public_notes', 'like', '%'.$filter.'%')
->orWhere('custom_value1', 'like', '%'.$filter.'%')
->orWhere('custom_value2', 'like', '%'.$filter.'%')
->orWhere('custom_value3', 'like', '%'.$filter.'%')
@ -164,6 +165,17 @@ class ExpenseFilters extends QueryFilters
return $this->builder;
}
if ($sort_col[0] == 'client_id') {
return $this->builder->orderBy(\App\Models\Client::select('name')
->whereColumn('clients.id', 'expenses.client_id'), $sort_col[1]);
}
if ($sort_col[0] == 'vendor_id') {
return $this->builder->orderBy(\App\Models\Vendor::select('name')
->whereColumn('vendors.id', 'expenses.vendor_id'), $sort_col[1]);
}
if (is_array($sort_col) && in_array($sort_col[1], ['asc', 'desc']) && in_array($sort_col[0], ['public_notes', 'date', 'id_number', 'custom_value1', 'custom_value2', 'custom_value3', 'custom_value4'])) {
return $this->builder->orderBy($sort_col[0], $sort_col[1]);
}

View File

@ -123,6 +123,11 @@ class PurchaseOrderFilters extends QueryFilters
return $this->builder;
}
if ($sort_col[0] == 'vendor_id') {
return $this->builder->orderBy(\App\Models\Vendor::select('name')
->whereColumn('vendors.id', 'purchase_orders.vendor_id'), $sort_col[1]);
}
return $this->builder->orderBy($sort_col[0], $sort_col[1]);
}

View File

@ -238,14 +238,20 @@ class InvoiceItemSum
{
$this->rule->tax($this->item);
$precision = strlen(substr(strrchr($this->rule->tax_rate1, "."), 1));
$this->item->tax_name1 = $this->rule->tax_name1;
$this->item->tax_rate1 = $this->rule->tax_rate1;
$this->item->tax_rate1 = round($this->rule->tax_rate1, $precision);
$precision = strlen(substr(strrchr($this->rule->tax_rate2, "."), 1));
$this->item->tax_name2 = $this->rule->tax_name2;
$this->item->tax_rate2 = $this->rule->tax_rate2;
$this->item->tax_rate2 = round($this->rule->tax_rate2, $precision);
$precision = strlen(substr(strrchr($this->rule->tax_rate3, "."), 1));
$this->item->tax_name3 = $this->rule->tax_name3;
$this->item->tax_rate3 = $this->rule->tax_rate3;
$this->item->tax_rate3 = round($this->rule->tax_rate3, $precision);
return $this;
}

View File

@ -11,24 +11,23 @@
namespace App\Http\Controllers;
use App\Http\Requests\Activity\DownloadHistoricalEntityRequest;
use App\Models\Activity;
use App\Transformers\ActivityTransformer;
use App\Utils\HostedPDF\NinjaPdf;
use App\Utils\Ninja;
use App\Utils\PhantomJS\Phantom;
use App\Utils\Traits\Pdf\PageNumbering;
use App\Utils\Traits\Pdf\PdfMaker;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Storage;
use stdClass;
use Symfony\Component\HttpFoundation\StreamedResponse;
use App\Utils\Ninja;
use App\Models\Activity;
use Illuminate\Http\Request;
use App\Utils\Traits\MakesHash;
use App\Utils\PhantomJS\Phantom;
use App\Utils\HostedPDF\NinjaPdf;
use App\Utils\Traits\Pdf\PdfMaker;
use App\Utils\Traits\Pdf\PageNumbering;
use Illuminate\Support\Facades\Storage;
use App\Transformers\ActivityTransformer;
use App\Http\Requests\Activity\ShowActivityRequest;
use App\Http\Requests\Activity\DownloadHistoricalEntityRequest;
class ActivityController extends BaseController
{
use PdfMaker, PageNumbering;
use PdfMaker, PageNumbering, MakesHash;
protected $entity_type = Activity::class;
@ -39,50 +38,6 @@ class ActivityController extends BaseController
parent::__construct();
}
/**
* @OA\Get(
* path="/api/v1/activities",
* operationId="getActivities",
* tags={"actvities"},
* summary="Gets a list of actvities",
* description="Lists all activities",
* @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(ref="#/components/parameters/index"),
* @OA\Parameter(
* name="rows",
* in="query",
* description="The number of activities to return",
* example="50",
* required=false,
* @OA\Schema(
* type="number",
* format="integer",
* ),
* ),
* @OA\Response(
* response=200,
* description="A list of actvities",
* @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\JsonContent(ref="#/components/schemas/Activity"),
* ),
* @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"),
* ),
* )
* @param Request $request
* @return Response|mixed
*/
public function index(Request $request)
{
$default_activities = $request->has('rows') ? $request->input('rows') : 75;
@ -115,47 +70,36 @@ class ActivityController extends BaseController
return $this->listResponse($activities);
}
/**
* @OA\Get(
* path="/api/v1/actvities/download_entity/{activity_id}",
* operationId="getActivityHistoricalEntityPdf",
* tags={"actvities"},
* summary="Gets a PDF for the given activity",
* description="Gets a PDF for the given activity",
* @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(
* name="activity_id",
* in="path",
* description="The Activity Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="PDF File",
* @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=404,
* description="No file exists for the given record",
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
* @param DownloadHistoricalEntityRequest $request
* @param Activity $activity
* @return JsonResponse|StreamedResponse
*/
public function entityActivity(ShowActivityRequest $request)
{
$default_activities = request()->has('rows') ? request()->input('rows') : 75;
$activities = Activity::with('user')
->orderBy('created_at', 'DESC')
->company()
->where("{$request->entity}_id", $request->entity_id)
->take($default_activities);
/** @var \App\Models\User auth()->user() */
$user = auth()->user();
if (!$user->isAdmin()) {
$activities->where('user_id', auth()->user()->id);
}
$system = ctrans('texts.system');
$data = $activities->cursor()->map(function ($activity) use ($system) {
return $activity->activity_string();
});
return response()->json(['data' => $data->toArray()], 200);
}
public function downloadHistoricalEntity(DownloadHistoricalEntityRequest $request, Activity $activity)
{
$backup = $activity->backup;
@ -204,6 +148,8 @@ class ActivityController extends BaseController
}
}
$activity->company->setLocale();
if (isset($activity->invoice_id)) {
$filename = $activity->invoice->numberFormatter().'.pdf';
} elseif (isset($activity->quote_id)) {

View File

@ -85,22 +85,20 @@ class InvitationController extends Controller
->with('contact.client')
->firstOrFail();
//09-03-2023 do not show entity if the invitation has been trashed.
if ($invitation->trashed() || $invitation->{$entity}->is_deleted) {
return $this->render('generic.not_available', ['account' => $invitation->company->account, 'company' => $invitation->company]);
}
/* 12/01/2022 Clean up an edge case where if the contact is trashed, restore if a invitation comes back. */
if ($invitation->contact->trashed()) {
$invitation->contact->restore();
}
/* Return early if we have the correct client_hash embedded */
$client_contact = $invitation->contact;
if (empty($client_contact->email)) {
$client_contact->email = Str::random(15) . "@example.com";
} $client_contact->save();
$client_contact->save();
}
if (request()->has('client_hash') && request()->input('client_hash') == $invitation->contact->client->client_hash) {
request()->session()->invalidate();

View File

@ -86,7 +86,7 @@ class InvoiceController extends Controller
public function showBlob($hash)
{
$data = Cache::pull($hash);
$data = Cache::get($hash);
match($data['entity_type']){
'invoice' => $invitation = InvoiceInvitation::withTrashed()->find($data['invitation_id']),

View File

@ -24,6 +24,7 @@ use App\Http\Requests\CompanyGateway\UpdateCompanyGatewayRequest;
use App\Jobs\Util\ApplePayDomain;
use App\Models\Client;
use App\Models\CompanyGateway;
use App\PaymentDrivers\CheckoutCom\CheckoutSetupWebhook;
use App\PaymentDrivers\Stripe\Jobs\StripeWebhook;
use App\Repositories\CompanyRepository;
use App\Transformers\CompanyGatewayTransformer;
@ -49,6 +50,8 @@ class CompanyGatewayController extends BaseController
private array $stripe_keys = ['d14dd26a47cecc30fdd65700bfb67b34', 'd14dd26a37cecc30fdd65700bfb55b23'];
private string $checkout_key = '3758e7f7c6f4cecf0f4f348b9a00f456';
/**
* CompanyGatewayController constructor.
* @param CompanyRepository $company_repo
@ -211,6 +214,9 @@ class CompanyGatewayController extends BaseController
if (in_array($company_gateway->gateway_key, $this->stripe_keys)) {
StripeWebhook::dispatch($company_gateway->company->company_key, $company_gateway->id);
}
elseif($company_gateway->gateway_key == $this->checkout_key) {
CheckoutSetupWebhook::dispatch($company_gateway->company->company_key, $company_gateway->id);
}
return $this->itemResponse($company_gateway);
}
@ -382,7 +388,9 @@ class CompanyGatewayController extends BaseController
$company_gateway->save();
// ApplePayDomain::dispatch($company_gateway, $company_gateway->company->db);
if($company_gateway->gateway_key == $this->checkout_key) {
CheckoutSetupWebhook::dispatch($company_gateway->company->company_key, $company_gateway->fresh()->id);
}
return $this->itemResponse($company_gateway);
}

View File

@ -11,34 +11,35 @@
namespace App\Http\Controllers;
use App\Events\Credit\CreditWasCreated;
use App\Events\Credit\CreditWasUpdated;
use App\Factory\CloneCreditFactory;
use App\Utils\Ninja;
use App\Models\Client;
use App\Models\Credit;
use App\Models\Account;
use App\Models\Invoice;
use Illuminate\Http\Response;
use App\Factory\CreditFactory;
use App\Filters\CreditFilters;
use App\Http\Requests\Credit\ActionCreditRequest;
use App\Jobs\Credit\ZipCredits;
use App\Utils\Traits\MakesHash;
use App\Jobs\Entity\EmailEntity;
use App\Factory\CloneCreditFactory;
use App\Services\PdfMaker\PdfMerge;
use Illuminate\Support\Facades\App;
use App\Utils\Traits\SavesDocuments;
use App\Repositories\CreditRepository;
use App\Events\Credit\CreditWasCreated;
use App\Events\Credit\CreditWasUpdated;
use App\Transformers\CreditTransformer;
use Illuminate\Support\Facades\Storage;
use App\Http\Requests\Credit\BulkCreditRequest;
use App\Http\Requests\Credit\CreateCreditRequest;
use App\Http\Requests\Credit\DestroyCreditRequest;
use App\Http\Requests\Credit\EditCreditRequest;
use App\Http\Requests\Credit\ShowCreditRequest;
use App\Http\Requests\Credit\StoreCreditRequest;
use App\Http\Requests\Credit\ActionCreditRequest;
use App\Http\Requests\Credit\CreateCreditRequest;
use App\Http\Requests\Credit\UpdateCreditRequest;
use App\Http\Requests\Credit\UploadCreditRequest;
use App\Jobs\Credit\ZipCredits;
use App\Jobs\Entity\EmailEntity;
use App\Models\Account;
use App\Models\Client;
use App\Models\Credit;
use App\Models\Invoice;
use App\Repositories\CreditRepository;
use App\Services\PdfMaker\PdfMerge;
use App\Transformers\CreditTransformer;
use App\Utils\Ninja;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\SavesDocuments;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Storage;
use App\Http\Requests\Credit\DestroyCreditRequest;
/**
* Class CreditController.
@ -691,6 +692,8 @@ class CreditController extends BaseController
$credit = $invitation->credit;
App::setLocale($invitation->contact->preferredLocale());
$file = $credit->service()->getCreditPdf($invitation);
$headers = ['Content-Type' => 'application/pdf'];

View File

@ -97,6 +97,8 @@ class ImportController extends Controller
];
}
$data = mb_convert_encoding($data, 'UTF-8', 'UTF-8');
return response()->json($data);
}

View File

@ -12,38 +12,39 @@
namespace App\Http\Controllers;
use App\Events\Invoice\InvoiceWasCreated;
use App\Events\Invoice\InvoiceWasUpdated;
use App\Factory\CloneInvoiceFactory;
use App\Factory\CloneInvoiceToQuoteFactory;
use App\Utils\Ninja;
use App\Models\Quote;
use App\Models\Account;
use App\Models\Invoice;
use App\Jobs\Cron\AutoBill;
use Illuminate\Http\Response;
use App\Factory\InvoiceFactory;
use App\Filters\InvoiceFilters;
use App\Http\Requests\Invoice\ActionInvoiceRequest;
use App\Utils\Traits\MakesHash;
use App\Jobs\Invoice\ZipInvoices;
use App\Services\PdfMaker\PdfMerge;
use Illuminate\Support\Facades\App;
use App\Factory\CloneInvoiceFactory;
use App\Jobs\Invoice\BulkInvoiceJob;
use App\Utils\Traits\SavesDocuments;
use App\Jobs\Invoice\UpdateReminders;
use App\Transformers\QuoteTransformer;
use App\Repositories\InvoiceRepository;
use Illuminate\Support\Facades\Storage;
use App\Transformers\InvoiceTransformer;
use App\Events\Invoice\InvoiceWasCreated;
use App\Events\Invoice\InvoiceWasUpdated;
use App\Factory\CloneInvoiceToQuoteFactory;
use App\Http\Requests\Invoice\BulkInvoiceRequest;
use App\Http\Requests\Invoice\CreateInvoiceRequest;
use App\Http\Requests\Invoice\DestroyInvoiceRequest;
use App\Http\Requests\Invoice\EditInvoiceRequest;
use App\Http\Requests\Invoice\ShowInvoiceRequest;
use App\Http\Requests\Invoice\StoreInvoiceRequest;
use App\Http\Requests\Invoice\ActionInvoiceRequest;
use App\Http\Requests\Invoice\CreateInvoiceRequest;
use App\Http\Requests\Invoice\UpdateInvoiceRequest;
use App\Http\Requests\Invoice\UpdateReminderRequest;
use App\Http\Requests\Invoice\UploadInvoiceRequest;
use App\Jobs\Cron\AutoBill;
use App\Jobs\Invoice\BulkInvoiceJob;
use App\Jobs\Invoice\UpdateReminders;
use App\Jobs\Invoice\ZipInvoices;
use App\Models\Account;
use App\Models\Invoice;
use App\Models\Quote;
use App\Repositories\InvoiceRepository;
use App\Services\PdfMaker\PdfMerge;
use App\Transformers\InvoiceTransformer;
use App\Transformers\QuoteTransformer;
use App\Utils\Ninja;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\SavesDocuments;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Storage;
use App\Http\Requests\Invoice\DestroyInvoiceRequest;
use App\Http\Requests\Invoice\UpdateReminderRequest;
/**
* Class InvoiceController.
@ -821,6 +822,8 @@ class InvoiceController extends BaseController
$invoice = $invitation->invoice;
App::setLocale($invitation->contact->preferredLocale());
$file_name = $invoice->numberFormatter().'.pdf';
$file = (new \App\Jobs\Entity\CreateRawPdf($invitation, $invitation->company->db))->handle();

View File

@ -285,7 +285,7 @@ class PreviewController extends BaseController
return $maker->getCompiledHTML();
}
} catch(\Exception $e) {
nlog($e->getMessage());
// nlog($e->getMessage());
DB::connection(config('database.default'))->rollBack();
return;

View File

@ -11,6 +11,7 @@
namespace App\Http\Controllers;
use App\Utils\Ninja;
use Illuminate\Http\Request;
use App\Jobs\Util\UnlinkFile;
use App\Exceptions\SystemError;
@ -30,7 +31,9 @@ class ProtectedDownloadController extends BaseController
abort(404, 'File no longer available');
}
return response()->download($hashed_path, basename($hashed_path), [])->deleteFileAfterSend(true);
return response()->streamDownload(function () use ($hashed_path) {
echo Storage::get($hashed_path);
}, basename($hashed_path), []);
}

View File

@ -11,32 +11,33 @@
namespace App\Http\Controllers;
use App\Events\PurchaseOrder\PurchaseOrderWasCreated;
use App\Events\PurchaseOrder\PurchaseOrderWasUpdated;
use App\Utils\Ninja;
use App\Models\Client;
use App\Models\Account;
use App\Models\PurchaseOrder;
use Illuminate\Http\Response;
use App\Utils\Traits\MakesHash;
use App\Services\PdfMaker\PdfMerge;
use Illuminate\Support\Facades\App;
use App\Utils\Traits\SavesDocuments;
use App\Factory\PurchaseOrderFactory;
use App\Filters\PurchaseOrderFilters;
use App\Http\Requests\PurchaseOrder\ActionPurchaseOrderRequest;
use Illuminate\Support\Facades\Storage;
use App\Jobs\PurchaseOrder\ZipPurchaseOrders;
use App\Repositories\PurchaseOrderRepository;
use App\Jobs\PurchaseOrder\PurchaseOrderEmail;
use App\Transformers\PurchaseOrderTransformer;
use App\Events\PurchaseOrder\PurchaseOrderWasCreated;
use App\Events\PurchaseOrder\PurchaseOrderWasUpdated;
use App\Http\Requests\PurchaseOrder\BulkPurchaseOrderRequest;
use App\Http\Requests\PurchaseOrder\CreatePurchaseOrderRequest;
use App\Http\Requests\PurchaseOrder\DestroyPurchaseOrderRequest;
use App\Http\Requests\PurchaseOrder\EditPurchaseOrderRequest;
use App\Http\Requests\PurchaseOrder\ShowPurchaseOrderRequest;
use App\Http\Requests\PurchaseOrder\StorePurchaseOrderRequest;
use App\Http\Requests\PurchaseOrder\ActionPurchaseOrderRequest;
use App\Http\Requests\PurchaseOrder\CreatePurchaseOrderRequest;
use App\Http\Requests\PurchaseOrder\UpdatePurchaseOrderRequest;
use App\Http\Requests\PurchaseOrder\UploadPurchaseOrderRequest;
use App\Jobs\PurchaseOrder\PurchaseOrderEmail;
use App\Jobs\PurchaseOrder\ZipPurchaseOrders;
use App\Models\Account;
use App\Models\Client;
use App\Models\PurchaseOrder;
use App\Repositories\PurchaseOrderRepository;
use App\Services\PdfMaker\PdfMerge;
use App\Transformers\PurchaseOrderTransformer;
use App\Utils\Ninja;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\SavesDocuments;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Storage;
use App\Http\Requests\PurchaseOrder\DestroyPurchaseOrderRequest;
class PurchaseOrderController extends BaseController
{

View File

@ -11,38 +11,39 @@
namespace App\Http\Controllers;
use App\Events\Quote\QuoteWasCreated;
use App\Events\Quote\QuoteWasUpdated;
use App\Factory\CloneQuoteFactory;
use App\Factory\CloneQuoteToInvoiceFactory;
use App\Factory\CloneQuoteToProjectFactory;
use App\Utils\Ninja;
use App\Models\Quote;
use App\Models\Client;
use App\Models\Account;
use App\Models\Invoice;
use Illuminate\Http\Request;
use App\Factory\QuoteFactory;
use App\Filters\QuoteFilters;
use App\Http\Requests\Quote\ActionQuoteRequest;
use App\Http\Requests\Quote\BulkActionQuoteRequest;
use App\Http\Requests\Quote\CreateQuoteRequest;
use App\Http\Requests\Quote\DestroyQuoteRequest;
use App\Jobs\Quote\ZipQuotes;
use Illuminate\Http\Response;
use App\Utils\Traits\MakesHash;
use App\Factory\CloneQuoteFactory;
use App\Services\PdfMaker\PdfMerge;
use Illuminate\Support\Facades\App;
use App\Utils\Traits\SavesDocuments;
use App\Events\Quote\QuoteWasCreated;
use App\Events\Quote\QuoteWasUpdated;
use App\Repositories\QuoteRepository;
use App\Transformers\QuoteTransformer;
use App\Utils\Traits\GeneratesCounter;
use Illuminate\Support\Facades\Storage;
use App\Transformers\InvoiceTransformer;
use App\Factory\CloneQuoteToInvoiceFactory;
use App\Factory\CloneQuoteToProjectFactory;
use App\Http\Requests\Quote\EditQuoteRequest;
use App\Http\Requests\Quote\ShowQuoteRequest;
use App\Http\Requests\Quote\StoreQuoteRequest;
use App\Http\Requests\Quote\ActionQuoteRequest;
use App\Http\Requests\Quote\CreateQuoteRequest;
use App\Http\Requests\Quote\UpdateQuoteRequest;
use App\Http\Requests\Quote\UploadQuoteRequest;
use App\Jobs\Quote\ZipQuotes;
use App\Models\Account;
use App\Models\Client;
use App\Models\Invoice;
use App\Models\Quote;
use App\Repositories\QuoteRepository;
use App\Services\PdfMaker\PdfMerge;
use App\Transformers\InvoiceTransformer;
use App\Transformers\QuoteTransformer;
use App\Utils\Ninja;
use App\Utils\Traits\GeneratesCounter;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\SavesDocuments;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Storage;
use App\Http\Requests\Quote\DestroyQuoteRequest;
use App\Http\Requests\Quote\BulkActionQuoteRequest;
/**
* Class QuoteController.
@ -825,6 +826,8 @@ class QuoteController extends BaseController
$contact = $invitation->contact;
$quote = $invitation->quote;
App::setLocale($invitation->contact->preferredLocale());
$file = $quote->service()->getQuotePdf($contact);
$headers = ['Content-Type' => 'application/pdf'];

View File

@ -11,29 +11,28 @@
namespace App\Http\Controllers;
use App\Events\RecurringInvoice\RecurringInvoiceWasCreated;
use App\Events\RecurringInvoice\RecurringInvoiceWasUpdated;
use App\Utils\Ninja;
use App\Models\Account;
use Illuminate\Http\Response;
use App\Utils\Traits\MakesHash;
use App\Models\RecurringInvoice;
use App\Utils\Traits\SavesDocuments;
use App\Factory\RecurringInvoiceFactory;
use App\Filters\RecurringInvoiceFilters;
use App\Http\Requests\RecurringInvoice\ActionRecurringInvoiceRequest;
use App\Jobs\RecurringInvoice\UpdateRecurring;
use App\Repositories\RecurringInvoiceRepository;
use App\Transformers\RecurringInvoiceTransformer;
use App\Events\RecurringInvoice\RecurringInvoiceWasCreated;
use App\Events\RecurringInvoice\RecurringInvoiceWasUpdated;
use App\Http\Requests\RecurringInvoice\BulkRecurringInvoiceRequest;
use App\Http\Requests\RecurringInvoice\CreateRecurringInvoiceRequest;
use App\Http\Requests\RecurringInvoice\DestroyRecurringInvoiceRequest;
use App\Http\Requests\RecurringInvoice\EditRecurringInvoiceRequest;
use App\Http\Requests\RecurringInvoice\ShowRecurringInvoiceRequest;
use App\Http\Requests\RecurringInvoice\StoreRecurringInvoiceRequest;
use App\Http\Requests\RecurringInvoice\ActionRecurringInvoiceRequest;
use App\Http\Requests\RecurringInvoice\CreateRecurringInvoiceRequest;
use App\Http\Requests\RecurringInvoice\UpdateRecurringInvoiceRequest;
use App\Http\Requests\RecurringInvoice\UploadRecurringInvoiceRequest;
use App\Jobs\RecurringInvoice\UpdateRecurring;
use App\Models\Account;
use App\Models\RecurringInvoice;
use App\Repositories\RecurringInvoiceRepository;
use App\Transformers\RecurringInvoiceTransformer;
use App\Utils\Ninja;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\SavesDocuments;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Storage;
use App\Http\Requests\RecurringInvoice\DestroyRecurringInvoiceRequest;
/**
* Class RecurringInvoiceController.
@ -567,6 +566,8 @@ class RecurringInvoiceController extends BaseController
$invoice = $invitation->recurring_invoice;
\Illuminate\Support\Facades\App::setLocale($invitation->contact->preferredLocale());
$file_name = $invoice->numberFormatter().'.pdf';
$file = (new \App\Jobs\Entity\CreateRawPdf($invitation, $invitation->company->db))->handle();

View File

@ -53,7 +53,7 @@ class SelfUpdateController extends BaseController
nlog('Test filesystem is writable');
$this->testWritable();
// $this->testWritable();
nlog('Clear cache directory');

View File

@ -11,16 +11,17 @@
namespace App\Http\Controllers\VendorPortal;
use App\Events\Misc\InvitationWasViewed;
use App\Events\PurchaseOrder\PurchaseOrderWasViewed;
use App\Http\Controllers\Controller;
use App\Jobs\Vendor\CreatePurchaseOrderPdf;
use App\Models\PurchaseOrderInvitation;
use App\Utils\Ninja;
use App\Utils\Traits\MakesDates;
use App\Utils\Traits\MakesHash;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\MakesDates;
use Illuminate\Support\Facades\App;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
use App\Models\PurchaseOrderInvitation;
use App\Events\Misc\InvitationWasViewed;
use App\Jobs\Vendor\CreatePurchaseOrderPdf;
use App\Events\PurchaseOrder\PurchaseOrderWasViewed;
/**
* Class InvitationController.
@ -89,6 +90,8 @@ class InvitationController extends Controller
return response()->json(["message" => "no record found"], 400);
}
App::setLocale($invitation->contact->preferredLocale());
$file_name = $invitation->purchase_order->numberFormatter().'.pdf';
$file = (new CreatePurchaseOrderPdf($invitation))->rawPdf();

View File

@ -20,15 +20,12 @@ use Illuminate\Support\Str;
use App\Models\QuoteInvitation;
use App\Utils\VendorHtmlEngine;
use App\Models\CreditInvitation;
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;
use App\Jobs\Vendor\CreatePurchaseOrderPdf;
use App\Services\PdfMaker\Designs\Utilities\DesignHelpers;
class PdfSlot extends Component
{
@ -48,12 +45,12 @@ class PdfSlot extends Component
private $entity_type;
protected $listeners = ['viewportChanged' => 'getPdf'];
public $show_cost = true;
public $show_quantity = true;
public $show_line_total = true;
public $route_entity = 'client';
public function mount()
@ -106,11 +103,12 @@ class PdfSlot extends Component
$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);
$this->show_line_total = in_array('$product.line_total', $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->show_line_total = in_array('$product.line_total', $this->settings->pdf_variables->product_quote_columns);
}
$this->html_variables = $this->entity->client ?
@ -175,20 +173,20 @@ class PdfSlot extends Component
if($this->entity_type == 'invoice' || $this->entity_type == 'recurring_invoice') {
foreach($this->settings->pdf_variables->invoice_details as $variable)
$entity_details .= "<div class='flex px-3 block'><p class= w-36 block'>{$variable}_label</p><p class='pl-1 w-36 block entity-field'>{$variable}</p></div>";
$entity_details .= "<div class='flex px-5 block'><p class= w-36 block'>{$variable}_label</p><p class='pl-5 w-36 block entity-field'>{$variable}</p></div>";
}
elseif($this->entity_type == 'quote'){
foreach($this->settings->pdf_variables->quote_details as $variable)
$entity_details .= "<div class='flex px-3 block'><p class= w-36 block'>{$variable}_label</p><p class='pl-1 w-36 block entity-field'>{$variable}</p></div>";
$entity_details .= "<div class='flex px-5 block'><p class= w-36 block'>{$variable}_label</p><p class='pl-5 w-36 block entity-field'>{$variable}</p></div>";
}
elseif($this->entity_type == 'credit') {
foreach($this->settings->pdf_variables->credit_details as $variable)
$entity_details .= "<div class='flex px-3 block'><p class= w-36 block'>{$variable}_label</p><p class='pl-1 w-36 block entity-field'>{$variable}</p></div>";
$entity_details .= "<div class='flex px-5 block'><p class= w-36 block'>{$variable}_label</p><p class='pl-5 w-36 block entity-field'>{$variable}</p></div>";
}
elseif($this->entity_type == 'purchase_order'){
foreach($this->settings->pdf_variables->purchase_order_details as $variable)
$entity_details .= "<div class='flex px-3 block'><p class= w-36 block'>{$variable}_label</p><p class='pl-1 w-36 block entity-field'>{$variable}</p></div>";
$entity_details .= "<div class='flex px-5 block'><p class= w-36 block'>{$variable}_label</p><p class='pl-5 w-36 block entity-field'>{$variable}</p></div>";
}
return $this->convertVariables($entity_details);
@ -231,13 +229,16 @@ class PdfSlot extends Component
private function getProducts()
{
$product_items = collect($this->entity->line_items)->filter(function ($item) {
return $item->type_id == 1 || $item->type_id == 6 || $item->type_id == 5;
})->map(function ($item){
return [
'quantity' => $item->quantity,
'cost' => Number::formatMoney($item->cost, $this->entity->client ?: $this->entity->vendor),
'notes' => $item->notes,
'notes' => $this->invitation->company->markdown_enabled ? DesignHelpers::parseMarkdownToHtml($item->notes) : $item->notes,
'line_total' => Number::formatMoney($item->line_total, $this->entity->client ?: $this->entity->vendor),
];
});
@ -253,7 +254,7 @@ class PdfSlot extends Component
return [
'quantity' => $item->quantity,
'cost' => Number::formatMoney($item->cost, $this->entity->client ?: $this->entity->vendor),
'notes' => $item->notes,
'notes' => $this->invitation->company->markdown_enabled ? DesignHelpers::parseMarkdownToHtml($item->notes) : $item->notes,
'line_total' => Number::formatMoney($item->line_total, $this->entity->client ?: $this->entity->vendor),
];
});

View File

@ -53,7 +53,6 @@ class SetInviteDb
/* Try and determine the DB from the invitation key STRING*/
if (config('ninja.db.multi_db_enabled')) {
// nlog("/ Try and determine the DB from the invitation key /");
$hashids = new Hashids(config('ninja.hash_salt'), 10);
$segments = explode('-', $request->route('invitation_key'));

View File

@ -12,10 +12,12 @@
namespace App\Http\Requests\Activity;
use App\Http\Requests\Request;
use App\Models\Activity;
use App\Utils\Traits\MakesHash;
class ShowActivityRequest extends Request
{
use MakesHash;
/**
* Determine if the user is authorized to make this request.
*
@ -23,7 +25,25 @@ class ShowActivityRequest extends Request
*/
public function authorize() : bool
{
// return auth()->user()->isAdmin();
return auth()->user()->can('view', Activity::class);
return true;
}
public function rules()
{
return [
'entity' => 'bail|required|in:invoice,quote,credit,purchase_order,payment,client,vendor,expense,task,project,subscription,recurring_invoice,',
'entity_id' => 'bail|required|exists:'.$this->entity.'s,id,company_id,'.auth()->user()->company()->id,
];
}
public function prepareForValidation()
{
$input = $this->all();
if(isset($input['entity_id']))
$input['entity_id'] = $this->decodePrimaryKey($input['entity_id']);
$this->replace($input);
}
}

View File

@ -45,7 +45,7 @@ class ShowChartRequest extends Request
$input = $this->all();
if(isset($input['date_range'])) {
$dates = $this->calculateStartAndEndDates($input);
$dates = $this->calculateStartAndEndDates($input, auth()->user()->company());
$input['start_date'] = $dates[0];
$input['end_date'] = $dates[1];
}

View File

@ -67,6 +67,7 @@ class StoreCreditRequest extends Request
$rules['tax_name1'] = 'bail|sometimes|string|nullable';
$rules['tax_name2'] = 'bail|sometimes|string|nullable';
$rules['tax_name3'] = 'bail|sometimes|string|nullable';
$rules['exchange_rate'] = 'bail|sometimes|gt:0';
if ($this->invoice_id) {
$rules['invoice_id'] = new ValidInvoiceCreditRule();
@ -88,7 +89,11 @@ class StoreCreditRequest extends Request
$input = $this->decodePrimaryKeys($input);
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
//$input['line_items'] = json_encode($input['line_items']);
if (array_key_exists('exchange_rate', $input) && is_null($input['exchange_rate'])) {
$input['exchange_rate'] = 1;
}
$this->replace($input);
}
}

View File

@ -67,6 +67,7 @@ class UpdateCreditRequest extends Request
$rules['tax_name1'] = 'bail|sometimes|string|nullable';
$rules['tax_name2'] = 'bail|sometimes|string|nullable';
$rules['tax_name3'] = 'bail|sometimes|string|nullable';
$rules['exchange_rate'] = 'bail|sometimes|gt:0';
return $rules;
}
@ -81,6 +82,10 @@ class UpdateCreditRequest extends Request
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
}
if (array_key_exists('exchange_rate', $input) && is_null($input['exchange_rate'])) {
$input['exchange_rate'] = 1;
}
$input['id'] = $this->credit->id;
$this->replace($input);

View File

@ -72,6 +72,7 @@ class StoreInvoiceRequest extends Request
$rules['tax_name1'] = 'bail|sometimes|string|nullable';
$rules['tax_name2'] = 'bail|sometimes|string|nullable';
$rules['tax_name3'] = 'bail|sometimes|string|nullable';
$rules['exchange_rate'] = 'bail|sometimes|gt:0';
return $rules;
}

View File

@ -72,6 +72,7 @@ class UpdateInvoiceRequest extends Request
$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.
$rules['exchange_rate'] = 'bail|sometimes|gt:0';
// not needed.
// $rules['partial_due_date'] = 'bail|sometimes|required_unless:partial,0,null';
@ -95,6 +96,10 @@ class UpdateInvoiceRequest extends Request
unset($input['documents']);
}
if (array_key_exists('exchange_rate', $input) && is_null($input['exchange_rate'])) {
$input['exchange_rate'] = 1;
}
$this->replace($input);
}

View File

@ -60,6 +60,7 @@ class StorePurchaseOrderRequest extends Request
}
$rules['status_id'] = 'nullable|integer|in:1,2,3,4,5';
$rules['exchange_rate'] = 'bail|sometimes|gt:0';
return $rules;
}
@ -77,6 +78,10 @@ class StorePurchaseOrderRequest extends Request
$input['amount'] = 0;
$input['balance'] = 0;
if (array_key_exists('exchange_rate', $input) && is_null($input['exchange_rate'])) {
$input['exchange_rate'] = 1;
}
$this->replace($input);
}
}

View File

@ -63,6 +63,7 @@ class UpdatePurchaseOrderRequest extends Request
}
$rules['status_id'] = 'sometimes|integer|in:1,2,3,4,5';
$rules['exchange_rate'] = 'bail|sometimes|gt:0';
return $rules;
}
@ -79,6 +80,10 @@ class UpdatePurchaseOrderRequest extends Request
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
}
if (array_key_exists('exchange_rate', $input) && is_null($input['exchange_rate'])) {
$input['exchange_rate'] = 1;
}
$this->replace($input);
}
}

View File

@ -55,6 +55,7 @@ class StoreQuoteRequest extends Request
$rules['discount'] = 'sometimes|numeric';
$rules['is_amount_discount'] = ['boolean'];
$rules['exchange_rate'] = 'bail|sometimes|gt:0';
// $rules['number'] = new UniqueQuoteNumberRule($this->all());
$rules['line_items'] = 'array';
@ -72,6 +73,10 @@ class StoreQuoteRequest extends Request
$input['amount'] = 0;
$input['balance'] = 0;
if (array_key_exists('exchange_rate', $input) && is_null($input['exchange_rate'])) {
$input['exchange_rate'] = 1;
}
$this->replace($input);
}
}

View File

@ -57,6 +57,7 @@ class UpdateQuoteRequest extends Request
$rules['line_items'] = 'array';
$rules['discount'] = 'sometimes|numeric';
$rules['is_amount_discount'] = ['boolean'];
$rules['exchange_rate'] = 'bail|sometimes|gt:0';
return $rules;
}
@ -75,6 +76,10 @@ class UpdateQuoteRequest extends Request
unset($input['documents']);
}
if (array_key_exists('exchange_rate', $input) && is_null($input['exchange_rate'])) {
$input['exchange_rate'] = 1;
}
$input['id'] = $this->quote->id;
$this->replace($input);

View File

@ -67,6 +67,7 @@ class StoreRecurringInvoiceRequest extends Request
$rules['tax_name2'] = 'bail|sometimes|string|nullable';
$rules['tax_name3'] = 'bail|sometimes|string|nullable';
$rules['due_date_days'] = 'bail|sometimes|string';
$rules['exchange_rate'] = 'bail|sometimes|gt:0';
return $rules;
}
@ -143,6 +144,10 @@ class StoreRecurringInvoiceRequest extends Request
unset($input['number']);
}
if (array_key_exists('exchange_rate', $input) && (is_null($input['exchange_rate']) || $input['exchange_rate'] == 0)) {
$input['exchange_rate'] = 1;
}
$this->replace($input);
}

View File

@ -61,6 +61,7 @@ class UpdateRecurringInvoiceRequest extends Request
$rules['tax_name1'] = 'bail|sometimes|string|nullable';
$rules['tax_name2'] = 'bail|sometimes|string|nullable';
$rules['tax_name3'] = 'bail|sometimes|string|nullable';
$rules['exchange_rate'] = 'bail|sometimes|gt:0';
return $rules;
}
@ -121,6 +122,10 @@ class UpdateRecurringInvoiceRequest extends Request
unset($input['documents']);
}
if (array_key_exists('exchange_rate', $input) && (is_null($input['exchange_rate']) || $input['exchange_rate'] == 0)) {
$input['exchange_rate'] = 1;
}
$this->replace($input);
}

View File

@ -477,8 +477,12 @@ class BaseImport
}
nlog($invoice_data);
$saveable_invoice_data = $invoice_data;
$invoice_repository->save($invoice_data, $invoice);
if(array_key_exists('payments', $saveable_invoice_data))
unset($saveable_invoice_data['payments']);
$invoice_repository->save($saveable_invoice_data, $invoice);
$count++;
// If we're doing a generic CSV import, only import payment data if we're not importing a payment CSV.
@ -504,7 +508,7 @@ class BaseImport
];
/* Make sure we don't apply any payments to invoices with a Zero Amount*/
if ($invoice->amount > 0) {
if ($invoice->amount > 0 && $payment_data['amount'] > 0) {
$payment = $payment_repository->save(
$payment_data,
@ -761,7 +765,8 @@ class BaseImport
{
$keys = array_shift($data);
ksort($keys);
// nlog($data);
// nlog($keys);
return array_map(function ($values) use ($keys) {
return array_combine($keys, $values);
}, $data);

View File

@ -319,6 +319,21 @@ class BaseTransformer
// return Number::parseFloat($number);
}
/**
* @param $data
* @param $field
*
* @return float
*/
public function getFloatOrOne($data, $field)
{
if (array_key_exists($field, $data))
return Number::parseStringFloat($data[$field]) > 0 ? Number::parseStringFloat($data[$field]) : 1;
return 1;
}
/**
* @param $name
*

View File

@ -114,7 +114,7 @@ class InvoiceTransformer extends BaseTransformer
$invoice_data,
'invoice.custom_surcharge4'
),
'exchange_rate' => $this->getString(
'exchange_rate' => $this->getFloatOrOne(
$invoice_data,
'invoice.exchange_rate'
),

View File

@ -46,7 +46,7 @@ class PaymentTransformer extends BaseTransformer
$data,
'payment.transaction_reference '
),
'date' => $this->getString($data, 'payment.date'),
'date' => isset($data['payment.date']) ? $this->parseDate($data['payment.date']) : date('y-m-d'),
'private_notes' => $this->getString($data, 'payment.private_notes'),
'custom_value1' => $this->getString($data, 'payment.custom_value1'),
'custom_value2' => $this->getString($data, 'payment.custom_value2'),

View File

@ -114,7 +114,7 @@ class QuoteTransformer extends BaseTransformer
$quote_data,
'quote.custom_surcharge4'
),
'exchange_rate' => $this->getString(
'exchange_rate' => $this->getFloatOrOne(
$quote_data,
'quote.exchange_rate'
),

View File

@ -122,7 +122,7 @@ class RecurringInvoiceTransformer extends BaseTransformer
$invoice_data,
'invoice.custom_surcharge4'
),
'exchange_rate' => $this->getString(
'exchange_rate' => $this->getFloat(
$invoice_data,
'invoice.exchange_rate'
),

View File

@ -441,15 +441,15 @@ class CompanyExport implements ShouldQueue
$path = 'backups';
Storage::makeDirectory(public_path('storage/backups/'));
Storage::makeDirectory(storage_path('backups/'));
try {
mkdir(public_path('storage/backups/'));
mkdir(storage_path('backups/'));
} catch(\Exception $e) {
nlog("could not create directory");
}
$zip_path = public_path('storage/backups/'.$file_name);
$zip_path = storage_path('backups/'.$file_name);
$zip = new \ZipArchive();
if ($zip->open($zip_path, \ZipArchive::CREATE)!==true) {
@ -459,14 +459,16 @@ class CompanyExport implements ShouldQueue
$zip->addFromString("backup.json", json_encode($this->export_data));
$zip->close();
if (Ninja::isHosted()) {
Storage::disk(config('filesystems.default'))->put('backups/'.$file_name, file_get_contents($zip_path));
}
unlink($zip_path);
$storage_file_path = Storage::disk(config('filesystems.default'))->url('backups/'.$file_name);
if(Ninja::isSelfHost())
$storage_path = 'backups/'.$file_name;
else
$storage_path = Storage::disk(config('filesystems.default'))->path('backups/'.$file_name);
$url = Cache::get($this->hash);
Cache::put($this->hash, $storage_path, now()->addHour());
App::forgetInstance('translator');

View File

@ -398,6 +398,9 @@ class Import implements ShouldQueue
$data = $this->transformCompanyData($data);
if (Ninja::isHosted()) {
$data['subdomain'] = str_replace("_","",$data['subdomain']);
if (!MultiDB::checkDomainAvailable($data['subdomain'])) {
$data['subdomain'] = MultiDB::randomSubdomainGenerator();
}

View File

@ -129,12 +129,10 @@ class ReminderJob implements ShouldQueue
$invoice->service()->touchReminder($reminder_template)->save();
$fees = $this->calcLateFee($invoice, $reminder_template);
if(in_array($invoice->client->getSetting('lock_invoices'), ['when_sent','when_paid'])) {
if($invoice->isLocked())
return $this->addFeeToNewInvoice($invoice, $reminder_template, $fees);
}
else
$invoice = $this->setLateFee($invoice, $fees[0], $fees[1]);
$invoice = $this->setLateFee($invoice, $fees[0], $fees[1]);
//20-04-2022 fixes for endless reminders - generic template naming was wrong
$enabled_reminder = 'enable_'.$reminder_template;

View File

@ -554,7 +554,7 @@ class Account extends BaseModel
$nmo->to_user = $this->companies()->first()->owner();
NinjaMailerJob::dispatch($nmo, true);
Cache::put("throttle_notified:{$this->key}", true, 60 * 24);
Cache::put("throttle_notified:{$this->key}", true, 60 * 60 * 24);
if (config('ninja.notification.slack')) {
$this->companies()->first()->notification(new EmailQuotaNotification($this))->ninja();

View File

@ -347,6 +347,11 @@ class Client extends BaseModel implements HasLocalePreference
return $this->hasMany(Credit::class)->withTrashed();
}
public function purgeable_activities()
{
return $this->hasMany(Activity::class);
}
public function activities()
{
return $this->hasMany(Activity::class)->take(50)->orderBy('id', 'desc');

View File

@ -16,6 +16,7 @@ use App\Casts\EncryptedCast;
use App\Utils\Traits\AppSetup;
use App\Utils\Traits\MakesHash;
use App\DataMapper\CompanySettings;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Cache;
use Laracasts\Presenter\PresentableTrait;
use App\Utils\Traits\CompanySettingsSaver;
@ -686,6 +687,11 @@ class Company extends BaseModel
return $this->getLocale();
}
public function setLocale()
{
App::setLocale($this->getLocale());
}
public function getSetting($setting)
{
if (property_exists($this->settings, $setting) != false) {

View File

@ -455,7 +455,7 @@ class Quote extends BaseModel
case self::STATUS_DRAFT:
return ctrans('texts.draft');
case self::STATUS_SENT:
return ctrans('texts.pending');
return ctrans('texts.sent');
case self::STATUS_APPROVED:
return ctrans('texts.approved');
case self::STATUS_EXPIRED:

View File

@ -0,0 +1,122 @@
<?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\PaymentDrivers\CheckoutCom;
use App\Models\Payment;
use App\Models\SystemLog;
use App\Libraries\MultiDB;
use App\Models\GatewayType;
use App\Models\PaymentHash;
use App\Models\PaymentType;
use Illuminate\Bus\Queueable;
use App\Models\CompanyGateway;
use App\Jobs\Util\SystemLogger;
use Checkout\CheckoutApiException;
use Illuminate\Queue\SerializesModels;
use App\PaymentDrivers\Stripe\Utilities;
use Illuminate\Queue\InteractsWithQueue;
use App\PaymentDrivers\CheckoutCom\Webhook;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Checkout\CheckoutAuthorizationException;
use Checkout\Workflows\CreateWorkflowRequest;
use App\PaymentDrivers\CheckoutComPaymentDriver;
use Checkout\Workflows\Actions\WebhookSignature;
use Checkout\Workflows\Actions\WebhookWorkflowActionRequest;
use Checkout\Workflows\Conditions\EventWorkflowConditionRequest;
class CheckoutSetupWebhook implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, Utilities;
public $tries = 1;
public $deleteWhenMissingModels = true;
private string $authentication_webhook_name = 'Invoice_Ninja_3DS_Workflow';
public CheckoutComPaymentDriver $checkout;
public function __construct(private string $company_key, private int $company_gateway_id)
{
}
public function handle()
{
MultiDB::findAndSetDbByCompanyKey($this->company_key);
$company_gateway = CompanyGateway::find($this->company_gateway_id);
$this->checkout = $company_gateway->driver()->init();
$webhook = new Webhook($this->checkout);
$workflows = $webhook->getWorkFlows();
$wf = collect($workflows['data'])->first(function ($workflow) {
return $workflow['name'] == $this->authentication_webhook_name;
});
if($wf)
return;
$this->createAuthenticationWorkflow();
}
/**
* Creates an authentication workflow for 3DS
* and also a registration mechanism for payments that have been approved.
*
* @return void
*/
public function createAuthenticationWorkflow()
{
$signature = new WebhookSignature();
$signature->key = $this->checkout->company_gateway->company->company_key;
$signature->method = "HMACSHA256";
$actionRequest = new WebhookWorkflowActionRequest();
$actionRequest->url = $this->checkout->company_gateway->webhookUrl();
$actionRequest->signature = $signature;
$eventWorkflowConditionRequest = new EventWorkflowConditionRequest();
$eventWorkflowConditionRequest->events = [
"gateway" => ["payment_approved"],
"issuing" => ["authorization_approved","authorization_declined"],
];
$request = new CreateWorkflowRequest();
$request->actions = [$actionRequest];
$request->conditions = [$eventWorkflowConditionRequest];
$request->name = $this->authentication_webhook_name;
$request->active = true;
try {
$response = $this->checkout->gateway->getWorkflowsClient()->createWorkflow($request);
} catch (CheckoutApiException $e) {
// API error
$error_details = $e->error_details;
$http_status_code = isset($e->http_metadata) ? $e->http_metadata->getStatusCode() : null;
nlog("Checkout WEBHOOK creation error");
nlog($error_details);
} catch (CheckoutAuthorizationException $e) {
// Bad Invalid authorization
}
}
}

View File

@ -0,0 +1,117 @@
<?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\PaymentDrivers\CheckoutCom;
use App\Models\Payment;
use App\Models\SystemLog;
use App\Libraries\MultiDB;
use App\Models\GatewayType;
use App\Models\PaymentHash;
use App\Models\PaymentType;
use Illuminate\Bus\Queueable;
use App\Models\CompanyGateway;
use App\Jobs\Util\SystemLogger;
use Illuminate\Queue\SerializesModels;
use App\PaymentDrivers\Stripe\Utilities;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
class CheckoutWebhook implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, Utilities;
public $tries = 1;
public $deleteWhenMissingModels = true;
public CompanyGateway $company_gateway;
public function __construct(public array $webhook_array, public string $company_key, public int $company_gateway_id)
{
}
public function handle()
{
nlog("Checkout Webhook");
MultiDB::findAndSetDbByCompanyKey($this->company_key);
$this->company_gateway = CompanyGateway::withTrashed()->find($this->company_gateway_id);
if(!isset($this->webhook_array['type']))
nlog("Checkout Webhook type not set");
match($this->webhook_array['type']){
'payment_approved' => $this->paymentApproved(),
};
}
/**
* {
* "id":"evt_dli6ty4qo5vuxle5wklf5gwbwy","type":"payment_approved","version":"1.0.33","created_on":"2023-07-21T10:03:07.1555904Z",
* "data":{"id":"pay_oqwbsd22kvpuvd35y5fhbdawxa","action_id":"act_buviezur7zsurnsorcgfn63e44","reference":"0014","amount":584168,"auth_code":"113059","currency":"USD","customer":{"id":"cus_6n4yt4q5kf4unn36o5qpbevxhe","email":"cypress@example.com"},
* "metadata":{"udf1":"Invoice Ninja","udf2":"ofhgiGjyQXbsbUwygURfYFT2C3E7iY7U"},"payment_type":"Regular","processed_on":"2023-07-21T10:02:57.4678165Z","processing":{"acquirer_transaction_id":"645272142084717830381","retrieval_reference_number":"183042259107"},"response_code":"10000","response_summary":"Approved","risk":{"flagged":false,"score":0},"3ds":{"version":"2.2.0","challenged":true,"challenge_indicator":"no_preference","exemption":"none","eci":"05","cavv":"AAABAVIREQAAAAAAAAAAAAAAAAA=","xid":"74afa3ac-25d3-4d95-b815-cefbdd7c8270","downgraded":false,"enrolled":"Y","authentication_response":"Y","flow_type":"challenged"},"scheme_id":"114455763095262",
* "source":{"id":"src_ghavmefpetjellmteqwj5jjcli","type":"card","billing_address":{},"expiry_month":10,"expiry_year":2025,"scheme":"VISA","last_4":"4242","fingerprint":"BD864B08D0B098DD83052A038FD2BA967DF2D48E375AAEEF54E37BC36B385E9A","bin":"424242","card_type":"CREDIT","card_category":"CONSUMER","issuer_country":"GB","product_id":"F","product_type":"Visa Classic","avs_check":"G","cvv_check":"Y"},"balances":{"total_authorized":584168,"total_voided":0,"available_to_void":584168,"total_captured":0,"available_to_capture":584168,"total_refunded":0,"available_to_refund":0},"event_links":{"payment":"https://api.sandbox.checkout.com/payments/pay_oqwbsd22kvpuvd35y5fhbdawxa","payment_actions":"https://api.sandbox.checkout.com/payments/pay_oqwbsd22kvpuvd35y5fhbdawxa/actions","capture":"https://api.sandbox.checkout.com/payments/pay_oqwbsd22kvpuvd35y5fhbdawxa/captures","void":"https://api.sandbox.checkout.com/payments/pay_oqwbsd22kvpuvd35y5fhbdawxa/voids"}},"_links":{"self":{"href":"https://api.sandbox.checkout.com/workflows/events/evt_dli6ty4qo5vuxle5wklf5gwbwy"},"subject":{"href":"https://api.sandbox.checkout.com/workflows/events/subject/pay_oqwbsd22kvpuvd35y5fhbdawxa"},"payment":{"href":"https://api.sandbox.checkout.com/payments/pay_oqwbsd22kvpuvd35y5fhbdawxa"},"payment_actions":{"href":"https://api.sandbox.checkout.com/payments/pay_oqwbsd22kvpuvd35y5fhbdawxa/actions"},"capture":{"href":"https://api.sandbox.checkout.com/payments/pay_oqwbsd22kvpuvd35y5fhbdawxa/captures"},"void":{"href":"https://api.sandbox.checkout.com/payments/pay_oqwbsd22kvpuvd35y5fhbdawxa/voids"}}}
*/
private function paymentApproved()
{
$payment_object = $this->webhook_array['data'];
$payment = Payment::withTrashed()->where('transaction_reference', $payment_object['id'])->first();
if($payment && $payment->status_id == Payment::STATUS_COMPLETED)
return;
if($payment){
$payment->status_id = Payment::STATUS_COMPLETED;
$payment->save();
return;
}
if(isset($this->webhook_array['metadata'])) {
$metadata = $this->webhook_array['metadata'];
$payment_hash = PaymentHash::where('hash', $metadata['udf2'])->first();
$driver = $this->company_gateway->driver($payment_hash->fee_invoice->client)->init()->setPaymentMethod();
$payment_hash->data = array_merge((array) $payment_hash->data, $this->webhook_array);
$payment_hash->save();
$driver->setPaymentHash($payment_hash);
$data = [
'payment_method' => isset($this->webhook_array['source']['id']) ? $this->webhook_array['source']['id'] : '',
'payment_type' => PaymentType::CREDIT_CARD_OTHER,
'amount' => $payment_hash->data->raw_value,
'transaction_reference' => $payment_object['id'],
'gateway_type_id' => GatewayType::CREDIT_CARD,
];
$payment = $driver->createPayment($data, \App\Models\Payment::STATUS_COMPLETED);
SystemLogger::dispatch(
['response' => $this->webhook_array, 'data' => $data],
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_SUCCESS,
SystemLog::TYPE_CHECKOUT,
$payment_hash->fee_invoice->client,
$this->company_gateway->company,
);
}
}
}

View File

@ -228,7 +228,7 @@ class CreditCard implements MethodInterface
$paymentRequest->amount = $this->checkout->payment_hash->data->value;
$paymentRequest->reference = substr($this->checkout->getDescription(), 0, 49);
$paymentRequest->customer = $this->checkout->getCustomer();
$paymentRequest->metadata = ['udf1' => 'Invoice Ninja'];
$paymentRequest->metadata = ['udf1' => 'Invoice Ninja', 'udf2' => $this->checkout->payment_hash->hash];
$paymentRequest->currency = $this->checkout->client->getCurrencyCode();
$this->checkout->payment_hash->data = array_merge((array) $this->checkout->payment_hash->data, ['checkout_payment_ref' => $paymentRequest]);

View File

@ -12,12 +12,13 @@
namespace App\PaymentDrivers\CheckoutCom;
use App\Exceptions\PaymentFailed;
use App\Jobs\Util\SystemLogger;
use App\Models\GatewayType;
use App\Models\SystemLog;
use Exception;
use stdClass;
use Exception;
use App\Models\SystemLog;
use App\Models\GatewayType;
use App\Jobs\Util\SystemLogger;
use App\Exceptions\PaymentFailed;
use Checkout\Payments\PaymentType;
trait Utilities
{
@ -60,7 +61,7 @@ trait Utilities
$data = [
'payment_method' => $_payment['source']['id'],
'payment_type' => 12,
'payment_type' => \App\Models\PaymentType::CREDIT_CARD_OTHER,
'amount' => $this->getParent()->payment_hash->data->raw_value,
'transaction_reference' => $_payment['id'],
'gateway_type_id' => GatewayType::CREDIT_CARD,

View File

@ -0,0 +1,91 @@
<?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\PaymentDrivers\CheckoutCom;
use App\Exceptions\PaymentFailed;
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
use App\Jobs\Util\SystemLogger;
use App\Models\ClientGatewayToken;
use App\Models\GatewayType;
use App\Models\SystemLog;
use App\PaymentDrivers\CheckoutComPaymentDriver;
use App\PaymentDrivers\Common\MethodInterface;
use App\Utils\Traits\MakesHash;
use Checkout\CheckoutApiException;
use Checkout\CheckoutArgumentException;
use Checkout\CheckoutAuthorizationException;
use Checkout\Payments\Four\Request\PaymentRequest;
use Checkout\Payments\Four\Request\Source\RequestTokenSource;
use Checkout\Payments\PaymentRequest as PaymentsPaymentRequest;
use Checkout\Payments\Source\RequestTokenSource as SourceRequestTokenSource;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\Request;
use Illuminate\View\View;
class Webhook
{
public function __construct(public CheckoutComPaymentDriver $checkout)
{
$this->checkout = $checkout;
}
/**
* Lists all possible events in checkout and a brief description
*
* @return void
*/
public function getEventTypes()
{
try {
$response = $this->checkout->gateway->getWorkflowsClient()->getEventTypes();
return $response;
} catch (CheckoutApiException $e) {
// API error
$error_details = $e->error_details;
nlog($error_details);
$http_status_code = isset($e->http_metadata) ? $e->http_metadata->getStatusCode() : null;
} catch (CheckoutAuthorizationException $e) {
// Bad Invalid authorization
}
}
/**
* Lists the workflows in Checkout
*
* @return void
*/
public function getWorkFlows()
{
try {
$response = $this->checkout->gateway->getWorkflowsClient()->getWorkflows();
return $response;
} catch (CheckoutApiException $e) {
// API error
$error_details = $e->error_details;
$http_status_code = isset($e->http_metadata) ? $e->http_metadata->getStatusCode() : null;
} catch (CheckoutAuthorizationException $e) {
// Bad Invalid authorization
}
}
}

View File

@ -27,6 +27,7 @@ use App\Models\PaymentType;
use App\Models\SystemLog;
use App\PaymentDrivers\CheckoutCom\CreditCard;
use App\PaymentDrivers\CheckoutCom\Utilities;
use App\PaymentDrivers\CheckoutCom\CheckoutWebhook;
use App\Utils\Traits\SystemLogTrait;
use Checkout\CheckoutApi;
use Checkout\CheckoutApiException;
@ -334,7 +335,7 @@ class CheckoutComPaymentDriver extends BaseDriver
$paymentRequest->amount = $this->convertToCheckoutAmount($amount, $this->client->getCurrencyCode());
$paymentRequest->reference = '#'.$invoice->number.' - '.now();
$paymentRequest->customer = $this->getCustomer();
$paymentRequest->metadata = ['udf1' => 'Invoice Ninja'];
$paymentRequest->metadata = ['udf1' => 'Invoice Ninja', 'udf2' => $payment_hash->hash];
$paymentRequest->currency = $this->client->getCurrencyCode();
$request = new PaymentResponseRequest();
@ -421,7 +422,19 @@ class CheckoutComPaymentDriver extends BaseDriver
public function processWebhookRequest(PaymentWebhookRequest $request)
{
return true;
header('Content-Type: text/plain');
$webhook_payload = file_get_contents('php://input');
if($request->header('cko-signature') == hash_hmac('sha256', $webhook_payload, $this->company_gateway->company->company_key)) {
CheckoutWebhook::dispatch($request->all(), $request->company_key, $this->company_gateway->id)->delay(10);
}
else {
nlog("Hash Mismatch = {$request->header('cko-signature')} ".hash_hmac('sha256', $webhook_payload, $this->company_gateway->company->company_key));
nlog($request->all());
}
return response()->json(['success' => true]);
}
public function process3dsConfirmation(Checkout3dsRequest $request)
@ -440,6 +453,9 @@ class CheckoutComPaymentDriver extends BaseDriver
$request->query('cko-session-id')
);
nlog("checkout3ds");
nlog($payment);
if (isset($payment['approved']) && $payment['approved']) {
return $this->processSuccessfulPayment($payment);
} else {

View File

@ -136,7 +136,7 @@ class ClientRepository extends BaseRepository
$client->projects()->forceDelete();
$client->credits()->forceDelete();
$client->quotes()->forceDelete();
$client->activities()->forceDelete();
$client->purgeable_activities()->forceDelete();
$client->recurring_invoices()->forceDelete();
$client->expenses()->forceDelete();
$client->recurring_expenses()->forceDelete();

View File

@ -97,6 +97,10 @@ class InvoiceRepository extends BaseRepository
// reversed delete invoice actions
$invoice = $invoice->service()->handleRestore()->save();
/* If the reverse did not succeed due to rules, then do not restore / unarchive */
if($invoice->is_deleted)
return $invoice;
parent::restore($invoice);
return $invoice;

View File

@ -101,9 +101,6 @@ class TaskRepository extends BaseRepository
$key_values = array_column($time_log, 0);
array_multisort($key_values, SORT_ASC, $time_log);
// array_multisort($time_log);
// ksort($time_log);
if (isset($data['action'])) {
if ($data['action'] == 'start') {
$task->is_running = true;
@ -121,8 +118,12 @@ class TaskRepository extends BaseRepository
$task->is_running = $data['is_running'] ? 1 : 0;
}
$task->calculated_start_date = $this->harvestStartDate($time_log);
$task->time_log = json_encode($time_log);
$task->saveQuietly();
if (array_key_exists('documents', $data)) {
@ -132,6 +133,17 @@ class TaskRepository extends BaseRepository
return $task;
}
private function harvestStartDate($time_log)
{
if(isset($time_log[0][0])){
return \Carbon\Carbon::createFromTimestamp($time_log[0][0]);
}
return null;
}
/**
* Store tasks in bulk.
*
@ -199,8 +211,12 @@ class TaskRepository extends BaseRepository
if (strlen($task->time_log) < 5) {
$log = [];
$log = array_merge($log, [[time(), 0]]);
$start_time = time();
$log = array_merge($log, [[$start_time, 0]]);
$task->time_log = json_encode($log);
$task->calculated_start_date = \Carbon\Carbon::createFromTimestamp($start_time);
$task->saveQuietly();
}

View File

@ -43,7 +43,13 @@ class VendorRepository extends BaseRepository
*/
public function save(array $data, Vendor $vendor) : ?Vendor
{
$vendor->fill($data);
$saveable_vendor = $data;
if(array_key_exists('contacts', $data)) {
unset($saveable_vendor['contacts']);
}
$vendor->fill($saveable_vendor);
$vendor->saveQuietly();

View File

@ -44,6 +44,7 @@ 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();
return $this->invoice;
}
}

View File

@ -148,7 +148,7 @@ class DeletePayment
$client
->service()
->updatePaidToDate(($paymentable_credit->pivot->amount) * -1)
// ->updatePaidToDate(($paymentable_credit->pivot->amount) * -1)
->adjustCreditBalance($paymentable_credit->pivot->amount)
->save();
});

View File

@ -11,11 +11,11 @@
namespace App\Services\PurchaseOrder;
use App\Jobs\Vendor\CreatePurchaseOrderPdf;
use App\Models\PurchaseOrder;
use App\Models\VendorContact;
use App\Services\AbstractService;
use Illuminate\Support\Facades\Storage;
use App\Jobs\Vendor\CreatePurchaseOrderPdf;
class GetPurchaseOrderPdf extends AbstractService
{
@ -31,6 +31,7 @@ class GetPurchaseOrderPdf extends AbstractService
$invitation = $this->purchase_order->invitations()->where('vendor_contact_id', $this->contact->id)->first();
if (! $invitation) {
$invitation = $this->purchase_order->invitations()->first();
}

View File

@ -37,6 +37,7 @@ class TaskTransformer extends EntityTransformer
'status',
'project',
'user',
'invoice',
];
public function includeDocuments(Task $task)
@ -46,6 +47,17 @@ class TaskTransformer extends EntityTransformer
return $this->includeCollection($task->documents, $transformer, Document::class);
}
public function includeInvoice(Task $task): ?Item
{
$transformer = new InvoiceTransformer($this->serializer);
if (!$task->user) {
return null;
}
return $this->includeItem($task->invoice, $transformer, Invoice::class);
}
public function includeUser(Task $task): ?Item
{
$transformer = new UserTransformer($this->serializer);
@ -118,6 +130,7 @@ class TaskTransformer extends EntityTransformer
'status_sort_order' => (int) $task->status_sort_order, //deprecated 5.0.34
'is_date_based' => (bool) $task->is_date_based,
'status_order' => is_null($task->status_order) ? null : (int) $task->status_order,
'date' => $task->calculated_start_date ?: '',
];
}
}

View File

@ -583,8 +583,11 @@ class HtmlEngine
if ($this->settings->signature_on_pdf) {
$data['$contact.signature'] = ['value' => $this->invitation->signature_base64, 'label' => ctrans('texts.signature')];
$data['$contact.signature_date'] = ['value' => $this->translateDate($this->invitation->signature_date, $this->client->date_format(), $this->client->locale()), 'label' => ctrans('texts.date')];
} else {
$data['$contact.signature'] = ['value' => '', 'label' => ''];
$data['$contact.signature_date'] = ['value' => '', 'label' => ctrans('texts.date')];
}
$data['$thanks'] = ['value' => '', 'label' => ctrans('texts.thanks')];

View File

@ -14,6 +14,7 @@ namespace App\Utils\Traits;
use DateTime;
use DateTimeZone;
use Carbon\Carbon;
use App\Models\Company;
use App\DataMapper\Schedule\EmailStatement;
/**
@ -119,8 +120,32 @@ trait MakesDates
*
* @return array [$start_date, $end_date];
*/
public function calculateStartAndEndDates(array $data): array
public function calculateStartAndEndDates(array $data, ?Company $company = null): array
{
//override for financial years
if($data['date_range'] == 'this_year') {
$first_month_of_year = $company ? $company?->first_month_of_year : 1;
$fin_year_start = now()->createFromDate(now()->year, $first_month_of_year, 1);
if(now()->lt($fin_year_start))
$fin_year_start->subYearNoOverflow();
}
//override for financial years
if($data['date_range'] == 'last_year') {
$first_month_of_year = $company ? $company?->first_month_of_year : 1;
$fin_year_start = now()->createFromDate(now()->year, $first_month_of_year, 1);
$fin_year_start->subYearNoOverflow();
if(now()->subYear()->lt($fin_year_start)) {
$fin_year_start->subYearNoOverflow();
}
}
return match ($data['date_range']) {
EmailStatement::LAST7 => [now()->startOfDay()->subDays(7)->format('Y-m-d'), now()->startOfDay()->format('Y-m-d')],
EmailStatement::LAST30 => [now()->startOfDay()->subDays(30)->format('Y-m-d'), now()->startOfDay()->format('Y-m-d')],
@ -129,8 +154,8 @@ trait MakesDates
EmailStatement::LAST_MONTH => [now()->startOfDay()->subMonthNoOverflow()->firstOfMonth()->format('Y-m-d'), now()->startOfDay()->subMonthNoOverflow()->lastOfMonth()->format('Y-m-d')],
EmailStatement::THIS_QUARTER => [now()->startOfDay()->firstOfQuarter()->format('Y-m-d'), now()->startOfDay()->lastOfQuarter()->format('Y-m-d')],
EmailStatement::LAST_QUARTER => [now()->startOfDay()->subQuarterNoOverflow()->firstOfQuarter()->format('Y-m-d'), now()->startOfDay()->subQuarterNoOverflow()->lastOfQuarter()->format('Y-m-d')],
EmailStatement::THIS_YEAR => [now()->startOfDay()->firstOfYear()->format('Y-m-d'), now()->startOfDay()->lastOfYear()->format('Y-m-d')],
EmailStatement::LAST_YEAR => [now()->startOfDay()->subYearNoOverflow()->firstOfYear()->format('Y-m-d'), now()->startOfDay()->subYearNoOverflow()->lastOfYear()->format('Y-m-d')],
EmailStatement::THIS_YEAR => [$fin_year_start->format('Y-m-d'), $fin_year_start->copy()->addYear()->subDay()->format('Y-m-d')],
EmailStatement::LAST_YEAR => [$fin_year_start->format('Y-m-d'), $fin_year_start->copy()->addYear()->subDay()->format('Y-m-d')],
EmailStatement::CUSTOM_RANGE => [$data['start_date'], $data['end_date']],
default => [now()->startOfDay()->firstOfMonth()->format('Y-m-d'), now()->startOfDay()->lastOfMonth()->format('Y-m-d')],
};

146
composer.lock generated
View File

@ -424,16 +424,16 @@
},
{
"name": "aws/aws-sdk-php",
"version": "3.275.7",
"version": "3.276.2",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
"reference": "54dcef3349c81b46c0f5f6e54b5f9bfb5db19903"
"reference": "78c9510280512121e8fdaa9538b744778ab48bf9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/54dcef3349c81b46c0f5f6e54b5f9bfb5db19903",
"reference": "54dcef3349c81b46c0f5f6e54b5f9bfb5db19903",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/78c9510280512121e8fdaa9538b744778ab48bf9",
"reference": "78c9510280512121e8fdaa9538b744778ab48bf9",
"shasum": ""
},
"require": {
@ -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.275.7"
"source": "https://github.com/aws/aws-sdk-php/tree/3.276.2"
},
"time": "2023-07-13T18:21:04+00:00"
"time": "2023-07-20T18:16:37+00:00"
},
{
"name": "bacon/bacon-qr-code",
@ -2167,16 +2167,16 @@
},
{
"name": "firebase/php-jwt",
"version": "v6.8.0",
"version": "v6.8.1",
"source": {
"type": "git",
"url": "https://github.com/firebase/php-jwt.git",
"reference": "48b0210c51718d682e53210c24d25c5a10a2299b"
"reference": "5dbc8959427416b8ee09a100d7a8588c00fb2e26"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/firebase/php-jwt/zipball/48b0210c51718d682e53210c24d25c5a10a2299b",
"reference": "48b0210c51718d682e53210c24d25c5a10a2299b",
"url": "https://api.github.com/repos/firebase/php-jwt/zipball/5dbc8959427416b8ee09a100d7a8588c00fb2e26",
"reference": "5dbc8959427416b8ee09a100d7a8588c00fb2e26",
"shasum": ""
},
"require": {
@ -2224,9 +2224,9 @@
],
"support": {
"issues": "https://github.com/firebase/php-jwt/issues",
"source": "https://github.com/firebase/php-jwt/tree/v6.8.0"
"source": "https://github.com/firebase/php-jwt/tree/v6.8.1"
},
"time": "2023-06-20T16:45:35+00:00"
"time": "2023-07-14T18:33:00+00:00"
},
{
"name": "fruitcake/php-cors",
@ -2485,16 +2485,16 @@
},
{
"name": "google/apiclient-services",
"version": "v0.308.0",
"version": "v0.309.0",
"source": {
"type": "git",
"url": "https://github.com/googleapis/google-api-php-client-services.git",
"reference": "85cf00383e6bf6eca131bd3261b7859ea418a578"
"reference": "562f8e5ddbca68d52afc3bf47d03839e78722026"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/85cf00383e6bf6eca131bd3261b7859ea418a578",
"reference": "85cf00383e6bf6eca131bd3261b7859ea418a578",
"url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/562f8e5ddbca68d52afc3bf47d03839e78722026",
"reference": "562f8e5ddbca68d52afc3bf47d03839e78722026",
"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.308.0"
"source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.309.0"
},
"time": "2023-07-09T01:06:13+00:00"
"time": "2023-07-16T01:08:14+00:00"
},
{
"name": "google/auth",
@ -4554,16 +4554,16 @@
},
{
"name": "laravel/socialite",
"version": "v5.7.0",
"version": "v5.8.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/socialite.git",
"reference": "f5996f499e14db15407201a6bfbaba3ce6ce736c"
"reference": "50148edf24b6cd3e428aa9bc06a5d915b24376bb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/socialite/zipball/f5996f499e14db15407201a6bfbaba3ce6ce736c",
"reference": "f5996f499e14db15407201a6bfbaba3ce6ce736c",
"url": "https://api.github.com/repos/laravel/socialite/zipball/50148edf24b6cd3e428aa9bc06a5d915b24376bb",
"reference": "50148edf24b6cd3e428aa9bc06a5d915b24376bb",
"shasum": ""
},
"require": {
@ -4620,7 +4620,7 @@
"issues": "https://github.com/laravel/socialite/issues",
"source": "https://github.com/laravel/socialite"
},
"time": "2023-07-08T20:51:43+00:00"
"time": "2023-07-14T14:22:58+00:00"
},
{
"name": "laravel/tinker",
@ -5716,16 +5716,16 @@
},
{
"name": "microsoft/microsoft-graph",
"version": "1.102.0",
"version": "1.103.0",
"source": {
"type": "git",
"url": "https://github.com/microsoftgraph/msgraph-sdk-php.git",
"reference": "4b450b06ac9df3868bbdbddb31bfcc4595f643bc"
"reference": "6e325c22145dbed4e51970ffecca4d7648c3c27e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/microsoftgraph/msgraph-sdk-php/zipball/4b450b06ac9df3868bbdbddb31bfcc4595f643bc",
"reference": "4b450b06ac9df3868bbdbddb31bfcc4595f643bc",
"url": "https://api.github.com/repos/microsoftgraph/msgraph-sdk-php/zipball/6e325c22145dbed4e51970ffecca4d7648c3c27e",
"reference": "6e325c22145dbed4e51970ffecca4d7648c3c27e",
"shasum": ""
},
"require": {
@ -5762,22 +5762,22 @@
"homepage": "https://developer.microsoft.com/en-us/graph",
"support": {
"issues": "https://github.com/microsoftgraph/msgraph-sdk-php/issues",
"source": "https://github.com/microsoftgraph/msgraph-sdk-php/tree/1.102.0"
"source": "https://github.com/microsoftgraph/msgraph-sdk-php/tree/1.103.0"
},
"time": "2023-07-05T13:07:19+00:00"
"time": "2023-07-19T03:27:15+00:00"
},
{
"name": "mollie/mollie-api-php",
"version": "v2.58.0",
"version": "v2.59.0",
"source": {
"type": "git",
"url": "https://github.com/mollie/mollie-api-php.git",
"reference": "5120e5b3e4622a290b64acf87266ea47d10d7301"
"reference": "9834e5779c695d1cc278b2c78ee514de9434b084"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mollie/mollie-api-php/zipball/5120e5b3e4622a290b64acf87266ea47d10d7301",
"reference": "5120e5b3e4622a290b64acf87266ea47d10d7301",
"url": "https://api.github.com/repos/mollie/mollie-api-php/zipball/9834e5779c695d1cc278b2c78ee514de9434b084",
"reference": "9834e5779c695d1cc278b2c78ee514de9434b084",
"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.58.0"
"source": "https://github.com/mollie/mollie-api-php/tree/v2.59.0"
},
"time": "2023-07-11T12:01:27+00:00"
"time": "2023-07-18T13:41:40+00:00"
},
{
"name": "moneyphp/money",
@ -8514,16 +8514,16 @@
},
{
"name": "psy/psysh",
"version": "v0.11.18",
"version": "v0.11.19",
"source": {
"type": "git",
"url": "https://github.com/bobthecow/psysh.git",
"reference": "4f00ee9e236fa6a48f4560d1300b9c961a70a7ec"
"reference": "1724ceff278daeeac5a006744633bacbb2dc4706"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/bobthecow/psysh/zipball/4f00ee9e236fa6a48f4560d1300b9c961a70a7ec",
"reference": "4f00ee9e236fa6a48f4560d1300b9c961a70a7ec",
"url": "https://api.github.com/repos/bobthecow/psysh/zipball/1724ceff278daeeac5a006744633bacbb2dc4706",
"reference": "1724ceff278daeeac5a006744633bacbb2dc4706",
"shasum": ""
},
"require": {
@ -8584,9 +8584,9 @@
],
"support": {
"issues": "https://github.com/bobthecow/psysh/issues",
"source": "https://github.com/bobthecow/psysh/tree/v0.11.18"
"source": "https://github.com/bobthecow/psysh/tree/v0.11.19"
},
"time": "2023-05-23T02:31:11+00:00"
"time": "2023-07-15T19:42:19+00:00"
},
{
"name": "pusher/pusher-php-server",
@ -15188,16 +15188,16 @@
},
{
"name": "friendsofphp/php-cs-fixer",
"version": "v3.21.1",
"version": "v3.22.0",
"source": {
"type": "git",
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
"reference": "229b55b3eae4729a8e2a321441ba40fcb3720b86"
"reference": "92b019f6c8d79aa26349d0db7671d37440dc0ff3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/229b55b3eae4729a8e2a321441ba40fcb3720b86",
"reference": "229b55b3eae4729a8e2a321441ba40fcb3720b86",
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/92b019f6c8d79aa26349d0db7671d37440dc0ff3",
"reference": "92b019f6c8d79aa26349d0db7671d37440dc0ff3",
"shasum": ""
},
"require": {
@ -15207,7 +15207,7 @@
"doctrine/lexer": "^2 || ^3",
"ext-json": "*",
"ext-tokenizer": "*",
"php": "^8.0.1",
"php": "^7.4 || ^8.0",
"sebastian/diff": "^4.0 || ^5.0",
"symfony/console": "^5.4 || ^6.0",
"symfony/event-dispatcher": "^5.4 || ^6.0",
@ -15221,6 +15221,7 @@
"symfony/stopwatch": "^5.4 || ^6.0"
},
"require-dev": {
"facile-it/paraunit": "^1.3 || ^2.0",
"justinrainbow/json-schema": "^5.2",
"keradus/cli-executor": "^2.0",
"mikey179/vfsstream": "^1.6.11",
@ -15272,7 +15273,7 @@
],
"support": {
"issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.21.1"
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.22.0"
},
"funding": [
{
@ -15280,7 +15281,7 @@
"type": "github"
}
],
"time": "2023-07-05T21:50:25+00:00"
"time": "2023-07-16T23:08:06+00:00"
},
{
"name": "hamcrest/hamcrest-php",
@ -15460,37 +15461,33 @@
},
{
"name": "mockery/mockery",
"version": "1.6.2",
"version": "1.6.4",
"source": {
"type": "git",
"url": "https://github.com/mockery/mockery.git",
"reference": "13a7fa2642c76c58fa2806ef7f565344c817a191"
"reference": "d1413755e26fe56a63455f7753221c86cbb88f66"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mockery/mockery/zipball/13a7fa2642c76c58fa2806ef7f565344c817a191",
"reference": "13a7fa2642c76c58fa2806ef7f565344c817a191",
"url": "https://api.github.com/repos/mockery/mockery/zipball/d1413755e26fe56a63455f7753221c86cbb88f66",
"reference": "d1413755e26fe56a63455f7753221c86cbb88f66",
"shasum": ""
},
"require": {
"hamcrest/hamcrest-php": "^2.0.1",
"lib-pcre": ">=7.0",
"php": "^7.4 || ^8.0"
"php": ">=7.4,<8.3"
},
"conflict": {
"phpunit/phpunit": "<8.0"
},
"require-dev": {
"phpunit/phpunit": "^8.5 || ^9.3",
"psalm/plugin-phpunit": "^0.18",
"vimeo/psalm": "^5.9"
"psalm/plugin-phpunit": "^0.18.4",
"symplify/easy-coding-standard": "^11.5.0",
"vimeo/psalm": "^5.13.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.6.x-dev"
}
},
"autoload": {
"files": [
"library/helpers.php",
@ -15508,12 +15505,20 @@
{
"name": "Pádraic Brady",
"email": "padraic.brady@gmail.com",
"homepage": "http://blog.astrumfutura.com"
"homepage": "https://github.com/padraic",
"role": "Author"
},
{
"name": "Dave Marshall",
"email": "dave.marshall@atstsolutions.co.uk",
"homepage": "http://davedevelopment.co.uk"
"homepage": "https://davedevelopment.co.uk",
"role": "Developer"
},
{
"name": "Nathanael Esayeas",
"email": "nathanael.esayeas@protonmail.com",
"homepage": "https://github.com/ghostwriter",
"role": "Lead Developer"
}
],
"description": "Mockery is a simple yet flexible PHP mock object framework",
@ -15531,10 +15536,13 @@
"testing"
],
"support": {
"docs": "https://docs.mockery.io/",
"issues": "https://github.com/mockery/mockery/issues",
"source": "https://github.com/mockery/mockery/tree/1.6.2"
"rss": "https://github.com/mockery/mockery/releases.atom",
"security": "https://github.com/mockery/mockery/security/advisories",
"source": "https://github.com/mockery/mockery"
},
"time": "2023-06-07T09:07:52+00:00"
"time": "2023-07-19T15:51:02+00:00"
},
{
"name": "myclabs/deep-copy",
@ -16140,16 +16148,16 @@
},
{
"name": "phpstan/phpstan",
"version": "1.10.25",
"version": "1.10.26",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "578f4e70d117f9a90699324c555922800ac38d8c"
"reference": "5d660cbb7e1b89253a47147ae44044f49832351f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/578f4e70d117f9a90699324c555922800ac38d8c",
"reference": "578f4e70d117f9a90699324c555922800ac38d8c",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/5d660cbb7e1b89253a47147ae44044f49832351f",
"reference": "5d660cbb7e1b89253a47147ae44044f49832351f",
"shasum": ""
},
"require": {
@ -16198,7 +16206,7 @@
"type": "tidelift"
}
],
"time": "2023-07-06T12:11:37+00:00"
"time": "2023-07-19T12:44:37+00:00"
},
{
"name": "phpunit/php-code-coverage",

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.19'),
'app_tag' => env('APP_TAG','5.6.19'),
'app_version' => env('APP_VERSION','5.6.20'),
'app_tag' => env('APP_TAG','5.6.20'),
'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', ''),

View File

@ -0,0 +1,33 @@
<?php
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()
{
Schema::table('tasks', function (Blueprint $table) {
$table->date('calculated_start_date')->nullable();
});
Schema::table('webhooks', function (Blueprint $table){
$table->text('target_url')->change();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
}
};

View File

@ -4,9 +4,9 @@ includes:
parameters:
treatPhpDocTypesAsCertain: false
parallel:
jobSize: 5
maximumNumberOfProcesses: 16
processTimeout: 600.0
jobSize: 10
maximumNumberOfProcesses: 1
processTimeout: 60.0
ignoreErrors:
- '#Call to an undefined method .*badMethod\(\)#'
- '#Call to an undefined method Illuminate\Database\Eloquent\Builder::exclude#'

View File

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

347136
public/main.dart.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

354460
public/main.foss.dart.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -86,11 +86,16 @@ span {
{{ $product['cost'] }}
@endif
</p>
<p class="overflow-ellipsis overflow-hidden px-1 mb-2">{{ $product['notes'] }}</p>
<p class="overflow-ellipsis overflow-hidden px-1 mb-2">{!! $product['notes'] !!}</p>
</div>
</div>
</td>
<td style="text-align:right; padding-right:2px;">{{ $product['line_total'] }}</td>
<td style="text-align:right; padding-right:2px;">
@if($show_line_total)
{{ $product['line_total'] }}
@endif
</td>
</tr>
@endforeach
</tbody>
@ -101,19 +106,19 @@ span {
<div id="task-details" class="py-6 mr-3 ml-3">
<table width="100%">
<thead>
<tr class="border-bottom">
<tr class="border-b-2">
<th style="text-align:left; width:70%; padding-left:2px;">Service</th>
<th style="text-align:right; width:30%; padding-right:2px;">Amount</th>
</tr>
</thead>
<tbody>
@foreach($services as $service)
<tr style="display: table-row;">
<tr style="display: table-row;" class="border-b-2">
<td>
<div class="">
<div class="">
<p class="mt-2">{{ $service['quantity'] }} × {{ $service['cost'] }}</p>
<p class="overflow-ellipsis overflow-hidden px-1 mb-2">{{ $service['notes'] }}</p>
<p class="overflow-ellipsis overflow-hidden px-1 mb-2">{!! $service['notes'] !!}</p>
</div>
</div>
</td>

View File

@ -1,24 +1,19 @@
<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">
<div class="flex flex-col items-end mb-2" x-data>
<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" type="button">
<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> -->
<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>
<div class="flex mt-4 place-items-center" id="loader" wire:ignore>
<span class="loader m-auto" wire:ignore></span>
<style type="text/css">
.loader {
width: 48px;
@ -54,6 +49,9 @@
}
</style>
</div>
@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>
@endif
</div>
</div>
@ -62,3 +60,41 @@
@include('portal.ninja2020.components.html-viewer')
</div>
</div>
<script type="text/javascript">
waitForElement("#pdf-iframe", 0).then(function(){
const iframe = document.getElementById("pdf-iframe");
iframe.addEventListener("load", function () {
const loader = document.getElementById("loader")
loader.classList.add("hidden");
});
});
function waitForElement(querySelector, timeout){
return new Promise((resolve, reject)=>{
var timer = false;
if(document.querySelectorAll(querySelector).length) return resolve();
const observer = new MutationObserver(()=>{
if(document.querySelectorAll(querySelector).length){
observer.disconnect();
if(timer !== false) clearTimeout(timer);
return resolve();
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
if(timeout) timer = setTimeout(()=>{
observer.disconnect();
reject();
}, timeout);
});
}
</script>

View File

@ -143,6 +143,7 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale']
Route::get('health_check', [PingController::class, 'health'])->name('health_check');
Route::get('activities', [ActivityController::class, 'index']);
Route::post('activities/entity', [ActivityController::class, 'entityActivity']);
Route::get('activities/download_entity/{activity}', [ActivityController::class, 'downloadHistoricalEntity']);
Route::post('charts/totals', [ChartController::class, 'totals'])->name('chart.totals');
@ -406,6 +407,6 @@ Route::post('api/v1/yodlee/data_updates', [YodleeController::class, 'dataUpdates
Route::post('api/v1/yodlee/refresh_updates', [YodleeController::class, 'refreshUpdatesWebhook'])->middleware('throttle:100,1');
Route::post('api/v1/yodlee/balance', [YodleeController::class, 'balanceWebhook'])->middleware('throttle:100,1');
Route::get('api/v1/protected_download/{hash}', [ProtectedDownloadController::class, 'index'])->name('protected_download')->middleware('signed')->middleware('throttle:300,1');
Route::get('api/v1/protected_download/{hash}', [ProtectedDownloadController::class, 'index'])->name('protected_download')->middleware('throttle:300,1');
Route::fallback([BaseController::class, 'notFound'])->middleware('throttle:404');

View File

@ -11,10 +11,11 @@
namespace Tests\Feature;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Routing\Middleware\ThrottleRequests;
use Tests\MockAccountData;
use Tests\TestCase;
use Tests\MockAccountData;
use Illuminate\Validation\ValidationException;
use Illuminate\Routing\Middleware\ThrottleRequests;
use Illuminate\Foundation\Testing\DatabaseTransactions;
/**
* @test
@ -34,6 +35,38 @@ class ActivityApiTest extends TestCase
$this->withoutMiddleware(
ThrottleRequests::class
);
$this->withoutExceptionHandling();
}
public function testActivityEntity()
{
$invoice = $this->company->invoices()->first();
$invoice->service()->markSent()->markPaid()->markDeleted()->handleRestore()->save();
$data = [
'entity' => 'invoice',
'entity_id' => $invoice->hashed_id
];
$response = false;
try {
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->postJson('/api/v1/activities/entity', $data);
} catch (ValidationException $e) {
$message = json_decode($e->validator->getMessageBag(), 1);
nlog($message);
}
$response->assertStatus(200);
}
public function testActivityGet()

View File

@ -33,6 +33,8 @@ class CreditsTest extends TestCase
use DatabaseTransactions;
use AppSetup;
private $faker;
protected function setUp(): void
{
parent::setUp();

File diff suppressed because it is too large Load Diff

View File

@ -11,20 +11,21 @@
namespace Tests\Feature;
use Tests\TestCase;
use App\Models\Client;
use App\Models\ClientContact;
use App\Models\Credit;
use App\Models\Invoice;
use App\Models\Payment;
use Tests\MockAccountData;
use App\Models\ClientContact;
use App\Utils\Traits\MakesHash;
use App\Factory\InvoiceItemFactory;
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;
use Illuminate\Foundation\Testing\WithoutEvents;
use Illuminate\Routing\Middleware\ThrottleRequests;
use Illuminate\Foundation\Testing\DatabaseTransactions;
/**
* @test
@ -189,4 +190,172 @@ class PaymentV2Test extends TestCase
$this->assertEquals(20, $client->fresh()->paid_to_date);
}
public function testStorePaymentWithCreditsThenDeletingInvoicesAndThenPayments()
{
$client = Client::factory()->create(['company_id' =>$this->company->id, 'user_id' => $this->user->id, 'balance' => 100, 'paid_to_date' => 0]);
ClientContact::factory()->create([
'user_id' => $this->user->id,
'client_id' => $client->id,
'company_id' => $this->company->id,
'is_primary' => 1,
]);
$line_items = [];
$item = InvoiceItemFactory::create();
$item->quantity = 1;
$item->cost = 100;
$line_items[] = $item;
$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' => 100,
'balance' => 100,
'discount' => 0,
'number' => uniqid("st", true),
'line_items' => $line_items
]);
$this->assertEquals(100, $client->balance);
$this->assertEquals(0, $client->paid_to_date);
$this->assertEquals(100, $invoice->amount);
$this->assertEquals(100, $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' => 100,
],
],
'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));
$credit = $credit->fresh();
$this->assertNotNull($payment);
$this->assertNotNull($payment->invoices());
$this->assertEquals(1, $payment->invoices()->count());
$this->assertEquals(80, $payment->amount);
$this->assertEquals(0, $client->fresh()->balance);
$this->assertEquals(100, $client->fresh()->paid_to_date);
$this->assertEquals(0, $credit->balance);
$invoice = $invoice->fresh();
//delete the invoice
$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);
$payment = $payment->fresh();
$invoice = $invoice->fresh();
$this->assertTrue($invoice->is_deleted);
$this->assertFalse($payment->is_deleted);
$data = [
'action' => 'delete',
'ids' => [
$payment->hashed_id,
],
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/payments/bulk', $data);
$payment = $payment->fresh();
$this->assertTrue($payment->is_deleted);
$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);
$response->assertStatus(200);
$invoice = $invoice->fresh();
$this->assertTrue($invoice->is_deleted);
$this->assertTrue($invoice->trashed());
$client = $client->fresh();
$credit = $credit->fresh();
$this->assertEquals(0, $client->balance);
$this->assertEquals(0, $client->paid_to_date);
// $this->assertEquals(20, $client->credit_balance);
$this->assertEquals(20, $credit->balance);
}
}

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