Merge branch 'v5-develop' into v5-stable

This commit is contained in:
David Bomba 2023-07-06 17:19:46 +10:00
commit 6370b1f061
224 changed files with 423471 additions and 218345 deletions

View File

@ -46,7 +46,9 @@ jobs:
git checkout main
npm i
npm run build
cp -r dist/react/* ../public/react
cp -r dist/react/* ../public/react
mkdir -p ../public/tinymce_6.4.2/tinymce/js/
cp -r node_modules/tinymce ../public/tinymce_6.4.2/tinymce/js/
cd ..
rm -rf ui
php artisan ninja:react

View File

@ -1 +1 @@
5.6.4
5.6.10

View File

@ -154,7 +154,7 @@ class CheckData extends Command
->subject('Check-Data: '.strtoupper($this->isValid ? Account::RESULT_SUCCESS : Account::RESULT_FAILURE)." [{$database}]");
});
} elseif (! $this->isValid) {
new Exception("Check data failed!!\n".$this->log);
new \Exception("Check data failed!!".$this->log);
}
}

View File

@ -105,7 +105,7 @@ class ImportMigrations extends Command
$import_file = public_path("storage/migrations/$filename/migration.json");
Import::dispatch($import_file, $this->getUser()->companies()->first(), $this->getUser());
// StartMigration::dispatch($file->getRealPath(), $this->getUser(), $this->getUser()->companies()->first());
} catch (NonExistingMigrationFile | ProcessingMigrationArchiveFailed | ResourceNotAvailableForMigration | MigrationValidatorFailed | ResourceDependencyMissing $e) {
\Mail::to($user)->send(new MigrationFailed($e, $company));

View File

@ -133,11 +133,9 @@ class TranslationsExport extends Command
Storage::disk('local')->makeDirectory('lang');
foreach ($this->langs as $lang) {
nlog($lang);
Storage::disk('local')->makeDirectory("lang/{$lang}");
$translations = Lang::getLoader()->load($lang, 'texts');
nlog($translations);
Storage::disk('local')->put("lang/{$lang}/{$lang}.json", json_encode(Arr::dot($translations), JSON_UNESCAPED_UNICODE));
}
}

View File

@ -235,7 +235,7 @@ class BaseRule implements RuleInterface
$this->client_region = $this->region_codes[$this->client->country->iso_3166_2];
match($this->client_region){
'US' => $this->client_subregion = strlen($this->invoice?->client?->tax_data?->geoState) > 1 ? $this->invoice->client->tax_data->geoState : $this->getUSState(),
'US' => $this->client_subregion = isset($this->invoice?->client?->tax_data?->geoState) ? $this->invoice->client->tax_data->geoState : $this->getUSState(),
'EU' => $this->client_subregion = $this->client->country->iso_3166_2,
'AU' => $this->client_subregion = 'AU',
default => $this->client_subregion = $this->client->country->iso_3166_2,

View File

@ -48,7 +48,6 @@ class Rule extends BaseRule implements RuleInterface
{
$this->tax_rate1 = $item->tax_rate1;
$this->tax_name1 = $item->tax_name1;
return $this;
@ -117,6 +116,9 @@ class Rule extends BaseRule implements RuleInterface
if(in_array($this->tax_data?->txbService,['Y','L'])) {
$this->default($item);
}
else {
$this->taxExempt($item);
}
return $this;
}
@ -162,14 +164,13 @@ class Rule extends BaseRule implements RuleInterface
if($this->tax_data?->stateSalesTax == 0) {
$this->tax_rate1 = $this->invoice->client->company->tax_data->regions->{$this->client_region}->subregions->{$this->client_subregion}->tax_rate;
$this->tax_name1 = "Sales Tax";
$this->tax_rate1 = 0;
$this->tax_name1 = '';
return $this;
}
$this->tax_rate1 = $this->tax_data->taxSales * 100;
// $this->tax_name1 = "{$this->tax_data->geoState} Sales Tax";
$this->tax_name1 = "Sales Tax";
return $this;

View File

@ -13,9 +13,10 @@ namespace App\Events\Payment;
use App\Models\Company;
use App\Models\Payment;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use App\Models\ClientContact;
use Illuminate\Queue\SerializesModels;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
/**
* Class PaymentWasEmailed.
@ -24,26 +25,15 @@ class PaymentWasEmailed
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* @var Payment
*/
public $payment;
public $company;
public $event_vars;
/**
* Create a new event instance.
*
* @param Payment $payment
* @param Company $company
* @param ClientContact $contact
* @param array $event_vars
*/
public function __construct(Payment $payment, Company $company, array $event_vars)
public function __construct(public Payment $payment, public Company $company, public ClientContact $contact, public array $event_vars)
{
$this->payment = $payment;
$this->company = $company;
$this->event_vars = $event_vars;
}
}

View File

@ -105,7 +105,7 @@ class Handler extends ExceptionHandler
if($exception instanceof ThrottleRequestsException && class_exists(\Modules\Admin\Events\ThrottledExceptionRaised::class)) {
$uri = urldecode(request()->getRequestUri());
event(new \Modules\Admin\Events\ThrottledExceptionRaised(auth()->user()?->account?->key, $uri, request()->ip()));
// event(new \Modules\Admin\Events\ThrottledExceptionRaised(auth()->user()?->account?->key, $uri, request()->ip()));
}
Integration::configureScope(function (Scope $scope): void {

View File

@ -0,0 +1,41 @@
<?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\Exceptions;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class QuoteConversion extends Exception
{
/**
* Report the exception.
*
* @return void
*/
public function report()
{
//
}
/**
* Render the exception into an HTTP response.
*
* @param Request $request
* @return Response
*/
public function render($request)
{
return response()->json(['message' => 'This quote has already been converted. Cannot convert again.'], 400);
}
}

View File

@ -11,11 +11,18 @@
namespace App\Export\CSV;
use App\Utils\Number;
use App\Models\Client;
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\PaymentTransformer;
use Illuminate\Database\Eloquent\Builder;
use League\Fractal\Serializer\ArraySerializer;
class BaseExport
{
@ -35,6 +42,161 @@ class BaseExport
public array $forced_keys = [];
protected array $client_report_keys = [
"name" => "client.name",
"user" => "client.user_id",
"balance" => "client.balance",
"paid_to_date" => "client.paid_to_date",
"currency" => "client.currency_id",
"website" => "client.website",
"private_notes" => "client.private_notes",
"industry" => "client.industry_id",
"size" => "client.size_id",
"work_phone" => "client.phone",
"address1" => "client.address1",
"address2" => "client.address2",
"city" => "client.city",
"state" => "client.state",
"postal_code" => "client.postal_code",
"country" => "client.country_id",
"custom_value4" => "contact.custom_value4",
"shipping_address1" => "client.shipping_address1",
"shipping_address2" => "client.shipping_address2",
"shipping_city" => "client.shipping_city",
"shipping_state" => "client.shipping_state",
"shipping_postal_code" => "client.shipping_postal_code",
"shipping_country" => "client.shipping_country_id",
"payment_terms" => "client.payment_terms",
"vat_number" => "client.vat_number",
"id_number" => "client.id_number",
"public_notes" => "client.public_notes",
"phone" => "contact.phone",
"first_name" => "contact.first_name",
"last_name" => "contact.last_name",
"email" => "contact.email",
];
protected array $invoice_report_keys = [
"invoice_number" => "invoice.number",
"amount" => "invoice.amount",
"balance" => "invoice.balance",
"paid_to_date" => "invoice.paid_to_date",
"po_number" => "invoice.po_number",
"date" => "invoice.date",
"due_date" => "invoice.due_date",
"terms" => "invoice.terms",
"footer" => "invoice.footer",
"status" => "invoice.status",
"public_notes" => "invoice.public_notes",
"private_notes" => "invoice.private_notes",
"uses_inclusive_taxes" => "invoice.uses_inclusive_taxes",
"is_amount_discount" => "invoice.is_amount_discount",
"partial" => "invoice.partial",
"partial_due_date" => "invoice.partial_due_date",
"surcharge1" => "invoice.custom_surcharge1",
"surcharge2" => "invoice.custom_surcharge2",
"surcharge3" => "invoice.custom_surcharge3",
"surcharge4" => "invoice.custom_surcharge4",
"exchange_rate" => "invoice.exchange_rate",
"tax_amount" => "invoice.total_taxes",
"assigned_user" => "invoice.assigned_user_id",
"user" => "invoice.user_id",
];
protected array $item_report_keys = [
"quantity" => "item.quantity",
"cost" => "item.cost",
"product_key" => "item.product_key",
"notes" => "item.notes",
"item_tax1" => "item.tax_name1",
"item_tax_rate1" => "item.tax_rate1",
"item_tax2" => "item.tax_name2",
"item_tax_rate2" => "item.tax_rate2",
"item_tax3" => "item.tax_name3",
"item_tax_rate3" => "item.tax_rate3",
"custom_value1" => "item.custom_value1",
"custom_value2" => "item.custom_value2",
"custom_value3" => "item.custom_value3",
"custom_value4" => "item.custom_value4",
"discount" => "item.discount",
"type" => "item.type_id",
"tax_category" => "item.tax_id",
];
protected array $quote_report_keys = [
"quote_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",
"terms" => "quote.terms",
"footer" => "quote.footer",
"status" => "quote.status",
"public_notes" => "quote.public_notes",
"private_notes" => "quote.private_notes",
"uses_inclusive_taxes" => "quote.uses_inclusive_taxes",
"is_amount_discount" => "quote.is_amount_discount",
"partial" => "quote.partial",
"partial_due_date" => "quote.partial_due_date",
"surcharge1" => "quote.custom_surcharge1",
"surcharge2" => "quote.custom_surcharge2",
"surcharge3" => "quote.custom_surcharge3",
"surcharge4" => "quote.custom_surcharge4",
"exchange_rate" => "quote.exchange_rate",
"tax_amount" => "quote.total_taxes",
"assigned_user" => "quote.assigned_user_id",
"user" => "quote.user_id",
];
protected array $credit_report_keys = [
"credit_number" => "credit.number",
"amount" => "credit.amount",
"balance" => "credit.balance",
"paid_to_date" => "credit.paid_to_date",
"po_number" => "credit.po_number",
"date" => "credit.date",
"due_date" => "credit.due_date",
"terms" => "credit.terms",
"footer" => "credit.footer",
"status" => "credit.status",
"public_notes" => "credit.public_notes",
"private_notes" => "credit.private_notes",
"uses_inclusive_taxes" => "credit.uses_inclusive_taxes",
"is_amount_discount" => "credit.is_amount_discount",
"partial" => "credit.partial",
"partial_due_date" => "credit.partial_due_date",
"surcharge1" => "credit.custom_surcharge1",
"surcharge2" => "credit.custom_surcharge2",
"surcharge3" => "credit.custom_surcharge3",
"surcharge4" => "credit.custom_surcharge4",
"exchange_rate" => "credit.exchange_rate",
"tax_amount" => "credit.total_taxes",
"assigned_user" => "credit.assigned_user_id",
"user" => "credit.user_id",
];
protected array $payment_report_keys = [
"date" => "payment.date",
"amount" => "payment.amount",
"refunded" => "payment.refunded",
"applied" => "payment.applied",
"transaction_reference" => "payment.transaction_reference",
"currency" => "payment.currency",
"exchange_rate" => "payment.exchange_rate",
"number" => "payment.number",
"method" => "payment.method",
"status" => "payment.status",
"private_notes" => "payment.private_notes",
"custom_value1" => "payment.custom_value1",
"custom_value2" => "payment.custom_value2",
"custom_value3" => "payment.custom_value3",
"custom_value4" => "payment.custom_value4",
"user" => "payment.user_id",
"assigned_user" => "payment.assigned_user_id",
];
protected function filterByClients($query)
{
if (isset($this->input['client_id']) && $this->input['client_id'] != 'all') {
@ -50,6 +212,167 @@ class BaseExport
return $query;
}
protected function resolveKey($key, $entity, $transformer) :string
{
$parts = explode(".", $key);
if(!is_array($parts) || count($parts) < 2)
return '';
match($parts[0]) {
'contact' => $value = $this->resolveClientContactKey($parts[1], $entity, $transformer),
'client' => $value = $this->resolveClientKey($parts[1], $entity, $transformer),
'invoice' => $value = $this->resolveInvoiceKey($parts[1], $entity, $transformer),
'payment' => $value = $this->resolvePaymentKey($parts[1], $entity, $transformer),
default => $value = ''
};
return $value;
}
private function resolveClientContactKey($column, $entity, $transformer)
{
$primary_contact = $entity->client->primary_contact()->first() ?? $entity->client->contacts()->first();
return $primary_contact?->{$column} ?? '';
}
private function resolveClientKey($column, $entity, $transformer)
{
$transformed_client = $transformer->includeClient($entity);
$manager = new Manager();
$manager->setSerializer(new ArraySerializer());
$transformed_client = $manager->createData($transformed_client)->toArray();
if($column == 'name')
return $transformed_client['display_name'];
if($column == 'user_id')
return $entity->client->user->present()->name();
if($column == 'country_id')
return $entity->client->country ? ctrans("texts.country_{$entity->client->country->name}") : '';
if($column == 'shipping_country_id')
return $entity->client->shipping_country ? ctrans("texts.country_{$entity->client->shipping_country->name}") : '';
if($column == 'size_id')
return $entity->client->size?->name ?? '';
if($column == 'industry_id')
return $entity->client->industry?->name ?? '';
if ($column == 'currency_id') {
return $entity->client->currency() ? $entity->client->currency()->code : $entity->company->currency()->code;
}
if($column == 'client.payment_terms') {
return $entity->client->getSetting('payment_terms');
}
if(array_key_exists($column, $transformed_client))
return $transformed_client[$column];
nlog("export: Could not resolve client key: {$column}");
return '';
}
private function resolveInvoiceKey($column, $entity, $transformer)
{
nlog("searching for {$column}");
if($transformer instanceof PaymentTransformer) {
$transformed_invoices = $transformer->includeInvoices($entity);
$manager = new Manager();
$manager->setSerializer(new ArraySerializer());
$transformed_invoices = $manager->createData($transformed_invoices)->toArray();
if(!isset($transformed_invoices['App\\Models\\Invoice']))
return '';
$transformed_invoices = $transformed_invoices['App\\Models\\Invoice'];
if(count($transformed_invoices) == 1 && array_key_exists($column, $transformed_invoices[0]))
return $transformed_invoices[0][$column];
if(count($transformed_invoices) > 1 && array_key_exists($column, $transformed_invoices[0]))
return implode(', ', array_column($transformed_invoices, $column));
return "";
}
$transformed_invoice = $transformer->transform($entity);
if($column == 'status')
return $entity->stringStatus($entity->status_id);
return '';
}
private function resolvePaymentKey($column, $entity, $transformer)
{
if($entity instanceof Payment){
$transformed_payment = $transformer->transform($entity);
if(array_key_exists($column, $transformed_payment)) {
return $transformed_payment[$column];
} elseif (array_key_exists(str_replace("payment.", "", $column), $transformed_payment)) {
return $transformed_payment[$column];
}
nlog("export: Could not resolve payment key: {$column}");
return '';
}
if($column == 'amount')
return $entity->payments()->exists() ? Number::formatMoney($entity->payments()->sum('paymentables.amount'), $entity->company) : ctrans('texts.unpaid');
if($column == 'refunded') {
return $entity->payments()->exists() ? Number::formatMoney($entity->payments()->sum('paymentables.refunded'), $entity->company) : 0;
}
if($column == 'applied') {
$refunded = $entity->payments()->sum('paymentables.refunded');
$amount = $entity->payments()->sum('paymentables.amount');
return $entity->payments()->exists() ? Number::formatMoney(($amount - $refunded), $entity->company) : 0;
}
$payment = $entity->payments()->first();
if(!$payment)
return '';
if($column == 'method')
return $payment->translatedType();
if($column == 'currency')
return $payment?->currency?->code ?? '';
$payment_transformer = new PaymentTransformer();
$transformed_payment = $payment_transformer->transform($payment);
if($column == 'status'){
return $payment->stringStatus($transformed_payment['status_id']);
}
if(array_key_exists($column, $transformed_payment))
return $transformed_payment[$column];
return '';
}
protected function addInvoiceStatusFilter($query, $status): Builder
{
@ -173,14 +496,53 @@ class BaseExport
$header = [];
foreach (array_merge($this->input['report_keys'], $this->forced_keys) as $value) {
$key = array_search($value, $this->entity_keys);
$prefix = '';
if(!$key) {
$prefix = stripos($value, 'client.') !== false ? ctrans('texts.client') : ctrans('texts.contact');
$key = array_search($value, $this->client_report_keys);
}
if(!$key) {
$prefix = ctrans('texts.invoice');
$key = array_search($value, $this->invoice_report_keys);
}
if(!$key) {
$prefix = ctrans('texts.payment');
$key = array_search($value, $this->payment_report_keys);
}
if(!$key) {
$prefix = ctrans('texts.quote');
$key = array_search($value, $this->quote_report_keys);
}
if(!$key) {
$prefix = ctrans('texts.credit');
$key = array_search($value, $this->credit_report_keys);
}
if(!$key) {
$prefix = ctrans('texts.item');
$key = array_search($value, $this->item_report_keys);
}
if(!$key) {
$prefix = '';
}
$key = str_replace('item.', '', $key);
$key = str_replace('invoice.', '', $key);
$key = str_replace('client.', '', $key);
$key = str_replace('contact.', '', $key);
$key = str_replace('payment.', '', $key);
$header[] = ctrans("texts.{$key}");
$header[] = "{$prefix} " . ctrans("texts.{$key}");
}
return $header;

View File

@ -143,9 +143,9 @@ class ClientExport extends BaseExport
$keyval = array_search($key, $this->entity_keys);
if ($parts[0] == 'client' && array_key_exists($parts[1], $transformed_client)) {
if (is_array($parts) && $parts[0] == 'client' && array_key_exists($parts[1], $transformed_client)) {
$entity[$keyval] = $transformed_client[$parts[1]];
} elseif ($parts[0] == 'contact' && array_key_exists($parts[1], $transformed_contact)) {
} elseif (is_array($parts) && $parts[0] == 'contact' && array_key_exists($parts[1], $transformed_contact)) {
$entity[$keyval] = $transformed_contact[$parts[1]];
} else {
$entity[$keyval] = '';

View File

@ -123,10 +123,19 @@ class CreditExport 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("credit.", "", $key), $this->entity_keys) ?? $key;
if(!$keyval)
$keyval = $key;
if (array_key_exists($key, $transformed_credit)) {
$entity[$keyval] = $transformed_credit[$key];
} else {
$entity[$keyval] = '';
} elseif (array_key_exists($keyval, $transformed_credit)) {
$entity[$keyval] = $transformed_credit[$keyval];
}
else {
$entity[$keyval] = $this->resolveKey($keyval, $credit, $this->credit_transformer);
}
}
@ -138,9 +147,9 @@ class CreditExport extends BaseExport
if (in_array('country_id', $this->input['report_keys'])) {
$entity['country'] = $credit->client->country ? ctrans("texts.country_{$credit->client->country->name}") : '';
}
if (in_array('currency_id', $this->input['report_keys'])) {
$entity['currency_id'] = $credit->client->currency() ? $credit->client->currency()->code : $invoice->company->currency()->code;
$entity['currency_id'] = $credit->client->currency() ? $credit->client->currency()->code : $credit->company->currency()->code;
}
if (in_array('invoice_id', $this->input['report_keys'])) {
@ -155,6 +164,10 @@ class CreditExport extends BaseExport
$entity['status'] = $credit->stringStatus($credit->status_id);
}
if(in_array('credit.status', $this->input['report_keys'])) {
$entity['credit.status'] = $credit->stringStatus($credit->status_id);
}
return $entity;
}
}

View File

@ -80,6 +80,7 @@ class InvoiceExport extends BaseExport
'project',
];
public function __construct(Company $company, array $input)
{
$this->company = $company;
@ -134,10 +135,21 @@ class InvoiceExport 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("invoice.", "", $key), $this->entity_keys) ?? $key;
}
if(!$keyval) {
$keyval = $key;
}
if (array_key_exists($key, $transformed_invoice)) {
$entity[$keyval] = $transformed_invoice[$key];
} else {
$entity[$keyval] = '';
} elseif (array_key_exists($keyval, $transformed_invoice)) {
$entity[$keyval] = $transformed_invoice[$keyval];
}
else {
$entity[$keyval] = $this->resolveKey($keyval, $invoice, $this->invoice_transformer);
}
}
@ -162,15 +174,15 @@ class InvoiceExport extends BaseExport
$entity['status'] = $invoice->stringStatus($invoice->status_id);
}
$payment_exists = $invoice->payments()->exists();
// $payment_exists = $invoice->payments()->exists();
$entity['payment_number'] = $payment_exists ? $invoice->payments()->pluck('number')->implode(',') : '';
// $entity['payment_number'] = $payment_exists ? $invoice->payments()->pluck('number')->implode(',') : '';
$entity['payment_date'] = $payment_exists ? $invoice->payments()->pluck('date')->implode(',') : '';
// $entity['payment_date'] = $payment_exists ? $invoice->payments()->pluck('date')->implode(',') : '';
$entity['payment_amount'] = $payment_exists ? Number::formatMoney($invoice->payments()->sum('paymentables.amount'), $invoice->company) : ctrans('texts.unpaid');
// $entity['payment_amount'] = $payment_exists ? Number::formatMoney($invoice->payments()->sum('paymentables.amount'), $invoice->company) : ctrans('texts.unpaid');
$entity['method'] = $payment_exists ? $invoice->payments()->first()->translatedType() : "";
// $entity['method'] = $payment_exists ? $invoice->payments()->first()->translatedType() : "";
return $entity;
}

