Merge pull request #9604 from turbo124/v5-develop

v5.9.3
This commit is contained in:
David Bomba 2024-06-07 20:32:43 +10:00 committed by GitHub
commit dcf474a95c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
65 changed files with 11638 additions and 10429 deletions

View File

@ -44,6 +44,7 @@ jobs:
git clone https://${{secrets.commit_secret}}@github.com/invoiceninja/ui.git git clone https://${{secrets.commit_secret}}@github.com/invoiceninja/ui.git
cd ui cd ui
git checkout develop git checkout develop
cp ../vite.config.ts.react ./vite.config.js
npm i npm i
npm run build npm run build
cp -r dist/* ../public/ cp -r dist/* ../public/
@ -60,7 +61,8 @@ jobs:
sudo rm -rf node_modules sudo rm -rf node_modules
sudo rm -rf .git sudo rm -rf .git
sudo rm .env sudo rm .env
sudo rm -rf ui
- name: Build project - name: Build project
run: | run: |
shopt -s dotglob shopt -s dotglob

View File

@ -72,7 +72,6 @@ jobs:
- name: Build project - name: Build project
run: | run: |
zip -r /home/runner/work/invoiceninja/invoiceninja.zip .* -x "../*"
shopt -s dotglob shopt -s dotglob
tar --exclude='public/storage' --exclude='./htaccess' --exclude='invoiceninja.zip' -zcvf /home/runner/work/invoiceninja/invoiceninja.tar * tar --exclude='public/storage' --exclude='./htaccess' --exclude='invoiceninja.zip' -zcvf /home/runner/work/invoiceninja/invoiceninja.tar *
- name: Release - name: Release
@ -82,5 +81,4 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
files: | files: |
/home/runner/work/invoiceninja/invoiceninja.tar /home/runner/work/invoiceninja/invoiceninja.tar
/home/runner/work/invoiceninja/invoiceninja.zip

View File

@ -1 +1 @@
5.9.2 5.9.3

View File

@ -66,12 +66,14 @@ class DbQuery extends GenericMixedMetric
public $double_metric2 = 1; public $double_metric2 = 1;
public function __construct($string_metric5, $string_metric6, $int_metric1, $double_metric2, $string_metric7) public function __construct($string_metric5, $string_metric6, $int_metric1, $double_metric2, $string_metric7, $string_metric8, $string_metric9)
{ {
$this->int_metric1 = $int_metric1; $this->int_metric1 = $int_metric1;
$this->string_metric5 = $string_metric5; $this->string_metric5 = $string_metric5;
$this->string_metric6 = $string_metric6; $this->string_metric6 = $string_metric6;
$this->double_metric2 = $double_metric2; $this->double_metric2 = $double_metric2;
$this->string_metric7 = $string_metric7; $this->string_metric7 = $string_metric7;
$this->string_metric8 = $string_metric8;
$this->string_metric9 = $string_metric9;
} }
} }

View File

@ -29,7 +29,7 @@ class CompanySettings extends BaseSettings
public $besr_id = ''; //@implemented public $besr_id = ''; //@implemented
public $lock_invoices = 'off'; //off,when_sent,when_paid //@implemented public $lock_invoices = 'off'; //off,when_sent,when_paid,end_of_month //@implemented
public $enable_client_portal_tasks = false; //@ben to implement public $enable_client_portal_tasks = false; //@ben to implement

View File

@ -1,24 +0,0 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataProviders;
/**
* Class FACT1.
*/
class FACT1
{
public function build()
{
$i = new \InvoiceNinja\EInvoice\Models\FACT1\Invoice();
}
}

View File

@ -24,9 +24,10 @@ use App\Services\Email\Email;
use App\Models\BankIntegration; use App\Models\BankIntegration;
use App\Services\Email\EmailObject; use App\Services\Email\EmailObject;
use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Cache;
use Illuminate\Mail\Mailables\Address;
use App\Helpers\Bank\Nordigen\Transformer\AccountTransformer; use App\Helpers\Bank\Nordigen\Transformer\AccountTransformer;
use App\Helpers\Bank\Nordigen\Transformer\TransactionTransformer; use App\Helpers\Bank\Nordigen\Transformer\TransactionTransformer;
use Illuminate\Mail\Mailables\Address;
class Nordigen class Nordigen
{ {
@ -149,6 +150,10 @@ class Nordigen
public function disabledAccountEmail(BankIntegration $bank_integration): void public function disabledAccountEmail(BankIntegration $bank_integration): void
{ {
$cache_key = "email_quota:{$bank_integration->company->company_key}:{$bank_integration->id}";
if(Cache::has($cache_key))
return;
App::setLocale($bank_integration->company->getLocale()); App::setLocale($bank_integration->company->getLocale());
@ -163,7 +168,8 @@ class Nordigen
$mo->email_template_subject = 'nordigen_requisition_subject'; $mo->email_template_subject = 'nordigen_requisition_subject';
Email::dispatch($mo, $bank_integration->company); Email::dispatch($mo, $bank_integration->company);
Cache::put($cache_key, true, 60 * 60 * 24);
} }

View File

@ -38,7 +38,7 @@ use App\Transformers\ArraySerializer;
use App\Transformers\EntityTransformer; use App\Transformers\EntityTransformer;
use League\Fractal\Resource\Collection; use League\Fractal\Resource\Collection;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Invoiceninja\Einvoice\Decoder\Schema; use InvoiceNinja\EInvoice\Decoder\Schema;
use League\Fractal\Serializer\JsonApiSerializer; use League\Fractal\Serializer\JsonApiSerializer;
use League\Fractal\Pagination\IlluminatePaginatorAdapter; use League\Fractal\Pagination\IlluminatePaginatorAdapter;
use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Contracts\Container\BindingResolutionException;
@ -998,7 +998,7 @@ class BaseController extends Controller
if(request()->has('einvoice')){ if(request()->has('einvoice')){
$ro = new Schema(); $ro = new Schema();
$response_data['einvoice_schema'] = $ro('FACT1'); $response_data['einvoice_schema'] = $ro('Peppol');
} }

View File

@ -117,11 +117,21 @@ class CompanyController extends BaseController
/** @var \App\Models\User $user */ /** @var \App\Models\User $user */
$user = auth()->user(); $user = auth()->user();
$companies = Company::whereAccountId($user->company()->account->id); $companies = Company::where('account_id', $user->company()->account->id);
return $this->listResponse($companies); return $this->listResponse($companies);
} }
public function current()
{
/** @var \App\Models\User $user */
$user = auth()->user();
$company = Company::find($user->company()->id);
return $this->itemResponse($company);
}
/** /**
* Show the form for creating a new resource. * Show the form for creating a new resource.
* *

View File

@ -408,7 +408,7 @@ class InvoiceController extends BaseController
} }
if ($invoice->isLocked()) { if ($invoice->isLocked()) {
return response()->json(['message' => ctrans('texts.locked_invoice')], 422); return response()->json(['message' => '', 'errors' => ['number' => ctrans('texts.locked_invoice')]], 422);
} }
$old_invoice = $invoice->line_items; $old_invoice = $invoice->line_items;

View File

@ -13,7 +13,7 @@ namespace App\Http\Controllers;
use App\Utils\Statics; use App\Utils\Statics;
use Illuminate\Http\Response; use Illuminate\Http\Response;
use Invoiceninja\Einvoice\Decoder\Schema; use InvoiceNinja\EInvoice\Decoder\Schema;
class StaticController extends BaseController class StaticController extends BaseController
{ {
@ -62,7 +62,7 @@ class StaticController extends BaseController
if(request()->has('einvoice')){ if(request()->has('einvoice')){
$schema = new Schema(); $schema = new Schema();
$response_data['einvoice_schema'] = $schema('FACT1'); $response_data['einvoice_schema'] = $schema('Peppol');
} }

View File

@ -67,7 +67,7 @@ class UpdateInvoiceRequest extends Request
$rules['client_id'] = ['bail', 'sometimes', Rule::in([$this->invoice->client_id])]; $rules['client_id'] = ['bail', 'sometimes', Rule::in([$this->invoice->client_id])];
$rules['line_items'] = 'array'; $rules['line_items'] = 'array';
$rules['discount'] = 'sometimes|numeric|max:99999999999999'; $rules['discount'] = 'sometimes|numeric|max:99999999999999';
$rules['project_id'] = ['bail', 'sometimes', new ValidProjectForClient($this->all())]; $rules['project_id'] = ['bail', 'sometimes', new ValidProjectForClient($this->all())];
$rules['tax_rate1'] = 'bail|sometimes|numeric'; $rules['tax_rate1'] = 'bail|sometimes|numeric';
$rules['tax_rate2'] = 'bail|sometimes|numeric'; $rules['tax_rate2'] = 'bail|sometimes|numeric';
@ -80,7 +80,7 @@ $rules['discount'] = 'sometimes|numeric|max:99999999999999';
$rules['partial'] = 'bail|sometimes|nullable|numeric'; $rules['partial'] = 'bail|sometimes|nullable|numeric';
$rules['amount'] = ['sometimes', 'bail', 'numeric', 'max:99999999999999']; $rules['amount'] = ['sometimes', 'bail', 'numeric', 'max:99999999999999'];
$rules['date'] = 'bail|sometimes|date:Y-m-d'; $rules['date'] = 'bail|sometimes|date:Y-m-d';
// $rules['partial_due_date'] = ['bail', 'sometimes', 'exclude_if:partial,0', Rule::requiredIf(fn () => $this->partial > 0), 'date', 'before:due_date']; // $rules['partial_due_date'] = ['bail', 'sometimes', 'exclude_if:partial,0', Rule::requiredIf(fn () => $this->partial > 0), 'date', 'before:due_date'];
// $rules['due_date'] = ['bail', 'sometimes', 'nullable', 'after:partial_due_date', Rule::requiredIf(fn () => strlen($this->partial_due_date) > 1), 'date']; // $rules['due_date'] = ['bail', 'sometimes', 'nullable', 'after:partial_due_date', Rule::requiredIf(fn () => strlen($this->partial_due_date) > 1), 'date'];

View File

@ -16,6 +16,7 @@ use Illuminate\Contracts\Validation\Rule;
/** /**
* Class LockedInvoiceRule. * Class LockedInvoiceRule.
* @deprecated
*/ */
class LockedInvoiceRule implements Rule class LockedInvoiceRule implements Rule
{ {
@ -67,6 +68,13 @@ class LockedInvoiceRule implements Rule
} }
return true; return true;
//if now is greater than the end of month the invoice was dated - do not modify
case 'end_of_month':
if(\Carbon\Carbon::parse($this->invoice->date)->setTimezone($this->invoice->company->timezone()->name)->endOfMonth()->lte(now()))
return false;
return true;
default: default:
return true; return true;
} }

View File

@ -31,6 +31,7 @@ class ProductMap
12 => 'product.custom_value2', 12 => 'product.custom_value2',
13 => 'product.custom_value3', 13 => 'product.custom_value3',
14 => 'product.custom_value4', 14 => 'product.custom_value4',
15 => 'product.image_url'
]; ];
} }
@ -52,6 +53,7 @@ class ProductMap
12 => 'texts.custom_value', 12 => 'texts.custom_value',
13 => 'texts.custom_value', 13 => 'texts.custom_value',
14 => 'texts.custom_value', 14 => 'texts.custom_value',
15 => 'texts.image_url',
]; ];
} }
} }

View File

@ -42,6 +42,7 @@ class ProductTransformer extends BaseTransformer
'custom_value2' => $this->getString($data, 'product.custom_value2'), 'custom_value2' => $this->getString($data, 'product.custom_value2'),
'custom_value3' => $this->getString($data, 'product.custom_value3'), 'custom_value3' => $this->getString($data, 'product.custom_value3'),
'custom_value4' => $this->getString($data, 'product.custom_value4'), 'custom_value4' => $this->getString($data, 'product.custom_value4'),
'product_image' => $this->getString($data, 'product.image_url'),
]; ];
} }
} }

View File

