From fdba7ee0deafeebfa9d06c41c4790b312db443c5 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 8 May 2023 10:36:30 +1000 Subject: [PATCH 01/11] ES E-Invoicing --- app/Services/Invoice/EInvoice/FacturaEInvoice.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/Services/Invoice/EInvoice/FacturaEInvoice.php b/app/Services/Invoice/EInvoice/FacturaEInvoice.php index 2b28468ad9..cd6a3e5bb7 100644 --- a/app/Services/Invoice/EInvoice/FacturaEInvoice.php +++ b/app/Services/Invoice/EInvoice/FacturaEInvoice.php @@ -169,6 +169,7 @@ class FacturaEInvoice extends AbstractService foreach($this->invoice->line_items as $item) { $this->fac->addItem(new FacturaeItem([ + 'name' => $item->product_key, 'description' => $item->notes, 'quantity' => $item->quantity, 'unitPrice' => $item->cost, @@ -302,7 +303,7 @@ class FacturaEInvoice extends AbstractService "email" => $this->invoice->client->present()->email(), "phone" => $this->invoice->client->present()->phone(), "fax" => "", - "website" => $this->invoice->client->present()->website(), + "website" => substr($this->invoice->client->present()->website(), 0 ,60), "contactPeople" => $this->invoice->client->present()->first_name()." ".$this->invoice->client->present()->last_name(), // "cnoCnae" => "04791", // Clasif. Nacional de Act. Económicas // "ineTownCode" => "280796" // Cód. de municipio del INE @@ -317,7 +318,7 @@ class FacturaEInvoice extends AbstractService { // $ssl_cert = file_get_contents(base_path('e_invoice_cert.p12')); - // $this->fac->sign($ssl_cert, null, "SuperSecrethere"); + // $this->fac->sign($ssl_cert, null, "SuperSecretPassword"); return $this; } From 0f4113ca5761a469eae5872fb91104b48b9672ea Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 8 May 2023 14:13:14 +1000 Subject: [PATCH 02/11] Minor fixes for tests --- tests/Feature/EInvoice/FacturaeTest.php | 40 +++++++++++++++++++++---- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/tests/Feature/EInvoice/FacturaeTest.php b/tests/Feature/EInvoice/FacturaeTest.php index 3085d4221b..77da9477a0 100644 --- a/tests/Feature/EInvoice/FacturaeTest.php +++ b/tests/Feature/EInvoice/FacturaeTest.php @@ -11,11 +11,16 @@ namespace Tests\Feature\EInvoice; -use App\Services\Invoice\EInvoice\FacturaEInvoice; -use Illuminate\Foundation\Testing\DatabaseTransactions; -use Illuminate\Routing\Middleware\ThrottleRequests; -use Tests\MockAccountData; use Tests\TestCase; +use Tests\MockAccountData; +use Http\Message\CookieJar; +use Illuminate\Support\Facades\Http; +use Illuminate\Support\Facades\Storage; +use App\Services\Invoice\EInvoice\FacturaEInvoice; +use Illuminate\Routing\Middleware\ThrottleRequests; +use Illuminate\Foundation\Testing\DatabaseTransactions; + +use function Amp\Iterator\toArray; /** * @test @@ -40,10 +45,35 @@ class FacturaeTest extends TestCase { $f = new FacturaEInvoice($this->invoice, "3.2.2"); - $f->run(); + $path = $f->run(); $this->assertNotNull($f->run()); nlog($f->run()); + + // $this->assertTrue($this->validateInvoiceXML($path)); } + + +// private function validateInvoiceXML($path) { + + + +// $jar = (new \GuzzleHttp\Cookie\CookieJar())->toArray(); + +// echo print_r($jar); + +// $response = Http::withCookies($jar, '.ninja.test')->attach( +// 'xmlFile', +// Storage::get($path), +// basename($path) +// )->post('https://viewer.facturadirecta.com/dp/viewer/upload.void'); // Instance of Guzzle/CookieJar + +// echo print_r($jar); + +// $response = Http::withCookies($jar, '.ninja.test')->post('https://viewer.facturadirecta.com/dp/viewer/viewer.void'); +// echo print_r($response->body(), 1); + +// } + } \ No newline at end of file From f2df6c2e796525e9f7a12f49389afde9b5ee0a0e Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 8 May 2023 19:11:14 +1000 Subject: [PATCH 03/11] Updates for factuare invoices --- .../Invoice/EInvoice/FacturaEInvoice.php | 66 ++++++------ openapi/api-docs.yaml | 52 +++++++-- .../components/schemas/fillable_invoice.yaml | 7 +- openapi/components/schemas/invoice_item.yaml | 41 ++++++- openapi/components/schemas/product.yaml | 6 +- tests/Feature/EInvoice/FacturaeTest.php | 100 ++++++++++++++---- 6 files changed, 201 insertions(+), 71 deletions(-) diff --git a/app/Services/Invoice/EInvoice/FacturaEInvoice.php b/app/Services/Invoice/EInvoice/FacturaEInvoice.php index cd6a3e5bb7..5a2bcce7c3 100644 --- a/app/Services/Invoice/EInvoice/FacturaEInvoice.php +++ b/app/Services/Invoice/EInvoice/FacturaEInvoice.php @@ -212,6 +212,9 @@ class FacturaEInvoice extends AbstractService } + if(count($data) == 0) + $data[Facturae::TAX_IVA] = 0; + return $data; } @@ -258,29 +261,30 @@ class FacturaEInvoice extends AbstractService $company = $this->invoice->company; $seller = new FacturaeParty([ - "isLegalEntity" => true, // Se asume true si se omite - "taxNumber" => $company->settings->vat_number, - "name" => $company->present()->name(), - "address" => $company->settings->address1, - "postCode" => $company->settings->postal_code, - "town" => $company->settings->city, - "province" => $company->settings->state, - "countryCode" => $company->country()->iso_3166_3, // Se asume España si se omite - "book" => "0", // Libro - "merchantRegister" => "RG", // Registro Mercantil - "sheet" => "1", // Hoja - "folio" => "2", // Folio - "section" => "3", // Sección - "volume" => "4", // Tomo - "email" => $company->settings->email, - "phone" => $company->settings->phone, - "fax" => "", - "website" => $company->settings->website, - "contactPeople" => $company->owner()->present()->name(), - // "cnoCnae" => "04647", // Clasif. Nacional de Act. Económicas - // "ineTownCode" => "280796" // Cód. de municipio del INE + "isLegalEntity" => true, // Se asume true si se omite + "taxNumber" => $company->settings->vat_number, + "name" => 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), + "province" => substr($company->settings->state, 0, 20), + "countryCode" => $company->country()->iso_3166_3, // Se asume España si se omite + "book" => "0", // Libro + "merchantRegister" => "RG", // Registro Mercantil + "sheet" => "1", // Hoja + "folio" => "2", // Folio + "section" => "3", // Sección + "volume" => "4", // Tomo + "email" => substr($company->settings->email, 0, 60), + "phone" => substr($company->settings->phone, 0, 15), + "fax" => "", + "website" => substr($company->settings->website, 0, 50), + "contactPeople" => substr($company->owner()->present()->name(), 0, 40), + // "cnoCnae" => "04647", // Clasif. Nacional de Act. Económicas + // "ineTownCode" => "280796" // Cód. de municipio del INE ]); + $this->fac->setSeller($seller); return $this; @@ -292,19 +296,19 @@ class FacturaEInvoice extends AbstractService $buyer = new FacturaeParty([ "isLegalEntity" => $this->invoice->client->has_valid_vat_number, "taxNumber" => $this->invoice->client->vat_number, - "name" => $this->invoice->client->present()->name(), - "firstSurname" => $this->invoice->client->present()->first_name(), - "lastSurname" => $this->invoice->client->present()->last_name(), - "address" => $this->invoice->client->address1, - "postCode" => $this->invoice->client->postal_code, - "town" => $this->invoice->client->city, - "province" => $this->invoice->client->state, + "name" => substr($this->invoice->client->present()->name(),0, 40), + "firstSurname" => substr($this->invoice->client->present()->first_name(),0, 40), + "lastSurname" => substr($this->invoice->client->present()->last_name(),0, 40), + "address" => substr($this->invoice->client->address1,0, 80), + "postCode" => substr($this->invoice->client->postal_code,0,5), + "town" => substr($this->invoice->client->city,0, 50), + "province" => substr($this->invoice->client->state,0, 20), "countryCode" => $this->invoice->client->country->iso_3166_3, // Se asume España si se omite - "email" => $this->invoice->client->present()->email(), - "phone" => $this->invoice->client->present()->phone(), + "email" => substr($this->invoice->client->present()->email(),0, 60), + "phone" => substr($this->invoice->client->present()->phone(),0, 15), "fax" => "", "website" => substr($this->invoice->client->present()->website(), 0 ,60), - "contactPeople" => $this->invoice->client->present()->first_name()." ".$this->invoice->client->present()->last_name(), + "contactPeople" => substr($this->invoice->client->present()->first_name()." ".$this->invoice->client->present()->last_name(), 0, 40), // "cnoCnae" => "04791", // Clasif. Nacional de Act. Económicas // "ineTownCode" => "280796" // Cód. de municipio del INE ]); diff --git a/openapi/api-docs.yaml b/openapi/api-docs.yaml index b875dd9f07..1e96c319fc 100644 --- a/openapi/api-docs.yaml +++ b/openapi/api-docs.yaml @@ -16907,9 +16907,10 @@ components: type: number example: 8.00 line_items: - description: "Array of line items included in the invoice" - type: object - example: "[{item1}, {item2}]" + type: array + description: 'An array of objects which define the line items of the invoice' + items: + $ref: '#/components/schemas/InvoiceItem' discount: description: "The discount applied to the invoice" type: number @@ -17324,83 +17325,116 @@ components: quantity: type: integer example: 1 + description: 'The quantity of the product offered for this line item' cost: type: number format: float example: 10.00 + description: 'The cost of the product offered for this line item' product_key: type: string example: 'Product key' + description: 'The product key of the product offered for this line item (Referred to as Product in the product tab)' product_cost: type: number format: float example: 10.00 + description: 'The cost of the product offered for this line item (Referred to as Cost in the product tab)' notes: type: string example: 'Item notes' + description: 'The notes/description for the product offered for this line item' discount: type: number format: float example: 5.00 + description: 'The discount applied to the product offered for this line item' is_amount_discount: type: boolean example: false + description: 'Indicates whether the discount applied to the product offered for this line item is a fixed amount or a percentage' tax_name1: type: string - example: 'Tax name 1' + example: 'GST' + description: 'The name of the first tax applied to the product offered for this line item' tax_rate1: type: number format: float example: 10.00 + description: 'The rate of the first tax applied to the product offered for this line item' tax_name2: type: string - example: 'Tax name 2' + example: 'VAT' + description: 'The name of the second tax applied to the product offered for this line item' tax_rate2: type: number format: float example: 5.00 + description: 'The rate of the second tax applied to the product offered for this line item' tax_name3: type: string - example: 'Tax name 3' + example: 'CA Sales Tax' + description: 'The name of the third tax applied to the product offered for this line item' tax_rate3: type: number format: float example: 3.00 + description: 'The rate of the third tax applied to the product offered for this line item' sort_id: type: string example: '0' + description: 'Deprecated' + deprecated: true line_total: type: number format: float example: 10.00 + description: 'The total amount of the product offered for this line item' + readOnly: true gross_line_total: type: number format: float example: 15.00 + description: 'The total amount of the product offered for this line item before discounts' + readOnly: true tax_amount: type: number format: float example: 1.00 + description: 'The total amount of tax applied to the product offered for this line item' + readOnly: true date: type: string format: date-time example: '2023-03-19T00:00:00Z' + description: 'Deprecated' + deprecated: true custom_value1: type: string example: 'Custom value 1' + description: 'The first custom value of the product offered for this line item' custom_value2: type: string example: 'Custom value 2' + description: 'The second custom value of the product offered for this line item' custom_value3: type: string example: 'Custom value 3' + description: 'The third custom value of the product offered for this line item' custom_value4: type: string example: 'Custom value 4' + description: 'The fourth custom value of the product offered for this line item' type_id: type: string example: '1' description: '1 = product, 2 = service, 3 unpaid gateway fee, 4 paid gateway fee, 5 late fee, 6 expense' + default: '1' + tax_id: + type: string + example: '1' + default: '1' + description: 'The tax ID of the product: 1 product, 2 service, 3 digital, 4 shipping, 5 exempt, 5 reduced tax, 7 override, 8 zero rate, 9 reverse tax' CompanyUser: properties: permissions: @@ -19431,17 +19465,17 @@ components: cost: type: number format: double - description: 'The cost of the product.' + description: 'The cost of the product. (Your purchase price for this product)' example: 10.0 price: type: number format: double - description: 'The price of the product.' + description: 'The price of the product that you are charging.' example: 20.0 quantity: type: number format: double - description: 'The quantity of the product.' + description: 'The quantity of the product. (used as a default)' example: 5.0 tax_name1: type: string diff --git a/openapi/components/schemas/fillable_invoice.yaml b/openapi/components/schemas/fillable_invoice.yaml index d6593eefe1..2ae598a460 100644 --- a/openapi/components/schemas/fillable_invoice.yaml +++ b/openapi/components/schemas/fillable_invoice.yaml @@ -73,9 +73,10 @@ type: number example: 8.00 line_items: - description: "Array of line items included in the invoice" - type: object - example: "[{item1}, {item2}]" + type: array + description: 'An array of objects which define the line items of the invoice' + items: + $ref: '#/components/schemas/InvoiceItem' discount: description: "The discount applied to the invoice" type: number diff --git a/openapi/components/schemas/invoice_item.yaml b/openapi/components/schemas/invoice_item.yaml index 94c512f49e..2357c127c5 100644 --- a/openapi/components/schemas/invoice_item.yaml +++ b/openapi/components/schemas/invoice_item.yaml @@ -4,80 +4,113 @@ quantity: type: integer example: 1 + description: 'The quantity of the product offered for this line item' cost: type: number format: float example: 10.00 + description: 'The cost of the product offered for this line item' product_key: type: string example: 'Product key' + description: 'The product key of the product offered for this line item (Referred to as Product in the product tab)' product_cost: type: number format: float example: 10.00 + description: 'The cost of the product offered for this line item (Referred to as Cost in the product tab)' notes: type: string example: 'Item notes' + description: 'The notes/description for the product offered for this line item' discount: type: number format: float example: 5.00 + description: 'The discount applied to the product offered for this line item' is_amount_discount: type: boolean example: false + description: 'Indicates whether the discount applied to the product offered for this line item is a fixed amount or a percentage' tax_name1: type: string - example: 'Tax name 1' + example: 'GST' + description: 'The name of the first tax applied to the product offered for this line item' tax_rate1: type: number format: float example: 10.00 + description: 'The rate of the first tax applied to the product offered for this line item' tax_name2: type: string - example: 'Tax name 2' + example: 'VAT' + description: 'The name of the second tax applied to the product offered for this line item' tax_rate2: type: number format: float example: 5.00 + description: 'The rate of the second tax applied to the product offered for this line item' tax_name3: type: string - example: 'Tax name 3' + example: 'CA Sales Tax' + description: 'The name of the third tax applied to the product offered for this line item' tax_rate3: type: number format: float example: 3.00 + description: 'The rate of the third tax applied to the product offered for this line item' sort_id: type: string example: '0' + description: 'Deprecated' + deprecated: true line_total: type: number format: float example: 10.00 + description: 'The total amount of the product offered for this line item' + readOnly: true gross_line_total: type: number format: float example: 15.00 + description: 'The total amount of the product offered for this line item before discounts' + readOnly: true tax_amount: type: number format: float example: 1.00 + description: 'The total amount of tax applied to the product offered for this line item' + readOnly: true date: type: string format: date-time example: '2023-03-19T00:00:00Z' + description: 'Deprecated' + deprecated: true custom_value1: type: string example: 'Custom value 1' + description: 'The first custom value of the product offered for this line item' custom_value2: type: string example: 'Custom value 2' + description: 'The second custom value of the product offered for this line item' custom_value3: type: string example: 'Custom value 3' + description: 'The third custom value of the product offered for this line item' custom_value4: type: string example: 'Custom value 4' + description: 'The fourth custom value of the product offered for this line item' type_id: type: string example: '1' - description: '1 = product, 2 = service, 3 unpaid gateway fee, 4 paid gateway fee, 5 late fee, 6 expense' \ No newline at end of file + description: '1 = product, 2 = service, 3 unpaid gateway fee, 4 paid gateway fee, 5 late fee, 6 expense' + default: '1' + tax_id: + type: string + example: '1' + default: '1' + description: 'The tax ID of the product: 1 product, 2 service, 3 digital, 4 shipping, 5 exempt, 5 reduced tax, 7 override, 8 zero rate, 9 reverse tax' \ No newline at end of file diff --git a/openapi/components/schemas/product.yaml b/openapi/components/schemas/product.yaml index a88238e392..52480f7197 100644 --- a/openapi/components/schemas/product.yaml +++ b/openapi/components/schemas/product.yaml @@ -55,17 +55,17 @@ cost: type: number format: double - description: 'The cost of the product.' + description: 'The cost of the product. (Your purchase price for this product)' example: 10.0 price: type: number format: double - description: 'The price of the product.' + description: 'The price of the product that you are charging.' example: 20.0 quantity: type: number format: double - description: 'The quantity of the product.' + description: 'The quantity of the product. (used as a default)' example: 5.0 tax_name1: type: string diff --git a/tests/Feature/EInvoice/FacturaeTest.php b/tests/Feature/EInvoice/FacturaeTest.php index 77da9477a0..06d6f2b05a 100644 --- a/tests/Feature/EInvoice/FacturaeTest.php +++ b/tests/Feature/EInvoice/FacturaeTest.php @@ -13,8 +13,6 @@ namespace Tests\Feature\EInvoice; use Tests\TestCase; use Tests\MockAccountData; -use Http\Message\CookieJar; -use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Storage; use App\Services\Invoice\EInvoice\FacturaEInvoice; use Illuminate\Routing\Middleware\ThrottleRequests; @@ -51,29 +49,89 @@ class FacturaeTest extends TestCase nlog($f->run()); - // $this->assertTrue($this->validateInvoiceXML($path)); + $this->assertTrue($this->validateInvoiceXML($path)); } -// private function validateInvoiceXML($path) { +// protected function validateInvoiceXML($path, $validateSignature=false) { +// // Prepare file to upload +// if (function_exists('curl_file_create')) { +// $postFile = curl_file_create($path); +// } else { +// $postFile = "@" . realpath($path); +// } + +// // Send upload request +// $ch = curl_init(); +// curl_setopt_array($ch, array( +// CURLOPT_RETURNTRANSFER => true, +// CURLOPT_FOLLOWLOCATION => true, +// CURLOPT_URL => "http://plataforma.firma-e.com/VisualizadorFacturae/index2.jsp", +// CURLOPT_POST => 1, +// CURLOPT_POSTFIELDS => array( +// "referencia" => $postFile, +// "valContable" => "on", +// "valFirma" => $validateSignature ? "on" : "off", +// "aceptarCondiciones" => "on", +// "submit" => "Siguiente" +// ), +// CURLOPT_COOKIEJAR => base_path()."/cookie.txt" +// )); +// $res = curl_exec($ch); +// curl_close($ch); +// unset($ch); + +// nlog($res); + +// if (strpos($res, "window.open('facturae.jsp'") === false) { +// $this->expectException(\UnexpectedValueException::class); +// } + +// // Fetch results +// $ch = curl_init(); +// curl_setopt_array($ch, array( +// CURLOPT_RETURNTRANSFER => true, +// CURLOPT_FOLLOWLOCATION => true, +// CURLOPT_URL => "http://plataforma.firma-e.com/VisualizadorFacturae/facturae.jsp", +// CURLOPT_COOKIEFILE => base_path()."/cookie.txt" +// )); +// $res = curl_exec($ch); +// curl_close($ch); +// unset($ch); + +// nlog($res); + +// // Validate results +// $this->assertNotEmpty($res, 'Invalid Validator Response'); +// $this->assertNotEmpty(strpos($res, 'euro_ok.png'), 'Invalid XML Format'); +// if ($validateSignature) { +// $this->assertNotEmpty(strpos($res, '>Nivel de Firma Válido<'), 'Invalid Signature'); +// } +// if (strpos($res, '>Sellos de Tiempo<') !== false) { +// $this->assertNotEmpty(strpos($res, '>XAdES_T<'), 'Invalid Timestamp'); +// } +// } + + // private function validateInvoiceXML($path) + // { + // $client = new \GuzzleHttp\Client(['cookies' => true]); + + // $response = $client->request('POST', 'https://face.gob.es/api/v1/herramientas/validador',[ + // 'multipart' => [ + // [ + // 'name' => 'validador[factura]', + // 'contents' => Storage::get($path), + // ], + // ] + // ]); + + // $response = $client->request('POST', 'http://plataforma.firma-e.com/VisualizadorFacturae/facturae.jsp'); + // $body = $response->getBody(); + // $stringBody = (string) $body; + + // echo print_r($stringBody,1); - -// $jar = (new \GuzzleHttp\Cookie\CookieJar())->toArray(); - -// echo print_r($jar); - -// $response = Http::withCookies($jar, '.ninja.test')->attach( -// 'xmlFile', -// Storage::get($path), -// basename($path) -// )->post('https://viewer.facturadirecta.com/dp/viewer/upload.void'); // Instance of Guzzle/CookieJar - -// echo print_r($jar); - -// $response = Http::withCookies($jar, '.ninja.test')->post('https://viewer.facturadirecta.com/dp/viewer/viewer.void'); -// echo print_r($response->body(), 1); - -// } + // } } \ No newline at end of file From c0677dfb1d8cfb312eb67f3d303240b13cd7a9a1 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 8 May 2023 19:23:08 +1000 Subject: [PATCH 04/11] Fixes for tests --- tests/Feature/EInvoice/FacturaeTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Feature/EInvoice/FacturaeTest.php b/tests/Feature/EInvoice/FacturaeTest.php index 06d6f2b05a..0770768da1 100644 --- a/tests/Feature/EInvoice/FacturaeTest.php +++ b/tests/Feature/EInvoice/FacturaeTest.php @@ -49,7 +49,7 @@ class FacturaeTest extends TestCase nlog($f->run()); - $this->assertTrue($this->validateInvoiceXML($path)); + // $this->assertTrue($this->validateInvoiceXML($path)); } From 2e091abdc00d8ec9b55c92bba3e02554e8ae6d7f Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 8 May 2023 19:28:31 +1000 Subject: [PATCH 05/11] Increase parallel tests --- .github/workflows/phpunit.yml | 4 ++-- app/Services/Tax/TaxService.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index 4a70b9c6fc..4306039b34 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -16,8 +16,8 @@ jobs: operating-system: ['ubuntu-20.04', 'ubuntu-22.04'] php-versions: ['8.1','8.2'] phpunit-versions: ['latest'] - ci_node_total: [ 6 ] - ci_node_index: [ 0, 1, 2, 3, 4, 5] + ci_node_total: [ 8 ] + ci_node_index: [ 0, 1, 2, 3, 4, 5, 6, 7] laravel: [9.*] dependency-version: [prefer-stable] diff --git a/app/Services/Tax/TaxService.php b/app/Services/Tax/TaxService.php index 354681502b..38ab4dcbea 100644 --- a/app/Services/Tax/TaxService.php +++ b/app/Services/Tax/TaxService.php @@ -19,11 +19,11 @@ use App\Services\Tax\Providers\ZipTax; class TaxService { - public function __construct(protected Company $company, protected Client $client) + public function __construct(public Client $client) { } - private function validateVat(): self + public function validateVat(): self { $client_country_code = $this->client->shipping_country ? $this->client->shipping_country->iso_3166_2 : $this->client->country->iso_3166_2; From 80e20c0f1eb895255a108652c21d70dcfdf19d03 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 8 May 2023 19:42:47 +1000 Subject: [PATCH 06/11] Fixes for client tax_data --- app/DataMapper/Tax/BaseRule.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/DataMapper/Tax/BaseRule.php b/app/DataMapper/Tax/BaseRule.php index 714fd3c3c3..6f664dd814 100644 --- a/app/DataMapper/Tax/BaseRule.php +++ b/app/DataMapper/Tax/BaseRule.php @@ -160,7 +160,7 @@ class BaseRule implements RuleInterface return $this; //determine if we are taxing locally or if we are taxing globally - $tax_data = $this->invoice->client->tax_data ?? new Response([]); + $tax_data = is_object($this->invoice->client->tax_data) ? $this->invoice->client->tax_data : new Response([]); if(strlen($this->invoice->tax_data?->originDestination) == 0 && $this->client->company->tax_data->seller_subregion != $this->client_subregion) { $tax_data->originDestination = "D"; From 9fd016e6e1ac248874e2cb97fc534c40a38a98c7 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 8 May 2023 20:33:21 +1000 Subject: [PATCH 07/11] Fixes for stripe sepa payment method duplicating each time token billing is used. --- app/PaymentDrivers/Stripe/ACH.php | 17 ++++++++++++++++- app/PaymentDrivers/Stripe/Charge.php | 3 --- app/PaymentDrivers/Stripe/SEPA.php | 22 +++++++++++++++++----- app/PaymentDrivers/StripePaymentDriver.php | 2 +- 4 files changed, 34 insertions(+), 10 deletions(-) diff --git a/app/PaymentDrivers/Stripe/ACH.php b/app/PaymentDrivers/Stripe/ACH.php index 7578908424..631e2a7be6 100644 --- a/app/PaymentDrivers/Stripe/ACH.php +++ b/app/PaymentDrivers/Stripe/ACH.php @@ -543,7 +543,8 @@ class ACH SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_STRIPE, - $this->stripe->client + $this->stripe->client, + $this->stripe->client->company, ); throw new PaymentFailed('Failed to process the payment.', 500); @@ -570,6 +571,20 @@ class ACH 'payment_method_id' => $payment_method_id, ]; + /** + * Ensure the method does not already exist!! + */ + + $token = ClientGatewayToken::where([ + 'gateway_customer_reference' => $customer->id, + 'token' => $method->id, + 'client_id' => $this->stripe->client->id, + 'company_id' => $this->stripe->client->company_id, + ])->first(); + + if($token) + return $token; + return $this->stripe->storeGatewayToken($data, ['gateway_customer_reference' => $customer->id]); } catch (Exception $e) { return $this->stripe->processInternallyFailedPayment($this->stripe, $e); diff --git a/app/PaymentDrivers/Stripe/Charge.php b/app/PaymentDrivers/Stripe/Charge.php index fbed56e34e..05833ccecc 100644 --- a/app/PaymentDrivers/Stripe/Charge.php +++ b/app/PaymentDrivers/Stripe/Charge.php @@ -194,17 +194,14 @@ class Charge switch ($type) { case 'visa': return PaymentType::VISA; - break; case 'mastercard': return PaymentType::MASTERCARD; - break; case PaymentType::SEPA: return PaymentType::SEPA; case PaymentType::BACS: return PaymentType::BACS; default: return PaymentType::CREDIT_CARD_OTHER; - break; } } } diff --git a/app/PaymentDrivers/Stripe/SEPA.php b/app/PaymentDrivers/Stripe/SEPA.php index b5b5a7268d..1023d8ff65 100644 --- a/app/PaymentDrivers/Stripe/SEPA.php +++ b/app/PaymentDrivers/Stripe/SEPA.php @@ -11,14 +11,15 @@ namespace App\PaymentDrivers\Stripe; -use App\Exceptions\PaymentFailed; -use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest; -use App\Jobs\Util\SystemLogger; -use App\Models\GatewayType; use App\Models\Payment; -use App\Models\PaymentType; use App\Models\SystemLog; +use App\Models\GatewayType; +use App\Models\PaymentType; +use App\Jobs\Util\SystemLogger; +use App\Exceptions\PaymentFailed; +use App\Models\ClientGatewayToken; use App\PaymentDrivers\StripePaymentDriver; +use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest; class SEPA { @@ -159,6 +160,17 @@ class SEPA 'payment_method_id' => GatewayType::SEPA, ]; + $token = ClientGatewayToken::where([ + 'gateway_customer_reference' => $method->customer, + 'token' => $method->id, + 'client_id' => $this->stripe->client->id, + 'company_id' => $this->stripe->client->company_id, + ])->first(); + + if($token) { + return $token; + } + $this->stripe->storeGatewayToken($data, ['gateway_customer_reference' => $method->customer]); } catch (\Exception $e) { return $this->stripe->processInternallyFailedPayment($this->stripe, $e); diff --git a/app/PaymentDrivers/StripePaymentDriver.php b/app/PaymentDrivers/StripePaymentDriver.php index 8e813802b1..bdac90e9c0 100644 --- a/app/PaymentDrivers/StripePaymentDriver.php +++ b/app/PaymentDrivers/StripePaymentDriver.php @@ -106,7 +106,7 @@ class StripePaymentDriver extends BaseDriver /** * Initializes the Stripe API. - * @return void + * @return self */ public function init() { From 4a75fc37255e4b5adf2e581543291671cc1be102 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 8 May 2023 22:55:37 +1000 Subject: [PATCH 08/11] Allow document names to be modified --- app/Models/Document.php | 3 ++- app/Services/Tax/TaxService.php | 3 --- resources/views/pdf-designs/clean.html | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/app/Models/Document.php b/app/Models/Document.php index e2be3a232f..2195d893f7 100644 --- a/app/Models/Document.php +++ b/app/Models/Document.php @@ -95,11 +95,12 @@ class Document extends BaseModel const DOCUMENT_PREVIEW_SIZE = 300; // pixels /** - * @var array + * @var array */ protected $fillable = [ 'is_default', 'is_public', + 'name', ]; /** diff --git a/app/Services/Tax/TaxService.php b/app/Services/Tax/TaxService.php index 38ab4dcbea..7213b023c5 100644 --- a/app/Services/Tax/TaxService.php +++ b/app/Services/Tax/TaxService.php @@ -12,9 +12,6 @@ namespace App\Services\Tax; use App\Models\Client; -use App\Models\Company; -use App\Services\Tax\Providers\ZipTax; - class TaxService { diff --git a/resources/views/pdf-designs/clean.html b/resources/views/pdf-designs/clean.html index 5b0005a81a..d575eff585 100644 --- a/resources/views/pdf-designs/clean.html +++ b/resources/views/pdf-designs/clean.html @@ -28,7 +28,7 @@ margin-left: $global_margin; margin-right: $global_margin; margin-top: 5; - margin-bottom: 0; + margin-bottom: 5; size: $page_size $page_layout; } From a69c50d9e84cccfe91559502271f47504f102f72 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 9 May 2023 13:01:27 +1000 Subject: [PATCH 09/11] Tests for user deleting themselves --- app/Http/Requests/User/BulkUserRequest.php | 16 +++++++++++++--- tests/Feature/UserTest.php | 21 +++++++++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/app/Http/Requests/User/BulkUserRequest.php b/app/Http/Requests/User/BulkUserRequest.php index 1ae2ee8dca..afea05c1da 100644 --- a/app/Http/Requests/User/BulkUserRequest.php +++ b/app/Http/Requests/User/BulkUserRequest.php @@ -11,9 +11,10 @@ namespace App\Http\Requests\User; -use App\Http\Requests\Request; -use App\Http\ValidationRules\Ninja\CanRestoreUserRule; use App\Utils\Ninja; +use App\Http\Requests\Request; +use Illuminate\Auth\Access\AuthorizationException; +use App\Http\ValidationRules\Ninja\CanRestoreUserRule; class BulkUserRequest extends Request { @@ -23,7 +24,11 @@ class BulkUserRequest extends Request * @return bool */ public function authorize() : bool - { + {nlog($this->all()); + nlog($this->ids); + if($this->action == 'delete' && in_array(auth()->user()->hashed_id, $this->ids)) + return false; + return auth()->user()->isAdmin(); } @@ -44,4 +49,9 @@ class BulkUserRequest extends Request $this->replace($input); } + + protected function failedAuthorization() + { + throw new AuthorizationException("This Action is unauthorized."); + } } diff --git a/tests/Feature/UserTest.php b/tests/Feature/UserTest.php index 97668b4f0e..f11ab71df9 100644 --- a/tests/Feature/UserTest.php +++ b/tests/Feature/UserTest.php @@ -56,6 +56,27 @@ class UserTest extends TestCase ); } + public function testUserAttemptingtToDeleteThemselves() + { + $data = [ + 'action' => 'delete', + 'ids' => [$this->user->hashed_id], + ]; + + nlog($data); + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + // 'X-API-PASSWORD' => 'ALongAndBriliantPassword', + ])->postJson('/api/v1/users/bulk', $data) + ->assertStatus(200); + + // nlog($response->json()); + + // $response->assertStatus(403); + } + public function testDisconnectUserOauthMailer() { $user = From ec5a8eb100d450b35017f62c5e560fe0e9a72c31 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 9 May 2023 13:37:53 +1000 Subject: [PATCH 10/11] Minor fixes for tests --- app/Http/Requests/User/BulkUserRequest.php | 3 +- tests/Feature/UserTest.php | 85 ++++++++++++++++------ 2 files changed, 63 insertions(+), 25 deletions(-) diff --git a/app/Http/Requests/User/BulkUserRequest.php b/app/Http/Requests/User/BulkUserRequest.php index afea05c1da..c07032f4f2 100644 --- a/app/Http/Requests/User/BulkUserRequest.php +++ b/app/Http/Requests/User/BulkUserRequest.php @@ -24,8 +24,7 @@ class BulkUserRequest extends Request * @return bool */ public function authorize() : bool - {nlog($this->all()); - nlog($this->ids); + { if($this->action == 'delete' && in_array(auth()->user()->hashed_id, $this->ids)) return false; diff --git a/tests/Feature/UserTest.php b/tests/Feature/UserTest.php index f11ab71df9..5d49460132 100644 --- a/tests/Feature/UserTest.php +++ b/tests/Feature/UserTest.php @@ -11,19 +11,21 @@ namespace Tests\Feature; -use App\Factory\CompanyUserFactory; -use App\Http\Middleware\PasswordProtection; -use App\Models\Company; -use App\Models\CompanyToken; -use App\Models\CompanyUser; -use App\Models\User; -use Illuminate\Database\Eloquent\Model; -use Illuminate\Foundation\Testing\DatabaseTransactions; -use Illuminate\Routing\Middleware\ThrottleRequests; -use Illuminate\Support\Facades\Session; -use Illuminate\Validation\ValidationException; -use Tests\MockAccountData; use Tests\TestCase; +use App\Models\User; +use App\Models\Account; +use App\Models\Company; +use Tests\MockAccountData; +use App\Models\CompanyUser; +use App\Models\CompanyToken; +use App\DataMapper\CompanySettings; +use App\Factory\CompanyUserFactory; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Facades\Session; +use App\Http\Middleware\PasswordProtection; +use Illuminate\Validation\ValidationException; +use Illuminate\Routing\Middleware\ThrottleRequests; +use Illuminate\Foundation\Testing\DatabaseTransactions; /** * @test @@ -58,23 +60,60 @@ class UserTest extends TestCase public function testUserAttemptingtToDeleteThemselves() { - $data = [ - 'action' => 'delete', - 'ids' => [$this->user->hashed_id], - ]; - nlog($data); + + $account = Account::factory()->create([ + 'hosted_client_count' => 1000, + 'hosted_company_count' => 1000, + ]); + + $account->num_users = 3; + $account->save(); + + $user = User::factory()->create([ + 'account_id' => $this->account->id, + 'confirmation_code' => 'xyz123', + 'email' => $this->faker->unique()->safeEmail(), + ]); + + $settings = CompanySettings::defaults(); + $settings->client_online_payment_notification = false; + $settings->client_manual_payment_notification = false; + + $company = Company::factory()->create([ + 'account_id' => $account->id, + 'settings' => $settings, + ]); + + + $cu = CompanyUserFactory::create($user->id, $company->id, $account->id); + $cu->is_owner = true; + $cu->is_admin = true; + $cu->is_locked = false; + $cu->save(); + + $token = \Illuminate\Support\Str::random(64); + + $company_token = new CompanyToken(); + $company_token->user_id = $user->id; + $company_token->company_id = $company->id; + $company_token->account_id = $account->id; + $company_token->name = 'test token'; + $company_token->token = $token; + $company_token->is_system = true; + + $data = [ + 'ids' => [$user->hashed_id], + ]; $response = $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), - 'X-API-TOKEN' => $this->token, - // 'X-API-PASSWORD' => 'ALongAndBriliantPassword', - ])->postJson('/api/v1/users/bulk', $data) - ->assertStatus(200); + 'X-API-TOKEN' => $token, + 'X-API-PASSWORD' => 'ALongAndBriliantPassword', + ])->postJson('/api/v1/users/bulk?action=dete', $data) + ->assertStatus(403); - // nlog($response->json()); - // $response->assertStatus(403); } public function testDisconnectUserOauthMailer() From 879d3181cbc7085c1717c9a58aa012db501b0515 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 9 May 2023 19:53:09 +1000 Subject: [PATCH 11/11] Change the way we display shipping addresses --- app/Services/PdfMaker/Design.php | 152 +++++++++++++++++-------------- app/Utils/HtmlEngine.php | 6 ++ 2 files changed, 91 insertions(+), 67 deletions(-) diff --git a/app/Services/PdfMaker/Design.php b/app/Services/PdfMaker/Design.php index 1c180aa87a..ccf8694c18 100644 --- a/app/Services/PdfMaker/Design.php +++ b/app/Services/PdfMaker/Design.php @@ -254,24 +254,41 @@ class Design extends BaseDesign return $elements; } - $elements = [ - ['element' => 'p', 'content' => ctrans('texts.shipping_address'), 'properties' => ['data-ref' => 'shipping_address-label', 'style' => 'font-weight: bold; text-transform: uppercase']], - ['element' => 'p', 'content' => $this->client->name, 'show_empty' => false, 'properties' => ['data-ref' => 'shipping_address-client.name']], - ['element' => 'p', 'content' => $this->client->shipping_address1, 'show_empty' => false, 'properties' => ['data-ref' => 'shipping_address-client.shipping_address1']], - ['element' => 'p', 'content' => $this->client->shipping_address2, 'show_empty' => false, 'properties' => ['data-ref' => 'shipping_address-client.shipping_address2']], - ['element' => 'p', 'show_empty' => false, 'elements' => [ - ['element' => 'span', 'content' => "{$this->client->shipping_city} ", 'properties' => ['ref' => 'shipping_address-client.shipping_city']], - ['element' => 'span', 'content' => "{$this->client->shipping_state} ", 'properties' => ['ref' => 'shipping_address-client.shipping_state']], - ['element' => 'span', 'content' => "{$this->client->shipping_postal_code} ", 'properties' => ['ref' => 'shipping_address-client.shipping_postal_code']], - ]], - ['element' => 'p', 'content' => optional($this->client->shipping_country)->name, 'show_empty' => false], + // $elements = [ + // ['element' => 'p', 'content' => ctrans('texts.shipping_address'), 'properties' => ['data-ref' => 'shipping_address-label', 'style' => 'font-weight: bold; text-transform: uppercase']], + // ['element' => 'p', 'content' => $this->client->name, 'show_empty' => false, 'properties' => ['data-ref' => 'shipping_address-client.name']], + // ['element' => 'p', 'content' => $this->client->shipping_address1, 'show_empty' => false, 'properties' => ['data-ref' => 'shipping_address-client.shipping_address1']], + // ['element' => 'p', 'content' => $this->client->shipping_address2, 'show_empty' => false, 'properties' => ['data-ref' => 'shipping_address-client.shipping_address2']], + // ['element' => 'p', 'show_empty' => false, 'elements' => [ + // ['element' => 'span', 'content' => "{$this->client->shipping_city} ", 'properties' => ['ref' => 'shipping_address-client.shipping_city']], + // ['element' => 'span', 'content' => "{$this->client->shipping_state} ", 'properties' => ['ref' => 'shipping_address-client.shipping_state']], + // ['element' => 'span', 'content' => "{$this->client->shipping_postal_code} ", 'properties' => ['ref' => 'shipping_address-client.shipping_postal_code']], + // ]], + // ['element' => 'p', 'content' => optional($this->client->shipping_country)->name, 'show_empty' => false], + // ]; + + $address_variables = [ + '$client.address1', + '$client.address2', + '$client.city_state_postal', + '$client.country', + '$client.postal_city_state', + '$client.postal_city', ]; - // if (!is_null($this->context['contact'])) { - // $elements[] = ['element' => 'p', 'content' => $this->context['contact']->email, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-contact.email']]; - // } + $variables = $this->context['pdf_variables']['client_details']; + + $elements = collect($variables)->filter(function ($variable) use ($address_variables){ + return in_array($variable, $address_variables); + })->map(function ($variable){ + + $variable = str_replace('$client.', '$client.shipping_', $variable); + return ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => "client_details-shipping-" . substr($variable, 1)]]; + + })->toArray(); return $elements; + } public function clientDetails(): array @@ -312,71 +329,72 @@ class Design extends BaseDesign return $elements; } - public function entityDetailsx(): array - { - if ($this->type === 'statement') { - $s_date = $this->translateDate($this->options['start_date'], $this->client->date_format(), $this->client->locale()) . " - " . $this->translateDate($this->options['end_date'], $this->client->date_format(), $this->client->locale()); + //@deprecated + // public function entityDetailsx(): array + // { + // if ($this->type === 'statement') { + // $s_date = $this->translateDate($this->options['start_date'], $this->client->date_format(), $this->client->locale()) . " - " . $this->translateDate($this->options['end_date'], $this->client->date_format(), $this->client->locale()); - return [ - ['element' => 'p', 'content' => "

".ctrans('texts.statement')."

", 'properties' => ['data-ref' => 'statement-label']], - ['element' => 'p', 'content' => ctrans('texts.statement_date'), 'properties' => ['data-ref' => 'statement-label'],'elements' => - ['element' => 'span', 'content' => "{$s_date} "] - ], - ['element' => 'p', 'content' => '$balance_due_label', 'properties' => ['data-ref' => 'statement-label'],'elements' => - ['element' => 'span', 'content' => Number::formatMoney($this->invoices->sum('balance'), $this->client)] - ], - ]; - } + // return [ + // ['element' => 'p', 'content' => "

".ctrans('texts.statement')."

", 'properties' => ['data-ref' => 'statement-label']], + // ['element' => 'p', 'content' => ctrans('texts.statement_date'), 'properties' => ['data-ref' => 'statement-label'],'elements' => + // ['element' => 'span', 'content' => "{$s_date} "] + // ], + // ['element' => 'p', 'content' => '$balance_due_label', 'properties' => ['data-ref' => 'statement-label'],'elements' => + // ['element' => 'span', 'content' => Number::formatMoney($this->invoices->sum('balance'), $this->client)] + // ], + // ]; + // } - $variables = $this->context['pdf_variables']['invoice_details']; + // $variables = $this->context['pdf_variables']['invoice_details']; - if ($this->entity instanceof Quote) { - $variables = $this->context['pdf_variables']['quote_details']; + // if ($this->entity instanceof Quote) { + // $variables = $this->context['pdf_variables']['quote_details']; - if ($this->entity->partial > 0) { - $variables[] = '$quote.balance_due'; - } - } + // if ($this->entity->partial > 0) { + // $variables[] = '$quote.balance_due'; + // } + // } - if ($this->entity instanceof Credit) { - $variables = $this->context['pdf_variables']['credit_details']; - } + // if ($this->entity instanceof Credit) { + // $variables = $this->context['pdf_variables']['credit_details']; + // } - if ($this->vendor) { - $variables = $this->context['pdf_variables']['purchase_order_details']; - } + // if ($this->vendor) { + // $variables = $this->context['pdf_variables']['purchase_order_details']; + // } - $elements = []; + // $elements = []; - // We don't want to show account balance or invoice total on PDF.. or any amount with currency. - if ($this->type == self::DELIVERY_NOTE) { - $variables = array_filter($variables, function ($m) { - return !in_array($m, ['$invoice.balance_due', '$invoice.total']); - }); - } + // // We don't want to show account balance or invoice total on PDF.. or any amount with currency. + // if ($this->type == self::DELIVERY_NOTE) { + // $variables = array_filter($variables, function ($m) { + // return !in_array($m, ['$invoice.balance_due', '$invoice.total']); + // }); + // } - foreach ($variables as $variable) { - $_variable = explode('.', $variable)[1]; - $_customs = ['custom1', 'custom2', 'custom3', 'custom4']; + // foreach ($variables as $variable) { + // $_variable = explode('.', $variable)[1]; + // $_customs = ['custom1', 'custom2', 'custom3', 'custom4']; - /* 2/7/2022 don't show custom values if they are empty */ - $var = str_replace("custom", "custom_value", $_variable); + // /* 2/7/2022 don't show custom values if they are empty */ + // $var = str_replace("custom", "custom_value", $_variable); - if (in_array($_variable, $_customs) && !empty($this->entity->{$var})) { - $elements[] = ['element' => 'div', 'properties' => ['style' => "display: table-row; visibility: {$this->entityVariableCheck($_variable)};"],'elements' => [ - ['element' => 'div', 'content' => $variable . '_label', 'properties' => ['class' => 'entity-details-cell', 'data-ref' => 'entity_details-' . substr($variable, 1) . '_label']], - ['element' => 'div', 'content' => $variable, 'properties' => ['class' => 'entity-details-cell', 'data-ref' => 'entity_details-' . substr($variable, 1)]], - ]]; - } else { - $elements[] = ['element' => 'div', 'properties' => ['style' => "display: table-row; visibility: {$this->entityVariableCheck($variable)};"], 'elements' => [ - ['element' => 'div', 'content' => $variable . '_label', 'properties' => ['class' => 'entity-details-cell','data-ref' => 'entity_details-' . substr($variable, 1) . '_label']], - ['element' => 'div', 'content' => $variable, 'properties' => ['class' => 'entity-details-cell','data-ref' => 'entity_details-' . substr($variable, 1)]], - ]]; - } - } + // if (in_array($_variable, $_customs) && !empty($this->entity->{$var})) { + // $elements[] = ['element' => 'div', 'properties' => ['style' => "display: table-row; visibility: {$this->entityVariableCheck($_variable)};"],'elements' => [ + // ['element' => 'div', 'content' => $variable . '_label', 'properties' => ['class' => 'entity-details-cell', 'data-ref' => 'entity_details-' . substr($variable, 1) . '_label']], + // ['element' => 'div', 'content' => $variable, 'properties' => ['class' => 'entity-details-cell', 'data-ref' => 'entity_details-' . substr($variable, 1)]], + // ]]; + // } else { + // $elements[] = ['element' => 'div', 'properties' => ['style' => "display: table-row; visibility: {$this->entityVariableCheck($variable)};"], 'elements' => [ + // ['element' => 'div', 'content' => $variable . '_label', 'properties' => ['class' => 'entity-details-cell','data-ref' => 'entity_details-' . substr($variable, 1) . '_label']], + // ['element' => 'div', 'content' => $variable, 'properties' => ['class' => 'entity-details-cell','data-ref' => 'entity_details-' . substr($variable, 1)]], + // ]]; + // } + // } - return $elements; - } + // return $elements; + // } public function entityDetails(): array { diff --git a/app/Utils/HtmlEngine.php b/app/Utils/HtmlEngine.php index 76186c9e90..17c613482d 100644 --- a/app/Utils/HtmlEngine.php +++ b/app/Utils/HtmlEngine.php @@ -460,6 +460,12 @@ class HtmlEngine $data['$client.shipping_state'] = ['value' => $this->client->shipping_state ?: ' ', 'label' => ctrans('texts.shipping_state')]; $data['$client.shipping_postal_code'] = ['value' => $this->client->shipping_postal_code ?: ' ', 'label' => ctrans('texts.shipping_postal_code')]; $data['$client.shipping_country'] = ['value' => isset($this->client->shipping_country->name) ? ctrans('texts.country_' . $this->client->shipping_country->name) : '', 'label' => ctrans('texts.shipping_country')]; + $data['$shipping_postal_city_state'] = ['value' => $this->entity->present()->cityStateZip($this->client->shipping_city, $this->client->shipping_state, $this->client->shipping_postal_code, true) ?: ' ', 'label' => ctrans('texts.postal_city_state')]; + $data['$client.shipping_postal_city_state'] = &$data['$shipping_postal_city_state']; + $data['$shipping_postal_city'] = ['value' => $this->entity->present()->cityStateZip($this->client->shipping_city, null, $this->client->shipping_postal_code, true) ?: ' ', 'label' => ctrans('texts.postal_city')]; + $data['$client.shipping_postal_city'] = &$data['$shipping_postal_city']; + $data['$shipping_city_state_postal'] = ['value' => $this->entity->present()->cityStateZip($this->client->shipping_city, $this->client->shipping_state, $this->client->shipping_postal_code, false) ?: ' ', 'label' => ctrans('texts.city_state_postal')]; + $data['$client.shipping_city_state_postal'] = &$data['$shipping_city_state_postal']; $data['$client.currency'] = ['value' => $this->client->currency()->code, 'label' => ''];