View File

@ -30,6 +30,8 @@ class InvoiceItemExport extends BaseExport
public Writer $csv;
private bool $force_keys = false;
public array $entity_keys = [
'amount' => 'amount',
'balance' => 'balance',
@ -67,9 +69,9 @@ class InvoiceItemExport extends BaseExport
'total_taxes' => 'total_taxes',
'currency' => 'currency_id',
'quantity' => 'item.quantity',
'unit_cost' => 'item.cost',
'cost' => 'item.cost',
'product_key' => 'item.product_key',
'cost' => 'item.product_cost',
'buy_price' => 'item.product_cost',
'notes' => 'item.notes',
'discount' => 'item.discount',
'is_amount_discount' => 'item.is_amount_discount',
@ -85,6 +87,8 @@ class InvoiceItemExport extends BaseExport
'invoice2' => 'item.custom_value2',
'invoice3' => 'item.custom_value3',
'invoice4' => 'item.custom_value4',
'tax_category' => 'item.tax_id',
'type' => 'item.type_id',
];
private array $decorate_keys = [
@ -112,6 +116,7 @@ class InvoiceItemExport extends BaseExport
$this->csv = Writer::createFromString();
if (count($this->input['report_keys']) == 0) {
$this->force_keys = true;
$this->input['report_keys'] = array_values($this->entity_keys);
}
@ -140,24 +145,36 @@ class InvoiceItemExport extends BaseExport
$transformed_items = [];
foreach ($invoice->line_items as $item) {
$item_array = [];
$item_array = [];
foreach (array_values($this->input['report_keys']) as $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", "invoice", $key);
if($key == 'type_id')
$keyval = 'type';
if($key == 'tax_id')
$keyval = 'tax_category';
if (property_exists($item, $key)) {
$item_array[$key] = $item->{$key};
$item_array[$keyval] = $item->{$key};
} else {
$item_array[$key] = '';
$item_array[$keyval] = '';
}
}
}
$entity = [];
foreach (array_values($this->input['report_keys']) as $key) {
$keyval = array_search($key, $this->entity_keys);
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];
@ -182,10 +199,21 @@ class InvoiceItemExport 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("invoice.", "", $key), $this->entity_keys) ?? $key;
}
if(!$keyval) {
$keyval = $key;
}
if (array_key_exists($key, $transformed_invoice)) {
$entity[$keyval] = $transformed_invoice[$key];
} else {
$entity[$keyval] = "";
} elseif (array_key_exists($keyval, $transformed_invoice)) {
$entity[$keyval] = $transformed_invoice[$keyval];
}
else {
$entity[$keyval] = $this->resolveKey($keyval, $invoice, $this->invoice_transformer);
}
}
@ -198,13 +226,20 @@ class InvoiceItemExport extends BaseExport
$entity['currency'] = $invoice->client->currency() ? $invoice->client->currency()->code : $invoice->company->currency()->code;
}
// if(in_array('client_id', $this->input['report_keys']))
$entity['client'] = $invoice->client->present()->name();
$entity['client_id_number'] = $invoice->client->id_number;
$entity['client_number'] = $invoice->client->number;
if(array_key_exists('type', $entity)) {
$entity['type'] = $invoice->typeIdString($entity['type']);
}
// if(in_array('status_id', $this->input['report_keys']))
$entity['status'] = $invoice->stringStatus($invoice->status_id);
if(array_key_exists('tax_category', $entity)) {
$entity['tax_category'] = $invoice->taxTypeString($entity['tax_category']);
}
if($this->force_keys) {
$entity['client'] = $invoice->client->present()->name();
$entity['client_id_number'] = $invoice->client->id_number;
$entity['client_number'] = $invoice->client->number;
$entity['status'] = $invoice->stringStatus($invoice->status_id);
}
return $entity;
}

View File

@ -112,10 +112,21 @@ class PaymentExport 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("payment.", "", $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, $payment, $this->entity_transformer);
}
}
@ -140,6 +151,10 @@ class PaymentExport extends BaseExport
$entity['currency'] = $payment->currency()->exists() ? $payment->currency->code : '';
}
if (in_array('payment.currency', $this->input['report_keys'])) {
$entity['payment.currency'] = $payment->currency()->exists() ? $payment->currency->code : '';
}
if (in_array('exchange_currency_id', $this->input['report_keys'])) {
$entity['exchange_currency'] = $payment->exchange_currency()->exists() ? $payment->exchange_currency->code : '';
}
@ -152,11 +167,19 @@ class PaymentExport extends BaseExport
$entity['type'] = $payment->translatedType();
}
if (in_array('payment.method', $this->input['report_keys'])) {
$entity['payment.method'] = $payment->translatedType();
}
if (in_array('payment.status', $this->input['report_keys'])) {
$entity['payment.status'] = $payment->stringStatus($payment->status_id);
}
if (in_array('gateway_type_id', $this->input['report_keys'])) {
$entity['gateway'] = $payment->gateway_type ? $payment->gateway_type->name : 'Unknown Type';
}
$entity['invoices'] = $payment->invoices()->exists() ? $payment->invoices->pluck('number')->implode(',') : '';
// $entity['invoices'] = $payment->invoices()->exists() ? $payment->invoices->pluck('number')->implode(',') : '';
return $entity;
}

View File

@ -95,6 +95,11 @@ class RecurringExpenseToExpenseFactory
$replacements = [
'literal' => [
':MONTHYEAR' => \sprintf(
'%s %s',
Carbon::createFromDate(now()->year, now()->month)->translatedFormat('F'),
now()->year,
),
':MONTH' => Carbon::createFromDate(now()->year, now()->month)->translatedFormat('F'),
':YEAR' => now()->year,
':QUARTER' => 'Q'.now()->quarter,
@ -240,6 +245,17 @@ class RecurringExpenseToExpenseFactory
$output = \Carbon\Carbon::create()->month($output)->translatedFormat('F');
}
if ($matches->keys()->first() == ':MONTHYEAR') {
$final_date = now()->addMonths($output-now()->month);
$output = \sprintf(
'%s %s',
$final_date->translatedFormat('F'),
$final_date->year,
);
}
$value = preg_replace(
$target,
$output,

View File

@ -115,6 +115,11 @@ class CreditFilters extends QueryFilters
return $this->builder;
}
if ($sort_col[0] == 'client_id') {
return $this->builder->orderBy(\App\Models\Client::select('name')
->whereColumn('clients.id', 'credits.client_id'), $sort_col[1]);
}
return $this->builder->orderBy($sort_col[0], $sort_col[1]);
}