@ -94,7 +94,7 @@ class QuoteTransformer extends BaseTransformer
), ),
'footer' => $this->getString($quote_data, 'quote.footer'), 'footer' => $this->getString($quote_data, 'quote.footer'),
'partial' => $this->getFloat($quote_data, 'quote.partial'), 'partial' => $this->getFloat($quote_data, 'quote.partial'),
'partial_due_date' => isset($invoice_data['quote.partial_due_date']) ? $this->parseDate($quote_data['quote.partial_due_date']) : null, 'partial_due_date' => isset($quote_data['quote.partial_due_date']) ? $this->parseDate($quote_data['quote.partial_due_date']) : null,
'custom_surcharge1' => $this->getString( 'custom_surcharge1' => $this->getString(
$quote_data, $quote_data,
'quote.custom_surcharge1' 'quote.custom_surcharge1'

View File

@ -79,6 +79,7 @@ class SubscriptionCron
->cursor() ->cursor()
->each(function ($company_id){ ->each(function ($company_id){
/** @var \App\Models\Company $company */
$company = Company::find($company_id); $company = Company::find($company_id);
$timezone_now = now()->setTimezone($company->timezone()->name ?? 'Pacific/Midway'); $timezone_now = now()->setTimezone($company->timezone()->name ?? 'Pacific/Midway');

View File

@ -168,11 +168,6 @@ class NinjaMailerJob implements ShouldQueue
$this->logMailError($e->getMessage(), $this->company->clients()->first()); $this->logMailError($e->getMessage(), $this->company->clients()->first());
return; return;
} }
catch(\Symfony\Component\Mailer\Transport\Dsn $e){
nlog("Incorrectly configured mail server - setting to default mail driver.");
$this->nmo->settings->email_sending_method = 'default';
return $this->setMailDriver();
}
catch(\Google\Service\Exception $e){ catch(\Google\Service\Exception $e){
if ($e->getCode() == '429') { if ($e->getCode() == '429') {
@ -194,7 +189,7 @@ class NinjaMailerJob implements ShouldQueue
* this merges a text string with a json object * this merges a text string with a json object
* need to harvest the ->Message property using the following * need to harvest the ->Message property using the following
*/ */
if (stripos($e->getMessage(), 'code 300') || stripos($e->getMessage(), 'code 413')) { if (stripos($e->getMessage(), 'code 300') !== false || stripos($e->getMessage(), 'code 413') !== false) {
$message = "Either Attachment too large, or recipient has been suppressed."; $message = "Either Attachment too large, or recipient has been suppressed.";
$this->fail(); $this->fail();
@ -209,7 +204,15 @@ class NinjaMailerJob implements ShouldQueue
return; return;
} }
if (stripos($e->getMessage(), 'code 406')) { if(stripos($e->getMessage(), 'Dsn') !== false){
nlog("Incorrectly configured mail server - setting to default mail driver.");
$this->nmo->settings->email_sending_method = 'default';
return $this->setMailDriver();
}
if (stripos($e->getMessage(), 'code 406') !== false) {
$email = $this->nmo->to_user->email ?? ''; $email = $this->nmo->to_user->email ?? '';
@ -386,17 +389,17 @@ class NinjaMailerJob implements ShouldQueue
$company = $this->company; $company = $this->company;
$smtp_host = $company->smtp_host; $smtp_host = $company->smtp_host ?? '';
$smtp_port = $company->smtp_port; $smtp_port = $company->smtp_port;
$smtp_username = $company->smtp_username; $smtp_username = $company->smtp_username ?? '';
$smtp_password = $company->smtp_password; $smtp_password = $company->smtp_password ?? '';
$smtp_encryption = $company->smtp_encryption ?? 'tls'; $smtp_encryption = $company->smtp_encryption ?? 'tls';
$smtp_local_domain = strlen($company->smtp_local_domain) > 2 ? $company->smtp_local_domain : null; $smtp_local_domain = strlen($company->smtp_local_domain) > 2 ? $company->smtp_local_domain : null;
$smtp_verify_peer = $company->smtp_verify_peer ?? true; $smtp_verify_peer = $company->smtp_verify_peer ?? true;
if(strlen($smtp_host ?? '') <= 1 || if(strlen($smtp_host) <= 1 ||
strlen($smtp_username ?? '') <= 1 || strlen($smtp_username) <= 1 ||
strlen($smtp_password ?? '') <= 1 strlen($smtp_password) <= 1
) )
{ {
$this->nmo->settings->email_sending_method = 'default'; $this->nmo->settings->email_sending_method = 'default';
@ -790,6 +793,7 @@ class NinjaMailerJob implements ShouldQueue
private function refreshOfficeToken(User $user) private function refreshOfficeToken(User $user)
{ {
$expiry = $user->oauth_user_token_expiry ?: now()->subDay(); $expiry = $user->oauth_user_token_expiry ?: now()->subDay();
$token = false;
if ($expiry->lt(now())) { if ($expiry->lt(now())) {
$guzzle = new \GuzzleHttp\Client(); $guzzle = new \GuzzleHttp\Client();
@ -798,7 +802,7 @@ class NinjaMailerJob implements ShouldQueue
if (!$user->oauth_user_refresh_token || $user->oauth_user_refresh_token == '') { if (!$user->oauth_user_refresh_token || $user->oauth_user_refresh_token == '') {
return false; return false;
} }
try { try {
$token = json_decode($guzzle->post($url, [ $token = json_decode($guzzle->post($url, [
'form_params' => [ 'form_params' => [

View File

@ -38,7 +38,7 @@ class NinjaMailerObject
public $template = false; public $template = false;
/* @var bool | App\Models\Invoice | App\Models\Quote | App\Models\Credit | App\Models\RecurringInvoice | App\Models\PurchaseOrder | App\Models\Payment $entity */ /* @var \bool | App\Models\Invoice | App\Models\Quote | App\Models\Credit | App\Models\RecurringInvoice | App\Models\PurchaseOrder | App\Models\Payment $entity */
public $entity = false; public $entity = false;
public $reminder_template = ''; public $reminder_template = '';

View File

@ -168,46 +168,51 @@ class ReminderJob implements ShouldQueue
$amount = $fees[0]; $amount = $fees[0];
$percent = $fees[1]; $percent = $fees[1];
$temp_invoice_balance = $over_due_invoice->balance; $invoice = false;
if ($amount <= 0 && $percent <= 0) { //2024-06-07 this early return prevented any reminders from sending for users who enabled lock_invoices.
return; if ($amount > 0 || $percent > 0) {
// return;
$fee = $amount;
if ($over_due_invoice->partial > 0) {
$fee += round($over_due_invoice->partial * $percent / 100, 2);
} else {
$fee += round($over_due_invoice->balance * $percent / 100, 2);
}
/** @var \App\Models\Invoice $invoice */
$invoice = InvoiceFactory::create($over_due_invoice->company_id, $over_due_invoice->user_id);
$invoice->client_id = $over_due_invoice->client_id;
$invoice->date = now()->format('Y-m-d');
$invoice->due_date = now()->format('Y-m-d');
$invoice_item = new InvoiceItem();
$invoice_item->type_id = '5';
$invoice_item->product_key = trans('texts.fee');
$invoice_item->notes = ctrans('texts.late_fee_added_locked_invoice', ['invoice' => $over_due_invoice->number, 'date' => $this->translateDate(now()->startOfDay(), $over_due_invoice->client->date_format(), $over_due_invoice->client->locale())]);
$invoice_item->quantity = 1;
$invoice_item->cost = $fee;
$invoice_items = [];
$invoice_items[] = $invoice_item;
$invoice->line_items = $invoice_items;
/**Refresh Invoice values*/
$invoice = $invoice->calc()->getInvoice();
$invoice->service()
->createInvitations()
->applyNumber()
->markSent()
->save();
} }
$fee = $amount; if(!$invoice){
$invoice = $over_due_invoice;
if ($over_due_invoice->partial > 0) {
$fee += round($over_due_invoice->partial * $percent / 100, 2);
} else {
$fee += round($over_due_invoice->balance * $percent / 100, 2);
} }
/** @var \App\Models\Invoice $invoice */
$invoice = InvoiceFactory::create($over_due_invoice->company_id, $over_due_invoice->user_id);
$invoice->client_id = $over_due_invoice->client_id;
$invoice->date = now()->format('Y-m-d');
$invoice->due_date = now()->format('Y-m-d');
$invoice_item = new InvoiceItem();
$invoice_item->type_id = '5';
$invoice_item->product_key = trans('texts.fee');
$invoice_item->notes = ctrans('texts.late_fee_added_locked_invoice', ['invoice' => $over_due_invoice->number, 'date' => $this->translateDate(now()->startOfDay(), $over_due_invoice->client->date_format(), $over_due_invoice->client->locale())]);
$invoice_item->quantity = 1;
$invoice_item->cost = $fee;
$invoice_items = [];
$invoice_items[] = $invoice_item;
$invoice->line_items = $invoice_items;
/**Refresh Invoice values*/
$invoice = $invoice->calc()->getInvoice();
$invoice->service()
->createInvitations()
->applyNumber()
->markSent()
->save();
$enabled_reminder = 'enable_'.$reminder_template; $enabled_reminder = 'enable_'.$reminder_template;
if ($reminder_template == 'endless_reminder') { if ($reminder_template == 'endless_reminder') {
$enabled_reminder = 'enable_reminder_endless'; $enabled_reminder = 'enable_reminder_endless';

View File

@ -45,6 +45,7 @@ class RFF extends Component
$contact = auth()->guard('contact'); $contact = auth()->guard('contact');
/** @var \App\Models\ClientContact $contact */
$contact->user()->update([ $contact->user()->update([
'first_name' => $data['contact_first_name'], 'first_name' => $data['contact_first_name'],
'last_name' => $data['contact_last_name'], 'last_name' => $data['contact_last_name'],
@ -65,6 +66,7 @@ class RFF extends Component
public function render() public function render()
{ {
/** @var \App\Models\CompanyGateway $gateway */
$gateway = CompanyGateway::find($this->context['form']['company_gateway_id']); $gateway = CompanyGateway::find($this->context['form']['company_gateway_id']);
$countries = Cache::get('countries'); $countries = Cache::get('countries');

View File

@ -402,7 +402,7 @@ class RequiredClientInfo extends Component
if ($this->unfilled_fields === 0 && (!$this->company_gateway->always_show_required_fields || $this->is_subscription)) { if ($this->unfilled_fields === 0 && (!$this->company_gateway->always_show_required_fields || $this->is_subscription)) {
$this->dispatch( $this->dispatch(
'passed-required-fields-check', 'passed-required-fields-check',
client_postal_code: $this->contact->client->postal_code client_postal_code: $_contact->client->postal_code
); );
} }

View File

@ -386,6 +386,7 @@ class Client extends BaseModel implements HasLocalePreference
// } // }
return $languages->first(function ($item) { return $languages->first(function ($item) {
/** @var \stdClass $item */
return $item->id == $this->getSetting('language_id'); return $item->id == $this->getSetting('language_id');
}); });
} }
@ -419,6 +420,8 @@ class Client extends BaseModel implements HasLocalePreference
// } // }
return $date_formats->first(function ($item) { return $date_formats->first(function ($item) {
/** @var \stdClass $item */
return $item->id == $this->getSetting('date_format_id'); return $item->id == $this->getSetting('date_format_id');
})->format; })->format;
} }
@ -433,6 +436,8 @@ class Client extends BaseModel implements HasLocalePreference
// } // }
return $currencies->first(function ($item) { return $currencies->first(function ($item) {
/** @var \stdClass $item */
return $item->id == $this->getSetting('currency_id'); return $item->id == $this->getSetting('currency_id');
}); });
} }

View File

@ -636,17 +636,21 @@ class Company extends BaseModel
public function country() public function country()
{ {
$companies = Cache::get('countries'); $countries = app('countries');
if (! $companies) { // $countries = Cache::get('countries');
$this->buildCache(true);
$companies = Cache::get('countries'); // if (! $companies) {
} // $this->buildCache(true);
return $companies->filter(function ($item) { // $companies = Cache::get('countries');
// }
return $countries->first(function ($item) {
/** @var \stdClass $item */
return $item->id == $this->getSetting('country_id'); return $item->id == $this->getSetting('country_id');
})->first(); });
// return $this->belongsTo(Country::class); // return $this->belongsTo(Country::class);
// return Country::find($this->settings->country_id); // return Country::find($this->settings->country_id);
@ -659,15 +663,18 @@ class Company extends BaseModel
public function timezone() public function timezone()
{ {
$timezones = Cache::get('timezones'); // $timezones = Cache::get('timezones');
if (! $timezones) { $timezones = app('timezones');
$this->buildCache(true);
}
return $timezones->filter(function ($item) { // if (! $timezones) {
// $this->buildCache(true);
// }
return $timezones->first(function ($item) {
/** @var \stdClass $item */
return $item->id == $this->settings->timezone_id; return $item->id == $this->settings->timezone_id;
})->first(); });
// return Timezone::find($this->settings->timezone_id); // return Timezone::find($this->settings->timezone_id);
} }

View File

@ -563,6 +563,8 @@ class Invoice extends BaseModel
return $this->status_id == self::STATUS_SENT; return $this->status_id == self::STATUS_SENT;
case 'when_paid': case 'when_paid':
return $this->status_id == self::STATUS_PAID || $this->status_id == self::STATUS_PARTIAL; return $this->status_id == self::STATUS_PAID || $this->status_id == self::STATUS_PARTIAL;
case 'end_of_month':
return \Carbon\Carbon::parse($this->date)->setTimezone($this->company->timezone()->name)->endOfMonth()->lte(now());
default: default:
return false; return false;
} }

View File

@ -87,7 +87,8 @@ class BTCPayPaymentDriver extends BaseDriver
public function processWebhookRequest() public function processWebhookRequest()
{ {
$webhook_payload = file_get_contents('php://input'); $webhook_payload = file_get_contents('php://input');
$sig = false;
/** @var \stdClass $btcpayRep */
$btcpayRep = json_decode($webhook_payload); $btcpayRep = json_decode($webhook_payload);
if ($btcpayRep == null) { if ($btcpayRep == null) {
throw new PaymentFailed('Empty data'); throw new PaymentFailed('Empty data');

View File

@ -176,6 +176,27 @@ class PayPalBasePaymentDriver extends BaseDriver
} }
public function handleDuplicateInvoiceId(string $orderID)
{
$_invoice = collect($this->payment_hash->data->invoices)->first();
$invoice = Invoice::withTrashed()->find($this->decodePrimaryKey($_invoice->invoice_id));
$new_invoice_number = $invoice->number."_".Str::random(5);
$update_data =
[[
"op" => "replace",
"path" => "/purchase_units/@reference_id=='default'/invoice_id",
"value" => $new_invoice_number,
]];
$r = $this->gatewayRequest("/v2/checkout/orders/{$orderID}", 'patch', $update_data);
$r = $this->gatewayRequest("/v2/checkout/orders/{$orderID}/capture", 'post', ['body' => '']);
return $r;
}
public function getShippingAddress(): ?array public function getShippingAddress(): ?array
{ {
return $this->company_gateway->require_shipping_address ? return $this->company_gateway->require_shipping_address ?
@ -369,13 +390,30 @@ class PayPalBasePaymentDriver extends BaseDriver
} }
public function handleProcessingFailure(array $response)
{
SystemLogger::dispatch(
['response' => $response],
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_FAILURE,
SystemLog::TYPE_PAYPAL,
$this->client,
$this->client->company ?? $this->company_gateway->company,
);
switch ($response['name']) {
case 'NOT_AUTHORIZED':
throw new PaymentFailed("There was a permissions issue processing this payment, please contact the merchant. ", 401);
break;
default:
throw new PaymentFailed("Unknown error occurred processing payment. Please contact merchant.", 500);
break;
}
}
public function handleRetry($response, $request) { public function handleRetry($response, $request) {
// $response = $r->json();
// nlog($response['details']);
// if(in_array($response['details'][0]['issue'], ['INSTRUMENT_DECLINED', 'PAYER_ACTION_REQUIRED']))
return response()->json($response->json()); return response()->json($response->json());
} }

View File

@ -114,15 +114,19 @@ class PayPalPPCPPaymentDriver extends PayPalBasePaymentDriver
public function processPaymentResponse($request) public function processPaymentResponse($request)
{ {
nlog("response");
$request['gateway_response'] = str_replace("Error: ", "", $request['gateway_response']); $request['gateway_response'] = str_replace("Error: ", "", $request['gateway_response']);
$response = json_decode($request['gateway_response'], true); $response = json_decode($request['gateway_response'], true);
nlog($response);
if($request->has('token') && strlen($request->input('token')) > 2) { if($request->has('token') && strlen($request->input('token')) > 2) {
return $this->processTokenPayment($request, $response); return $this->processTokenPayment($request, $response);
} }
//capture //capture
$orderID = $response['orderID']; $orderID = $response['orderID'] ?? $this->payment_hash->data->orderID;
if($this->company_gateway->require_shipping_address) { if($this->company_gateway->require_shipping_address) {
@ -149,36 +153,26 @@ class PayPalPPCPPaymentDriver extends PayPalBasePaymentDriver
if($r->status() == 422) { if($r->status() == 422) {
//handle conditions where the client may need to try again. //handle conditions where the client may need to try again.
return $this->handleRetry($r, $request); // return $this->handleRetry($r, $request);
$r = $this->handleDuplicateInvoiceId($orderID);
} }
} catch(\Exception $e) { } catch(\Exception $e) {
//Rescue for duplicate invoice_id //Rescue for duplicate invoice_id
if(stripos($e->getMessage(), 'DUPLICATE_INVOICE_ID') !== false) { if(stripos($e->getMessage(), 'DUPLICATE_INVOICE_ID') !== false) {
$r = $this->handleDuplicateInvoiceId($orderID);
$_invoice = collect($this->payment_hash->data->invoices)->first();
$invoice = Invoice::withTrashed()->find($this->decodePrimaryKey($_invoice->invoice_id));
$new_invoice_number = $invoice->number."_".Str::random(5);
$update_data =
[[
"op" => "replace",
"path" => "/purchase_units/@reference_id=='default'/invoice_id",
"value" => $new_invoice_number,
]];
$r = $this->gatewayRequest("/v2/checkout/orders/{$orderID}", 'patch', $update_data);
$r = $this->gatewayRequest("/v2/checkout/orders/{$orderID}/capture", 'post', ['body' => '']);
} }
} }
$response = $r; $response = $r;
nlog("Process response =>");
nlog($response->json());
if(isset($response['status']) && $response['status'] == 'COMPLETED' && isset($response['purchase_units'])) { if(isset($response['status']) && $response['status'] == 'COMPLETED' && isset($response['purchase_units'])) {
$data = [ $data = [
@ -222,7 +216,6 @@ class PayPalPPCPPaymentDriver extends PayPalBasePaymentDriver
return response()->json(['message' => $message], 400); return response()->json(['message' => $message], 400);
// throw new PaymentFailed($message, 400);
} }
} }
@ -303,6 +296,8 @@ class PayPalPPCPPaymentDriver extends PayPalBasePaymentDriver
$r = $this->gatewayRequest('/v2/checkout/orders', 'post', $order); $r = $this->gatewayRequest('/v2/checkout/orders', 'post', $order);
$this->payment_hash->withData("orderID", $r->json()['id']);
return $r->json()['id']; return $r->json()['id'];
} }

View File

@ -60,7 +60,7 @@ class PayPalRestPaymentDriver extends PayPalBasePaymentDriver
*/ */
public function processPaymentResponse($request) public function processPaymentResponse($request)
{ {
nlog("response");
$this->init(); $this->init();
$request['gateway_response'] = str_replace("Error: ", "", $request['gateway_response']); $request['gateway_response'] = str_replace("Error: ", "", $request['gateway_response']);
@ -72,7 +72,8 @@ class PayPalRestPaymentDriver extends PayPalBasePaymentDriver
return $this->processTokenPayment($request, $response); return $this->processTokenPayment($request, $response);
//capture //capture
$orderID = $response['orderID'];
$orderID = $response['orderID'] ?? $this->payment_hash->data->orderID;
if($this->company_gateway->require_shipping_address) { if($this->company_gateway->require_shipping_address) {
@ -100,7 +101,10 @@ class PayPalRestPaymentDriver extends PayPalBasePaymentDriver
if($r->status() == 422){ if($r->status() == 422){
//handle conditions where the client may need to try again. //handle conditions where the client may need to try again.
return $this->handleRetry($r, $request);
$r = $this->handleDuplicateInvoiceId($orderID);
} }
} }
@ -109,21 +113,8 @@ class PayPalRestPaymentDriver extends PayPalBasePaymentDriver
//Rescue for duplicate invoice_id //Rescue for duplicate invoice_id
if(stripos($e->getMessage(), 'DUPLICATE_INVOICE_ID') !== false){ if(stripos($e->getMessage(), 'DUPLICATE_INVOICE_ID') !== false){
$_invoice = collect($this->payment_hash->data->invoices)->first(); $r = $this->handleDuplicateInvoiceId($orderID);
$invoice = Invoice::withTrashed()->find($this->decodePrimaryKey($_invoice->invoice_id));
$new_invoice_number = $invoice->number."_".Str::random(5);
$update_data =
[[
"op" => "replace",
"path" => "/purchase_units/@reference_id=='default'/invoice_id",
"value" => $new_invoice_number,
]];
$r = $this->gatewayRequest("/v2/checkout/orders/{$orderID}", 'patch', $update_data);
$r = $this->gatewayRequest("/v2/checkout/orders/{$orderID}/capture", 'post', ['body' => '']);
} }
@ -157,11 +148,12 @@ class PayPalRestPaymentDriver extends PayPalBasePaymentDriver
return response()->json(['message' => $message], 400); return response()->json(['message' => $message], 400);
//throw new PaymentFailed($message, 400);
} }
} }
private function createNinjaPayment($request, $response) { private function createNinjaPayment($request, $response) {
$data = [ $data = [
@ -273,10 +265,19 @@ class PayPalRestPaymentDriver extends PayPalBasePaymentDriver
$r = $this->gatewayRequest('/v2/checkout/orders', 'post', $order); $r = $this->gatewayRequest('/v2/checkout/orders', 'post', $order);
nlog($r->json()); nlog($r->json());
return $r->json()['id']; $response = $r->json();
if(!isset($response['id']))
$this->handleProcessingFailure($response);
$this->payment_hash->withData("orderID", $response['id']);
return $response['id'];
} }
/** /**
* processTokenPayment * processTokenPayment
* *
@ -293,6 +294,7 @@ class PayPalRestPaymentDriver extends PayPalBasePaymentDriver
*/ */
public function processTokenPayment($request, array $response) { public function processTokenPayment($request, array $response) {
/** @car \App\Models\ClientGatwayToken $cgt */
$cgt = ClientGatewayToken::where('client_id', $this->client->id) $cgt = ClientGatewayToken::where('client_id', $this->client->id)
->where('token', $request['token']) ->where('token', $request['token'])
->firstOrFail(); ->firstOrFail();

View File

@ -161,9 +161,9 @@ class WePayPaymentDriver extends BaseDriver
} }
public function detach(ClientGatewayToken $token) public function detach(ClientGatewayToken $token): bool
{ {
return true;
} }
public function getClientRequiredFields(): array public function getClientRequiredFields(): array

View File

@ -13,24 +13,24 @@ namespace App\Services\EDocument\Standards;
use App\Models\Invoice; use App\Models\Invoice;
use App\Services\AbstractService; use App\Services\AbstractService;
use Invoiceninja\Einvoice\Models\FatturaPA\FatturaElettronica; use InvoiceNinja\EInvoice\Models\FatturaPA\FatturaElettronica;
use Invoiceninja\Einvoice\Models\FatturaPA\IndirizzoType\Sede; use InvoiceNinja\EInvoice\Models\FatturaPA\IndirizzoType\Sede;
use Invoiceninja\Einvoice\Models\FatturaPA\AnagraficaType\Anagrafica; use InvoiceNinja\EInvoice\Models\FatturaPA\AnagraficaType\Anagrafica;
use Invoiceninja\Einvoice\Models\FatturaPA\IdFiscaleType\IdFiscaleIVA; use InvoiceNinja\EInvoice\Models\FatturaPA\IdFiscaleType\IdFiscaleIVA;
use Invoiceninja\Einvoice\Models\FatturaPA\IdFiscaleType\IdTrasmittente; use InvoiceNinja\EInvoice\Models\FatturaPA\IdFiscaleType\IdTrasmittente;
use Invoiceninja\Einvoice\Models\FatturaPA\DatiGeneraliType\DatiGenerali; use InvoiceNinja\EInvoice\Models\FatturaPA\DatiGeneraliType\DatiGenerali;
use Invoiceninja\Einvoice\Models\FatturaPA\DatiPagamentoType\DatiPagamento; use InvoiceNinja\EInvoice\Models\FatturaPA\DatiPagamentoType\DatiPagamento;
use Invoiceninja\Einvoice\Models\FatturaPA\DatiRiepilogoType\DatiRiepilogo; use InvoiceNinja\EInvoice\Models\FatturaPA\DatiRiepilogoType\DatiRiepilogo;
use Invoiceninja\Einvoice\Models\FatturaPA\DettaglioLineeType\DettaglioLinee; use InvoiceNinja\EInvoice\Models\FatturaPA\DettaglioLineeType\DettaglioLinee;
use Invoiceninja\Einvoice\Models\FatturaPA\DatiBeniServiziType\DatiBeniServizi; use InvoiceNinja\EInvoice\Models\FatturaPA\DatiBeniServiziType\DatiBeniServizi;
use Invoiceninja\Einvoice\Models\FatturaPA\DatiTrasmissioneType\DatiTrasmissione; use InvoiceNinja\EInvoice\Models\FatturaPA\DatiTrasmissioneType\DatiTrasmissione;
use Invoiceninja\Einvoice\Models\FatturaPA\CedentePrestatoreType\CedentePrestatore; use InvoiceNinja\EInvoice\Models\FatturaPA\CedentePrestatoreType\CedentePrestatore;
use Invoiceninja\Einvoice\Models\FatturaPA\DatiAnagraficiCedenteType\DatiAnagrafici; use InvoiceNinja\EInvoice\Models\FatturaPA\DatiAnagraficiCedenteType\DatiAnagrafici;
use Invoiceninja\Einvoice\Models\FatturaPA\DettaglioPagamentoType\DettaglioPagamento; use InvoiceNinja\EInvoice\Models\FatturaPA\DettaglioPagamentoType\DettaglioPagamento;
use Invoiceninja\Einvoice\Models\FatturaPA\DatiGeneraliDocumentoType\DatiGeneraliDocumento; use InvoiceNinja\EInvoice\Models\FatturaPA\DatiGeneraliDocumentoType\DatiGeneraliDocumento;
use Invoiceninja\Einvoice\Models\FatturaPA\CessionarioCommittenteType\CessionarioCommittente; use InvoiceNinja\EInvoice\Models\FatturaPA\CessionarioCommittenteType\CessionarioCommittente;
use Invoiceninja\Einvoice\Models\FatturaPA\FatturaElettronicaBodyType\FatturaElettronicaBody; use InvoiceNinja\EInvoice\Models\FatturaPA\FatturaElettronicaBodyType\FatturaElettronicaBody;
use Invoiceninja\Einvoice\Models\FatturaPA\FatturaElettronicaHeaderType\FatturaElettronicaHeader; use InvoiceNinja\EInvoice\Models\FatturaPA\FatturaElettronicaHeaderType\FatturaElettronicaHeader;
class FatturaPANew extends AbstractService class FatturaPANew extends AbstractService
{ {
@ -178,7 +178,7 @@ class FatturaPANew extends AbstractService
$this->DatiGeneraliDocumento->Divisa = $this->invoice->client->currency()->code; $this->DatiGeneraliDocumento->Divisa = $this->invoice->client->currency()->code;
$this->DatiGeneraliDocumento->Data = new \DateTime($this->invoice->date); $this->DatiGeneraliDocumento->Data = new \DateTime($this->invoice->date);
$this->DatiGeneraliDocumento->Numero = $this->invoice->number; $this->DatiGeneraliDocumento->Numero = $this->invoice->number;
$this->DatiGeneraliDocumento->Causale[] = substr($this->invoice->public_notes ?? '',0, 200); //unsure.. $this->DatiGeneraliDocumento->Causale[] = substr($this->invoice->public_notes ?? ' ', 0, 200); //unsure..
return $this; return $this;
} }

View File

@ -26,14 +26,17 @@ class OrderXDocument extends AbstractService
{ {
public OrderDocumentBuilder $orderxdocument; public OrderDocumentBuilder $orderxdocument;
public function __construct(public object $document, private readonly bool $returnObject = false, private array $tax_map = []) /** @var \App\Models\Invoice | \App\Models\Quote | \App\Models\PurchaseOrder | \App\Models\Credit $document */
public function __construct(public mixed $document, private readonly bool $returnObject = false, private array $tax_map = [])
{ {
} }
public function run(): self public function run(): self
{ {
$company = $this->document->company; $company = $this->document->company;
/** @var \App\Models\Client | \App\Models\Vendor $settings_entity */
$settings_entity = ($this->document instanceof PurchaseOrder) ? $this->document->vendor : $this->document->client; $settings_entity = ($this->document instanceof PurchaseOrder) ? $this->document->vendor : $this->document->client;
$profile = $settings_entity->getSetting('e_quote_type') ? $settings_entity->getSetting('e_quote_type') : "OrderX_Extended"; $profile = $settings_entity->getSetting('e_quote_type') ? $settings_entity->getSetting('e_quote_type') : "OrderX_Extended";

View File

@ -0,0 +1,518 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Services\EDocument\Standards;
use App\Models\Invoice;
use App\Services\AbstractService;
use App\Helpers\Invoice\InvoiceSum;
use App\Helpers\Invoice\InvoiceSumInclusive;
use InvoiceNinja\EInvoice\Models\Peppol\ItemType\Item;
use InvoiceNinja\EInvoice\Models\Peppol\PartyType\Party;
use InvoiceNinja\EInvoice\Models\Peppol\PriceType\Price;
use InvoiceNinja\EInvoice\Models\Peppol\AddressType\Address;
use InvoiceNinja\EInvoice\Models\Peppol\ContactType\Contact;
use InvoiceNinja\EInvoice\Models\Peppol\CountryType\Country;
use InvoiceNinja\EInvoice\Models\Peppol\AmountType\TaxAmount;
use InvoiceNinja\EInvoice\Models\Peppol\TaxTotalType\TaxTotal;
use InvoiceNinja\EInvoice\Models\Peppol\PartyNameType\PartyName;
use InvoiceNinja\EInvoice\Models\Peppol\TaxSchemeType\TaxScheme;
use InvoiceNinja\EInvoice\Models\Peppol\AmountType\PayableAmount;
use InvoiceNinja\EInvoice\Models\Peppol\InvoiceLineType\InvoiceLine;
use InvoiceNinja\EInvoice\Models\Peppol\TaxCategoryType\TaxCategory;
use InvoiceNinja\EInvoice\Models\Peppol\TaxSubtotalType\TaxSubtotal;
use InvoiceNinja\EInvoice\Models\Peppol\TaxScheme as PeppolTaxScheme;
use InvoiceNinja\EInvoice\Models\Peppol\AmountType\TaxExclusiveAmount;
use InvoiceNinja\EInvoice\Models\Peppol\AmountType\TaxInclusiveAmount;
use InvoiceNinja\EInvoice\Models\Peppol\AmountType\LineExtensionAmount;
use InvoiceNinja\EInvoice\Models\Peppol\AmountType\PriceAmount;
use InvoiceNinja\EInvoice\Models\Peppol\AmountType\TaxableAmount;
use InvoiceNinja\EInvoice\Models\Peppol\MonetaryTotalType\LegalMonetaryTotal;
use InvoiceNinja\EInvoice\Models\Peppol\TaxCategoryType\ClassifiedTaxCategory;
use InvoiceNinja\EInvoice\Models\Peppol\CustomerPartyType\AccountingCustomerParty;
use InvoiceNinja\EInvoice\Models\Peppol\SupplierPartyType\AccountingSupplierParty;
use InvoiceNinja\EInvoice\Models\Peppol\TaxTotal as PeppolTaxTotal;
class Peppol extends AbstractService
{
private array $InvoiceTypeCodes = [
"380" => "Commercial invoice",
"381" => "Credit note",
"383" => "Corrected invoice",
"384" => "Prepayment invoice",
"386" => "Proforma invoice",
"875" => "Self-billed invoice",
"976" => "Factored invoice",
"84" => "Invoice for cross border services",
"82" => "Simplified invoice",
"80" => "Debit note",
"875" => "Self-billed credit note",
"896" => "Debit note related to self-billed invoice"
];
private \InvoiceNinja\EInvoice\Models\Peppol\Invoice $p_invoice;
private InvoiceSum | InvoiceSumInclusive $calc;
/**
* @param Invoice $invoice
*/
public function __construct(public Invoice $invoice)
{
$this->p_invoice = new \InvoiceNinja\EInvoice\Models\Peppol\Invoice;
$this->calc = $this->invoice->calc();
}
public function getInvoice(): \InvoiceNinja\EInvoice\Models\Peppol\Invoice
{
return $this->p_invoice;
}
public function run()
{
$this->p_invoice->ID = $this->invoice->number;
$this->p_invoice->IssueDate = new \DateTime($this->invoice->date);
$this->p_invoice->InvoiceTypeCode = 380; //
$this->p_invoice->AccountingSupplierParty = $this->getAccountingSupplierParty();
$this->p_invoice->AccountingCustomerParty = $this->getAccountingCustomerParty();
$this->p_invoice->InvoiceLine = $this->getInvoiceLines();
$this->p_invoice->TaxTotal = $this->getTotalTaxes();
$this->p_invoice->LegalMonetaryTotal = $this->getLegalMonetaryTotal();
// $payeeFinancialAccount = (new PayeeFinancialAccount())
// ->setBankId($company->settings->custom_value1)
// ->setBankName($company->settings->custom_value2);
// $paymentMeans = (new PaymentMeans())
// ->setPaymentMeansCode($invoice->custom_value1)
// ->setPayeeFinancialAccount($payeeFinancialAccount);
// $ubl_invoice->setPaymentMeans($paymentMeans);
}
private function getLegalMonetaryTotal(): LegalMonetaryTotal
{
$taxable = $this->getTaxable();
$lmt = new LegalMonetaryTotal;
$lea = new LineExtensionAmount;
$lea->currencyID = $this->invoice->client->currency()->code;
$lea->amount = $taxable;
$lmt->LineExtensionAmount = $lea;
$tea = new TaxExclusiveAmount;
$tea->currencyID = $this->invoice->client->currency()->code;
$tea->amount = $taxable;
$lmt->TaxExclusiveAmount = $tea;
$tia = new TaxInclusiveAmount;
$tia->currencyID = $this->invoice->client->currency()->code;
$tia->amount = $this->invoice->amount;
$lmt->TaxInclusiveAmount = $tia;
$pa = new PayableAmount;
$pa->currencyID = $this->invoice->client->currency()->code;
$pa->amount = $this->invoice->amount;
$lmt->PayableAmount = $pa;
return $lmt;
}
private function getTotalTaxes(): array
{
$taxes = [];
$type_id = $this->invoice->line_items[0]->type_id;
if( strlen($this->invoice->tax_name1 ?? '') > 1)
{
$tax_amount = new TaxAmount();
$tax_amount->currencyID = $this->invoice->client->currency()->code;
$tax_amount->amount = round($this->invoice->amount * (1 / $this->invoice->tax_rate1),2);
$tax_subtotal = new TaxSubtotal();
$tax_subtotal->TaxAmount = $tax_amount;
$taxable_amount = new TaxableAmount();
$taxable_amount->currencyID = $this->invoice->client->currency()->code;
$taxable_amount->amount = $this->invoice->amount;
$tax_subtotal->TaxableAmount = $taxable_amount;
$tc = new TaxCategory();
$tc->ID = $type_id == '2' ? 'HUR' : 'C62';
$tc->Percent = $this->invoice->tax_rate1;
$ts = new PeppolTaxScheme();
$ts->ID = $this->invoice->tax_name1;
$tc->TaxScheme = $ts;
$tax_subtotal->TaxCategory = $tc;
$tax_total = new TaxTotal;
$tax_total->TaxAmount = $tax_amount;
$tax_total->TaxSubtotal = $tax_subtotal;
$taxes[] = $tax_total;
}
if(strlen($this->invoice->tax_name2 ?? '') > 1) {
$tax_amount = new TaxAmount();
$tax_amount->currencyID = $this->invoice->client->currency()->code;
$tax_amount->amount = round($this->invoice->amount * (1 / $this->invoice->tax_rate2), 2);
$tax_subtotal = new TaxSubtotal();
$tax_subtotal->TaxAmount = $tax_amount;
$taxable_amount = new TaxableAmount();
$taxable_amount->currencyID = $this->invoice->client->currency()->code;
$taxable_amount->amount = $this->invoice->amount;
$tax_subtotal->TaxableAmount = $taxable_amount;
$tc = new TaxCategory();
$tc->ID = $type_id == '2' ? 'HUR' : 'C62';
$tc->Percent = $this->invoice->tax_rate2;
$ts = new PeppolTaxScheme();
$ts->ID = $this->invoice->tax_name2;
$tc->TaxScheme = $ts;
$tax_subtotal->TaxCategory = $tc;
$tax_total = new TaxTotal();
$tax_total->TaxAmount = $tax_amount;
$tax_total->TaxSubtotal = $tax_subtotal;
$taxes[] = $tax_total;
}
if(strlen($this->invoice->tax_name3 ?? '') > 1) {
$tax_amount = new TaxAmount();
$tax_amount->currencyID = $this->invoice->client->currency()->code;
$tax_amount->amount = round($this->invoice->amount * (1 / $this->invoice->tax_rate1), 2);
$tax_subtotal = new TaxSubtotal();
$tax_subtotal->TaxAmount = $tax_amount;
$taxable_amount = new TaxableAmount();
$taxable_amount->currencyID = $this->invoice->client->currency()->code;
$taxable_amount->amount = $this->invoice->amount;
$tax_subtotal->TaxableAmount = $taxable_amount;
$tc = new TaxCategory();
$tc->ID = $type_id == '2' ? 'HUR' : 'C62';
$tc->Percent = $this->invoice->tax_rate3;
$ts = new PeppolTaxScheme();
$ts->ID = $this->invoice->tax_name3;
$tc->TaxScheme = $ts;
$tax_subtotal->TaxCategory = $tc;
$tax_total = new TaxTotal();
$tax_total->TaxAmount = $tax_amount;
$tax_total->TaxSubtotal = $tax_subtotal;
$taxes[] = $tax_total;
}
return $taxes;
}
private function getInvoiceLines(): array
{
$lines = [];
foreach($this->invoice->line_items as $key => $item)
{
$_item = new Item;
$_item->Name = $item->product_key;
$_item->Description = $item->notes;
$line = new InvoiceLine;
$line->ID = $key+1;
$line->InvoicedQuantity = $item->quantity;
$lea = new LineExtensionAmount;
$lea->currencyID = $this->invoice->client->currency()->code;
$lea->amount = $item->line_total;
$line->LineExtensionAmount = $lea;
$line->Item = $_item;
// $ta = new TaxAmount;
// $ta->amount = $this->getItemTaxes($item);
// $ta->currencyID = $this->invoice->client->currency()->Code;
// $tt->TaxAmount = $ta;
$item_taxes = $this->getItemTaxes($item);
if(count($item_taxes) > 0)
$line->TaxTotal = $item_taxes;
$price = new Price;
$pa = new PriceAmount;
$pa->currencyID = $this->invoice->client->currency()->code;
$pa->amount = $this->costWithDiscount($item);
$price->PriceAmount = $pa;
$line->Price = $price;
$lines[] = $line;
}
return $lines;
}
private function costWithDiscount($item)
{
$cost = $item->cost;
if ($item->discount != 0) {
if ($this->invoice->is_amount_discount) {
$cost -= $item->discount / $item->quantity;
} else {
$cost -= $cost * $item->discount / 100;
}
}
return $cost;
}
private function getItemTaxes(object $item): array
{
$item_taxes = [];
if(strlen($item->tax_name1 ?? '') > 1)
{
$tax_amount = new TaxAmount;
$tax_amount->currencyID = $this->invoice->client->currency()->code;
$tax_amount->amount = round(($item->line_total * (1/$item->tax_rate1)),2);
$tax_subtotal = new TaxSubtotal;
$tax_subtotal->TaxAmount = $tax_amount;
$taxable_amount = new TaxableAmount;
$taxable_amount->currencyID = $this->invoice->client->currency()->code;
$taxable_amount->amount = $item->line_total;
$tax_subtotal->TaxableAmount = $taxable_amount;
$tc = new TaxCategory;
$tc->ID = $item->type_id == '2' ? 'HUR' : 'C62';
$tc->Percent = $item->tax_rate1;
$ts = new PeppolTaxScheme;
$ts->ID = $item->tax_name1;
$tc->TaxScheme = $ts;
$tax_subtotal->TaxCategory = $tc;
$tax_total = new TaxTotal();
$tax_total->TaxAmount = $tax_amount;
$tax_total->TaxSubtotal[] = $tax_subtotal;
$item_taxes[] = $tax_total;
}
if(strlen($item->tax_name2 ?? '') > 1) {
$tax_amount = new TaxAmount();
$tax_amount->currencyID = $this->invoice->client->currency()->code;
$tax_amount->amount = round(($item->line_total * (1 / $item->tax_rate2)),2);
$tax_subtotal = new TaxSubtotal();
$tax_subtotal->TaxAmount = $tax_amount;
$taxable_amount = new TaxableAmount();
$taxable_amount->currencyID = $this->invoice->client->currency()->code;
$taxable_amount->amount = $item->line_total;
$tax_subtotal->TaxableAmount = $taxable_amount;
$tc = new TaxCategory();
$tc->ID = $item->type_id == '2' ? 'HUR' : 'C62';
$tc->Percent = $item->tax_rate2;
$ts = new PeppolTaxScheme();
$ts->ID = $item->tax_name2;
$tc->TaxScheme = $ts;
$tax_subtotal->TaxCategory = $tc;
$tax_total = new TaxTotal();
$tax_total->TaxAmount = $tax_amount;
$tax_total->TaxSubtotal[] = $tax_subtotal;
$item_taxes[] = $tax_total;
}
if(strlen($item->tax_name3 ?? '') > 1) {
$tax_amount = new TaxAmount();
$tax_amount->currencyID = $this->invoice->client->currency()->code;
$tax_amount->amount = round(($item->line_total * (1 / $item->tax_rate3)),2);
$tax_subtotal = new TaxSubtotal();
$tax_subtotal->TaxAmount = $tax_amount;
$taxable_amount = new TaxableAmount();
$taxable_amount->currencyID = $this->invoice->client->currency()->code;
$taxable_amount->amount = $item->line_total;
$tax_subtotal->TaxableAmount = $taxable_amount;
$tc = new TaxCategory();
$tc->ID = $item->type_id == '2' ? 'HUR' : 'C62';
$tc->Percent = $item->tax_rate3;
$ts = new PeppolTaxScheme();
$ts->ID = $item->tax_name3;
$tc->TaxScheme = $ts;
$tax_subtotal->TaxCategory = $tc;
$tax_total = new TaxTotal();
$tax_total->TaxAmount = $tax_amount;
$tax_total->TaxSubtotal[] = $tax_subtotal;
$item_taxes[] = $tax_total;
}
return $item_taxes;
}
private function getAccountingSupplierParty(): AccountingSupplierParty
{
$asp = new AccountingSupplierParty();
$party = new Party();
$party_name = new PartyName;
$party_name->Name = $this->invoice->company->present()->name();
$party->PartyName[] = $party_name;
$address = new Address();
$address->CityName = $this->invoice->company->settings->city;
$address->StreetName = $this->invoice->company->settings->address1;
// $address->BuildingName = $this->invoice->company->settings->address2;
$address->PostalZone = $this->invoice->company->settings->postal_code;
$address->CountrySubentity = $this->invoice->company->settings->state;
// $address->CountrySubentityCode = $this->invoice->company->settings->state;
$country = new Country();
$country->IdentificationCode = $this->invoice->company->country()->iso_3166_2;
$address->Country = $country;
$party->PostalAddress = $address;
$party->PhysicalLocation = $address;
$contact = new Contact();
$contact->ElectronicMail = $this->invoice->company->owner()->email ?? 'owner@gmail.com';
$party->Contact = $contact;
$asp->Party = $party;
return $asp;
}
private function getAccountingCustomerParty(): AccountingCustomerParty
{
$acp = new AccountingCustomerParty();
$party = new Party();
$party_name = new PartyName();
$party_name->Name = $this->invoice->client->present()->name();
$party->PartyName[] = $party_name;
$address = new Address();
$address->CityName = $this->invoice->client->city;
$address->StreetName = $this->invoice->client->address1;
// $address->BuildingName = $this->invoice->client->address2;
$address->PostalZone = $this->invoice->client->postal_code;
$address->CountrySubentity = $this->invoice->client->state;
// $address->CountrySubentityCode = $this->invoice->client->state;
$country = new Country();
$country->IdentificationCode = $this->invoice->client->country->iso_3166_2;
$address->Country = $country;
$party->PostalAddress = $address;
$party->PhysicalLocation = $address;
$contact = new Contact();
$contact->ElectronicMail = $this->invoice->client->present()->email();
$party->Contact = $contact;
$acp->Party = $party;
return $acp;
}
private function getTaxable(): float
{
$total = 0;
foreach ($this->invoice->line_items as $item) {
$line_total = $item->quantity * $item->cost;
if ($item->discount != 0) {
if ($this->invoice->is_amount_discount) {
$line_total -= $item->discount;
} else {
$line_total -= $line_total * $item->discount / 100;
}
}
$total += $line_total;
}
if ($this->invoice->discount > 0) {
if ($this->invoice->is_amount_discount) {
$total -= $this->invoice->discount;
} else {
$total *= (100 - $this->invoice->discount) / 100;
$total = round($total, 2);
}
}
if ($this->invoice->custom_surcharge1 && $this->invoice->custom_surcharge_tax1) {
$total += $this->invoice->custom_surcharge1;
}
if ($this->invoice->custom_surcharge2 && $this->invoice->custom_surcharge_tax2) {
$total += $this->invoice->custom_surcharge2;
}
if ($this->invoice->custom_surcharge3 && $this->invoice->custom_surcharge_tax3) {
$total += $this->invoice->custom_surcharge3;
}
if ($this->invoice->custom_surcharge4 && $this->invoice->custom_surcharge_tax4) {
$total += $this->invoice->custom_surcharge4;
}
return $total;
}
}

View File

@ -32,8 +32,13 @@ class ZugferdEDokument extends AbstractService
public function run(): self public function run(): self
{ {
/** @var \App\Models\Company $company */
$company = $this->document->company; $company = $this->document->company;
/** @var \App\Models\Client $client */
$client = $this->document->client; $client = $this->document->client;
$profile = $client->getSetting('e_invoice_type'); $profile = $client->getSetting('e_invoice_type');
$profile = match ($profile) { $profile = match ($profile) {

View File

@ -303,11 +303,6 @@ class Email implements ShouldQueue
$this->logMailError($e->getMessage(), $this->company->clients()->first()); $this->logMailError($e->getMessage(), $this->company->clients()->first());
return; return;
} }
catch(\Symfony\Component\Mailer\Transport\Dsn $e){
nlog("Incorrectly configured mail server - setting to default mail driver.");
$this->email_object->settings->email_sending_method = 'default';
return $this->setMailDriver();
}
catch(\Google\Service\Exception $e){ catch(\Google\Service\Exception $e){
if ($e->getCode() == '429') { if ($e->getCode() == '429') {
@ -326,7 +321,7 @@ class Email implements ShouldQueue
$message = $e->getMessage(); $message = $e->getMessage();
if (stripos($e->getMessage(), 'code 300') || stripos($e->getMessage(), 'code 413')) { if (stripos($e->getMessage(), 'code 300') !== false || stripos($e->getMessage(), 'code 413') !== false) {
$message = "Either Attachment too large, or recipient has been suppressed."; $message = "Either Attachment too large, or recipient has been suppressed.";
$this->fail(); $this->fail();
@ -337,8 +332,16 @@ class Email implements ShouldQueue
return; return;
} }
if(stripos($e->getMessage(), 'Dsn') !== false) {
if (stripos($e->getMessage(), 'code 406')) { nlog("Incorrectly configured mail server - setting to default mail driver.");
$this->email_object->settings->email_sending_method = 'default';
return $this->setMailDriver();
}
if (stripos($e->getMessage(), 'code 406') !== false) {
$address_object = reset($this->email_object->to); $address_object = reset($this->email_object->to);
@ -613,17 +616,17 @@ class Email implements ShouldQueue
$company = $this->company; $company = $this->company;
$smtp_host = $company->smtp_host; $smtp_host = $company->smtp_host ?? '';
$smtp_port = $company->smtp_port; $smtp_port = $company->smtp_port;
$smtp_username = $company->smtp_username; $smtp_username = $company->smtp_username ?? '';
$smtp_password = $company->smtp_password; $smtp_password = $company->smtp_password ?? '';
$smtp_encryption = $company->smtp_encryption ?? 'tls'; $smtp_encryption = $company->smtp_encryption ?? 'tls';
$smtp_local_domain = strlen($company->smtp_local_domain) > 2 ? $company->smtp_local_domain : null; $smtp_local_domain = strlen($company->smtp_local_domain) > 2 ? $company->smtp_local_domain : null;
$smtp_verify_peer = $company->smtp_verify_peer ?? true; $smtp_verify_peer = $company->smtp_verify_peer ?? true;
if(strlen($smtp_host ?? '') <= 1 || if(strlen($smtp_host) <= 1 ||
strlen($smtp_username ?? '') <= 1 || strlen($smtp_username) <= 1 ||
strlen($smtp_password ?? '') <= 1 strlen($smtp_password) <= 1
) { ) {
$this->email_object->settings->email_sending_method = 'default'; $this->email_object->settings->email_sending_method = 'default';
return $this->setMailDriver(); return $this->setMailDriver();

View File

@ -11,22 +11,25 @@
namespace App\Services\Invoice; namespace App\Services\Invoice;
use App\Events\Invoice\InvoiceWasPaid; use Carbon\Carbon;
use App\Events\Payment\PaymentWasCreated; use App\Utils\Ninja;
use App\Factory\PaymentFactory; use App\Utils\Number;
use App\Libraries\MultiDB;
use App\Models\Client; use App\Models\Client;
use App\Models\ClientGatewayToken;
use App\Models\Credit; use App\Models\Credit;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\Payment; use App\Models\Payment;
use App\Libraries\MultiDB;
use App\Models\PaymentHash; use App\Models\PaymentHash;
use App\Models\PaymentType; use App\Models\PaymentType;
use Illuminate\Support\Str;
use App\DataMapper\InvoiceItem;
use App\Factory\PaymentFactory;
use App\Services\AbstractService;
use App\Models\ClientGatewayToken;
use App\Events\Invoice\InvoiceWasPaid;
use App\Repositories\CreditRepository; use App\Repositories\CreditRepository;
use App\Repositories\PaymentRepository; use App\Repositories\PaymentRepository;
use App\Services\AbstractService; use App\Events\Payment\PaymentWasCreated;
use App\Utils\Ninja;
use Illuminate\Support\Str;
class AutoBillInvoice extends AbstractService class AutoBillInvoice extends AbstractService
{ {
@ -205,11 +208,27 @@ class AutoBillInvoice extends AbstractService
info("adjusting credit balance {$current_credit->balance} by this amount ".$credit['amount']); info("adjusting credit balance {$current_credit->balance} by this amount ".$credit['amount']);
$item_date = Carbon::parse($payment->date)->format($payment->client->date_format());
$invoice_numbers = $this->invoice->number;
$item = new InvoiceItem();
$item->quantity = 0;
$item->cost = $credit['amount'] * -1;
$item->notes = "{$item_date} - " . ctrans('texts.credit_payment', ['invoice_number' => $invoice_numbers]) . " ". Number::formatMoney($credit['amount'], $payment->client);
$item->type_id = "1";
$line_items = $current_credit->line_items;
$line_items[] = $item;
$current_credit->line_items = $line_items;
$current_credit->service() $current_credit->service()
->adjustBalance($credit['amount'] * -1) ->adjustBalance($credit['amount'] * -1)
->updatePaidToDate($credit['amount']) ->updatePaidToDate($credit['amount'])
->setCalculatedStatus() ->setCalculatedStatus()
->save(); ->save();
} }
$payment->ledger() $payment->ledger()
@ -240,6 +259,7 @@ class AutoBillInvoice extends AbstractService
return $this->invoice return $this->invoice
->service() ->service()
->setCalculatedStatus() ->setCalculatedStatus()
->workFlow() //07-06-2024 - run the workflow if paid!
->save(); ->save();
} }

183
composer.lock generated
View File

@ -1385,16 +1385,16 @@
}, },
{ {
"name": "aws/aws-sdk-php", "name": "aws/aws-sdk-php",
"version": "3.309.0", "version": "3.311.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/aws/aws-sdk-php.git", "url": "https://github.com/aws/aws-sdk-php.git",
"reference": "9213b2101afa17fe8ea99b8ea2dad85c1b1166a8" "reference": "90218b9372469babf294f97bdd764c9d47ec8a57"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/9213b2101afa17fe8ea99b8ea2dad85c1b1166a8", "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/90218b9372469babf294f97bdd764c9d47ec8a57",
"reference": "9213b2101afa17fe8ea99b8ea2dad85c1b1166a8", "reference": "90218b9372469babf294f97bdd764c9d47ec8a57",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1474,9 +1474,9 @@
"support": { "support": {
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
"issues": "https://github.com/aws/aws-sdk-php/issues", "issues": "https://github.com/aws/aws-sdk-php/issues",
"source": "https://github.com/aws/aws-sdk-php/tree/3.309.0" "source": "https://github.com/aws/aws-sdk-php/tree/3.311.1"
}, },
"time": "2024-06-03T18:10:07+00:00" "time": "2024-06-06T18:05:50+00:00"
}, },
{ {
"name": "bacon/bacon-qr-code", "name": "bacon/bacon-qr-code",
@ -4630,16 +4630,16 @@
}, },
{ {
"name": "horstoeko/zugferd", "name": "horstoeko/zugferd",
"version": "v1.0.51", "version": "v1.0.53",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/horstoeko/zugferd.git", "url": "https://github.com/horstoeko/zugferd.git",
"reference": "9e036d4a9660638b4f51d2babb397fcff29ef046" "reference": "939e93ab2e84ec476735e5957f4db7e7d58880c3"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/horstoeko/zugferd/zipball/9e036d4a9660638b4f51d2babb397fcff29ef046", "url": "https://api.github.com/repos/horstoeko/zugferd/zipball/939e93ab2e84ec476735e5957f4db7e7d58880c3",
"reference": "9e036d4a9660638b4f51d2babb397fcff29ef046", "reference": "939e93ab2e84ec476735e5957f4db7e7d58880c3",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -4699,9 +4699,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/horstoeko/zugferd/issues", "issues": "https://github.com/horstoeko/zugferd/issues",
"source": "https://github.com/horstoeko/zugferd/tree/v1.0.51" "source": "https://github.com/horstoeko/zugferd/tree/v1.0.53"
}, },
"time": "2024-05-31T17:20:07+00:00" "time": "2024-06-05T16:49:22+00:00"
}, },
{ {
"name": "http-interop/http-factory-guzzle", "name": "http-interop/http-factory-guzzle",
@ -5076,16 +5076,17 @@
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/invoiceninja/einvoice.git", "url": "https://github.com/invoiceninja/einvoice.git",
"reference": "228560c6eea03d9ae10a174a0d9f1456efc5eabd" "reference": "1b9a488d65715272941f1ae6ddfe2d94ad25c2c1"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/invoiceninja/einvoice/zipball/228560c6eea03d9ae10a174a0d9f1456efc5eabd", "url": "https://api.github.com/repos/invoiceninja/einvoice/zipball/1b9a488d65715272941f1ae6ddfe2d94ad25c2c1",
"reference": "228560c6eea03d9ae10a174a0d9f1456efc5eabd", "reference": "1b9a488d65715272941f1ae6ddfe2d94ad25c2c1",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"illuminate/collections": "^10.48", "illuminate/collections": "^10|^11",
"php": "^8.2",
"phpdocumentor/reflection-docblock": "^5.4", "phpdocumentor/reflection-docblock": "^5.4",
"sabre/xml": "^4.0", "sabre/xml": "^4.0",
"symfony/property-access": "^7", "symfony/property-access": "^7",
@ -5094,6 +5095,7 @@
"symfony/validator": "^7" "symfony/validator": "^7"
}, },
"require-dev": { "require-dev": {
"friendsofphp/php-cs-fixer": "^3.58",
"milo/schematron": "^1.0", "milo/schematron": "^1.0",
"nette/php-generator": "^4.1", "nette/php-generator": "^4.1",
"phpstan/phpstan": "^1.11", "phpstan/phpstan": "^1.11",
@ -5104,7 +5106,7 @@
"type": "library", "type": "library",
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"Invoiceninja\\Einvoice\\": "src/" "InvoiceNinja\\EInvoice\\": "src/"
} }
}, },
"license": [ "license": [
@ -5121,7 +5123,7 @@
"source": "https://github.com/invoiceninja/einvoice/tree/main", "source": "https://github.com/invoiceninja/einvoice/tree/main",
"issues": "https://github.com/invoiceninja/einvoice/issues" "issues": "https://github.com/invoiceninja/einvoice/issues"
}, },
"time": "2024-06-03T07:03:23+00:00" "time": "2024-06-07T04:32:09+00:00"
}, },
{ {
"name": "invoiceninja/inspector", "name": "invoiceninja/inspector",
@ -12517,16 +12519,16 @@
}, },
{ {
"name": "symfony/css-selector", "name": "symfony/css-selector",
"version": "v7.1.0", "version": "v7.1.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/css-selector.git", "url": "https://github.com/symfony/css-selector.git",
"reference": "843f2f7ac5e4c5bf0ec77daef23ca6d4d8922adc" "reference": "1c7cee86c6f812896af54434f8ce29c8d94f9ff4"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/css-selector/zipball/843f2f7ac5e4c5bf0ec77daef23ca6d4d8922adc", "url": "https://api.github.com/repos/symfony/css-selector/zipball/1c7cee86c6f812896af54434f8ce29c8d94f9ff4",
"reference": "843f2f7ac5e4c5bf0ec77daef23ca6d4d8922adc", "reference": "1c7cee86c6f812896af54434f8ce29c8d94f9ff4",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -12562,7 +12564,7 @@
"description": "Converts CSS selectors to XPath expressions", "description": "Converts CSS selectors to XPath expressions",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/css-selector/tree/v7.1.0" "source": "https://github.com/symfony/css-selector/tree/v7.1.1"
}, },
"funding": [ "funding": [
{ {
@ -12578,7 +12580,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-04-18T09:32:20+00:00" "time": "2024-05-31T14:57:53+00:00"
}, },
{ {
"name": "symfony/deprecation-contracts", "name": "symfony/deprecation-contracts",
@ -12724,16 +12726,16 @@
}, },
{ {
"name": "symfony/event-dispatcher", "name": "symfony/event-dispatcher",
"version": "v7.1.0", "version": "v7.1.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/event-dispatcher.git", "url": "https://github.com/symfony/event-dispatcher.git",
"reference": "522d2772d6c7bab843b0c52466dc7844622bacc2" "reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/522d2772d6c7bab843b0c52466dc7844622bacc2", "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7",
"reference": "522d2772d6c7bab843b0c52466dc7844622bacc2", "reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -12784,7 +12786,7 @@
"description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/event-dispatcher/tree/v7.1.0" "source": "https://github.com/symfony/event-dispatcher/tree/v7.1.1"
}, },
"funding": [ "funding": [
{ {
@ -12800,7 +12802,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-04-18T09:32:20+00:00" "time": "2024-05-31T14:57:53+00:00"
}, },
{ {
"name": "symfony/event-dispatcher-contracts", "name": "symfony/event-dispatcher-contracts",
@ -13372,16 +13374,16 @@
}, },
{ {
"name": "symfony/intl", "name": "symfony/intl",
"version": "v7.1.0", "version": "v7.1.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/intl.git", "url": "https://github.com/symfony/intl.git",
"reference": "1b8974c7ff07c61d4acf0e873e34dfb75190b2bb" "reference": "66c1ecda092b1130ada2cf5f59dacfd5b6e9c99c"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/intl/zipball/1b8974c7ff07c61d4acf0e873e34dfb75190b2bb", "url": "https://api.github.com/repos/symfony/intl/zipball/66c1ecda092b1130ada2cf5f59dacfd5b6e9c99c",
"reference": "1b8974c7ff07c61d4acf0e873e34dfb75190b2bb", "reference": "66c1ecda092b1130ada2cf5f59dacfd5b6e9c99c",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -13438,7 +13440,7 @@
"localization" "localization"
], ],
"support": { "support": {
"source": "https://github.com/symfony/intl/tree/v7.1.0" "source": "https://github.com/symfony/intl/tree/v7.1.1"
}, },
"funding": [ "funding": [
{ {
@ -13454,7 +13456,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-04-18T09:32:20+00:00" "time": "2024-05-31T14:57:53+00:00"
}, },
{ {
"name": "symfony/mailer", "name": "symfony/mailer",
@ -13692,16 +13694,16 @@
}, },
{ {
"name": "symfony/options-resolver", "name": "symfony/options-resolver",
"version": "v7.1.0", "version": "v7.1.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/options-resolver.git", "url": "https://github.com/symfony/options-resolver.git",
"reference": "9564f64c16f99e29f252eafc642965e8fcb755ce" "reference": "47aa818121ed3950acd2b58d1d37d08a94f9bf55"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/9564f64c16f99e29f252eafc642965e8fcb755ce", "url": "https://api.github.com/repos/symfony/options-resolver/zipball/47aa818121ed3950acd2b58d1d37d08a94f9bf55",
"reference": "9564f64c16f99e29f252eafc642965e8fcb755ce", "reference": "47aa818121ed3950acd2b58d1d37d08a94f9bf55",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -13739,7 +13741,7 @@
"options" "options"
], ],
"support": { "support": {
"source": "https://github.com/symfony/options-resolver/tree/v7.1.0" "source": "https://github.com/symfony/options-resolver/tree/v7.1.1"
}, },
"funding": [ "funding": [
{ {
@ -13755,7 +13757,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-04-18T09:32:20+00:00" "time": "2024-05-31T14:57:53+00:00"
}, },
{ {
"name": "symfony/polyfill-ctype", "name": "symfony/polyfill-ctype",
@ -14685,16 +14687,16 @@
}, },
{ {
"name": "symfony/property-access", "name": "symfony/property-access",
"version": "v7.1.0", "version": "v7.1.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/property-access.git", "url": "https://github.com/symfony/property-access.git",
"reference": "1e8c1e6ac1b19cf945d8094a0ee50296872c4cb2" "reference": "74e39e6a6276b8e384f34c6ddbc10a6c9a60193a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/property-access/zipball/1e8c1e6ac1b19cf945d8094a0ee50296872c4cb2", "url": "https://api.github.com/repos/symfony/property-access/zipball/74e39e6a6276b8e384f34c6ddbc10a6c9a60193a",
"reference": "1e8c1e6ac1b19cf945d8094a0ee50296872c4cb2", "reference": "74e39e6a6276b8e384f34c6ddbc10a6c9a60193a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -14741,7 +14743,7 @@
"reflection" "reflection"
], ],
"support": { "support": {
"source": "https://github.com/symfony/property-access/tree/v7.1.0" "source": "https://github.com/symfony/property-access/tree/v7.1.1"
}, },
"funding": [ "funding": [
{ {
@ -14757,20 +14759,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-04-18T09:32:20+00:00" "time": "2024-05-31T14:57:53+00:00"
}, },
{ {
"name": "symfony/property-info", "name": "symfony/property-info",
"version": "v7.1.0", "version": "v7.1.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/property-info.git", "url": "https://github.com/symfony/property-info.git",
"reference": "b10cb8cf0179aec96769df2affb881ecfc293f79" "reference": "0f80f818c6728f15de30a4f89866d68e4912ae84"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/property-info/zipball/b10cb8cf0179aec96769df2affb881ecfc293f79", "url": "https://api.github.com/repos/symfony/property-info/zipball/0f80f818c6728f15de30a4f89866d68e4912ae84",
"reference": "b10cb8cf0179aec96769df2affb881ecfc293f79", "reference": "0f80f818c6728f15de30a4f89866d68e4912ae84",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -14825,7 +14827,7 @@
"validator" "validator"
], ],
"support": { "support": {
"source": "https://github.com/symfony/property-info/tree/v7.1.0" "source": "https://github.com/symfony/property-info/tree/v7.1.1"
}, },
"funding": [ "funding": [
{ {
@ -14841,7 +14843,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-05-30T12:09:55+00:00" "time": "2024-05-31T14:57:53+00:00"
}, },
{ {
"name": "symfony/psr-http-message-bridge", "name": "symfony/psr-http-message-bridge",
@ -15017,16 +15019,16 @@
}, },
{ {
"name": "symfony/serializer", "name": "symfony/serializer",
"version": "v7.1.0", "version": "v7.1.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/serializer.git", "url": "https://github.com/symfony/serializer.git",
"reference": "972eb05320d06d07399b71b05e6da9032c865f1d" "reference": "74817ee48e37cce1a1b33c66ffdb750e7e048c3c"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/serializer/zipball/972eb05320d06d07399b71b05e6da9032c865f1d", "url": "https://api.github.com/repos/symfony/serializer/zipball/74817ee48e37cce1a1b33c66ffdb750e7e048c3c",
"reference": "972eb05320d06d07399b71b05e6da9032c865f1d", "reference": "74817ee48e37cce1a1b33c66ffdb750e7e048c3c",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -15094,7 +15096,7 @@
"description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/serializer/tree/v7.1.0" "source": "https://github.com/symfony/serializer/tree/v7.1.1"
}, },
"funding": [ "funding": [
{ {
@ -15110,7 +15112,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-05-21T15:59:31+00:00" "time": "2024-05-31T14:57:53+00:00"
}, },
{ {
"name": "symfony/service-contracts", "name": "symfony/service-contracts",
@ -15197,16 +15199,16 @@
}, },
{ {
"name": "symfony/string", "name": "symfony/string",
"version": "v7.1.0", "version": "v7.1.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/string.git", "url": "https://github.com/symfony/string.git",
"reference": "6f41b185e742737917e6f2e3eca37767fba5f17a" "reference": "60bc311c74e0af215101235aa6f471bcbc032df2"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/6f41b185e742737917e6f2e3eca37767fba5f17a", "url": "https://api.github.com/repos/symfony/string/zipball/60bc311c74e0af215101235aa6f471bcbc032df2",
"reference": "6f41b185e742737917e6f2e3eca37767fba5f17a", "reference": "60bc311c74e0af215101235aa6f471bcbc032df2",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -15264,7 +15266,7 @@
"utf8" "utf8"
], ],
"support": { "support": {
"source": "https://github.com/symfony/string/tree/v7.1.0" "source": "https://github.com/symfony/string/tree/v7.1.1"
}, },
"funding": [ "funding": [
{ {
@ -15280,7 +15282,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-05-17T10:55:18+00:00" "time": "2024-06-04T06:40:14+00:00"
}, },
{ {
"name": "symfony/translation", "name": "symfony/translation",
@ -15457,16 +15459,16 @@
}, },
{ {
"name": "symfony/type-info", "name": "symfony/type-info",
"version": "v7.1.0", "version": "v7.1.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/type-info.git", "url": "https://github.com/symfony/type-info.git",
"reference": "b429d0710588fc396ba5def5329cf637d9861f9f" "reference": "60b28eb733f1453287f1263ed305b96091e0d1dc"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/type-info/zipball/b429d0710588fc396ba5def5329cf637d9861f9f", "url": "https://api.github.com/repos/symfony/type-info/zipball/60b28eb733f1453287f1263ed305b96091e0d1dc",
"reference": "b429d0710588fc396ba5def5329cf637d9861f9f", "reference": "60b28eb733f1453287f1263ed305b96091e0d1dc",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -15519,7 +15521,7 @@
"type" "type"
], ],
"support": { "support": {
"source": "https://github.com/symfony/type-info/tree/v7.1.0" "source": "https://github.com/symfony/type-info/tree/v7.1.1"
}, },
"funding": [ "funding": [
{ {
@ -15535,7 +15537,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-05-02T10:19:13+00:00" "time": "2024-05-31T14:59:31+00:00"
}, },
{ {
"name": "symfony/uid", "name": "symfony/uid",
@ -15613,16 +15615,16 @@
}, },
{ {
"name": "symfony/validator", "name": "symfony/validator",
"version": "v7.1.0", "version": "v7.1.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/validator.git", "url": "https://github.com/symfony/validator.git",
"reference": "ffcc8c56502f6adaeaf6307aef5b98b53a8d0326" "reference": "fcab7598968b21c361becc930fcae8846638c4c0"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/validator/zipball/ffcc8c56502f6adaeaf6307aef5b98b53a8d0326", "url": "https://api.github.com/repos/symfony/validator/zipball/fcab7598968b21c361becc930fcae8846638c4c0",
"reference": "ffcc8c56502f6adaeaf6307aef5b98b53a8d0326", "reference": "fcab7598968b21c361becc930fcae8846638c4c0",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -15669,7 +15671,8 @@
"Symfony\\Component\\Validator\\": "" "Symfony\\Component\\Validator\\": ""
}, },
"exclude-from-classmap": [ "exclude-from-classmap": [
"/Tests/" "/Tests/",
"/Resources/bin/"
] ]
}, },
"notification-url": "https://packagist.org/downloads/", "notification-url": "https://packagist.org/downloads/",
@ -15689,7 +15692,7 @@
"description": "Provides tools to validate values", "description": "Provides tools to validate values",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/validator/tree/v7.1.0" "source": "https://github.com/symfony/validator/tree/v7.1.1"
}, },
"funding": [ "funding": [
{ {
@ -15705,7 +15708,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-05-21T15:59:31+00:00" "time": "2024-06-04T05:58:56+00:00"
}, },
{ {
"name": "symfony/var-dumper", "name": "symfony/var-dumper",
@ -18242,16 +18245,16 @@
}, },
{ {
"name": "phpstan/phpstan", "name": "phpstan/phpstan",
"version": "1.11.3", "version": "1.11.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpstan/phpstan.git", "url": "https://github.com/phpstan/phpstan.git",
"reference": "e64220a05c1209fc856d58e789c3b7a32c0bb9a5" "reference": "9100a76ce8015b9aa7125b9171ae3a76887b6c82"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/e64220a05c1209fc856d58e789c3b7a32c0bb9a5", "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9100a76ce8015b9aa7125b9171ae3a76887b6c82",
"reference": "e64220a05c1209fc856d58e789c3b7a32c0bb9a5", "reference": "9100a76ce8015b9aa7125b9171ae3a76887b6c82",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -18296,7 +18299,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2024-05-31T13:53:37+00:00" "time": "2024-06-06T12:19:22+00:00"
}, },
{ {
"name": "phpunit/php-code-coverage", "name": "phpunit/php-code-coverage",
@ -20613,16 +20616,16 @@
}, },
{ {
"name": "symfony/stopwatch", "name": "symfony/stopwatch",
"version": "v7.1.0", "version": "v7.1.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/stopwatch.git", "url": "https://github.com/symfony/stopwatch.git",
"reference": "13c750a45ac43c45f45d944d22499768aa1b72d8" "reference": "5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/stopwatch/zipball/13c750a45ac43c45f45d944d22499768aa1b72d8", "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d",
"reference": "13c750a45ac43c45f45d944d22499768aa1b72d8", "reference": "5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -20655,7 +20658,7 @@
"description": "Provides a way to profile code", "description": "Provides a way to profile code",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/stopwatch/tree/v7.1.0" "source": "https://github.com/symfony/stopwatch/tree/v7.1.1"
}, },
"funding": [ "funding": [
{ {
@ -20671,7 +20674,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-04-18T09:32:20+00:00" "time": "2024-05-31T14:57:53+00:00"
}, },
{ {
"name": "theseer/tokenizer", "name": "theseer/tokenizer",

View File

@ -201,7 +201,7 @@ return [
App\Providers\MultiDBProvider::class, App\Providers\MultiDBProvider::class,
App\Providers\ClientPortalServiceProvider::class, App\Providers\ClientPortalServiceProvider::class,
App\Providers\NinjaTranslationServiceProvider::class, App\Providers\NinjaTranslationServiceProvider::class,
App\Providers\StaticServiceProvider::class, App\Providers\StaticServiceProvider::class,
], ],
/* /*

View File

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

View File

@ -5334,6 +5334,7 @@ $lang = array(
'btcpay_refund_body' => 'A refund intended for you has been issued. To claim it via BTCPay, please click on this link:', 'btcpay_refund_body' => 'A refund intended for you has been issued. To claim it via BTCPay, please click on this link:',
'currency_mauritanian_ouguiya' => 'Mauritanian Ouguiya', 'currency_mauritanian_ouguiya' => 'Mauritanian Ouguiya',
'currency_bhutan_ngultrum' => 'Bhutan Ngultrum', 'currency_bhutan_ngultrum' => 'Bhutan Ngultrum',
'end_of_month' => 'End Of Month'
); );
return $lang; return $lang;

View File

@ -1,4 +1,3 @@
{ {
"Admin": true, "Admin": true
"Ronin": true
} }

File diff suppressed because it is too large Load Diff

View File

@ -123,16 +123,16 @@
examples: examples:
user: user:
value: user value: user
summary: include=user will include the user object in the response summary: include=user will include the Cser object in the response
company: company:
value: company value: company
summary: include=company will include the company object in the response summary: include=company will include the Company object in the response
token: token:
value: token value: token
summary: include=token will include the token object in the response summary: include=token will include the Company Token object in the response
account: account:
value: account value: account
summary: include=account will include the account object in the response summary: include=account will include the Account object in the response
per_page_meta: per_page_meta:
name: per_page name: per_page
in: query in: query
@ -160,7 +160,15 @@
include_static: include_static:
name: include_static name: include_static
in: query in: query
description: 'Returns static variables' description: |
Static variables include:
- Currencies
- Countries
- Languages
- Payment Types
- Email Templatees
- Industries
required: false required: false
schema: schema:
type: string type: string
@ -168,7 +176,11 @@
clear_cache: clear_cache:
name: clear_cache name: clear_cache
in: query in: query
description: 'Clears the static cache' description: |
Clears cache
Sometimes after a system update where the static variables have been updated, it may be necessary to clear the cache so that the static variables repopulate
required: false required: false
schema: schema:
type: string type: string

File diff suppressed because it is too large Load Diff

View File

@ -40,7 +40,53 @@
company: company:
$ref: '#/components/schemas/Company' $ref: '#/components/schemas/Company'
user: user:
$ref: '#/components/schemas/User' $ref: '#/components/schemas/UserRef'
token:
$ref: '#/components/schemas/CompanyToken'
type: object
CompanyUserRef:
properties:
permissions:
description: 'The user permissionsfor this company in a comma separated list'
type: string
example: 'create_invoice,create_client,view_client'
settings:
description: 'Settings that are used for the flutter applications to store user preferences / metadata'
type: object
readOnly: true
react_settings:
description: 'Dedicated settings object for the react web application'
type: object
readOnly: true
is_owner:
description: 'Determines whether the user owns this company'
type: boolean
example: true
readOnly: true
is_admin:
description: 'Determines whether the user is the admin of this company'
type: boolean
example: true
readOnly: true
is_locked:
description: 'Determines whether the users access to this company has been locked'
type: boolean
example: true
readOnly: true
updated_at:
description: 'The last time the record was modified, format Unix Timestamp'
type: integer
example: '1231232312321'
deleted_at:
description: 'Timestamp when the user was archived, format Unix Timestamp'
type: integer
example: '12312312321'
account:
$ref: '#/components/schemas/Account'
company:
$ref: '#/components/schemas/Company'
user:
$ref: '#/components/schemas/UserRef'
token: token:
$ref: '#/components/schemas/CompanyToken' $ref: '#/components/schemas/CompanyToken'
type: object type: object

View File

@ -100,5 +100,107 @@
example: '123456' example: '123456'
readOnly: true readOnly: true
company_user: company_user:
$ref: '#/components/schemas/CompanyUser' $ref: '#/components/schemas/CompanyUserRef'
type: object
UserRef:
properties:
id:
description: 'The hashed id of the user'
type: string
example: Opnel5aKBz
readOnly: true
first_name:
description: 'The first name of the user'
type: string
example: Brad
last_name:
description: 'The last name of the user'
type: string
example: Pitt
email:
description: 'The users email address'
type: string
example: brad@pitt.com
phone:
description: 'The users phone number'
type: string
example: 555-1233-23232
signature:
description: 'The users sign off signature'
type: string
example: 'Have a nice day!'
avatar:
description: 'The users avatar'
type: string
example: 'https://url.to.your/avatar.png'
accepted_terms_version:
description: 'The version of the invoice ninja terms that has been accepted by the user'
type: string
example: 1.0.1
readOnly: true
oauth_user_id:
description: 'The provider id of the oauth entity'
type: string
example: jkhasdf789as6f675sdf768sdfs
readOnly: true
oauth_provider_id:
description: 'The oauth entity id'
type: string
example: google
readOnly: true
language_id:
description: 'The language id of the user'
type: string
example: 1
verified_phone_number:
description: 'Boolean flag if the user has their phone verified. Required to settings up 2FA'
type: boolean
example: true
readOnly: true
sms_verification_code:
description: 'The sms verification code for the user. Required to settings up 2FA'
type: string
example: '123456'
readOnly: true
oauth_user_token_expiry:
description: 'The expiry date of the oauth token'
type: string
example: '2022-10-10'
readOnly: true
has_password:
description: 'Boolean flag determining if the user has a password'
type: boolean
example: true
readOnly: true
last_confirmed_email_address:
description: 'The last confirmed email address of the user'
type: string
example: 'bob@gmail.com'
readOnly: true
custom_value1:
description: 'A custom value'
type: string
example: 'Custom value 1'
custom_value2:
description: 'A custom value'
type: string
example: '$1000'
custom_value3:
description: 'A custom value'
type: string
example: 'Custom value 3'
custom_value4:
description: 'A custom value'
type: string
example: 'Custom value 4'
is_deleted:
description: 'Boolean flag determining if the user has been deleted'
type: boolean
example: true
readOnly: true
google_2fa_secret:
description: 'The google 2fa secret for the user'
type: string
example: '123456'
readOnly: true
type: object type: object

View File

@ -3,22 +3,88 @@ info:
title: 'Invoice Ninja API Reference.' title: 'Invoice Ninja API Reference.'
description: | description: |
--- ---
<br> ## Introduction
<div style="color: white; background-color:SlateBlue; padding: 12px; border-radius:2px"> Welcome to the Invoice Ninja API documentation, your comprehensive guide to integrating Invoice Ninja's powerful features into your applications. Whether you're building a custom client, automating workflows, or integrating with other systems, our API provides the tools you need to streamline your invoicing and billing processes.
The Invoice Ninja API is organized around REST and returns JSON-encoded responses, and uses standard HTTP response codes, authentication, and verbs.
</div>
<br>
## What is Invoice Ninja?
Invoice Ninja is a robust source-available platform designed to simplify invoicing, billing, and payment management for freelancers, small businesses, and enterprises alike. With a user-friendly interface, customizable templates, and a suite of powerful features, Invoice Ninja empowers businesses to create professional invoices, track expenses, manage clients, and get paid faster.
## Why use the Invoice Ninja API?
The Invoice Ninja API allows developers to extend the functionality of Invoice Ninja by programmatically accessing and manipulating data within their Invoice Ninja accounts. With the API, you can automate repetitive tasks, integrate with third-party services, and build custom solutions tailored to your specific business needs.
## Getting Started
To get started with the Invoice Ninja API, you'll need an active Invoice Ninja account (or your own self hosted installation) and API credentials. If you haven't already done so, sign up for an account at Invoice Ninja and generate your API keys from the settings section.
Once you have your API credentials, you can start exploring the API endpoints, authentication methods, request and response formats, and more using the documentation provided here.
## Explore the Documentation
This documentation is organized into sections to help you navigate and understand the various aspects of the Invoice Ninja API:
Authentication: Learn how to authenticate your requests to the API using API tokens.
Endpoints: Explore the available API endpoints for managing invoices, clients, payments, expenses, and more.
Request and Response Formats: Understand the structure of API requests and responses, including parameters, headers, and payloads.
Error Handling: Learn about error codes, status messages, and best practices for handling errors gracefully.
Code Examples: Find code examples and tutorials to help you get started with integrating the Invoice Ninja API into your applications.
## Need Help?
If you have any questions, encounter any issues, or need assistance with using the Invoice Ninja API, don't hesitate to reach out to our support team or join our community forums. We're here to help you succeed with Invoice Ninja and make the most of our API.
Let's start building together!
## Endpoints
<div style="background-color: #2D394E; color: #fff padding: 20px; border-radius: 5px; border: 4px solid #212A3B; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);">
<p style="padding:10px; color: #DBDBDB;"">Production: https://invoicing.co</p>
<p style="padding:10px; color: #DBDBDB;">Demo: https://demo.invoiceninja.com</p>
</div>
## Client Libraries
PHP SDK can be found [here](https://github.com/invoiceninja/sdk-php)
## Authentication:
Invoice Ninja uses API tokens to authenticate requests. You can view and manage your API keys in Settings > Account Management > Integrations > API tokens
API requests must be made over HTTPS. Calls made to HTTP will fail.
## Errors:
Invoice Ninja uses standard HTTP response codes to indicate the success or failure of a request. below is a table of standard status codes and responses
| Status Code | Explanation |
|-------------|-----------------------------------------------------------------------------|
| 200 | OK: The request has succeeded. The information returned with the response is dependent on the method used in the request. |
| 301 | Moved Permanently: This and all future requests should be directed to the given URI. |
| 303 | See Other: The response to the request can be found under another URI using the GET method. |
| 400 | Bad Request: The server cannot or will not process the request due to an apparent client error. |
| 401 | Unauthorized: Similar to 403 Forbidden, but specifically for use when authentication is required and has failed or has not yet been provided. |
| 403 | Forbidden: The request was valid, but the server is refusing action. |
| 404 | Not Found: The requested resource could not be found but may be available in the future. |
| 405 | Method Not Allowed: A request method is not supported for the requested resource. |
| 409 | Conflict: Indicates that the request could not be processed because of conflict in the request. |
| 422 | Unprocessable Entity: The request was well-formed but was unable to be followed due to semantic errors. |
| 429 | Too Many Requests: The user has sent too many requests in a given amount of time ("rate limiting"). |
| 500 | Internal Server Error: A generic error message, given when an unexpected condition was encountered and no more specific message is suitable. |
## Pagination
When using index routes to retrieve lists of data, by default we limit the number of records returned to 20. You can using standard pagination to paginate results, ie:
termsOfService: 'https://invoiceninja.github.io/docs/legal/terms_of_service/#page-content' termsOfService: 'https://invoiceninja.github.io/docs/legal/terms_of_service/#page-content'
contact: contact:
email: contact@invoiceninja.com email: contact@invoiceninja.com
name: Invoice Ninja Support
url: 'https://www.invoiceninja.com'
license: license:
name: 'Elastic License' name: 'Elastic License'
url: 'https://www.elastic.co/licensing/elastic-license' url: 'https://www.elastic.co/licensing/elastic-license'
version: 5.8.34 version: 5.9.2
servers: servers:
- - url: 'https://demo.invoiceninja.com'
url: 'https://demo.invoiceninja.com' description: |
description: | ## Demo API endpoint
## Demo API Server InvoiceNinja. You can use the demo API key `TOKEN` to test the endpoints from within this API spec
You can use the demo API key `TOKEN` to test the endpoints from within this API spec - url: 'https://invoicing.co'
description: |
## Production API endpoint

View File

@ -116,12 +116,10 @@ paths:
description: "The users email address." description: "The users email address."
type: string type: string
example: "demo@invoiceninja.com" example: "demo@invoiceninja.com"
required: true
password: password:
description: "The user password. Must meet minimum criteria ~ > 6 characters" description: "The user password. Must meet minimum criteria ~ > 6 characters"
type: string type: string
example: "Password0" example: "Password0"
required: true
one_time_password: one_time_password:
description: "The one time password if 2FA is enabled" description: "The one time password if 2FA is enabled"
type: string type: string

View File

@ -3,6 +3,17 @@
tags: tags:
- clients - clients
summary: 'List clients' summary: 'List clients'
x-code-samples:
- lang: go
label: php
source: |
$ninja = new InvoiceNinja("your_token");
$invoices = $ninja->clients->all();
x-custom-element:
type: markdown
value: |
### Custom Response Description
This is a custom description for the response returned by the `/example` endpoint.
description: | description: |
When retrieving a list of clients you can also chain query parameters in order to filter the dataset that is returned. For example, you can send a request to the following URL to retrieve clients that have a balance greater than 1000:\ When retrieving a list of clients you can also chain query parameters in order to filter the dataset that is returned. For example, you can send a request to the following URL to retrieve clients that have a balance greater than 1000:\

2
package-lock.json generated
View File

@ -1,5 +1,5 @@
{ {
"name": "@invoiceninja/invoiceninja", "name": "invoiceninja",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {

File diff suppressed because one or more lines are too long

View File

@ -240,7 +240,7 @@
"src": "resources/js/setup/setup.js" "src": "resources/js/setup/setup.js"
}, },
"resources/sass/app.scss": { "resources/sass/app.scss": {
"file": "assets/app-c7c5fad4.css", "file": "assets/app-f3b33400.css",
"isEntry": true, "isEntry": true,
"src": "resources/sass/app.scss" "src": "resources/sass/app.scss"
} }

View File

@ -1,4 +1,4 @@
<div wire:ignore.self class="@unless($form_only) container mx-auto grid grid-cols-12 @endunless mb-4" data-ref="required-fields-container"> <div wire:ignore.self class="@unless($form_only) container mx-auto grid grid-cols-12 @endunless mb-4 transition ease-out duration-1000 h-500" data-ref="required-fields-container">
<div class="col-span-12 lg:col-span-6 lg:col-start-4 overflow-hidden @unless($form_only) bg-white shadow rounded-lg @endunless"> <div class="col-span-12 lg:col-span-6 lg:col-start-4 overflow-hidden @unless($form_only) bg-white shadow rounded-lg @endunless">
@unless($form_only) @unless($form_only)
<div class="px-4 py-5 border-b border-gray-200 sm:px-6"> <div class="px-4 py-5 border-b border-gray-200 sm:px-6">

View File

@ -16,7 +16,12 @@
<div class="alert alert-failure mb-4" hidden id="errors"></div> <div class="alert alert-failure mb-4" hidden id="errors"></div>
<div id="paypal-button-container" class="paypal-button-container"></div> <div id="paypal-button-container" class="paypal-button-container">
</div>
<div id="is_working" class="flex mt-4 place-items-center hidden">
<span class="loader m-auto"></span>
</div>
@endsection @endsection
@ -24,7 +29,40 @@
@endsection @endsection
@push('footer') @push('footer')
<style type="text/css">
.loader {
width: 48px;
height: 48px;
border-radius: 50%;
position: relative;
animation: rotate 1s linear infinite
}
.loader::before , .loader::after {
content: "";
box-sizing: border-box;
position: absolute;
inset: 0px;
border-radius: 50%;
border: 5px solid #454545;
animation: prixClipFix 2s linear infinite ;
}
.loader::after{
border-color: #FF3D00;
animation: prixClipFix 2s linear infinite , rotate 0.5s linear infinite reverse;
inset: 6px;
}
@keyframes rotate {
0% {transform: rotate(0deg)}
100% {transform: rotate(360deg)}
}
@keyframes prixClipFix {
0% {clip-path:polygon(50% 50%,0 0,0 0,0 0,0 0,0 0)}
25% {clip-path:polygon(50% 50%,0 0,100% 0,100% 0,100% 0,100% 0)}
50% {clip-path:polygon(50% 50%,0 0,100% 0,100% 100%,100% 100%,100% 100%)}
75% {clip-path:polygon(50% 50%,0 0,100% 0,100% 100%,0 100%,0 100%)}
100% {clip-path:polygon(50% 50%,0 0,100% 0,100% 100%,0 100%,0 0)}
}
</style>
<script src="https://www.paypal.com/sdk/js?client-id={!! $client_id !!}&currency={!! $currency !!}&components=buttons,funding-eligibility&intent=capture&enable-funding={!! $funding_source !!}" data-partner-attribution-id="invoiceninja_SP_PPCP"></script> <script src="https://www.paypal.com/sdk/js?client-id={!! $client_id !!}&currency={!! $currency !!}&components=buttons,funding-eligibility&intent=capture&enable-funding={!! $funding_source !!}" data-partner-attribution-id="invoiceninja_SP_PPCP"></script>
<script> <script>
@ -106,6 +144,8 @@
if(fundingSource != 'card') if(fundingSource != 'card')
document.getElementById('paypal-button-container').hidden = true; document.getElementById('paypal-button-container').hidden = true;
document.getElementById('is_working').classList.remove('hidden');
document.querySelector('div[data-ref="required-fields-container').classList.add('hidden'); document.querySelector('div[data-ref="required-fields-container').classList.add('hidden');
}, },

View File

@ -75,7 +75,6 @@
@endsection @endsection
@push('footer') @push('footer')
<link rel="stylesheet" type="text/css" href=https://www.paypalobjects.com/webstatic/en_US/developer/docs/css/cardfields.css />
@if(isset($merchantId)) @if(isset($merchantId))
<script src="https://www.paypal.com/sdk/js?client-id={!! $client_id !!}&merchantId={!! $merchantId !!}&components=card-fields" data-partner-attribution-id="invoiceninja_SP_PPCP"></script> <script src="https://www.paypal.com/sdk/js?client-id={!! $client_id !!}&merchantId={!! $merchantId !!}&components=card-fields" data-partner-attribution-id="invoiceninja_SP_PPCP"></script>
@ -87,24 +86,7 @@
const clientId = "{{ $client_id }}"; const clientId = "{{ $client_id }}";
const orderId = "{!! $order_id !!}"; const orderId = "{!! $order_id !!}";
const cardStyle = {
'input': {
'font-size': '16px',
'font-family': 'courier, monospace',
'font-weight': 'lighter',
'color': '#ccc',
},
'.invalid': {
'color': 'purple',
},
'.expcvv': {
'display': 'grid',
'grid-template-columns': 'auto'
}
};
const cardField = paypal.CardFields({ const cardField = paypal.CardFields({
// style: cardStyle,
client: clientId, client: clientId,
createOrder: function(data, actions) { createOrder: function(data, actions) {
return orderId; return orderId;
@ -125,7 +107,6 @@
} }
let storeCard = document.querySelector('input[name=token-billing-checkbox]:checked'); let storeCard = document.querySelector('input[name=token-billing-checkbox]:checked');
if (storeCard) { if (storeCard) {
@ -197,7 +178,6 @@
const numberField = cardField.NumberField({ const numberField = cardField.NumberField({
inputEvents: { inputEvents: {
onChange: (event)=> { onChange: (event)=> {
// console.log("returns a stateObject", event);
} }
}, },
}); });
@ -207,7 +187,6 @@
const cvvField = cardField.CVVField({ const cvvField = cardField.CVVField({
inputEvents: { inputEvents: {
onChange: (event)=> { onChange: (event)=> {
// console.log("returns a stateObject", event);
} }
}, },
}); });
@ -216,7 +195,6 @@
const expiryField = cardField.ExpiryField({ const expiryField = cardField.ExpiryField({
inputEvents: { inputEvents: {
onChange: (event)=> { onChange: (event)=> {
// console.log("returns a stateObject", event);
} }
}, },
}); });
@ -312,6 +290,18 @@
document.getElementById("token").value = token.value; document.getElementById("token").value = token.value;
} }
document.getElementById('errors').textContent = '';
document.getElementById('errors').hidden = true;
document.getElementById('pay-now-token').disabled = true;
document.querySelector('#pay-now-token > svg').classList.remove('hidden');
document.querySelector('#pay-now-token > svg').classList.add('justify-center');
document.querySelector('#pay-now-token > svg').classList.add('mx-auto');
document.querySelector('#pay-now-token > svg').classList.add('item-center');
document.querySelector('#pay-now-token > span').classList.add('hidden');
document.getElementById("gateway_response").value = JSON.stringify( {token: token.value, orderID: "{!! $order_id !!}"} ); document.getElementById("gateway_response").value = JSON.stringify( {token: token.value, orderID: "{!! $order_id !!}"} );
document.getElementById("server_response").submit(); document.getElementById("server_response").submit();

View File

@ -50,8 +50,10 @@
document.addEventListener('livewire:init', () => { document.addEventListener('livewire:init', () => {
Livewire.on('passed-required-fields-check', () => { Livewire.on('passed-required-fields-check', () => {
document.querySelector('div[data-ref="required-fields-container"]').classList.add('opacity-25');
document.querySelector('div[data-ref="required-fields-container"]').classList.add('pointer-events-none'); document.querySelector('div[data-ref="required-fields-container"]').classList.toggle('h-0');
// document.querySelector('div[data-ref="required-fields-container"]').classList.add('opacity-25');
// document.querySelector('div[data-ref="required-fields-container"]').classList.add('pointer-events-none');
document.querySelector('div[data-ref="gateway-container"]').classList.remove('opacity-25'); document.querySelector('div[data-ref="gateway-container"]').classList.remove('opacity-25');
document.querySelector('div[data-ref="gateway-container"]').classList.remove('pointer-events-none'); document.querySelector('div[data-ref="gateway-container"]').classList.remove('pointer-events-none');

View File

@ -184,7 +184,10 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale']
Route::post('client_statement', [ClientStatementController::class, 'statement'])->name('client.statement'); Route::post('client_statement', [ClientStatementController::class, 'statement'])->name('client.statement');
Route::post('companies/purge/{company}', [MigrationController::class, 'purgeCompany'])->middleware('password_protected');
Route::post('companies/purge/{company}', [MigrationController::class, 'purgeCompany'])->middleware('password_protected');
Route::post('companies/current', [CompanyController::class, 'current'])->name('companies.current');
Route::post('companies/purge_save_settings/{company}', [MigrationController::class, 'purgeCompanySaveSettings'])->middleware('password_protected'); Route::post('companies/purge_save_settings/{company}', [MigrationController::class, 'purgeCompanySaveSettings'])->middleware('password_protected');
Route::resource('companies', CompanyController::class); // name = (companies. index / create / show / update / destroy / edit Route::resource('companies', CompanyController::class); // name = (companies. index / create / show / update / destroy / edit

View File

@ -15,6 +15,9 @@ module.exports = {
fontFamily: { fontFamily: {
sans: ['Open Sans', ...defaultTheme.fontFamily.sans], sans: ['Open Sans', ...defaultTheme.fontFamily.sans],
}, },
transitionProperty: {
'height': 'height'
}
}, },
}, },
plugins: [ plugins: [

View File

@ -72,6 +72,21 @@ class CompanyTest extends TestCase
$this->assertEquals(1, TaxRate::count()); $this->assertEquals(1, TaxRate::count());
} }
public function testCompanyCurrent()
{
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->postJson("/api/v1/companies/current");
$response->assertStatus(200);
$arr = $response->json();
$this->assertEquals($arr['data']['id'], $this->company->hashed_id);
}
public function testCompanyLogoInline() public function testCompanyLogoInline()
{ {
$response = $this->withHeaders([ $response = $this->withHeaders([

View File

@ -19,13 +19,14 @@ use App\DataMapper\ClientSettings;
use App\DataMapper\CompanySettings; use App\DataMapper\CompanySettings;
use App\DataMapper\InvoiceItem; use App\DataMapper\InvoiceItem;
use App\Models\Invoice; use App\Models\Invoice;
use Invoiceninja\Einvoice\Symfony\Encode; use InvoiceNinja\EInvoice\Symfony\Encode;
use App\Services\EDocument\Standards\FatturaPANew; use App\Services\EDocument\Standards\FatturaPANew;
use Illuminate\Routing\Middleware\ThrottleRequests; use Illuminate\Routing\Middleware\ThrottleRequests;
use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\DatabaseTransactions;
use Invoiceninja\Einvoice\Models\FatturaPA\FatturaElettronica; use InvoiceNinja\EInvoice\EInvoice;
use Invoiceninja\Einvoice\Models\FatturaPA\FatturaElettronicaBodyType\FatturaElettronicaBody; use InvoiceNinja\EInvoice\Models\FatturaPA\FatturaElettronica;
use Invoiceninja\Einvoice\Models\FatturaPA\FatturaElettronicaHeaderType\FatturaElettronicaHeader; use InvoiceNinja\EInvoice\Models\FatturaPA\FatturaElettronicaBodyType\FatturaElettronicaBody;
use InvoiceNinja\EInvoice\Models\FatturaPA\FatturaElettronicaHeaderType\FatturaElettronicaHeader;
/** /**
* @test * @test
@ -125,15 +126,22 @@ class FatturaPATest extends TestCase
$this->assertInstanceOf(FatturaElettronicaBody::class, $fe->FatturaElettronicaBody[0]); $this->assertInstanceOf(FatturaElettronicaBody::class, $fe->FatturaElettronicaBody[0]);
$this->assertInstanceOf(FatturaElettronicaHeader::class, $fe->FatturaElettronicaHeader); $this->assertInstanceOf(FatturaElettronicaHeader::class, $fe->FatturaElettronicaHeader);
$e = new EInvoice;
$errors = $e->validate($fe);
$encoder = new Encode($fe); if(count($errors) > 0)
$xml = $encoder->toXml(); nlog($errors);
$this->assertCount(0, $errors);
nlog($xml);
$xml = $e->encode($fe, 'xml');
$this->assertNotNull($xml); $this->assertNotNull($xml);
$json = $e->encode($fe, 'json');
$this->assertNotNull($json);
$decode = $e->decode('FatturaPA', $json, 'json');
$this->assertInstanceOf(FatturaElettronica::class, $decode);
} }
} }

View File

@ -0,0 +1,154 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace Tests\Feature\EInvoice;
use Tests\TestCase;
use App\Models\Client;
use App\Models\Company;
use Tests\MockAccountData;
use App\DataMapper\ClientSettings;
use App\DataMapper\CompanySettings;
use App\DataMapper\InvoiceItem;
use App\Models\Invoice;
use InvoiceNinja\EInvoice\Symfony\Encode;
use App\Services\EDocument\Standards\FatturaPANew;
use App\Services\EDocument\Standards\Peppol;
use Illuminate\Routing\Middleware\ThrottleRequests;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use InvoiceNinja\EInvoice\EInvoice;
use InvoiceNinja\EInvoice\Models\FatturaPA\FatturaElettronica;
use InvoiceNinja\EInvoice\Models\FatturaPA\FatturaElettronicaBodyType\FatturaElettronicaBody;
use InvoiceNinja\EInvoice\Models\FatturaPA\FatturaElettronicaHeaderType\FatturaElettronicaHeader;
/**
* @test
*/
class PeppolTest extends TestCase
{
use DatabaseTransactions;
use MockAccountData;
protected function setUp(): void
{
parent::setUp();
$this->makeTestData();
// $this->markTestSkipped('prevent running in CI');
$this->withoutMiddleware(
ThrottleRequests::class
);
}
public function testInvoiceBoot()
{
$settings = CompanySettings::defaults();
$settings->address1 = 'Via Silvio Spaventa 108';
$settings->city = 'Calcinelli';
$settings->state = 'PA';
// $settings->state = 'Perugia';
$settings->postal_code = '61030';
$settings->country_id = '380';
$settings->currency_id = '3';
$settings->vat_number = '01234567890';
$settings->id_number = '';
$company = Company::factory()->create([
'account_id' => $this->account->id,
'settings' => $settings,
]);
$client_settings = ClientSettings::defaults();
$client_settings->currency_id = '3';
$client = Client::factory()->create([
'company_id' => $company->id,
'user_id' => $this->user->id,
'name' => 'Italian Client Name',
'address1' => 'Via Antonio da Legnago 68',
'city' => 'Monasterace',
'state' => 'CR',
// 'state' => 'Reggio Calabria',
'postal_code' => '89040',
'country_id' => 380,
'routing_id' => 'ABC1234',
'settings' => $client_settings,
]);
$item = new InvoiceItem();
$item->product_key = "Product Key";
$item->notes = "Product Description";
$item->cost = 10;
$item->quantity = 10;
$item->tax_rate1 = 22;
$item->tax_name1 = 'IVA';
$invoice = Invoice::factory()->create([
'company_id' => $company->id,
'user_id' => $this->user->id,
'client_id' => $client->id,
'discount' => 0,
'uses_inclusive_taxes' => false,
'status_id' => 1,
'tax_rate1' => 0,
'tax_name1' => '',
'tax_rate2' => 0,
'tax_rate3' => 0,
'tax_name2' => '',
'tax_name3' => '',
'line_items' => [$item],
'number' => 'ITA-'.rand(1000, 100000),
'date' => now()->format('Y-m-d')
]);
$invoice->service()->markSent()->save();
$fat = new Peppol($invoice);
$fat->run();
$fe = $fat->getInvoice();
$this->assertNotNull($fe);
$this->assertInstanceOf(\InvoiceNinja\EInvoice\Models\Peppol\Invoice::class, $fe);
$e = new EInvoice();
$xml = $e->encode($fe, 'xml');
$this->assertNotNull($xml);
nlog($xml);
$json = $e->encode($fe, 'json');
$this->assertNotNull($json);
nlog($json);
$decode = $e->decode('Peppol', $json, 'json');
$this->assertInstanceOf(\InvoiceNinja\EInvoice\Models\Peppol\Invoice::class, $decode);
$errors = $e->validate($fe);
if(count($errors) > 0) {
nlog($errors);
}
$this->assertCount(0, $errors);
}
}

View File

@ -184,7 +184,7 @@ class ReminderTest extends TestCase
$settings->schedule_reminder1 = 'after_invoice_date'; $settings->schedule_reminder1 = 'after_invoice_date';
$settings->schedule_reminder2 = 'after_invoice_date'; $settings->schedule_reminder2 = 'after_invoice_date';
$settings->schedule_reminder3 = 'after_invoice_date'; $settings->schedule_reminder3 = 'after_invoice_date';
$settings->lock_invoices = true;
$settings->num_days_reminder1 = 5; $settings->num_days_reminder1 = 5;
$settings->num_days_reminder2 = 10; $settings->num_days_reminder2 = 10;
$settings->num_days_reminder3 = 15; $settings->num_days_reminder3 = 15;
@ -231,7 +231,6 @@ class ReminderTest extends TestCase
} }
while($x === false); while($x === false);
$this->assertNotNull($invoice->reminder_last_sent); $this->assertNotNull($invoice->reminder_last_sent);
//check next send date is on day "10" //check next send date is on day "10"

View File

@ -23,51 +23,51 @@ use Illuminate\Support\Facades\Cache;
use Symfony\Component\Validator\Validation; use Symfony\Component\Validator\Validation;
use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Encoder\XmlEncoder; use Symfony\Component\Serializer\Encoder\XmlEncoder;
use Invoiceninja\Einvoice\Models\FACT1\ItemType\Item; use InvoiceNinja\EInvoice\Models\Peppol\ItemType\Item;
use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\DatabaseTransactions;
use Invoiceninja\Einvoice\Models\FACT1\PartyType\Party; use InvoiceNinja\EInvoice\Models\Peppol\PartyType\Party;
use Invoiceninja\Einvoice\Models\FACT1\PriceType\Price; use InvoiceNinja\EInvoice\Models\Peppol\PriceType\Price;
use Symfony\Component\PropertyInfo\PropertyInfoExtractor; use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
use Invoiceninja\Einvoice\Models\FACT1\ContactType\Contact; use InvoiceNinja\EInvoice\Models\Peppol\ContactType\Contact;
use Invoiceninja\Einvoice\Models\FACT1\CountryType\Country; use InvoiceNinja\EInvoice\Models\Peppol\CountryType\Country;
use Invoiceninja\Einvoice\Models\FACT1\AmountType\TaxAmount; use InvoiceNinja\EInvoice\Models\Peppol\AmountType\TaxAmount;
use Invoiceninja\Einvoice\Models\FACT1\TaxTotalType\TaxTotal; use InvoiceNinja\EInvoice\Models\Peppol\TaxTotalType\TaxTotal;
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Invoiceninja\Einvoice\Models\FACT1\AmountType\PriceAmount; use InvoiceNinja\EInvoice\Models\Peppol\AmountType\PriceAmount;
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
use Invoiceninja\Einvoice\Models\FACT1\TaxSchemeType\TaxScheme; use InvoiceNinja\EInvoice\Models\Peppol\TaxSchemeType\TaxScheme;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
use Invoiceninja\Einvoice\Models\FACT1\AddressType\PostalAddress; use InvoiceNinja\EInvoice\Models\Peppol\AddressType\PostalAddress;
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
use Invoiceninja\Einvoice\Models\FACT1\InvoiceLineType\InvoiceLine; use InvoiceNinja\EInvoice\Models\Peppol\InvoiceLineType\InvoiceLine;
use Invoiceninja\Einvoice\Models\FACT1\TaxScheme as FACT1TaxScheme; use InvoiceNinja\EInvoice\Models\Peppol\TaxScheme as PeppolTaxScheme;
use Invoiceninja\Einvoice\Models\FACT1\TaxSubtotalType\TaxSubtotal; use InvoiceNinja\EInvoice\Models\Peppol\TaxSubtotalType\TaxSubtotal;
use Invoiceninja\Einvoice\Models\FACT1\QuantityType\InvoicedQuantity; use InvoiceNinja\EInvoice\Models\Peppol\QuantityType\InvoicedQuantity;
use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
use Invoiceninja\Einvoice\Models\FACT1\AmountType\LineExtensionAmount; use InvoiceNinja\EInvoice\Models\Peppol\AmountType\LineExtensionAmount;
use Invoiceninja\Einvoice\Models\FACT1\AmountType\PayableAmount; use InvoiceNinja\EInvoice\Models\Peppol\AmountType\PayableAmount;
use Invoiceninja\Einvoice\Models\FACT1\AmountType\TaxableAmount; use InvoiceNinja\EInvoice\Models\Peppol\AmountType\TaxableAmount;
use Invoiceninja\Einvoice\Models\FACT1\AmountType\TaxExclusiveAmount; use InvoiceNinja\EInvoice\Models\Peppol\AmountType\TaxExclusiveAmount;
use Invoiceninja\Einvoice\Models\FACT1\AmountType\TaxInclusiveAmount; use InvoiceNinja\EInvoice\Models\Peppol\AmountType\TaxInclusiveAmount;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
use Invoiceninja\Einvoice\Models\FACT1\PartyTaxSchemeType\PartyTaxScheme; use InvoiceNinja\EInvoice\Models\Peppol\PartyTaxSchemeType\PartyTaxScheme;
use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter; use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter;
use Invoiceninja\Einvoice\Models\FACT1\MonetaryTotalType\LegalMonetaryTotal; use InvoiceNinja\EInvoice\Models\Peppol\MonetaryTotalType\LegalMonetaryTotal;
use Invoiceninja\Einvoice\Models\FACT1\PartyLegalEntityType\PartyLegalEntity; use InvoiceNinja\EInvoice\Models\Peppol\PartyLegalEntityType\PartyLegalEntity;
use Invoiceninja\Einvoice\Models\FACT1\TaxCategoryType\ClassifiedTaxCategory; use InvoiceNinja\EInvoice\Models\Peppol\TaxCategoryType\ClassifiedTaxCategory;
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata;
use Invoiceninja\Einvoice\Models\FACT1\CustomerPartyType\AccountingCustomerParty; use InvoiceNinja\EInvoice\Models\Peppol\CustomerPartyType\AccountingCustomerParty;
use Invoiceninja\Einvoice\Models\FACT1\SupplierPartyType\AccountingSupplierParty; use InvoiceNinja\EInvoice\Models\Peppol\SupplierPartyType\AccountingSupplierParty;
use Invoiceninja\Einvoice\Models\FACT1\PartyIdentificationType\PartyIdentification; use InvoiceNinja\EInvoice\Models\Peppol\PartyIdentificationType\PartyIdentification;
use Invoiceninja\Einvoice\Models\FACT1\TaxCategoryType\TaxCategory; use InvoiceNinja\EInvoice\Models\Peppol\TaxCategoryType\TaxCategory;
/** /**
* @test * @test
*/ */
class Fact1Test extends TestCase class FACT1Test extends TestCase
{ {
use MockAccountData; use MockAccountData;
use DatabaseTransactions; use DatabaseTransactions;
@ -140,7 +140,7 @@ class Fact1Test extends TestCase
$_invoice->service()->markSent()->save(); $_invoice->service()->markSent()->save();
$calc = $_invoice->calc(); $calc = $_invoice->calc();
$invoice = new \Invoiceninja\Einvoice\Models\FACT1\Invoice(); $invoice = new \InvoiceNinja\EInvoice\Models\Peppol\Invoice();
$invoice->UBLVersionID = '2.1'; $invoice->UBLVersionID = '2.1';
$invoice->CustomizationID = 'urn:cen.eu:en16931:2017#compliant#urn:efactura.mfinante.ro:CIUS-RO:1.0.1'; $invoice->CustomizationID = 'urn:cen.eu:en16931:2017#compliant#urn:efactura.mfinante.ro:CIUS-RO:1.0.1';
$invoice->ID = $_invoice->number; $invoice->ID = $_invoice->number;
@ -298,7 +298,7 @@ class Fact1Test extends TestCase
$i->Description = $item->notes; $i->Description = $item->notes;
$i->Name = $item->product_key; $i->Name = $item->product_key;
$tax_scheme = new FACT1TaxScheme(); $tax_scheme = new PeppolTaxScheme();
$tax_scheme->ID = $item->tax_name1; $tax_scheme->ID = $item->tax_name1;
$tax_scheme->Name = $item->tax_rate1; $tax_scheme->Name = $item->tax_rate1;

View File

@ -31,6 +31,29 @@ class DatesTest extends TestCase
// $this->makeTestData(); // $this->makeTestData();
} }
public function testDateNotGreaterThanMonthsEnd()
{
$this->travelTo(now()->createFromDate(2024, 6, 20));
$date = '2024-05-20';
$this->assertTrue(\Carbon\Carbon::parse($date)->endOfMonth()->lte(now()));
$this->travelBack();
}
public function testDatLessThanMonthsEnd()
{
$this->travelTo(now()->createFromDate(2024, 5, 30));
$date = '2024-05-20';
$this->assertFalse(\Carbon\Carbon::parse($date)->endOfMonth()->lte(now()));
$this->travelBack();
}
public function testLastFinancialYear3() public function testLastFinancialYear3()
{ {
$this->travelTo(now()->createFromDate(2020, 6, 30)); $this->travelTo(now()->createFromDate(2020, 6, 30));

21
vite.config.ts.react Normal file
View File

@ -0,0 +1,21 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tsconfigPaths from 'vite-tsconfig-paths';
export default defineConfig({
plugins: [tsconfigPaths(), react()],
server: {
port: 3000,
},
build: {
assetsDir: '',
chunkSizeWarningLimit: 1500,
rollupOptions: {
output: {
// This will output a single bundle file
entryFileNames: 'bundle.js',
chunkFileNames: 'bundle.js'
}
},
},
});