diff --git a/app/DataMapper/CompanySettings.php b/app/DataMapper/CompanySettings.php index d2241f95b2f5..cbfaea8767cb 100644 --- a/app/DataMapper/CompanySettings.php +++ b/app/DataMapper/CompanySettings.php @@ -497,7 +497,10 @@ class CompanySettings extends BaseSettings public $use_unapplied_payment = 'off'; //always, option, off //@implemented + public $enable_rappen_rounding = false; + public static $casts = [ + 'enable_rappen_rounding' => 'bool', 'use_unapplied_payment' => 'string', 'show_pdfhtml_on_mobile' => 'bool', 'payment_email_all_contacts' => 'bool', diff --git a/app/Helpers/Invoice/InvoiceSum.php b/app/Helpers/Invoice/InvoiceSum.php index e1ae39609d80..f142ce0b9f4a 100644 --- a/app/Helpers/Invoice/InvoiceSum.php +++ b/app/Helpers/Invoice/InvoiceSum.php @@ -52,6 +52,7 @@ class InvoiceSum public InvoiceItemSum $invoice_items; + private $rappen_rounding = false; /** * Constructs the object with Invoice and Settings object. * @@ -63,8 +64,11 @@ class InvoiceSum if ($this->invoice->client) { $this->precision = $this->invoice->client->currency()->precision; + $this->rappen_rounding = $this->invoice->client->getSetting('enable_rappen_rounding'); } else { $this->precision = $this->invoice->vendor->currency()->precision; + $this->rappen_rounding = $this->invoice->vendor->getSetting('enable_rappen_rounding'); + } $this->tax_map = new Collection(); @@ -250,14 +254,22 @@ class InvoiceSum } } /* Set new calculated total */ - /** @todo - rappen rounding here */ $this->invoice->amount = $this->formatValue($this->getTotal(), $this->precision); + if($this->rappen_rounding) + $this->invoice->amount = $this->roundRappen($this->invoice->amount); + $this->invoice->total_taxes = $this->getTotalTaxes(); return $this; } + + function roundRappen($value): float + { + return round($value / .05, 0) * .05; + } + public function getSubTotal() { return $this->sub_total; diff --git a/app/Helpers/Invoice/InvoiceSumInclusive.php b/app/Helpers/Invoice/InvoiceSumInclusive.php index 13ebf8940924..d7c8a75dbeae 100644 --- a/app/Helpers/Invoice/InvoiceSumInclusive.php +++ b/app/Helpers/Invoice/InvoiceSumInclusive.php @@ -47,6 +47,8 @@ class InvoiceSumInclusive private $precision; + private $rappen_rounding = false; + public InvoiceItemSumInclusive $invoice_items; /** * Constructs the object with Invoice and Settings object. @@ -59,8 +61,10 @@ class InvoiceSumInclusive if ($this->invoice->client) { $this->precision = $this->invoice->client->currency()->precision; + $this->rappen_rounding = $this->invoice->client->getSetting('enable_rappen_rounding'); } else { $this->precision = $this->invoice->vendor->currency()->precision; + $this->rappen_rounding = $this->invoice->vendor->getSetting('enable_rappen_rounding'); } $this->tax_map = new Collection(); @@ -271,10 +275,19 @@ class InvoiceSumInclusive /** @todo - rappen rounding here */ $this->invoice->amount = $this->formatValue($this->getTotal(), $this->precision); + if($this->rappen_rounding) { + $this->invoice->amount = $this->roundRappen($this->invoice->amount); + } + $this->invoice->total_taxes = $this->getTotalTaxes(); return $this; } + + function roundRappen($value): float + { + return round($value / .05, 0) * .05; + } public function getSubTotal() { diff --git a/app/Models/Client.php b/app/Models/Client.php index 61bda7230946..6ac021a4bc43 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -485,7 +485,7 @@ class Client extends BaseModel implements HasLocalePreference } /*Company Settings*/ - elseif ((property_exists($this->company->settings, $setting) != false) && (isset($this->company->settings->{$setting}) !== false)) { + elseif ((property_exists($this->company->settings, $setting) !== false) && (isset($this->company->settings->{$setting}) !== false)) { return $this->company->settings->{$setting}; } elseif (property_exists(CompanySettings::defaults(), $setting)) { return CompanySettings::defaults()->{$setting}; diff --git a/app/PaymentDrivers/GoCardlessPaymentDriver.php b/app/PaymentDrivers/GoCardlessPaymentDriver.php index 0ec94d1032c5..afd08c49a6aa 100644 --- a/app/PaymentDrivers/GoCardlessPaymentDriver.php +++ b/app/PaymentDrivers/GoCardlessPaymentDriver.php @@ -558,4 +558,17 @@ class GoCardlessPaymentDriver extends BaseDriver { return render('gateways.gocardless.verification'); } + + public function auth(): bool + { + try { + $customers = $this->init()->gateway->customers()->list(); + return true; + } + catch(\Exception $e){ + + } + + return false; + } } diff --git a/app/PaymentDrivers/PayPalPPCPPaymentDriver.php b/app/PaymentDrivers/PayPalPPCPPaymentDriver.php index 5b757d907600..25fb85094884 100644 --- a/app/PaymentDrivers/PayPalPPCPPaymentDriver.php +++ b/app/PaymentDrivers/PayPalPPCPPaymentDriver.php @@ -560,5 +560,18 @@ class PayPalPPCPPaymentDriver extends BaseDriver PayPalWebhook::dispatch($request->all(), $request->headers->all(), $this->access_token); } + + public function auth(): bool + { + try { + $this->init()->getClientToken(); + return true; + } + catch(\Exception $e) { + + } + + return false; + } } diff --git a/app/PaymentDrivers/PayPalRestPaymentDriver.php b/app/PaymentDrivers/PayPalRestPaymentDriver.php index f08decd67501..ae6d68e6f301 100644 --- a/app/PaymentDrivers/PayPalRestPaymentDriver.php +++ b/app/PaymentDrivers/PayPalRestPaymentDriver.php @@ -81,11 +81,7 @@ class PayPalRestPaymentDriver extends BaseDriver public function init() { - // $this->omnipay_gateway = Omnipay::create( - // $this->company_gateway->gateway->provider - // ); - // $this->omnipay_gateway->initialize((array) $this->company_gateway->getConfig()); $this->api_endpoint_url = $this->company_gateway->getConfigField('testMode') ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com'; $secret = $this->company_gateway->getConfigField('secret'); @@ -380,61 +376,6 @@ class PayPalRestPaymentDriver extends BaseDriver return $r->json()['id']; - - - // $_invoice = collect($this->payment_hash->data->invoices)->first(); - - // $invoice = Invoice::withTrashed()->find($this->decodePrimaryKey($_invoice->invoice_id)); - - // $order = [ - // "intent" => "CAPTURE", - // "payer" => [ - // "name" => [ - // "given_name" => $this->client->present()->first_name(), - // "surname" => $this->client->present()->last_name(), - // ], - // "email_address" => $this->client->present()->email(), - // "address" => [ - // "address_line_1" => $this->client->address1, - // "address_line_2" => $this->client->address2, - // "admin_area_1" => $this->client->city, - // "admin_area_2" => $this->client->state, - // "postal_code" => $this->client->postal_code, - // "country_code" => $this->client->country->iso_3166_2, - // ] - // ], - // "purchase_units" => [ - // [ - // "description" => ctrans('texts.invoice_number').'# '.$invoice->number, - // "invoice_id" => $invoice->number, - // "amount" => [ - // "value" => (string)$data['amount_with_fee'], - // "currency_code" => $this->client->currency()->code, - // "breakdown" => [ - // "item_total" => [ - // "currency_code" => $this->client->currency()->code, - // "value" => (string)$data['amount_with_fee'] - // ] - // ] - // ], - // "items" => [ - // [ - // "name" => ctrans('texts.invoice_number').'# '.$invoice->number, - // "quantity" => "1", - // "unit_amount" => [ - // "currency_code" => $this->client->currency()->code, - // "value" => (string)$data['amount_with_fee'] - // ], - // ], - // ], - // ] - // ] - // ]; - - // $r = $this->gatewayRequest('/v2/checkout/orders', 'post', $order); - - // return $r->json()['id']; - } private function getShippingAddress(): ?array @@ -520,5 +461,17 @@ class PayPalRestPaymentDriver extends BaseDriver return 0; } + public function auth(): bool + { + try { + $this->init()->getClientToken(); + return true; + } + catch(\Exception $e) { + + } + + return false; + } } diff --git a/app/PaymentDrivers/PaytracePaymentDriver.php b/app/PaymentDrivers/PaytracePaymentDriver.php index 4f76b95645a2..2d262e105cae 100644 --- a/app/PaymentDrivers/PaytracePaymentDriver.php +++ b/app/PaymentDrivers/PaytracePaymentDriver.php @@ -246,4 +246,18 @@ class PaytracePaymentDriver extends BaseDriver return false; } + + public function auth(): bool + { + try { + $this->init()->generateAuthHeaders() && strlen($this->company_gateway->getConfigField('integratorId')) > 2; + return true; + } + catch(\Exception $e){ + + } + + return false; + + } } diff --git a/app/PaymentDrivers/SquarePaymentDriver.php b/app/PaymentDrivers/SquarePaymentDriver.php index d58aa4f7cf9a..f0420f1af8e3 100644 --- a/app/PaymentDrivers/SquarePaymentDriver.php +++ b/app/PaymentDrivers/SquarePaymentDriver.php @@ -429,4 +429,17 @@ class SquarePaymentDriver extends BaseDriver return $amount; } + + public function auth(): bool + { + + $api_response = $this->init() + ->square + ->getCustomersApi() + ->listCustomers(); + + + return (bool) count($api_response->getErrors()) == 0; + + } } diff --git a/app/Services/Pdf/PdfMock.php b/app/Services/Pdf/PdfMock.php index d6fb4e5aa53c..1afc3ef8a49b 100644 --- a/app/Services/Pdf/PdfMock.php +++ b/app/Services/Pdf/PdfMock.php @@ -113,24 +113,30 @@ class PdfMock /** @var \App\Models\Invoice | \App\Models\Credit | \App\Models\Quote $entity */ $entity = Invoice::factory()->make(); $entity->client = Client::factory()->make(['settings' => $settings]); + $entity->client->setRelation('company', $this->company); $entity->invitation = InvoiceInvitation::factory()->make(); break; case 'quote': /** @var \App\Models\Invoice | \App\Models\Credit | \App\Models\Quote $entity */ $entity = Quote::factory()->make(); $entity->client = Client::factory()->make(['settings' => $settings]); + $entity->client->setRelation('company', $this->company); $entity->invitation = QuoteInvitation::factory()->make(); break; case 'credit': /** @var \App\Models\Invoice | \App\Models\Credit | \App\Models\Quote $entity */ $entity = Credit::factory()->make(); $entity->client = Client::factory()->make(['settings' => $settings]); + $entity->client->setRelation('company', $this->company); $entity->invitation = CreditInvitation::factory()->make(); break; case 'purchase_order': - /** @var \App\Models\Invoice | \App\Models\Credit | \App\Models\Quote $entity */ + + /** @var \App\Models\PurchaseOrder $entity */ $entity = PurchaseOrder::factory()->make(); - $entity->client = Client::factory()->make(['settings' => $settings]); + // $entity->client = Client::factory()->make(['settings' => $settings]); + $entity->vendor = Vendor::factory()->make(); + $entity->vendor->setRelation('company', $this->company); $entity->invitation = PurchaseOrderInvitation::factory()->make(); break; case PurchaseOrder::class: @@ -138,17 +144,17 @@ class PdfMock $entity = PurchaseOrder::factory()->make(); $entity->invitation = PurchaseOrderInvitation::factory()->make(); $entity->vendor = Vendor::factory()->make(); + $entity->invitation->setRelation('company', $this->company); break; default: $entity = false; break; } - $entity->tax_map = $this->getTaxMap(); $entity->total_tax_map = $this->getTotalTaxMap(); $entity->invitation->company = $this->company; - + return $entity; } diff --git a/database/migrations/2024_03_14_201844_adjust_discount_column_max_resolution.php b/database/migrations/2024_03_14_201844_adjust_discount_column_max_resolution.php index 3aa0099e3cf0..ad36915c62d2 100644 --- a/database/migrations/2024_03_14_201844_adjust_discount_column_max_resolution.php +++ b/database/migrations/2024_03_14_201844_adjust_discount_column_max_resolution.php @@ -16,7 +16,6 @@ return new class extends Migration $table->decimal('discount', 20, 6)->default(0)->change(); }); - Schema::table('credits', function (Blueprint $table) { $table->decimal('discount', 20, 6)->default(0)->change(); }); diff --git a/tests/Unit/InvoiceTest.php b/tests/Unit/InvoiceTest.php index b87106079bb5..d9b9cab429e6 100644 --- a/tests/Unit/InvoiceTest.php +++ b/tests/Unit/InvoiceTest.php @@ -49,6 +49,88 @@ class InvoiceTest extends TestCase $this->invoice_calc = new InvoiceSum($this->invoice); } + public function testRappenRounding() + { + + $c_settings = $this->client->settings; + $c_settings->enable_rappen_rounding = true; + + $c = \App\Models\Client::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'settings' => $c_settings, + ]); + + $item = InvoiceItemFactory::create(); + $item->quantity = 1; + $item->cost = 10.01; + $item->type_id = '1'; + $item->tax_id = '1'; + $line_items[] = $item; + + $i = Invoice::factory()->create([ + 'discount' => 0, + 'tax_name1' => '', + 'tax_name2' => '', + 'tax_name3' => '', + 'tax_rate1' => 0, + 'tax_rate2' => 0, + 'tax_rate3' => 0, + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'client_id' => $c->id, + 'line_items' => $line_items, + 'status_id' => 1, + ]); + + $invoice_calc = new InvoiceSum($i); + $ii = $invoice_calc->build()->getInvoice(); + + $this->assertEquals(10, $ii->amount); + + } + + public function testRappenRoundingUp() + { + + $c_settings = $this->client->settings; + $c_settings->enable_rappen_rounding = true; + + $c = \App\Models\Client::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'settings' => $c_settings, + ]); + + $item = InvoiceItemFactory::create(); + $item->quantity = 1; + $item->cost = 10.09; + $item->type_id = '1'; + $item->tax_id = '1'; + $line_items[] = $item; + + $i = Invoice::factory()->create([ + 'discount' => 0, + 'tax_name1' => '', + 'tax_name2' => '', + 'tax_name3' => '', + 'tax_rate1' => 0, + 'tax_rate2' => 0, + 'tax_rate3' => 0, + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'client_id' => $c->id, + 'line_items' => $line_items, + 'status_id' => 1, + ]); + + $invoice_calc = new InvoiceSum($i); + $ii = $invoice_calc->build()->getInvoice(); + + $this->assertEquals(10.10, round($ii->amount,2)); + + } + public function testPartialDueDateCast() { $i = Invoice::factory()