View File

@ -106,6 +106,29 @@ class ExpenseFilters extends QueryFilters
return $this->builder;
}
/**
* Filter expenses that only have invoices
*
* @param string $value
* @return Builder
*/
public function has_invoices(string $value = ''): Builder
{
$split = explode(",", $value);
if (is_array($split) && in_array($split[0], ['client', 'project'])) {
$search_key = $split[0] == 'client' ? 'client_id' : 'project_id';
return $this->builder->whereHas('invoice', function ($query) use ($search_key, $split){
$query->where($search_key, $this->decodePrimaryKey($split[1]))
->whereIn('status_id', [\App\Models\Invoice::STATUS_DRAFT, \App\Models\Invoice::STATUS_SENT, \App\Models\Invoice::STATUS_PARTIAL]);
});
}
return $this->builder;
}
/**
* Returns a list of expenses that can be matched to bank transactions
*/

View File

@ -11,12 +11,14 @@
namespace App\Filters;
use RuntimeException;
use App\Models\Client;
use App\Models\Invoice;
use App\Filters\QueryFilters;
use InvalidArgumentException;
use Illuminate\Support\Carbon;
use App\Utils\Traits\MakesHash;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
use InvalidArgumentException;
use RuntimeException;
/**
* InvoiceFilters.
@ -205,10 +207,9 @@ class InvoiceFilters extends QueryFilters
}
if ($sort_col[0] == 'client_id') {
$this->builder->with(['client' => function($q) use($sort_col){
$q->orderBy('name', $sort_col[1]);
}]);
return $this->builder->orderBy(\App\Models\Client::select('name')
->whereColumn('clients.id', 'invoices.client_id'), $sort_col[1]);
}

View File

@ -158,6 +158,13 @@ class PaymentFilters extends QueryFilters
return $this->builder;
}
if ($sort_col[0] == 'client_id') {
return $this->builder->orderBy(\App\Models\Client::select('name')
->whereColumn('clients.id', 'payments.client_id'), $sort_col[1]);
}
return $this->builder->orderBy($sort_col[0], $sort_col[1]);
}

View File

@ -135,6 +135,13 @@ class QuoteFilters extends QueryFilters
return $this->builder;
}
if($sort_col[0] == 'client_id'){
return $this->builder->orderBy(\App\Models\Client::select('name')
->whereColumn('clients.id', 'quotes.client_id'), $sort_col[1]);
}
if ($sort_col[0] == 'valid_until') {
$sort_col[0] = 'due_date';
}

View File

@ -115,6 +115,13 @@ class RecurringInvoiceFilters extends QueryFilters
return $this->builder;
}
if ($sort_col[0] == 'client_id') {
return $this->builder->orderBy(\App\Models\Client::select('name')
->whereColumn('clients.id', 'recurring_invoices.client_id'), $sort_col[1]);
}
return $this->builder->orderBy($sort_col[0], $sort_col[1]);
}

View File

@ -110,9 +110,34 @@ class TaskFilters extends QueryFilters
return $this->builder;
}
if ($sort_col[0] == 'client_id') {
return $this->builder->orderBy(\App\Models\Client::select('name')
->whereColumn('clients.id', 'tasks.client_id'), $sort_col[1]);
}
if ($sort_col[0] == 'user_id') {
return $this->builder->orderBy(\App\Models\User::select('first_name')
->whereColumn('users.id', 'tasks.user_id'), $sort_col[1]);
}
return $this->builder->orderBy($sort_col[0], $sort_col[1]);
}
public function task_status(string $value = ''): Builder
{
if (strlen($value) == 0) {
return $this->builder;
}
$status_parameters = explode(',', $value);
if(count($status_parameters) > 0)
return $this->builder->whereIn('status_id', $this->transformKeys($status_parameters));
return $this->builder;
}
/**
* Filters the query by the users company ID.
*

View File

@ -36,6 +36,12 @@ class TokenFilters extends QueryFilters
});
}
public function is_system(bool $value = false): Builder
{
return $this->builder->where('is_system', $value);
}
/**
* Sorts the list based on $sort.
*

View File

@ -89,6 +89,15 @@ class UserFilters extends QueryFilters
->where('account_id', auth()->user()->account_id);
}
public function sending_users(string $value = ''): Builder
{
if (strlen($value) == 0 || $value != 'true') {
return $this->builder;
}
return $this->builder->whereNotNull('oauth_user_refresh_token');
}
/**
* Exclude a list of user_ids, can pass multiple
* user IDs by separating them with a comma.

View File

@ -127,9 +127,12 @@ class IncomeTransformer implements BankRevenueInterface
foreach ($transaction->transaction as $transaction) {
//do not store duplicate / pending transactions
if (property_exists($transaction, 'status') && $transaction->status == 'PENDING') {
if (property_exists($transaction, 'status') && $transaction->status == 'PENDING')
continue;
//some object do no store amounts ignore these
if(!property_exists($transaction, 'amount'))
continue;
}
$data[] = $this->transformTransaction($transaction);
}
@ -148,7 +151,7 @@ class IncomeTransformer implements BankRevenueInterface
'category_type' => $transaction->categoryType,
'date' => $transaction->date,
'bank_account_id' => $transaction->accountId,
'description' => $transaction->description->original,
'description' => $transaction?->description?->original ?? '',
'base_type' => property_exists($transaction, 'baseType') ? $transaction->baseType : $this->calculateBaseType($transaction),
];
}

View File

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

View File

@ -682,8 +682,6 @@ class LoginController extends BaseController
'email' => $socialite_user->getEmail(),
'oauth_user_id' => $socialite_user->getId(),
'oauth_provider_id' => $provider,
// 'oauth_user_token' => $oauth_user_token,
// 'oauth_user_refresh_token' => $socialite_user->refreshToken,
];
$user->update($update_user);
@ -699,7 +697,7 @@ class LoginController extends BaseController
$request_from_react = Cache::pull("react_redir:".auth()->user()?->account?->key);
if($request_from_react)
// if($request_from_react)
$redirect_url = config('ninja.react_url')."/#/settings/user_details/connect";
return redirect($redirect_url);
@ -735,6 +733,10 @@ class LoginController extends BaseController
nlog('user not found for oauth');
}
return redirect('/#/');
$redirect_url = config('ninja.react_url')."/#/settings/user_details/connect";
return redirect($redirect_url);
// return redirect('/#/');
}
}

View File

@ -61,7 +61,7 @@ class ChartController extends BaseController
/** @var \App\Models\User auth()->user() */
$user = auth()->user();
$cs = new ChartService($user->company(), $user, $user->isAdmin());
return response()->json($cs->chart_summary($request->input('start_date'), $request->input('end_date')), 200);
}

View File

@ -14,6 +14,7 @@ namespace App\Http\Controllers;
use App\Models\User;
use App\Models\CompanyUser;
use Illuminate\Http\Response;
use App\Transformers\UserTransformer;
use App\Transformers\CompanyUserTransformer;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use App\Http\Requests\CompanyUser\UpdateCompanyUserRequest;
@ -134,6 +135,7 @@ class CompanyUserController extends BaseController
public function updatePreferences(UpdateCompanyUserPreferencesRequest $request, User $user)
{
/** @var \App\Models\User $logged_in_user */
$company = auth()->user()->company();
$company_user = CompanyUser::whereUserId($user->id)->whereCompanyId($company->id)->first();
@ -143,10 +145,14 @@ class CompanyUserController extends BaseController
return;
}
$this->entity_type = User::class;
$this->entity_transformer = UserTransformer::class;
$company_user->react_settings = $request->react_settings;
$company_user->save();
return $this->itemResponse($company_user->fresh());
return $this->itemResponse($user->fresh());
}

View File

@ -38,7 +38,7 @@ class ConnectedAccountController extends BaseController
* Connect an OAuth account to a regular email/password combination account
*
* @param Request $request
* @return User Refresh Feed.
* @return JsonResponse.
*
*
* @OA\Post(
@ -90,14 +90,15 @@ class ConnectedAccountController extends BaseController
private function handleMicrosoftOauth($request)
{
nlog($request->all());
$access_token = false;
$access_token = $request->has('access_token') ? $request->input('access_token') : $request->input('accessToken');
if (!$request->has('access_token')) {
if (!$access_token) {
return response()->json(['message' => 'No access_token parameter found!'], 400);
}
$graph = new \Microsoft\Graph\Graph();
$graph->setAccessToken($request->input('access_token'));
$graph->setAccessToken($access_token);
$user = $graph->createRequest("GET", "/me")
->setReturnType(Model\User::class)

View File

@ -162,16 +162,14 @@ class ImportController extends Controller
$delimiters = [',', '.', ';'];
$bestDelimiter = ' ';
$count = 0;
foreach ($delimiters as $delimiter) {
// if (substr_count($csvfile, $delimiter) > $count) {
// $count = substr_count($csvfile, $delimiter);
// $bestDelimiter = $delimiter;
// }
if (substr_count(strstr($csvfile, "\n", true), $delimiter) > $count) {
if (substr_count(strstr($csvfile, "\n", true), $delimiter) >= $count) {
$count = substr_count($csvfile, $delimiter);
$bestDelimiter = $delimiter;
}
}
return $bestDelimiter;
}

View File

@ -408,7 +408,7 @@ class InvoiceController extends BaseController
$invoice->service()
->triggeredActions($request)
->touchPdf()
->deletePdf()
->adjustInventory($old_invoice);
event(new InvoiceWasUpdated($invoice, $invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
@ -740,7 +740,8 @@ class InvoiceController extends BaseController
}
break;
case 'cancel':
$invoice = $invoice->service()->handleCancellation()->touchPdf()->save();
$invoice = $invoice->service()->handleCancellation()->deletePdf()->save();
// $invoice = $invoice->service()->handleCancellation()->touchPdf()->save();
if (! $bulk) {
$this->itemResponse($invoice);
@ -818,17 +819,11 @@ class InvoiceController extends BaseController
return response()->json(['message' => 'no record found'], 400);
}
$contact = $invitation->contact;
$invoice = $invitation->invoice;
// $file = $invoice->service()->getInvoicePdf($contact);
$file_name = $invoice->numberFormatter().'.pdf';
/************** */
$file_name = $invoice->numberFormatter().'.pdf';
$file = (new \App\Jobs\Entity\CreateRawPdf($invitation, $invitation->company->db))->handle();
/************* */
$file = (new \App\Jobs\Entity\CreateRawPdf($invitation, $invitation->company->db))->handle();
$headers = ['Content-Type' => 'application/pdf'];

View File

@ -565,10 +565,11 @@ class RecurringInvoiceController extends BaseController
return response()->json(['message' => 'no record found'], 400);
}
$contact = $invitation->contact;
$invoice = $invitation->recurring_invoice;
$file = $invoice->service()->getInvoicePdf($contact);
$file_name = $invoice->numberFormatter().'.pdf';
$file = (new \App\Jobs\Entity\CreateRawPdf($invitation, $invitation->company->db))->handle();
$headers = ['Content-Type' => 'application/pdf'];
@ -577,8 +578,9 @@ class RecurringInvoiceController extends BaseController
}
return response()->streamDownload(function () use ($file) {
echo Storage::get($file);
}, basename($file), $headers);
echo $file;
}, $file_name, $headers);
}
}

View File

@ -175,6 +175,7 @@ class UserController extends BaseController
$user->oauth_user_refresh_token = null;
$user->oauth_user_token = null;
$user->save();
UserEmailChanged::dispatch($new_user, json_decode($old_user), $logged_in_user->company());
}

View File

@ -12,9 +12,12 @@
namespace App\Http\Requests\Chart;
use App\Http\Requests\Request;
use App\Utils\Traits\MakesDates;
class ShowChartRequest extends Request
{
use MakesDates;
/**
* Determine if the user is authorized to make this request.
*
@ -22,14 +25,18 @@ class ShowChartRequest extends Request
*/
public function authorize() : bool
{
return auth()->user()->isAdmin();
/**@var \App\Models\User auth()->user */
$user = auth()->user();
return $user->isAdmin();
}
public function rules()
{
return [
'start_date' => 'date',
'end_date' => 'date',
'date_range' => 'bail|sometimes|string|in:last7_days,last30_days,last365_days,this_month,last_month,this_quarter,last_quarter,this_year,last_year,all_time,custom',
'start_date' => 'bail|sometimes|date',
'end_date' => 'bail|sometimes|date',
];
}
@ -37,12 +44,18 @@ class ShowChartRequest extends Request
{
$input = $this->all();
if (! array_key_exists('start_date', $input)) {
$input['start_date'] = now()->subDays(20);
if(isset($input['date_range'])) {
$dates = $this->calculateStartAndEndDates($input);
$input['start_date'] = $dates[0];
$input['end_date'] = $dates[1];
}
if (! array_key_exists('end_date', $input)) {
$input['end_date'] = now();
if (! isset($input['start_date'])) {
$input['start_date'] = now()->subDays(20)->format('Y-m-d');
}
if (! isset($input['end_date'])) {
$input['end_date'] = now()->format('Y-m-d');
}
$this->replace($input);

View File

@ -48,7 +48,7 @@ class PreviewInvoiceRequest extends Request
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
$input['amount'] = 0;
$input['balance'] = 0;
$input['number'] = ctrans('texts.live_preview').' #'.rand(0, 1000);
$input['number'] = isset($input['number']) ? $input['number'] : ctrans('texts.live_preview').' #'.rand(0, 1000);
$this->replace($input);
}

View File

@ -48,7 +48,7 @@ class PreviewPurchaseOrderRequest extends Request
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
$input['amount'] = 0;
$input['balance'] = 0;
$input['number'] = ctrans('texts.live_preview') . " #". rand(0, 1000);
$input['number'] = isset($input['number']) ? $input['number'] : ctrans('texts.live_preview').' #'.rand(0, 1000); //30-06-2023
$this->replace($input);
}

View File

@ -33,7 +33,7 @@ class GenericReportRequest extends Request
'start_date' => 'bail|required_if:date_range,custom|nullable|date',
'report_keys' => 'present|array',
'send_email' => 'required|bool',
'status' => 'sometimes|string|nullable|in:all,draft,sent,viewed,paid,unpaid,overdue',
// 'status' => 'sometimes|string|nullable|in:all,draft,sent,viewed,paid,unpaid,overdue',
];
}

