Merge pull request #8653 from turbo124/v5-develop

Adjustments for restoring a deleted invoice with a deleted payment
This commit is contained in:
David Bomba 2023-07-19 08:50:22 +10:00 committed by GitHub
commit d08a8d74c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
64 changed files with 2115 additions and 339 deletions

View File

@ -24,8 +24,7 @@ use App\Transformers\ActivityTransformer;
class ActivityExport extends BaseExport class ActivityExport extends BaseExport
{ {
private Company $company;
private $entity_transformer; private $entity_transformer;
public string $date_key = 'created_at'; public string $date_key = 'created_at';

View File

@ -13,14 +13,14 @@ namespace App\Export\CSV;
use App\Utils\Number; use App\Utils\Number;
use App\Models\Client; use App\Models\Client;
use App\Models\Company;
use App\Models\Expense; use App\Models\Expense;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\GatewayType;
use App\Models\Payment; use App\Models\Payment;
use League\Fractal\Manager; use League\Fractal\Manager;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use App\Transformers\ClientTransformer; use App\Transformers\TaskTransformer;
use App\Transformers\PaymentTransformer; use App\Transformers\PaymentTransformer;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use League\Fractal\Serializer\ArraySerializer; use League\Fractal\Serializer\ArraySerializer;
@ -29,6 +29,8 @@ class BaseExport
{ {
use MakesHash; use MakesHash;
public Company $company;
public array $input; public array $input;
public string $date_key = ''; public string $date_key = '';
@ -136,6 +138,35 @@ class BaseExport
"user" => "invoice.user_id", "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 = [ protected array $purchase_order_report_keys = [
'amount' => 'purchase_order.amount', 'amount' => 'purchase_order.amount',
'balance' => 'purchase_order.balance', 'balance' => 'purchase_order.balance',
@ -193,13 +224,17 @@ class BaseExport
]; ];
protected array $quote_report_keys = [ 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", "amount" => "quote.amount",
"balance" => "quote.balance", "balance" => "quote.balance",
"paid_to_date" => "quote.paid_to_date", "paid_to_date" => "quote.paid_to_date",
"po_number" => "quote.po_number", "po_number" => "quote.po_number",
"date" => "quote.date", "date" => "quote.date",
"due_date" => "quote.due_date", "valid_until" => "quote.due_date",
"terms" => "quote.terms", "terms" => "quote.terms",
"footer" => "quote.footer", "footer" => "quote.footer",
"status" => "quote.status", "status" => "quote.status",
@ -314,8 +349,6 @@ class BaseExport
'custom_value4' => 'task.custom_value4', 'custom_value4' => 'task.custom_value4',
'status' => 'task.status_id', 'status' => 'task.status_id',
'project' => 'task.project_id', 'project' => 'task.project_id',
'invoice' => 'task.invoice_id',
'client' => 'task.client_id',
]; ];
protected function filterByClients($query) protected function filterByClients($query)
@ -347,8 +380,11 @@ class BaseExport
'vendor' => $value = $this->resolveVendorKey($parts[1], $entity, $transformer), 'vendor' => $value = $this->resolveVendorKey($parts[1], $entity, $transformer),
'vendor_contact' => $value = $this->resolveVendorContactKey($parts[1], $entity, $transformer), 'vendor_contact' => $value = $this->resolveVendorContactKey($parts[1], $entity, $transformer),
'invoice' => $value = $this->resolveInvoiceKey($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), 'purchase_order' => $value = $this->resolvePurchaseOrderKey($parts[1], $entity, $transformer),
'payment' => $value = $this->resolvePaymentKey($parts[1], $entity, $transformer), 'payment' => $value = $this->resolvePaymentKey($parts[1], $entity, $transformer),
'task' => $value = $this->resolveTaskKey($parts[1], $entity, $transformer),
default => $value = '' 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) private function resolveVendorKey($column, $entity, $transformer)
{ {
@ -511,6 +563,20 @@ class BaseExport
return ''; 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) private function resolveInvoiceKey($column, $entity, $transformer)
{ {
nlog("searching for {$column}"); 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') if($column == 'status')
return $entity->stringStatus($entity->status_id); return $entity->stringStatus($entity->status_id);
@ -707,8 +789,27 @@ class BaseExport
$this->end_date = now()->startOfDay()->format('Y-m-d'); $this->end_date = now()->startOfDay()->format('Y-m-d');
return $query->whereBetween($this->date_key, [now()->subDays(365), now()])->orderBy($this->date_key, 'ASC'); return $query->whereBetween($this->date_key, [now()->subDays(365), now()])->orderBy($this->date_key, 'ASC');
case 'this_year': 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'); return $query->whereBetween($this->date_key, [now()->startOfYear(), now()])->orderBy($this->date_key, 'ASC');
case 'custom': case 'custom':
$this->start_date = $custom_start_date->format('Y-m-d'); $this->start_date = $custom_start_date->format('Y-m-d');
@ -743,6 +844,11 @@ class BaseExport
$key = array_search($value, $this->invoice_report_keys); $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) { if(!$key) {
$prefix = ctrans('texts.payment')." "; $prefix = ctrans('texts.payment')." ";
$key = array_search($value, $this->payment_report_keys); $key = array_search($value, $this->payment_report_keys);
@ -790,6 +896,7 @@ class BaseExport
$key = str_replace('item.', '', $key); $key = str_replace('item.', '', $key);
$key = str_replace('recurring_invoice.', '', $key); $key = str_replace('recurring_invoice.', '', $key);
$key = str_replace('purchase_order.', '', $key);
$key = str_replace('invoice.', '', $key); $key = str_replace('invoice.', '', $key);
$key = str_replace('quote.', '', $key); $key = str_replace('quote.', '', $key);
$key = str_replace('credit.', '', $key); $key = str_replace('credit.', '', $key);
@ -800,9 +907,18 @@ class BaseExport
$key = str_replace('payment.', '', $key); $key = str_replace('payment.', '', $key);
$key = str_replace('expense.', '', $key); $key = str_replace('expense.', '', $key);
$header[] = "{$prefix}" . ctrans("texts.{$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; return $header;
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -22,7 +22,6 @@ use League\Csv\Writer;
class PurchaseOrderExport extends BaseExport class PurchaseOrderExport extends BaseExport
{ {
private Company $company;
private $purchase_order_transformer; private $purchase_order_transformer;
@ -31,39 +30,39 @@ class PurchaseOrderExport extends BaseExport
public Writer $csv; public Writer $csv;
public array $entity_keys = [ public array $entity_keys = [
'amount' => 'amount', 'amount' => 'purchase_order.amount',
'balance' => 'balance', 'balance' => 'purchase_order.balance',
'vendor' => 'vendor_id', 'vendor' => 'purchase_order.vendor_id',
'custom_surcharge1' => 'custom_surcharge1', // 'custom_surcharge1' => 'purchase_order.custom_surcharge1',
'custom_surcharge2' => 'custom_surcharge2', // 'custom_surcharge2' => 'purchase_order.custom_surcharge2',
'custom_surcharge3' => 'custom_surcharge3', // 'custom_surcharge3' => 'purchase_order.custom_surcharge3',
'custom_surcharge4' => 'custom_surcharge4', // 'custom_surcharge4' => 'purchase_order.custom_surcharge4',
'custom_value1' => 'custom_value1', 'custom_value1' => 'purchase_order.custom_value1',
'custom_value2' => 'custom_value2', 'custom_value2' => 'purchase_order.custom_value2',
'custom_value3' => 'custom_value3', 'custom_value3' => 'purchase_order.custom_value3',
'custom_value4' => 'custom_value4', 'custom_value4' => 'purchase_order.custom_value4',
'date' => 'date', 'date' => 'purchase_order.date',
'discount' => 'discount', 'discount' => 'purchase_order.discount',
'due_date' => 'due_date', 'due_date' => 'purchase_order.due_date',
'exchange_rate' => 'exchange_rate', 'exchange_rate' => 'purchase_order.exchange_rate',
'footer' => 'footer', 'footer' => 'purchase_order.footer',
'number' => 'number', 'number' => 'purchase_order.number',
'paid_to_date' => 'paid_to_date', 'paid_to_date' => 'purchase_order.paid_to_date',
'partial' => 'partial', 'partial' => 'purchase_order.partial',
'partial_due_date' => 'partial_due_date', 'partial_due_date' => 'purchase_order.partial_due_date',
'po_number' => 'po_number', 'po_number' => 'purchase_order.po_number',
'private_notes' => 'private_notes', 'private_notes' => 'purchase_order.private_notes',
'public_notes' => 'public_notes', 'public_notes' => 'purchase_order.public_notes',
'status' => 'status_id', 'status' => 'purchase_order.status_id',
'tax_name1' => 'tax_name1', 'tax_name1' => 'purchase_order.tax_name1',
'tax_name2' => 'tax_name2', 'tax_name2' => 'purchase_order.tax_name2',
'tax_name3' => 'tax_name3', 'tax_name3' => 'purchase_order.tax_name3',
'tax_rate1' => 'tax_rate1', 'tax_rate1' => 'purchase_order.tax_rate1',
'tax_rate2' => 'tax_rate2', 'tax_rate2' => 'purchase_order.tax_rate2',
'tax_rate3' => 'tax_rate3', 'tax_rate3' => 'purchase_order.tax_rate3',
'terms' => 'terms', 'terms' => 'purchase_order.terms',
'total_taxes' => 'total_taxes', 'total_taxes' => 'purchase_order.total_taxes',
'currency_id' => 'currency_id', 'currency_id' => 'purchase_order.currency_id',
]; ];
private array $decorate_keys = [ private array $decorate_keys = [
@ -130,7 +129,7 @@ class PurchaseOrderExport extends BaseExport
$keyval = array_search($key, $this->entity_keys); $keyval = array_search($key, $this->entity_keys);
if(!$keyval) { 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) { if(!$keyval) {

View File

@ -21,7 +21,6 @@ use League\Csv\Writer;
class PurchaseOrderItemExport extends BaseExport class PurchaseOrderItemExport extends BaseExport
{ {
private Company $company;
private $purchase_order_transformer; private $purchase_order_transformer;
@ -37,10 +36,10 @@ class PurchaseOrderItemExport extends BaseExport
'vendor' => 'vendor_id', 'vendor' => 'vendor_id',
'vendor_number' => 'vendor.number', 'vendor_number' => 'vendor.number',
'vendor_id_number' => 'vendor.id_number', 'vendor_id_number' => 'vendor.id_number',
'custom_surcharge1' => 'custom_surcharge1', // 'custom_surcharge1' => 'custom_surcharge1',
'custom_surcharge2' => 'custom_surcharge2', // 'custom_surcharge2' => 'custom_surcharge2',
'custom_surcharge3' => 'custom_surcharge3', // 'custom_surcharge3' => 'custom_surcharge3',
'custom_surcharge4' => 'custom_surcharge4', // 'custom_surcharge4' => 'custom_surcharge4',
// 'custom_value1' => 'custom_value1', // 'custom_value1' => 'custom_value1',
// 'custom_value2' => 'custom_value2', // 'custom_value2' => 'custom_value2',
// 'custom_value3' => 'custom_value3', // 'custom_value3' => 'custom_value3',
@ -82,10 +81,10 @@ class PurchaseOrderItemExport extends BaseExport
'tax_name3' => 'item.tax_name3', 'tax_name3' => 'item.tax_name3',
'line_total' => 'item.line_total', 'line_total' => 'item.line_total',
'gross_line_total' => 'item.gross_line_total', 'gross_line_total' => 'item.gross_line_total',
// 'invoice1' => 'item.custom_value1', 'purchase_order1' => 'item.custom_value1',
// 'invoice2' => 'item.custom_value2', 'purchase_order2' => 'item.custom_value2',
// 'invoice3' => 'item.custom_value3', 'purchase_order3' => 'item.custom_value3',
// 'invoice4' => 'item.custom_value4', 'purchase_order4' => 'item.custom_value4',
'tax_category' => 'item.tax_id', 'tax_category' => 'item.tax_id',
'type' => 'item.type_id', 'type' => 'item.type_id',
]; ];
@ -139,7 +138,7 @@ class PurchaseOrderItemExport extends BaseExport
private function iterateItems(PurchaseOrder $purchase_order) private function iterateItems(PurchaseOrder $purchase_order)
{ {
$transformed_invoice = $this->buildRow($purchase_order); $transformed_purchase_order = $this->buildRow($purchase_order);
$transformed_items = []; $transformed_items = [];
@ -154,7 +153,7 @@ class PurchaseOrderItemExport extends BaseExport
$keyval = $key; $keyval = $key;
$keyval = str_replace("custom_value", "invoice", $key); $keyval = str_replace("custom_value", "purchase_order", $key);
if($key == 'type_id') { if($key == 'type_id') {
$keyval = 'type'; $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); $entity = $this->decorateAdvancedFields($purchase_order, $transformed_items);
$this->csv->insertOne($entity); $this->csv->insertOne($entity);
@ -193,7 +192,7 @@ class PurchaseOrderItemExport extends BaseExport
private function buildRow(PurchaseOrder $purchase_order) :array 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 = []; $entity = [];
@ -201,17 +200,17 @@ class PurchaseOrderItemExport extends BaseExport
$keyval = array_search($key, $this->entity_keys); $keyval = array_search($key, $this->entity_keys);
if(!$keyval) { 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) { if(!$keyval) {
$keyval = $key; $keyval = $key;
} }
if (array_key_exists($key, $transformed_invoice)) { if (array_key_exists($key, $transformed_purchase_order)) {
$entity[$keyval] = $transformed_invoice[$key]; $entity[$keyval] = $transformed_purchase_order[$key];
} elseif (array_key_exists($keyval, $transformed_invoice)) { } elseif (array_key_exists($keyval, $transformed_purchase_order)) {
$entity[$keyval] = $transformed_invoice[$keyval]; $entity[$keyval] = $transformed_purchase_order[$keyval];
} else { } else {
$entity[$keyval] = $this->resolveKey($keyval, $purchase_order, $this->purchase_order_transformer); $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 class QuoteExport extends BaseExport
{ {
private Company $company;
private $quote_transformer; private $quote_transformer;
@ -43,7 +42,7 @@ class QuoteExport extends BaseExport
'custom_value4' => 'custom_value4', 'custom_value4' => 'custom_value4',
'date' => 'date', 'date' => 'date',
'discount' => 'discount', 'discount' => 'discount',
'due_date' => 'due_date', 'valid_until' => 'due_date',
'exchange_rate' => 'exchange_rate', 'exchange_rate' => 'exchange_rate',
'footer' => 'footer', 'footer' => 'footer',
'number' => 'number', 'number' => 'number',
@ -115,17 +114,28 @@ class QuoteExport extends BaseExport
private function buildRow(Quote $quote) :array private function buildRow(Quote $quote) :array
{ {
$transformed_quote = $this->quote_transformer->transform($quote); $transformed_entity = $this->quote_transformer->transform($quote);
$entity = []; $entity = [];
foreach (array_values($this->input['report_keys']) as $key) { foreach (array_values($this->input['report_keys']) as $key) {
$keyval = array_search($key, $this->entity_keys); $keyval = array_search($key, $this->entity_keys);
if (array_key_exists($key, $transformed_quote)) { if(!$keyval) {
$entity[$keyval] = $transformed_quote[$key]; $keyval = array_search(str_replace("invoice.", "", $key), $this->entity_keys) ?? $key;
} else { }
$entity[$keyval] = '';
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 class QuoteItemExport extends BaseExport
{ {
private Company $company;
private $quote_transformer; private $quote_transformer;
@ -63,10 +62,11 @@ class QuoteItemExport extends BaseExport
'terms' => 'terms', 'terms' => 'terms',
'total_taxes' => 'total_taxes', 'total_taxes' => 'total_taxes',
'currency' => 'currency_id', 'currency' => 'currency_id',
'qty' => 'item.quantity', 'quantity' => 'item.quantity',
'unit_cost' => 'item.cost', 'cost' => 'item.cost',
'product_key' => 'item.product_key', 'product_key' => 'item.product_key',
'cost' => 'item.product_cost', 'buy_price' => 'item.product_cost',
'cost' => 'item.cost',
'notes' => 'item.notes', 'notes' => 'item.notes',
'discount' => 'item.discount', 'discount' => 'item.discount',
'is_amount_discount' => 'item.is_amount_discount', 'is_amount_discount' => 'item.is_amount_discount',
@ -77,11 +77,13 @@ class QuoteItemExport extends BaseExport
'tax_name2' => 'item.tax_name2', 'tax_name2' => 'item.tax_name2',
'tax_name3' => 'item.tax_name3', 'tax_name3' => 'item.tax_name3',
'line_total' => 'item.line_total', 'line_total' => 'item.line_total',
// 'gross_line_total' => 'item.gross_line_total', 'gross_line_total' => 'item.gross_line_total',
'custom_value1' => 'item.custom_value1', 'quote1' => 'item.custom_value1',
'custom_value2' => 'item.custom_value2', 'quote2' => 'item.custom_value2',
'custom_value3' => 'item.custom_value3', 'quote3' => 'item.custom_value3',
'custom_value4' => 'item.custom_value4', 'quote4' => 'item.custom_value4',
'tax_category' => 'item.tax_id',
'type' => 'item.type_id',
]; ];
private array $decorate_keys = [ private array $decorate_keys = [
@ -135,25 +137,44 @@ class QuoteItemExport extends BaseExport
$transformed_items = []; $transformed_items = [];
foreach ($quote->line_items as $item) { $transformed_items = [];
$item_array = [];
foreach (array_values($this->input['report_keys']) as $key) { foreach ($quote->line_items as $item) {
if (str_contains($key, 'item.')) { $item_array = [];
$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 = []; $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); $keyval = array_search($key, $this->entity_keys);
if (array_key_exists($key, $transformed_items)) { if (array_key_exists($key, $transformed_items)) {
$entity[$keyval] = $transformed_items[$key]; $entity[$keyval] = $transformed_items[$key];
} else { } else {
$entity[$keyval] = ''; $entity[$keyval] = "";
} }
} }
@ -173,16 +194,26 @@ class QuoteItemExport extends BaseExport
foreach (array_values($this->input['report_keys']) as $key) { foreach (array_values($this->input['report_keys']) as $key) {
$keyval = array_search($key, $this->entity_keys); $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)) { if (array_key_exists($key, $transformed_quote)) {
$entity[$keyval] = $transformed_quote[$key]; $entity[$keyval] = $transformed_quote[$key];
} else { } elseif (array_key_exists($keyval, $transformed_quote)) {
$entity[$keyval] = ''; $entity[$keyval] = $transformed_quote[$keyval];
}
else {
$entity[$keyval] = $this->resolveKey($keyval, $quote, $this->quote_transformer);
} }
} }
return $this->decorateAdvancedFields($quote, $entity); return $this->decorateAdvancedFields($quote, $entity);
} }
private function decorateAdvancedFields(Quote $quote, array $entity) :array private function decorateAdvancedFields(Quote $quote, array $entity) :array
{ {
if (in_array('currency_id', $this->input['report_keys'])) { if (in_array('currency_id', $this->input['report_keys'])) {

View File

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

View File

@ -24,7 +24,6 @@ use League\Csv\Writer;
class TaskExport extends BaseExport class TaskExport extends BaseExport
{ {
private Company $company;
private $entity_transformer; private $entity_transformer;
@ -47,9 +46,7 @@ class TaskExport extends BaseExport
'custom_value4' => 'custom_value4', 'custom_value4' => 'custom_value4',
'status' => 'status_id', 'status' => 'status_id',
'project' => 'project_id', 'project' => 'project_id',
'invoice' => 'invoice_id', ];
'client' => 'client_id',
];
private array $decorate_keys = [ private array $decorate_keys = [
'status', 'status',
@ -110,38 +107,39 @@ class TaskExport extends BaseExport
$entity = []; $entity = [];
$transformed_entity = $this->entity_transformer->transform($task); $transformed_entity = $this->entity_transformer->transform($task);
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];
} elseif (array_key_exists($keyval, $transformed_entity)) {
$entity[$keyval] = $transformed_entity[$keyval];
}
else {
$entity[$keyval] = $this->resolveKey($keyval, $task, $this->entity_transformer);
}
}
$entity['start_date'] = '';
$entity['end_date'] = '';
$entity['duration'] = '';
if (is_null($task->time_log) || (is_array(json_decode($task->time_log, 1)) && count(json_decode($task->time_log, 1)) == 0)) { 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 (array_key_exists($key, $transformed_entity)) {
$entity[$keyval] = $transformed_entity[$key];
} else {
$entity[$keyval] = '';
}
}
$entity['start_date'] = '';
$entity['end_date'] = '';
$entity['duration'] = '';
$entity = $this->decorateAdvancedFields($task, $entity);
ksort($entity);
$this->csv->insertOne($entity); $this->csv->insertOne($entity);
} elseif (is_array(json_decode($task->time_log, 1)) && count(json_decode($task->time_log, 1)) > 0) { } else {
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); $this->iterateLogs($task, $entity);
} }
} }
private function iterateLogs(Task $task, array $entity) private function iterateLogs(Task $task, array $entity)
@ -164,39 +162,26 @@ class TaskExport extends BaseExport
} }
foreach ($logs as $key => $item) { 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); $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); $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'); $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(); $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); $entity = $this->decorateAdvancedFields($task, $entity);
ksort($entity);
$this->csv->insertOne($entity); $this->csv->insertOne($entity);
unset($entity['start_date']); unset($entity['start_date']);
unset($entity['end_date']); unset($entity['end_date']);
unset($entity['duration']); unset($entity['duration']);
@ -213,14 +198,6 @@ class TaskExport extends BaseExport
$entity['project'] = $task->project()->exists() ? $task->project->name : ''; $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; return $entity;
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -32,11 +32,12 @@ class ExpenseFilters extends QueryFilters
} }
return $this->builder->where(function ($query) use ($filter) { return $this->builder->where(function ($query) use ($filter) {
$query->where('public_notes', 'like', '%'.$filter.'%') $query->where('number', 'like', '%'.$filter.'%')
->orWhere('custom_value1', 'like', '%'.$filter.'%') ->orWhere('public_notes', 'like', '%'.$filter.'%')
->orWhere('custom_value2', 'like', '%'.$filter.'%') ->orWhere('custom_value1', 'like', '%'.$filter.'%')
->orWhere('custom_value3', 'like', '%'.$filter.'%') ->orWhere('custom_value2', 'like', '%'.$filter.'%')
->orWhere('custom_value4', 'like', '%'.$filter.'%'); ->orWhere('custom_value3', 'like', '%'.$filter.'%')
->orWhere('custom_value4', 'like', '%'.$filter.'%');
}); });
} }

View File

@ -238,14 +238,20 @@ class InvoiceItemSum
{ {
$this->rule->tax($this->item); $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_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_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_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; return $this;
} }

View File

@ -11,24 +11,23 @@
namespace App\Http\Controllers; 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 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 class ActivityController extends BaseController
{ {
use PdfMaker, PageNumbering; use PdfMaker, PageNumbering, MakesHash;
protected $entity_type = Activity::class; protected $entity_type = Activity::class;
@ -39,50 +38,6 @@ class ActivityController extends BaseController
parent::__construct(); 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) public function index(Request $request)
{ {
$default_activities = $request->has('rows') ? $request->input('rows') : 75; $default_activities = $request->has('rows') ? $request->input('rows') : 75;
@ -115,47 +70,36 @@ class ActivityController extends BaseController
return $this->listResponse($activities); return $this->listResponse($activities);
} }
/** public function entityActivity(ShowActivityRequest $request)
* @OA\Get( {
* path="/api/v1/actvities/download_entity/{activity_id}",
* operationId="getActivityHistoricalEntityPdf", $default_activities = request()->has('rows') ? request()->input('rows') : 75;
* tags={"actvities"},
* summary="Gets a PDF for the given activity", $activities = Activity::with('user')
* description="Gets a PDF for the given activity", ->orderBy('created_at', 'DESC')
* @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"), ->company()
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"), ->where("{$request->entity}_id", $request->entity_id)
* @OA\Parameter( ->take($default_activities);
* name="activity_id",
* in="path", /** @var \App\Models\User auth()->user() */
* description="The Activity Hashed ID", $user = auth()->user();
* example="D2J234DFA",
* required=true, if (!$user->isAdmin()) {
* @OA\Schema( $activities->where('user_id', auth()->user()->id);
* type="string", }
* format="string",
* ), $system = ctrans('texts.system');
* ),
* @OA\Response( $data = $activities->cursor()->map(function ($activity) use ($system) {
* response=200,
* description="PDF File", return $activity->activity_string();
* @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"),
* ), return response()->json(['data' => $data->toArray()], 200);
* @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 downloadHistoricalEntity(DownloadHistoricalEntityRequest $request, Activity $activity) public function downloadHistoricalEntity(DownloadHistoricalEntityRequest $request, Activity $activity)
{ {
$backup = $activity->backup; $backup = $activity->backup;

View File

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

View File

@ -29,8 +29,10 @@ class ProtectedDownloadController extends BaseController
throw new SystemError('File no longer available', 404); throw new SystemError('File no longer available', 404);
abort(404, 'File no longer available'); 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

@ -12,10 +12,12 @@
namespace App\Http\Requests\Activity; namespace App\Http\Requests\Activity;
use App\Http\Requests\Request; use App\Http\Requests\Request;
use App\Models\Activity; use App\Utils\Traits\MakesHash;
class ShowActivityRequest extends Request class ShowActivityRequest extends Request
{ {
use MakesHash;
/** /**
* Determine if the user is authorized to make this request. * Determine if the user is authorized to make this request.
* *
@ -23,7 +25,25 @@ class ShowActivityRequest extends Request
*/ */
public function authorize() : bool public function authorize() : bool
{ {
// return auth()->user()->isAdmin(); return true;
return auth()->user()->can('view', Activity::class); }
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(); $input = $this->all();
if(isset($input['date_range'])) { if(isset($input['date_range'])) {
$dates = $this->calculateStartAndEndDates($input); $dates = $this->calculateStartAndEndDates($input, auth()->user()->company());
$input['start_date'] = $dates[0]; $input['start_date'] = $dates[0];
$input['end_date'] = $dates[1]; $input['end_date'] = $dates[1];
} }

View File

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

View File

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

View File

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

View File

@ -72,7 +72,8 @@ class UpdateInvoiceRequest extends Request
$rules['tax_name2'] = 'bail|sometimes|string|nullable'; $rules['tax_name2'] = 'bail|sometimes|string|nullable';
$rules['tax_name3'] = '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['status_id'] = 'bail|sometimes|not_in:5'; //do not all cancelled invoices to be modfified.
$rules['exchange_rate'] = 'bail|sometimes|gt:0';
// not needed. // not needed.
// $rules['partial_due_date'] = 'bail|sometimes|required_unless:partial,0,null'; // $rules['partial_due_date'] = 'bail|sometimes|required_unless:partial,0,null';
@ -95,6 +96,10 @@ class UpdateInvoiceRequest extends Request
unset($input['documents']); unset($input['documents']);
} }
if (array_key_exists('exchange_rate', $input) && is_null($input['exchange_rate'])) {
$input['exchange_rate'] = 1;
}
$this->replace($input); $this->replace($input);
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -319,6 +319,21 @@ class BaseTransformer
// return Number::parseFloat($number); // 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 * @param $name
* *

View File

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

View File

@ -46,7 +46,7 @@ class PaymentTransformer extends BaseTransformer
$data, $data,
'payment.transaction_reference ' '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'), 'private_notes' => $this->getString($data, 'payment.private_notes'),
'custom_value1' => $this->getString($data, 'payment.custom_value1'), 'custom_value1' => $this->getString($data, 'payment.custom_value1'),
'custom_value2' => $this->getString($data, 'payment.custom_value2'), 'custom_value2' => $this->getString($data, 'payment.custom_value2'),

View File

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

View File

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

View File

@ -129,12 +129,10 @@ class ReminderJob implements ShouldQueue
$invoice->service()->touchReminder($reminder_template)->save(); $invoice->service()->touchReminder($reminder_template)->save();
$fees = $this->calcLateFee($invoice, $reminder_template); $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); 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 //20-04-2022 fixes for endless reminders - generic template naming was wrong
$enabled_reminder = 'enable_'.$reminder_template; $enabled_reminder = 'enable_'.$reminder_template;

View File

@ -554,7 +554,7 @@ class Account extends BaseModel
$nmo->to_user = $this->companies()->first()->owner(); $nmo->to_user = $this->companies()->first()->owner();
NinjaMailerJob::dispatch($nmo, true); 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')) { if (config('ninja.notification.slack')) {
$this->companies()->first()->notification(new EmailQuotaNotification($this))->ninja(); $this->companies()->first()->notification(new EmailQuotaNotification($this))->ninja();

View File

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

View File

@ -440,6 +440,9 @@ class CheckoutComPaymentDriver extends BaseDriver
$request->query('cko-session-id') $request->query('cko-session-id')
); );
nlog("checkout3ds");
nlog($payment);
if (isset($payment['approved']) && $payment['approved']) { if (isset($payment['approved']) && $payment['approved']) {
return $this->processSuccessfulPayment($payment); return $this->processSuccessfulPayment($payment);
} else { } else {

View File

@ -180,6 +180,8 @@ class BaseRepository
unset($tmp_data['client_contacts']); unset($tmp_data['client_contacts']);
} }
nlog($tmp_data);
$model->fill($tmp_data); $model->fill($tmp_data);
$model->custom_surcharge_tax1 = $client->company->custom_surcharge_taxes1; $model->custom_surcharge_tax1 = $client->company->custom_surcharge_taxes1;

View File

@ -97,6 +97,10 @@ class InvoiceRepository extends BaseRepository
// reversed delete invoice actions // reversed delete invoice actions
$invoice = $invoice->service()->handleRestore()->save(); $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); parent::restore($invoice);
return $invoice; return $invoice;

View File

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

View File

@ -44,7 +44,9 @@ class VendorRepository extends BaseRepository
public function save(array $data, Vendor $vendor) : ?Vendor public function save(array $data, Vendor $vendor) : ?Vendor
{ {
$vendor->fill($data); $vendor->fill($data);
nlog($data);
$vendor->saveQuietly(); $vendor->saveQuietly();
if ($vendor->number == '' || ! $vendor->number) { if ($vendor->number == '' || ! $vendor->number) {

View File

@ -44,6 +44,7 @@ class HandleRestore extends AbstractService
//cannot restore an invoice with a deleted payment //cannot restore an invoice with a deleted payment
foreach ($this->invoice->payments as $payment) { foreach ($this->invoice->payments as $payment) {
if (($this->invoice->paid_to_date == 0) && $payment->is_deleted) { if (($this->invoice->paid_to_date == 0) && $payment->is_deleted) {
$this->invoice->delete();
return $this->invoice; return $this->invoice;
} }
} }

View File

@ -37,6 +37,7 @@ class TaskTransformer extends EntityTransformer
'status', 'status',
'project', 'project',
'user', 'user',
'invoice',
]; ];
public function includeDocuments(Task $task) public function includeDocuments(Task $task)
@ -46,6 +47,17 @@ class TaskTransformer extends EntityTransformer
return $this->includeCollection($task->documents, $transformer, Document::class); 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 public function includeUser(Task $task): ?Item
{ {
$transformer = new UserTransformer($this->serializer); $transformer = new UserTransformer($this->serializer);

View File

@ -583,8 +583,11 @@ class HtmlEngine
if ($this->settings->signature_on_pdf) { if ($this->settings->signature_on_pdf) {
$data['$contact.signature'] = ['value' => $this->invitation->signature_base64, 'label' => ctrans('texts.signature')]; $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 { } else {
$data['$contact.signature'] = ['value' => '', 'label' => '']; $data['$contact.signature'] = ['value' => '', 'label' => ''];
$data['$contact.signature_date'] = ['value' => '', 'label' => ctrans('texts.date')];
} }
$data['$thanks'] = ['value' => '', 'label' => ctrans('texts.thanks')]; $data['$thanks'] = ['value' => '', 'label' => ctrans('texts.thanks')];

View File

@ -14,6 +14,7 @@ namespace App\Utils\Traits;
use DateTime; use DateTime;
use DateTimeZone; use DateTimeZone;
use Carbon\Carbon; use Carbon\Carbon;
use App\Models\Company;
use App\DataMapper\Schedule\EmailStatement; use App\DataMapper\Schedule\EmailStatement;
/** /**
@ -119,8 +120,32 @@ trait MakesDates
* *
* @return array [$start_date, $end_date]; * @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']) { return match ($data['date_range']) {
EmailStatement::LAST7 => [now()->startOfDay()->subDays(7)->format('Y-m-d'), now()->startOfDay()->format('Y-m-d')], 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')], 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::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::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::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::THIS_YEAR => [$fin_year_start->format('Y-m-d'), $fin_year_start->copy()->addYear()->subDay()->format('Y-m-d')],
EmailStatement::LAST_YEAR => [now()->startOfDay()->subYearNoOverflow()->firstOfYear()->format('Y-m-d'), now()->startOfDay()->subYearNoOverflow()->lastOfYear()->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']], EmailStatement::CUSTOM_RANGE => [$data['start_date'], $data['end_date']],
default => [now()->startOfDay()->firstOfMonth()->format('Y-m-d'), now()->startOfDay()->lastOfMonth()->format('Y-m-d')], default => [now()->startOfDay()->firstOfMonth()->format('Y-m-d'), now()->startOfDay()->lastOfMonth()->format('Y-m-d')],
}; };

View File

@ -0,0 +1,29 @@
<?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();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
}
};

View File

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

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('health_check', [PingController::class, 'health'])->name('health_check');
Route::get('activities', [ActivityController::class, 'index']); Route::get('activities', [ActivityController::class, 'index']);
Route::post('activities/entity', [ActivityController::class, 'entityActivity']);
Route::get('activities/download_entity/{activity}', [ActivityController::class, 'downloadHistoricalEntity']); Route::get('activities/download_entity/{activity}', [ActivityController::class, 'downloadHistoricalEntity']);
Route::post('charts/totals', [ChartController::class, 'totals'])->name('chart.totals'); 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/refresh_updates', [YodleeController::class, 'refreshUpdatesWebhook'])->middleware('throttle:100,1');
Route::post('api/v1/yodlee/balance', [YodleeController::class, 'balanceWebhook'])->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'); Route::fallback([BaseController::class, 'notFound'])->middleware('throttle:404');

View File

@ -11,10 +11,11 @@
namespace Tests\Feature; namespace Tests\Feature;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Routing\Middleware\ThrottleRequests;
use Tests\MockAccountData;
use Tests\TestCase; use Tests\TestCase;
use Tests\MockAccountData;
use Illuminate\Validation\ValidationException;
use Illuminate\Routing\Middleware\ThrottleRequests;
use Illuminate\Foundation\Testing\DatabaseTransactions;
/** /**
* @test * @test
@ -34,6 +35,38 @@ class ActivityApiTest extends TestCase
$this->withoutMiddleware( $this->withoutMiddleware(
ThrottleRequests::class 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() public function testActivityGet()

File diff suppressed because it is too large Load Diff

View File

@ -11,20 +11,21 @@
namespace Tests\Feature; namespace Tests\Feature;
use Tests\TestCase;
use App\Models\Client; use App\Models\Client;
use App\Models\ClientContact;
use App\Models\Credit; use App\Models\Credit;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\Payment; use App\Models\Payment;
use Tests\MockAccountData;
use App\Models\ClientContact;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use App\Factory\InvoiceItemFactory;
use Illuminate\Database\Eloquent\Model; 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\Support\Facades\Session;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
use Tests\MockAccountData; use Illuminate\Foundation\Testing\WithoutEvents;
use Tests\TestCase; use Illuminate\Routing\Middleware\ThrottleRequests;
use Illuminate\Foundation\Testing\DatabaseTransactions;
/** /**
* @test * @test
@ -189,4 +190,172 @@ class PaymentV2Test extends TestCase
$this->assertEquals(20, $client->fresh()->paid_to_date); $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);
}
} }

View File

@ -31,6 +31,8 @@ class TaskApiTest extends TestCase
use DatabaseTransactions; use DatabaseTransactions;
use MockAccountData; use MockAccountData;
private $faker;
protected function setUp() :void protected function setUp() :void
{ {
parent::setUp(); parent::setUp();
@ -100,6 +102,20 @@ class TaskApiTest extends TestCase
} }
} }
public function testStartDate()
{
$x = [];
$this->assertFalse(isset($x[0][0]));
$x[0][0] = 'a';
$this->assertTrue(isset($x[0][0]));
$this->assertNotNull(\Carbon\Carbon::createFromTimestamp($x[0][0]));
}
public function testMultiSortArray() public function testMultiSortArray()
{ {

View File

@ -31,6 +31,150 @@ class DatesTest extends TestCase
// $this->makeTestData(); // $this->makeTestData();
} }
public function testLastFinancialYear3()
{
$this->travelTo(now()->createFromDate(2020, 6, 30));
//override for financial years
$first_month_of_year = 7;
$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->assertEquals('2018-07-01', $fin_year_start->format('Y-m-d'));
$this->assertEquals('2019-06-30', $fin_year_start->copy()->addYear()->subDay()->format('Y-m-d'));
$this->travelBack();
}
public function testLastFinancialYear2()
{
$this->travelTo(now()->createFromDate(2020, 7, 1));
//override for financial years
$first_month_of_year = 7;
$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->assertEquals('2019-07-01', $fin_year_start->format('Y-m-d'));
$this->assertEquals('2020-06-30', $fin_year_start->copy()->addYear()->subDay()->format('Y-m-d'));
$this->travelBack();
}
public function testLastFinancialYear()
{
$this->travelTo(now()->createFromDate(2020, 12, 1));
//override for financial years
$first_month_of_year = 7;
$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->assertEquals('2019-07-01', $fin_year_start->format('Y-m-d'));
$this->assertEquals('2020-06-30', $fin_year_start->copy()->addYear()->subDay()->format('Y-m-d'));
$this->travelBack();
}
public function testFinancialYearDates4()
{
$this->travelTo(now()->createFromDate(2020, 12, 1));
$first_month_of_year = 7;
$fin_year_start = now()->createFromDate(now()->year, $first_month_of_year, 1);
if(now()->lt($fin_year_start))
$fin_year_start->subYear();
$fin_year_end = $fin_year_start->copy()->addYear()->subDay();
$this->assertEquals('2020-07-01', $fin_year_start->format('Y-m-d'));
$this->assertEquals('2021-06-30', $fin_year_end->format('Y-m-d'));
$this->travelBack();
}
public function testFinancialYearDates3()
{
$this->travelTo(now()->createFromDate(2021, 12, 1));
$first_month_of_year = 7;
$fin_year_start = now()->createFromDate(now()->year, $first_month_of_year, 1);
if(now()->lt($fin_year_start))
$fin_year_start->subYear();
$fin_year_end = $fin_year_start->copy()->addYear()->subDay();
$this->assertEquals('2021-07-01', $fin_year_start->format('Y-m-d'));
$this->assertEquals('2022-06-30', $fin_year_end->format('Y-m-d'));
$this->travelBack();
}
public function testFinancialYearDates2()
{
$this->travelTo(now()->createFromDate(2021, 8, 1));
$first_month_of_year = 7;
$fin_year_start = now()->createFromDate(now()->year, $first_month_of_year, 1);
if(now()->lt($fin_year_start))
$fin_year_start->subYear();
$fin_year_end = $fin_year_start->copy()->addYear()->subDay();
$this->assertEquals('2021-07-01', $fin_year_start->format('Y-m-d'));
$this->assertEquals('2022-06-30', $fin_year_end->format('Y-m-d'));
$this->travelBack();
}
public function testFinancialYearDates()
{
$this->travelTo(now()->createFromDate(2021, 1, 1));
$first_month_of_year = 7;
$fin_year_start = now()->createFromDate(now()->year, $first_month_of_year, 1);
if(now()->lt($fin_year_start))
$fin_year_start->subYear();
$fin_year_end = $fin_year_start->copy()->addYear()->subDay();
$this->assertEquals('2020-07-01', $fin_year_start->format('Y-m-d'));
$this->assertEquals('2021-06-30', $fin_year_end->format('Y-m-d'));
$this->travelBack();
}
public function testDaysDiff() public function testDaysDiff()
{ {
$string_date = '2021-06-01'; $string_date = '2021-06-01';