1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-09-21 00:41:34 +02:00

Merge pull request #8506 from turbo124/v5-develop

ES E-Invoicing
This commit is contained in:
David Bomba 2023-05-09 20:20:34 +10:00 committed by GitHub
commit 6265cee79e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 434 additions and 158 deletions

View File

@ -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]

View File

@ -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";

View File

@ -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
{
@ -24,6 +25,9 @@ class BulkUserRequest extends Request
*/
public function authorize() : bool
{
if($this->action == 'delete' && in_array(auth()->user()->hashed_id, $this->ids))
return false;
return auth()->user()->isAdmin();
}
@ -44,4 +48,9 @@ class BulkUserRequest extends Request
$this->replace($input);
}
protected function failedAuthorization()
{
throw new AuthorizationException("This Action is unauthorized.");
}
}

View File

@ -95,11 +95,12 @@ class Document extends BaseModel
const DOCUMENT_PREVIEW_SIZE = 300; // pixels
/**
* @var array
* @var array<string>
*/
protected $fillable = [
'is_default',
'is_public',
'name',
];
/**

View File

@ -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);

View File

@ -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;
}
}
}

View File

@ -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);

View File

@ -106,7 +106,7 @@ class StripePaymentDriver extends BaseDriver
/**
* Initializes the Stripe API.
* @return void
* @return self
*/
public function init()
{

View File

@ -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,
@ -211,6 +212,9 @@ class FacturaEInvoice extends AbstractService
}
if(count($data) == 0)
$data[Facturae::TAX_IVA] = 0;
return $data;
}
@ -257,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;
@ -291,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" => $this->invoice->client->present()->website(),
"contactPeople" => $this->invoice->client->present()->first_name()." ".$this->invoice->client->present()->last_name(),
"website" => substr($this->invoice->client->present()->website(), 0 ,60),
"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
]);
@ -317,7 +322,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;
}

View File

@ -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' => "<h2>".ctrans('texts.statement')."</h2>", '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' => "<h2>".ctrans('texts.statement')."</h2>", '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
{

View File

@ -12,18 +12,15 @@
namespace App\Services\Tax;
use App\Models\Client;
use App\Models\Company;
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;

View File

@ -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' => ''];

View File

@ -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

View File

@ -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

View File

@ -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'
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'

View File

@ -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

View File

@ -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;
}

View File

@ -11,11 +11,14 @@
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 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 +43,95 @@ 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));
}
// 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);
// }
}

View File

@ -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
@ -56,6 +58,64 @@ class UserTest extends TestCase
);
}
public function testUserAttemptingtToDeleteThemselves()
{
$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' => $token,
'X-API-PASSWORD' => 'ALongAndBriliantPassword',
])->postJson('/api/v1/users/bulk?action=dete', $data)
->assertStatus(403);
}
public function testDisconnectUserOauthMailer()
{
$user =