View File

@ -57,7 +57,7 @@ class StoreSchedulerRequest extends Request
$input = $this->all();
if (array_key_exists('next_run', $input) && is_string($input['next_run'])) {
$this->merge(['next_run_client' => $input['next_run']]);
$input['next_run_client'] = $input['next_run'];
}
if($input['template'] == 'email_record'){

View File

@ -54,7 +54,7 @@ class UpdateSchedulerRequest extends Request
$input = $this->all();
if (array_key_exists('next_run', $input) && is_string($input['next_run'])) {
$this->merge(['next_run_client' => $input['next_run']]);
$input['next_run_client'] = $input['next_run'];
}
if($input['template'] == 'email_record') {

View File

@ -20,12 +20,14 @@ class BankTransactionMap
1 => 'transaction.amount',
2 => 'transaction.currency',
3 => 'transaction.account_type',
4 => 'transaction.category_id',
4 => 'transaction.category',
5 => 'transaction.category_type',
6 => 'transaction.date',
7 => 'transaction.bank_account_id',
7 => 'transaction.bank_account',
8 => 'transaction.description',
9 => 'transaction.base_type',
10 => 'transaction.payment_type_Credit',
11 => 'transaction.payment_type_Debit',
];
}
@ -42,6 +44,8 @@ class BankTransactionMap
7 => 'texts.bank_account_id',
8 => 'texts.description',
9 => 'texts.type',
10 => 'transaction.credit',
11 => 'transaction.debit',
];
}
}

View File

@ -91,6 +91,10 @@ class BaseImport
public function getCsvData($entity_type)
{
if (! ini_get('auto_detect_line_endings')) {
ini_set('auto_detect_line_endings', '1');
}
$base64_encoded_csv = Cache::pull($this->hash.'-'.$entity_type);
if (empty($base64_encoded_csv)) {
@ -132,14 +136,12 @@ class BaseImport
$bestDelimiter = ',';
$count = 0;
foreach ($delimiters as $delimiter) {
// if (substr_count($csvfile, $delimiter) > $count) {
// $count = substr_count($csvfile, $delimiter);
// $bestDelimiter = $delimiter;
// }
if (substr_count(strstr($csvfile, "\n", true), $delimiter) > $count) {
if (substr_count(strstr($csvfile, "\n", true), $delimiter) >= $count) {
$count = substr_count($csvfile, $delimiter);
$bestDelimiter = $delimiter;
}
}
return $bestDelimiter;
}

View File

@ -30,7 +30,7 @@ class BankTransformer extends BaseTransformer
$transformed = [
'bank_integration_id' => $transaction['transaction.bank_integration_id'],
'transaction_id' => $this->getNumber($transaction, 'transaction.transaction_id'),
'amount' => abs($this->getFloat($transaction, 'transaction.amount')),
'amount' => $this->calculateAmount($transaction),
'currency_id' => $this->getCurrencyByCode($transaction, 'transaction.currency'),
'account_type' => strlen($this->getString($transaction, 'transaction.account_type')) > 1 ? $this->getString($transaction, 'transaction.account_type') : 'bank',
'category_id' => $this->getNumber($transaction, 'transaction.category_id') > 0 ? $this->getNumber($transaction, 'transaction.category_id') : null,
@ -49,13 +49,35 @@ class BankTransformer extends BaseTransformer
return $transformed;
}
private function calculateAmount(array $transaction):float
{
if (array_key_exists('transaction.amount', $transaction) && is_numeric($transaction['transaction.amount'])) {
return abs($this->getFloat($transaction, 'transaction.amount'));
}
if (array_key_exists('transaction.payment_type_Credit', $transaction) && is_numeric($transaction['transaction.payment_type_Credit'])) {
return abs($this->getFloat($transaction, 'transaction.payment_type_Credit'));
}
if (array_key_exists('transaction.payment_type_Debit', $transaction) && is_numeric($transaction['transaction.payment_type_Debit'])) {
return abs($this->getFloat($transaction, 'transaction.payment_type_Debit'));
}
return 0;
}
private function calculateType($transaction)
{
if (array_key_exists('transaction.base_type', $transaction) && (($transaction['transaction.base_type'] == 'CREDIT') || strtolower($transaction['transaction.base_type']) == 'deposit')) {
if (array_key_exists('transaction.payment_type_Credit', $transaction) && is_numeric($transaction['transaction.payment_type_Credit'])) {
return 'CREDIT';
}
if (array_key_exists('transaction.transaction.payment_type_Debit', $transaction) && is_numeric($transaction['transaction.payment_type_Debit'])) {
return 'DEBIT';
}
if (array_key_exists('transaction.base_type', $transaction) && (($transaction['transaction.base_type'] == 'DEBIT') || strtolower($transaction['transaction.base_type']) == 'withdrawal')) {
return 'DEBIT';
}

View File

@ -362,7 +362,7 @@ class MatchBankTransactions implements ShouldQueue
$this->invoice
->service()
->applyNumber()
->touchPdf()
->deletePdf()
->save();
$payment->ledger()

View File

@ -11,17 +11,19 @@
namespace App\Jobs\Bank;
use App\Helpers\Bank\Yodlee\Yodlee;
use App\Models\Company;
use App\Libraries\MultiDB;
use Illuminate\Bus\Queueable;
use App\Models\BankIntegration;
use App\Models\BankTransaction;
use App\Models\Company;
use App\Services\Bank\BankMatchingService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use App\Helpers\Bank\Yodlee\Yodlee;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use App\Services\Bank\BankMatchingService;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use App\Notifications\Ninja\GenericNinjaAdminNotification;
class ProcessBankTransactions implements ShouldQueue
{
@ -70,6 +72,14 @@ class ProcessBankTransactions implements ShouldQueue
$this->processTransactions();
} catch(\Exception $e) {
nlog("{$this->bank_integration_account_id} - exited abnormally => ". $e->getMessage());
$content = [
"Processing transactions for account: {$this->bank_integration->account->key} failed",
"Exception Details => ",
$e->getMessage(),
];
$this->bank_integration->company->notification(new GenericNinjaAdminNotification($content))->ninja();
return;
}
} while ($this->stop_loop);
@ -152,4 +162,15 @@ class ProcessBankTransactions implements ShouldQueue
$this->bank_integration->save();
}
}
public function middleware()
{
return [new WithoutOverlapping($this->bank_integration_account_id)];
}
public function backoff()
{
return [rand(10, 15), rand(30, 40), rand(60, 79), rand(160, 200), rand(3000, 5000)];
}
}

View File

@ -73,12 +73,11 @@ class UpdateTaxData implements ShouldQueue
nlog("problem getting tax data => ".$e->getMessage());
}
/** Set static tax information */
/*
if(!$tax_provider->updatedTaxStatus() && $this->client->country_id == 840){
$calculated_state = false;
/** State must be calculated else default to the company state for taxes */
if(array_key_exists($this->client->shipping_state, USStates::get())) {
$calculated_state = $this->client->shipping_state;
$calculated_postal_code = $this->client->shipping_postal_code;
@ -136,7 +135,7 @@ class UpdateTaxData implements ShouldQueue
$this->client->saveQuietly();
}
*/
}
public function middleware()
@ -147,6 +146,8 @@ class UpdateTaxData implements ShouldQueue
public function failed($exception)
{
nlog("UpdateTaxData failed => ".$exception->getMessage());
config(['queue.failed.driver' => null]);
}
}

View File

