Merge pull request #8867 from turbo124/v5-develop

v5.7.29
This commit is contained in:
David Bomba 2023-10-12 13:31:22 +11:00 committed by GitHub
commit 5be3b38f96
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 421 additions and 60 deletions

View File

@ -1 +1 @@
5.7.28
5.7.29

View File

@ -63,17 +63,25 @@ class CreateAccount extends Command
private function createAccount()
{
$settings = CompanySettings::defaults();
$settings->name = "Untitled Company";
$settings->currency_id = '1';
$settings->language_id = '1';
$account = Account::factory()->create();
$company = Company::factory()->create([
'account_id' => $account->id,
'portal_domain' => config('ninja.app_url'),
'portal_mode' => 'domain',
'settings' => $settings,
]);
$company->client_registration_fields = ClientRegistrationFields::generate();
$company->save();
$account->default_company_id = $company->id;
$account->set_react_as_default_ap = true;
$account->save();
$email = $this->option('email') ?? 'admin@example.com';

View File

@ -841,6 +841,8 @@ class CompanySettings extends BaseSettings
{
$notification = new stdClass;
$notification->email = [];
$notification->email = ['invoice_sent_all'];
// $notification->email = ['all_notifications'];
return $notification;

View File

@ -144,7 +144,7 @@ class InvoiceItemSum
public function process(): self
{
if (!$this->invoice->line_items || !is_array($this->invoice->line_items)) {
if (!$this->invoice->line_items || !is_iterable($this->invoice->line_items)) {
$this->items = [];
return $this;
}

View File

@ -131,8 +131,7 @@ class InvoiceItemSumInclusive
public function process()
{
if (! $this->invoice->line_items || ! is_array($this->invoice->line_items) || count($this->invoice->line_items) == 0) {
if (!$this->invoice->line_items || ! is_iterable($this->invoice->line_items) || count($this->invoice->line_items) == 0) {
return $this;
}

View File

@ -195,7 +195,7 @@ class InvoiceController extends Controller
//format data
$invoices->map(function ($invoice) {
$invoice->service()->removeUnpaidGatewayFees();
// $invoice->service()->removeUnpaidGatewayFees();
$invoice->balance = $invoice->balance > 0 ? Number::formatValue($invoice->balance, $invoice->client->currency()) : 0;
$invoice->partial = $invoice->partial > 0 ? Number::formatValue($invoice->partial, $invoice->client->currency()) : 0;

View File

@ -290,10 +290,14 @@ class PreviewController extends BaseController
return $maker->getCompiledHTML();
}
} catch(\Exception $e) {
// nlog($e->getMessage());
DB::connection(config('database.default'))->rollBack();
return;
if (DB::connection(config('database.default'))->transactionLevel() > 0) {
DB::connection(config('database.default'))->rollBack();
}
return response()->json(['message' => 'Error generating preview. Please retry again shortly.'], 400);
}
//if phantom js...... inject here..

View File

@ -62,6 +62,7 @@ class Statement
}
$variables = $html->generateLabelsAndValues();
$variables['values']['$show_paid_stamp'] = 'none'; //do not show paid stamp on statement
$state = [
'template' => $template->elements([

View File

@ -29,7 +29,7 @@ class AddGatewayFee extends AbstractService
{
$gateway_fee = round($this->company_gateway->calcGatewayFee($this->amount, $this->gateway_type_id, $this->invoice->uses_inclusive_taxes), $this->invoice->client->currency()->precision);
if (! $gateway_fee) {
if (! $gateway_fee || $gateway_fee == 0) {
return $this->invoice;
}
@ -150,7 +150,7 @@ class AddGatewayFee extends AbstractService
$this->invoice
->ledger()
->updateInvoiceBalance($adjustment * -1, 'Adjustment for adding gateway fee');
->updateInvoiceBalance($adjustment * -1, 'Adjustment for adding gateway DISCOUNT');
}
return $this->invoice;

View File

@ -468,7 +468,7 @@ class FacturaEInvoice extends AbstractService
{
$company = $this->invoice->company;
if($company->getSetting('classification'))
if($company->getSetting('classification') == 'individual')
return $this->setIndividualSeller();
$seller = new FacturaeParty([
@ -512,7 +512,7 @@ class FacturaEInvoice extends AbstractService
$seller = new FacturaeParty([
"isLegalEntity" => false,
"taxNumber" => $company->settings->vat_number,
"name" => $company->getSetting('classification') === 'individual' ? substr($company->owner()->present()->name(), 0, 40) : substr($company->present()->name(), 0, 40),
// "name" => $company->getSetting('classification') === 'individual' ? substr($company->owner()->present()->name(), 0, 40) : substr($company->present()->name(), 0, 40),
"address" => substr($company->settings->address1, 0, 80),
"postCode" => substr($this->invoice->client->postal_code, 0, 5),
"town" => substr($company->settings->city, 0, 50),
@ -529,7 +529,8 @@ class FacturaEInvoice extends AbstractService
"fax" => "",
"website" => substr($company->settings->website, 0, 50),
// "contactPeople" => substr($company->owner()->present()->name(), 0, 40),
"firstSurname" => $company->owner()->present()->firstName(),
"name" => $company->owner()->present()->firstName(),
// "firstSurname" => $company->owner()->present()->firstName(),
"lastSurname" => $company->owner()->present()->lastName(),
]);

View File

@ -408,14 +408,15 @@ class InvoiceService
$pre_count = count($this->invoice->line_items);
$this->invoice->line_items = collect($this->invoice->line_items)
$items = collect($this->invoice->line_items)
->reject(function ($item) {
return $item->type_id == '3';
})->toArray();
$this->invoice->line_items = array_values($items);
$this->invoice = $this->invoice->calc()->getInvoice();
// $this->deletePdf();
$this->deleteEInvoice();
$this->deleteEInvoice(); //@deprecated
/* 24-03-2022 */
$new_balance = $this->invoice->balance;

View File

@ -12,21 +12,22 @@
namespace App\Utils;
use App\Helpers\Epc\EpcQrGenerator;
use App\Helpers\SwissQr\SwissQrGenerator;
use App\Models\Country;
use App\Models\CreditInvitation;
use App\Models\GatewayType;
use App\Models\InvoiceInvitation;
use App\Models\QuoteInvitation;
use App\Models\RecurringInvoiceInvitation;
use App\Utils\Traits\AppSetup;
use App\Utils\Traits\DesignCalculator;
use App\Utils\Traits\MakesDates;
use App\Utils\Traits\MakesHash;
use Exception;
use App\Models\Account;
use App\Models\Country;
use App\Models\GatewayType;
use App\Utils\Traits\AppSetup;
use App\Models\QuoteInvitation;
use App\Utils\Traits\MakesHash;
use App\Models\CreditInvitation;
use App\Utils\Traits\MakesDates;
use App\Models\InvoiceInvitation;
use App\Helpers\Epc\EpcQrGenerator;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Cache;
use App\Utils\Traits\DesignCalculator;
use App\Helpers\SwissQr\SwissQrGenerator;
use App\Models\RecurringInvoiceInvitation;
class HtmlEngine
{
@ -1013,14 +1014,14 @@ html {
*/
protected function generateEntityImagesMarkup()
{
if ($this->client->getSetting('embed_documents') === false) {
return '';
}
// if (!$this->client->getSetting('embed_documents') && !$this->company->account->hasFeature(Account::FEATURE_DOCUMENTS)) {
// return '';
// }
$dom = new \DOMDocument('1.0', 'UTF-8');
$container = $dom->createElement('div');
$container->setAttribute('style', 'display:grid; grid-auto-flow: row; grid-template-columns: repeat(4, 1fr); grid-template-rows: repeat(2, 1fr);');
$container->setAttribute('style', 'display:grid; grid-auto-flow: row; grid-template-columns: repeat(2, 1fr); grid-template-rows: repeat(2, 1fr);justify-items: center;');
foreach ($this->entity->documents as $document) {
if (!$document->isImage()) {
@ -1029,8 +1030,8 @@ html {
$image = $dom->createElement('img');
$image->setAttribute('src', $document->generateUrl());
$image->setAttribute('style', 'max-height: 100px; margin-top: 20px;');
$image->setAttribute('src', "data:image/png;base64,".base64_encode($document->getFile()));
$image->setAttribute('style', 'max-width: 50%; margin-top: 20px;');
$container->appendChild($image);
}

View File

@ -33,7 +33,7 @@ trait UserNotifies
$notifiable_methods = [];
$notifications = $company_user->notifications;
if ($invitation->company->is_disabled &&
if ($company_user->company->is_disabled &&
is_array($notifications->email) ||
$company_user->trashed() ||
! $company_user->user ||
@ -43,7 +43,7 @@ trait UserNotifies
//if a user owns this record or is assigned to it, they are attached the permission for notification.
if ($invitation->{$entity_name}->user_id == $company_user->user_id || $invitation->{$entity_name}->assigned_user_id == $company_user->user_id) {
// $required_permissions = $this->addSpecialUserPermissionForEntity($invitation->{$entity_name}, $required_permissions);
} else {
$required_permissions = $this->removeSpecialUserPermissionForEntity($invitation->{$entity_name}, $required_permissions);
}
@ -60,7 +60,7 @@ trait UserNotifies
$notifiable_methods = [];
$notifications = $company_user->notifications;
if ($entity->company->is_disabled ||
if ($company_user->company->is_disabled ||
! $notifications ||
$company_user->trashed() ||
! $company_user->user ||
@ -84,23 +84,6 @@ trait UserNotifies
private function addSpecialUserPermissionForEntity($entity, array $required_permissions) :array
{
return array_merge($required_permissions, ['all_notifications', 'all_user_notifications']);
// switch ($entity) {
// case $entity instanceof Payment || $entity instanceof Client: //we pass client also as this is the proxy for Payment Failures (ie, there is no payment)
// return array_merge($required_permissions, ['all_notifications', 'all_user_notifications', 'payment_failure_user', 'payment_success_user']);
// case $entity instanceof Invoice:
// return array_merge($required_permissions, ['all_notifications', 'all_user_notifications', 'invoice_created_user', 'invoice_sent_user', 'invoice_viewed_user', 'invoice_late_user']);
// case $entity instanceof Quote:
// return array_merge($required_permissions, ['all_notifications', 'all_user_notifications', 'quote_created_user', 'quote_sent_user', 'quote_viewed_user', 'quote_approved_user', 'quote_expired_user']);
// case $entity instanceof Credit:
// return array_merge($required_permissions, ['all_notifications', 'all_user_notifications', 'credit_created_user', 'credit_sent_user', 'credit_viewed_user']);
// case $entity instanceof PurchaseOrder:
// return array_merge($required_permissions, ['all_notifications', 'all_user_notifications', 'purchase_order_created_user', 'purchase_order_sent_user', 'purchase_order_viewed_user']);
// case $entity instanceof Product:
// return array_merge($required_permissions, ['all_notifications', 'all_user_notifications', 'inventory_user', 'inventory_all']);
// default:
// return [];
// }
}
private function removeSpecialUserPermissionForEntity($entity, $required_permissions)

View File

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

View File

@ -0,0 +1,39 @@
<?php
use App\Models\Currency;
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
$cur = Currency::find(119);
if(!$cur) {
$cur = new \App\Models\Currency();
$cur->id = 119;
$cur->code = 'MGA';
$cur->name = 'Malagasy ariary';
$cur->symbol = 'Ar';
$cur->thousand_separator = ',';
$cur->decimal_separator = '.';
$cur->precision = 0;
$cur->save();
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
//
}
};

View File

@ -141,6 +141,7 @@ class CurrenciesSeeder extends Seeder
['id' => 116, 'name' => 'Silver Troy Ounce', 'code' => 'XAG', 'symbol' => 'XAG', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['id' => 117, 'name' => 'Gold Troy Ounce', 'code' => 'XAU', 'symbol' => 'XAU', 'precision' => '3', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['id' => 118, 'name' => 'Nicaraguan Córdoba', 'code' => 'NIO', 'symbol' => 'C$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
['id' => 119, 'name' => 'Malagasy ariary', 'code' => 'MGA', 'symbol' => 'Ar', 'precision' => '0', 'thousand_separator' => ',', 'decimal_separator' => '.'],
];
foreach ($currencies as $currency) {

View File

@ -89,10 +89,16 @@ class RandomDataSeeder extends Seeder
Model::unguard();
$faker = \Faker\Factory::create();
$settings= CompanySettings::defaults();
$settings->name = "Random Test Company";
$settings->currency_id = '1';
$settings->language_id = '1';
$account = Account::factory()->create();
$company = Company::factory()->create([
'account_id' => $account->id,
'settings' => $settings,
]);
$account->default_company_id = $company->id;

View File

@ -37,7 +37,9 @@
@endcomponent
@endif
@if (count($tokens) > 0)
@include('portal.ninja2020.gateways.includes.pay_now')
@endif
@endsection
@push('footer')

View File

@ -38,7 +38,9 @@
@endcomponent
@endif
@if (count($tokens) > 0)
@include('portal.ninja2020.gateways.includes.pay_now')
@endif
@endsection
@push('footer')

View File

@ -38,7 +38,9 @@
@endcomponent
@endif
@if (count($tokens) > 0)
@include('portal.ninja2020.gateways.includes.pay_now')
@endif
@endsection
@push('footer')

View File

@ -22,6 +22,8 @@ use App\Jobs\Util\ReminderJob;
use App\DataMapper\InvoiceItem;
use App\DataMapper\FeesAndLimits;
use App\DataMapper\CompanySettings;
use App\Factory\InvoiceItemFactory;
use App\Factory\ClientGatewayTokenFactory;
use Illuminate\Foundation\Testing\DatabaseTransactions;
/**
@ -93,6 +95,313 @@ class LateFeeTest extends TestCase
return $client;
}
public function testAddLateFeeAppropriately()
{
$invoice_item = new InvoiceItem;
$invoice_item->type_id = '5';
$invoice_item->product_key = trans('texts.fee');
$invoice_item->notes = ctrans('texts.late_fee_added', ['date' => 'xyz']);
$invoice_item->quantity = 1;
$invoice_item->cost = 20;
$invoice_items = $this->invoice->line_items;
$invoice_items[] = $invoice_item;
$this->invoice->line_items = $invoice_items;
$this->assertGreaterThan(1, count($this->invoice->line_items));
/**Refresh Invoice values*/
$invoice = $this->invoice->calc()->getInvoice();
$this->assertGreaterThan(1, count($this->invoice->line_items));
}
public function testModelBehaviourInsideMap()
{
$i = Invoice::factory()->count(5)
->create([
'client_id' => $this->client->id,
'company_id' => $this->company->id,
'user_id' => $this->user->id,
'tax_name1' => '',
'tax_rate1' => 0,
'tax_name2' => '',
'tax_rate2' => 0,
'tax_name3' => '',
'tax_rate3' => 0,
'discount' => 0,
]);
$i->each(function($invoice){
$this->assertGreaterThan(1, count($invoice->line_items));
});
$this->assertCount(5, $i);
$invoices = $i->map(function ($invoice) {
$invoice->service()->removeUnpaidGatewayFees();
return $invoice;
});
$invoices->each(function ($invoice) {
$this->assertGreaterThan(1, count($invoice->line_items));
});
$ids = $invoices->pluck('id');
$invoices = $i->map(function ($invoice) {
$line_items = $invoice->line_items;
$item = new InvoiceItem;
$item->type_id = '3';
$item->product_key = trans('texts.fee');
$item->quantity = 1;
$item->cost = 10;
$line_items[] = $item;
$item = new InvoiceItem;
$item->type_id = '5';
$item->product_key = trans('texts.fee');
$item->quantity = 1;
$item->cost = 10;
$line_items[] = $item;
$invoice->line_items = $line_items;
$invoice->saveQuietly();
return $invoice;
});
$invoices = Invoice::whereIn('id', $ids)->cursor()->map(function ($invoice){
$this->assertGreaterThan(0, count($invoice->line_items));
$invoice->service()->removeUnpaidGatewayFees();
$invoice = $invoice->fresh();
$this->assertGreaterThan(0, count($invoice->line_items));
return $invoice;
});
$invoices->each(function ($invoice) {
$this->assertGreaterThan(0, count($invoice->line_items));
});
}
public function testCollectionPassesIsArray()
{
$line_items = collect($this->invoice->line_items);
$this->assertTrue(is_array($this->invoice->line_items));
$this->assertTrue(is_iterable($line_items));
$this->assertFalse(is_array($line_items));
}
public function testLineItemResiliency()
{
$line_count = count($this->invoice->line_items);
$this->assertGreaterThan(0, $line_count);
$this->invoice->service()->removeUnpaidGatewayFees();
$this->invoice = $this->invoice->fresh();
$this->assertCount($line_count, $this->invoice->line_items);
}
public function testCollectionAsLineItemArray()
{
$i = Invoice::factory()->create([
'client_id' => $this->client->id,
'company_id' => $this->company->id,
'user_id' => $this->user->id,
'tax_name1' => '',
'tax_rate1' => 0,
'tax_name2' => '',
'tax_rate2' => 0,
'tax_name3' => '',
'tax_rate3' => 0,
'discount' => 0,
]);
$line_items = [];
$item = InvoiceItemFactory::create();
$item->quantity = 1;
$item->cost = 10;
$item->type_id = '1';
$line_items[] = $item;
$item = new InvoiceItem;
$item->type_id = '5';
$item->product_key = trans('texts.fee');
$item->quantity = 1;
$item->cost = 10;
$line_items[] = $item;
$item = InvoiceItemFactory::create();
$item->quantity = 1;
$item->cost = 1;
$item->type_id = '3';
$line_items[] = $item;
$i->line_items = $line_items;
$this->assertEquals(3, count($line_items));
$i = $i->calc()->getInvoice();
$this->assertEquals(3, count($i->line_items));
$this->assertEquals(21, $i->amount);
// $invoice_items = collect($invoice_items)->filter(function ($item) {
// return $item->type_id != '3';
// });
// $this->invoice->line_items = $invoice_items;
}
public function testLateFeeRemovals()
{
$data = [];
$data[1]['min_limit'] = -1;
$data[1]['max_limit'] = -1;
$data[1]['fee_amount'] = 0.00;
$data[1]['fee_percent'] = 1;
$data[1]['fee_tax_name1'] = '';
$data[1]['fee_tax_rate1'] = 0;
$data[1]['fee_tax_name2'] = '';
$data[1]['fee_tax_rate2'] = 0;
$data[1]['fee_tax_name3'] = '';
$data[1]['fee_tax_rate3'] = 0;
$data[1]['adjust_fee_percent'] = false;
$data[1]['fee_cap'] = 0;
$data[1]['is_enabled'] = true;
$cg = new \App\Models\CompanyGateway;
$cg->company_id = $this->company->id;
$cg->user_id = $this->user->id;
$cg->gateway_key = 'd14dd26a37cecc30fdd65700bfb55b23';
$cg->require_cvv = true;
$cg->require_billing_address = true;
$cg->require_shipping_address = true;
$cg->update_details = true;
$cg->config = encrypt(config('ninja.testvars.stripe'));
$cg->fees_and_limits = $data;
$cg->save();
$cgt = ClientGatewayTokenFactory::create($this->company->id);
$cgt->client_id = $this->client->id;
$cgt->token = '';
$cgt->gateway_customer_reference = '';
$cgt->gateway_type_id = 1;
$cgt->company_gateway_id = $cg->id;
$cgt->save();
$i = Invoice::factory()->create([
'client_id' => $this->client->id,
'company_id' => $this->company->id,
'user_id' => $this->user->id,
'tax_name1' => '',
'tax_rate1' => 0,
'tax_name2' => '',
'tax_rate2' => 0,
'tax_name3' => '',
'tax_rate3' => 0,
'discount' => 0,
]);
$line_items = [];
$item = InvoiceItemFactory::create();
$item->quantity = 1;
$item->cost = 10;
$item->type_id = '1';
$line_items[] = $item;
$item = new InvoiceItem;
$item->type_id = '5';
$item->product_key = trans('texts.fee');
$item->quantity = 1;
$item->cost = 10;
$line_items[] = $item;
$item = InvoiceItemFactory::create();
$item->quantity = 1;
$item->cost = 1;
$item->type_id = '3';
$line_items[] = $item;
$i->line_items = $line_items;
$i = $i->calc()->getInvoice();
$this->assertEquals(3, count($i->line_items));
$this->assertEquals(21, $i->amount);
$invoice_items = (array) $i->line_items;
$invoice_items = collect($invoice_items)->filter(function ($item) {
return $item->type_id != '3';
});
$i->line_items = $invoice_items;
$i = $i->calc()->getInvoice();
$this->assertEquals(20, $i->amount);
$i->line_items = collect($i->line_items)
->reject(function ($item) {
return $item->type_id == '3';
})->toArray();
$this->assertEquals(2, count($i->line_items));
$i->service()->autoBill();
$i = $i->fresh();
$this->assertCount(2, $i->line_items);
$this->assertEquals(20, $i->amount);
$line_items = $i->line_items;
$item = new InvoiceItem;
$item->type_id = '5';
$item->product_key = trans('texts.fee');
$item->quantity = 1;
$item->cost = 10;
$line_items[] = $item;
$i->line_items = $line_items;
$i = $i->calc()->getInvoice();
$this->assertEquals(30, $i->amount);
$this->assertCount(3, $i->line_items);
$i->service()->autoBill();
$i = $i->fresh();
$this->assertEquals(30, $i->amount);
$this->assertCount(3, $i->line_items);
}
public function testLateFeeAdded()
{