@ -243,19 +243,20 @@ class NinjaMailerJob implements ShouldQueue
case 'gmail':
$this->mailer = 'gmail';
$this->setGmailMailer();
return;
return $this;
case 'office365':
case 'microsoft':
$this->mailer = 'office365';
$this->setOfficeMailer();
return;
return $this;
case 'client_postmark':
$this->mailer = 'postmark';
$this->setPostmarkMailer();
return;
return $this;
case 'client_mailgun':
$this->mailer = 'mailgun';
$this->setMailgunMailer();
return;
return $this;
default:
break;
@ -264,6 +265,8 @@ class NinjaMailerJob implements ShouldQueue
if (Ninja::isSelfHost()) {
$this->setSelfHostMultiMailer();
}
return $this;
}
/**

View File

@ -65,7 +65,7 @@ class EmailPayment implements ShouldQueue
public function handle()
{
if ($this->company->is_disabled) {
return true;
return;
}
if ($this->contact->email) {
@ -96,7 +96,7 @@ class EmailPayment implements ShouldQueue
(new NinjaMailerJob($nmo))->handle();
event(new PaymentWasEmailed($this->payment, $this->payment->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
event(new PaymentWasEmailed($this->payment, $this->payment->company, $this->contact, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
}
}
}

View File

@ -101,7 +101,7 @@ class EmailRefundPayment implements ShouldQueue
(new NinjaMailerJob($nmo))->handle();
event(new PaymentWasEmailed($this->payment, $this->payment->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
event(new PaymentWasEmailed($this->payment, $this->payment->company, $this->contact, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
}
}
}

View File

@ -11,85 +11,89 @@
namespace App\Jobs\Util;
use App\DataMapper\Analytics\MigrationFailure;
use App\DataMapper\CompanySettings;
use App\Exceptions\ClientHostedMigrationException;
use App\Exceptions\MigrationValidatorFailed;
use App\Exceptions\ResourceDependencyMissing;
use Exception;
use App\Models\Task;
use App\Models\User;
use App\Utils\Ninja;
use App\Models\Quote;
use App\Models\Client;
use App\Models\Credit;
use App\Models\Vendor;
use App\Models\Company;
use App\Models\Expense;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\Product;
use App\Models\Project;
use App\Models\TaxRate;
use App\Models\Activity;
use App\Models\Document;
use App\Libraries\MultiDB;
use App\Models\TaskStatus;
use App\Models\PaymentTerm;
use Illuminate\Support\Str;
use App\Factory\UserFactory;
use App\Factory\QuoteFactory;
use App\Models\ClientContact;
use Illuminate\Bus\Queueable;
use App\Factory\ClientFactory;
use App\Factory\CompanyLedgerFactory;
use App\Factory\CreditFactory;
use App\Factory\VendorFactory;
use App\Models\CompanyGateway;
use Illuminate\Support\Carbon;
use App\Factory\InvoiceFactory;
use App\Factory\PaymentFactory;
use App\Factory\ProductFactory;
use App\Factory\QuoteFactory;
use App\Factory\RecurringInvoiceFactory;
use App\Factory\TaxRateFactory;
use App\Factory\UserFactory;
use App\Factory\VendorFactory;
use App\Http\Requests\Company\UpdateCompanyRequest;
use App\Http\ValidationRules\ValidCompanyGatewayFeesAndLimitsRule;
use App\Http\ValidationRules\ValidUserForCompany;
use App\Jobs\Company\CreateCompanyToken;
use App\Jobs\Mail\NinjaMailerJob;
use App\Jobs\Mail\NinjaMailerObject;
use App\Jobs\Ninja\CheckCompanyData;
use App\Libraries\MultiDB;
use App\Mail\Migration\StripeConnectMigration;
use App\Mail\MigrationCompleted;
use App\Models\Activity;
use App\Models\Client;
use App\Models\ClientContact;
use App\Models\ClientGatewayToken;
use App\Models\Company;
use App\Models\CompanyGateway;
use App\Models\Credit;
use App\Models\Document;
use App\Models\Expense;
use App\Jobs\Util\VersionCheck;
use App\Models\ExpenseCategory;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\PaymentTerm;
use App\Models\Product;
use App\Models\Project;
use App\Models\Quote;
use App\Utils\Traits\MakesHash;
use App\Mail\MigrationCompleted;
use App\Models\RecurringExpense;
use App\Models\RecurringInvoice;
use App\Models\Task;
use App\Models\TaskStatus;
use App\Models\TaxRate;
use App\Models\User;
use App\Models\Vendor;
use App\Repositories\ClientContactRepository;
use App\Repositories\ClientRepository;
use App\Repositories\CompanyRepository;
use App\Repositories\CreditRepository;
use App\Repositories\Migration\InvoiceMigrationRepository;
use App\Repositories\Migration\PaymentMigrationRepository;
use App\Repositories\ProductRepository;
use App\Repositories\UserRepository;
use App\Repositories\VendorContactRepository;
use App\Repositories\VendorRepository;
use App\Utils\Ninja;
use App\Utils\Traits\CleanLineItems;
use App\Utils\Traits\CompanyGatewayFeesAndLimitsSaver;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\SavesDocuments;
use App\Utils\Traits\Uploadable;
use Exception;
use Illuminate\Bus\Queueable;
use App\Jobs\Mail\NinjaMailerJob;
use Illuminate\Http\UploadedFile;
use App\Models\ClientGatewayToken;
use Illuminate\Support\Facades\DB;
use App\DataMapper\CompanySettings;
use Illuminate\Support\Facades\App;
use App\Jobs\Mail\NinjaMailerObject;
use App\Jobs\Ninja\CheckCompanyData;
use App\Repositories\UserRepository;
use App\Utils\Traits\CleanLineItems;
use App\Utils\Traits\SavesDocuments;
use Illuminate\Support\Facades\Mail;
use App\Factory\CompanyLedgerFactory;
use App\Repositories\ClientRepository;
use App\Repositories\CreditRepository;
use App\Repositories\VendorRepository;
use Illuminate\Queue\SerializesModels;
use Turbo124\Beacon\Facades\LightLogs;
use App\Repositories\CompanyRepository;
use App\Repositories\ProductRepository;
use App\Factory\RecurringInvoiceFactory;
use App\Jobs\Company\CreateCompanyToken;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Validator;
use Modules\Admin\Jobs\Account\NinjaUser;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Http\UploadedFile;
use Illuminate\Queue\InteractsWithQueue;
use App\DataMapper\ClientRegistrationFields;
use App\Exceptions\MigrationValidatorFailed;
use App\Exceptions\ResourceDependencyMissing;
use App\Repositories\ClientContactRepository;
use App\Repositories\VendorContactRepository;
use App\DataMapper\Analytics\MigrationFailure;
use App\Mail\Migration\StripeConnectMigration;
use App\Http\ValidationRules\ValidUserForCompany;
use App\Exceptions\ClientHostedMigrationException;
use App\Http\Requests\Company\UpdateCompanyRequest;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use Turbo124\Beacon\Facades\LightLogs;
use App\Utils\Traits\CompanyGatewayFeesAndLimitsSaver;
use App\Repositories\Migration\InvoiceMigrationRepository;
use App\Repositories\Migration\PaymentMigrationRepository;
use App\Http\ValidationRules\ValidCompanyGatewayFeesAndLimitsRule;
class Import implements ShouldQueue
{
@ -135,6 +139,7 @@ class Import implements ShouldQueue
'recurring_expenses',
'tasks',
'documents',
'activities',
];
/**
@ -181,7 +186,7 @@ class Import implements ShouldQueue
public function middleware()
{
return [(new WithoutOverlapping($this->user->account_id))];
return [(new WithoutOverlapping($this->company->company_key))];
}
/**
@ -303,7 +308,7 @@ class Import implements ShouldQueue
if (round($total_invoice_payments, 2) != round($client->paid_to_date, 2)) {
$client->paid_to_date = $total_invoice_payments;
$client->save();
$client->saveQuietly();
}
});
}
@ -319,12 +324,12 @@ class Import implements ShouldQueue
$company_ledger->notes = 'Migrated Client Balance';
$company_ledger->balance = $invoice_balances;
$company_ledger->activity_id = Activity::CREATE_CLIENT;
$company_ledger->save();
$company_ledger->saveQuietly();
$client->company_ledger()->save($company_ledger);
$client->balance = $invoice_balances;
$client->save();
$client->saveQuietly();
});
}
@ -1504,7 +1509,19 @@ class Import implements ShouldQueue
false
);
$this->saveDocument($uploaded_file, $entity, $is_public = true);
// $this->saveDocument($uploaded_file, $entity, $is_public = true);
$document = (new \App\Jobs\Util\UploadFile(
$uploaded_file,
\App\Jobs\Util\UploadFile::DOCUMENT,
$this->user,
$this->company,
$entity,
null,
true
))->handle();
} catch(\Exception $e) {
//do nothing, gracefully :)
}
@ -1776,6 +1793,78 @@ class Import implements ShouldQueue
$data = null;
}
private function processActivities(array $data): void
{
Activity::where('company_id', $this->company->id)->cursor()->each(function ($a){
$a->forceDelete();
nlog("deleting {$a->id}");
});
Activity::unguard();
foreach ($data as $resource) {
$modified = $resource;
$modified['company_id'] = $this->company->id;
$modified['user_id'] = $this->processUserId($resource);
try {
if (isset($modified['client_id'])) {
$modified['client_id'] = $this->transformId('clients', $resource['client_id']);
}
if (isset($modified['invoice_id'])) {
$modified['invoice_id'] = $this->transformId('invoices', $resource['invoice_id']);
}
if (isset($modified['quote_id'])) {
$modified['quote_id'] = $this->transformId('quotes', $resource['quote_id']);
}
if (isset($modified['recurring_invoice_id'])) {
$modified['recurring_invoice_id'] = $this->transformId('recurring_invoices', $resource['recurring_invoice_id']);
}
if (isset($modified['payment_id'])) {
$modified['payment_id'] = $this->transformId('payments', $resource['payment_id']);
}
if (isset($modified['credit_id'])) {
$modified['credit_id'] = $this->transformId('credits', $resource['credit_id']);
}
if (isset($modified['expense_id'])) {
$modified['expense_id'] = $this->transformId('expenses', $resource['expense_id']);
}
if (isset($modified['task_id'])) {
$modified['task_id'] = $this->transformId('tasks', $resource['task_id']);
}
if (isset($modified['client_contact_id'])) {
$modified['client_contact_id'] = $this->transformId('client_contacts', $resource['client_contact_id']);
}
$modified['updated_at'] = $modified['created_at'];
$act = Activity::make($modified);
$act->save(['timestamps' => false]);
}
catch (\Exception $e) {
nlog("could not import activity: {$e->getMessage()}");
}
}
Activity::reguard();
}
private function processExpenses(array $data) :void
{
Expense::unguard();

View File

@ -126,7 +126,7 @@ class ReminderJob implements ShouldQueue
}
$reminder_template = $invoice->calculateTemplate('invoice');
nlog("reminder template = {$reminder_template}");
// nlog("reminder template = {$reminder_template}");
$invoice->service()->touchReminder($reminder_template)->save();
$fees = $this->calcLateFee($invoice, $reminder_template);
@ -208,7 +208,8 @@ class ReminderJob implements ShouldQueue
->markSent()
->save();
$invoice->service()->touchPdf(true);
//30-6-2023 - fix for duplicate touching
// $invoice->service()->touchPdf(true);
$enabled_reminder = 'enable_'.$reminder_template;
if ($reminder_template == 'endless_reminder') {
@ -268,7 +269,6 @@ class ReminderJob implements ShouldQueue
}
return [$late_fee_amount, $late_fee_percent];
// return $this->setLateFee($invoice, $late_fee_amount, $late_fee_percent);
}
/**

View File

@ -60,10 +60,6 @@ class StartMigration implements ShouldQueue
public $timeout = 0;
// public $maxExceptions = 2;
//public $backoff = 86430;
public function __construct($filepath, User $user, Company $company)
{
$this->filepath = $filepath;
@ -159,8 +155,6 @@ class StartMigration implements ShouldQueue
}
//always make sure we unset the migration as running
return true;
}

View File

@ -122,7 +122,9 @@ class WebhookSingle implements ShouldQueue
$client = new Client(['headers' => array_merge($base_headers, $headers)]);
try {
$response = $client->post($subscription->target_url, [
$verb = $subscription->rest_method ?? 'post';
$response = $client->{$verb}($subscription->target_url, [
RequestOptions::JSON => $data, // or 'json' => [...]
]);

View File

@ -38,25 +38,28 @@ class PaymentCreatedActivity implements ShouldQueue
* @return void
*/
public function handle($event)
{
{
MultiDB::setDb($event->company->db);
$payment = $event->payment;
$invoice_id = null;
if($payment->invoices()->exists())
$invoice_id = $payment->invoices()->first()->id;
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->payment->user_id;
$invoices = $payment->invoices;
$fields = new stdClass;
$fields->payment_id = $payment->id;
$fields->invoice_id = $invoice_id;
$fields->client_id = $payment->client_id;
$fields->user_id = $user_id;
$fields->company_id = $payment->company_id;
$fields->activity_type_id = Activity::CREATE_PAYMENT;
if (count($invoices) == 0) {
$this->activity_repo->save($fields, $payment, $event->event_vars);
}
$this->activity_repo->save($fields, $payment, $event->event_vars);
}
}

View File

@ -58,7 +58,6 @@ class InvoiceEmailFailedActivity implements ShouldQueue
$fields->client_contact_id = $event->invitation->client_contact_id;
$fields->company_id = $event->invitation->invoice->company_id;
$fields->activity_type_id = Activity::EMAIL_INVOICE_FAILED;
$fields->notes = $event->message;
$this->activity_repo->save($fields, $event->invitation->invoice, $event->event_vars);
}

View File

@ -45,6 +45,7 @@ class PaymentEmailedActivity implements ShouldQueue
$fields->user_id = $user_id;
$fields->client_id = $event->payment->client_id;
$fields->client_contact_id = $event->contact->id;
$fields->company_id = $event->payment->company_id;
$fields->activity_type_id = Activity::PAYMENT_EMAILED;
$fields->payment_id = $event->payment->id;

View File

@ -46,7 +46,6 @@ class ArchivedUserActivity implements ShouldQueue
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->creating_user->id;
$fields->user_id = $user_id;
$fields->notes = $event->creating_user->present()->name.' Archived User '.$event->user->present()->name();
$fields->company_id = $event->company->id;
$fields->activity_type_id = Activity::ARCHIVE_USER;

View File

@ -46,7 +46,6 @@ class CreatedUserActivity implements ShouldQueue
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->creating_user->id;
$fields->user_id = $user_id;
$fields->notes = $event->creating_user->present()->name().' Created the user '.$event->user->present()->name();
$fields->company_id = $event->company->id;
$fields->activity_type_id = Activity::CREATE_USER;

View File

@ -51,8 +51,6 @@ class DeletedUserActivity implements ShouldQueue
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->creating_user->id;
$fields->user_id = $user_id;
$fields->notes = $event->creating_user->present()->name().' Deleted the user '.$event->user->present()->name();
$fields->company_id = $event->company->id;
$fields->activity_type_id = Activity::DELETE_USER;

View File

@ -45,7 +45,6 @@ class RestoredUserActivity implements ShouldQueue
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->creating_user->id;
$fields->user_id = $user_id;
$fields->notes = $event->creating_user->present()->name().' Restored user '.$event->user->present()->name();
$fields->company_id = $event->company->id;
$fields->activity_type_id = Activity::RESTORE_USER;

View File

@ -45,7 +45,6 @@ class UpdatedUserActivity implements ShouldQueue
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->creating_user->id;
$fields->user_id = $user_id;
$fields->notes = $event->creating_user->present()->name().' Updated user '.$event->user->present()->name();
$fields->company_id = $event->company->id;
$fields->activity_type_id = Activity::UPDATE_USER;

View File

@ -11,6 +11,7 @@
namespace App\Models;
use App\Utils\Number;
use App\Utils\Traits\MakesHash;
/**
@ -346,7 +347,7 @@ class Activity extends StaticModel
*/
public function contact()
{
return $this->belongsTo(ClientContact::class)->withTrashed();
return $this->belongsTo(ClientContact::class, 'client_contact_id', 'id')->withTrashed();
}
/**
@ -460,4 +461,90 @@ class Activity extends StaticModel
{
return $this->belongsTo(Company::class);
}
public function activity_string()
{
$intersect = [
':invoice',
':client',
':contact',
':user',
':vendor',
':quote',
':credit',
':payment',
':task',
':expense',
':purchase_order',
':subscription',
':recurring_invoice',
':recurring_expense',
':amount',
':balance',
':number',
':payment_amount',
':gateway',
':adjustment'
];
$found_variables = array_intersect(explode(" ",trans("texts.activity_{$this->activity_type_id}")), $intersect);
$replacements = [];
foreach($found_variables as $var)
$replacements = array_merge($replacements, $this->matchVar($var));
if($this->client)
$replacements['client'] = ['label' => $this?->client?->present()->name() ?? '', 'hashed_id' => $this->client->hashed_id ?? ''];
if($this->vendor)
$replacements['vendor'] = ['label' => $this?->vendor?->present()->name() ?? '', 'hashed_id' => $this->vendor->hashed_id ?? ''];
$replacements['activity_type_id'] = $this->activity_type_id;
$replacements['id'] = $this->id;
$replacements['hashed_id'] = $this->hashed_id;
$replacements['notes'] = $this->notes ?? '';
$replacements['created_at'] = $this->created_at ?? '';
$replacements['ip'] = $this->ip ?? '';
return $replacements;
}
private function matchVar(string $variable)
{
$system = ctrans('texts.system');
match($variable) {
':invoice' => $translation = [substr($variable, 1) => [ 'label' => $this?->invoice?->number ?? '', 'hashed_id' => $this->invoice?->hashed_id ?? '']],
':user' => $translation = [substr($variable, 1) => [ 'label' => $this?->user?->present()->name() ?? $system, 'hashed_id' => $this->user->hashed_id ?? '']],
':quote' => $translation = [substr($variable, 1) => [ 'label' => $this?->quote?->number ?? '', 'hashed_id' => $this->quote->hashed_id ?? '']],
':credit' => $translation = [substr($variable, 1) => [ 'label' => $this?->credit?->number ?? '', 'hashed_id' => $this->credit->hashed_id ?? '']],
':payment' => $translation = [substr($variable, 1) => [ 'label' => $this?->payment?->number ?? '', 'hashed_id' => $this->payment->hashed_id ?? '']],
':task' => $translation = [substr($variable, 1) => [ 'label' => $this?->task?->number ?? '', 'hashed_id' => $this->task->hashed_id ?? '']],
':expense' => $translation = [substr($variable, 1) => [ 'label' => $this?->expense?->number ?? '', 'hashed_id' => $this->expense->hashed_id ?? '']],
':purchase_order' => $translation = [substr($variable, 1) => [ 'label' => $this?->purchase_order?->number ?? '', 'hashed_id' => $this->purchase_order->hashed_id ?? '']],
':subscription' => $translation = [substr($variable, 1) => [ 'label' => $this?->subscription?->number ?? '', 'hashed_id' => $this->subscription->hashed_id ?? '' ]],
':recurring_invoice' => $translation = [substr($variable, 1) =>[ 'label' => $this?->recurring_invoice?->number ??'', 'hashed_id' => $this->recurring_invoice->hashed_id ?? '']],
':recurring_expense' => $translation = [substr($variable, 1) => [ 'label' => $this?->recurring_expense?->number ??'', 'hashed_id' => $this->recurring_expense->hashed_id ?? '']],
':payment_amount' => $translation = [substr($variable, 1) =>[ 'label' => Number::formatMoney($this?->payment?->amount, $this?->payment?->client) ?? '', 'hashed_id' => '']],
':adjustment' => $translation = [substr($variable, 1) =>[ 'label' => Number::formatMoney($this?->payment?->refunded, $this?->payment?->client) ?? '', 'hashed_id' => '']],
':ip' => $translation = [ 'ip' => $this->ip ?? ''],
':contact' => $translation = $this->resolveContact(),
default => $translation = [],
};
return $translation;
}
private function resolveContact() : array
{
$contact = $this->contact ? $this->contact : $this->vendor_contact;
$entity = $this->contact ? $this->client : $this->vendor;
$contact_entity = $this->contact ? 'clients' : 'vendors';
return ['contact' => [ 'label' => $contact?->present()->name() ?? '', 'hashed_id' => $entity->hashed_id ?? '', 'contact_entity' => $contact_entity]];
}
}

View File

@ -11,15 +11,17 @@
namespace App\Models;
use Illuminate\Support\Str;
use Illuminate\Support\Carbon;
use App\Utils\Traits\MakesHash;
use App\Jobs\Entity\CreateRawPdf;
use App\Jobs\Util\WebhookHandler;
use App\Models\Traits\Excludable;
use App\Utils\Traits\MakesHash;
use Illuminate\Database\Eloquent\Model;
use App\Jobs\Vendor\CreatePurchaseOrderPdf;
use App\Utils\Traits\UserSessionAttributes;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundException;
use Illuminate\Support\Carbon;
use Illuminate\Support\Str;
/**
* Class BaseModel
@ -45,8 +47,11 @@ use Illuminate\Support\Str;
* @method static \Illuminate\Database\Eloquent\Builder|BaseModel count()
* @method static \Illuminate\Database\Eloquent\Builder|BaseModel create()
* @method static \Illuminate\Database\Eloquent\Builder|BaseModel insert()
* @method static \Illuminate\Database\Eloquent\Builder|BaseModel service()
* @method static \Illuminate\Database\Eloquent\Builder|BaseModel whereHas()
* @method static \Illuminate\Database\Eloquent\Builder|BaseModel withTrashed()
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\InvoiceInvitation | \App\Models\CreditInvitation | \App\Models\QuoteInvitation | \App\Models\RecurringInvoiceInvitation> $invitations
* @property-read int|null $invitations_count
*
* @method \App\Models\Company company()
* @method int companyId()
@ -255,4 +260,30 @@ class BaseModel extends Model
WebhookHandler::dispatch($event_id, $this, $this->company, $additional_data);
}
}
/**
* Returns the base64 encoded PDF string of the entity
*/
public function fullscreenPdfViewer($invitation = null): string
{
if (! $invitation) {
if ($this->invitations()->exists()) {
$invitation = $this->invitations()->first();
} else {
$this->service()->createInvitations();
$invitation = $this->invitations()->first();
}
}
if (! $invitation) {
throw new \Exception('Hard fail, could not create an invitation.');
}
if($this instanceof \App\Models\PurchaseOrder)
return "data:application/pdf;base64,".base64_encode((new CreatePurchaseOrderPdf($invitation, $invitation->company->db))->rawPdf());
return "data:application/pdf;base64,".base64_encode((new CreateRawPdf($invitation, $invitation->company->db))->handle());
}
}

View File

@ -91,7 +91,6 @@ use Laracasts\Presenter\PresentableTrait;
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ClientGatewayToken> $gateway_tokens
* @property-read int|null $gateway_tokens_count
* @property-read mixed $hashed_id
* @property-read \App\Models\GroupSetting|null $group_settings
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Invoice> $invoices
* @property-read int|null $invoices_count
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyLedger> $ledger
@ -112,6 +111,7 @@ use Laracasts\Presenter\PresentableTrait;
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\SystemLog> $system_logs
* @property-read int|null $system_logs_count
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Task> $tasks
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\RecurringInvoice> $recurring_invoices
* @property-read int|null $tasks_count
* @property-read \App\Models\User $user
* @method static \Illuminate\Database\Eloquent\Builder|BaseModel company()
@ -175,26 +175,7 @@ use Laracasts\Presenter\PresentableTrait;
* @method static \Illuminate\Database\Eloquent\Builder|Client whereTaxData($value)
* @property int $is_tax_exempt
* @method static \Illuminate\Database\Eloquent\Builder|Client whereIsTaxExempt($value)
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Activity> $activities
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyLedger> $company_ledger
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ClientContact> $contacts
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Credit> $credits
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Expense> $expenses
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\GroupSetting> $group_settings
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ClientGatewayToken> $gateway_tokens
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Invoice> $invoices
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyLedger> $ledger
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Payment> $payments
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ClientContact> $primary_contact
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Project> $projects
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Quote> $quotes
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\RecurringExpense> $recurring_expenses
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\RecurringInvoice> $recurring_invoices
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\SystemLog> $system_logs
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Task> $tasks
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\CompanyLedger> $company_ledger
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ClientContact> $contacts
* @property int $has_valid_vat_number
* @mixin \Eloquent
*/
@ -464,6 +445,11 @@ class Client extends BaseModel implements HasLocalePreference
return $this->belongsTo(Industry::class);
}
public function size()
{
return $this->belongsTo(Size::class);
}
public function locale()
{
if (! $this->language()) {
@ -544,9 +530,9 @@ class Client extends BaseModel implements HasLocalePreference
return $this->settings->{$setting};
} elseif (is_bool($this->settings->{$setting})) {
return $this->settings->{$setting};
} elseif (is_int($this->settings->{$setting})) { //10-08-2022 integer client values are not being passed back! This resolves it.
} elseif (is_int($this->settings->{$setting})) {
return $this->settings->{$setting};
} elseif(is_float($this->settings->{$setting})) { //10-08-2022 integer client values are not being passed back! This resolves it.
} elseif(is_float($this->settings->{$setting})) {
return $this->settings->{$setting};
}
}

View File

@ -212,29 +212,29 @@ class Company extends BaseModel
use AppSetup;
use \Awobaz\Compoships\Compoships;
const ENTITY_RECURRING_INVOICE = 'recurring_invoice';
// const ENTITY_RECURRING_INVOICE = 'recurring_invoice';
const ENTITY_CREDIT = 'credit';
// const ENTITY_CREDIT = 'credit';
const ENTITY_QUOTE = 'quote';
// const ENTITY_QUOTE = 'quote';
const ENTITY_TASK = 'task';
// const ENTITY_TASK = 'task';
const ENTITY_EXPENSE = 'expense';
// const ENTITY_EXPENSE = 'expense';
const ENTITY_PROJECT = 'project';
// const ENTITY_PROJECT = 'project';
const ENTITY_VENDOR = 'vendor';
// const ENTITY_VENDOR = 'vendor';
const ENTITY_TICKET = 'ticket';
// const ENTITY_TICKET = 'ticket';
const ENTITY_PROPOSAL = 'proposal';
// const ENTITY_PROPOSAL = 'proposal';
const ENTITY_RECURRING_EXPENSE = 'recurring_expense';
// const ENTITY_RECURRING_EXPENSE = 'recurring_expense';
const ENTITY_RECURRING_TASK = 'task';
// const ENTITY_RECURRING_TASK = 'task';
const ENTITY_RECURRING_QUOTE = 'recurring_quote';
// const ENTITY_RECURRING_QUOTE = 'recurring_quote';
protected $presenter = CompanyPresenter::class;
@ -309,7 +309,6 @@ class Company extends BaseModel
'google_analytics_key',
'matomo_url',
'matomo_id',
'enable_e_invoice',
'client_can_register',
'enable_shop_api',
'invoice_task_timelog',
@ -368,31 +367,26 @@ class Company extends BaseModel
protected $with = [];
public static $modules = [
self::ENTITY_RECURRING_INVOICE => 1,
self::ENTITY_CREDIT => 2,
self::ENTITY_QUOTE => 4,
self::ENTITY_TASK => 8,
self::ENTITY_EXPENSE => 16,
self::ENTITY_PROJECT => 32,
self::ENTITY_VENDOR => 64,
self::ENTITY_TICKET => 128,
self::ENTITY_PROPOSAL => 256,
self::ENTITY_RECURRING_EXPENSE => 512,
self::ENTITY_RECURRING_TASK => 1024,
self::ENTITY_RECURRING_QUOTE => 2048,
];
// public static $modules = [
// self::ENTITY_RECURRING_INVOICE => 1,
// self::ENTITY_CREDIT => 2,
// self::ENTITY_QUOTE => 4,
// self::ENTITY_TASK => 8,
// self::ENTITY_EXPENSE => 16,
// self::ENTITY_PROJECT => 32,
// self::ENTITY_VENDOR => 64,
// self::ENTITY_TICKET => 128,
// self::ENTITY_PROPOSAL => 256,
// self::ENTITY_RECURRING_EXPENSE => 512,
// self::ENTITY_RECURRING_TASK => 1024,
// self::ENTITY_RECURRING_QUOTE => 2048,
// ];
public function shouldCalculateTax()
{
return $this->calculate_taxes && in_array($this->getSetting('country_id'), $this->tax_coverage_countries);
}
public function refreshTaxData()
{
}
public function documents()
{
return $this->morphMany(Document::class, 'documentable');

View File

@ -225,6 +225,11 @@ class Expense extends BaseModel
return $this->belongsTo(Company::class);
}
public function invoice()
{
return $this->belongsTo(Invoice::class);
}
public function vendor()
{
return $this->belongsTo(Vendor::class);

View File

@ -14,6 +14,7 @@ namespace App\Models;
use App\Utils\Ninja;
use Illuminate\Support\Carbon;
use App\Utils\Traits\MakesDates;
use App\Jobs\Entity\CreateRawPdf;
use App\Helpers\Invoice\InvoiceSum;
use App\Jobs\Entity\CreateEntityPdf;
use App\Utils\Traits\MakesReminders;
@ -683,6 +684,7 @@ class Invoice extends BaseModel
public function pdf_file_path($invitation = null, string $type = 'path', bool $portal = false)
{
if (! $invitation) {
if ($this->invitations()->exists()) {
$invitation = $this->invitations()->first();
@ -725,7 +727,6 @@ class Invoice extends BaseModel
return Storage::disk(config('filesystems.default'))->{$type}($file_path);
}
try {
$file_exists = Storage::disk('public')->exists($file_path);
} catch (\Exception $e) {
@ -888,4 +889,38 @@ class Invoice extends BaseModel
{
return ctrans('texts.invoice');
}
public function taxTypeString($id)
{
match(intval($id)){
Product::PRODUCT_TYPE_PHYSICAL => $tax_type = ctrans('texts.physical_goods'),
Product::PRODUCT_TYPE_SERVICE => $tax_type = ctrans('texts.services'),
Product::PRODUCT_TYPE_DIGITAL => $tax_type = ctrans('texts.digital_products'),
Product::PRODUCT_TYPE_SHIPPING => $tax_type = ctrans('texts.shipping'),
Product::PRODUCT_TYPE_EXEMPT => $tax_type = ctrans('texts.tax_exempt'),
Product::PRODUCT_TYPE_REDUCED_TAX => $tax_type = ctrans('texts.reduced_tax'),
Product::PRODUCT_TYPE_OVERRIDE_TAX => $tax_type = ctrans('texts.override_tax'),
Product::PRODUCT_TYPE_ZERO_RATED => $tax_type = ctrans('texts.zero_rated'),
Product::PRODUCT_TYPE_REVERSE_TAX => $tax_type = ctrans('texts.reverse_tax'),
default => $tax_type = ctrans('texts.physical_goods'),
};
return $tax_type;
}
public function typeIdString($id)
{
match($id) {
'1' => $type = ctrans('texts.product'),
'2' => $type = ctrans('texts.service'),
'3' => $type = ctrans('texts.gateway_fees'),
'4' => $type = ctrans('texts.gateway_fees'),
'5' => $type = ctrans('texts.late_fees'),
'6' => $type = ctrans('texts.expense'),
default => $type = ctrans('texts.product'),
};
return $type;
}
}

View File

@ -522,17 +522,18 @@ class Payment extends BaseModel
event(new PaymentWasVoided($this, $this->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
}
// public function getLink()
// {
// return route('client.payments.show', $this->hashed_id);
// }
public function getLink() :string
{
// if (Ninja::isHosted()) {
// $domain = isset($this->company->portal_domain) ? $this->company->portal_domain : $this->company->domain();
// } else {
// $domain = config('ninja.app_url');
// }
if (Ninja::isHosted()) {
$domain = isset($this->company->portal_domain) ? $this->company->portal_domain : $this->company->domain();
$domain = $this->company->domain();
} else {
$domain = config('ninja.app_url');
$domain = strlen($this->company->portal_domain) > 5 ? $this->company->portal_domain : config('ninja.app_url');
}
return $domain.'/client/payment/'.$this->client->contacts()->first()->contact_key.'/'.$this->hashed_id.'?next=/client/payments/'.$this->hashed_id;

View File

@ -34,7 +34,8 @@ class PaymentType extends StaticModel
*/
public $timestamps = false;
const CREDIT = 32;
const BANK_TRANSFER = 1;
const CASH = 2;
const ACH = 4;
const VISA = 5;
const MASTERCARD = 6;
@ -53,11 +54,14 @@ class PaymentType extends StaticModel
const MAESTRO = 20;
const SOLO = 21;
const SWITCH = 22;
const VENMO = 24;
const ALIPAY = 27;
const SOFORT = 28;
const SEPA = 29;
const GOCARDLESS = 30;
const CRYPTO = 31;
const CREDIT = 32;
const ZELLE = 33;
const MOLLIE_BANK_TRANSFER = 34;
const KBC = 35;
const BANCONTACT = 36;
@ -76,10 +80,12 @@ class PaymentType extends StaticModel
const BACS = 49;
const STRIPE_BANK_TRANSFER = 50;
const CASH_APP = 51;
const VENMO = 24;
public array $type_names = [
self::BANK_TRANSFER => 'payment_type_Bank Transfer',
self::CASH => 'payment_type_Cash',
self::CREDIT => 'payment_type_Credit',
self::ZELLE => 'payment_type_Zelle',
self::ACH => 'payment_type_ACH',
self::VISA => 'payment_type_Visa Card',
self::MASTERCARD => 'payment_type_MasterCard',

View File

@ -202,5 +202,16 @@ class Scheduler extends BaseModel
$this->save();
}
public function adjustOffset(): void
{
if (! $this->next_run) {
return;
}
$offset = $this->company->timezone_offset();
$this->next_run = $this->next_run->copy()->addSeconds($offset);
$this->save();
}
}

View File

@ -26,6 +26,8 @@ use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundExceptio
* @method static \Illuminate\Database\Eloquent\Builder|StaticModel newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|StaticModel query()
* @method static \Illuminate\Database\Eloquent\Builder|StaticModel find()
* @method static \Illuminate\Database\Eloquent\Builder|StaticModel with()
* @method static \Illuminate\Database\Eloquent\Builder|StaticModel withTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|StaticModel findOrFail()
* @mixin \Eloquent
*/

View File

@ -0,0 +1,71 @@
<?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\Notifications\Ninja;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
class GenericNinjaAdminNotification extends Notification
{
public function __construct(protected array $message_array)
{
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return ['slack'];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
*/
public function toMail($notifiable)
{
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
public function toSlack($notifiable)
{
$content = '';
foreach($this->message_array as $message) {
$content .= $message . "\n";
}
return (new SlackMessage)
->success()
->from(ctrans('texts.notification_bot'))
->image('https://app.invoiceninja.com/favicon.png')
->content($content);
}
}

View File

@ -525,7 +525,7 @@ class BaseDriver extends AbstractPaymentDriver
$invoices = Invoice::whereIn('id', $this->transformKeys(array_column($this->payment_hash->invoices(), 'invoice_id')))->withTrashed()->get();
$invoices->each(function ($invoice) {
$invoice->service()->touchPdf();
$invoice->service()->deletePdf();
});
$invoices->first()->invitations->each(function ($invitation) use ($nmo) {
@ -570,7 +570,7 @@ class BaseDriver extends AbstractPaymentDriver
$invoices = Invoice::whereIn('id', $this->transformKeys(array_column($this->payment_hash->invoices(), 'invoice_id')))->withTrashed()->get();
$invoices->each(function ($invoice) {
$invoice->service()->touchPdf();
$invoice->service()->deletePdf();
});
$invoices->first()->invitations->each(function ($invitation) use ($nmo) {
@ -732,6 +732,9 @@ class BaseDriver extends AbstractPaymentDriver
$invoices_string = \implode(', ', collect($this->payment_hash->invoices())->pluck('invoice_number')->toArray()) ?: null;
if (!$invoices_string)
return str_replace(["*","<",">","'",'"'], "", $this->client->company->present()->name());
$invoices_string = str_replace(["*","<",">","'",'"'], "-", $invoices_string);
$invoices_string = "I-".$invoices_string;

View File

@ -70,12 +70,15 @@ class CreditCard
$response = $this->eway_driver->init()->eway->createCustomer(\Eway\Rapid\Enum\ApiMethod::DIRECT, $transaction);
$response_status = ErrorCode::getStatus($response->ResponseMessage);
if($response->getErrors()) {
$response_status['message'] = \Eway\Rapid::getMessage($response->getErrors()[0]);
if (! $response_status['success']) {
$this->eway_driver->sendFailureMail($response_status['message']);
throw new PaymentFailed($response_status['message'], 400);
$this->logResponse($response);
throw new PaymentFailed($response_status['message'] ?? 'Unknown response from gateway, please contact you merchant.', 400);
}
//success
@ -94,6 +97,8 @@ class CreditCard
$token = $this->eway_driver->storeGatewayToken($cgt, []);
$this->logResponse($response);
return $token;
}
@ -135,7 +140,7 @@ class CreditCard
$amount = array_sum(array_column($this->eway_driver->payment_hash->invoices(), 'amount')) + $this->eway_driver->payment_hash->fee_total;
$description = "Invoices: {$invoice_numbers} for {$amount} for client {$this->eway_driver->client->present()->name()}";
// $description = "Invoices: {$invoice_numbers} for {$amount} for client {$this->eway_driver->client->present()->name()}";
$transaction = [
'Payment' => [
@ -152,29 +157,6 @@ class CreditCard
$this->logResponse($response);
// if(!$response || !property_exists($response, 'ResponseMessage'))
// throw new PaymentFailed('The gateway did not return a valid response. Please check your gateway credentials.', 400);
// $response_status = ErrorCode::getStatus($response->ResponseMessage);
// if(!$response_status['success']){
// if($response->getErrors())
// {
// $message = false;
// foreach ($response->getErrors() as $error) {
// $message = \Eway\Rapid::getMessage($error);
// }
// $return_message = $message ?: $response_status['message'];
// }
// $this->eway_driver->sendFailureMail($response_status['message']);
// throw new PaymentFailed($response_status['message'], 400);
// }
if ($response->TransactionStatus) {
$payment = $this->storePayment($response);
} else {

View File

@ -123,6 +123,7 @@ class CreditCard
'city' => $this->paytrace->client->city,
'state' => $this->paytrace->client->state,
'zip' => $this->paytrace->client->postal_code,
'country' => $this->paytrace->client->country->iso_3166_2
];
return $data;
@ -177,6 +178,7 @@ class CreditCard
'customer_id' => $token,
'integrator_id' => $this->paytrace->company_gateway->getConfigField('integratorId'),
'amount' => $request->input('amount_with_fee'),
'invoice_id' => $this->harvestInvoiceId(),
];
$response = $this->paytrace->gatewayRequest('/v1/transactions/sale/by_customer', $data);

View File

@ -106,7 +106,7 @@ class iDeal
'gateway_type_id' => GatewayType::IDEAL,
];
$this->stripe->createPayment($data, Payment::STATUS_PENDING);
$this->stripe->createPayment($data, Payment::STATUS_COMPLETED);
SystemLogger::dispatch(
['response' => $this->stripe->payment_hash->data, 'data' => $data],

View File

@ -122,7 +122,7 @@ class PaymentMigrationRepository extends BaseRepository
$invoices = Invoice::whereIn('id', array_column($data['invoices'], 'invoice_id'))->withTrashed()->get();
$payment->invoices()->saveMany($invoices);
$payment->invoices()->saveMany($invoices); // 1:1 relationship so this is ok
$payment->invoices->each(function ($inv) use ($invoice_totals, $refund_totals, $payment) {
if ($payment->status_id != Payment::STATUS_CANCELLED || ! $payment->is_deleted) {

View File

@ -11,19 +11,20 @@
namespace App\Repositories;
use App\Events\Payment\PaymentWasCreated;
use App\Events\Payment\PaymentWasDeleted;
use App\Jobs\Credit\ApplyCreditPayment;
use App\Libraries\Currency\Conversion\CurrencyApi;
use App\Utils\Ninja;
use App\Models\Client;
use App\Models\Credit;
use App\Models\Invoice;
use App\Models\Payment;
use App\Utils\Ninja;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\SavesDocuments;
use App\Models\Paymentable;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\SavesDocuments;
use App\Jobs\Credit\ApplyCreditPayment;
use App\Events\Payment\PaymentWasCreated;
use App\Events\Payment\PaymentWasDeleted;
use App\Libraries\Currency\Conversion\CurrencyApi;
/**
* PaymentRepository.
@ -138,7 +139,7 @@ class PaymentRepository extends BaseRepository
$invoices = Invoice::withTrashed()->whereIn('id', array_column($data['invoices'], 'invoice_id'))->get();
$payment->invoices()->saveMany($invoices);
// $payment->invoices()->saveMany($invoices); //25-06-2023
//todo optimize this into a single query
foreach ($data['invoices'] as $paid_invoice) {
@ -146,6 +147,16 @@ class PaymentRepository extends BaseRepository
$invoice = $invoices->firstWhere('id', $paid_invoice['invoice_id']);
if ($invoice) {
//25-06-2023
$paymentable = new Paymentable();
$paymentable->payment_id = $payment->id;
$paymentable->paymentable_id = $invoice->id;
$paymentable->paymentable_type = 'invoices';
$paymentable->amount = $paid_invoice['amount'];
$paymentable->save();
$invoice = $invoice->service()
->markSent()
->applyPayment($payment, $paid_invoice['amount'])
@ -153,26 +164,30 @@ class PaymentRepository extends BaseRepository
}
}
} else {
//payment is made, but not to any invoice, therefore we are applying the payment to the clients paid_to_date only
//01-07-2020 i think we were duplicating the paid to date here.
//$payment->client->service()->updatePaidToDate($payment->amount)->save();
}
if (array_key_exists('credits', $data) && is_array($data['credits'])) {
$credit_totals = array_sum(array_column($data['credits'], 'amount'));
// $credits = Credit::whereIn('id', $this->transformKeys(array_column($data['credits'], 'credit_id')))->get();
$credits = Credit::whereIn('id', array_column($data['credits'], 'credit_id'))->get();
$payment->credits()->saveMany($credits);
// $payment->credits()->saveMany($credits);
//todo optimize into a single query
foreach ($data['credits'] as $paid_credit) {
// $credit = Credit::withTrashed()->find($paid_credit['credit_id']);
$credit = $credits->firstWhere('id', $paid_credit['credit_id']);
if ($credit) {
$paymentable = new Paymentable();
$paymentable->payment_id = $payment->id;
$paymentable->paymentable_id = $credit->id;
$paymentable->paymentable_type = Credit::class;
$paymentable->amount = $paid_credit['amount'];
$paymentable->save();
$credit = $credit->service()->markSent()->save();
(new ApplyCreditPayment($credit, $payment, $paid_credit['amount'], $credit->company))->handle();
}

View File

@ -30,8 +30,7 @@ class SchedulerRepository extends BaseRepository
$scheduler->save();
/** 18-5-2023 set client specific send times. */
$scheduler->calculateNextRun();
$scheduler->adjustOffset();
return $scheduler->fresh();
}

View File

@ -38,13 +38,15 @@ class TaskStatusRepository extends BaseRepository
public function archive($task_status)
{
$task_status = TaskStatus::where('id', $task_status->id)
$task_status = TaskStatus::withTrashed()
->where('id', $task_status->id)
->where('company_id', $task_status->company_id)
->first();
$new_status = $task_status ? $task_status->id : null;
Task::where('status_id', $task_status->id)
Task::withTrashed()
->where('status_id', $task_status->id)
->where('company_id', $task_status->company_id)
->update(['status_id' => $new_status]);

View File

@ -76,6 +76,8 @@ class ChartService
$currencies = $this->getCurrencyCodes();
$data = [];
$data['start_date'] = $start_date;
$data['end_date'] = $end_date;
foreach ($currencies as $key => $value) {
$data[$key]['invoices'] = $this->getInvoiceChartQuery($start_date, $end_date, $key);
@ -97,6 +99,9 @@ class ChartService
$data['currencies'] = $this->getCurrencyCodes();
$data['start_date'] = $start_date;
$data['end_date'] = $end_date;
$revenue = $this->getRevenue($start_date, $end_date);
$outstanding = $this->getOutstanding($start_date, $end_date);
$expenses = $this->getExpenses($start_date, $end_date);

View File

@ -137,7 +137,7 @@ class ApplyPayment
->updateBalance($this->amount_applied * -1)
->updatePaidToDate($this->amount_applied)
->updateStatus()
->touchPdf()
->deletePdf()
->save();
$this->credit
@ -147,7 +147,7 @@ class ApplyPayment
event(new InvoiceWasUpdated($this->invoice, $this->invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
if ((int) $this->invoice->balance == 0) {
$this->invoice->service()->touchPdf();
$this->invoice->service()->deletePdf();
$this->invoice = $this->invoice->fresh();
event(new InvoiceWasPaid($this->invoice, $this->payment, $this->payment->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
}

View File

@ -11,15 +11,17 @@
namespace App\Services\Credit;
use App\Factory\PaymentFactory;
use App\Jobs\Entity\CreateEntityPdf;
use App\Jobs\Util\UnlinkFile;
use App\Utils\Ninja;
use App\Models\Credit;
use App\Models\Payment;
use App\Models\PaymentType;
use App\Jobs\Util\UnlinkFile;
use App\Factory\PaymentFactory;
use App\Utils\Traits\MakesHash;
use App\Jobs\Entity\CreateEntityPdf;
use App\Repositories\CreditRepository;
use App\Repositories\PaymentRepository;
use App\Utils\Traits\MakesHash;
use Illuminate\Support\Facades\Storage;
class CreditService
{
@ -235,7 +237,24 @@ class CreditService
public function deletePdf()
{
$this->credit->invitations->each(function ($invitation) {
(new UnlinkFile(config('filesystems.default'), $this->credit->client->credit_filepath($invitation).$this->credit->numberFormatter().'.pdf'))->handle();
// (new UnlinkFile(config('filesystems.default'), $this->credit->client->credit_filepath($invitation).$this->credit->numberFormatter().'.pdf'))->handle();
//30-06-2023
try {
// if (Storage::disk(config('filesystems.default'))->exists($this->invoice->client->invoice_filepath($invitation).$this->invoice->numberFormatter().'.pdf')) {
Storage::disk(config('filesystems.default'))->delete($this->credit->client->credit_filepath($invitation).$this->credit->numberFormatter().'.pdf');
// }
// if (Ninja::isHosted() && Storage::disk('public')->exists($this->invoice->client->invoice_filepath($invitation).$this->invoice->numberFormatter().'.pdf')) {
if (Ninja::isHosted()) {
Storage::disk('public')->delete($this->credit->client->credit_filepath($invitation).$this->credit->numberFormatter().'.pdf');
}
} catch (\Exception $e) {
nlog($e->getMessage());
}
});
return $this;

View File

@ -42,7 +42,7 @@ class MarkSent
->setStatus(Credit::STATUS_SENT)
->applyNumber()
->adjustBalance($this->credit->amount)
->touchPdf()
->deletePdf()
->save();
$this->client

View File

@ -432,6 +432,7 @@ class Email implements ShouldQueue
$this->setGmailMailer();
return $this;
case 'office365':
case 'microsoft':
$this->mailer = 'office365';
$this->setOfficeMailer();
return $this;
@ -445,7 +446,8 @@ class Email implements ShouldQueue
return $this;
default:
break;
$this->mailer = config('mail.default');
return $this;
}
if (Ninja::isSelfHost()) {

View File

@ -168,7 +168,7 @@ class EmailDefaults
*/
private function setBody(): self
{
if (strlen($this->email->email_object->body) > 3) {
// A Custom Message has been set in the email screen.
// return $this;
@ -181,7 +181,7 @@ class EmailDefaults
}
$this->email->email_object->text_body = strip_tags($this->email->email_object->body);
if ($this->template == 'email.template.custom') {
$this->email->email_object->body = (str_replace('$body', $this->email->email_object->body, str_replace(["\r","\n"], "", $this->email->email_object->settings->email_style_custom)));
}
@ -230,7 +230,7 @@ class EmailDefaults
$this->email->email_object->subject = strtr($this->email->email_object->subject, $this->email->email_object->variables);
//06-06-2023 ensure we do not parse markdown in custom templates
if ($this->template != 'custom' && $this->template != 'email.template.custom') {
$this->email->email_object->body = $this->parseMarkdownToHtml($this->email->email_object->body);
@ -303,20 +303,6 @@ class EmailDefaults
$this->email->email_object->entity instanceof Quote ||
$this->email->email_object->entity instanceof Credit)) {
$pdf = ((new CreateRawPdf($this->email->email_object->invitation, $this->email->company->db))->handle());
if ($this->email->email_object->settings->enable_e_invoice && $this->email->email_object->entity instanceof Invoice) {
$xinvoice_path = $this->email->email_object->entity->service()->getEInvoice();
// $xinvoice_path = (new CreateEInvoice($this->email->email_object->entity, true, stream_get_meta_data($tempfile)['uri']))->handle();
// $this->email->email_object->attachments = array_merge($this->email->email_object->attachments, [['file' => base64_encode($pdf), 'name' => $this->email->email_object->entity->numberFormatter().'.pdf']]);
if(Storage::disk(config('filesystems.default'))->exists($xinvoice_path))
$this->email->email_object->attachments = array_merge($this->email->email_object->attachments, [['file' => base64_encode(Storage::get($xinvoice_path)), 'name' => explode(".", $this->email->email_object->entity->getFileName('xml'))[0]."-xinvoice.xml"]]);
}
else {
$this->email->email_object->attachments = array_merge($this->email->email_object->attachments, [['file' => base64_encode($pdf), 'name' => $this->email->email_object->entity->numberFormatter().'.pdf']]);
}
$this->email->email_object->attachments = array_merge($this->email->email_object->attachments, [['file' => base64_encode($pdf), 'name' => $this->email->email_object->entity->numberFormatter().'.pdf']]);
}

View File

@ -84,15 +84,15 @@ class ApplyPayment extends AbstractService
/* Update Pivot Record amount */
$this->payment->invoices->each(function ($inv) use ($amount_paid) {
if ($inv->id == $this->invoice->id) {
$inv->pivot->amount = ($amount_paid * -1);
$inv->pivot->save();
// $inv->pivot->amount = ($amount_paid * -1);
// $inv->pivot->save();
//25-06-2023
$inv->paid_to_date += floatval($amount_paid * -1);
$inv->save();
}
});
$this->invoice->service()->applyNumber()->workFlow()->touchPdf()->save();
$this->invoice->service()->applyNumber()->workFlow()->deletePdf()->save();
return $this->invoice;
}

View File

@ -98,7 +98,9 @@ class ZugferdEInvoice extends AbstractService
if (!empty($item->notes)){
$xrechnung->setDocumentPositionProductDetails($item->product_key, $item->notes);
}
$xrechnung->setDocumentPositionProductDetails($item->product_key);
else {
$xrechnung->setDocumentPositionProductDetails($item->product_key);
}
}
else {
if (!empty($item->notes)){
@ -159,13 +161,7 @@ class ZugferdEInvoice extends AbstractService
}
}
if ($this->invoice->isPartial()) {
$xrechnung->setDocumentSummation($this->invoice->amount, $this->invoice->balance, $invoicing_data->getSubTotal(), $invoicing_data->getTotalSurcharges(), $invoicing_data->getTotalDiscount(), $invoicing_data->getSubTotal(), $invoicing_data->getItemTotalTaxes(), null, $this->invoice->partial);
} else {
$xrechnung->setDocumentSummation($this->invoice->amount, $this->invoice->balance, $invoicing_data->getSubTotal(), $invoicing_data->getTotalSurcharges(), $invoicing_data->getTotalDiscount(), $invoicing_data->getSubTotal(), $invoicing_data->getItemTotalTaxes(), null, 0.0);
}
$xrechnung->setDocumentSummation($this->invoice->amount, $this->invoice->balance, $invoicing_data->getSubTotal(), $invoicing_data->getTotalSurcharges(), $invoicing_data->getTotalDiscount(), $invoicing_data->getSubTotal(), $invoicing_data->getItemTotalTaxes(), 0.0, $this->invoice->amount-$this->invoice->balance);
foreach ($this->tax_map as $item){
$xrechnung->addDocumentTax($item["tax_type"], "VAT", $item["net_amount"], $item["tax_rate"]*$item["net_amount"], $item["tax_rate"]*100);
@ -236,7 +232,8 @@ class ZugferdEInvoice extends AbstractService
}
return $tax_type;
}
private function addtoTaxMap(string $tax_type, float $net_amount, float $tax_rate){
private function addtoTaxMap(string $tax_type, float $net_amount, float $tax_rate): void
{
$hash = hash("md5", $tax_type."-".$tax_rate);
if (array_key_exists($hash, $this->tax_map)){
$this->tax_map[$hash]["net_amount"] += $net_amount;

View File

@ -44,9 +44,7 @@ class HandleCancellation extends AbstractService
$this->invoice->balance = 0;
$this->invoice = $this->invoice->service()->setStatus(Invoice::STATUS_CANCELLED)->save();
//adjust client balance
$this->invoice->client->service()->updateBalance($adjustment)->save();
// $this->invoice->fresh();
$this->invoice->service()->workFlow()->save();
@ -54,16 +52,6 @@ class HandleCancellation extends AbstractService
event('eloquent.updated: App\Models\Invoice', $this->invoice);
$transaction = [
'invoice' => $this->invoice->transaction_event(),
'payment' => [],
'client' => $this->invoice->client->transaction_event(),
'credit' => [],
'metadata' => [],
];
// TransactionLog::dispatch(TransactionEvent::INVOICE_CANCELLED, $transaction, $this->invoice->company->db);
return $this->invoice;
}

View File

@ -115,7 +115,6 @@ class InvoiceService
*/
public function applyPayment(Payment $payment, float $payment_amount)
{
// $this->deletePdf();
$this->invoice = $this->markSent()->save();
$this->invoice = (new ApplyPayment($this->invoice, $payment, $payment_amount))->run();
@ -339,7 +338,7 @@ class InvoiceService
return $item;
})->toArray();
$this->touchPdf();
$this->deletePdf();
return $this;
}
@ -348,13 +347,15 @@ class InvoiceService
{
$this->invoice->load('invitations');
//30-06-2023
$this->invoice->invitations->each(function ($invitation) {
try {
if (Storage::disk(config('filesystems.default'))->exists($this->invoice->client->invoice_filepath($invitation).$this->invoice->numberFormatter().'.pdf')) {
// if (Storage::disk(config('filesystems.default'))->exists($this->invoice->client->invoice_filepath($invitation).$this->invoice->numberFormatter().'.pdf')) {
Storage::disk(config('filesystems.default'))->delete($this->invoice->client->invoice_filepath($invitation).$this->invoice->numberFormatter().'.pdf');
}
// }
if (Ninja::isHosted() && Storage::disk('public')->exists($this->invoice->client->invoice_filepath($invitation).$this->invoice->numberFormatter().'.pdf')) {
// if (Ninja::isHosted() && Storage::disk('public')->exists($this->invoice->client->invoice_filepath($invitation).$this->invoice->numberFormatter().'.pdf')) {
if (Ninja::isHosted()) {
Storage::disk('public')->delete($this->invoice->client->invoice_filepath($invitation).$this->invoice->numberFormatter().'.pdf');
}
} catch (\Exception $e) {
@ -371,11 +372,12 @@ class InvoiceService
$this->invoice->invitations->each(function ($invitation) {
try {
if (Storage::disk(config('filesystems.default'))->exists($this->invoice->client->e_invoice_filepath($invitation).$this->invoice->getFileName("xml"))) {
// if (Storage::disk(config('filesystems.default'))->exists($this->invoice->client->e_invoice_filepath($invitation).$this->invoice->getFileName("xml"))) {
Storage::disk(config('filesystems.default'))->delete($this->invoice->client->e_invoice_filepath($invitation).$this->invoice->getFileName("xml"));
}
// }
if (Ninja::isHosted() && Storage::disk('public')->exists($this->invoice->client->e_invoice_filepath($invitation).$this->invoice->getFileName("xml"))) {
// if (Ninja::isHosted() && Storage::disk('public')->exists($this->invoice->client->e_invoice_filepath($invitation).$this->invoice->getFileName("xml"))) {
if (Ninja::isHosted()) {
Storage::disk('public')->delete($this->invoice->client->e_invoice_filepath($invitation).$this->invoice->getFileName("xml"));
}
} catch (\Exception $e) {
@ -403,7 +405,7 @@ class InvoiceService
})->toArray();
$this->invoice = $this->invoice->calc()->getInvoice();
$this->touchPdf();
$this->deletePdf();
/* 24-03-2022 */
$new_balance = $this->invoice->balance;

View File

@ -102,7 +102,7 @@ class MarkPaid extends AbstractService
$this->invoice
->service()
->applyNumber()
->touchPdf()
->deletePdf()
->save();
$payment->ledger()

View File

@ -54,7 +54,7 @@ class TriggeredActions extends AbstractService
}
if ($this->request->has('send_email') && $this->request->input('send_email') == 'true') {
$this->invoice->service()->markSent()->touchPdf()->save();
$this->invoice->service()->markSent()->save();
$this->sendEmail();
$this->updated = false;
}

View File

@ -33,14 +33,7 @@ class SendEmail
$this->contact = $this->payment->client->contacts()->first();
}
// $this->payment->invoices->sortByDesc('id')->first(function ($invoice) {
// $invoice->invitations->each(function ($invitation) {
// if (!$invitation->contact->trashed() && $invitation->contact->email) {
EmailPayment::dispatch($this->payment, $this->payment->company, $this->contact);
// event(new PaymentWasEmailed($this->payment, $this->payment->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
// }
// });
// });
}
}

View File

@ -79,7 +79,7 @@ class UpdateInvoicePayment
$invoice = $invoice->service()
->clearPartial()
->updateStatus()
->touchPdf()
->deletePdf()
->workFlow()
->save();

View File

@ -653,7 +653,7 @@ class PdfBuilder
$data[$key][$table_type.".{$_table_type}4"] = strlen($item->custom_value4) >= 1 ? $helpers->formatCustomFieldValue($this->service->company->custom_fields, "{$_table_type}4", $item->custom_value4, $this->service->config->currency_entity) : '';
if ($item->quantity > 0 || $item->cost > 0) {
$data[$key][$table_type.'.quantity'] = $this->service->config->formatMoney($item->quantity);
$data[$key][$table_type.'.quantity'] = $item->quantity;
$data[$key][$table_type.'.unit_cost'] = $this->service->config->formatMoney($item->cost);

View File

@ -232,6 +232,9 @@ class PdfMock
'$secondary_font_url' => 'https://fonts.googleapis.com/css2?family=Roboto&display=swap',
'$contact.signature' => '',
'$company_logo_size' => $this->settings->company_logo_size ?: '65%',
'$product.tax_rate1' => ctrans('texts.tax'),
'$product.tax_rate2' => ctrans('texts.tax'),
'$product.tax_rate3' => ctrans('texts.tax'),
'$product.tax_name1' => '',
'$product.tax_name2' => '',
'$product.tax_name3' => '',
@ -688,8 +691,11 @@ class PdfMock
'$net_subtotal_label' => ctrans('texts.net_subtotal'),
'$credit.total_label' => ctrans('texts.total'),
'$quote.amount_label' => ctrans('texts.amount'),
'$description_label' => ctrans('texts.description'),
'$product.tax_rate1_label' => ctrans('texts.tax'),
'$product.tax_rate2_label' => ctrans('texts.tax'),
'$product.tax_rate3_label' => ctrans('texts.tax'),
'$product.tax_label' => ctrans('texts.tax'),
'$description_label' => ctrans('texts.description'),
'$your_entity_label' => ctrans("texts.your_{$this->entity_string}"),
'$view_button_label' => ctrans('texts.view'),
'$status_logo_label' => ctrans('texts.logo'),
@ -782,9 +788,9 @@ class PdfMock
'$amount_label' => ctrans('texts.amount'),
'$notes_label' => ctrans('texts.notes'),
'$terms_label' => ctrans('texts.terms'),
'tax_rate1_label' => ctrans('texts.tax_rate1'),
'tax_rate2_label' => ctrans('texts.tax_rate2'),
'tax_rate3_label' => ctrans('texts.tax_rate3'),
'$tax_rate1_label' => ctrans('texts.tax_rate1'),
'$tax_rate2_label' => ctrans('texts.tax_rate2'),
'$tax_rate3_label' => ctrans('texts.tax_rate3'),
'$phone_label' => ctrans('texts.phone'),
'$email_label' => ctrans('texts.email'),
'$taxes_label' => ctrans('texts.taxes'),

View File

@ -19,11 +19,8 @@ use Illuminate\Support\Facades\Storage;
class GetPurchaseOrderPdf extends AbstractService
{
public function __construct(PurchaseOrder $purchase_order, VendorContact $contact = null)
public function __construct(public PurchaseOrder $purchase_order, public ?VendorContact $contact = null)
{
$this->purchase_order = $purchase_order;
$this->contact = $contact;
}
public function run()

View File

@ -43,10 +43,6 @@ class TriggeredActions extends AbstractService
$this->purchase_order = $this->purchase_order->service()->markSent()->touchPdf()->save();
}
// if ($this->request->has('cancel') && $this->request->input('cancel') == 'true') {
// $this->purchase_order = $this->purchase_order->service()->handleCancellation()->save();
// }
if ($this->request->has('save_default_footer') && $this->request->input('save_default_footer') == 'true') {
$company = $this->purchase_order->company;
$settings = $company->settings;

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