diff --git a/_ide_helper_custom.php b/_ide_helper_custom.php index bfe7b673dd79..747187a2d147 100644 --- a/_ide_helper_custom.php +++ b/_ide_helper_custom.php @@ -13,5 +13,10 @@ namespace Illuminate\Contracts\Mail { return true; } + + public function brevo_config(string $key) + { + return true; + } } } diff --git a/app/Console/Commands/CreateSingleAccount.php b/app/Console/Commands/CreateSingleAccount.php index eb9395c1b152..75258b35f3e7 100644 --- a/app/Console/Commands/CreateSingleAccount.php +++ b/app/Console/Commands/CreateSingleAccount.php @@ -11,53 +11,54 @@ namespace App\Console\Commands; -use App\DataMapper\ClientRegistrationFields; -use App\DataMapper\CompanySettings; -use App\DataMapper\FeesAndLimits; -use App\Events\Invoice\InvoiceWasCreated; -use App\Events\RecurringInvoice\RecurringInvoiceWasCreated; -use App\Factory\GroupSettingFactory; -use App\Factory\InvoiceFactory; -use App\Factory\InvoiceItemFactory; -use App\Factory\RecurringInvoiceFactory; -use App\Factory\SubscriptionFactory; -use App\Helpers\Invoice\InvoiceSum; -use App\Jobs\Company\CreateCompanyTaskStatuses; -use App\Libraries\MultiDB; -use App\Models\Account; -use App\Models\BankIntegration; -use App\Models\BankTransaction; -use App\Models\BankTransactionRule; +use stdClass; +use Carbon\Carbon; +use Faker\Factory; +use App\Models\Task; +use App\Models\User; +use App\Utils\Ninja; +use App\Models\Quote; use App\Models\Client; -use App\Models\ClientContact; -use App\Models\Company; -use App\Models\CompanyGateway; -use App\Models\CompanyToken; -use App\Models\Country; use App\Models\Credit; +use App\Models\Vendor; +use App\Models\Account; +use App\Models\Company; +use App\Models\Country; use App\Models\Expense; use App\Models\Invoice; use App\Models\Product; use App\Models\Project; -use App\Models\Quote; -use App\Models\RecurringInvoice; -use App\Models\Task; -use App\Models\TaskStatus; use App\Models\TaxRate; -use App\Models\User; -use App\Models\Vendor; +use App\Libraries\MultiDB; +use App\Models\TaskStatus; +use App\Models\CompanyToken; +use App\Models\ClientContact; use App\Models\VendorContact; -use App\Repositories\InvoiceRepository; -use App\Utils\Ninja; -use App\Utils\Traits\GeneratesCounter; +use App\Models\CompanyGateway; +use App\Factory\InvoiceFactory; +use App\Models\BankIntegration; +use App\Models\BankTransaction; use App\Utils\Traits\MakesHash; -use Carbon\Carbon; -use Faker\Factory; use Illuminate\Console\Command; -use Illuminate\Support\Facades\Cache; +use App\Models\RecurringInvoice; +use App\DataMapper\FeesAndLimits; +use App\DataMapper\ClientSettings; +use App\DataMapper\CompanySettings; +use App\Factory\InvoiceItemFactory; +use App\Helpers\Invoice\InvoiceSum; +use App\Models\BankTransactionRule; +use App\Factory\GroupSettingFactory; +use App\Factory\SubscriptionFactory; use Illuminate\Support\Facades\Hash; +use Illuminate\Support\Facades\Cache; +use App\Utils\Traits\GeneratesCounter; use Illuminate\Support\Facades\Schema; -use stdClass; +use App\Repositories\InvoiceRepository; +use App\Factory\RecurringInvoiceFactory; +use App\Events\Invoice\InvoiceWasCreated; +use App\DataMapper\ClientRegistrationFields; +use App\Jobs\Company\CreateCompanyTaskStatuses; +use App\Events\RecurringInvoice\RecurringInvoiceWasCreated; class CreateSingleAccount extends Command { @@ -951,7 +952,7 @@ class CreateSingleAccount extends Command } - if (config('ninja.testvars.paytrace.decrypted') && ($this->gateway == 'all' || $this->gateway == 'paytrace')) { + if (config('ninja.testvars.paytrace') && ($this->gateway == 'all' || $this->gateway == 'paytrace')) { $cg = new CompanyGateway(); $cg->company_id = $company->id; $cg->user_id = $user->id; @@ -960,7 +961,7 @@ class CreateSingleAccount extends Command $cg->require_billing_address = true; $cg->require_shipping_address = true; $cg->update_details = true; - $cg->config = encrypt(config('ninja.testvars.paytrace.decrypted')); + $cg->config = encrypt(config('ninja.testvars.paytrace')); $cg->save(); @@ -1015,6 +1016,85 @@ class CreateSingleAccount extends Command $cg->fees_and_limits = $fees_and_limits; $cg->save(); } + + if (config('ninja.testvars.eway') && ($this->gateway == 'all' || $this->gateway == 'eway')) { + $cg = new CompanyGateway(); + $cg->company_id = $company->id; + $cg->user_id = $user->id; + $cg->gateway_key = '944c20175bbe6b9972c05bcfe294c2c7'; + $cg->require_cvv = true; + $cg->require_billing_address = true; + $cg->require_shipping_address = true; + $cg->update_details = true; + $cg->config = encrypt(config('ninja.testvars.eway')); + $cg->save(); + + $gateway_types = $cg->driver()->gatewayTypes(); + + $fees_and_limits = new stdClass(); + $fees_and_limits->{$gateway_types[0]} = new FeesAndLimits(); + + $cg->fees_and_limits = $fees_and_limits; + $cg->save(); + } + + + if (config('ninja.testvars.gocardless') && ($this->gateway == 'all' || $this->gateway == 'gocardless')) { + + $c_settings = ClientSettings::defaults(); + $c_settings->currency_id = '2'; + + $client = Client::factory()->create([ + 'user_id' => $user->id, + 'company_id' => $company->id, + 'name' => 'cypress', + 'country_id' => 826, + 'settings' => $c_settings + ]); + + $cg = new CompanyGateway(); + $cg->company_id = $company->id; + $cg->user_id = $user->id; + $cg->gateway_key = 'b9886f9257f0c6ee7c302f1c74475f6c'; + $cg->require_cvv = true; + $cg->require_billing_address = true; + $cg->require_shipping_address = true; + $cg->update_details = true; + $cg->config = encrypt(config('ninja.testvars.gocardless')); + $cg->save(); + + $gateway_types = $cg->driver($client)->gatewayTypes(); + + $fees_and_limits = new stdClass(); + $fees_and_limits->{$gateway_types[0]} = new FeesAndLimits(); + + $cg->fees_and_limits = $fees_and_limits; + $cg->save(); + } + + if (config('ninja.testvars.forte') && ($this->gateway == 'all' || $this->gateway == 'forte')) { + $cg = new CompanyGateway(); + $cg->company_id = $company->id; + $cg->user_id = $user->id; + $cg->gateway_key = 'kivcvjexxvdiyqtj3mju5d6yhpeht2xs'; + $cg->require_cvv = true; + $cg->require_billing_address = true; + $cg->require_shipping_address = true; + $cg->update_details = true; + $cg->config = encrypt(config('ninja.testvars.forte')); + $cg->save(); + + $gateway_types = $cg->driver()->gatewayTypes(); + + $fees_and_limits = new stdClass(); + $fees_and_limits->{$gateway_types[0]} = new FeesAndLimits(); + + $cg->fees_and_limits = $fees_and_limits; + $cg->save(); + } + + + } private function createRecurringInvoice(Client $client) diff --git a/app/DataMapper/CompanySettings.php b/app/DataMapper/CompanySettings.php index d2241f95b2f5..0cb14e9b96e0 100644 --- a/app/DataMapper/CompanySettings.php +++ b/app/DataMapper/CompanySettings.php @@ -229,7 +229,7 @@ class CompanySettings extends BaseSettings public $require_quote_signature = false; //@TODO ben to confirm //email settings - public $email_sending_method = 'default'; //enum 'default','gmail','office365' 'client_postmark', 'client_mailgun', 'mailgun' //@implemented + public $email_sending_method = 'default'; //enum 'default','gmail','office365' 'client_postmark', 'client_mailgun', 'mailgun', 'client_brevo' //@implemented public $gmail_sending_user_id = '0'; //@implemented @@ -451,6 +451,8 @@ class CompanySettings extends BaseSettings public $mailgun_endpoint = 'api.mailgun.net'; //api.eu.mailgun.net + public $brevo_secret = ''; + public $auto_bill_standard_invoices = false; public $email_alignment = 'center'; // center , left, right @@ -477,6 +479,8 @@ class CompanySettings extends BaseSettings public $e_invoice_type = 'EN16931'; + public $e_quote_type = 'OrderX_Comfort'; + public $default_expense_payment_type_id = '0'; public $enable_e_invoice = false; @@ -497,7 +501,11 @@ class CompanySettings extends BaseSettings public $use_unapplied_payment = 'off'; //always, option, off //@implemented + public $enable_rappen_rounding = false; + public static $casts = [ + 'e_quote_type' => 'string', + 'enable_rappen_rounding' => 'bool', 'use_unapplied_payment' => 'string', 'show_pdfhtml_on_mobile' => 'bool', 'payment_email_all_contacts' => 'bool', @@ -517,262 +525,263 @@ class CompanySettings extends BaseSettings 'show_task_item_description' => 'bool', 'allow_billable_task_items' => 'bool', 'accept_client_input_quote_approval' => 'bool', - 'custom_sending_email' => 'string', - 'show_paid_stamp' => 'bool', - 'show_shipping_address' => 'bool', - 'company_logo_size' => 'string', - 'show_email_footer' => 'bool', - 'email_alignment' => 'string', - 'auto_bill_standard_invoices' => 'bool', - 'postmark_secret' => 'string', - 'mailgun_secret' => 'string', - 'mailgun_domain' => 'string', - 'send_email_on_mark_paid' => 'bool', - 'vendor_portal_enable_uploads' => 'bool', - 'besr_id' => 'string', - 'qr_iban' => 'string', - 'email_subject_purchase_order' => 'string', - 'email_template_purchase_order' => 'string', - 'require_purchase_order_signature' => 'bool', - 'purchase_order_public_notes' => 'string', - 'purchase_order_terms' => 'string', - 'purchase_order_design_id' => 'string', - 'purchase_order_footer' => 'string', - 'purchase_order_number_pattern' => 'string', - 'page_numbering_alignment' => 'string', - 'page_numbering' => 'bool', - 'auto_archive_invoice_cancelled' => 'bool', - 'email_from_name' => 'string', - 'show_all_tasks_client_portal' => 'string', - 'entity_send_time' => 'int', - 'shared_invoice_credit_counter' => 'bool', - 'reply_to_name' => 'string', - 'hide_empty_columns_on_pdf' => 'bool', - 'enable_reminder_endless' => 'bool', - 'use_credits_payment' => 'string', - 'recurring_invoice_number_pattern' => 'string', - 'recurring_invoice_number_counter' => 'int', + 'custom_sending_email' => 'string', + 'show_paid_stamp' => 'bool', + 'show_shipping_address' => 'bool', + 'company_logo_size' => 'string', + 'show_email_footer' => 'bool', + 'email_alignment' => 'string', + 'auto_bill_standard_invoices' => 'bool', + 'postmark_secret' => 'string', + 'mailgun_secret' => 'string', + 'mailgun_domain' => 'string', + 'brevo_secret' => 'string', + 'send_email_on_mark_paid' => 'bool', + 'vendor_portal_enable_uploads' => 'bool', + 'besr_id' => 'string', + 'qr_iban' => 'string', + 'email_subject_purchase_order' => 'string', + 'email_template_purchase_order' => 'string', + 'require_purchase_order_signature' => 'bool', + 'purchase_order_public_notes' => 'string', + 'purchase_order_terms' => 'string', + 'purchase_order_design_id' => 'string', + 'purchase_order_footer' => 'string', + 'purchase_order_number_pattern' => 'string', + 'page_numbering_alignment' => 'string', + 'page_numbering' => 'bool', + 'auto_archive_invoice_cancelled' => 'bool', + 'email_from_name' => 'string', + 'show_all_tasks_client_portal' => 'string', + 'entity_send_time' => 'int', + 'shared_invoice_credit_counter' => 'bool', + 'reply_to_name' => 'string', + 'hide_empty_columns_on_pdf' => 'bool', + 'enable_reminder_endless' => 'bool', + 'use_credits_payment' => 'string', + 'recurring_invoice_number_pattern' => 'string', + 'recurring_invoice_number_counter' => 'int', 'client_portal_under_payment_minimum' => 'float', - 'auto_bill_date' => 'string', - 'primary_color' => 'string', - 'secondary_color' => 'string', - 'client_portal_allow_under_payment' => 'bool', - 'client_portal_allow_over_payment' => 'bool', - 'auto_bill' => 'string', - 'lock_invoices' => 'string', - 'client_portal_terms' => 'string', - 'client_portal_privacy_policy' => 'string', - 'client_can_register' => 'bool', - 'portal_design_id' => 'string', - 'late_fee_endless_percent' => 'float', - 'late_fee_endless_amount' => 'float', - 'auto_email_invoice' => 'bool', - 'reminder_send_time' => 'int', - 'email_sending_method' => 'string', - 'gmail_sending_user_id' => 'string', - 'counter_number_applied' => 'string', - 'quote_number_applied' => 'string', - 'email_subject_custom1' => 'string', - 'email_subject_custom2' => 'string', - 'email_subject_custom3' => 'string', - 'email_template_custom1' => 'string', - 'email_template_custom2' => 'string', - 'email_template_custom3' => 'string', - 'enable_reminder1' => 'bool', - 'enable_reminder2' => 'bool', - 'enable_reminder3' => 'bool', - 'num_days_reminder1' => 'int', - 'num_days_reminder2' => 'int', - 'num_days_reminder3' => 'int', - 'schedule_reminder1' => 'string', // (enum: after_invoice_date, before_due_date, after_due_date) - 'schedule_reminder2' => 'string', // (enum: after_invoice_date, before_due_date, after_due_date) - 'schedule_reminder3' => 'string', // (enum: after_invoice_date, before_due_date, after_due_date) - 'late_fee_amount1' => 'float', - 'late_fee_amount2' => 'float', - 'late_fee_amount3' => 'float', - 'late_fee_percent1' => 'float', - 'late_fee_percent2' => 'float', - 'late_fee_percent3' => 'float', - 'endless_reminder_frequency_id' => 'integer', + 'auto_bill_date' => 'string', + 'primary_color' => 'string', + 'secondary_color' => 'string', + 'client_portal_allow_under_payment' => 'bool', + 'client_portal_allow_over_payment' => 'bool', + 'auto_bill' => 'string', + 'lock_invoices' => 'string', + 'client_portal_terms' => 'string', + 'client_portal_privacy_policy' => 'string', + 'client_can_register' => 'bool', + 'portal_design_id' => 'string', + 'late_fee_endless_percent' => 'float', + 'late_fee_endless_amount' => 'float', + 'auto_email_invoice' => 'bool', + 'reminder_send_time' => 'int', + 'email_sending_method' => 'string', + 'gmail_sending_user_id' => 'string', + 'counter_number_applied' => 'string', + 'quote_number_applied' => 'string', + 'email_subject_custom1' => 'string', + 'email_subject_custom2' => 'string', + 'email_subject_custom3' => 'string', + 'email_template_custom1' => 'string', + 'email_template_custom2' => 'string', + 'email_template_custom3' => 'string', + 'enable_reminder1' => 'bool', + 'enable_reminder2' => 'bool', + 'enable_reminder3' => 'bool', + 'num_days_reminder1' => 'int', + 'num_days_reminder2' => 'int', + 'num_days_reminder3' => 'int', + 'schedule_reminder1' => 'string', // (enum: after_invoice_date, before_due_date, after_due_date) + 'schedule_reminder2' => 'string', // (enum: after_invoice_date, before_due_date, after_due_date) + 'schedule_reminder3' => 'string', // (enum: after_invoice_date, before_due_date, after_due_date) + 'late_fee_amount1' => 'float', + 'late_fee_amount2' => 'float', + 'late_fee_amount3' => 'float', + 'late_fee_percent1' => 'float', + 'late_fee_percent2' => 'float', + 'late_fee_percent3' => 'float', + 'endless_reminder_frequency_id' => 'integer', 'client_online_payment_notification' => 'bool', 'client_manual_payment_notification' => 'bool', - 'document_email_attachment' => 'bool', - 'enable_client_portal_password' => 'bool', - 'enable_email_markup' => 'bool', - 'enable_client_portal_dashboard' => 'bool', - 'enable_client_portal' => 'bool', - 'email_template_statement' => 'string', - 'email_subject_statement' => 'string', - 'signature_on_pdf' => 'bool', - 'quote_footer' => 'string', - 'page_size' => 'string', - 'page_layout' => 'string', - 'font_size' => 'int', - 'primary_font' => 'string', - 'secondary_font' => 'string', - 'hide_paid_to_date' => 'bool', - 'embed_documents' => 'bool', - 'all_pages_header' => 'bool', - 'all_pages_footer' => 'bool', - 'project_number_pattern' => 'string', - 'project_number_counter' => 'int', - 'task_number_pattern' => 'string', - 'task_number_counter' => 'int', - 'expense_number_pattern' => 'string', - 'expense_number_counter' => 'int', - 'recurring_expense_number_pattern' => 'string', - 'recurring_expense_number_counter' => 'int', - 'recurring_quote_number_pattern' => 'string', - 'recurring_quote_number_counter' => 'int', - 'vendor_number_pattern' => 'string', - 'vendor_number_counter' => 'int', - 'ticket_number_pattern' => 'string', - 'ticket_number_counter' => 'int', - 'payment_number_pattern' => 'string', - 'payment_number_counter' => 'int', - 'reply_to_email' => 'string', - 'bcc_email' => 'string', - 'pdf_email_attachment' => 'bool', - 'ubl_email_attachment' => 'bool', - 'email_style' => 'string', - 'email_style_custom' => 'string', - 'company_gateway_ids' => 'string', - 'address1' => 'string', - 'address2' => 'string', - 'city' => 'string', - 'company_logo' => 'string', - 'country_id' => 'string', - 'client_number_pattern' => 'string', - 'client_number_counter' => 'integer', - 'credit_number_pattern' => 'string', - 'credit_number_counter' => 'integer', - 'currency_id' => 'string', - 'custom_value1' => 'string', - 'custom_value2' => 'string', - 'custom_value3' => 'string', - 'custom_value4' => 'string', - 'custom_message_dashboard' => 'string', - 'custom_message_unpaid_invoice' => 'string', - 'custom_message_paid_invoice' => 'string', - 'custom_message_unapproved_quote' => 'string', - 'default_task_rate' => 'float', - 'email_signature' => 'string', - 'email_subject_invoice' => 'string', - 'email_subject_quote' => 'string', - 'email_subject_credit' => 'string', - 'email_subject_payment' => 'string', - 'email_subject_payment_partial' => 'string', - 'email_template_invoice' => 'string', - 'email_template_quote' => 'string', - 'email_template_credit' => 'string', - 'email_template_payment' => 'string', - 'email_template_payment_partial' => 'string', - 'email_subject_reminder1' => 'string', - 'email_subject_reminder2' => 'string', - 'email_subject_reminder3' => 'string', - 'email_subject_reminder_endless' => 'string', - 'email_template_reminder1' => 'string', - 'email_template_reminder2' => 'string', - 'email_template_reminder3' => 'string', - 'email_template_reminder_endless' => 'string', - 'inclusive_taxes' => 'bool', - 'invoice_number_pattern' => 'string', - 'invoice_number_counter' => 'integer', - 'invoice_design_id' => 'string', + 'document_email_attachment' => 'bool', + 'enable_client_portal_password' => 'bool', + 'enable_email_markup' => 'bool', + 'enable_client_portal_dashboard' => 'bool', + 'enable_client_portal' => 'bool', + 'email_template_statement' => 'string', + 'email_subject_statement' => 'string', + 'signature_on_pdf' => 'bool', + 'quote_footer' => 'string', + 'page_size' => 'string', + 'page_layout' => 'string', + 'font_size' => 'int', + 'primary_font' => 'string', + 'secondary_font' => 'string', + 'hide_paid_to_date' => 'bool', + 'embed_documents' => 'bool', + 'all_pages_header' => 'bool', + 'all_pages_footer' => 'bool', + 'project_number_pattern' => 'string', + 'project_number_counter' => 'int', + 'task_number_pattern' => 'string', + 'task_number_counter' => 'int', + 'expense_number_pattern' => 'string', + 'expense_number_counter' => 'int', + 'recurring_expense_number_pattern' => 'string', + 'recurring_expense_number_counter' => 'int', + 'recurring_quote_number_pattern' => 'string', + 'recurring_quote_number_counter' => 'int', + 'vendor_number_pattern' => 'string', + 'vendor_number_counter' => 'int', + 'ticket_number_pattern' => 'string', + 'ticket_number_counter' => 'int', + 'payment_number_pattern' => 'string', + 'payment_number_counter' => 'int', + 'reply_to_email' => 'string', + 'bcc_email' => 'string', + 'pdf_email_attachment' => 'bool', + 'ubl_email_attachment' => 'bool', + 'email_style' => 'string', + 'email_style_custom' => 'string', + 'company_gateway_ids' => 'string', + 'address1' => 'string', + 'address2' => 'string', + 'city' => 'string', + 'company_logo' => 'string', + 'country_id' => 'string', + 'client_number_pattern' => 'string', + 'client_number_counter' => 'integer', + 'credit_number_pattern' => 'string', + 'credit_number_counter' => 'integer', + 'currency_id' => 'string', + 'custom_value1' => 'string', + 'custom_value2' => 'string', + 'custom_value3' => 'string', + 'custom_value4' => 'string', + 'custom_message_dashboard' => 'string', + 'custom_message_unpaid_invoice' => 'string', + 'custom_message_paid_invoice' => 'string', + 'custom_message_unapproved_quote' => 'string', + 'default_task_rate' => 'float', + 'email_signature' => 'string', + 'email_subject_invoice' => 'string', + 'email_subject_quote' => 'string', + 'email_subject_credit' => 'string', + 'email_subject_payment' => 'string', + 'email_subject_payment_partial' => 'string', + 'email_template_invoice' => 'string', + 'email_template_quote' => 'string', + 'email_template_credit' => 'string', + 'email_template_payment' => 'string', + 'email_template_payment_partial' => 'string', + 'email_subject_reminder1' => 'string', + 'email_subject_reminder2' => 'string', + 'email_subject_reminder3' => 'string', + 'email_subject_reminder_endless' => 'string', + 'email_template_reminder1' => 'string', + 'email_template_reminder2' => 'string', + 'email_template_reminder3' => 'string', + 'email_template_reminder_endless' => 'string', + 'inclusive_taxes' => 'bool', + 'invoice_number_pattern' => 'string', + 'invoice_number_counter' => 'integer', + 'invoice_design_id' => 'string', // 'invoice_fields' => 'string', - 'invoice_taxes' => 'int', + 'invoice_taxes' => 'int', //'enabled_item_tax_rates' => 'int', - 'invoice_footer' => 'string', - 'invoice_labels' => 'string', - 'invoice_terms' => 'string', - 'credit_footer' => 'string', - 'credit_terms' => 'string', - 'name' => 'string', - 'payment_terms' => 'string', - 'payment_type_id' => 'string', - 'phone' => 'string', - 'postal_code' => 'string', - 'quote_design_id' => 'string', - 'credit_design_id' => 'string', - 'quote_number_pattern' => 'string', - 'quote_number_counter' => 'integer', - 'quote_terms' => 'string', - 'recurring_number_prefix' => 'string', - 'reset_counter_frequency_id' => 'integer', - 'reset_counter_date' => 'string', - 'require_invoice_signature' => 'bool', - 'require_quote_signature' => 'bool', - 'state' => 'string', - 'email' => 'string', - 'vat_number' => 'string', - 'id_number' => 'string', - 'tax_name1' => 'string', - 'tax_name2' => 'string', - 'tax_name3' => 'string', - 'tax_rate1' => 'float', - 'tax_rate2' => 'float', - 'tax_rate3' => 'float', - 'show_accept_quote_terms' => 'bool', - 'show_accept_invoice_terms' => 'bool', - 'timezone_id' => 'string', - 'valid_until' => 'string', - 'date_format_id' => 'string', - 'military_time' => 'bool', - 'language_id' => 'string', - 'show_currency_code' => 'bool', - 'send_reminders' => 'bool', - 'enable_client_portal_tasks' => 'bool', - 'auto_archive_invoice' => 'bool', - 'auto_archive_quote' => 'bool', - 'auto_convert_quote' => 'bool', - 'shared_invoice_quote_counter' => 'bool', - 'counter_padding' => 'integer', + 'invoice_footer' => 'string', + 'invoice_labels' => 'string', + 'invoice_terms' => 'string', + 'credit_footer' => 'string', + 'credit_terms' => 'string', + 'name' => 'string', + 'payment_terms' => 'string', + 'payment_type_id' => 'string', + 'phone' => 'string', + 'postal_code' => 'string', + 'quote_design_id' => 'string', + 'credit_design_id' => 'string', + 'quote_number_pattern' => 'string', + 'quote_number_counter' => 'integer', + 'quote_terms' => 'string', + 'recurring_number_prefix' => 'string', + 'reset_counter_frequency_id' => 'integer', + 'reset_counter_date' => 'string', + 'require_invoice_signature' => 'bool', + 'require_quote_signature' => 'bool', + 'state' => 'string', + 'email' => 'string', + 'vat_number' => 'string', + 'id_number' => 'string', + 'tax_name1' => 'string', + 'tax_name2' => 'string', + 'tax_name3' => 'string', + 'tax_rate1' => 'float', + 'tax_rate2' => 'float', + 'tax_rate3' => 'float', + 'show_accept_quote_terms' => 'bool', + 'show_accept_invoice_terms' => 'bool', + 'timezone_id' => 'string', + 'valid_until' => 'string', + 'date_format_id' => 'string', + 'military_time' => 'bool', + 'language_id' => 'string', + 'show_currency_code' => 'bool', + 'send_reminders' => 'bool', + 'enable_client_portal_tasks' => 'bool', + 'auto_archive_invoice' => 'bool', + 'auto_archive_quote' => 'bool', + 'auto_convert_quote' => 'bool', + 'shared_invoice_quote_counter' => 'bool', + 'counter_padding' => 'integer', //'design' => 'string', - 'website' => 'string', - 'pdf_variables' => 'object', - 'portal_custom_head' => 'string', - 'portal_custom_css' => 'string', - 'portal_custom_footer' => 'string', - 'portal_custom_js' => 'string', - 'client_portal_enable_uploads' => 'bool', - 'purchase_order_number_counter' => 'integer', + 'website' => 'string', + 'pdf_variables' => 'object', + 'portal_custom_head' => 'string', + 'portal_custom_css' => 'string', + 'portal_custom_footer' => 'string', + 'portal_custom_js' => 'string', + 'client_portal_enable_uploads' => 'bool', + 'purchase_order_number_counter' => 'integer', ]; public static $free_plan_casts = [ - 'currency_id' => 'string', - 'company_gateway_ids' => 'string', - 'address1' => 'string', - 'address2' => 'string', - 'city' => 'string', - 'company_logo' => 'string', - 'country_id' => 'string', - 'custom_value1' => 'string', - 'custom_value2' => 'string', - 'custom_value3' => 'string', - 'custom_value4' => 'string', - 'inclusive_taxes' => 'bool', - 'name' => 'string', - 'payment_terms' => 'string', - 'payment_type_id' => 'string', - 'phone' => 'string', - 'postal_code' => 'string', - 'state' => 'string', - 'email' => 'string', - 'vat_number' => 'string', - 'id_number' => 'string', - 'tax_name1' => 'string', - 'tax_name2' => 'string', - 'tax_name3' => 'string', - 'tax_rate1' => 'float', - 'tax_rate2' => 'float', - 'tax_rate3' => 'float', - 'timezone_id' => 'string', - 'date_format_id' => 'string', - 'military_time' => 'bool', - 'language_id' => 'string', - 'show_currency_code' => 'bool', - 'website' => 'string', - 'default_task_rate' => 'float', + 'currency_id' => 'string', + 'company_gateway_ids' => 'string', + 'address1' => 'string', + 'address2' => 'string', + 'city' => 'string', + 'company_logo' => 'string', + 'country_id' => 'string', + 'custom_value1' => 'string', + 'custom_value2' => 'string', + 'custom_value3' => 'string', + 'custom_value4' => 'string', + 'inclusive_taxes' => 'bool', + 'name' => 'string', + 'payment_terms' => 'string', + 'payment_type_id' => 'string', + 'phone' => 'string', + 'postal_code' => 'string', + 'state' => 'string', + 'email' => 'string', + 'vat_number' => 'string', + 'id_number' => 'string', + 'tax_name1' => 'string', + 'tax_name2' => 'string', + 'tax_name3' => 'string', + 'tax_rate1' => 'float', + 'tax_rate2' => 'float', + 'tax_rate3' => 'float', + 'timezone_id' => 'string', + 'date_format_id' => 'string', + 'military_time' => 'bool', + 'language_id' => 'string', + 'show_currency_code' => 'bool', + 'website' => 'string', + 'default_task_rate' => 'float', ]; /** @@ -848,7 +857,7 @@ class CompanySettings extends BaseSettings $company_settings = (object) get_class_vars(self::class); foreach ($company_settings as $key => $value) { - if (! property_exists($settings, $key)) { + if (!property_exists($settings, $key)) { $settings->{$key} = self::castAttribute($key, $company_settings->{$key}); } } @@ -881,7 +890,7 @@ class CompanySettings extends BaseSettings { $notification = new stdClass(); $notification->email = []; - $notification->email = ['invoice_sent_all','payment_success_all','payment_manual_all']; + $notification->email = ['invoice_sent_all', 'payment_success_all', 'payment_manual_all']; return $notification; } diff --git a/app/DataMapper/Settings/SettingsData.php b/app/DataMapper/Settings/SettingsData.php index 189864a27e47..14ddcf123f3a 100644 --- a/app/DataMapper/Settings/SettingsData.php +++ b/app/DataMapper/Settings/SettingsData.php @@ -213,7 +213,7 @@ class SettingsData public bool $show_accept_quote_terms = false; //@TODO ben to confirm - public string $email_sending_method = 'default'; // enum 'default', 'gmail', 'office365', 'client_postmark', 'client_mailgun' //@implemented + public string $email_sending_method = 'default'; // enum 'default', 'gmail', 'office365', 'client_postmark', 'client_mailgun' , 'client_brevo' //@implemented public string $gmail_sending_user_id = '0'; //@implemented @@ -433,6 +433,8 @@ class SettingsData public string $mailgun_endpoint = 'api.mailgun.net'; // api.eu.mailgun.net + public string $brevo_secret = ''; + public bool $auto_bill_standard_invoices = false; public string $email_alignment = 'center'; // center, left, right @@ -469,8 +471,8 @@ class SettingsData public function cast(mixed $object) { - if(is_array($object)) { - $object = (object)$object; + if (is_array($object)) { + $object = (object) $object; } if (is_object($object)) { @@ -478,9 +480,9 @@ class SettingsData try { settype($object->{$key}, gettype($this->{$key})); - } catch(\Exception | \Error | \Throwable $e) { + } catch (\Exception | \Error | \Throwable $e) { - if(property_exists($this, $key)) { + if (property_exists($this, $key)) { $object->{$key} = $this->{$key}; } else { unset($object->{$key}); @@ -506,11 +508,11 @@ class SettingsData public function toObject(): object { - return (object)$this->object; + return (object) $this->object; } public function toArray(): array { - return (array)$this->object; + return (array) $this->object; } } diff --git a/app/DataProviders/SMSNumbers.php b/app/DataProviders/SMSNumbers.php new file mode 100644 index 000000000000..0b777f477714 --- /dev/null +++ b/app/DataProviders/SMSNumbers.php @@ -0,0 +1,88398 @@ + 'item.line_total', 'gross_line_total' => 'item.gross_line_total', 'tax_amount' => 'item.tax_amount', + 'product_cost' => 'item.product_cost' ]; protected array $quote_report_keys = [ @@ -426,8 +427,11 @@ class BaseExport protected array $task_report_keys = [ 'start_date' => 'task.start_date', + 'start_time' => 'task.start_time', 'end_date' => 'task.end_date', + 'end_time' => 'task.end_time', 'duration' => 'task.duration', + 'duration_words' => 'task.duration_words', 'rate' => 'task.rate', 'number' => 'task.number', 'description' => 'task.description', diff --git a/app/Export/CSV/TaskExport.php b/app/Export/CSV/TaskExport.php index 33685b5092c2..e70609307bdb 100644 --- a/app/Export/CSV/TaskExport.php +++ b/app/Export/CSV/TaskExport.php @@ -11,18 +11,19 @@ namespace App\Export\CSV; -use App\Export\Decorators\Decorator; -use App\Libraries\MultiDB; -use App\Models\Company; -use App\Models\DateFormat; use App\Models\Task; -use App\Models\Timezone; -use App\Transformers\TaskTransformer; use App\Utils\Ninja; -use Illuminate\Database\Eloquent\Builder; +use League\Csv\Writer; +use App\Models\Company; +use App\Models\Timezone; +use App\Libraries\MultiDB; +use App\Models\DateFormat; +use Carbon\CarbonInterval; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\App; -use League\Csv\Writer; +use App\Export\Decorators\Decorator; +use App\Transformers\TaskTransformer; +use Illuminate\Database\Eloquent\Builder; class TaskExport extends BaseExport { @@ -177,19 +178,26 @@ class TaskExport extends BaseExport foreach ($logs as $key => $item) { if (in_array('task.start_date', $this->input['report_keys']) || in_array('start_date', $this->input['report_keys'])) { - $entity['task.start_date'] = Carbon::createFromTimeStamp($item[0])->setTimezone($timezone_name)->format($date_format_default); + $carbon_object = Carbon::createFromTimeStamp($item[0])->setTimezone($timezone_name); + $entity['task.start_date'] = $carbon_object->format($date_format_default); + $entity['task.start_time'] = $carbon_object->format('H:i:s'); } if ((in_array('task.end_date', $this->input['report_keys']) || in_array('end_date', $this->input['report_keys'])) && $item[1] > 0) { - $entity['task.end_date'] = Carbon::createFromTimeStamp($item[1])->setTimezone($timezone_name)->format($date_format_default); + $carbon_object = Carbon::createFromTimeStamp($item[1])->setTimezone($timezone_name); + $entity['task.end_date'] = $carbon_object->format($date_format_default); + $entity['task.end_time'] = $carbon_object->format('H:i:s'); } if ((in_array('task.end_date', $this->input['report_keys']) || in_array('end_date', $this->input['report_keys'])) && $item[1] == 0) { $entity['task.end_date'] = ctrans('texts.is_running'); + $entity['task.end_time'] = ctrans('texts.is_running'); } if (in_array('task.duration', $this->input['report_keys']) || in_array('duration', $this->input['report_keys'])) { - $entity['task.duration'] = $task->calcDuration(); + $seconds = $task->calcDuration(); + $entity['task.duration'] = $seconds; + $entity['task.duration_words'] = CarbonInterval::seconds($seconds)->locale($this->company->locale())->cascade()->forHumans(); } $entity = $this->decorateAdvancedFields($task, $entity); @@ -197,8 +205,12 @@ class TaskExport extends BaseExport $this->storage_array[] = $entity; $entity['task.start_date'] = ''; + $entity['task.start_time'] = ''; $entity['task.end_date'] = ''; + $entity['task.end_time'] = ''; $entity['task.duration'] = ''; + $entity['task.duration_words'] = ''; + } } diff --git a/app/Export/Decorators/InvoiceDecorator.php b/app/Export/Decorators/InvoiceDecorator.php index 5ce6d936c6ce..8c308f55df52 100644 --- a/app/Export/Decorators/InvoiceDecorator.php +++ b/app/Export/Decorators/InvoiceDecorator.php @@ -23,7 +23,7 @@ class InvoiceDecorator extends Decorator implements DecoratorInterface $invoice = $entity; } elseif($entity->invoice) { $invoice = $entity->invoice; - } elseif($entity->invoices()->exists()) { + } elseif(method_exists($entity, 'invoices') && $entity->invoices()->exists()) { $invoice = $entity->invoices()->first(); } diff --git a/app/Filters/DesignFilters.php b/app/Filters/DesignFilters.php index 69c6cf60df55..45071226a161 100644 --- a/app/Filters/DesignFilters.php +++ b/app/Filters/DesignFilters.php @@ -58,7 +58,10 @@ class DesignFilters extends QueryFilters public function entities(string $entities = ''): Builder { - + + if(stripos($entities, 'statement') !== false) + $entities = 'client'; + if (strlen($entities) == 0 || str_contains($entities, ',')) { return $this->builder; } diff --git a/app/Filters/InvoiceFilters.php b/app/Filters/InvoiceFilters.php index c1b41dd4b7e2..0d6d2ff2470a 100644 --- a/app/Filters/InvoiceFilters.php +++ b/app/Filters/InvoiceFilters.php @@ -200,14 +200,16 @@ class InvoiceFilters extends QueryFilters */ public function payable(string $client_id = ''): Builder { + if (strlen($client_id) == 0) { return $this->builder; } - return $this->builder->whereIn('status_id', [Invoice::STATUS_DRAFT, Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL]) - ->where('balance', '>', 0) - ->where('is_deleted', 0) - ->where('client_id', $this->decodePrimaryKey($client_id)); + return $this->builder + ->where('client_id', $this->decodePrimaryKey($client_id)) + ->whereIn('status_id', [Invoice::STATUS_DRAFT, Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL]) + ->where('is_deleted', 0) + ->where('balance', '>', 0); } @@ -329,7 +331,9 @@ class InvoiceFilters extends QueryFilters if($sort_col[0] == 'number') { // return $this->builder->orderByRaw('CAST(number AS UNSIGNED), number ' . $dir); - return $this->builder->orderByRaw('ABS(number) ' . $dir); + // return $this->builder->orderByRaw("number REGEXP '^[A-Za-z]+$',CAST(number as SIGNED INTEGER),CAST(REPLACE(number,'-','')AS SIGNED INTEGER) ,number"); + // return $this->builder->orderByRaw('ABS(number) ' . $dir); + return $this->builder->orderByRaw("REGEXP_REPLACE(number,'[^0-9]+','')+0 " . $dir); } return $this->builder->orderBy($sort_col[0], $dir); diff --git a/app/Helpers/Invoice/InvoiceSum.php b/app/Helpers/Invoice/InvoiceSum.php index d6379356cd11..52cd745418e6 100644 --- a/app/Helpers/Invoice/InvoiceSum.php +++ b/app/Helpers/Invoice/InvoiceSum.php @@ -52,6 +52,7 @@ class InvoiceSum public InvoiceItemSum $invoice_items; + private $rappen_rounding = false; /** * Constructs the object with Invoice and Settings object. * @@ -63,8 +64,11 @@ class InvoiceSum if ($this->invoice->client) { $this->precision = $this->invoice->client->currency()->precision; + $this->rappen_rounding = $this->invoice->client->getSetting('enable_rappen_rounding'); } else { $this->precision = $this->invoice->vendor->currency()->precision; + $this->rappen_rounding = $this->invoice->vendor->getSetting('enable_rappen_rounding'); + } $this->tax_map = new Collection(); @@ -242,8 +246,6 @@ class InvoiceSum if ($this->invoice->status_id != Invoice::STATUS_DRAFT) { if ($this->invoice->amount != $this->invoice->balance) { - // $paid_to_date = $this->invoice->amount - $this->invoice->balance; - $this->invoice->balance = Number::roundValue($this->getTotal(), $this->precision) - $this->invoice->paid_to_date; //21-02-2024 cannot use the calculated $paid_to_date here as it could send the balance backward. } else { $this->invoice->balance = Number::roundValue($this->getTotal(), $this->precision); @@ -252,11 +254,22 @@ class InvoiceSum /* Set new calculated total */ $this->invoice->amount = $this->formatValue($this->getTotal(), $this->precision); + if($this->rappen_rounding){ + $this->invoice->amount = $this->roundRappen($this->invoice->amount); + $this->invoice->balance = $this->roundRappen($this->invoice->balance); + } + $this->invoice->total_taxes = $this->getTotalTaxes(); return $this; } + + function roundRappen($value): float + { + return round($value / .05, 0) * .05; + } + public function getSubTotal() { return $this->sub_total; diff --git a/app/Helpers/Invoice/InvoiceSumInclusive.php b/app/Helpers/Invoice/InvoiceSumInclusive.php index 3786b55f99e6..aae9c0848198 100644 --- a/app/Helpers/Invoice/InvoiceSumInclusive.php +++ b/app/Helpers/Invoice/InvoiceSumInclusive.php @@ -47,6 +47,8 @@ class InvoiceSumInclusive private $precision; + private $rappen_rounding = false; + public InvoiceItemSumInclusive $invoice_items; /** * Constructs the object with Invoice and Settings object. @@ -59,8 +61,10 @@ class InvoiceSumInclusive if ($this->invoice->client) { $this->precision = $this->invoice->client->currency()->precision; + $this->rappen_rounding = $this->invoice->client->getSetting('enable_rappen_rounding'); } else { $this->precision = $this->invoice->vendor->currency()->precision; + $this->rappen_rounding = $this->invoice->vendor->getSetting('enable_rappen_rounding'); } $this->tax_map = new Collection(); @@ -270,10 +274,20 @@ class InvoiceSumInclusive /* Set new calculated total */ $this->invoice->amount = $this->formatValue($this->getTotal(), $this->precision); + if($this->rappen_rounding) { + $this->invoice->amount = $this->roundRappen($this->invoice->amount); + $this->invoice->balance = $this->roundRappen($this->invoice->balance); + } + $this->invoice->total_taxes = $this->getTotalTaxes(); return $this; } + + function roundRappen($value): float + { + return round($value / .05, 0) * .05; + } public function getSubTotal() { diff --git a/app/Http/Controllers/Auth/ContactLoginController.php b/app/Http/Controllers/Auth/ContactLoginController.php index 42add3851db3..6060dabae268 100644 --- a/app/Http/Controllers/Auth/ContactLoginController.php +++ b/app/Http/Controllers/Auth/ContactLoginController.php @@ -43,7 +43,7 @@ class ContactLoginController extends Controller if ($request->session()->has('company_key')) { MultiDB::findAndSetDbByCompanyKey($request->session()->get('company_key')); - $company = Company::where('company_key', $request->input('company_key'))->first(); + $company = Company::where('company_key', $request->session()->get('company_key'))->first(); } elseif ($request->has('company_key')) { MultiDB::findAndSetDbByCompanyKey($request->input('company_key')); $company = Company::where('company_key', $request->input('company_key'))->first(); diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 61f52cafa0dc..7657f889caaa 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -390,13 +390,20 @@ class LoginController extends BaseController $truth->setUser($user); $truth->setCompany($set_company); - $user->account->companies->each(function ($company) use ($user) { - if ($company->tokens()->where('is_system', true)->count() == 0) { - (new CreateCompanyToken($company, $user, request()->server('HTTP_USER_AGENT')))->handle(); + //21-03-2024 + $cu->each(function ($cu){ + if(CompanyToken::where('company_id', $cu->company_id)->where('user_id', $cu->user_id)->where('is_system', true)->doesntExist()){ + (new CreateCompanyToken($cu->company, $cu->user, request()->server('HTTP_USER_AGENT')))->handle(); } }); - $truth->setCompanyToken(CompanyToken::where('user_id', $user->id)->where('company_id', $set_company->id)->first()); + // $user->account->companies->each(function ($company) use ($user) { + // if ($company->tokens()->where('user_id',$user->id)->where('is_system', true)->count() == 0) { + // (new CreateCompanyToken($company, $user, request()->server('HTTP_USER_AGENT')))->handle(); + // } + // }); + + $truth->setCompanyToken(CompanyToken::where('user_id', $user->id)->where('company_id', $set_company->id)->where('is_system', true)->first()); return CompanyUser::query()->where('user_id', $user->id); } diff --git a/app/Http/Controllers/Bank/YodleeController.php b/app/Http/Controllers/Bank/YodleeController.php index e6a154060d51..8fd10c95dec8 100644 --- a/app/Http/Controllers/Bank/YodleeController.php +++ b/app/Http/Controllers/Bank/YodleeController.php @@ -90,14 +90,13 @@ class YodleeController extends BaseController $bank_integration->balance = $account['current_balance']; $bank_integration->currency = $account['account_currency']; $bank_integration->from_date = now()->subYear(); - + $bank_integration->integration_type = BankIntegration::INTEGRATION_TYPE_YODLEE; $bank_integration->auto_sync = true; $bank_integration->save(); } } - $company->account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_YODLEE)->where('auto_sync', true)->each(function ($bank_integration) use ($company) { // TODO: filter to yodlee only ProcessBankTransactionsYodlee::dispatch($company->account->id, $bank_integration); }); diff --git a/app/Http/Controllers/BrevoController.php b/app/Http/Controllers/BrevoController.php new file mode 100644 index 000000000000..e1dce175f485 --- /dev/null +++ b/app/Http/Controllers/BrevoController.php @@ -0,0 +1,72 @@ +has('token') && $request->get('token') == config('services.brevo.key')) { + ProcessBrevoWebhook::dispatch($request->all())->delay(10); + + return response()->json(['message' => 'Success'], 200); + } + + return response()->json(['message' => 'Unauthorized'], 403); + } +} diff --git a/app/Http/Controllers/CompanyGatewayController.php b/app/Http/Controllers/CompanyGatewayController.php index 6c58f090a248..8d69b3738a1c 100644 --- a/app/Http/Controllers/CompanyGatewayController.php +++ b/app/Http/Controllers/CompanyGatewayController.php @@ -11,26 +11,29 @@ namespace App\Http\Controllers; +use App\Models\Client; +use App\Libraries\MultiDB; +use Illuminate\Http\Response; +use App\Models\CompanyGateway; +use App\Utils\Traits\MakesHash; use App\DataMapper\FeesAndLimits; +use App\Jobs\Util\ApplePayDomain; +use Illuminate\Support\Facades\Cache; use App\Factory\CompanyGatewayFactory; use App\Filters\CompanyGatewayFilters; +use App\Repositories\CompanyRepository; +use Illuminate\Foundation\Bus\DispatchesJobs; +use App\Transformers\CompanyGatewayTransformer; +use App\PaymentDrivers\Stripe\Jobs\StripeWebhook; +use App\PaymentDrivers\CheckoutCom\CheckoutSetupWebhook; use App\Http\Requests\CompanyGateway\BulkCompanyGatewayRequest; -use App\Http\Requests\CompanyGateway\CreateCompanyGatewayRequest; -use App\Http\Requests\CompanyGateway\DestroyCompanyGatewayRequest; use App\Http\Requests\CompanyGateway\EditCompanyGatewayRequest; use App\Http\Requests\CompanyGateway\ShowCompanyGatewayRequest; +use App\Http\Requests\CompanyGateway\TestCompanyGatewayRequest; use App\Http\Requests\CompanyGateway\StoreCompanyGatewayRequest; +use App\Http\Requests\CompanyGateway\CreateCompanyGatewayRequest; use App\Http\Requests\CompanyGateway\UpdateCompanyGatewayRequest; -use App\Jobs\Util\ApplePayDomain; -use App\Models\Client; -use App\Models\CompanyGateway; -use App\PaymentDrivers\CheckoutCom\CheckoutSetupWebhook; -use App\PaymentDrivers\Stripe\Jobs\StripeWebhook; -use App\Repositories\CompanyRepository; -use App\Transformers\CompanyGatewayTransformer; -use App\Utils\Traits\MakesHash; -use Illuminate\Foundation\Bus\DispatchesJobs; -use Illuminate\Http\Response; +use App\Http\Requests\CompanyGateway\DestroyCompanyGatewayRequest; /** * Class CompanyGatewayController. @@ -52,6 +55,9 @@ class CompanyGatewayController extends BaseController private string $checkout_key = '3758e7f7c6f4cecf0f4f348b9a00f456'; + private string $forte_key = 'kivcvjexxvdiyqtj3mju5d6yhpeht2xs'; + + /** * CompanyGatewayController constructor. * @param CompanyRepository $company_repo @@ -225,6 +231,13 @@ class CompanyGatewayController extends BaseController StripeWebhook::dispatch($company_gateway->company->company_key, $company_gateway->id); } elseif($company_gateway->gateway_key == $this->checkout_key) { CheckoutSetupWebhook::dispatch($company_gateway->company->company_key, $company_gateway->id); + } elseif($company_gateway->gateway_key == $this->forte_key) { + + dispatch(function () use ($company_gateway) { + MultiDB::setDb($company_gateway->company->db); + $company_gateway->driver()->updateFees(); + })->afterResponse(); + } return $this->itemResponse($company_gateway); @@ -407,6 +420,13 @@ class CompanyGatewayController extends BaseController if($company_gateway->gateway_key == $this->checkout_key) { CheckoutSetupWebhook::dispatch($company_gateway->company->company_key, $company_gateway->fresh()->id); + }elseif($company_gateway->gateway_key == $this->forte_key){ + + dispatch(function () use ($company_gateway) { + MultiDB::setDb($company_gateway->company->db); + $company_gateway->driver()->updateFees(); + })->afterResponse(); + } return $this->itemResponse($company_gateway); @@ -535,4 +555,28 @@ class CompanyGatewayController extends BaseController return $this->listResponse(CompanyGateway::withTrashed()->company()->whereIn('id', $request->ids)); } + + public function test(TestCompanyGatewayRequest $request, CompanyGateway $company_gateway) + { + + return response()->json(['message' => $company_gateway->driver()->auth() ? 'true' : 'false'], 200); + + } + + public function importCustomers(TestCompanyGatewayRequest $request, CompanyGateway $company_gateway) + { + //Throttle here + // if (Cache::get("throttle_polling:import_customers:{$company_gateway->company->company_key}:{$company_gateway->hashed_id}")) + // return response()->json(['message' => ctrans('texts.import_started')], 200); + + dispatch(function () use($company_gateway) { + MultiDB::setDb($company_gateway->company->db); + $company_gateway->driver()->importCustomers(); + })->afterResponse(); + + Cache::put("throttle_polling:import_customers:{$company_gateway->company->company_key}:{$company_gateway->hashed_id}", true, 300); + + return response()->json(['message' => ctrans('texts.import_started')], 200); + } + } diff --git a/app/Http/Controllers/CreditController.php b/app/Http/Controllers/CreditController.php index 3eedd8fe2ffc..e0db0722c133 100644 --- a/app/Http/Controllers/CreditController.php +++ b/app/Http/Controllers/CreditController.php @@ -727,6 +727,74 @@ class CreditController extends BaseController }, $credit->numberFormatter() . '.pdf', $headers); } + /** + * @OA\Get( + * path="/api/v1/credit/{invitation_key}/download_e_credit", + * operationId="downloadXcredit", + * tags={"credit"}, + * summary="Download a specific x-credit by invitation key", + * description="Downloads a specific x-credit", + * @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Parameter( + * name="invitation_key", + * in="path", + * description="The credit Invitation Key", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns the x-credit pdf", + * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + * @param $invitation_key + * @return \Symfony\Component\HttpFoundation\BinaryFileResponse + */ + public function downloadECredit($invitation_key) + { + $invitation = $this->credit_repository->getInvitationByKey($invitation_key); + + if (! $invitation) { + return response()->json(['message' => 'no record found'], 400); + } + + $contact = $invitation->contact; + $credit = $invitation->credit; + + $file = $credit->service()->getEInvoice($contact); + $file_name = $credit->getFileName("xml"); + + $headers = ['Content-Type' => 'application/xml']; + + if (request()->input('inline') == 'true') { + $headers = array_merge($headers, ['Content-Disposition' => 'inline']); + } + + return response()->streamDownload(function () use ($file) { + echo $file; + }, $file_name, $headers); + } + /** * Update the specified resource in storage. diff --git a/app/Http/Controllers/LogoutController.php b/app/Http/Controllers/LogoutController.php index 2889b777d0dd..423a382e71e1 100644 --- a/app/Http/Controllers/LogoutController.php +++ b/app/Http/Controllers/LogoutController.php @@ -63,8 +63,12 @@ class LogoutController extends BaseController $ct->company ->tokens() ->where('is_system', true) - ->forceDelete(); - + ->cursor() + ->each(function ($ct){ + $ct->token = \Illuminate\Support\Str::random(64); + $ct->save(); + }); + return response()->json(['message' => 'All tokens deleted'], 200); } } diff --git a/app/Http/Controllers/PurchaseOrderController.php b/app/Http/Controllers/PurchaseOrderController.php index 12b8cb143da5..1682380e02f1 100644 --- a/app/Http/Controllers/PurchaseOrderController.php +++ b/app/Http/Controllers/PurchaseOrderController.php @@ -851,4 +851,71 @@ class PurchaseOrderController extends BaseController echo $file; }, $purchase_order->numberFormatter().".pdf", $headers); } + /** + * @OA\Get( + * path="/api/v1/credit/{invitation_key}/download_e_purchase_order", + * operationId="downloadEPurchaseOrder", + * tags={"purchase_orders"}, + * summary="Download a specific E-Purchase-Order by invitation key", + * description="Downloads a specific E-Purchase-Order", + * @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Parameter( + * name="invitation_key", + * in="path", + * description="The E-Purchase-Order Invitation Key", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns the E-Purchase-Order pdf", + * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + * @param $invitation_key + * @return \Symfony\Component\HttpFoundation\BinaryFileResponse + */ + public function downloadEPurchaseOrder($invitation_key) + { + $invitation = $this->purchase_order_repository->getInvitationByKey($invitation_key); + + if (! $invitation) { + return response()->json(['message' => 'no record found'], 400); + } + + $contact = $invitation->contact; + $purchase_order = $invitation->purchase_order; + + $file = $purchase_order->service()->getEPurchaseOrder($contact); + $file_name = $purchase_order->getFileName("xml"); + + $headers = ['Content-Type' => 'application/xml']; + + if (request()->input('inline') == 'true') { + $headers = array_merge($headers, ['Content-Disposition' => 'inline']); + } + + return response()->streamDownload(function () use ($file) { + echo $file; + }, $file_name, $headers); + } } diff --git a/app/Http/Controllers/QuoteController.php b/app/Http/Controllers/QuoteController.php index 78a5bf625247..1ac67895f009 100644 --- a/app/Http/Controllers/QuoteController.php +++ b/app/Http/Controllers/QuoteController.php @@ -860,6 +860,75 @@ class QuoteController extends BaseController } + /** + * @OA\Get( + * path="/api/v1/invoice/{invitation_key}/download_e_quote", + * operationId="downloadXQuote", + * tags={"quotes"}, + * summary="Download a specific x-quote by invitation key", + * description="Downloads a specific x-quote", + * @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"), + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Parameter(ref="#/components/parameters/include"), + * @OA\Parameter( + * name="invitation_key", + * in="path", + * description="The Quote Invitation Key", + * example="D2J234DFA", + * required=true, + * @OA\Schema( + * type="string", + * format="string", + * ), + * ), + * @OA\Response( + * response=200, + * description="Returns the x-quote pdf", + * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + * @param $invitation_key + * @return \Symfony\Component\HttpFoundation\BinaryFileResponse + */ + public function downloadEQuote($invitation_key) + { + $invitation = $this->quote_repo->getInvitationByKey($invitation_key); + + if (! $invitation) { + return response()->json(['message' => 'no record found'], 400); + } + + $contact = $invitation->contact; + $quote = $invitation->quote; + + $file = $quote->service()->getEInvoice($contact); + $file_name = $quote->getFileName("xml"); + + $headers = ['Content-Type' => 'application/xml']; + + if (request()->input('inline') == 'true') { + $headers = array_merge($headers, ['Content-Disposition' => 'inline']); + } + + return response()->streamDownload(function () use ($file) { + echo $file; + }, $file_name, $headers); + } + + /** * Update the specified resource in storage. * diff --git a/app/Http/Controllers/SmtpController.php b/app/Http/Controllers/SmtpController.php index edfc24c38689..629a92bc1b48 100644 --- a/app/Http/Controllers/SmtpController.php +++ b/app/Http/Controllers/SmtpController.php @@ -55,7 +55,17 @@ class SmtpController extends BaseController (new \Illuminate\Mail\MailServiceProvider(app()))->register(); try { - Mail::mailer('smtp')->to($user->email, $user->present()->name())->send(new TestMailServer('Email Server Works!', strlen($company->settings->custom_sending_email) > 1 ? $company->settings->custom_sending_email : $user->email)); + + $sending_email = (isset($company->settings->custom_sending_email) && stripos($company->settings->custom_sending_email, "@")) ? $company->settings->custom_sending_email : $user->email; + $sending_user = (isset($company->settings->email_from_name) && strlen($company->settings->email_from_name) > 2) ? $company->settings->email_from_name : $user->name(); + + $mailable = new TestMailServer('Email Server Works!', $sending_email); + $mailable->from($sending_email,$sending_user); + + Mail::mailer('smtp') + ->to($user->email, $user->present()->name()) + ->send($mailable); + } catch (\Exception $e) { app('mail.manager')->forgetMailers(); return response()->json(['message' => $e->getMessage()], 400); diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 8ff9f3070a99..f8ffe75fb6e5 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -260,8 +260,8 @@ class UserController extends BaseController /** @var \App\Models\User $logged_in_user */ $logged_in_user = auth()->user(); - $company_user = CompanyUser::whereUserId($user->id) - ->whereCompanyId($logged_in_user->companyId()) + $company_user = CompanyUser::where('user_id',$user->id) + ->where('company_id', $logged_in_user->companyId()) ->withTrashed() ->first(); @@ -269,14 +269,10 @@ class UserController extends BaseController return response()->json(['message', 'Cannot detach owner.'], 401); } - $token = $company_user->token->where('company_id', $company_user->company_id)->where('user_id', $company_user->user_id)->first(); - - if ($token) { - $token->delete(); - } + $company_user->tokens()->where('company_id', $company_user->company_id)->where('user_id', $company_user->user_id)->forceDelete(); if ($company_user) { - $company_user->delete(); + $company_user->forceDelete(); } return response()->json(['message' => ctrans('texts.user_detached')], 200); diff --git a/app/Http/Requests/BankIntegration/UploadBankIntegrationRequest.php b/app/Http/Requests/BankIntegration/UploadBankIntegrationRequest.php index 560a44d02c74..700e3e47daa8 100644 --- a/app/Http/Requests/BankIntegration/UploadBankIntegrationRequest.php +++ b/app/Http/Requests/BankIntegration/UploadBankIntegrationRequest.php @@ -33,15 +33,15 @@ class UploadBankIntegrationRequest extends Request $rules = []; if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } $rules['is_public'] = 'sometimes|boolean'; diff --git a/app/Http/Requests/BankTransaction/UploadBankTransactionRequest.php b/app/Http/Requests/BankTransaction/UploadBankTransactionRequest.php index 9c014cc008cb..c6b88410a929 100644 --- a/app/Http/Requests/BankTransaction/UploadBankTransactionRequest.php +++ b/app/Http/Requests/BankTransaction/UploadBankTransactionRequest.php @@ -33,15 +33,15 @@ class UploadBankTransactionRequest extends Request $rules = []; if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } $rules['is_public'] = 'sometimes|boolean'; diff --git a/app/Http/Requests/Client/StoreClientRequest.php b/app/Http/Requests/Client/StoreClientRequest.php index a0d807d7d6dd..d3b4d3982e0a 100644 --- a/app/Http/Requests/Client/StoreClientRequest.php +++ b/app/Http/Requests/Client/StoreClientRequest.php @@ -45,18 +45,18 @@ class StoreClientRequest extends Request $user = auth()->user(); if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); } else { $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } /* Ensure we have a client name, and that all emails are unique*/ diff --git a/app/Http/Requests/Client/UpdateClientRequest.php b/app/Http/Requests/Client/UpdateClientRequest.php index 3ffea3250f05..9ed1acde3fc3 100644 --- a/app/Http/Requests/Client/UpdateClientRequest.php +++ b/app/Http/Requests/Client/UpdateClientRequest.php @@ -44,15 +44,15 @@ class UpdateClientRequest extends Request $user = auth()->user(); if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } else { $rules['documents'] = 'bail|sometimes|array'; } diff --git a/app/Http/Requests/Client/UploadClientRequest.php b/app/Http/Requests/Client/UploadClientRequest.php index bf12d3c8df00..17e159b2ee37 100644 --- a/app/Http/Requests/Client/UploadClientRequest.php +++ b/app/Http/Requests/Client/UploadClientRequest.php @@ -33,15 +33,15 @@ class UploadClientRequest extends Request $rules = []; if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } $rules['is_public'] = 'sometimes|boolean'; diff --git a/app/Http/Requests/Company/UploadCompanyRequest.php b/app/Http/Requests/Company/UploadCompanyRequest.php index 2b85f6a1ff08..0e3a545e2d05 100644 --- a/app/Http/Requests/Company/UploadCompanyRequest.php +++ b/app/Http/Requests/Company/UploadCompanyRequest.php @@ -30,15 +30,15 @@ class UploadCompanyRequest extends Request $rules = []; if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } return $rules; diff --git a/app/Http/Requests/CompanyGateway/TestCompanyGatewayRequest.php b/app/Http/Requests/CompanyGateway/TestCompanyGatewayRequest.php new file mode 100644 index 000000000000..8442dc91cd72 --- /dev/null +++ b/app/Http/Requests/CompanyGateway/TestCompanyGatewayRequest.php @@ -0,0 +1,49 @@ +user(); + + return $user->isAdmin(); + } + + public function rules() + { + + return [ + + ]; + } + + public function prepareForValidation() + { + $input = $this->all(); + + $this->replace($input); + } +} diff --git a/app/Http/Requests/Credit/StoreCreditRequest.php b/app/Http/Requests/Credit/StoreCreditRequest.php index c24005063d68..7520dd465060 100644 --- a/app/Http/Requests/Credit/StoreCreditRequest.php +++ b/app/Http/Requests/Credit/StoreCreditRequest.php @@ -47,17 +47,17 @@ class StoreCreditRequest extends Request $rules = []; if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); }else { $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } /** @var \App\Models\User $user */ diff --git a/app/Http/Requests/Credit/UpdateCreditRequest.php b/app/Http/Requests/Credit/UpdateCreditRequest.php index 7c0c3adc0274..d521ce39db58 100644 --- a/app/Http/Requests/Credit/UpdateCreditRequest.php +++ b/app/Http/Requests/Credit/UpdateCreditRequest.php @@ -49,17 +49,17 @@ class UpdateCreditRequest extends Request $rules = []; if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); }else { $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } $rules['number'] = ['bail', 'sometimes', 'nullable', Rule::unique('credits')->where('company_id', $user->company()->id)->ignore($this->credit->id)]; diff --git a/app/Http/Requests/Credit/UploadCreditRequest.php b/app/Http/Requests/Credit/UploadCreditRequest.php index 6e78de9cd4ef..3411ad44739e 100644 --- a/app/Http/Requests/Credit/UploadCreditRequest.php +++ b/app/Http/Requests/Credit/UploadCreditRequest.php @@ -33,15 +33,15 @@ class UploadCreditRequest extends Request $rules = []; if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } $rules['is_public'] = 'sometimes|boolean'; diff --git a/app/Http/Requests/Expense/UploadExpenseRequest.php b/app/Http/Requests/Expense/UploadExpenseRequest.php index d50f6af80df7..867cfc34390c 100644 --- a/app/Http/Requests/Expense/UploadExpenseRequest.php +++ b/app/Http/Requests/Expense/UploadExpenseRequest.php @@ -33,15 +33,15 @@ class UploadExpenseRequest extends Request $rules = []; if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } $rules['is_public'] = 'sometimes|boolean'; diff --git a/app/Http/Requests/GroupSetting/UploadGroupSettingRequest.php b/app/Http/Requests/GroupSetting/UploadGroupSettingRequest.php index 9a142a689029..baa92c483f5d 100644 --- a/app/Http/Requests/GroupSetting/UploadGroupSettingRequest.php +++ b/app/Http/Requests/GroupSetting/UploadGroupSettingRequest.php @@ -30,15 +30,15 @@ class UploadGroupSettingRequest extends Request $rules = []; if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } return $rules; diff --git a/app/Http/Requests/Invoice/StoreInvoiceRequest.php b/app/Http/Requests/Invoice/StoreInvoiceRequest.php index 393ada465aa0..df893bed2716 100644 --- a/app/Http/Requests/Invoice/StoreInvoiceRequest.php +++ b/app/Http/Requests/Invoice/StoreInvoiceRequest.php @@ -44,17 +44,17 @@ class StoreInvoiceRequest extends Request $user = auth()->user(); if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); }else { $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } $rules['client_id'] = 'bail|required|exists:clients,id,company_id,'.$user->company()->id.',is_deleted,0'; diff --git a/app/Http/Requests/Invoice/UpdateInvoiceRequest.php b/app/Http/Requests/Invoice/UpdateInvoiceRequest.php index 42d32ef81479..d863c5323cd8 100644 --- a/app/Http/Requests/Invoice/UpdateInvoiceRequest.php +++ b/app/Http/Requests/Invoice/UpdateInvoiceRequest.php @@ -46,17 +46,17 @@ class UpdateInvoiceRequest extends Request $rules = []; if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); }else { $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } // $rules['id'] = new LockedInvoiceRule($this->invoice); diff --git a/app/Http/Requests/Invoice/UploadInvoiceRequest.php b/app/Http/Requests/Invoice/UploadInvoiceRequest.php index 3f5b0f8b8bad..8ca57bd1edca 100644 --- a/app/Http/Requests/Invoice/UploadInvoiceRequest.php +++ b/app/Http/Requests/Invoice/UploadInvoiceRequest.php @@ -33,15 +33,15 @@ class UploadInvoiceRequest extends Request $rules = []; if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } $rules['is_public'] = 'sometimes|boolean'; diff --git a/app/Http/Requests/Payment/RefundPaymentRequest.php b/app/Http/Requests/Payment/RefundPaymentRequest.php index bf43b65f70d6..abcb694fbb17 100644 --- a/app/Http/Requests/Payment/RefundPaymentRequest.php +++ b/app/Http/Requests/Payment/RefundPaymentRequest.php @@ -57,8 +57,6 @@ class RefundPaymentRequest extends Request if (isset($input['credits'])) { unset($input['credits']); - // foreach($input['credits'] as $key => $credit) - // $input['credits'][$key]['credit_id'] = $this->decodePrimaryKey($credit['credit_id']); } $this->replace($input); diff --git a/app/Http/Requests/Payment/StorePaymentRequest.php b/app/Http/Requests/Payment/StorePaymentRequest.php index 25b3b43500c6..554d729dbe78 100644 --- a/app/Http/Requests/Payment/StorePaymentRequest.php +++ b/app/Http/Requests/Payment/StorePaymentRequest.php @@ -16,7 +16,6 @@ use App\Http\ValidationRules\Credit\CreditsSumRule; use App\Http\ValidationRules\Credit\ValidCreditsRules; use App\Http\ValidationRules\Payment\ValidInvoicesRules; use App\Http\ValidationRules\PaymentAmountsBalanceRule; -use App\Http\ValidationRules\ValidCreditsPresentRule; use App\Http\ValidationRules\ValidPayableInvoicesRule; use App\Models\Payment; use App\Utils\Traits\MakesHash; @@ -39,6 +38,41 @@ class StorePaymentRequest extends Request return $user->can('create', Payment::class); } + public function rules() + { + /** @var \App\Models\User $user */ + $user = auth()->user(); + + $rules = [ + 'client_id' => ['bail','required',Rule::exists('clients','id')->where('company_id',$user->company()->id)->where('is_deleted', 0)], + 'amount' => ['bail', 'numeric', new PaymentAmountsBalanceRule()], + 'invoices.*.amount' => ['bail','required'], + 'invoices.*.invoice_id' => ['bail','required','distinct', new ValidInvoicesRules($this->all()),Rule::exists('invoices','id')->where('company_id', $user->company()->id)->where('client_id', $this->client_id)], + 'credits.*.credit_id' => ['bail','required','distinct', new ValidCreditsRules($this->all()),Rule::exists('credits','id')->where('company_id', $user->company()->id)->where('client_id', $this->client_id)], + 'credits.*.amount' => ['bail','required', new CreditsSumRule($this->all())], + 'invoices' => ['bail','sometimes', 'nullable', 'array', new ValidPayableInvoicesRule()], + 'number' => ['bail', 'nullable', Rule::unique('payments')->where('company_id', $user->company()->id)], + 'idempotency_key' => ['nullable', 'bail', 'string','max:64', Rule::unique('payments')->where('company_id', $user->company()->id)], + ]; + + if ($this->file('documents') && is_array($this->file('documents'))) { + $rules['documents.*'] = $this->fileValidation(); + } elseif ($this->file('documents')) { + $rules['documents'] = $this->fileValidation(); + }else { + $rules['documents'] = 'bail|sometimes|array'; + } + + if ($this->file('file') && is_array($this->file('file'))) { + $rules['file.*'] = $this->fileValidation(); + } elseif ($this->file('file')) { + $rules['file'] = $this->fileValidation(); + } + + return $rules; + } + + public function prepareForValidation() { @@ -78,7 +112,6 @@ class StorePaymentRequest extends Request foreach ($input['credits'] as $key => $value) { if (array_key_exists('credit_id', $input['credits'][$key])) { $input['credits'][$key]['credit_id'] = $this->decodePrimaryKey($value['credit_id']); - $credits_total += $value['amount']; } } @@ -103,39 +136,5 @@ class StorePaymentRequest extends Request $this->replace($input); } - public function rules() - { - /** @var \App\Models\User $user */ - $user = auth()->user(); - $rules = [ - 'amount' => ['numeric', 'bail', new PaymentAmountsBalanceRule(), new ValidCreditsPresentRule($this->all())], - 'client_id' => 'bail|required|exists:clients,id,company_id,'.$user->company()->id.',is_deleted,0', - 'invoices.*.invoice_id' => 'bail|required|distinct|exists:invoices,id', - 'invoices.*.amount' => 'bail|required', - 'invoices.*.invoice_id' => new ValidInvoicesRules($this->all()), - 'credits.*.credit_id' => 'bail|required|exists:credits,id', - 'credits.*.credit_id' => new ValidCreditsRules($this->all()), - 'credits.*.amount' => ['bail','required', new CreditsSumRule($this->all())], - 'invoices' => new ValidPayableInvoicesRule(), - 'number' => ['nullable', 'bail', Rule::unique('payments')->where('company_id', $user->company()->id)], - 'idempotency_key' => ['nullable', 'bail', 'string','max:64', Rule::unique('payments')->where('company_id', $user->company()->id)], - ]; - - if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; - } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; - }else { - $rules['documents'] = 'bail|sometimes|array'; - } - - if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; - } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; - } - - return $rules; - } } diff --git a/app/Http/Requests/Payment/UpdatePaymentRequest.php b/app/Http/Requests/Payment/UpdatePaymentRequest.php index 52a3c29d895b..02cc11d45edc 100644 --- a/app/Http/Requests/Payment/UpdatePaymentRequest.php +++ b/app/Http/Requests/Payment/UpdatePaymentRequest.php @@ -13,7 +13,6 @@ namespace App\Http\Requests\Payment; use App\Http\Requests\Request; use App\Http\ValidationRules\PaymentAppliedValidAmount; -use App\Http\ValidationRules\ValidCreditsPresentRule; use App\Utils\Traits\ChecksEntityStatus; use App\Utils\Traits\MakesHash; use Illuminate\Validation\Rule; @@ -41,28 +40,29 @@ class UpdatePaymentRequest extends Request /** @var \App\Models\User $user */ $user = auth()->user(); - + $rules = [ - 'invoices' => ['array', new PaymentAppliedValidAmount($this->all()), new ValidCreditsPresentRule($this->all())], - 'invoices.*.invoice_id' => 'distinct', + 'client_id' => ['sometimes', 'bail', Rule::in([$this->payment->client_id])], + 'number' => ['sometimes', 'bail', Rule::unique('payments')->where('company_id', $user->company()->id)->ignore($this->payment->id)], + 'invoices' => ['sometimes', 'bail', 'nullable', 'array', new PaymentAppliedValidAmount($this->all())], + 'invoices.*.invoice_id' => ['sometimes','distinct',Rule::exists('invoices','id')->where('company_id', $user->company()->id)->where('client_id', $this->client_id)], + 'invoices.*.amount' => ['sometimes','numeric','min:0'], + 'credits.*.credit_id' => ['sometimes','bail','distinct',Rule::exists('credits','id')->where('company_id', $user->company()->id)->where('client_id', $this->client_id)], + 'credits.*.amount' => ['required', 'bail'], ]; - if ($this->number) { - $rules['number'] = Rule::unique('payments')->where('company_id', $user->company()->id)->ignore($this->payment->id); - } - if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); }else { $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } return $rules; @@ -74,10 +74,6 @@ class UpdatePaymentRequest extends Request $input = $this->decodePrimaryKeys($input); - if (isset($input['client_id'])) { - unset($input['client_id']); - } - if (isset($input['amount'])) { unset($input['amount']); } @@ -85,7 +81,6 @@ class UpdatePaymentRequest extends Request if (isset($input['invoices']) && is_array($input['invoices']) !== false) { foreach ($input['invoices'] as $key => $value) { if(isset($input['invoices'][$key]['invoice_id'])) { - // if (array_key_exists('invoice_id', $input['invoices'][$key])) { $input['invoices'][$key]['invoice_id'] = $this->decodePrimaryKey($value['invoice_id']); } } @@ -93,7 +88,6 @@ class UpdatePaymentRequest extends Request if (isset($input['credits']) && is_array($input['credits']) !== false) { foreach ($input['credits'] as $key => $value) { - // if (array_key_exists('credits', $input['credits'][$key])) { if (isset($input['credits'][$key]['credit_id'])) { $input['credits'][$key]['credit_id'] = $this->decodePrimaryKey($value['credit_id']); } diff --git a/app/Http/Requests/Payment/UploadPaymentRequest.php b/app/Http/Requests/Payment/UploadPaymentRequest.php index bf8d88d44464..a52d0a68f098 100644 --- a/app/Http/Requests/Payment/UploadPaymentRequest.php +++ b/app/Http/Requests/Payment/UploadPaymentRequest.php @@ -33,15 +33,15 @@ class UploadPaymentRequest extends Request $rules = []; if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } $rules['is_public'] = 'sometimes|boolean'; diff --git a/app/Http/Requests/Product/StoreProductRequest.php b/app/Http/Requests/Product/StoreProductRequest.php index c53ad07edbea..8d696bc132a4 100644 --- a/app/Http/Requests/Product/StoreProductRequest.php +++ b/app/Http/Requests/Product/StoreProductRequest.php @@ -32,17 +32,17 @@ class StoreProductRequest extends Request public function rules() { if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); }else { $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } $rules['cost'] = 'sometimes|numeric'; diff --git a/app/Http/Requests/Product/UpdateProductRequest.php b/app/Http/Requests/Product/UpdateProductRequest.php index 0a76f6a4ab08..1572ddbc1b82 100644 --- a/app/Http/Requests/Product/UpdateProductRequest.php +++ b/app/Http/Requests/Product/UpdateProductRequest.php @@ -35,17 +35,17 @@ class UpdateProductRequest extends Request public function rules() { if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); }else { $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } $rules['cost'] = 'numeric'; diff --git a/app/Http/Requests/Product/UploadProductRequest.php b/app/Http/Requests/Product/UploadProductRequest.php index d5eaf1e7e391..ef6b6d49db82 100644 --- a/app/Http/Requests/Product/UploadProductRequest.php +++ b/app/Http/Requests/Product/UploadProductRequest.php @@ -32,15 +32,15 @@ class UploadProductRequest extends Request { $rules = []; if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } $rules['is_public'] = 'sometimes|boolean'; diff --git a/app/Http/Requests/Project/StoreProjectRequest.php b/app/Http/Requests/Project/StoreProjectRequest.php index 3bc1bca1bc52..365cfaf8ea46 100644 --- a/app/Http/Requests/Project/StoreProjectRequest.php +++ b/app/Http/Requests/Project/StoreProjectRequest.php @@ -51,17 +51,17 @@ class StoreProjectRequest extends Request } if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); }else { $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } return $this->globalRules($rules); diff --git a/app/Http/Requests/Project/UpdateProjectRequest.php b/app/Http/Requests/Project/UpdateProjectRequest.php index cbcf6882cf89..a0d0eaf39dc5 100644 --- a/app/Http/Requests/Project/UpdateProjectRequest.php +++ b/app/Http/Requests/Project/UpdateProjectRequest.php @@ -48,17 +48,17 @@ class UpdateProjectRequest extends Request $rules['budgeted_hours'] = 'sometimes|numeric'; if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); }else { $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } return $this->globalRules($rules); diff --git a/app/Http/Requests/Project/UploadProjectRequest.php b/app/Http/Requests/Project/UploadProjectRequest.php index 08d66f2b048b..6b8f9909b759 100644 --- a/app/Http/Requests/Project/UploadProjectRequest.php +++ b/app/Http/Requests/Project/UploadProjectRequest.php @@ -33,15 +33,15 @@ class UploadProjectRequest extends Request $rules = []; if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } $rules['is_public'] = 'sometimes|boolean'; diff --git a/app/Http/Requests/PurchaseOrder/StorePurchaseOrderRequest.php b/app/Http/Requests/PurchaseOrder/StorePurchaseOrderRequest.php index 91564eab092d..2b28882b297e 100644 --- a/app/Http/Requests/PurchaseOrder/StorePurchaseOrderRequest.php +++ b/app/Http/Requests/PurchaseOrder/StorePurchaseOrderRequest.php @@ -54,17 +54,17 @@ class StorePurchaseOrderRequest extends Request $rules['line_items'] = 'array'; if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); } else { $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } $rules['status_id'] = 'nullable|integer|in:1,2,3,4,5'; diff --git a/app/Http/Requests/PurchaseOrder/UpdatePurchaseOrderRequest.php b/app/Http/Requests/PurchaseOrder/UpdatePurchaseOrderRequest.php index 66984cd29a51..55abc60559c4 100644 --- a/app/Http/Requests/PurchaseOrder/UpdatePurchaseOrderRequest.php +++ b/app/Http/Requests/PurchaseOrder/UpdatePurchaseOrderRequest.php @@ -56,17 +56,17 @@ class UpdatePurchaseOrderRequest extends Request $rules['is_amount_discount'] = ['boolean']; if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); }else { $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } $rules['status_id'] = 'sometimes|integer|in:1,2,3,4,5'; diff --git a/app/Http/Requests/PurchaseOrder/UploadPurchaseOrderRequest.php b/app/Http/Requests/PurchaseOrder/UploadPurchaseOrderRequest.php index c41de6989e62..9c5b4ed4f5ec 100644 --- a/app/Http/Requests/PurchaseOrder/UploadPurchaseOrderRequest.php +++ b/app/Http/Requests/PurchaseOrder/UploadPurchaseOrderRequest.php @@ -33,15 +33,15 @@ class UploadPurchaseOrderRequest extends Request $rules = []; if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } $rules['is_public'] = 'sometimes|boolean'; diff --git a/app/Http/Requests/Quote/StoreQuoteRequest.php b/app/Http/Requests/Quote/StoreQuoteRequest.php index 3e0a498610cc..59f3409abb2e 100644 --- a/app/Http/Requests/Quote/StoreQuoteRequest.php +++ b/app/Http/Requests/Quote/StoreQuoteRequest.php @@ -46,17 +46,17 @@ class StoreQuoteRequest extends Request $rules['client_id'] = 'required|exists:clients,id,company_id,'.$user->company()->id; if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); }else { $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } $rules['number'] = ['nullable', Rule::unique('quotes')->where('company_id', $user->company()->id)]; diff --git a/app/Http/Requests/Quote/UpdateQuoteRequest.php b/app/Http/Requests/Quote/UpdateQuoteRequest.php index 4644e5af691c..4e93c58d560e 100644 --- a/app/Http/Requests/Quote/UpdateQuoteRequest.php +++ b/app/Http/Requests/Quote/UpdateQuoteRequest.php @@ -43,17 +43,17 @@ class UpdateQuoteRequest extends Request $rules = []; if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); }else { $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } diff --git a/app/Http/Requests/Quote/UploadQuoteRequest.php b/app/Http/Requests/Quote/UploadQuoteRequest.php index 3c5a4395d73e..d7883c8b6ae9 100644 --- a/app/Http/Requests/Quote/UploadQuoteRequest.php +++ b/app/Http/Requests/Quote/UploadQuoteRequest.php @@ -33,15 +33,15 @@ class UploadQuoteRequest extends Request $rules = []; if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } $rules['is_public'] = 'sometimes|boolean'; diff --git a/app/Http/Requests/RecurringExpense/StoreRecurringExpenseRequest.php b/app/Http/Requests/RecurringExpense/StoreRecurringExpenseRequest.php index 3607edae7870..a334b97fbde0 100644 --- a/app/Http/Requests/RecurringExpense/StoreRecurringExpenseRequest.php +++ b/app/Http/Requests/RecurringExpense/StoreRecurringExpenseRequest.php @@ -57,15 +57,15 @@ class StoreRecurringExpenseRequest extends Request $rules['currency_id'] = 'bail|required|integer|exists:currencies,id'; if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } return $this->globalRules($rules); diff --git a/app/Http/Requests/RecurringExpense/UpdateRecurringExpenseRequest.php b/app/Http/Requests/RecurringExpense/UpdateRecurringExpenseRequest.php index e36333566fda..80a5759f0f10 100644 --- a/app/Http/Requests/RecurringExpense/UpdateRecurringExpenseRequest.php +++ b/app/Http/Requests/RecurringExpense/UpdateRecurringExpenseRequest.php @@ -49,15 +49,15 @@ class UpdateRecurringExpenseRequest extends Request $rules['category_id'] = 'bail|nullable|sometimes|exists:expense_categories,id,company_id,'.auth()->user()->company()->id.',is_deleted,0'; if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } return $this->globalRules($rules); diff --git a/app/Http/Requests/RecurringExpense/UploadRecurringExpenseRequest.php b/app/Http/Requests/RecurringExpense/UploadRecurringExpenseRequest.php index fa6b04c92580..fb1d50b5f769 100644 --- a/app/Http/Requests/RecurringExpense/UploadRecurringExpenseRequest.php +++ b/app/Http/Requests/RecurringExpense/UploadRecurringExpenseRequest.php @@ -30,15 +30,15 @@ class UploadRecurringExpenseRequest extends Request $rules = []; if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } return $rules; diff --git a/app/Http/Requests/RecurringInvoice/StoreRecurringInvoiceRequest.php b/app/Http/Requests/RecurringInvoice/StoreRecurringInvoiceRequest.php index 7d69a2587e1d..94cd8caba951 100644 --- a/app/Http/Requests/RecurringInvoice/StoreRecurringInvoiceRequest.php +++ b/app/Http/Requests/RecurringInvoice/StoreRecurringInvoiceRequest.php @@ -46,17 +46,17 @@ class StoreRecurringInvoiceRequest extends Request $rules = []; if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); }else { $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } $rules['client_id'] = 'required|exists:clients,id,company_id,'.$user->company()->id; diff --git a/app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php b/app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php index f59edc91d61a..8aa9da8ed5cd 100644 --- a/app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php +++ b/app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php @@ -45,17 +45,17 @@ class UpdateRecurringInvoiceRequest extends Request $rules = []; if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); }else { $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } $rules['number'] = ['bail', 'sometimes', Rule::unique('recurring_invoices')->where('company_id', $user->company()->id)->ignore($this->recurring_invoice->id)]; diff --git a/app/Http/Requests/RecurringInvoice/UploadRecurringInvoiceRequest.php b/app/Http/Requests/RecurringInvoice/UploadRecurringInvoiceRequest.php index 345e566434c4..e401ef6958b4 100644 --- a/app/Http/Requests/RecurringInvoice/UploadRecurringInvoiceRequest.php +++ b/app/Http/Requests/RecurringInvoice/UploadRecurringInvoiceRequest.php @@ -33,15 +33,15 @@ class UploadRecurringInvoiceRequest extends Request $rules = []; if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } $rules['is_public'] = 'sometimes|boolean'; diff --git a/app/Http/Requests/RecurringQuote/StoreRecurringQuoteRequest.php b/app/Http/Requests/RecurringQuote/StoreRecurringQuoteRequest.php index 0cb7ef56d378..12b2c4441081 100644 --- a/app/Http/Requests/RecurringQuote/StoreRecurringQuoteRequest.php +++ b/app/Http/Requests/RecurringQuote/StoreRecurringQuoteRequest.php @@ -46,15 +46,15 @@ class StoreRecurringQuoteRequest extends Request $rules = []; if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } $rules['client_id'] = 'required|exists:clients,id,company_id,'.$user->company()->id; diff --git a/app/Http/Requests/RecurringQuote/UpdateRecurringQuoteRequest.php b/app/Http/Requests/RecurringQuote/UpdateRecurringQuoteRequest.php index a0290e252744..d52f4b0974c7 100644 --- a/app/Http/Requests/RecurringQuote/UpdateRecurringQuoteRequest.php +++ b/app/Http/Requests/RecurringQuote/UpdateRecurringQuoteRequest.php @@ -38,15 +38,15 @@ class UpdateRecurringQuoteRequest extends Request $rules = []; if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } if ($this->number) { diff --git a/app/Http/Requests/RecurringQuote/UploadRecurringQuoteRequest.php b/app/Http/Requests/RecurringQuote/UploadRecurringQuoteRequest.php index 0631ec361579..3c8b91f96e0a 100644 --- a/app/Http/Requests/RecurringQuote/UploadRecurringQuoteRequest.php +++ b/app/Http/Requests/RecurringQuote/UploadRecurringQuoteRequest.php @@ -30,15 +30,15 @@ class UploadRecurringQuoteRequest extends Request $rules = []; if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } return $rules; diff --git a/app/Http/Requests/Request.php b/app/Http/Requests/Request.php index e70c7f6b3a55..3f7c3f23008e 100644 --- a/app/Http/Requests/Request.php +++ b/app/Http/Requests/Request.php @@ -20,7 +20,7 @@ class Request extends FormRequest use MakesHash; use RuntimeFormRequest; - protected $file_validation = 'sometimes|file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx,webp,xml,zip,csv,ods,odt,odp|max:100000'; + protected $file_validation = 'sometimes|file|max:100000|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx,webp,xml,zip,csv,ods,odt,odp'; /** * Get the validation rules that apply to the request. * @@ -31,6 +31,15 @@ class Request extends FormRequest return []; } + public function fileValidation() + { + if(config('ninja.upload_extensions')) + return $this->file_validation. ",".config('ninja.upload_extensions'); + + return $this->file_validation; + + } + public function globalRules($rules) { $merge_rules = []; diff --git a/app/Http/Requests/Task/StoreTaskRequest.php b/app/Http/Requests/Task/StoreTaskRequest.php index e5fee49132a0..66577a23d97c 100644 --- a/app/Http/Requests/Task/StoreTaskRequest.php +++ b/app/Http/Requests/Task/StoreTaskRequest.php @@ -79,17 +79,17 @@ class StoreTaskRequest extends Request }]; if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); }else { $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } diff --git a/app/Http/Requests/Task/UpdateTaskRequest.php b/app/Http/Requests/Task/UpdateTaskRequest.php index 299905d8cd58..c71c7f0452e0 100644 --- a/app/Http/Requests/Task/UpdateTaskRequest.php +++ b/app/Http/Requests/Task/UpdateTaskRequest.php @@ -85,17 +85,17 @@ class UpdateTaskRequest extends Request }]; if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); }else { $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } return $this->globalRules($rules); diff --git a/app/Http/Requests/Task/UploadTaskRequest.php b/app/Http/Requests/Task/UploadTaskRequest.php index 99737bd71536..4415cf8a995e 100644 --- a/app/Http/Requests/Task/UploadTaskRequest.php +++ b/app/Http/Requests/Task/UploadTaskRequest.php @@ -33,15 +33,15 @@ class UploadTaskRequest extends Request $rules = []; if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } $rules['is_public'] = 'sometimes|boolean'; diff --git a/app/Http/Requests/Vendor/StoreVendorRequest.php b/app/Http/Requests/Vendor/StoreVendorRequest.php index 7cb6fc14f193..3c282aeaeaf9 100644 --- a/app/Http/Requests/Vendor/StoreVendorRequest.php +++ b/app/Http/Requests/Vendor/StoreVendorRequest.php @@ -61,17 +61,17 @@ class StoreVendorRequest extends Request $rules['currency_id'] = 'bail|required|exists:currencies,id'; if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); }else { $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } $rules['language_id'] = 'bail|nullable|sometimes|exists:languages,id'; diff --git a/app/Http/Requests/Vendor/UpdateVendorRequest.php b/app/Http/Requests/Vendor/UpdateVendorRequest.php index b06b861adfe6..3049a9c2d1b6 100644 --- a/app/Http/Requests/Vendor/UpdateVendorRequest.php +++ b/app/Http/Requests/Vendor/UpdateVendorRequest.php @@ -62,17 +62,17 @@ class UpdateVendorRequest extends Request $rules['currency_id'] = 'bail|sometimes|exists:currencies,id'; if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); }else { $rules['documents'] = 'bail|sometimes|array'; } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } $rules['language_id'] = 'bail|nullable|sometimes|exists:languages,id'; diff --git a/app/Http/Requests/Vendor/UploadVendorRequest.php b/app/Http/Requests/Vendor/UploadVendorRequest.php index 80b6195c600d..826a5ed14d65 100644 --- a/app/Http/Requests/Vendor/UploadVendorRequest.php +++ b/app/Http/Requests/Vendor/UploadVendorRequest.php @@ -30,15 +30,15 @@ class UploadVendorRequest extends Request $rules = []; if ($this->file('documents') && is_array($this->file('documents'))) { - $rules['documents.*'] = $this->file_validation; + $rules['documents.*'] = $this->fileValidation(); } elseif ($this->file('documents')) { - $rules['documents'] = $this->file_validation; + $rules['documents'] = $this->fileValidation(); } if ($this->file('file') && is_array($this->file('file'))) { - $rules['file.*'] = $this->file_validation; + $rules['file.*'] = $this->fileValidation(); } elseif ($this->file('file')) { - $rules['file'] = $this->file_validation; + $rules['file'] = $this->fileValidation(); } $rules['is_public'] = 'sometimes|boolean'; diff --git a/app/Http/ValidationRules/Account/BlackListRule.php b/app/Http/ValidationRules/Account/BlackListRule.php index 0d74f646b31a..e8149d23bc7b 100644 --- a/app/Http/ValidationRules/Account/BlackListRule.php +++ b/app/Http/ValidationRules/Account/BlackListRule.php @@ -5,7 +5,7 @@ * @link https://github.com/invoiceninja/invoiceninja source repository * * @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com) - * + *1` * @license https://www.elastic.co/licensing/elastic-license */ @@ -21,6 +21,7 @@ class BlackListRule implements ValidationRule { /** Bad domains +/- dispoable email domains */ private array $blacklist = [ + 'wireconnected.com', 'secure-coinspot.com', 'casasotombo.com', 'otpku.com', diff --git a/app/Http/ValidationRules/Payment/ValidRefundableRequest.php b/app/Http/ValidationRules/Payment/ValidRefundableRequest.php index 49530bca72a1..093aba14c839 100644 --- a/app/Http/ValidationRules/Payment/ValidRefundableRequest.php +++ b/app/Http/ValidationRules/Payment/ValidRefundableRequest.php @@ -57,9 +57,6 @@ class ValidRefundableRequest implements Rule if ($payment->invoices()->exists()) { $this->checkInvoice($payment->invoices, $request_invoices); - // foreach ($payment->invoices as $paymentable_invoice) { - // $this->checkInvoice($paymentable_invoice, $request_invoices); - // } } foreach ($request_invoices as $request_invoice) { diff --git a/app/Http/ValidationRules/PaymentAppliedValidAmount.php b/app/Http/ValidationRules/PaymentAppliedValidAmount.php index c6257fde8d1f..863b56d68986 100644 --- a/app/Http/ValidationRules/PaymentAppliedValidAmount.php +++ b/app/Http/ValidationRules/PaymentAppliedValidAmount.php @@ -61,7 +61,10 @@ class PaymentAppliedValidAmount implements Rule $payment_amounts = 0; $invoice_amounts = 0; - $payment_amounts = $payment->amount - $payment->refunded - $payment->applied; + // $payment_amounts = $payment->amount - $payment->refunded - $payment->applied; + + //20-03-2024 - applied amounts are never tainted by refunded amount. + $payment_amounts = $payment->amount - $payment->applied; if (request()->has('credits') && is_array(request()->input('credits')) @@ -84,10 +87,6 @@ class PaymentAppliedValidAmount implements Rule $inv = $inv_collection->firstWhere('id', $invoice['invoice_id']); - nlog($inv->status_id); - nlog($inv->amount); - nlog($invoice['amount']); - if($inv->status_id == Invoice::STATUS_DRAFT && $inv->amount >= $invoice['amount']) { } elseif ($inv->balance < $invoice['amount']) { diff --git a/app/Http/ValidationRules/ValidCreditsPresentRule.php b/app/Http/ValidationRules/ValidCreditsPresentRule.php index 7dfb07df5f99..50d3b32af5d0 100644 --- a/app/Http/ValidationRules/ValidCreditsPresentRule.php +++ b/app/Http/ValidationRules/ValidCreditsPresentRule.php @@ -17,6 +17,7 @@ use Illuminate\Contracts\Validation\Rule; /** * Class ValidCreditsPresentRule. + * @deprecated 20-03-2024 */ class ValidCreditsPresentRule implements Rule { @@ -49,11 +50,8 @@ class ValidCreditsPresentRule implements Rule private function validCreditsPresent(): bool { - //todo need to ensure the clients credits are here not random ones! - if (array_key_exists('credits', $this->input) && is_array($this->input['credits']) && count($this->input['credits']) > 0) { $credit_collection = Credit::query()->whereIn('id', array_column($this->input['credits'], 'credit_id'))->count(); - return $credit_collection == count($this->input['credits']); } diff --git a/app/Jobs/Bank/ProcessBankTransactionsNordigen.php b/app/Jobs/Bank/ProcessBankTransactionsNordigen.php index dd7c6c657b9e..8ff86efd14a5 100644 --- a/app/Jobs/Bank/ProcessBankTransactionsNordigen.php +++ b/app/Jobs/Bank/ProcessBankTransactionsNordigen.php @@ -85,6 +85,8 @@ class ProcessBankTransactionsNordigen implements ShouldQueue $this->bank_integration->company->notification(new GenericNinjaAdminNotification($content))->ninja(); + sleep(5); + throw $e; } if (!$this->nordigen_account) { diff --git a/app/Jobs/Brevo/ProcessBrevoWebhook.php b/app/Jobs/Brevo/ProcessBrevoWebhook.php new file mode 100644 index 000000000000..92959f7d2e8b --- /dev/null +++ b/app/Jobs/Brevo/ProcessBrevoWebhook.php @@ -0,0 +1,492 @@ + '', + 'subject' => 'Message not found.', + 'entity' => '', + 'entity_id' => '', + 'events' => [], + ]; + + private ?Company $company = null; + + /** + * Create a new job instance. + * + */ + public function __construct(private array $request) + { + } + + private function getSystemLog(string $message_id): ?SystemLog + { + return SystemLog::query() + ->where('company_id', $this->invitation->company_id) + ->where('type_id', SystemLog::TYPE_WEBHOOK_RESPONSE) + ->whereJsonContains('log', ['message-id' => $message_id]) + ->orderBy('id', 'desc') + ->first(); + + } + + private function updateSystemLog(SystemLog $system_log, array $data): void + { + $system_log->log = $data; + $system_log->save(); + } + + /** + * Execute the job. + * + * + * @return void + */ + public function handle() + { + MultiDB::findAndSetDbByCompanyKey($this->request['tags'][0]); + $this->company = Company::where('company_key', $this->request['tags'][0])->first(); + + $this->invitation = $this->discoverInvitation($this->request['message-id']); + + if ($this->company && $this->request['event'] == 'spam' && config('ninja.notification.slack')) { + $this->company->notification(new EmailSpamNotification($this->company))->ninja(); + } + + if (!$this->invitation) { + return; + } + + if (array_key_exists('reason', $this->request)) { + $this->invitation->email_error = $this->request['reason']; + } + + switch ($this->request['event']) { + case 'delivered': + return $this->processDelivery(); + case 'soft_bounce': + case 'hard_bounce': + case 'invalid_email': + case 'blocked': + + if ($this->request['subject'] == ctrans('texts.confirmation_subject')) { + $this->company->notification(new EmailBounceNotification($this->request['email']))->ninja(); + } + + return $this->processBounce(); + case 'spam': + return $this->processSpamComplaint(); + case 'unique_opened': + case 'opened': + case 'click': + return $this->processOpen(); + default: + # code... + break; + } + } + + // { + // "id": 948562, + // "email": "test@example.com", + // "message-id": "<202312211546.94160606300@smtp-relay.mailin.fr>", + // "date": "2023-12-21 18:34:42", + // "tags": [ + // "gMtwiTIJtJxklXCj1OUFANgY6YYynQxV" + // ], + // "tag": "[\"gMtwiTIJtJxklXCj1OUFANgY6YYynQxV\"]", + // "event": "unique_opened", + // "subject": "Reminder: Invoice 0002 from Untitled Company", + // "sending_ip": "74.125.208.8", + // "ts": 1703180082, + // "ts_epoch": 1703180082286, + // "ts_event": 1703180082, + // "link": "", + // "sender_email": "user@example.com" + // } + // { + // "id": 948562, + // "email": "test@example.com", + // "message-id": "<202312211555.14720890391@smtp-relay.mailin.fr>", + // "date": "2023-12-21 18:34:53", + // "tags": [ + // "gMtwiTIJtJxklXCj1OUFANgY6YYynQxV" + // ], + // "tag": "[\"gMtwiTIJtJxklXCj1OUFANgY6YYynQxV\"]", + // "event": "opened", + // "subject": "Reminder: Invoice 0002 from Untitled Company", + // "sending_ip": "74.125.208.8", + // "ts": 1703180093, + // "ts_epoch": 1703180093075, + // "ts_event": 1703180093, + // "link": "", + // "sender_email": "user@example.com" + // } + // { + // "id": 948562, + // "email": "paul@wer-ner.de", + // "message-id": "<202312280812.10968711117@smtp-relay.mailin.fr>", + // "date": "2023-12-28 09:20:18", + // "tags": [ + // "gMtwiTIJtJxklXCj1OUFANgY6YYynQxV" + // ], + // "tag": "[\"gMtwiTIJtJxklXCj1OUFANgY6YYynQxV\"]", + // "event": "click", + // "subject": "Reminder: Invoice 0002 from Untitled Company", + // "sending_ip": "79.235.133.157", + // "ts": 1703751618, + // "ts_epoch": 1703751618831, + // "ts_event": 1703751618, + // "link": "http://localhost/client/invoice/CssCvqOcKsenMCgYJ7EUNRZwxSDGUkau", + // "sender_email": "user@example.com" + // } + + private function processOpen() + { + $this->invitation->opened_date = now(); + $this->invitation->save(); + + $data = array_merge($this->request, ['history' => $this->fetchMessage()]); + + $sl = $this->getSystemLog($this->request['message-id']); + + if ($sl) { + $this->updateSystemLog($sl, $data); + return; + } + + ( + new SystemLogger( + $data, + SystemLog::CATEGORY_MAIL, + SystemLog::EVENT_MAIL_OPENED, + SystemLog::TYPE_WEBHOOK_RESPONSE, + $this->invitation->contact->client, + $this->invitation->company + ) + )->handle(); + } + + // { + // "id": 948562, + // "email": "test@example", + // "message-id": "<202312211742.12697514322@smtp-relay.mailin.fr>", + // "date": "2023-12-21 18:42:31", + // "tags": [ + // "gMtwiTIJtJxklXCj1OUFANgY6YYynQxV" + // ], + // "tag": "[\"gMtwiTIJtJxklXCj1OUFANgY6YYynQxV\"]", + // "event": "delivered", + // "subject": "Reminder: Invoice 0002 from Untitled Company", + // "sending_ip": "77.32.148.26", + // "ts_event": 1703180551, + // "ts": 1703180551, + // "reason": "sent", + // "ts_epoch": 1703180551324, + // "sender_email": "user@example.com" + // } + private function processDelivery() + { + $this->invitation->email_status = 'delivered'; + $this->invitation->save(); + + $data = array_merge($this->request, ['history' => $this->fetchMessage()]); + + $sl = $this->getSystemLog($this->request['message-id']); + + if ($sl) { + $this->updateSystemLog($sl, $data); + return; + } + + ( + new SystemLogger( + $data, + SystemLog::CATEGORY_MAIL, + SystemLog::EVENT_MAIL_DELIVERY, + SystemLog::TYPE_WEBHOOK_RESPONSE, + $this->invitation->contact->client, + $this->invitation->company + ) + )->handle(); + } + + // { + // "id": 948562, + // "email": "ryder36@example.net", + // "message-id": "<202312211744.55168080257@smtp-relay.mailin.fr>", + // "date": "2023-12-21 18:44:52", + // "tags": [ + // "gMtwiTIJtJxklXCj1OUFANgY6YYynQxV" + // ], + // "tag": "[\"gMtwiTIJtJxklXCj1OUFANgY6YYynQxV\"]", + // "event": "soft_bounce", + // "subject": "Reminder: Invoice 0001 from Untitled Company", + // "sending_ip": "77.32.148.26", + // "ts_event": 1703180692, + // "ts": 1703180692, + // "reason": "Unable to find MX of domain example.net", + // "ts_epoch": 1703180692382, + // "sender_email": "user@example.com" + // } + // { + // "id": 948562, + // "email": "gloria46@example.com", + // "message-id": "<202312211744.57456703957@smtp-relay.mailin.fr>", + // "date": "2023-12-21 18:44:54", + // "tags": [ + // "gMtwiTIJtJxklXCj1OUFANgY6YYynQxV" + // ], + // "tag": "[\"gMtwiTIJtJxklXCj1OUFANgY6YYynQxV\"]", + // "event": "hard_bounce", + // "subject": "Reminder: Invoice 0001 from Untitled Company", + // "sending_ip": "77.32.148.25", + // "ts_event": 1703180694, + // "ts": 1703180694, + // "reason": "blocked by Admin", + // "ts_epoch": 1703180694175, + // "sender_email": "user@example.com" + // } + // { + // "event" : "invalid_email", + // "email" : "example@example.com", + // "id" : 1, + // "date" : "yyyy-mm-dd hh:i:s", + // "message-id" : "", + // "subject" : "Test subject", + // "tag" : "",//json of array + // "tags": [ + // "company_key" + // ], + // "sending_ip" : "xxx.xx.xxx.xx", + // "ts_epoch" : 1534486682000, + // "template_id" : 1, + // "sender_email": "user@example.com", + // } + // { + // "id": 948562, + // "email": "neoma.langosh@example.com", + // "message-id": "<202312211745.65538701430@smtp-relay.mailin.fr>", + // "date": "2023-12-21 18:45:48", + // "tags": [ + // "gMtwiTIJtJxklXCj1OUFANgY6YYynQxV" + // ], + // "tag": "[\"gMtwiTIJtJxklXCj1OUFANgY6YYynQxV\"]", + // "event": "blocked", + // "subject": "Reminder: Invoice 0001 from Untitled Company", + // "ts_event": 1703180748, + // "ts": 1703180748, + // "reason": "blocked : due to blacklist user", + // "ts_epoch": 1703180748987, + // "sender_email": "user@example.com" + // } + + private function processBounce() + { + $this->invitation->email_status = 'bounced'; + $this->invitation->save(); + + $bounce = new EmailBounce( + $this->request['tags'][0], + $this->request['sender_email'], // TODO: @turbo124 is this the recipent? + $this->request['message-id'] + ); + + LightLogs::create($bounce)->send(); + + $data = array_merge($this->request, ['history' => $this->fetchMessage()]); + + $sl = $this->getSystemLog($this->request['message-id']); + + if ($sl) { + $this->updateSystemLog($sl, $data); + return; + } + + (new SystemLogger($data, SystemLog::CATEGORY_MAIL, SystemLog::EVENT_MAIL_BOUNCED, SystemLog::TYPE_WEBHOOK_RESPONSE, $this->invitation->contact->client, $this->invitation->company))->handle(); + + // if(config('ninja.notification.slack')) + // $this->invitation->company->notification(new EmailBounceNotification($this->invitation->company->account))->ninja(); + } + + // { + // "event" : "spam", + // "email" : "example@example.com", + // "id" : 1, + // "date" : "yyyy-mm-dd hh:i:s", + // "message-id" : "", + // "tag" : "",//json of array + // "tags": [ + // "company_key" + // ], + // "sending_ip" : "xxx.xx.xxx.xx", + // "sender_email": "user@example.com", + // } + private function processSpamComplaint() + { + $this->invitation->email_status = 'spam'; + $this->invitation->save(); + + $spam = new EmailSpam( + $this->request['tags'][0], + $this->request['sender_email'], + $this->request['message-id'] + ); + + LightLogs::create($spam)->send(); + + $data = array_merge($this->request, ['history' => $this->fetchMessage()]); + + $sl = $this->getSystemLog($this->request['message-id']); + + if ($sl) { + $this->updateSystemLog($sl, $data); + return; + } + + (new SystemLogger($data, SystemLog::CATEGORY_MAIL, SystemLog::EVENT_MAIL_SPAM_COMPLAINT, SystemLog::TYPE_WEBHOOK_RESPONSE, $this->invitation->contact->client, $this->invitation->company))->handle(); + + if (config('ninja.notification.slack')) { + $this->invitation->company->notification(new EmailSpamNotification($this->invitation->company->account))->ninja(); + } + } + + private function discoverInvitation(string $message_id) + { + $invitation = false; + + if ($invitation = InvoiceInvitation::where('message_id', $message_id)->first()) { + $this->entity = 'invoice'; + return $invitation; + } elseif ($invitation = QuoteInvitation::where('message_id', $message_id)->first()) { + $this->entity = 'quote'; + return $invitation; + } elseif ($invitation = RecurringInvoiceInvitation::where('message_id', $message_id)->first()) { + $this->entity = 'recurring_invoice'; + return $invitation; + } elseif ($invitation = CreditInvitation::where('message_id', $message_id)->first()) { + $this->entity = 'credit'; + return $invitation; + } elseif ($invitation = PurchaseOrderInvitation::where('message_id', $message_id)->first()) { + $this->entity = 'purchase_order'; + return $invitation; + } else { + return $invitation; + } + } + + public function getRawMessage(string $message_id) + { + + $brevo_secret = !empty($this->company->settings->brevo_secret) ? $this->company->settings->brevo_secret : config('services.brevo.key'); + + $brevo = new TransactionalEmailsApi(null, Configuration::getDefaultConfiguration()->setApiKey('api-key', $brevo_secret)); + $messageDetail = $brevo->getTransacEmailContent($message_id); + return $messageDetail; + + } + + + public function getBounceId(string $message_id): ?int + { + + $messageDetail = $this->getRawMessage($message_id); + + $event = collect($messageDetail->getEvents())->first(function ($event) { + + return $event?->Details?->BounceID ?? false; + + }); + + return $event?->Details?->BounceID ?? null; + + } + + // TODO + private function fetchMessage(): array + { + if (strlen($this->request['message-id']) < 1) { + return $this->default_response; + } + + try { + + $messageDetail = $this->getRawMessage($this->request['message-id']); + + $recipient = array_key_exists("email", $this->request) ? $this->request["email"] : ''; + $server_ip = array_key_exists("sending_ip", $this->request) ? $this->request["sending_ip"] : ''; + $delivery_message = array_key_exists("reason", $this->request) ? $this->request["reason"] : ''; + $subject = $messageDetail->getSubject() ?? ''; + + $events = collect($messageDetail->getEvents())->map(function (GetTransacEmailContentEvents $event) use ($recipient, $server_ip, $delivery_message) { // @turbo124 event does only contain name & time property, how to handle transformation?! + + return [ + 'bounce_id' => '', + 'recipient' => $recipient, + 'status' => $event->name ?? '', + 'delivery_message' => $delivery_message, // TODO: @turbo124 this results in all cases for the history in the string, which may be incorrect + 'server' => '', + 'server_ip' => $server_ip, + 'date' => \Carbon\Carbon::parse($event->getTime())->format('Y-m-d H:i:s') ?? '', + ]; + + })->toArray(); + + return [ + 'recipients' => $recipient, + 'subject' => $subject, + 'entity' => $this->entity ?? '', + 'entity_id' => $this->invitation->{$this->entity}->hashed_id ?? '', + 'events' => $events, + ]; + + } catch (\Exception $e) { + + return $this->default_response; + + } + } +} diff --git a/app/Jobs/Cron/UpdateCalculatedFields.php b/app/Jobs/Cron/UpdateCalculatedFields.php index 9145ab8f9842..8abf9a2bd48e 100644 --- a/app/Jobs/Cron/UpdateCalculatedFields.php +++ b/app/Jobs/Cron/UpdateCalculatedFields.php @@ -63,11 +63,14 @@ class UpdateCalculatedFields Project::query()->with('tasks')->whereHas('tasks', function ($query) { $query->where('updated_at', '>', now()->subHours(2)); }) - ->cursor() - ->each(function ($project) { - $project->current_hours = $this->calculateDuration($project); - $project->save(); - }); + ->cursor() + ->each(function ($project) { + $project->current_hours = $this->calculateDuration($project); + $project->save(); + }); + + //Clean password resets table + \DB::connection($db)->table('password_resets')->where('created_at', '<', now()->subHour())->delete(); } } diff --git a/app/Jobs/EDocument/CreateEDocument.php b/app/Jobs/EDocument/CreateEDocument.php new file mode 100644 index 000000000000..a2520b417a9e --- /dev/null +++ b/app/Jobs/EDocument/CreateEDocument.php @@ -0,0 +1,139 @@ +document instanceof PurchaseOrder) ? $this->document->vendor : $this->document->client; + App::setLocale($settings_entity->locale()); + + /* Set customized translations _NOW_ */ + $t->replace(Ninja::transformTranslations($this->document->client->getMergedSettings())); + + $e_document_type = strlen($settings_entity->getSetting('e_invoice_type')) > 2 ? $settings_entity->getSetting('e_invoice_type') : "XInvoice_3_0"; + $e_quote_type = strlen($settings_entity->getSetting('e_quote_type')) > 2 ? $settings_entity->getSetting('e_quote_type') : "OrderX_Extended"; + + if ($this->document instanceof Invoice){ + switch ($e_document_type) { + case "EN16931": + case "XInvoice_3_0": + case "XInvoice_2_3": + case "XInvoice_2_2": + case "XInvoice_2_1": + case "XInvoice_2_0": + case "XInvoice_1_0": + case "XInvoice-Extended": + case "XInvoice-BasicWL": + case "XInvoice-Basic": + $zugferd = (new ZugferdEDokument($this->document))->run(); + + return $this->returnObject ? $zugferd->xdocument : $zugferd->getXml(); + case "Facturae_3.2": + case "Facturae_3.2.1": + case "Facturae_3.2.2": + return (new FacturaEInvoice($this->document, str_replace("Facturae_", "", $e_document_type)))->run(); + default: + + $zugferd = (new ZugferdEDokument($this->document))->run(); + + return $this->returnObject ? $zugferd : $zugferd->getXml(); + + } + } + elseif ($this->document instanceof Quote){ + switch ($e_quote_type){ + case "OrderX_Basic": + case "OrderX_Comfort": + case "OrderX_Extended": + $orderx = (new OrderXDocument($this->document))->run(); + return $this->returnObject ? $orderx->orderxdocument : $orderx->getXml(); + default: + $orderx = (new OrderXDocument($this->document))->run(); + return $this->returnObject ? $orderx->orderxdocument : $orderx->getXml(); + } + } + elseif ($this->document instanceof PurchaseOrder){ + switch ($e_quote_type){ + case "OrderX_Basic": + case "OrderX_Comfort": + case "OrderX_Extended": + $orderx = (new OrderXDocument($this->document))->run(); + return $this->returnObject ? $orderx->orderxdocument : $orderx->getXml(); + default: + $orderx = (new OrderXDocument($this->document))->run(); + return $this->returnObject ? $orderx->orderxdocument : $orderx->getXml(); + } + } + elseif ($this->document instanceof Credit) { + switch ($e_document_type) { + case "EN16931": + case "XInvoice_3_0": + case "XInvoice_2_3": + case "XInvoice_2_2": + case "XInvoice_2_1": + case "XInvoice_2_0": + case "XInvoice_1_0": + case "XInvoice-Extended": + case "XInvoice-BasicWL": + case "XInvoice-Basic": + $zugferd = (new ZugferdEDokument($this->document))->run(); + return $this->returnObject ? $zugferd->xdocument : $zugferd->getXml(); + default: + $zugferd = (new ZugferdEDokument($this->document))->run(); + return $this->returnObject ? $zugferd : $zugferd->getXml(); + } + } + else{ + return ""; + } + } +} diff --git a/app/Jobs/Invoice/CreateEInvoice.php b/app/Jobs/Invoice/CreateEInvoice.php deleted file mode 100644 index 0ea7ae8a96d8..000000000000 --- a/app/Jobs/Invoice/CreateEInvoice.php +++ /dev/null @@ -1,86 +0,0 @@ -invoice->client->locale()); - - /* Set customized translations _NOW_ */ - $t->replace(Ninja::transformTranslations($this->invoice->client->getMergedSettings())); - - $e_invoice_type = $this->invoice->client->getSetting('e_invoice_type'); - - switch ($e_invoice_type) { - case "EN16931": - case "XInvoice_3_0": - case "XInvoice_2_3": - case "XInvoice_2_2": - case "XInvoice_2_1": - case "XInvoice_2_0": - case "XInvoice_1_0": - case "XInvoice-Extended": - case "XInvoice-BasicWL": - case "XInvoice-Basic": - $zugferd = (new ZugferdEInvoice($this->invoice))->run(); - - return $this->returnObject ? $zugferd->xrechnung : $zugferd->getXml(); - case "Facturae_3.2": - case "Facturae_3.2.1": - case "Facturae_3.2.2": - return (new FacturaEInvoice($this->invoice, str_replace("Facturae_", "", $e_invoice_type)))->run(); - default: - - $zugferd = (new ZugferdEInvoice($this->invoice))->run(); - - return $this->returnObject ? $zugferd : $zugferd->getXml(); - - } - - } -} diff --git a/app/Jobs/Mail/NinjaMailerJob.php b/app/Jobs/Mail/NinjaMailerJob.php index f421667a31a4..bff1f3f5aa7c 100644 --- a/app/Jobs/Mail/NinjaMailerJob.php +++ b/app/Jobs/Mail/NinjaMailerJob.php @@ -62,6 +62,7 @@ class NinjaMailerJob implements ShouldQueue protected $client_mailgun_domain = false; + protected $client_brevo_secret = false; public function __construct(public ?NinjaMailerObject $nmo, public bool $override = false) { @@ -99,7 +100,7 @@ class NinjaMailerJob implements ShouldQueue } $this->nmo->mailable->replyTo($this->nmo->settings->reply_to_email, $reply_to_name); - } elseif(isset($this->nmo->invitation->user)) { + } elseif (isset ($this->nmo->invitation->user)) { $this->nmo->mailable->replyTo($this->nmo->invitation->user->email, $this->nmo->invitation->user->present()->name()); } else { $this->nmo->mailable->replyTo($this->company->owner()->email, $this->company->owner()->present()->name()); @@ -112,16 +113,16 @@ class NinjaMailerJob implements ShouldQueue /* If we have an invitation present, we pass the invitation key into the email headers*/ if ($this->nmo->invitation) { $this->nmo - ->mailable - ->withSymfonyMessage(function ($message) { - $message->getHeaders()->addTextHeader('x-invitation', $this->nmo->invitation->key); - }); + ->mailable + ->withSymfonyMessage(function ($message) { + $message->getHeaders()->addTextHeader('x-invitation', $this->nmo->invitation->key); + }); } //send email try { - nlog("Trying to send to {$this->nmo->to_user->email} ". now()->toDateTimeString()); - nlog("Using mailer => ". $this->mailer); + nlog("Trying to send to {$this->nmo->to_user->email} " . now()->toDateTimeString()); + nlog("Using mailer => " . $this->mailer); $mailer = Mail::mailer($this->mailer); @@ -133,10 +134,14 @@ class NinjaMailerJob implements ShouldQueue $mailer->mailgun_config($this->client_mailgun_secret, $this->client_mailgun_domain, $this->nmo->settings->mailgun_endpoint); } + if ($this->client_brevo_secret) { + $mailer->brevo_config($this->client_brevo_secret); + } + $mailable = $this->nmo->mailable; /** May need to re-build it here */ - if(Ninja::isHosted() && method_exists($mailable, 'build')) { + if (Ninja::isHosted() && method_exists($mailable, 'build')) { $mailable->build(); } @@ -149,15 +154,15 @@ class NinjaMailerJob implements ShouldQueue $this->incrementEmailCounter(); LightLogs::create(new EmailSuccess($this->nmo->company->company_key, $this->nmo->mailable->subject)) - ->send(); + ->send(); - } catch(\Symfony\Component\Mime\Exception\RfcComplianceException $e) { + } catch (\Symfony\Component\Mime\Exception\RfcComplianceException $e) { nlog("Mailer failed with a Logic Exception {$e->getMessage()}"); $this->fail(); $this->cleanUpMailers(); $this->logMailError($e->getMessage(), $this->company->clients()->first()); return; - } catch(\Symfony\Component\Mime\Exception\LogicException $e) { + } catch (\Symfony\Component\Mime\Exception\LogicException $e) { nlog("Mailer failed with a Logic Exception {$e->getMessage()}"); $this->fail(); $this->cleanUpMailers(); @@ -224,7 +229,7 @@ class NinjaMailerJob implements ShouldQueue private function incrementEmailCounter(): void { - if(in_array($this->mailer, ['default','mailgun'])) + if(in_array($this->mailer, ['default','mailgun','postmark'])) Cache::increment("email_quota".$this->company->account->key); } @@ -273,7 +278,7 @@ class NinjaMailerJob implements ShouldQueue // return $this; // } - if(Ninja::isHosted() && $this->company->account->isPaid() && $this->nmo->settings->email_sending_method == 'default') { + if (Ninja::isHosted() && $this->company->account->isPaid() && $this->nmo->settings->email_sending_method == 'default') { //check if outlook. try { @@ -281,7 +286,7 @@ class NinjaMailerJob implements ShouldQueue $domain = explode("@", $email)[1] ?? ""; $dns = dns_get_record($domain, DNS_MX); $server = $dns[0]["target"]; - if(stripos($server, "outlook.com") !== false) { + if (stripos($server, "outlook.com") !== false) { $this->mailer = 'postmark'; $this->client_postmark_secret = config('services.postmark-outlook.token'); @@ -293,13 +298,13 @@ class NinjaMailerJob implements ShouldQueue } $this->nmo - ->mailable - ->from(config('services.postmark-outlook.from.address'), $email_from_name); + ->mailable + ->from(config('services.postmark-outlook.from.address'), $email_from_name); return $this; } - } catch(\Exception $e) { - + } catch (\Exception $e) { + nlog("problem switching outlook driver - hosted"); nlog($e->getMessage()); } @@ -331,6 +336,10 @@ class NinjaMailerJob implements ShouldQueue $this->mailer = 'mailgun'; $this->setMailgunMailer(); return $this; + case 'client_brevo': + $this->mailer = 'brevo'; + $this->setBrevoMailer(); + return $this; case 'smtp': $this->mailer = 'smtp'; $this->configureSmtpMailer(); @@ -380,11 +389,11 @@ class NinjaMailerJob implements ShouldQueue } $user = $this->resolveSendingUser(); - $sending_email = (isset($this->nmo->settings->custom_sending_email) && stripos($this->nmo->settings->custom_sending_email, "@")) ? $this->nmo->settings->custom_sending_email : $user->email; + $sending_email = (isset ($this->nmo->settings->custom_sending_email) && stripos($this->nmo->settings->custom_sending_email, "@")) ? $this->nmo->settings->custom_sending_email : $user->email; $this->nmo - ->mailable - ->from($sending_email, $email_from_name); + ->mailable + ->from($sending_email, $email_from_name); } @@ -407,8 +416,8 @@ class NinjaMailerJob implements ShouldQueue if (env($this->company->id . '_MAIL_FROM_ADDRESS')) { $this->nmo - ->mailable - ->from(env($this->company->id . '_MAIL_FROM_ADDRESS', env('MAIL_FROM_ADDRESS')), env($this->company->id . '_MAIL_FROM_NAME', env('MAIL_FROM_NAME'))); + ->mailable + ->from(env($this->company->id . '_MAIL_FROM_ADDRESS', env('MAIL_FROM_ADDRESS')), env($this->company->id . '_MAIL_FROM_NAME', env('MAIL_FROM_NAME'))); } } } @@ -426,6 +435,8 @@ class NinjaMailerJob implements ShouldQueue $this->client_mailgun_domain = false; + $this->client_brevo_secret = false; + //always dump the drivers to prevent reuse app('mail.manager')->forgetMailers(); } @@ -475,8 +486,8 @@ class NinjaMailerJob implements ShouldQueue } $this->nmo - ->mailable - ->from(config('services.mailgun.from.address'), $email_from_name); + ->mailable + ->from(config('services.mailgun.from.address'), $email_from_name); } @@ -496,12 +507,35 @@ class NinjaMailerJob implements ShouldQueue $user = $this->resolveSendingUser(); - $sending_email = (isset($this->nmo->settings->custom_sending_email) && stripos($this->nmo->settings->custom_sending_email, "@")) ? $this->nmo->settings->custom_sending_email : $user->email; - $sending_user = (isset($this->nmo->settings->email_from_name) && strlen($this->nmo->settings->email_from_name) > 2) ? $this->nmo->settings->email_from_name : $user->name(); + $sending_email = (isset ($this->nmo->settings->custom_sending_email) && stripos($this->nmo->settings->custom_sending_email, "@")) ? $this->nmo->settings->custom_sending_email : $user->email; + $sending_user = (isset ($this->nmo->settings->email_from_name) && strlen($this->nmo->settings->email_from_name) > 2) ? $this->nmo->settings->email_from_name : $user->name(); $this->nmo - ->mailable - ->from($sending_email, $sending_user); + ->mailable + ->from($sending_email, $sending_user); + } + + /** + * Configures Brevo using client supplied secret + * as the Mailer + */ + private function setBrevoMailer() + { + if (strlen($this->nmo->settings->brevo_secret) > 2) { + $this->client_brevo_secret = $this->nmo->settings->brevo_secret; + } else { + $this->nmo->settings->email_sending_method = 'default'; + return $this->setMailDriver(); + } + + $user = $this->resolveSendingUser(); + + $sending_email = (isset ($this->nmo->settings->custom_sending_email) && stripos($this->nmo->settings->custom_sending_email, "@")) ? $this->nmo->settings->custom_sending_email : $user->email; + $sending_user = (isset ($this->nmo->settings->email_from_name) && strlen($this->nmo->settings->email_from_name) > 2) ? $this->nmo->settings->email_from_name : $user->name(); + + $this->nmo + ->mailable + ->from($sending_email, $sending_user); } /** @@ -519,12 +553,12 @@ class NinjaMailerJob implements ShouldQueue $user = $this->resolveSendingUser(); - $sending_email = (isset($this->nmo->settings->custom_sending_email) && stripos($this->nmo->settings->custom_sending_email, "@")) ? $this->nmo->settings->custom_sending_email : $user->email; - $sending_user = (isset($this->nmo->settings->email_from_name) && strlen($this->nmo->settings->email_from_name) > 2) ? $this->nmo->settings->email_from_name : $user->name(); + $sending_email = (isset ($this->nmo->settings->custom_sending_email) && stripos($this->nmo->settings->custom_sending_email, "@")) ? $this->nmo->settings->custom_sending_email : $user->email; + $sending_user = (isset ($this->nmo->settings->email_from_name) && strlen($this->nmo->settings->email_from_name) > 2) ? $this->nmo->settings->email_from_name : $user->name(); $this->nmo - ->mailable - ->from($sending_email, $sending_user); + ->mailable + ->from($sending_email, $sending_user); } /** @@ -550,11 +584,11 @@ class NinjaMailerJob implements ShouldQueue } $this->nmo - ->mailable - ->from($user->email, $user->name()) - ->withSymfonyMessage(function ($message) use ($token) { - $message->getHeaders()->addTextHeader('gmailtoken', $token); - }); + ->mailable + ->from($user->email, $user->name()) + ->withSymfonyMessage(function ($message) use ($token) { + $message->getHeaders()->addTextHeader('gmailtoken', $token); + }); } /** @@ -578,7 +612,7 @@ class NinjaMailerJob implements ShouldQueue } $google->getClient()->setAccessToken(json_encode($user->oauth_user_token)); - } catch(\Exception $e) { + } catch (\Exception $e) { $this->logMailError('Gmail Token Invalid', $this->company->clients()->first()); $this->nmo->settings->email_sending_method = 'default'; return $this->setMailDriver(); @@ -598,7 +632,7 @@ class NinjaMailerJob implements ShouldQueue * Now that our token is refreshed and valid we can boot the * mail driver at runtime and also set the token which will persist * just for this request. - */ + */ $token = $user->oauth_user_token->access_token; @@ -609,11 +643,11 @@ class NinjaMailerJob implements ShouldQueue } $this->nmo - ->mailable - ->from($user->email, $user->name()) - ->withSymfonyMessage(function ($message) use ($token) { - $message->getHeaders()->addTextHeader('gmailtoken', $token); - }); + ->mailable + ->from($user->email, $user->name()) + ->withSymfonyMessage(function ($message) use ($token) { + $message->getHeaders()->addTextHeader('gmailtoken', $token); + }); } /** @@ -626,7 +660,7 @@ class NinjaMailerJob implements ShouldQueue private function preFlightChecksFail(): bool { /* Always send regardless */ - if($this->override) { + if ($this->override) { return false; } @@ -646,7 +680,7 @@ class NinjaMailerJob implements ShouldQueue } /* GMail users are uncapped */ - if (Ninja::isHosted() && (in_array($this->nmo->settings->email_sending_method, ['gmail', 'office365', 'client_postmark', 'client_mailgun']))) { + if (Ninja::isHosted() && (in_array($this->nmo->settings->email_sending_method, ['gmail', 'office365', 'client_postmark', 'client_mailgun', 'client_brevo']))) { return false; } @@ -690,21 +724,23 @@ class NinjaMailerJob implements ShouldQueue */ private function logMailError($errors, $recipient_object): void { - (new SystemLogger( - $errors, - SystemLog::CATEGORY_MAIL, - SystemLog::EVENT_MAIL_SEND, - SystemLog::TYPE_FAILURE, - $recipient_object, - $this->nmo->company - ))->handle(); + ( + new SystemLogger( + $errors, + SystemLog::CATEGORY_MAIL, + SystemLog::EVENT_MAIL_SEND, + SystemLog::TYPE_FAILURE, + $recipient_object, + $this->nmo->company + ) + )->handle(); $job_failure = new EmailFailure($this->nmo->company->company_key); $job_failure->string_metric5 = 'failed_email'; $job_failure->string_metric6 = substr($errors, 0, 150); LightLogs::create($job_failure) - ->send(); + ->send(); $job_failure = null; } @@ -729,8 +765,8 @@ class NinjaMailerJob implements ShouldQueue $token = json_decode($guzzle->post($url, [ 'form_params' => [ - 'client_id' => config('ninja.o365.client_id') , - 'client_secret' => config('ninja.o365.client_secret') , + 'client_id' => config('ninja.o365.client_id'), + 'client_secret' => config('ninja.o365.client_secret'), 'scope' => 'email Mail.Send offline_access profile User.Read openid', 'grant_type' => 'refresh_token', 'refresh_token' => $user->oauth_user_refresh_token diff --git a/app/Jobs/Ninja/BankTransactionSync.php b/app/Jobs/Ninja/BankTransactionSync.php index 89a0b33e4df7..a0d077a570c8 100644 --- a/app/Jobs/Ninja/BankTransactionSync.php +++ b/app/Jobs/Ninja/BankTransactionSync.php @@ -74,7 +74,7 @@ class BankTransactionSync implements ShouldQueue if ($account->isEnterprisePaidClient()) { $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_YODLEE)->where('auto_sync', true)->where('disabled_upstream', 0)->cursor()->each(function ($bank_integration) use ($account) { - (new ProcessBankTransactionsYodlee($account->id, $bank_integration))->handle(); + (new ProcessBankTransactionsYodlee($account->bank_integration_account_id, $bank_integration))->handle(); }); } @@ -90,7 +90,14 @@ class BankTransactionSync implements ShouldQueue if ((Ninja::isSelfHost() || (Ninja::isHosted() && $account->isEnterprisePaidClient()))) { $account->bank_integrations()->where('integration_type', BankIntegration::INTEGRATION_TYPE_NORDIGEN)->where('auto_sync', true)->where('disabled_upstream', 0)->cursor()->each(function ($bank_integration) { - (new ProcessBankTransactionsNordigen($bank_integration))->handle(); + try { + (new ProcessBankTransactionsNordigen($bank_integration))->handle(); + } + catch(\Exception $e) { + sleep(20); + } + + sleep(5); }); } diff --git a/app/Jobs/PostMark/ProcessPostmarkWebhook.php b/app/Jobs/PostMark/ProcessPostmarkWebhook.php index 963a4c64f8a9..1664cbafdcc7 100644 --- a/app/Jobs/PostMark/ProcessPostmarkWebhook.php +++ b/app/Jobs/PostMark/ProcessPostmarkWebhook.php @@ -45,7 +45,7 @@ class ProcessPostmarkWebhook implements ShouldQueue private $entity; - private array $default_response = [ + private array $default_response = [ 'recipients' => '', 'subject' => 'Message not found.', 'entity' => '', @@ -53,6 +53,8 @@ class ProcessPostmarkWebhook implements ShouldQueue 'events' => [], ]; + private ?Company $company = null; + /** * Create a new job instance. * @@ -64,11 +66,11 @@ class ProcessPostmarkWebhook implements ShouldQueue private function getSystemLog(string $message_id): ?SystemLog { return SystemLog::query() - ->where('company_id', $this->invitation->company_id) - ->where('type_id', SystemLog::TYPE_WEBHOOK_RESPONSE) - ->whereJsonContains('log', ['MessageID' => $message_id]) - ->orderBy('id', 'desc') - ->first(); + ->where('company_id', $this->invitation->company_id) + ->where('type_id', SystemLog::TYPE_WEBHOOK_RESPONSE) + ->whereJsonContains('log', ['MessageID' => $message_id]) + ->orderBy('id', 'desc') + ->first(); } @@ -87,12 +89,12 @@ class ProcessPostmarkWebhook implements ShouldQueue public function handle() { MultiDB::findAndSetDbByCompanyKey($this->request['Tag']); - $company = Company::where('company_key', $this->request['Tag'])->first(); + $this->company = Company::where('company_key', $this->request['Tag'])->first(); $this->invitation = $this->discoverInvitation($this->request['MessageID']); - if ($company && $this->request['RecordType'] == 'SpamComplaint' && config('ninja.notification.slack')) { - $company->notification(new EmailSpamNotification($company))->ninja(); + if ($this->company && $this->request['RecordType'] == 'SpamComplaint' && config('ninja.notification.slack')) { + $this->company->notification(new EmailSpamNotification($this->company))->ninja(); } if (!$this->invitation) { @@ -108,8 +110,8 @@ class ProcessPostmarkWebhook implements ShouldQueue return $this->processDelivery(); case 'Bounce': - if($this->request['Subject'] == ctrans('texts.confirmation_subject')) { - $company->notification(new EmailBounceNotification($this->request['Email']))->ninja(); + if ($this->request['Subject'] == ctrans('texts.confirmation_subject')) { + $this->company->notification(new EmailBounceNotification($this->request['Email']))->ninja(); } return $this->processBounce(); @@ -169,19 +171,21 @@ class ProcessPostmarkWebhook implements ShouldQueue $sl = $this->getSystemLog($this->request['MessageID']); - if($sl) { + if ($sl) { $this->updateSystemLog($sl, $data); return; } - (new SystemLogger( - $data, - SystemLog::CATEGORY_MAIL, - SystemLog::EVENT_MAIL_OPENED, - SystemLog::TYPE_WEBHOOK_RESPONSE, - $this->invitation->contact->client, - $this->invitation->company - ))->handle(); + ( + new SystemLogger( + $data, + SystemLog::CATEGORY_MAIL, + SystemLog::EVENT_MAIL_OPENED, + SystemLog::TYPE_WEBHOOK_RESPONSE, + $this->invitation->contact->client, + $this->invitation->company + ) + )->handle(); } // { @@ -207,19 +211,21 @@ class ProcessPostmarkWebhook implements ShouldQueue $sl = $this->getSystemLog($this->request['MessageID']); - if($sl) { + if ($sl) { $this->updateSystemLog($sl, $data); return; } - (new SystemLogger( - $data, - SystemLog::CATEGORY_MAIL, - SystemLog::EVENT_MAIL_DELIVERY, - SystemLog::TYPE_WEBHOOK_RESPONSE, - $this->invitation->contact->client, - $this->invitation->company - ))->handle(); + ( + new SystemLogger( + $data, + SystemLog::CATEGORY_MAIL, + SystemLog::EVENT_MAIL_DELIVERY, + SystemLog::TYPE_WEBHOOK_RESPONSE, + $this->invitation->contact->client, + $this->invitation->company + ) + )->handle(); } // { @@ -265,7 +271,7 @@ class ProcessPostmarkWebhook implements ShouldQueue $sl = $this->getSystemLog($this->request['MessageID']); - if($sl) { + if ($sl) { $this->updateSystemLog($sl, $data); return; } @@ -316,7 +322,7 @@ class ProcessPostmarkWebhook implements ShouldQueue $sl = $this->getSystemLog($this->request['MessageID']); - if($sl) { + if ($sl) { $this->updateSystemLog($sl, $data); } @@ -349,7 +355,9 @@ class ProcessPostmarkWebhook implements ShouldQueue public function getRawMessage(string $message_id) { - $postmark = new PostmarkClient(config('services.postmark.token')); + $postmark_secret = !empty($this->company->settings->postmark_secret) ? $this->company->settings->postmark_secret : config('services.postmark.token'); + + $postmark = new PostmarkClient($postmark_secret); $messageDetail = $postmark->getOutboundMessageDetails($message_id); return $messageDetail; @@ -362,7 +370,7 @@ class ProcessPostmarkWebhook implements ShouldQueue $messageDetail = $this->getRawMessage($message_id); - $event = collect($messageDetail->messageevents)->first(function ($event) { + $event = collect($messageDetail->messageevents)->first(function ($event) { return $event?->Details?->BounceID ?? false; @@ -374,29 +382,31 @@ class ProcessPostmarkWebhook implements ShouldQueue private function fetchMessage(): array { - if(strlen($this->request['MessageID']) < 1) { + if (strlen($this->request['MessageID']) < 1) { return $this->default_response; } try { - $postmark = new PostmarkClient(config('services.postmark.token')); + $postmark_secret = !empty($this->company->settings->postmark_secret) ? $this->company->settings->postmark_secret : config('services.postmark.token'); + + $postmark = new PostmarkClient($postmark_secret); $messageDetail = $postmark->getOutboundMessageDetails($this->request['MessageID']); $recipients = collect($messageDetail['recipients'])->flatten()->implode(','); $subject = $messageDetail->subject ?? ''; - $events = collect($messageDetail->messageevents)->map(function ($event) { + $events = collect($messageDetail->messageevents)->map(function ($event) { return [ - 'bounce_id' => $event?->Details?->BounceID ?? '', - 'recipient' => $event->Recipient ?? '', - 'status' => $event->Type ?? '', - 'delivery_message' => $event->Details->DeliveryMessage ?? $event->Details->Summary ?? '', - 'server' => $event->Details->DestinationServer ?? '', - 'server_ip' => $event->Details->DestinationIP ?? '', - 'date' => \Carbon\Carbon::parse($event->ReceivedAt)->format('Y-m-d H:i:s') ?? '', - ]; + 'bounce_id' => $event?->Details?->BounceID ?? '', + 'recipient' => $event->Recipient ?? '', + 'status' => $event->Type ?? '', + 'delivery_message' => $event->Details->DeliveryMessage ?? $event->Details->Summary ?? '', + 'server' => $event->Details->DestinationServer ?? '', + 'server_ip' => $event->Details->DestinationIP ?? '', + 'date' => \Carbon\Carbon::parse($event->ReceivedAt)->format('Y-m-d H:i:s') ?? '', + ]; })->toArray(); diff --git a/app/Jobs/PurchaseOrder/ZipPurchaseOrders.php b/app/Jobs/PurchaseOrder/ZipPurchaseOrders.php index 76a91d6beed3..401e2e5f48e5 100644 --- a/app/Jobs/PurchaseOrder/ZipPurchaseOrders.php +++ b/app/Jobs/PurchaseOrder/ZipPurchaseOrders.php @@ -67,6 +67,11 @@ class ZipPurchaseOrders implements ShouldQueue try { foreach ($invitations as $invitation) { + if ($invitation->purchase_order->vendor->getSetting("enable_e_invoice")) { + $xml = $invitation->purchase_order->service()->getEInvoice(); + $zipFile->addFromString($invitation->purchase_order->getFileName("xml"), $xml); + } + $file = (new CreateRawPdf($invitation))->handle(); $zipFile->addFromString($invitation->purchase_order->numberFormatter().".pdf", $file); diff --git a/app/Jobs/Quote/ZipQuotes.php b/app/Jobs/Quote/ZipQuotes.php index a39c7615a4ce..fad437718993 100644 --- a/app/Jobs/Quote/ZipQuotes.php +++ b/app/Jobs/Quote/ZipQuotes.php @@ -63,6 +63,10 @@ class ZipQuotes implements ShouldQueue try { foreach ($invitations as $invitation) { + if ($invitation->quote->client->getSetting('enable_e_invoice')) { + $xml = $invitation->quote->service()->getEInvoice(); + $zipFile->addFromString($invitation->quote->getFileName("xml"), $xml); + } $file = (new \App\Jobs\Entity\CreateRawPdf($invitation))->handle(); $zipFile->addFromString($invitation->quote->numberFormatter() . '.pdf', $file); } diff --git a/app/Jobs/RecurringInvoice/SendRecurring.php b/app/Jobs/RecurringInvoice/SendRecurring.php index def24172b528..5bed2111b564 100644 --- a/app/Jobs/RecurringInvoice/SendRecurring.php +++ b/app/Jobs/RecurringInvoice/SendRecurring.php @@ -76,12 +76,12 @@ class SendRecurring implements ShouldQueue $invoice = $invoice->service() ->markSent() ->applyNumber() - ->fillDefaults() + ->fillDefaults(true) ->adjustInventory() ->save(); } else { $invoice = $invoice->service() - ->fillDefaults() + ->fillDefaults(true) ->save(); } diff --git a/app/Libraries/MultiDB.php b/app/Libraries/MultiDB.php index fd028a03811c..aaa48916ee47 100644 --- a/app/Libraries/MultiDB.php +++ b/app/Libraries/MultiDB.php @@ -21,6 +21,7 @@ use Illuminate\Support\Str; use App\Models\CompanyToken; use App\Models\ClientContact; use App\Models\VendorContact; +use App\DataProviders\SMSNumbers; use Illuminate\Support\Facades\DB; /** @@ -537,6 +538,10 @@ class MultiDB $current_db = config('database.default'); + if(SMSNumbers::hasNumber($phone)){ + return true; + } + foreach (self::$dbs as $db) { self::setDB($db); if ($exists = Account::where('account_sms_verification_number', $phone)->where('account_sms_verified', true)->exists()) { diff --git a/app/Livewire/PdfSlot.php b/app/Livewire/PdfSlot.php index 1322d6a9664f..81ad1fab61c1 100644 --- a/app/Livewire/PdfSlot.php +++ b/app/Livewire/PdfSlot.php @@ -12,7 +12,7 @@ namespace App\Livewire; -use App\Jobs\Invoice\CreateEInvoice; +use App\Jobs\EDocument\CreateEDocument; use App\Libraries\MultiDB; use App\Models\CreditInvitation; use App\Models\InvoiceInvitation; @@ -113,7 +113,7 @@ class PdfSlot extends Component $file_name = $this->entity->numberFormatter().'.xml'; - $file = (new CreateEInvoice($this->entity))->handle(); + $file = (new CreateEDocument($this->entity))->handle(); $headers = ['Content-Type' => 'application/xml']; diff --git a/app/Livewire/RequiredClientInfo.php b/app/Livewire/RequiredClientInfo.php index 3d045ef81f4c..240d803c8366 100644 --- a/app/Livewire/RequiredClientInfo.php +++ b/app/Livewire/RequiredClientInfo.php @@ -223,8 +223,11 @@ class RequiredClientInfo extends Component $this->show_form = true; $hash = Cache::get(request()->input('hash')); + + /** @var \App\Models\Invoice $invoice */ + $invoice = Invoice::find($this->decodePrimaryKey($hash['invoice_id'])); - $this->invoice_terms = Invoice::find($this->decodePrimaryKey($hash['invoice_id']))->terms; + $this->invoice_terms = $invoice->terms; } count($this->fields) > 0 || $this->show_terms diff --git a/app/Mail/TemplateEmail.php b/app/Mail/TemplateEmail.php index 9beac3dbe3b2..da9cfa4fbfe1 100644 --- a/app/Mail/TemplateEmail.php +++ b/app/Mail/TemplateEmail.php @@ -140,7 +140,7 @@ class TemplateEmail extends Mailable 'whitelabel' => $this->client->user->account->isPaid() ? true : false, 'logo' => $this->company->present()->logo($settings), 'links' => $this->build_email->getAttachmentLinks(), - 'email_preferences' => (Ninja::isHosted() && in_array($settings->email_sending_method, ['default', 'mailgun'])) ? $this->company->domain() . URL::signedRoute('client.email_preferences', ['entity' => $this->invitation->getEntityString(), 'invitation_key' => $this->invitation->key], absolute: false) : false, + 'email_preferences' => (Ninja::isHosted() && $this->invitation && in_array($settings->email_sending_method, ['default', 'mailgun'])) ? $this->company->domain() . URL::signedRoute('client.email_preferences', ['entity' => $this->invitation->getEntityString(), 'invitation_key' => $this->invitation->key], absolute: false) : false, ]); foreach ($this->build_email->getAttachments() as $file) { @@ -159,15 +159,46 @@ class TemplateEmail extends Mailable } } - if ($this->invitation && $this->invitation->invoice && $this->invitation->invoice->client->getSetting('enable_e_invoice') && $this->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) { - $xml_string = $this->invitation->invoice->service()->getEInvoice($this->invitation->contact); + if ($this->invitation->invoice) { + if ($this->invitation && $this->invitation->invoice && $this->invitation->invoice->client->getSetting('enable_e_invoice') && $this->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) { + $xml_string = $this->invitation->invoice->service()->getEInvoice($this->invitation->contact); + + if ($xml_string) { + $this->attachData($xml_string, $this->invitation->invoice->getEFileName("xml")); + } - if($xml_string) { - $this->attachData($xml_string, $this->invitation->invoice->getEFileName("xml")); } - } + elseif ($this->invitation->credit){ + if ($this->invitation && $this->invitation->credit && $this->invitation->credit->client->getSetting('enable_e_invoice') && $this->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) { + $xml_string = $this->invitation->credit->service()->getECredit($this->invitation->contact); + if ($xml_string) { + $this->attachData($xml_string, $this->invitation->credit->getEFileName("xml")); + } + + } + } + elseif ($this->invitation->quote){ + if ($this->invitation && $this->invitation->quote && $this->invitation->quote->client->getSetting('enable_e_invoice') && $this->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) { + $xml_string = $this->invitation->quote->service()->getEQuote($this->invitation->contact); + + if ($xml_string) { + $this->attachData($xml_string, $this->invitation->quote->getEFileName("xml")); + } + + } + } + elseif ($this->invitation->purchase_order){ + if ($this->invitation && $this->invitation->purchase_order && $this->invitation->purchase_order->client->getSetting('enable_e_invoice') && $this->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) { + $xml_string = $this->invitation->purchase_order->service()->getEPurchaseOrder($this->invitation->contact); + + if ($xml_string) { + $this->attachData($xml_string, $this->invitation->purchase_order->getEFileName("xml")); + } + + } + } return $this; } } diff --git a/app/Models/Account.php b/app/Models/Account.php index 382eec8692ee..adf95c62086f 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -102,7 +102,7 @@ class Account extends BaseModel private $free_plan_email_quota = 20; - private $paid_plan_email_quota = 400; + private $paid_plan_email_quota = 300; /** * @var string @@ -494,7 +494,7 @@ class Account extends BaseModel return 0; } - if (Carbon::createFromTimestamp($this->created_at)->diffInWeeks() == 0) { + if (Carbon::createFromTimestamp($this->created_at)->diffInWeeks() <= 1) { return 20; } @@ -503,11 +503,13 @@ class Account extends BaseModel } if ($this->isPaid()) { + $multiplier = $this->plan == 'enterprise' ? 2 : 1.2; + $limit = $this->paid_plan_email_quota; - $limit += Carbon::createFromTimestamp($this->created_at)->diffInMonths() * 50; + $limit += Carbon::createFromTimestamp($this->created_at)->diffInMonths() * (20 * $multiplier); } else { $limit = $this->free_plan_email_quota; - $limit += Carbon::createFromTimestamp($this->created_at)->diffInMonths() * 2; + $limit += Carbon::createFromTimestamp($this->created_at)->diffInMonths() * 1.5; } return min($limit, 1000); diff --git a/app/Models/Client.php b/app/Models/Client.php index 61bda7230946..8525cde2784f 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -485,7 +485,7 @@ class Client extends BaseModel implements HasLocalePreference } /*Company Settings*/ - elseif ((property_exists($this->company->settings, $setting) != false) && (isset($this->company->settings->{$setting}) !== false)) { + elseif ((property_exists($this->company->settings, $setting) !== false) && (isset($this->company->settings->{$setting}) !== false)) { return $this->company->settings->{$setting}; } elseif (property_exists(CompanySettings::defaults(), $setting)) { return CompanySettings::defaults()->{$setting}; @@ -754,7 +754,7 @@ class Client extends BaseModel implements HasLocalePreference return $this->company->company_key.'/'.$this->client_hash.'/'.$contact_key.'/invoices/'; } - public function e_invoice_filepath($invitation): string + public function e_document_filepath($invitation): string { $contact_key = $invitation->contact->contact_key; diff --git a/app/Models/CompanyGateway.php b/app/Models/CompanyGateway.php index 253af5585fe6..aa8f0438e5a2 100644 --- a/app/Models/CompanyGateway.php +++ b/app/Models/CompanyGateway.php @@ -132,6 +132,7 @@ class CompanyGateway extends BaseModel // const TYPE_EWAY = 313; // const TYPE_FORTE = 314; // const PAYPAL_PPCP = 323; + // const SQUARE = 320; public $gateway_consts = [ '38f2c48af60c7dd69e04248cbb24c36e' => 300, @@ -144,7 +145,7 @@ class CompanyGateway extends BaseModel '8fdeed552015b3c7b44ed6c8ebd9e992' => 309, 'd6814fc83f45d2935e7777071e629ef9' => 310, 'bbd736b3254b0aabed6ad7fda1298c88' => 311, - '1bd651fb213ca0c9d66ae3c336dc77e7' => 312, + '1bd651fb213ca0c9d66ae3c336dc77e8' => 312, '944c20175bbe6b9972c05bcfe294c2c7' => 313, 'kivcvjexxvdiyqtj3mju5d6yhpeht2xs' => 314, '65faab2ab6e3223dbe848b1686490baz' => 320, diff --git a/app/Models/CompanyUser.php b/app/Models/CompanyUser.php index c9e3aa74b9cd..4f8352e23bd9 100644 --- a/app/Models/CompanyUser.php +++ b/app/Models/CompanyUser.php @@ -202,7 +202,6 @@ class CompanyUser extends Pivot */ public function portalType(): bool { - nlog(isset($this->react_settings->react_notification_link) && $this->react_settings->react_notification_link); return isset($this->react_settings->react_notification_link) && $this->react_settings->react_notification_link; } diff --git a/app/Models/RecurringInvoice.php b/app/Models/RecurringInvoice.php index c87375b3dcf6..61bfda77f8b3 100644 --- a/app/Models/RecurringInvoice.php +++ b/app/Models/RecurringInvoice.php @@ -698,7 +698,7 @@ class RecurringInvoice extends BaseModel public function subscription(): \Illuminate\Database\Eloquent\Relations\BelongsTo { - return $this->belongsTo(Subscription::class); + return $this->belongsTo(Subscription::class)->withTrashed(); } public function translate_entity() diff --git a/app/Models/Task.php b/app/Models/Task.php index f1716b15a870..a5d63760c19d 100644 --- a/app/Models/Task.php +++ b/app/Models/Task.php @@ -12,6 +12,7 @@ namespace App\Models; use App\Utils\Traits\MakesHash; +use Carbon\CarbonInterval; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Carbon; @@ -248,6 +249,7 @@ class Task extends BaseModel $duration += max($end_time - $start_time, 0); } + // return CarbonInterval::seconds(round($duration))->locale($this->company->locale())->cascade()->forHumans(); return round($duration); } diff --git a/app/Models/User.php b/app/Models/User.php index 35441ff43d89..7d8e2474e45d 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -226,6 +226,7 @@ class User extends Authenticatable implements MustVerifyEmail return $truth->getCompanyToken(); } + // if (request()->header('X-API-TOKEN')) { if (request()->header('X-API-TOKEN')) { return CompanyToken::with(['cu'])->where('token', request()->header('X-API-TOKEN'))->first(); } diff --git a/app/Models/Vendor.php b/app/Models/Vendor.php index e5b78ddc72e9..a06b71a3af3d 100644 --- a/app/Models/Vendor.php +++ b/app/Models/Vendor.php @@ -41,6 +41,7 @@ use Laracasts\Presenter\PresentableTrait; * @property string|null $phone * @property string|null $private_notes * @property string|null $website + * @property string|null $routing_id * @property bool $is_deleted * @property string|null $vat_number * @property string|null $transaction_name diff --git a/app/PaymentDrivers/AuthorizePaymentDriver.php b/app/PaymentDrivers/AuthorizePaymentDriver.php index a9ac9d568367..87c74fdb9209 100644 --- a/app/PaymentDrivers/AuthorizePaymentDriver.php +++ b/app/PaymentDrivers/AuthorizePaymentDriver.php @@ -193,6 +193,18 @@ class AuthorizePaymentDriver extends BaseDriver public function import() { + $this->init(); + return (new AuthorizeCustomer($this))->importCustomers(); } + + public function importCustomers() + { + return $this->import(); + } + + public function auth(): bool + { + return $this->init()->getPublicClientKey() ?? false; + } } diff --git a/app/PaymentDrivers/BaseDriver.php b/app/PaymentDrivers/BaseDriver.php index 4b4ed2488441..3be4137148e1 100644 --- a/app/PaymentDrivers/BaseDriver.php +++ b/app/PaymentDrivers/BaseDriver.php @@ -579,15 +579,18 @@ class BaseDriver extends AbstractPaymentDriver $nmo->company = $this->client->company; $nmo->settings = $this->client->company->settings; - $invoices = Invoice::query()->whereIn('id', $this->transformKeys(array_column($this->payment_hash->invoices(), 'invoice_id')))->withTrashed()->get(); - - $invoices->first()->invitations->each(function ($invitation) use ($nmo) { - if (! $invitation->contact->trashed()) { - $nmo->to_user = $invitation->contact; - NinjaMailerJob::dispatch($nmo); - } - }); + if($this->payment_hash) + { + $invoices = Invoice::query()->whereIn('id', $this->transformKeys(array_column($this->payment_hash->invoices(), 'invoice_id')))->withTrashed()->get(); + $invoices->first()->invitations->each(function ($invitation) use ($nmo) { + if (! $invitation->contact->trashed()) { + $nmo->to_user = $invitation->contact; + NinjaMailerJob::dispatch($nmo); + } + }); + } + $message = [ 'server_response' => $response, 'data' => $this->payment_hash->data, @@ -806,4 +809,14 @@ class BaseDriver extends AbstractPaymentDriver { return true; } + + public function auth(): bool + { + return true; + } + + public function importCustomers() + { + + } } diff --git a/app/PaymentDrivers/BraintreePaymentDriver.php b/app/PaymentDrivers/BraintreePaymentDriver.php index 6f90f0cd1482..cb214f62be02 100644 --- a/app/PaymentDrivers/BraintreePaymentDriver.php +++ b/app/PaymentDrivers/BraintreePaymentDriver.php @@ -12,29 +12,40 @@ namespace App\PaymentDrivers; -use App\Jobs\Util\SystemLogger; -use App\Models\ClientGatewayToken; -use App\Models\GatewayType; +use Exception; +use App\Models\Client; +use Braintree\Gateway; use App\Models\Invoice; use App\Models\Payment; +use App\Models\SystemLog; +use App\Models\GatewayType; use App\Models\PaymentHash; use App\Models\PaymentType; -use App\Models\SystemLog; +use App\Models\ClientContact; +use App\Factory\ClientFactory; +use Illuminate\Support\Carbon; +use App\Jobs\Util\SystemLogger; +use App\Models\ClientGatewayToken; +use App\Factory\ClientContactFactory; use App\PaymentDrivers\Braintree\ACH; -use App\PaymentDrivers\Braintree\CreditCard; +use App\Utils\Traits\GeneratesCounter; +use Illuminate\Database\QueryException; use App\PaymentDrivers\Braintree\PayPal; -use Braintree\Gateway; -use Exception; use Illuminate\Support\Facades\Validator; +use App\PaymentDrivers\Braintree\CreditCard; class BraintreePaymentDriver extends BaseDriver { + use GeneratesCounter; + public $refundable = true; public $token_billing = true; public $can_authorise_credit_card = true; + private bool $completed = true; + /** * @var Gateway; */ @@ -157,34 +168,6 @@ class BraintreePaymentDriver extends BaseDriver } } - // public function updateCustomer() - // { - // $customer = $this->findOrCreateCustomer(); - - // $result = $this->gateway->customer()->update( - // $customer->id, - // [ - // 'firstName' => $this->client->present()->name(), - // 'email' => $this->client->present()->email(), - // 'phone' => $this->client->present()->phone(), - // 'creditCard' => [ - // 'billingAddress' => [ - // 'options' => [ - // 'updateExisting' => true - // ], - // 'firstName' => $this->client->present()->first_name() ?: $this->client->present()->name(), - // 'lastName' => $this->client->present()->last_name() ?: '', - // 'streetAddress' => $this->client->address1 ?: '', - // 'extendedAddress' =>$this->client->address2 ?: '', - // 'locality' => $this->client->city ?: '', - // 'postalCode' => $this->client->postal_code ?: '', - // 'countryCodeAlpha2' => $this->client->country ? $this->client->country->iso_3166_2 : 'US', - // ], - // ], - // ] - // ); - // } - public function refund(Payment $payment, $amount, $return_client_response = false) { $this->init(); @@ -324,13 +307,198 @@ class BraintreePaymentDriver extends BaseDriver nlog('braintree webhook'); - // if($webhookNotification) - // nlog($webhookNotification->kind); - - // // Example values for webhook notification properties - // $message = $webhookNotification->kind; // "subscription_went_past_due" - // $message = $webhookNotification->timestamp->format('D M j G:i:s T Y'); // "Sun Jan 1 00:00:00 UTC 2012" - return response()->json([], 200); } + + public function auth(): bool + { + + try { + $ct =$this->init()->gateway->clientToken()->generate(); + + return true; + } + catch(\Exception $e) { + + } + + return false; + } + + private function find(string $customer_id = '') { + + try { + return $this->init()->gateway->customer()->find($customer_id); + } + catch(\Exception $e){ + return false; + } + + return false; + } + + private function findTokens(string $gateway_customer_reference) + { + return ClientGatewayToken::where('company_id', $this->company_gateway->company_id) + ->where('gateway_customer_reference', $gateway_customer_reference) + ->exists(); + } + + private function getToken(string $token, string $gateway_customer_reference) + { + + return ClientGatewayToken::where('company_id', $this->company_gateway->company_id) + ->where('gateway_customer_reference', $gateway_customer_reference) + ->where('token', $token) + ->first(); + + } + + private function findClient(string $email) { + return ClientContact::where('company_id', $this->company_gateway->company_id) + ->where('email', $email) + ->first()->client ?? false; + } + + private function addClientCards(Client $client, array $cards) + { + + $this->client = $client; + + foreach($cards as $card) { + + if($this->getToken($card->token, $card->customerId) || Carbon::createFromDate($card->expirationYear, $card->expirationMonth, '1')->lt(now())) + continue; + + $payment_meta = new \stdClass(); + $payment_meta->exp_month = (string) $card->expirationMonth; + $payment_meta->exp_year = (string) $card->expirationYear; + $payment_meta->brand = (string) $card->cardType; + $payment_meta->last4 = (string) $card->last4; + $payment_meta->type = GatewayType::CREDIT_CARD; + + $data = [ + 'payment_meta' => $payment_meta, + 'token' => $card->token, + 'payment_method_id' => GatewayType::CREDIT_CARD, + ]; + + $this->storeGatewayToken($data, ['gateway_customer_reference' => $card->customerId]); + + nlog("adding card to customer payment profile"); + + } + + } + + public function createNinjaClient(mixed $customer): Client + { + + $client = ClientFactory::create($this->company_gateway->company_id, $this->company_gateway->user_id); + + $b_business_address = count($customer->addresses) >= 1 ? $customer->addresses[0] : false; + $b_shipping_address = count($customer->addresses) > 1 ? $customer->addresses[1] : false; + $import_client_data = []; + + if($b_business_address) { + + $braintree_address = + [ + 'address1' => $b_business_address->extendedAddress ?? '', + 'address2' => $b_business_address->streetAddress ?? '', + 'city' => $b_business_address->locality ?? '', + 'postal_code' => $b_business_address->postalCode ?? '', + 'state' => $b_business_address->region ?? '', + 'country_id' => $b_business_address->countryCodeNumeric ?? '840', + ]; + + $import_client_data = array_merge($import_client_data, $braintree_address); + } + + if($b_shipping_address) { + + $braintree_shipping_address = + [ + 'shipping_address1' => $b_shipping_address->extendedAddress ?? '', + 'shipping_address2' => $b_shipping_address->streetAddress ?? '', + 'shipping_city' => $b_shipping_address->locality ?? '', + 'shipping_postal_code' => $b_shipping_address->postalCode ?? '', + 'shipping_state' => $b_shipping_address->region ?? '', + 'shipping_country_id' => $b_shipping_address->countryCodeNumeric ?? '840', + ]; + + $import_client_data = array_merge($import_client_data, $braintree_shipping_address); + + } + + $client->fill($import_client_data); + + $client->phone = $customer->phone ?? ''; + $client->name = $customer->company ?? $customer->firstName; + + $settings = $client->settings; + $settings->currency_id = (string) $this->company_gateway->company->settings->currency_id; + $client->settings = $settings; + $client->save(); + + $contact = ClientContactFactory::create($this->company_gateway->company_id, $this->company_gateway->user_id); + $contact->first_name = $customer->firstName ?? ''; + $contact->last_name = $customer->lastName ?? ''; + $contact->email = $customer->email ?? ''; + $contact->phone = $customer->phone ?? ''; + $contact->client_id = $client->id; + $contact->saveQuietly(); + + if (! isset($client->number) || empty($client->number)) { + $x = 1; + + do { + try { + $client->number = $this->getNextClientNumber($client); + $client->saveQuietly(); + + $this->completed = false; + } catch (QueryException $e) { + $x++; + + if ($x > 10) { + $this->completed = false; + } + } + } while ($this->completed); + } else { + $client->saveQuietly(); + } + + return $client; + + } + + public function importCustomers() + { + $customers = $this->init()->gateway->customer()->all(); + + foreach($customers as $c){ + + $customer = $this->find($c->id); + + // nlog(count($customer->creditCards). " Exists for {$c->id}"); + + if(!$customer) + continue; + + $client = $this->findClient($customer->email); + + if(!$this->findTokens($c->id) && !$client) { + //customer is not referenced in the system - create client + $client = $this->createNinjaClient($customer); + // nlog("Creating new Client"); + } + + $this->addClientCards($client, $customer->creditCards); + + // nlog("Adding Braintree Client: {$c->id} => {$client->id}"); + + } + } } diff --git a/app/PaymentDrivers/CheckoutComPaymentDriver.php b/app/PaymentDrivers/CheckoutComPaymentDriver.php index 176a763507b9..774bf6af64b5 100644 --- a/app/PaymentDrivers/CheckoutComPaymentDriver.php +++ b/app/PaymentDrivers/CheckoutComPaymentDriver.php @@ -12,37 +12,38 @@ namespace App\PaymentDrivers; -use App\Exceptions\PaymentFailed; -use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest; -use App\Http\Requests\Gateways\Checkout3ds\Checkout3dsRequest; -use App\Http\Requests\Payments\PaymentWebhookRequest; -use App\Jobs\Util\SystemLogger; -use App\Models\ClientGatewayToken; +use Exception; use App\Models\Company; -use App\Models\GatewayType; use App\Models\Invoice; use App\Models\Payment; +use App\Models\SystemLog; +use Checkout\CheckoutSdk; +use Checkout\Environment; +use Checkout\Common\Phone; +use App\Models\GatewayType; use App\Models\PaymentHash; use App\Models\PaymentType; -use App\Models\SystemLog; -use App\PaymentDrivers\CheckoutCom\CheckoutWebhook; -use App\PaymentDrivers\CheckoutCom\CreditCard; -use App\PaymentDrivers\CheckoutCom\Utilities; -use App\Utils\Traits\SystemLogTrait; +use Illuminate\Support\Carbon; +use App\Jobs\Util\SystemLogger; +use App\Exceptions\PaymentFailed; +use App\Models\ClientGatewayToken; use Checkout\CheckoutApiException; +use App\Utils\Traits\SystemLogTrait; +use Checkout\Payments\RefundRequest; +use Illuminate\Support\Facades\Auth; use Checkout\CheckoutArgumentException; -use Checkout\CheckoutAuthorizationException; -use Checkout\CheckoutSdk; -use Checkout\Common\Phone; use Checkout\Customers\CustomerRequest; -use Checkout\Environment; +use Checkout\CheckoutAuthorizationException; +use App\PaymentDrivers\CheckoutCom\Utilities; +use Checkout\Payments\Request\PaymentRequest; +use App\PaymentDrivers\CheckoutCom\CreditCard; +use App\PaymentDrivers\CheckoutCom\CheckoutWebhook; +use App\Http\Requests\Payments\PaymentWebhookRequest; +use Checkout\Payments\Request\Source\RequestIdSource; +use App\Http\Requests\Gateways\Checkout3ds\Checkout3dsRequest; +use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest; use Checkout\Payments\Previous\PaymentRequest as PreviousPaymentRequest; use Checkout\Payments\Previous\Source\RequestIdSource as SourceRequestIdSource; -use Checkout\Payments\RefundRequest; -use Checkout\Payments\Request\PaymentRequest; -use Checkout\Payments\Request\Source\RequestIdSource; -use Exception; -use Illuminate\Support\Facades\Auth; class CheckoutComPaymentDriver extends BaseDriver { @@ -534,4 +535,87 @@ class CheckoutComPaymentDriver extends BaseDriver { // Gateway doesn't support this feature. } + + public function auth(): bool + { + try{ + $this->init()->gateway->getCustomersClient('x'); + return true; + } + catch(\Exception $e){ + + } + return false; + } + + private function getToken(string $token, $gateway_customer_reference) + { + return ClientGatewayToken::query() + ->where('company_id', $this->company_gateway->company_id) + ->where('gateway_customer_reference', $gateway_customer_reference) + ->where('token', $token) + ->first(); + } + + /** + * ImportCustomers + * + * Only their methods because checkout.com + * does not have a list route for customers + * + * @return void + */ + public function importCustomers() + { + $this->init(); + + $this->company_gateway + ->company + ->clients() + ->cursor() + ->each(function ($client){ + + if(!str_contains($client->present()->email(), "@")) + return; + + try{ + $customer = $this->gateway->getCustomersClient()->get($client->present()->email()); + } + catch(\Exception $e) { + nlog("Checkout: Customer not found"); + return; + } + + $this->client = $client; + + nlog($customer['instruments']); + + foreach($customer['instruments'] as $card) + { + if( + $card['type'] != 'card' || + Carbon::createFromDate($card['expiry_year'], $card['expiry_month'], '1')->lt(now()) || + $this->getToken($card['id'], $customer['id']) + ) + continue; + + $payment_meta = new \stdClass(); + $payment_meta->exp_month = (string) $card['expiry_month']; + $payment_meta->exp_year = (string) $card['expiry_year']; + $payment_meta->brand = (string) $card['scheme']; + $payment_meta->last4 = (string) $card['last4']; + $payment_meta->type = (int) GatewayType::CREDIT_CARD; + + $data = [ + 'payment_meta' => $payment_meta, + 'token' => $card['id'], + 'payment_method_id' => GatewayType::CREDIT_CARD, + ]; + + $this->storeGatewayToken($data, ['gateway_customer_reference' => $customer['id']]); + + } + + }); + } } diff --git a/app/PaymentDrivers/EwayPaymentDriver.php b/app/PaymentDrivers/EwayPaymentDriver.php index 92459d460d32..0a772fb71758 100644 --- a/app/PaymentDrivers/EwayPaymentDriver.php +++ b/app/PaymentDrivers/EwayPaymentDriver.php @@ -211,4 +211,24 @@ class EwayPaymentDriver extends BaseDriver return $fields; } + + public function auth(): bool + { + + $response =$this->init()->eway->queryTransaction('xx'); + + return (bool) count($response->getErrors()) == 0; + + } + + /** + * importCustomers + * + * No support + * @return void + */ + public function importCustomers() + { + return true; + } } diff --git a/app/PaymentDrivers/Factory/ForteCustomerFactory.php b/app/PaymentDrivers/Factory/ForteCustomerFactory.php new file mode 100644 index 000000000000..eb6336d778fb --- /dev/null +++ b/app/PaymentDrivers/Factory/ForteCustomerFactory.php @@ -0,0 +1,137 @@ + $customer['company_name'] ?? $customer['first_name'], + 'contacts' => [ + [ + 'first_name' => $customer['first_name'], + 'last_name' => $customer['last_name'], + 'email' => $this->getBillingAddress($customer)['email'], + 'phone' => $this->getBillingAddress($customer)['phone'], + ] + ], + 'settings' => [ + 'currency_id' => $company->settings->currency_id, + ], + ])->merge($this->getShippingAddress($customer)) + ->merge($this->getBillingAddress($customer)) + ->toArray(); + + } + + // public function convertToGateway(Client $client): array + // { + + // } + + private function getBillingAddress(array $customer): array + { + if(isset($customer['default_billing_address_token'])) { + + foreach($customer['addresses'] as $address) { + + if($address['address_token'] != $customer['default_billing_address_token']) + continue; + + return [ + 'address1' => $address['physical_address']['street_line1'], + 'address2' => $address['physical_address']['street_line2'], + 'city' => $address['physical_address']['locality'], + 'state' => $address['physical_address']['region'], + 'postal_code' => $address['physical_address']['postal_code'], + 'country_id' => '840', + 'email' => $address['email'], + 'phone' => $address['phone'], + ]; + + } + + } + + if(isset($customer['addresses'][0])) { + + $address = $customer['addresses'][0]; + + return [ + 'address1' => $address['physical_address']['street_line1'], + 'address2' => $address['physical_address']['street_line2'], + 'city' => $address['physical_address']['locality'], + 'state' => $address['physical_address']['region'], + 'postal_code' => $address['physical_address']['postal_code'], + 'email' => $address['email'], + 'phone' => $address['phone'], + 'country_id' => '840', + ]; + + } + + return ['email' => '', 'phone' => '']; + + } + + private function getShippingAddress(array $customer): array + { + + if(isset($customer['default_shipping_address_token'])) { + + foreach($customer['addresses'] as $address) { + + if($address['address_token'] != $customer['default_shipping_address_token']) { + continue; + } + + return [ + 'shipping_address1' => $address['physical_address']['street_line1'], + 'shipping_address2' => $address['physical_address']['street_line2'], + 'shipping_city' => $address['physical_address']['locality'], + 'shipping_state' => $address['physical_address']['region'], + 'shipping_postal_code' => $address['physical_address']['postal_code'], + 'shipping_country_id' => '840', + ]; + + } + + } + + if(isset($customer['addresses'][1])){ + + $address = $customer['addresses'][1]; + + return [ + 'shipping_address1' => $address['physical_address']['street_line1'], + 'shipping_address2' => $address['physical_address']['street_line2'], + 'shipping_city' => $address['physical_address']['locality'], + 'shipping_state' => $address['physical_address']['region'], + 'shipping_postal_code' => $address['physical_address']['postal_code'], + 'shipping_country_id' => '840', + 'email' => $address['email'], + 'phone' => $address['phone'], + ]; + + } + + return ['email' => '', 'phone' => '']; + + } +} diff --git a/app/PaymentDrivers/Factory/PaytraceCustomerFactory.php b/app/PaymentDrivers/Factory/PaytraceCustomerFactory.php new file mode 100644 index 000000000000..731a6c7085f6 --- /dev/null +++ b/app/PaymentDrivers/Factory/PaytraceCustomerFactory.php @@ -0,0 +1,59 @@ + $customer->billing_address->name ?? $customer->shipping_address->name, + 'contacts' => [ + [ + 'first_name' => $customer->billing_address->name ?? $customer->shipping_address->name, + 'last_name' => '', + 'email' => $customer->email, + 'phone' => $customer->phone, + ] + ], + 'currency_id' => $company->settings->currency_id, + 'address1' => $customer->billing_address->street_address, + 'address2' => $customer->billing_address->street_address2, + 'city' => $customer->billing_address->city, + 'state' => $customer->billing_address->state, + 'postal_code' => $customer->billing_address->zip, + 'country_id' => '840', + 'shipping_address1' => $customer->shipping_address->street_address, + 'shipping_address2' => $customer->shipping_address->street_address2, + 'shipping_city' => $customer->shipping_address->city, + 'shipping_state' => $customer->shipping_address->state, + 'shipping_postal_code' => $customer->shipping_address->zip, + 'shipping_country_id' => '840', + 'settings' => [ + 'currency_id' => $company->settings->currency_id, + ], + 'card' => [ + 'token' => $customer->customer_id, + 'last4' => $customer->credit_card->masked_number, + 'expiry_month' => $customer->credit_card->expiration_month, + 'expiry_year' => $customer->credit_card->expiration_year, + ], + ]) + ->toArray(); + + } + +} diff --git a/app/PaymentDrivers/Factory/SquareCustomerFactory.php b/app/PaymentDrivers/Factory/SquareCustomerFactory.php new file mode 100644 index 000000000000..fa3d9ad71bfd --- /dev/null +++ b/app/PaymentDrivers/Factory/SquareCustomerFactory.php @@ -0,0 +1,132 @@ +getCards() ?? [] as $card){ + + $meta = new \stdClass; + $meta->exp_month = $card->getExpMonth(); + $meta->exp_year = $card->getExpYear(); + $meta->last4 = $card->getLast4(); + $meta->brand = $card->getCardBrand(); + $meta->type = GatewayType::CREDIT_CARD; + + $cards[] = [ + 'token' => $card->getId(), + 'payment_meta' => $meta, + 'payment_method_id' => GatewayType::CREDIT_CARD, + 'gateway_customer_reference' => $customer->getId(), + ]; + } + + $address = $customer->getAddress(); + + return + collect([ + 'name' => $customer->getCompanyName() ?? ($customer->getGivenName() ?? '' ." " . $customer->getFamilyName() ?? ''), + 'contacts' => [ + [ + 'first_name' => $customer->getGivenName(), + 'last_name' => $customer->getFamilyName(), + 'email' => $customer->getEmailAddress(), + 'phone' => $customer->getPhoneNumber(), + ] + ], + 'currency_id' => $company->settings->currency_id, + 'address1' => $address->getAddressLine1(), + 'address2' => $address->getAddressLine2(), + 'city' => $address->getLocality(), + 'state' => $address->getAdministrativeDistrictLevel1(), + 'postal_code' => $address->getPostalCode(), + 'country_id' => '840', + 'settings' => [ + 'currency_id' => $company->settings->currency_id, + ], + 'cards' => $cards, + ]) + ->toArray(); + + } + +} diff --git a/app/PaymentDrivers/FortePaymentDriver.php b/app/PaymentDrivers/FortePaymentDriver.php index 2a9ebb440ad3..0cbf1e3883df 100644 --- a/app/PaymentDrivers/FortePaymentDriver.php +++ b/app/PaymentDrivers/FortePaymentDriver.php @@ -11,13 +11,19 @@ namespace App\PaymentDrivers; -use App\Jobs\Util\SystemLogger; -use App\Models\GatewayType; use App\Models\Payment; use App\Models\SystemLog; -use App\PaymentDrivers\Forte\ACH; -use App\PaymentDrivers\Forte\CreditCard; +use App\Models\GatewayType; +use App\Models\ClientContact; +use App\Factory\ClientFactory; +use App\Jobs\Util\SystemLogger; use App\Utils\Traits\MakesHash; +use App\PaymentDrivers\Forte\ACH; +use Illuminate\Support\Facades\Http; +use App\Repositories\ClientRepository; +use App\PaymentDrivers\Forte\CreditCard; +use App\Repositories\ClientContactRepository; +use App\PaymentDrivers\Factory\ForteCustomerFactory; class FortePaymentDriver extends BaseDriver { @@ -183,8 +189,140 @@ class FortePaymentDriver extends BaseDriver ]; } - // public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash) - // { - // return $this->payment_method->yourTokenBillingImplmentation(); - // } + //////////////////////////////////////////// + // DB + /////////////////////////////////////////// + public function auth(): bool + { + + $forte_base_uri = "https://sandbox.forte.net/api/v3/"; + if ($this->company_gateway->getConfigField('testMode') == false) { + $forte_base_uri = "https://api.forte.net/v3/"; + } + $forte_api_access_id = $this->company_gateway->getConfigField('apiAccessId'); + $forte_secure_key = $this->company_gateway->getConfigField('secureKey'); + $forte_auth_organization_id = $this->company_gateway->getConfigField('authOrganizationId'); + $forte_organization_id = $this->company_gateway->getConfigField('organizationId'); + $forte_location_id = $this->company_gateway->getConfigField('locationId'); + + $response = Http::withBasicAuth($forte_api_access_id, $forte_secure_key) + ->withHeaders(['X-Forte-Auth-Organization-Id' => $forte_organization_id]) + ->get("{$forte_base_uri}/organizations/{$forte_organization_id}/locations/{$forte_location_id}/customers/"); + + return $response->successful(); + + } + + public function baseUri(): string + { + + $forte_base_uri = "https://sandbox.forte.net/api/v3/"; + if ($this->company_gateway->getConfigField('testMode') == false) { + $forte_base_uri = "https://api.forte.net/v3/"; + } + + return $forte_base_uri; + } + + private function getOrganisationId(): string + { + return $this->company_gateway->getConfigField('organizationId'); + } + + public function getLocationId(): string + { + return $this->company_gateway->getConfigField('locationId'); + } + + public function stubRequest() + { + + $forte_api_access_id = $this->company_gateway->getConfigField('apiAccessId'); + $forte_secure_key = $this->company_gateway->getConfigField('secureKey'); + $forte_auth_organization_id = $this->company_gateway->getConfigField('authOrganizationId'); + + return Http::withBasicAuth($forte_api_access_id, $forte_secure_key) + ->withHeaders(['X-Forte-Auth-Organization-Id' => $this->getOrganisationId()]); + } + + private function getClient(?string $email) + { + return ClientContact::query() + ->where('company_id', $this->company_gateway->company_id) + ->where('email', $email) + ->first(); + } + + public function getLocation() + { + + $response = $this->stubRequest() + ->withQueryParameters(['page_size' => 10000]) + ->get("{$this->baseUri()}/organizations/{$this->getOrganisationId()}/locations/{$this->getLocationId()}"); + + if($response->successful()) + return $response->json(); + + return false; + } + + public function updateFees() + { + $response = $this->getLocation(); + + if($response) + { + $body = $response['services']; + + $fees_and_limits = $this->company_gateway->fees_and_limits; + + if($body['card']['service_fee_percentage'] > 0 || $body['card']['service_fee_additional_amount'] > 0){ + + $fees_and_limits->{1}->fee_amount = $body['card']['service_fee_additional_amount']; + $fees_and_limits->{1}->fee_percent = $body['card']['service_fee_percentage']; + } + + if($body['debit']['service_fee_percentage'] > 0 || $body['debit']['service_fee_additional_amount'] > 0) { + + $fees_and_limits->{2}->fee_amount = $body['debit']['service_fee_additional_amount']; + $fees_and_limits->{2}->fee_percent = $body['debit']['service_fee_percentage']; + } + + $this->company_gateway->fees_and_limits = $fees_and_limits; + $this->company_gateway->save(); + + } + + return false; + + } + + public function importCustomers() + { + + $response = $this->stubRequest() + ->withQueryParameters(['page_size' => 10000]) + ->get("{$this->baseUri()}/organizations/{$this->getOrganisationId()}/locations/{$this->getLocationId()}/customers"); + + if($response->successful()){ + + foreach($response->json()['results'] as $customer) + { + + $client_repo = new ClientRepository(new ClientContactRepository()); + $factory = new ForteCustomerFactory(); + + $data = $factory->convertToNinja($customer, $this->company_gateway->company); + + if(strlen($data['email']) == 0 || $this->getClient($data['email'])) + continue; + + $client_repo->save($data, ClientFactory::create($this->company_gateway->company_id, $this->company_gateway->user_id)); + + //persist any payment methods here! + } + } + + } + } diff --git a/app/PaymentDrivers/GoCardlessPaymentDriver.php b/app/PaymentDrivers/GoCardlessPaymentDriver.php index 0ec94d1032c5..723a190f00be 100644 --- a/app/PaymentDrivers/GoCardlessPaymentDriver.php +++ b/app/PaymentDrivers/GoCardlessPaymentDriver.php @@ -84,11 +84,11 @@ class GoCardlessPaymentDriver extends BaseDriver $types[] = GatewayType::DIRECT_DEBIT; } - if (in_array($this->client->currency()->code, ['EUR', 'GBP'])) { + if ($this->client && in_array($this->client->currency()->code, ['EUR', 'GBP'])) { $types[] = GatewayType::SEPA; } - if ($this->client->currency()->code === 'GBP') { + if ($this->client && $this->client->currency()->code === 'GBP') { $types[] = GatewayType::INSTANT_BANK_PAY; } @@ -558,4 +558,17 @@ class GoCardlessPaymentDriver extends BaseDriver { return render('gateways.gocardless.verification'); } + + public function auth(): bool + { + try { + $customers = $this->init()->gateway->customers()->list(); + return true; + } + catch(\Exception $e){ + + } + + return false; + } } diff --git a/app/PaymentDrivers/MolliePaymentDriver.php b/app/PaymentDrivers/MolliePaymentDriver.php index 57adb0ca2698..ed02ba31a835 100644 --- a/app/PaymentDrivers/MolliePaymentDriver.php +++ b/app/PaymentDrivers/MolliePaymentDriver.php @@ -420,4 +420,20 @@ class MolliePaymentDriver extends BaseDriver { return \number_format((float) $amount, 2, '.', ''); } + + public function auth(): bool + { + $this->init(); + + try { + $p = $this->gateway->payments->page(); + return true; + } + catch(\Exception $e){ + + } + + return false; + + } } diff --git a/app/PaymentDrivers/PayPalPPCPPaymentDriver.php b/app/PaymentDrivers/PayPalPPCPPaymentDriver.php index 5b757d907600..35709850a55d 100644 --- a/app/PaymentDrivers/PayPalPPCPPaymentDriver.php +++ b/app/PaymentDrivers/PayPalPPCPPaymentDriver.php @@ -292,7 +292,32 @@ class PayPalPPCPPaymentDriver extends BaseDriver } - $r = $this->gatewayRequest("/v2/checkout/orders/{$orderID}/capture", 'post', ['body' => '']); + try { + $r = $this->gatewayRequest("/v2/checkout/orders/{$orderID}/capture", 'post', ['body' => '']); + } catch(\Exception $e) { + + //Rescue for duplicate invoice_id + if(stripos($e->getMessage(), 'DUPLICATE_INVOICE_ID') !== false) { + + + $_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; @@ -560,5 +585,28 @@ class PayPalPPCPPaymentDriver extends BaseDriver PayPalWebhook::dispatch($request->all(), $request->headers->all(), $this->access_token); } + + public function auth(): bool + { + try { + $this->init()->getClientToken(); + return true; + } + catch(\Exception $e) { + + } + + return false; + } + + public function importCustomers() + { + + // $response = $this->gatewayRequest('/v1/reporting/transactions', 'get', ['fields' => 'all','page_size' => 500,'start_date' => '2024-02-01T00:00:00-0000', 'end_date' => '2024-03-01T00:00:00-0000']); + + // nlog($response->json()); + + return true; + } } diff --git a/app/PaymentDrivers/PayPalRestPaymentDriver.php b/app/PaymentDrivers/PayPalRestPaymentDriver.php index f08decd67501..464177737f15 100644 --- a/app/PaymentDrivers/PayPalRestPaymentDriver.php +++ b/app/PaymentDrivers/PayPalRestPaymentDriver.php @@ -18,6 +18,7 @@ use App\Models\Invoice; use App\Models\SystemLog; use App\Models\GatewayType; use App\Models\PaymentType; +use Illuminate\Support\Str; use App\Jobs\Util\SystemLogger; use App\Utils\Traits\MakesHash; use App\Exceptions\PaymentFailed; @@ -81,11 +82,7 @@ class PayPalRestPaymentDriver extends BaseDriver public function init() { - // $this->omnipay_gateway = Omnipay::create( - // $this->company_gateway->gateway->provider - // ); - // $this->omnipay_gateway->initialize((array) $this->company_gateway->getConfig()); $this->api_endpoint_url = $this->company_gateway->getConfigField('testMode') ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com'; $secret = $this->company_gateway->getConfigField('secret'); @@ -215,7 +212,8 @@ class PayPalRestPaymentDriver extends BaseDriver $request['gateway_response'] = str_replace("Error: ", "", $request['gateway_response']); $response = json_decode($request['gateway_response'], true); - + + // nlog($response); //capture $orderID = $response['orderID']; @@ -239,7 +237,33 @@ class PayPalRestPaymentDriver extends BaseDriver } - $r = $this->gatewayRequest("/v2/checkout/orders/{$orderID}/capture", 'post', ['body' => '']); + try{ + $r = $this->gatewayRequest("/v2/checkout/orders/{$orderID}/capture", 'post', ['body' => '']); + } + catch(\Exception $e) { + + //Rescue for duplicate invoice_id + if(stripos($e->getMessage(), 'DUPLICATE_INVOICE_ID') !== false){ + + + $_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; @@ -380,61 +404,6 @@ class PayPalRestPaymentDriver extends BaseDriver return $r->json()['id']; - - - // $_invoice = collect($this->payment_hash->data->invoices)->first(); - - // $invoice = Invoice::withTrashed()->find($this->decodePrimaryKey($_invoice->invoice_id)); - - // $order = [ - // "intent" => "CAPTURE", - // "payer" => [ - // "name" => [ - // "given_name" => $this->client->present()->first_name(), - // "surname" => $this->client->present()->last_name(), - // ], - // "email_address" => $this->client->present()->email(), - // "address" => [ - // "address_line_1" => $this->client->address1, - // "address_line_2" => $this->client->address2, - // "admin_area_1" => $this->client->city, - // "admin_area_2" => $this->client->state, - // "postal_code" => $this->client->postal_code, - // "country_code" => $this->client->country->iso_3166_2, - // ] - // ], - // "purchase_units" => [ - // [ - // "description" => ctrans('texts.invoice_number').'# '.$invoice->number, - // "invoice_id" => $invoice->number, - // "amount" => [ - // "value" => (string)$data['amount_with_fee'], - // "currency_code" => $this->client->currency()->code, - // "breakdown" => [ - // "item_total" => [ - // "currency_code" => $this->client->currency()->code, - // "value" => (string)$data['amount_with_fee'] - // ] - // ] - // ], - // "items" => [ - // [ - // "name" => ctrans('texts.invoice_number').'# '.$invoice->number, - // "quantity" => "1", - // "unit_amount" => [ - // "currency_code" => $this->client->currency()->code, - // "value" => (string)$data['amount_with_fee'] - // ], - // ], - // ], - // ] - // ] - // ]; - - // $r = $this->gatewayRequest('/v2/checkout/orders', 'post', $order); - - // return $r->json()['id']; - } private function getShippingAddress(): ?array @@ -483,7 +452,7 @@ class PayPalRestPaymentDriver extends BaseDriver SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_PAYPAL, $this->client, - $this->client->company, + $this->client->company ?? $this->company_gateway->company, ); throw new PaymentFailed("Gateway failure - {$r->body()}", 401); @@ -520,5 +489,23 @@ class PayPalRestPaymentDriver extends BaseDriver return 0; } + public function auth(): bool + { + + try { + $this->init()->getClientToken(); + return true; + } + catch(\Exception $e) { + + } + + return false; + } + + public function importCustomers() + { + return true; + } } diff --git a/app/PaymentDrivers/PayTrace/CreditCard.php b/app/PaymentDrivers/PayTrace/CreditCard.php index 0fa5f37991d1..b08a27088d1e 100644 --- a/app/PaymentDrivers/PayTrace/CreditCard.php +++ b/app/PaymentDrivers/PayTrace/CreditCard.php @@ -59,6 +59,9 @@ class CreditCard 'enc_key' => $data['enc_key'], 'integrator_id' => $this->paytrace->company_gateway->getConfigField('integratorId'), 'billing_address' => $this->buildBillingAddress(), + 'email' => $this->paytrace->client->present()->email(), + 'phone' => $this->paytrace->client->present()->phone(), + ]; $response = $this->paytrace->gatewayRequest('/v1/customer/pt_protect_create', $post_data); diff --git a/app/PaymentDrivers/PaytracePaymentDriver.php b/app/PaymentDrivers/PaytracePaymentDriver.php index 4f76b95645a2..7d7838c5610c 100644 --- a/app/PaymentDrivers/PaytracePaymentDriver.php +++ b/app/PaymentDrivers/PaytracePaymentDriver.php @@ -11,19 +11,24 @@ namespace App\PaymentDrivers; -use App\Exceptions\SystemError; -use App\Http\Requests\Payments\PaymentWebhookRequest; -use App\Jobs\Util\SystemLogger; -use App\Models\ClientGatewayToken; -use App\Models\GatewayType; use App\Models\Invoice; use App\Models\Payment; +use App\Utils\CurlUtils; +use App\Models\SystemLog; +use App\Models\GatewayType; use App\Models\PaymentHash; use App\Models\PaymentType; -use App\Models\SystemLog; -use App\PaymentDrivers\PayTrace\CreditCard; -use App\Utils\CurlUtils; +use App\Factory\ClientFactory; +use App\Exceptions\SystemError; +use App\Jobs\Util\SystemLogger; use App\Utils\Traits\MakesHash; +use App\Models\ClientGatewayToken; +use App\Repositories\ClientRepository; +use App\PaymentDrivers\PayTrace\CreditCard; +use App\Repositories\ClientContactRepository; +use App\Http\Requests\Payments\PaymentWebhookRequest; +use App\Models\ClientContact; +use App\PaymentDrivers\Factory\PaytraceCustomerFactory; class PaytracePaymentDriver extends BaseDriver { @@ -246,4 +251,89 @@ class PaytracePaymentDriver extends BaseDriver return false; } + + public function getClientRequiredFields(): array + { + $fields = parent::getClientRequiredFields(); + + $fields[] = ['name' => 'client_address_line_1', 'label' => ctrans('texts.address1'), 'type' => 'text', 'validation' => 'required']; + $fields[] = ['name' => 'client_city', 'label' => ctrans('texts.city'), 'type' => 'text', 'validation' => 'required']; + $fields[] = ['name' => 'client_postal_code', 'label' => ctrans('texts.postal_code'), 'type' => 'text', 'validation' => 'required']; + $fields[] = ['name' => 'client_state', 'label' => ctrans('texts.state'), 'type' => 'text', 'validation' => 'required']; + $fields[] = ['name' => 'client_country_id', 'label' => ctrans('texts.country'), 'type' => 'text', 'validation' => 'required']; + + return $fields; + } + + public function auth(): bool + { + try { + $this->init()->generateAuthHeaders() && strlen($this->company_gateway->getConfigField('integratorId')) > 2; + return true; + } + catch(\Exception $e){ + + } + + return false; + + } + + public function importCustomers() + { + + $data = [ + 'integrator_id' => $this->company_gateway->getConfigField('integratorId'), + ]; + + $response = $this->gatewayRequest('/v1/customer/export', $data); + + nlog($response); + + if ($response && $response->success) { + + $client_repo = new ClientRepository(new ClientContactRepository()); + $factory = new PaytraceCustomerFactory(); + + foreach($response->customers as $customer) + { + $data = $factory->convertToNinja($customer, $this->company_gateway->company); + + $client = false; + + if(str_contains($data['contacts'][0]['email'], "@")) + { + $client = ClientContact::query() + ->where('company_id', $this->company_gateway->company_id) + ->where('email', $data['contacts'][0]['email']) + ->first()->client ?? false; + } + + if(!$client) + $client = $client_repo->save($data, ClientFactory::create($this->company_gateway->company_id, $this->company_gateway->user_id)); + + $this->client = $client; + + if(ClientGatewayToken::query()->where('client_id', $client->id)->where('token',$data['card']['token'])->exists()) + continue; + + $cgt = []; + $cgt['token'] = $data['card']['token']; + $cgt['payment_method_id'] = GatewayType::CREDIT_CARD; + + $payment_meta = new \stdClass(); + $payment_meta->exp_month = $data['card']['expiry_month']; + $payment_meta->exp_year = $data['card']['expiry_year']; + $payment_meta->brand = 'CC'; + $payment_meta->last4 = $data['card']['last4']; + $payment_meta->type = GatewayType::CREDIT_CARD; + + $cgt['payment_meta'] = $payment_meta; + + $token = $this->storeGatewayToken($cgt, []); + + } + } + + } } diff --git a/app/PaymentDrivers/Square/CreditCard.php b/app/PaymentDrivers/Square/CreditCard.php index da53d36a2610..80b48fb5a15a 100644 --- a/app/PaymentDrivers/Square/CreditCard.php +++ b/app/PaymentDrivers/Square/CreditCard.php @@ -197,7 +197,7 @@ class CreditCard implements MethodInterface { $square_card = new \Square\Models\Card(); - $square_card->setCustomerId($this->findOrCreateClient()); + $square_card->setCustomerId($this->square_driver->findOrCreateClient()); $body = new \Square\Models\CreateCardRequest(uniqid("st", true), $source_id, $square_card); @@ -238,82 +238,5 @@ class CreditCard implements MethodInterface return false; } - private function findOrCreateClient() - { - $email_address = new \Square\Models\CustomerTextFilter(); - $email_address->setExact($this->square_driver->client->present()->email()); - - $filter = new \Square\Models\CustomerFilter(); - $filter->setEmailAddress($email_address); - - $query = new \Square\Models\CustomerQuery(); - $query->setFilter($filter); - - $body = new \Square\Models\SearchCustomersRequest(); - $body->setQuery($query); - - $api_response = $this->square_driver - ->init() - ->square - ->getCustomersApi() - ->searchCustomers($body); - - $customers = false; - - if ($api_response->isSuccess()) { - $customers = $api_response->getBody(); - $customers = json_decode($customers); - - if (count([$api_response->getBody(), 1]) == 0) { - $customers = false; - } - } else { - $errors = $api_response->getErrors(); - } - - if ($customers && property_exists($customers, 'customers')) { - return $customers->customers[0]->id; - } - - return $this->createClient(); - } - - private function createClient() - { - $country = $this->square_driver->client->country ? $this->square_driver->client->country->iso_3166_2 : $this->square_driver->client->company->country()->iso_3166_2; - - /* Step two - create the customer */ - $billing_address = new \Square\Models\Address(); - $billing_address->setAddressLine1($this->square_driver->client->address1); - $billing_address->setAddressLine2($this->square_driver->client->address2); - $billing_address->setLocality($this->square_driver->client->city); - $billing_address->setAdministrativeDistrictLevel1($this->square_driver->client->state); - $billing_address->setPostalCode($this->square_driver->client->postal_code); - $billing_address->setCountry($country); - - $body = new \Square\Models\CreateCustomerRequest(); - $body->setGivenName($this->square_driver->client->present()->name()); - $body->setFamilyName(''); - $body->setEmailAddress($this->square_driver->client->present()->email()); - $body->setAddress($billing_address); - // $body->setPhoneNumber($this->square_driver->client->phone); - $body->setReferenceId($this->square_driver->client->number); - $body->setNote('Created by Invoice Ninja.'); - - $api_response = $this->square_driver - ->init() - ->square - ->getCustomersApi() - ->createCustomer($body); - - if ($api_response->isSuccess()) { - $result = $api_response->getResult(); - - return $result->getCustomer()->getId(); - } else { - $errors = $api_response->getErrors(); - nlog($errors); - return $this->processUnsuccessfulPayment($api_response); - } - } + } diff --git a/app/PaymentDrivers/SquarePaymentDriver.php b/app/PaymentDrivers/SquarePaymentDriver.php index d58aa4f7cf9a..079ef5c3626b 100644 --- a/app/PaymentDrivers/SquarePaymentDriver.php +++ b/app/PaymentDrivers/SquarePaymentDriver.php @@ -11,22 +11,27 @@ namespace App\PaymentDrivers; -use App\Http\Requests\Payments\PaymentWebhookRequest; -use App\Jobs\Util\SystemLogger; -use App\Models\ClientGatewayToken; -use App\Models\GatewayType; use App\Models\Invoice; use App\Models\Payment; +use App\Models\SystemLog; +use App\Models\GatewayType; use App\Models\PaymentHash; use App\Models\PaymentType; -use App\Models\SystemLog; +use App\Models\ClientContact; +use App\Factory\ClientFactory; +use App\Jobs\Util\SystemLogger; +use App\Utils\Traits\MakesHash; +use Square\Utils\WebhooksHelper; +use App\Models\ClientGatewayToken; +use App\Repositories\ClientRepository; +use Square\Models\WebhookSubscription; use App\PaymentDrivers\Square\CreditCard; use App\PaymentDrivers\Square\SquareWebhook; -use App\Utils\Traits\MakesHash; -use Square\Models\Builders\RefundPaymentRequestBuilder; +use App\Repositories\ClientContactRepository; use Square\Models\CreateWebhookSubscriptionRequest; -use Square\Models\WebhookSubscription; -use Square\Utils\WebhooksHelper; +use App\Http\Requests\Payments\PaymentWebhookRequest; +use App\PaymentDrivers\Factory\SquareCustomerFactory; +use Square\Models\Builders\RefundPaymentRequestBuilder; class SquarePaymentDriver extends BaseDriver { @@ -429,4 +434,205 @@ class SquarePaymentDriver extends BaseDriver return $amount; } + + public function auth(): bool + { + + $api_response = $this->init() + ->square + ->getCustomersApi() + ->listCustomers(); + + + return (bool) count($api_response->getErrors()) == 0; + + } + + public function importCustomers() + { + + $limit = 100; + + $api_response = $this->init() + ->square + ->getCustomersApi() + ->listCustomers(null, + $limit, + 'DEFAULT', + 'DESC' + ); + + if ($api_response->isSuccess()) { + + while ($api_response->getResult()->getCustomers()) { + + $customers = $api_response->getResult()->getCustomers(); + + $client_repo = new ClientRepository(new ClientContactRepository()); + + foreach($customers as $customer) + { + + $data = (new SquareCustomerFactory())->convertToNinja($customer, $this->company_gateway->company); + $client = ClientContact::where('company_id', $this->company_gateway->company_id)->where('email', $customer->getEmailAddress())->first()->client ?? false; + + if(!$client) + $client = $client_repo->save($data, ClientFactory::create($this->company_gateway->company_id, $this->company_gateway->user_id)); + + $this->client = $client; + + foreach($data['cards'] as $card) { + + if(ClientGatewayToken::where('company_id', $this->company_gateway->company_id)->where('token', $card['token'])->exists()) + continue; + + $this->storeGatewayToken($card); + + } + } + + $c = $api_response->getCursor(); + if ($c) { + + $api_response = $this->init() + ->square + ->getCustomersApi() + ->listCustomers( + $c, + $limit, + 'DEFAULT', + 'DESC' + ); + } else { + break; + } + + + } + + } + } + + private function findClient($email = null) + { + + $email_address_string = $email ?? $this->client->present()->email(); + + $email_address = new \Square\Models\CustomerTextFilter(); + $email_address->setExact($email_address_string); + + $filter = new \Square\Models\CustomerFilter(); + $filter->setEmailAddress($email_address); + + $query = new \Square\Models\CustomerQuery(); + $query->setFilter($filter); + + $body = new \Square\Models\SearchCustomersRequest(); + $body->setQuery($query); + + $api_response = $this->init() + ->square + ->getCustomersApi() + ->searchCustomers($body); + + $customers = false; + + if ($api_response->isSuccess()) { + $customers = $api_response->getBody(); + $customers = json_decode($customers); + + if (count([$api_response->getBody(), 1]) == 0) { + $customers = false; + } + } else { + $errors = $api_response->getErrors(); + } + + if ($customers && property_exists($customers, 'customers')) { + return $customers->customers[0]->id; + } + + return false; + + } + + public function findOrCreateClient() + { + if($customer_id = $this->findClient()) + return $customer_id; + + return $this->createClient(); + } + + private function createClient() + { + $country = $this->client->country ? $this->client->country->iso_3166_2 : $this->client->company->country()->iso_3166_2; + + /* Step two - create the customer */ + $billing_address = new \Square\Models\Address(); + $billing_address->setAddressLine1($this->client->address1); + $billing_address->setAddressLine2($this->client->address2); + $billing_address->setLocality($this->client->city); + $billing_address->setAdministrativeDistrictLevel1($this->client->state); + $billing_address->setPostalCode($this->client->postal_code); + $billing_address->setCountry($country); + + $body = new \Square\Models\CreateCustomerRequest(); + $body->setGivenName($this->client->present()->name()); + $body->setFamilyName(''); + $body->setEmailAddress($this->client->present()->email()); + $body->setAddress($billing_address); + $body->setReferenceId($this->client->number); + $body->setNote('Created by Invoice Ninja.'); + + $api_response = $this->init() + ->square + ->getCustomersApi() + ->createCustomer($body); + + if ($api_response->isSuccess()) { + $result = $api_response->getResult(); + + return $result->getCustomer()->getId(); + } else { + $errors = $api_response->getErrors(); + nlog($errors); + + $error = end($errors); + + $data = [ + 'response' => $error->getDetail(), + 'error' => $error->getDetail(), + 'error_code' => $error->getCode(), + ]; + + return $this->processUnsuccessfulTransaction($data); + + } + } + + + + + + + + + + + + + + + + + + + + + + + + + } diff --git a/app/PaymentDrivers/Stripe/ACH.php b/app/PaymentDrivers/Stripe/ACH.php index 4015fde43569..d7594ea1c05d 100644 --- a/app/PaymentDrivers/Stripe/ACH.php +++ b/app/PaymentDrivers/Stripe/ACH.php @@ -109,15 +109,36 @@ class ACH public function verificationView(ClientGatewayToken $token) { - if (isset($token->meta->state) && $token->meta->state === 'authorized') { - return redirect() - ->route('client.payment_methods.show', $token->hashed_id) - ->with('message', __('texts.payment_method_verified')); - } //double check here if we need to show the verification view. $this->stripe->init(); + if(substr($token->token,0,2) == 'pm'){ + $pm = $this->stripe->getStripePaymentMethod($token->token); + + if(!$pm->customer){ + + $meta = $token->meta; + $meta->state = 'unauthorized'; + $token->meta = $meta; + $token->save(); + + return redirect() + ->route('client.payment_methods.show', $token->hashed_id); + + } + + if (isset($token->meta->state) && $token->meta->state === 'authorized') { + return redirect() + ->route('client.payment_methods.show', $token->hashed_id) + ->with('message', __('texts.payment_method_verified')); + } + + if($token->meta->next_action) + return redirect($token->meta->next_action); + + } + $bank_account = Customer::retrieveSource($token->gateway_customer_reference, $token->token, [], $this->stripe->stripe_connect_auth); /* Catch externally validated bank accounts and mark them as verified */ @@ -319,6 +340,9 @@ class ACH $data['message'] = 'Too many requests made to the API too quickly'; break; case $e instanceof InvalidRequestException: + + return redirect()->route('client.payment_methods.verification', ['payment_method' => $cgt->hashed_id, 'method' => GatewayType::BANK_TRANSFER]); + $data['message'] = 'Invalid parameters were supplied to Stripe\'s API'; break; case $e instanceof AuthenticationException: diff --git a/app/PaymentDrivers/Stripe/ImportCustomers.php b/app/PaymentDrivers/Stripe/ImportCustomers.php index 6001ed84298e..aa874444398d 100644 --- a/app/PaymentDrivers/Stripe/ImportCustomers.php +++ b/app/PaymentDrivers/Stripe/ImportCustomers.php @@ -62,12 +62,6 @@ class ImportCustomers $this->addCustomer($customer); } - //handle - // if(is_array($customers->data) && end($customers->data) && array_key_exists('id', end($customers->data))) - // $starting_after = end($customers->data)['id']; - // else - // break; - $starting_after = isset(end($customers->data)['id']) ? end($customers->data)['id'] : false; if (!$starting_after) { diff --git a/app/PaymentDrivers/StripePaymentDriver.php b/app/PaymentDrivers/StripePaymentDriver.php index 3c4b145fe012..1bc7679e78e2 100644 --- a/app/PaymentDrivers/StripePaymentDriver.php +++ b/app/PaymentDrivers/StripePaymentDriver.php @@ -989,4 +989,20 @@ class StripePaymentDriver extends BaseDriver return mb_convert_encoding(pack('H*', $matches[1]), 'UTF-8', 'UCS-2BE'); }, $string); } + + public function auth(): bool + { + $this->init(); + + try { + $this->verifyConnect(); + return true; + } + catch(\Exception $e) { + + } + + return false; + + } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 53f49f4f854c..8ab1e78c91f6 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -29,6 +29,8 @@ use App\Http\Middleware\SetDomainNameDb; use Illuminate\Queue\Events\JobProcessing; use App\Helpers\Mail\Office365MailTransport; use Illuminate\Database\Eloquent\Relations\Relation; +use Symfony\Component\Mailer\Bridge\Brevo\Transport\BrevoTransportFactory; +use Symfony\Component\Mailer\Transport\Dsn; class AppServiceProvider extends ServiceProvider { @@ -55,7 +57,7 @@ class AppServiceProvider extends ServiceProvider /* Defines the name used in polymorphic tables */ Relation::morphMap([ - 'invoices' => Invoice::class, + 'invoices' => Invoice::class, 'proposals' => Proposal::class, ]); @@ -119,6 +121,30 @@ class AppServiceProvider extends ServiceProvider return $this; }); + Mail::extend('brevo', function () { + return (new BrevoTransportFactory)->create( + new Dsn( + 'brevo+api', + 'default', + config('services.brevo.key') + ) + ); + }); + Mailer::macro('brevo_config', function (string $brevo_key) { + // @phpstan-ignore /** @phpstan-ignore-next-line **/ + Mailer::setSymfonyTransport( + (new BrevoTransportFactory)->create( + new Dsn( + 'brevo+api', + 'default', + $brevo_key + ) + ) + ); + + return $this; + }); + } public function register(): void diff --git a/app/Repositories/UserRepository.php b/app/Repositories/UserRepository.php index 8a4996e04506..c3a9313c5f08 100644 --- a/app/Repositories/UserRepository.php +++ b/app/Repositories/UserRepository.php @@ -206,7 +206,8 @@ class UserRepository extends BaseRepository ->first(); $cu->restore(); - + $cu->tokens()->restore(); + event(new UserWasRestored($user, auth()->user(), auth()->user()->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null))); } diff --git a/app/Services/Client/Statement.php b/app/Services/Client/Statement.php index bedcacb34af4..163718ea6dbf 100644 --- a/app/Services/Client/Statement.php +++ b/app/Services/Client/Statement.php @@ -162,6 +162,7 @@ class Statement $ts->addGlobal(['show_credits' => $this->options['show_credits_table']]); $ts->addGlobal(['show_aging' => $this->options['show_aging_table']]); $ts->addGlobal(['show_payments' => $this->options['show_payments_table']]); + $ts->addGlobal(['currency_code' => $this->client->company->currency()->code]); $ts->build([ 'variables' => collect([$variables]), diff --git a/app/Services/Credit/CreditService.php b/app/Services/Credit/CreditService.php index deb2f3dffe0b..77409f913e37 100644 --- a/app/Services/Credit/CreditService.php +++ b/app/Services/Credit/CreditService.php @@ -12,6 +12,7 @@ namespace App\Services\Credit; use App\Factory\PaymentFactory; +use App\Jobs\EDocument\CreateEDocument; use App\Models\Credit; use App\Models\Payment; use App\Models\PaymentType; @@ -37,6 +38,11 @@ class CreditService return (new GetCreditPdf($invitation))->run(); } + public function getECredit($contact = null) + { + return (new CreateEDocument($this->credit))->handle(); + } + /** * Applies the invoice number. * @return $this InvoiceService object @@ -232,6 +238,27 @@ class CreditService return $this; } + public function deleteECredit() + { + $this->credit->load('invitations'); + + $this->credit->invitations->each(function ($invitation) { + try { + // if (Storage::disk(config('filesystems.default'))->exists($this->invoice->client->e_invoice_filepath($invitation).$this->invoice->getFileName("xml"))) { + Storage::disk(config('filesystems.default'))->delete($this->credit->client->e_document_filepath($invitation).$this->credit->getFileName("xml")); + // } + + // if (Ninja::isHosted() && Storage::disk('public')->exists($this->invoice->client->e_invoice_filepath($invitation).$this->invoice->getFileName("xml"))) { + if (Ninja::isHosted()) { + Storage::disk('public')->delete($this->credit->client->e_document_filepath($invitation).$this->credit->getFileName("xml")); + } + } catch (\Exception $e) { + nlog($e->getMessage()); + } + }); + + return $this; + } public function triggeredActions($request) { $this->credit = (new TriggeredActions($this->credit, $request))->run(); diff --git a/app/Services/Invoice/EInvoice/FacturaEInvoice.php b/app/Services/EDocument/Standards/FacturaEInvoice.php similarity index 99% rename from app/Services/Invoice/EInvoice/FacturaEInvoice.php rename to app/Services/EDocument/Standards/FacturaEInvoice.php index e4b7f28bd905..c4182efbbb9a 100644 --- a/app/Services/Invoice/EInvoice/FacturaEInvoice.php +++ b/app/Services/EDocument/Standards/FacturaEInvoice.php @@ -9,7 +9,7 @@ * @license https://www.elastic.co/licensing/elastic-license */ -namespace App\Services\Invoice\EInvoice; +namespace App\Services\EDocument\Standards; use App\Models\Invoice; use App\Models\PaymentType; diff --git a/app/Services/Invoice/EInvoice/FatturaPA.php b/app/Services/EDocument/Standards/FatturaPA.php similarity index 99% rename from app/Services/Invoice/EInvoice/FatturaPA.php rename to app/Services/EDocument/Standards/FatturaPA.php index 2c448c7cfa1b..e8c0ad2145c7 100644 --- a/app/Services/Invoice/EInvoice/FatturaPA.php +++ b/app/Services/EDocument/Standards/FatturaPA.php @@ -9,7 +9,7 @@ * @license https://www.elastic.co/licensing/elastic-license */ -namespace App\Services\Invoice\EInvoice; +namespace App\Services\EDocument\Standards; use App\Models\Invoice; use App\Services\AbstractService; diff --git a/app/Services/EDocument/Standards/OrderXDocument.php b/app/Services/EDocument/Standards/OrderXDocument.php new file mode 100644 index 000000000000..fb431531405b --- /dev/null +++ b/app/Services/EDocument/Standards/OrderXDocument.php @@ -0,0 +1,250 @@ +document->company; + $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 = match ($profile) { + "OrderX_Basic" => OrderProfiles::PROFILE_BASIC, + "OrderX_Comfort" => OrderProfiles::PROFILE_COMFORT, + "OrderX_Extended" => OrderProfiles::PROFILE_EXTENDED, + default => OrderProfiles::PROFILE_EXTENDED, + }; + + $this->orderxdocument = OrderDocumentBuilder::CreateNew($profile); + + $this->orderxdocument + ->setDocumentSeller($company->getSetting('name')) + ->setDocumentSellerAddress($company->getSetting("address1"), $company->getSetting("address2"), "", $company->getSetting("postal_code"), $company->getSetting("city"), $company->country()->iso_3166_2, $company->getSetting("state")) + ->setDocumentSellerContact($this->document->user->present()->getFullName(), "", $this->document->user->present()->phone(), "", $this->document->user->email) + ->setDocumentBuyer($settings_entity->present()->name(), $settings_entity->number) + ->setDocumentBuyerAddress($settings_entity->address1, "", "", $settings_entity->postal_code, $settings_entity->city, $settings_entity->country->iso_3166_2, $settings_entity->state) + ->setDocumentBuyerContact($settings_entity->present()->primary_contact_name(), "", $settings_entity->present()->phone(), "", $settings_entity->present()->email()) + ->addDocumentPaymentTerm(ctrans("texts.xinvoice_payable", ['payeddue' => date_create($this->document->date ?? now()->format('Y-m-d'))->diff(date_create($this->document->due_date ?? now()->format('Y-m-d')))->format("%d"), 'paydate' => $this->document->due_date])); + + if (!empty($this->document->public_notes)) { + $this->orderxdocument->addDocumentNote($this->document->public_notes ?? ''); + } + // Document type + $document_class = get_class($this->document); + switch ($document_class){ + case Quote::class: + // Probably wrong file code https://github.com/horstoeko/zugferd/blob/master/src/codelists/ZugferdInvoiceType.php + if (empty($this->document->number)) { + $this->orderxdocument->setDocumentInformation("DRAFT", OrderDocumentTypes::ORDER, date_create($this->document->date ?? now()->format('Y-m-d')), $settings_entity->getCurrencyCode()); + $this->orderxdocument->setIsTestDocument(true); + } else { + $this->orderxdocument->setDocumentInformation($this->document->number, OrderDocumentTypes::ORDER, date_create($this->document->date ?? now()->format('Y-m-d')), $settings_entity->getCurrencyCode()); + }; + break; + case PurchaseOrder::class: + if (empty($this->document->number)) { + $this->orderxdocument->setDocumentInformation("DRAFT", OrderDocumentTypes::ORDER_RESPONSE, date_create($this->document->date ?? now()->format('Y-m-d')), $settings_entity->getCurrencyCode()); + $this->orderxdocument->setIsTestDocument(true); + } else { + $this->orderxdocument->setDocumentInformation($this->document->number, OrderDocumentTypes::ORDER_RESPONSE, date_create($this->document->date ?? now()->format('Y-m-d')), $settings_entity->getCurrencyCode()); + } + break; + } + if (isset($this->document->po_number)) { + $this->orderxdocument->setDocumentBuyerOrderReferencedDocument($this->document->po_number); + } + + if (empty($settings_entity->routing_id)) { + $this->orderxdocument->setDocumentBuyerReference(ctrans("texts.xinvoice_no_buyers_reference")); + } else { + $this->orderxdocument->setDocumentBuyerReference($settings_entity->routing_id); + } + if (isset($settings_entity->shipping_address1) && $settings_entity->shipping_country) { + $this->orderxdocument->setDocumentShipToAddress($settings_entity->shipping_address1, $settings_entity->shipping_address2, "", $settings_entity->shipping_postal_code, $settings_entity->shipping_city, $settings_entity->shipping_country->iso_3166_2, $settings_entity->shipping_state); + } + + $this->orderxdocument->addDocumentPaymentMean(68, ctrans("texts.xinvoice_online_payment")); + + if (str_contains($company->getSetting('vat_number'), "/")) { + $this->orderxdocument->addDocumentSellerTaxRegistration("FC", $company->getSetting('vat_number')); + } else { + $this->orderxdocument->addDocumentSellerTaxRegistration("VA", $company->getSetting('vat_number')); + } + + $invoicing_data = $this->document->calc(); + + //Create line items and calculate taxes + foreach ($this->document->line_items as $index => $item) { + /** @var \App\DataMapper\InvoiceItem $item **/ + $this->orderxdocument->addNewPosition($index) + ->setDocumentPositionGrossPrice($item->gross_line_total) + ->setDocumentPositionNetPrice($item->line_total); + if (!empty($item->product_key)) { + if (!empty($item->notes)) { + $this->orderxdocument->setDocumentPositionProductDetails($item->product_key, $item->notes); + } else { + $this->orderxdocument->setDocumentPositionProductDetails($item->product_key); + } + } else { + if (!empty($item->notes)) { + $this->orderxdocument->setDocumentPositionProductDetails($item->notes); + } else { + $this->orderxdocument->setDocumentPositionProductDetails("no product name defined"); + } + } +// TODO: add item classification (kg, m^3, ...) +// if (isset($item->task_id)) { +// $this->orderxdocument->setDocumentPositionQuantity($item->quantity, "HUR"); +// } else { +// $this->orderxdocument->setDocumentPositionQuantity($item->quantity, "H87"); +// } + $linenetamount = $item->line_total; + if ($item->discount > 0) { + if ($this->document->is_amount_discount) { + $linenetamount -= $item->discount; + } else { + $linenetamount -= $linenetamount * ($item->discount / 100); + } + } + $this->orderxdocument->setDocumentPositionLineSummation($linenetamount); + // According to european law, each line item can only have one tax rate + if (!(empty($item->tax_name1) && empty($item->tax_name2) && empty($item->tax_name3))) { + $taxtype = $this->getTaxType($item->tax_id); + if (!empty($item->tax_name1)) { + $this->orderxdocument->addDocumentPositionTax($taxtype, 'VAT', $item->tax_rate1); + $this->addtoTaxMap($taxtype, $linenetamount, $item->tax_rate1); + } elseif (!empty($item->tax_name2)) { + $this->orderxdocument->addDocumentPositionTax($taxtype, 'VAT', $item->tax_rate2); + $this->addtoTaxMap($taxtype, $linenetamount, $item->tax_rate2); + } elseif (!empty($item->tax_name3)) { + $this->orderxdocument->addDocumentPositionTax($taxtype, 'VAT', $item->tax_rate3); + $this->addtoTaxMap($taxtype, $linenetamount, $item->tax_rate3); + } else { + nlog("Can't add correct tax position"); + } + } else { + if (!empty($this->document->tax_name1)) { + $taxtype = $this->getTaxType($this->document->tax_name1); + $this->orderxdocument->addDocumentPositionTax($taxtype, 'VAT', $this->document->tax_rate1); + $this->addtoTaxMap($taxtype, $linenetamount, $this->document->tax_rate1); + } elseif (!empty($this->document->tax_name2)) { + $taxtype = $this->getTaxType($this->document->tax_name2); + $this->orderxdocument->addDocumentPositionTax($taxtype, 'VAT', $this->document->tax_rate2); + $this->addtoTaxMap($taxtype, $linenetamount, $this->document->tax_rate2); + } elseif (!empty($this->document->tax_name3)) { + $taxtype = $this->getTaxType($this->document->tax_name3); + $this->orderxdocument->addDocumentPositionTax($taxtype, 'VAT', $this->document->tax_rate3); + $this->addtoTaxMap($taxtype, $linenetamount, $this->document->tax_rate3); + } else { + $taxtype = OrderDutyTaxFeeCategories::ZERO_RATED_GOODS; + $this->orderxdocument->addDocumentPositionTax($taxtype, 'VAT', 0); + $this->addtoTaxMap($taxtype, $linenetamount, 0); + // nlog("Can't add correct tax position"); + } + } + } + + $this->orderxdocument->setDocumentSummation($this->document->amount, $this->document->balance, $invoicing_data->getSubTotal(), $invoicing_data->getTotalSurcharges(), $invoicing_data->getTotalDiscount(), $invoicing_data->getSubTotal(), $invoicing_data->getItemTotalTaxes(), 0.0, $this->document->amount - $this->document->balance); + + foreach ($this->tax_map as $item) { + $this->orderxdocument->addDocumentTax($item["tax_type"], "VAT", $item["net_amount"], $item["tax_rate"] * $item["net_amount"], $item["tax_rate"] * 100); + } + + // The validity can be checked using https://portal3.gefeg.com/invoice/validation or https://e-rechnung.bayern.de/app/#/upload + return $this; + + } + + /** + * Returns the XML document + * in string format + * + * @return string + */ + public function getXml(): string + { + return $this->orderxdocument->getContent(); + } + + private function getTaxType($name): string + { + $tax_type = null; + switch ($name) { + case Product::PRODUCT_TYPE_SERVICE: + case Product::PRODUCT_TYPE_DIGITAL: + case Product::PRODUCT_TYPE_PHYSICAL: + case Product::PRODUCT_TYPE_SHIPPING: + case Product::PRODUCT_TYPE_REDUCED_TAX: + $tax_type = OrderDutyTaxFeeCategories::STANDARD_RATE; + break; + case Product::PRODUCT_TYPE_EXEMPT: + $tax_type = OrderDutyTaxFeeCategories::EXEMPT_FROM_TAX; + break; + case Product::PRODUCT_TYPE_ZERO_RATED: + $tax_type = OrderDutyTaxFeeCategories::ZERO_RATED_GOODS; + break; + case Product::PRODUCT_TYPE_REVERSE_TAX: + $tax_type = OrderDutyTaxFeeCategories::VAT_REVERSE_CHARGE; + break; + } + $eu_states = ["AT", "BE", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR", "DE", "EL", "GR", "HU", "IE", "IT", "LV", "LT", "LU", "MT", "NL", "PL", "PT", "RO", "SK", "SI", "ES", "SE", "IS", "LI", "NO", "CH"]; + if (empty($tax_type)) { + if ((in_array($this->document->company->country()->iso_3166_2, $eu_states) && in_array($this->document->client->country->iso_3166_2, $eu_states)) && $this->document->company->country()->iso_3166_2 != $this->document->client->country->iso_3166_2) { + $tax_type = OrderDutyTaxFeeCategories::VAT_EXEMPT_FOR_EEA_INTRACOMMUNITY_SUPPLY_OF_GOODS_AND_SERVICES; + } elseif (!in_array($this->document->client->country->iso_3166_2, $eu_states)) { + $tax_type = OrderDutyTaxFeeCategories::SERVICE_OUTSIDE_SCOPE_OF_TAX; + } elseif ($this->document->client->country->iso_3166_2 == "ES-CN") { + $tax_type = OrderDutyTaxFeeCategories::CANARY_ISLANDS_GENERAL_INDIRECT_TAX; + } elseif (in_array($this->document->client->country->iso_3166_2, ["ES-CE", "ES-ML"])) { + $tax_type = OrderDutyTaxFeeCategories::TAX_FOR_PRODUCTION_SERVICES_AND_IMPORTATION_IN_CEUTA_AND_MELILLA; + } else { + nlog("Unkown tax case for xinvoice"); + $tax_type = OrderDutyTaxFeeCategories::STANDARD_RATE; + } + } + return $tax_type; + } + private function addtoTaxMap(string $tax_type, float $net_amount, float $tax_rate): void + { + $hash = hash("md5", $tax_type."-".$tax_rate); + if (array_key_exists($hash, $this->tax_map)) { + $this->tax_map[$hash]["net_amount"] += $net_amount; + } else { + $this->tax_map[$hash] = [ + "tax_type" => $tax_type, + "net_amount" => $net_amount, + "tax_rate" => $tax_rate / 100 + ]; + } + } + +} diff --git a/app/Services/Invoice/EInvoice/RoEInvoice.php b/app/Services/EDocument/Standards/RoEInvoice.php similarity index 99% rename from app/Services/Invoice/EInvoice/RoEInvoice.php rename to app/Services/EDocument/Standards/RoEInvoice.php index d01aae492022..d9945e9834df 100644 --- a/app/Services/Invoice/EInvoice/RoEInvoice.php +++ b/app/Services/EDocument/Standards/RoEInvoice.php @@ -9,28 +9,28 @@ * @license https://www.elastic.co/licensing/elastic-license */ -namespace App\Services\Invoice\EInvoice; +namespace App\Services\EDocument\Standards; use App\Models\Invoice; use App\Services\AbstractService; use CleverIt\UBL\Invoice\Address; +use CleverIt\UBL\Invoice\ClassifiedTaxCategory; use CleverIt\UBL\Invoice\Contact; use CleverIt\UBL\Invoice\Country; use CleverIt\UBL\Invoice\Generator; use CleverIt\UBL\Invoice\Invoice as UBLInvoice; use CleverIt\UBL\Invoice\InvoiceLine; use CleverIt\UBL\Invoice\Item; +use CleverIt\UBL\Invoice\LegalEntity; use CleverIt\UBL\Invoice\LegalMonetaryTotal; use CleverIt\UBL\Invoice\Party; +use CleverIt\UBL\Invoice\PayeeFinancialAccount; +use CleverIt\UBL\Invoice\PaymentMeans; +use CleverIt\UBL\Invoice\Price; use CleverIt\UBL\Invoice\TaxCategory; use CleverIt\UBL\Invoice\TaxScheme; use CleverIt\UBL\Invoice\TaxSubTotal; use CleverIt\UBL\Invoice\TaxTotal; -use CleverIt\UBL\Invoice\PaymentMeans; -use CleverIt\UBL\Invoice\PayeeFinancialAccount; -use CleverIt\UBL\Invoice\LegalEntity; -use CleverIt\UBL\Invoice\ClassifiedTaxCategory; -use CleverIt\UBL\Invoice\Price; class RoEInvoice extends AbstractService { diff --git a/app/Services/Invoice/EInvoice/ZugferdEInvoice.php b/app/Services/EDocument/Standards/ZugferdEDokument.php similarity index 56% rename from app/Services/Invoice/EInvoice/ZugferdEInvoice.php rename to app/Services/EDocument/Standards/ZugferdEDokument.php index 59ab32605a58..918a96a6177b 100644 --- a/app/Services/Invoice/EInvoice/ZugferdEInvoice.php +++ b/app/Services/EDocument/Standards/ZugferdEDokument.php @@ -9,28 +9,31 @@ * @license https://www.elastic.co/licensing/elastic-license */ -namespace App\Services\Invoice\EInvoice; +namespace App\Services\EDocument\Standards; +use App\Models\Credit; use App\Models\Invoice; use App\Models\Product; +use App\Models\PurchaseOrder; +use App\Models\Quote; use App\Services\AbstractService; use horstoeko\zugferd\codelists\ZugferdDutyTaxFeeCategories; use horstoeko\zugferd\ZugferdDocumentBuilder; use horstoeko\zugferd\ZugferdProfiles; -class ZugferdEInvoice extends AbstractService +class ZugferdEDokument extends AbstractService { - public ZugferdDocumentBuilder $xrechnung; + public ZugferdDocumentBuilder $xdocument; - public function __construct(public Invoice $invoice, private readonly bool $returnObject = false, private array $tax_map = []) + public function __construct(public object $document, private readonly bool $returnObject = false, private array $tax_map = []) { } public function run(): self { - $company = $this->invoice->company; - $client = $this->invoice->client; + $company = $this->document->company; + $client = $this->document->client; $profile = $client->getSetting('e_invoice_type'); $profile = match ($profile) { @@ -46,123 +49,143 @@ class ZugferdEInvoice extends AbstractService default => ZugferdProfiles::PROFILE_EN16931, }; - $this->xrechnung = ZugferdDocumentBuilder::CreateNew($profile); + $this->xdocument = ZugferdDocumentBuilder::CreateNew($profile); - $this->xrechnung - ->setDocumentSupplyChainEvent(date_create($this->invoice->date ?? now()->format('Y-m-d'))) + $this->xdocument + ->setDocumentSupplyChainEvent(date_create($this->document->date ?? now()->format('Y-m-d'))) ->setDocumentSeller($company->getSetting('name')) ->setDocumentSellerAddress($company->getSetting("address1"), $company->getSetting("address2"), "", $company->getSetting("postal_code"), $company->getSetting("city"), $company->country()->iso_3166_2, $company->getSetting("state")) - ->setDocumentSellerContact($this->invoice->user->present()->getFullName(), "", $this->invoice->user->present()->phone(), "", $this->invoice->user->email) + ->setDocumentSellerContact($this->document->user->present()->getFullName(), "", $this->document->user->present()->phone(), "", $this->document->user->email) ->setDocumentBuyer($client->present()->name(), $client->number) ->setDocumentBuyerAddress($client->address1, "", "", $client->postal_code, $client->city, $client->country->iso_3166_2, $client->state) ->setDocumentBuyerContact($client->present()->primary_contact_name(), "", $client->present()->phone(), "", $client->present()->email()) - ->addDocumentPaymentTerm(ctrans("texts.xinvoice_payable", ['payeddue' => date_create($this->invoice->date ?? now()->format('Y-m-d'))->diff(date_create($this->invoice->due_date ?? now()->format('Y-m-d')))->format("%d"), 'paydate' => $this->invoice->due_date])); + ->addDocumentPaymentTerm(ctrans("texts.xinvoice_payable", ['payeddue' => date_create($this->document->date ?? now()->format('Y-m-d'))->diff(date_create($this->document->due_date ?? now()->format('Y-m-d')))->format("%d"), 'paydate' => $this->document->due_date])); - if (!empty($this->invoice->public_notes)) { - $this->xrechnung->addDocumentNote($this->invoice->public_notes ?? ''); + if (!empty($this->document->public_notes)) { + $this->xdocument->addDocumentNote($this->document->public_notes ?? ''); } - if (empty($this->invoice->number)) { - $this->xrechnung->setDocumentInformation("DRAFT", "380", date_create($this->invoice->date ?? now()->format('Y-m-d')), $client->getCurrencyCode()); - } else { - $this->xrechnung->setDocumentInformation($this->invoice->number, "380", date_create($this->invoice->date ?? now()->format('Y-m-d')), $client->getCurrencyCode()); + // Document type + $document_class = get_class($this->document); + switch ($document_class){ + case Quote::class: + // Probably wrong file code https://github.com/horstoeko/zugferd/blob/master/src/codelists/ZugferdInvoiceType.php + if (empty($this->document->number)) { + $this->xdocument->setDocumentInformation("DRAFT", "84", date_create($this->document->date ?? now()->format('Y-m-d')), $client->getCurrencyCode()); + } else { + $this->xdocument->setDocumentInformation($this->document->number, "84", date_create($this->document->date ?? now()->format('Y-m-d')), $client->getCurrencyCode()); + }; + break; + case Invoice::class: + if (empty($this->document->number)) { + $this->xdocument->setDocumentInformation("DRAFT", "380", date_create($this->document->date ?? now()->format('Y-m-d')), $client->getCurrencyCode()); + } else { + $this->xdocument->setDocumentInformation($this->document->number, "380", date_create($this->document->date ?? now()->format('Y-m-d')), $client->getCurrencyCode()); + } + break; + case Credit::class: + if (empty($this->document->number)) { + $this->xdocument->setDocumentInformation("DRAFT", "389", date_create($this->document->date ?? now()->format('Y-m-d')), $client->getCurrencyCode()); + } else { + $this->xdocument->setDocumentInformation($this->document->number, "389", date_create($this->document->date ?? now()->format('Y-m-d')), $client->getCurrencyCode()); + } } - if (isset($this->invoice->po_number)) { - $this->xrechnung->setDocumentBuyerOrderReferencedDocument($this->invoice->po_number); + if (isset($this->document->po_number)) { + $this->xdocument->setDocumentBuyerOrderReferencedDocument($this->document->po_number); } if (empty($client->routing_id)) { - $this->xrechnung->setDocumentBuyerReference(ctrans("texts.xinvoice_no_buyers_reference")); + $this->xdocument->setDocumentBuyerReference(ctrans("texts.xinvoice_no_buyers_reference")); } else { - $this->xrechnung->setDocumentBuyerReference($client->routing_id); + $this->xdocument->setDocumentBuyerReference($client->routing_id); } if (isset($client->shipping_address1) && $client->shipping_country) { - $this->xrechnung->setDocumentShipToAddress($client->shipping_address1, $client->shipping_address2, "", $client->shipping_postal_code, $client->shipping_city, $client->shipping_country->iso_3166_2, $client->shipping_state); + $this->xdocument->setDocumentShipToAddress($client->shipping_address1, $client->shipping_address2, "", $client->shipping_postal_code, $client->shipping_city, $client->shipping_country->iso_3166_2, $client->shipping_state); } - $this->xrechnung->addDocumentPaymentMean(68, ctrans("texts.xinvoice_online_payment")); + $this->xdocument->addDocumentPaymentMean(68, ctrans("texts.xinvoice_online_payment")); if (str_contains($company->getSetting('vat_number'), "/")) { - $this->xrechnung->addDocumentSellerTaxRegistration("FC", $company->getSetting('vat_number')); + $this->xdocument->addDocumentSellerTaxRegistration("FC", $company->getSetting('vat_number')); } else { - $this->xrechnung->addDocumentSellerTaxRegistration("VA", $company->getSetting('vat_number')); + $this->xdocument->addDocumentSellerTaxRegistration("VA", $company->getSetting('vat_number')); } - $invoicing_data = $this->invoice->calc(); + $invoicing_data = $this->document->calc(); //Create line items and calculate taxes - foreach ($this->invoice->line_items as $index => $item) { + foreach ($this->document->line_items as $index => $item) { /** @var \App\DataMapper\InvoiceItem $item **/ - $this->xrechnung->addNewPosition($index) + $this->xdocument->addNewPosition($index) ->setDocumentPositionGrossPrice($item->gross_line_total) ->setDocumentPositionNetPrice($item->line_total); if (!empty($item->product_key)) { if (!empty($item->notes)) { - $this->xrechnung->setDocumentPositionProductDetails($item->product_key, $item->notes); + $this->xdocument->setDocumentPositionProductDetails($item->product_key, $item->notes); } else { - $this->xrechnung->setDocumentPositionProductDetails($item->product_key); + $this->xdocument->setDocumentPositionProductDetails($item->product_key); } } else { if (!empty($item->notes)) { - $this->xrechnung->setDocumentPositionProductDetails($item->notes); + $this->xdocument->setDocumentPositionProductDetails($item->notes); } else { - $this->xrechnung->setDocumentPositionProductDetails("no product name defined"); + $this->xdocument->setDocumentPositionProductDetails("no product name defined"); } } - if (isset($item->task_id)) { - $this->xrechnung->setDocumentPositionQuantity($item->quantity, "HUR"); + if ($item->type_id == 2) { + $this->xdocument->setDocumentPositionQuantity($item->quantity, "HUR"); } else { - $this->xrechnung->setDocumentPositionQuantity($item->quantity, "H87"); + $this->xdocument->setDocumentPositionQuantity($item->quantity, "H87"); } $linenetamount = $item->line_total; if ($item->discount > 0) { - if ($this->invoice->is_amount_discount) { + if ($this->document->is_amount_discount) { $linenetamount -= $item->discount; } else { $linenetamount -= $linenetamount * ($item->discount / 100); } } - $this->xrechnung->setDocumentPositionLineSummation($linenetamount); + $this->xdocument->setDocumentPositionLineSummation($linenetamount); // According to european law, each line item can only have one tax rate if (!(empty($item->tax_name1) && empty($item->tax_name2) && empty($item->tax_name3))) { $taxtype = $this->getTaxType($item->tax_id); if (!empty($item->tax_name1)) { - $this->xrechnung->addDocumentPositionTax($taxtype, 'VAT', $item->tax_rate1); + $this->xdocument->addDocumentPositionTax($taxtype, 'VAT', $item->tax_rate1); $this->addtoTaxMap($taxtype, $linenetamount, $item->tax_rate1); } elseif (!empty($item->tax_name2)) { - $this->xrechnung->addDocumentPositionTax($taxtype, 'VAT', $item->tax_rate2); + $this->xdocument->addDocumentPositionTax($taxtype, 'VAT', $item->tax_rate2); $this->addtoTaxMap($taxtype, $linenetamount, $item->tax_rate2); } elseif (!empty($item->tax_name3)) { - $this->xrechnung->addDocumentPositionTax($taxtype, 'VAT', $item->tax_rate3); + $this->xdocument->addDocumentPositionTax($taxtype, 'VAT', $item->tax_rate3); $this->addtoTaxMap($taxtype, $linenetamount, $item->tax_rate3); } else { // nlog("Can't add correct tax position"); } } else { - if (!empty($this->invoice->tax_name1)) { - $taxtype = $this->getTaxType($this->invoice->tax_name1); - $this->xrechnung->addDocumentPositionTax($taxtype, 'VAT', $this->invoice->tax_rate1); - $this->addtoTaxMap($taxtype, $linenetamount, $this->invoice->tax_rate1); - } elseif (!empty($this->invoice->tax_name2)) { - $taxtype = $this->getTaxType($this->invoice->tax_name2); - $this->xrechnung->addDocumentPositionTax($taxtype, 'VAT', $this->invoice->tax_rate2); - $this->addtoTaxMap($taxtype, $linenetamount, $this->invoice->tax_rate2); - } elseif (!empty($this->invoice->tax_name3)) { - $taxtype = $this->getTaxType($this->invoice->tax_name3); - $this->xrechnung->addDocumentPositionTax($taxtype, 'VAT', $this->invoice->tax_rate3); - $this->addtoTaxMap($taxtype, $linenetamount, $this->invoice->tax_rate3); + if (!empty($this->document->tax_name1)) { + $taxtype = $this->getTaxType($this->document->tax_name1); + $this->xdocument->addDocumentPositionTax($taxtype, 'VAT', $this->document->tax_rate1); + $this->addtoTaxMap($taxtype, $linenetamount, $this->document->tax_rate1); + } elseif (!empty($this->document->tax_name2)) { + $taxtype = $this->getTaxType($this->document->tax_name2); + $this->xdocument->addDocumentPositionTax($taxtype, 'VAT', $this->document->tax_rate2); + $this->addtoTaxMap($taxtype, $linenetamount, $this->document->tax_rate2); + } elseif (!empty($this->document->tax_name3)) { + $taxtype = $this->getTaxType($this->document->tax_name3); + $this->xdocument->addDocumentPositionTax($taxtype, 'VAT', $this->document->tax_rate3); + $this->addtoTaxMap($taxtype, $linenetamount, $this->document->tax_rate3); } else { $taxtype = ZugferdDutyTaxFeeCategories::ZERO_RATED_GOODS; - $this->xrechnung->addDocumentPositionTax($taxtype, 'VAT', 0); + $this->xdocument->addDocumentPositionTax($taxtype, 'VAT', 0); $this->addtoTaxMap($taxtype, $linenetamount, 0); // nlog("Can't add correct tax position"); } } } - $this->xrechnung->setDocumentSummation($this->invoice->amount, $this->invoice->balance, $invoicing_data->getSubTotal(), $invoicing_data->getTotalSurcharges(), $invoicing_data->getTotalDiscount(), $invoicing_data->getSubTotal(), $invoicing_data->getItemTotalTaxes(), 0.0, $this->invoice->amount - $this->invoice->balance); + $this->xdocument->setDocumentSummation($this->document->amount, $this->document->balance, $invoicing_data->getSubTotal(), $invoicing_data->getTotalSurcharges(), $invoicing_data->getTotalDiscount(), $invoicing_data->getSubTotal(), $invoicing_data->getItemTotalTaxes(), 0.0, $this->document->amount - $this->document->balance); foreach ($this->tax_map as $item) { - $this->xrechnung->addDocumentTax($item["tax_type"], "VAT", $item["net_amount"], $item["tax_rate"] * $item["net_amount"], $item["tax_rate"] * 100); + $this->xdocument->addDocumentTax($item["tax_type"], "VAT", $item["net_amount"], $item["tax_rate"] * $item["net_amount"], $item["tax_rate"] * 100); } // The validity can be checked using https://portal3.gefeg.com/invoice/validation or https://e-rechnung.bayern.de/app/#/upload @@ -178,7 +201,7 @@ class ZugferdEInvoice extends AbstractService */ public function getXml(): string { - return $this->xrechnung->getContent(); + return $this->xdocument->getContent(); } private function getTaxType($name): string @@ -204,13 +227,13 @@ class ZugferdEInvoice extends AbstractService } $eu_states = ["AT", "BE", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR", "DE", "EL", "GR", "HU", "IE", "IT", "LV", "LT", "LU", "MT", "NL", "PL", "PT", "RO", "SK", "SI", "ES", "SE", "IS", "LI", "NO", "CH"]; if (empty($tax_type)) { - if ((in_array($this->invoice->company->country()->iso_3166_2, $eu_states) && in_array($this->invoice->client->country->iso_3166_2, $eu_states)) && $this->invoice->company->country()->iso_3166_2 != $this->invoice->client->country->iso_3166_2) { + if ((in_array($this->document->company->country()->iso_3166_2, $eu_states) && in_array($this->document->client->country->iso_3166_2, $eu_states)) && $this->document->company->country()->iso_3166_2 != $this->document->client->country->iso_3166_2) { $tax_type = ZugferdDutyTaxFeeCategories::VAT_EXEMPT_FOR_EEA_INTRACOMMUNITY_SUPPLY_OF_GOODS_AND_SERVICES; - } elseif (!in_array($this->invoice->client->country->iso_3166_2, $eu_states)) { + } elseif (!in_array($this->document->client->country->iso_3166_2, $eu_states)) { $tax_type = ZugferdDutyTaxFeeCategories::SERVICE_OUTSIDE_SCOPE_OF_TAX; - } elseif ($this->invoice->client->country->iso_3166_2 == "ES-CN") { + } elseif ($this->document->client->country->iso_3166_2 == "ES-CN") { $tax_type = ZugferdDutyTaxFeeCategories::CANARY_ISLANDS_GENERAL_INDIRECT_TAX; - } elseif (in_array($this->invoice->client->country->iso_3166_2, ["ES-CE", "ES-ML"])) { + } elseif (in_array($this->document->client->country->iso_3166_2, ["ES-CE", "ES-ML"])) { $tax_type = ZugferdDutyTaxFeeCategories::TAX_FOR_PRODUCTION_SERVICES_AND_IMPORTATION_IN_CEUTA_AND_MELILLA; } else { nlog("Unkown tax case for xinvoice"); diff --git a/app/Services/Email/AdminEmail.php b/app/Services/Email/AdminEmail.php index a2a874ca2308..15af006de861 100644 --- a/app/Services/Email/AdminEmail.php +++ b/app/Services/Email/AdminEmail.php @@ -59,6 +59,8 @@ class AdminEmail implements ShouldQueue protected ?string $client_mailgun_endpoint = null; + protected ?string $client_brevo_secret = null; + private string $mailer = 'default'; public Mailable $mailable; @@ -82,7 +84,7 @@ class AdminEmail implements ShouldQueue MultiDB::setDb($this->company->db); $this->setOverride() - ->buildMailable(); + ->buildMailable(); if ($this->preFlightChecksFail()) { return; @@ -137,24 +139,28 @@ class AdminEmail implements ShouldQueue $mailer->mailgun_config($this->client_mailgun_secret, $this->client_mailgun_domain, $this->client_mailgun_endpoint); } + if ($this->client_brevo_secret) { + $mailer->brevo_config($this->client_brevo_secret); + } + /* Attempt the send! */ try { - nlog("Using mailer => ". $this->mailer. " ". now()->toDateTimeString()); + nlog("Using mailer => " . $this->mailer . " " . now()->toDateTimeString()); $mailer->send($this->mailable); - Cache::increment("email_quota".$this->company->account->key); + Cache::increment("email_quota" . $this->company->account->key); LightLogs::create(new EmailSuccess($this->company->company_key, $this->mailable->subject)) - ->send(); + ->send(); - } catch(\Symfony\Component\Mime\Exception\RfcComplianceException $e) { + } catch (\Symfony\Component\Mime\Exception\RfcComplianceException $e) { nlog("Mailer failed with a Logic Exception {$e->getMessage()}"); $this->fail(); $this->cleanUpMailers(); $this->logMailError($e->getMessage(), $this->company->clients()->first()); return; - } catch(\Symfony\Component\Mime\Exception\LogicException $e) { + } catch (\Symfony\Component\Mime\Exception\LogicException $e) { nlog("Mailer failed with a Logic Exception {$e->getMessage()}"); $this->fail(); $this->cleanUpMailers(); @@ -215,16 +221,16 @@ class AdminEmail implements ShouldQueue } /** - * On the hosted platform we scan all outbound email for - * spam. This sequence processes the filters we use on all - * emails. - * - * @return bool - */ + * On the hosted platform we scan all outbound email for + * spam. This sequence processes the filters we use on all + * emails. + * + * @return bool + */ public function preFlightChecksFail(): bool { /* Always send if disabled */ - if($this->override) { + if ($this->override) { return false; } @@ -248,7 +254,7 @@ class AdminEmail implements ShouldQueue } /* GMail users are uncapped */ - if (in_array($this->email_object->settings->email_sending_method, ['gmail', 'office365', 'client_postmark', 'client_mailgun'])) { + if (in_array($this->email_object->settings->email_sending_method, ['gmail', 'office365', 'client_postmark', 'client_mailgun', 'client_brevo'])) { return false; } @@ -337,6 +343,10 @@ class AdminEmail implements ShouldQueue $this->mailer = 'mailgun'; $this->setMailgunMailer(); return $this; + case 'client_brevo': + $this->mailer = 'brevo'; + $this->setBrevoMailer(); + return $this; default: $this->mailer = config('mail.default'); @@ -369,7 +379,7 @@ class AdminEmail implements ShouldQueue if (env($this->company->id . '_MAIL_FROM_ADDRESS')) { $this->mailable - ->from(env($this->company->id . '_MAIL_FROM_ADDRESS', env('MAIL_FROM_ADDRESS')), env($this->company->id . '_MAIL_FROM_NAME', env('MAIL_FROM_NAME'))); + ->from(env($this->company->id . '_MAIL_FROM_ADDRESS', env('MAIL_FROM_ADDRESS')), env($this->company->id . '_MAIL_FROM_NAME', env('MAIL_FROM_NAME'))); } } } @@ -390,6 +400,8 @@ class AdminEmail implements ShouldQueue $this->client_mailgun_endpoint = null; + $this->client_brevo_secret = null; + //always dump the drivers to prevent reuse app('mail.manager')->forgetMailers(); } @@ -452,7 +464,29 @@ class AdminEmail implements ShouldQueue $sending_user = (isset($this->email_object->settings->email_from_name) && strlen($this->email_object->settings->email_from_name) > 2) ? $this->email_object->settings->email_from_name : $user->name(); $this->mailable - ->from($sending_email, $sending_user); + ->from($sending_email, $sending_user); + } + /** + * Configures Brevo using client supplied secret + * as the Mailer + */ + private function setBrevoMailer() + { + if (strlen($this->email_object->settings->brevo_secret) > 2) { + $this->client_brevo_secret = $this->email_object->settings->brevo_secret; + + } else { + $this->email_object->settings->email_sending_method = 'default'; + return $this->setMailDriver(); + } + + $user = $this->resolveSendingUser(); + + $sending_email = (isset($this->email_object->settings->custom_sending_email) && stripos($this->email_object->settings->custom_sending_email, "@")) ? $this->email_object->settings->custom_sending_email : $user->email; + $sending_user = (isset($this->email_object->settings->email_from_name) && strlen($this->email_object->settings->email_from_name) > 2) ? $this->email_object->settings->email_from_name : $user->name(); + + $this->mailable + ->from($sending_email, $sending_user); } /** @@ -474,7 +508,7 @@ class AdminEmail implements ShouldQueue $sending_user = (isset($this->email_object->settings->email_from_name) && strlen($this->email_object->settings->email_from_name) > 2) ? $this->email_object->settings->email_from_name : $user->name(); $this->mailable - ->from($sending_email, $sending_user); + ->from($sending_email, $sending_user); } /** @@ -500,10 +534,10 @@ class AdminEmail implements ShouldQueue } $this->mailable - ->from($user->email, $user->name()) - ->withSymfonyMessage(function ($message) use ($token) { - $message->getHeaders()->addTextHeader('gmailtoken', $token); - }); + ->from($user->email, $user->name()) + ->withSymfonyMessage(function ($message) use ($token) { + $message->getHeaders()->addTextHeader('gmailtoken', $token); + }); } /** @@ -527,7 +561,7 @@ class AdminEmail implements ShouldQueue } $google->getClient()->setAccessToken(json_encode($user->oauth_user_token)); - } catch(\Exception $e) { + } catch (\Exception $e) { $this->logMailError('Gmail Token Invalid', $this->company->clients()->first()); $this->email_object->settings->email_sending_method = 'default'; return $this->setMailDriver(); @@ -547,7 +581,7 @@ class AdminEmail implements ShouldQueue * Now that our token is refreshed and valid we can boot the * mail driver at runtime and also set the token which will persist * just for this request. - */ + */ $token = $user->oauth_user_token->access_token; @@ -558,10 +592,10 @@ class AdminEmail implements ShouldQueue } $this->mailable - ->from($user->email, $user->name()) - ->withSymfonyMessage(function ($message) use ($token) { - $message->getHeaders()->addTextHeader('gmailtoken', $token); - }); + ->from($user->email, $user->name()) + ->withSymfonyMessage(function ($message) use ($token) { + $message->getHeaders()->addTextHeader('gmailtoken', $token); + }); } /** @@ -573,21 +607,23 @@ class AdminEmail implements ShouldQueue */ private function logMailError($errors, $recipient_object): void { - (new SystemLogger( - $errors, - SystemLog::CATEGORY_MAIL, - SystemLog::EVENT_MAIL_SEND, - SystemLog::TYPE_FAILURE, - $recipient_object, - $this->company - ))->handle(); + ( + new SystemLogger( + $errors, + SystemLog::CATEGORY_MAIL, + SystemLog::EVENT_MAIL_SEND, + SystemLog::TYPE_FAILURE, + $recipient_object, + $this->company + ) + )->handle(); $job_failure = new EmailFailure($this->company->company_key); $job_failure->string_metric5 = 'failed_email'; $job_failure->string_metric6 = substr($errors, 0, 150); LightLogs::create($job_failure) - ->send(); + ->send(); $job_failure = null; } @@ -608,8 +644,8 @@ class AdminEmail implements ShouldQueue $token = json_decode($guzzle->post($url, [ 'form_params' => [ - 'client_id' => config('ninja.o365.client_id') , - 'client_secret' => config('ninja.o365.client_secret') , + 'client_id' => config('ninja.o365.client_id'), + 'client_secret' => config('ninja.o365.client_secret'), 'scope' => 'email Mail.Send offline_access profile User.Read openid', 'grant_type' => 'refresh_token', 'refresh_token' => $user->oauth_user_refresh_token diff --git a/app/Services/Email/Email.php b/app/Services/Email/Email.php index f47189009b3f..c07d34515a0f 100644 --- a/app/Services/Email/Email.php +++ b/app/Services/Email/Email.php @@ -41,6 +41,7 @@ use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Mail; +use Log; use Turbo124\Beacon\Facades\LightLogs; class Email implements ShouldQueue @@ -72,6 +73,9 @@ class Email implements ShouldQueue /** MailGun endpoint */ protected ?string $client_mailgun_endpoint = null; + /** Brevo endpoint */ + protected ?string $client_brevo_secret = null; + /** Default mailer */ private string $mailer = 'default'; @@ -100,9 +104,9 @@ class Email implements ShouldQueue MultiDB::setDb($this->company->db); $this->setOverride() - ->initModels() - ->setDefaults() - ->buildMailable(); + ->initModels() + ->setDefaults() + ->buildMailable(); /** Ensure quota's on hosted platform are respected. :) */ $this->setMailDriver(); @@ -246,10 +250,10 @@ class Email implements ShouldQueue private function incrementEmailCounter(): void { - if(in_array($this->mailer, ['default','mailgun'])) + if(in_array($this->mailer, ['default','mailgun','postmark'])) Cache::increment("email_quota".$this->company->account->key); } - + /** * Attempts to send the email * @@ -261,6 +265,7 @@ class Email implements ShouldQueue /* Init the mailer*/ $mailer = Mail::mailer($this->mailer); + /* Additional configuration if using a client third party mailer */ if ($this->client_postmark_secret) { $mailer->postmark_config($this->client_postmark_secret); @@ -270,24 +275,28 @@ class Email implements ShouldQueue $mailer->mailgun_config($this->client_mailgun_secret, $this->client_mailgun_domain, $this->client_mailgun_endpoint); } + if ($this->client_brevo_secret) { + $mailer->brevo_config($this->client_brevo_secret); + } + /* Attempt the send! */ try { - nlog("Using mailer => ". $this->mailer. " ". now()->toDateTimeString()); + nlog("Using mailer => " . $this->mailer . " " . now()->toDateTimeString()); $mailer->send($this->mailable); $this->incrementEmailCounter(); LightLogs::create(new EmailSuccess($this->company->company_key, $this->mailable->subject)) - ->send(); + ->send(); - } catch(\Symfony\Component\Mime\Exception\RfcComplianceException $e) { + } catch (\Symfony\Component\Mime\Exception\RfcComplianceException $e) { nlog("Mailer failed with a Logic Exception {$e->getMessage()}"); $this->fail(); $this->cleanUpMailers(); $this->logMailError($e->getMessage(), $this->company->clients()->first()); return; - } catch(\Symfony\Component\Mime\Exception\LogicException $e) { + } catch (\Symfony\Component\Mime\Exception\LogicException $e) { nlog("Mailer failed with a Logic Exception {$e->getMessage()}"); $this->fail(); $this->cleanUpMailers(); @@ -367,16 +376,16 @@ class Email implements ShouldQueue } /** - * On the hosted platform we scan all outbound email for - * spam. This sequence processes the filters we use on all - * emails. - * - * @return bool - */ + * On the hosted platform we scan all outbound email for + * spam. This sequence processes the filters we use on all + * emails. + * + * @return bool + */ public function preFlightChecksFail(): bool { /* Always send if disabled */ - if($this->override) { + if ($this->override) { return false; } @@ -400,7 +409,7 @@ class Email implements ShouldQueue } /* GMail users are uncapped */ - if (in_array($this->email_object->settings->email_sending_method, ['gmail', 'office365', 'client_postmark', 'client_mailgun'])) { + if (in_array($this->email_object->settings->email_sending_method, ['gmail', 'office365', 'client_postmark', 'client_mailgun', 'client_brevo'])) { return false; } @@ -461,7 +470,7 @@ class Email implements ShouldQueue return true; } - if($address_object->name == " " || $address_object->name == "") { + if ($address_object->name == " " || $address_object->name == "") { return true; } } @@ -479,7 +488,7 @@ class Email implements ShouldQueue } $this->mailable - ->from(config('services.mailgun.from.address'), $email_from_name); + ->from(config('services.mailgun.from.address'), $email_from_name); } @@ -498,7 +507,7 @@ class Email implements ShouldQueue // return $this; // } - if(Ninja::isHosted() && $this->company->account->isPaid() && $this->email_object->settings->email_sending_method == 'default') { + if (Ninja::isHosted() && $this->company->account->isPaid() && $this->email_object->settings->email_sending_method == 'default') { try { @@ -507,7 +516,7 @@ class Email implements ShouldQueue $domain = explode("@", $email)[1] ?? ""; $dns = dns_get_record($domain, DNS_MX); $server = $dns[0]["target"]; - if(stripos($server, "outlook.com") !== false) { + if (stripos($server, "outlook.com") !== false) { if (property_exists($this->email_object->settings, 'email_from_name') && strlen($this->email_object->settings->email_from_name) > 1) { $email_from_name = $this->email_object->settings->email_from_name; @@ -518,12 +527,12 @@ class Email implements ShouldQueue $this->mailer = 'postmark'; $this->client_postmark_secret = config('services.postmark-outlook.token'); $this->mailable - ->from(config('services.postmark-outlook.from.address'), $email_from_name); + ->from(config('services.postmark-outlook.from.address'), $email_from_name); return $this; - + } - } catch(\Exception $e) { + } catch (\Exception $e) { nlog("problem switching outlook driver - hosted"); nlog($e->getMessage()); } @@ -555,6 +564,10 @@ class Email implements ShouldQueue $this->mailer = 'mailgun'; $this->setMailgunMailer(); return $this; + case 'client_brevo': + $this->mailer = 'brevo'; + $this->setBrevoMailer(); + return $this; case 'smtp': $this->mailer = 'smtp'; $this->configureSmtpMailer(); @@ -600,11 +613,11 @@ class Email implements ShouldQueue $user = $this->resolveSendingUser(); - $sending_email = (isset($this->email_object->settings->custom_sending_email) && stripos($this->email_object->settings->custom_sending_email, "@")) ? $this->email_object->settings->custom_sending_email : $user->email; - $sending_user = (isset($this->email_object->settings->email_from_name) && strlen($this->email_object->settings->email_from_name) > 2) ? $this->email_object->settings->email_from_name : $user->name(); + $sending_email = (isset ($this->email_object->settings->custom_sending_email) && stripos($this->email_object->settings->custom_sending_email, "@")) ? $this->email_object->settings->custom_sending_email : $user->email; + $sending_user = (isset ($this->email_object->settings->email_from_name) && strlen($this->email_object->settings->email_from_name) > 2) ? $this->email_object->settings->email_from_name : $user->name(); $this->mailable - ->from($sending_email, $sending_user); + ->from($sending_email, $sending_user); } @@ -627,7 +640,7 @@ class Email implements ShouldQueue if (env($this->company->id . '_MAIL_FROM_ADDRESS')) { $this->mailable - ->from(env($this->company->id . '_MAIL_FROM_ADDRESS', env('MAIL_FROM_ADDRESS')), env($this->company->id . '_MAIL_FROM_NAME', env('MAIL_FROM_NAME'))); + ->from(env($this->company->id . '_MAIL_FROM_ADDRESS', env('MAIL_FROM_ADDRESS')), env($this->company->id . '_MAIL_FROM_NAME', env('MAIL_FROM_NAME'))); } } } @@ -648,6 +661,8 @@ class Email implements ShouldQueue $this->client_mailgun_endpoint = null; + $this->client_brevo_secret = null; + //always dump the drivers to prevent reuse app('mail.manager')->forgetMailers(); } @@ -706,11 +721,33 @@ class Email implements ShouldQueue $user = $this->resolveSendingUser(); - $sending_email = (isset($this->email_object->settings->custom_sending_email) && stripos($this->email_object->settings->custom_sending_email, "@")) ? $this->email_object->settings->custom_sending_email : $user->email; - $sending_user = (isset($this->email_object->settings->email_from_name) && strlen($this->email_object->settings->email_from_name) > 2) ? $this->email_object->settings->email_from_name : $user->name(); + $sending_email = (isset ($this->email_object->settings->custom_sending_email) && stripos($this->email_object->settings->custom_sending_email, "@")) ? $this->email_object->settings->custom_sending_email : $user->email; + $sending_user = (isset ($this->email_object->settings->email_from_name) && strlen($this->email_object->settings->email_from_name) > 2) ? $this->email_object->settings->email_from_name : $user->name(); $this->mailable - ->from($sending_email, $sending_user); + ->from($sending_email, $sending_user); + } + /** + * Configures Brevo using client supplied secret + * as the Mailer + */ + private function setBrevoMailer() + { + if (strlen($this->email_object->settings->brevo_secret) > 2) { + $this->client_brevo_secret = $this->email_object->settings->brevo_secret; + + } else { + $this->email_object->settings->email_sending_method = 'default'; + return $this->setMailDriver(); + } + + $user = $this->resolveSendingUser(); + + $sending_email = (isset ($this->email_object->settings->custom_sending_email) && stripos($this->email_object->settings->custom_sending_email, "@")) ? $this->email_object->settings->custom_sending_email : $user->email; + $sending_user = (isset ($this->email_object->settings->email_from_name) && strlen($this->email_object->settings->email_from_name) > 2) ? $this->email_object->settings->email_from_name : $user->name(); + + $this->mailable + ->from($sending_email, $sending_user); } /** @@ -728,11 +765,11 @@ class Email implements ShouldQueue $user = $this->resolveSendingUser(); - $sending_email = (isset($this->email_object->settings->custom_sending_email) && stripos($this->email_object->settings->custom_sending_email, "@")) ? $this->email_object->settings->custom_sending_email : $user->email; - $sending_user = (isset($this->email_object->settings->email_from_name) && strlen($this->email_object->settings->email_from_name) > 2) ? $this->email_object->settings->email_from_name : $user->name(); + $sending_email = (isset ($this->email_object->settings->custom_sending_email) && stripos($this->email_object->settings->custom_sending_email, "@")) ? $this->email_object->settings->custom_sending_email : $user->email; + $sending_user = (isset ($this->email_object->settings->email_from_name) && strlen($this->email_object->settings->email_from_name) > 2) ? $this->email_object->settings->email_from_name : $user->name(); $this->mailable - ->from($sending_email, $sending_user); + ->from($sending_email, $sending_user); } /** @@ -758,10 +795,10 @@ class Email implements ShouldQueue } $this->mailable - ->from($user->email, $user->name()) - ->withSymfonyMessage(function ($message) use ($token) { - $message->getHeaders()->addTextHeader('gmailtoken', $token); - }); + ->from($user->email, $user->name()) + ->withSymfonyMessage(function ($message) use ($token) { + $message->getHeaders()->addTextHeader('gmailtoken', $token); + }); } /** @@ -785,7 +822,7 @@ class Email implements ShouldQueue } $google->getClient()->setAccessToken(json_encode($user->oauth_user_token)); - } catch(\Exception $e) { + } catch (\Exception $e) { $this->logMailError('Gmail Token Invalid', $this->company->clients()->first()); $this->email_object->settings->email_sending_method = 'default'; return $this->setMailDriver(); @@ -805,7 +842,7 @@ class Email implements ShouldQueue * Now that our token is refreshed and valid we can boot the * mail driver at runtime and also set the token which will persist * just for this request. - */ + */ $token = $user->oauth_user_token->access_token; @@ -816,10 +853,10 @@ class Email implements ShouldQueue } $this->mailable - ->from($user->email, $user->name()) - ->withSymfonyMessage(function ($message) use ($token) { - $message->getHeaders()->addTextHeader('gmailtoken', $token); - }); + ->from($user->email, $user->name()) + ->withSymfonyMessage(function ($message) use ($token) { + $message->getHeaders()->addTextHeader('gmailtoken', $token); + }); } /** @@ -831,21 +868,23 @@ class Email implements ShouldQueue */ private function logMailError($errors, $recipient_object): void { - (new SystemLogger( - $errors, - SystemLog::CATEGORY_MAIL, - SystemLog::EVENT_MAIL_SEND, - SystemLog::TYPE_FAILURE, - $recipient_object, - $this->company - ))->handle(); + ( + new SystemLogger( + $errors, + SystemLog::CATEGORY_MAIL, + SystemLog::EVENT_MAIL_SEND, + SystemLog::TYPE_FAILURE, + $recipient_object, + $this->company + ) + )->handle(); $job_failure = new EmailFailure($this->company->company_key); $job_failure->string_metric5 = 'failed_email'; $job_failure->string_metric6 = substr($errors, 0, 150); LightLogs::create($job_failure) - ->send(); + ->send(); $job_failure = null; } @@ -866,8 +905,8 @@ class Email implements ShouldQueue $token = json_decode($guzzle->post($url, [ 'form_params' => [ - 'client_id' => config('ninja.o365.client_id') , - 'client_secret' => config('ninja.o365.client_secret') , + 'client_id' => config('ninja.o365.client_id'), + 'client_secret' => config('ninja.o365.client_secret'), 'scope' => 'email Mail.Send offline_access profile User.Read openid', 'grant_type' => 'refresh_token', 'refresh_token' => $user->oauth_user_refresh_token diff --git a/app/Services/Email/EmailDefaults.php b/app/Services/Email/EmailDefaults.php index 04b945ff47c8..5346c0636fe6 100644 --- a/app/Services/Email/EmailDefaults.php +++ b/app/Services/Email/EmailDefaults.php @@ -17,6 +17,8 @@ use App\Jobs\Invoice\CreateUbl; use App\Models\Account; use App\Models\Expense; use App\Models\Invoice; +use App\Models\PurchaseOrder; +use App\Models\Quote; use App\Models\Task; use App\Utils\Ninja; use App\Utils\Traits\MakesHash; @@ -318,7 +320,7 @@ class EmailDefaults } } /** E-Invoice xml file */ - if ($this->email->email_object->settings->enable_e_invoice && $this->email->email_object->entity instanceof Invoice) { + if ($this->email->email_object->settings->enable_e_invoice && ! $this->email->email_object->entity instanceof PurchaseOrder) { $xml_string = $this->email->email_object->entity->service()->getEInvoice(); if($xml_string) { diff --git a/app/Services/Invoice/InvoiceService.php b/app/Services/Invoice/InvoiceService.php index effe34daceba..c89c246a49da 100644 --- a/app/Services/Invoice/InvoiceService.php +++ b/app/Services/Invoice/InvoiceService.php @@ -11,21 +11,21 @@ namespace App\Services\Invoice; -use App\Models\Task; -use App\Utils\Ninja; +use App\Events\Invoice\InvoiceWasArchived; +use App\Jobs\EDocument\CreateEDocument; +use App\Jobs\Entity\CreateRawPdf; +use App\Jobs\Inventory\AdjustProductInventory; +use App\Libraries\Currency\Conversion\CurrencyApi; +use App\Models\CompanyGateway; use App\Models\Expense; use App\Models\Invoice; use App\Models\Payment; use App\Models\Subscription; -use App\Models\CompanyGateway; -use Illuminate\Support\Carbon; +use App\Models\Task; +use App\Utils\Ninja; use App\Utils\Traits\MakesHash; -use App\Jobs\Entity\CreateRawPdf; -use App\Jobs\Invoice\CreateEInvoice; +use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Storage; -use App\Events\Invoice\InvoiceWasArchived; -use App\Jobs\Inventory\AdjustProductInventory; -use App\Libraries\Currency\Conversion\CurrencyApi; class InvoiceService { @@ -201,7 +201,7 @@ class InvoiceService public function getEInvoice($contact = null) { - return (new CreateEInvoice($this->invoice))->handle(); + return (new CreateEDocument($this->invoice))->handle(); } public function sendEmail($contact = null) @@ -409,12 +409,12 @@ class InvoiceService $this->invoice->invitations->each(function ($invitation) { try { // if (Storage::disk(config('filesystems.default'))->exists($this->invoice->client->e_invoice_filepath($invitation).$this->invoice->getFileName("xml"))) { - Storage::disk(config('filesystems.default'))->delete($this->invoice->client->e_invoice_filepath($invitation).$this->invoice->getFileName("xml")); + Storage::disk(config('filesystems.default'))->delete($this->invoice->client->e_document_filepath($invitation).$this->invoice->getFileName("xml")); // } // if (Ninja::isHosted() && Storage::disk('public')->exists($this->invoice->client->e_invoice_filepath($invitation).$this->invoice->getFileName("xml"))) { if (Ninja::isHosted()) { - Storage::disk('public')->delete($this->invoice->client->e_invoice_filepath($invitation).$this->invoice->getFileName("xml")); + Storage::disk('public')->delete($this->invoice->client->e_document_filepath($invitation).$this->invoice->getFileName("xml")); } } catch (\Exception $e) { nlog($e->getMessage()); @@ -544,7 +544,7 @@ class InvoiceService return $this; } - public function fillDefaults() + public function fillDefaults(bool $is_recurring = false) { $this->invoice->load('client.company'); @@ -571,7 +571,7 @@ class InvoiceService $this->invoice->exchange_rate = $this->invoice->client->setExchangeRate(); } - if ($this->invoice->client->getSetting('auto_bill_standard_invoices')) { + if ($is_recurring && $this->invoice->client->getSetting('auto_bill_standard_invoices')) { $this->invoice->auto_bill_enabled = true; } diff --git a/app/Services/Pdf/PdfMock.php b/app/Services/Pdf/PdfMock.php index d6fb4e5aa53c..1afc3ef8a49b 100644 --- a/app/Services/Pdf/PdfMock.php +++ b/app/Services/Pdf/PdfMock.php @@ -113,24 +113,30 @@ class PdfMock /** @var \App\Models\Invoice | \App\Models\Credit | \App\Models\Quote $entity */ $entity = Invoice::factory()->make(); $entity->client = Client::factory()->make(['settings' => $settings]); + $entity->client->setRelation('company', $this->company); $entity->invitation = InvoiceInvitation::factory()->make(); break; case 'quote': /** @var \App\Models\Invoice | \App\Models\Credit | \App\Models\Quote $entity */ $entity = Quote::factory()->make(); $entity->client = Client::factory()->make(['settings' => $settings]); + $entity->client->setRelation('company', $this->company); $entity->invitation = QuoteInvitation::factory()->make(); break; case 'credit': /** @var \App\Models\Invoice | \App\Models\Credit | \App\Models\Quote $entity */ $entity = Credit::factory()->make(); $entity->client = Client::factory()->make(['settings' => $settings]); + $entity->client->setRelation('company', $this->company); $entity->invitation = CreditInvitation::factory()->make(); break; case 'purchase_order': - /** @var \App\Models\Invoice | \App\Models\Credit | \App\Models\Quote $entity */ + + /** @var \App\Models\PurchaseOrder $entity */ $entity = PurchaseOrder::factory()->make(); - $entity->client = Client::factory()->make(['settings' => $settings]); + // $entity->client = Client::factory()->make(['settings' => $settings]); + $entity->vendor = Vendor::factory()->make(); + $entity->vendor->setRelation('company', $this->company); $entity->invitation = PurchaseOrderInvitation::factory()->make(); break; case PurchaseOrder::class: @@ -138,17 +144,17 @@ class PdfMock $entity = PurchaseOrder::factory()->make(); $entity->invitation = PurchaseOrderInvitation::factory()->make(); $entity->vendor = Vendor::factory()->make(); + $entity->invitation->setRelation('company', $this->company); break; default: $entity = false; break; } - $entity->tax_map = $this->getTaxMap(); $entity->total_tax_map = $this->getTotalTaxMap(); $entity->invitation->company = $this->company; - + return $entity; } diff --git a/app/Services/Pdf/PdfService.php b/app/Services/Pdf/PdfService.php index e0db7d406419..4a579db80c52 100644 --- a/app/Services/Pdf/PdfService.php +++ b/app/Services/Pdf/PdfService.php @@ -11,7 +11,7 @@ namespace App\Services\Pdf; -use App\Jobs\Invoice\CreateEInvoice; +use App\Jobs\EDocument\CreateEDocument; use App\Models\Company; use App\Models\CreditInvitation; use App\Models\Invoice; @@ -216,7 +216,7 @@ class PdfService { try { - $e_rechnung = (new CreateEInvoice($this->config->entity, true))->handle(); + $e_rechnung = (new CreateEDocument($this->config->entity, true))->handle(); $pdfBuilder = new ZugferdDocumentPdfBuilder($e_rechnung, $pdf); $pdfBuilder->generateDocument(); diff --git a/app/Services/PurchaseOrder/PurchaseOrderService.php b/app/Services/PurchaseOrder/PurchaseOrderService.php index b7bc07cb2c77..686329599f62 100644 --- a/app/Services/PurchaseOrder/PurchaseOrderService.php +++ b/app/Services/PurchaseOrder/PurchaseOrderService.php @@ -11,8 +11,11 @@ namespace App\Services\PurchaseOrder; +use App\Jobs\EDocument\CreateEDocument; use App\Models\PurchaseOrder; +use App\Utils\Ninja; use App\Utils\Traits\MakesHash; +use Illuminate\Support\Facades\Storage; class PurchaseOrderService { @@ -75,6 +78,32 @@ class PurchaseOrderService return (new GetPurchaseOrderPdf($this->purchase_order, $contact))->run(); } + public function getEPurchaseOrder($contact = null) + { + return (new CreateEDocument($this->purchase_order))->handle(); + } + public function deleteEPurchaseOrder() + { + $this->purchase_order->load('invitations'); + + $this->purchase_order->invitations->each(function ($invitation) { + try { + // if (Storage::disk(config('filesystems.default'))->exists($this->invoice->client->e_invoice_filepath($invitation).$this->invoice->getFileName("xml"))) { + Storage::disk(config('filesystems.default'))->delete($this->purchase_order->vendor->e_document_filepath($invitation).$this->purchase_order->getFileName("xml")); + // } + + // if (Ninja::isHosted() && Storage::disk('public')->exists($this->invoice->client->e_invoice_filepath($invitation).$this->invoice->getFileName("xml"))) { + if (Ninja::isHosted()) { + Storage::disk('public')->delete($this->purchase_order->vendor->e_document_filepath($invitation).$this->purchase_order->getFileName("xml")); + } + } catch (\Exception $e) { + nlog($e->getMessage()); + } + }); + + return $this; + } + public function setStatus($status) { $this->purchase_order->status_id = $status; diff --git a/app/Services/Quote/QuoteService.php b/app/Services/Quote/QuoteService.php index ddfebbe00b0d..f2ee47666a67 100644 --- a/app/Services/Quote/QuoteService.php +++ b/app/Services/Quote/QuoteService.php @@ -13,6 +13,7 @@ namespace App\Services\Quote; use App\Events\Quote\QuoteWasApproved; use App\Exceptions\QuoteConversion; +use App\Jobs\EDocument\CreateEDocument; use App\Models\Project; use App\Models\Quote; use App\Repositories\QuoteRepository; @@ -72,6 +73,11 @@ class QuoteService return (new GetQuotePdf($this->quote, $contact))->run(); } + public function getEQuote($contact = null) + { + return (new CreateEDocument($this->quote))->handle(); + } + public function sendEmail($contact = null): self { $send_email = new SendEmail($this->quote, null, $contact); @@ -226,6 +232,27 @@ class QuoteService return $this; } + public function deleteEQuote() + { + $this->quote->load('invitations'); + + $this->quote->invitations->each(function ($invitation) { + try { + // if (Storage::disk(config('filesystems.default'))->exists($this->invoice->client->e_invoice_filepath($invitation).$this->invoice->getFileName("xml"))) { + Storage::disk(config('filesystems.default'))->delete($this->quote->client->e_document_filepath($invitation).$this->quote->getFileName("xml")); + // } + + // if (Ninja::isHosted() && Storage::disk('public')->exists($this->invoice->client->e_invoice_filepath($invitation).$this->invoice->getFileName("xml"))) { + if (Ninja::isHosted()) { + Storage::disk('public')->delete($this->quote->client->e_document_filepath($invitation).$this->quote->getFileName("xml")); + } + } catch (\Exception $e) { + nlog($e->getMessage()); + } + }); + + return $this; + } /** * Saves the quote. diff --git a/app/Services/Template/TemplateService.php b/app/Services/Template/TemplateService.php index e9fcced20a5e..d3710ba722b0 100644 --- a/app/Services/Template/TemplateService.php +++ b/app/Services/Template/TemplateService.php @@ -613,8 +613,6 @@ class TemplateService $this->payment = $payment; - $this->addGlobal(['currency_code' => $payment->currency->code ?? $this->company->currency()->code]); - $credits = $payment->credits->map(function ($credit) use ($payment) { return [ 'credit' => $credit->number, diff --git a/app/Transformers/AccountTransformer.php b/app/Transformers/AccountTransformer.php index 4e38ee26a385..30023ce9623b 100644 --- a/app/Transformers/AccountTransformer.php +++ b/app/Transformers/AccountTransformer.php @@ -92,7 +92,8 @@ class AccountTransformer extends EntityTransformer 'account_sms_verified' => (bool) $account->account_sms_verified, 'has_iap_plan' => (bool)$account->inapp_transaction_id, 'tax_api_enabled' => (bool) config('services.tax.zip_tax.key') ? true : false, - 'nordigen_enabled' => (bool) (config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key')) ? true : false + 'nordigen_enabled' => (bool) (config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key')) ? true : false, + 'upload_extensions' => (string) config('ninja.upload_extensions'), ]; } diff --git a/app/Transformers/UserTransformer.php b/app/Transformers/UserTransformer.php index c0274a3e81a7..78cbf8e718c1 100644 --- a/app/Transformers/UserTransformer.php +++ b/app/Transformers/UserTransformer.php @@ -109,7 +109,10 @@ class UserTransformer extends EntityTransformer $transformer = new CompanyUserTransformer($this->serializer); - $cu = $user->company_users()->whereCompanyId($user->company_id)->first(); + $cu = $user->company_users()->where('company_id',$user->company_id)->first(); + + if(!$cu) + return null; return $this->includeItem($cu, $transformer, CompanyUser::class); } diff --git a/app/Transformers/VendorTransformer.php b/app/Transformers/VendorTransformer.php index aa3a28669234..7bd715b13e80 100644 --- a/app/Transformers/VendorTransformer.php +++ b/app/Transformers/VendorTransformer.php @@ -105,6 +105,7 @@ class VendorTransformer extends EntityTransformer 'language_id' => (string) $vendor->language_id ?: '', 'classification' => (string) $vendor->classification ?: '', 'display_name' => (string) $vendor->present()->name(), + 'routing_id' => (string) $vendor->routing_id ?: '', ]; } } diff --git a/composer.json b/composer.json index 31a81cf7bc62..5a69c9480dcb 100644 --- a/composer.json +++ b/composer.json @@ -47,6 +47,7 @@ "doctrine/dbal": "^3.0", "eway/eway-rapid-php": "^1.3", "fakerphp/faker": "^1.14", + "getbrevo/brevo-php": "^1.0", "gocardless/gocardless-pro": "^4.12", "google/apiclient": "^2.7", "guzzlehttp/guzzle": "^7.2", @@ -54,6 +55,7 @@ "hashids/hashids": "^4.0", "hedii/laravel-gelf-logger": "^8", "horstoeko/zugferd": "^1", + "horstoeko/orderx": "^1", "imdhemy/laravel-purchases": "^1.7", "intervention/image": "^2.5", "invoiceninja/inspector": "^2.0", @@ -92,6 +94,7 @@ "sprain/swiss-qr-bill": "^4.3", "square/square": "30.0.0.*", "stripe/stripe-php": "^12", + "symfony/brevo-mailer": "6.4", "symfony/http-client": "^6.0", "symfony/mailgun-mailer": "^6.1", "symfony/postmark-mailer": "^6.1", @@ -110,7 +113,6 @@ "barryvdh/laravel-ide-helper": "^2.13", "beyondcode/laravel-query-detector": "^1.8", "brianium/paratest": "^7", - "fakerphp/faker": "^1.14", "filp/whoops": "^2.7", "friendsofphp/php-cs-fixer": "^3.14", "laracasts/cypress": "^3.0", diff --git a/composer.lock b/composer.lock index d7bf37057647..5ed66e3bed32 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,48 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5dc5b1a1f1f5070d5377b7f657f28e09", + "content-hash": "b5347cd9ca42d75b5c80691e6b64dae5", "packages": [ + { + "name": "adrienrn/php-mimetyper", + "version": "0.2.2", + "source": { + "type": "git", + "url": "https://github.com/adrienrn/php-mimetyper.git", + "reference": "702e00a604b4baed34d69730ce055e05c0f43932" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/adrienrn/php-mimetyper/zipball/702e00a604b4baed34d69730ce055e05c0f43932", + "reference": "702e00a604b4baed34d69730ce055e05c0f43932", + "shasum": "" + }, + "require": { + "dflydev/apache-mime-types": "^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "MimeTyper\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Hussard", + "email": "adrien.ricartnoblet@gmail.com" + } + ], + "description": "PHP mime type and extension mapping library: compatible with Symfony, powered by jshttp/mime-db", + "support": { + "issues": "https://github.com/adrienrn/php-mimetyper/issues", + "source": "https://github.com/adrienrn/php-mimetyper/tree/0.2.2" + }, + "time": "2018-09-27T09:45:05+00:00" + }, { "name": "afosto/yaac", "version": "v1.5.2", @@ -369,21 +409,22 @@ }, { "name": "amphp/parallel", - "version": "v2.2.6", + "version": "v2.2.8", "source": { "type": "git", "url": "https://github.com/amphp/parallel.git", - "reference": "5aeaad20297507cc754859236720501b54306eba" + "reference": "efd71b342b64c2e46d904e4eb057ed5ab20f8e2d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/parallel/zipball/5aeaad20297507cc754859236720501b54306eba", - "reference": "5aeaad20297507cc754859236720501b54306eba", + "url": "https://api.github.com/repos/amphp/parallel/zipball/efd71b342b64c2e46d904e4eb057ed5ab20f8e2d", + "reference": "efd71b342b64c2e46d904e4eb057ed5ab20f8e2d", "shasum": "" }, "require": { "amphp/amp": "^3", "amphp/byte-stream": "^2", + "amphp/cache": "^2", "amphp/parser": "^1", "amphp/pipeline": "^1", "amphp/process": "^2", @@ -440,7 +481,7 @@ ], "support": { "issues": "https://github.com/amphp/parallel/issues", - "source": "https://github.com/amphp/parallel/tree/v2.2.6" + "source": "https://github.com/amphp/parallel/tree/v2.2.8" }, "funding": [ { @@ -448,7 +489,7 @@ "type": "github" } ], - "time": "2024-01-07T18:12:13+00:00" + "time": "2024-03-19T16:09:34+00:00" }, { "name": "amphp/parser", @@ -514,16 +555,16 @@ }, { "name": "amphp/pipeline", - "version": "v1.1.0", + "version": "v1.2.0", "source": { "type": "git", "url": "https://github.com/amphp/pipeline.git", - "reference": "8a0ecc281bb0932d6b4a786453aff18c55756e63" + "reference": "f1c2ce35d27ae86ead018adb803eccca7421dd9b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/pipeline/zipball/8a0ecc281bb0932d6b4a786453aff18c55756e63", - "reference": "8a0ecc281bb0932d6b4a786453aff18c55756e63", + "url": "https://api.github.com/repos/amphp/pipeline/zipball/f1c2ce35d27ae86ead018adb803eccca7421dd9b", + "reference": "f1c2ce35d27ae86ead018adb803eccca7421dd9b", "shasum": "" }, "require": { @@ -569,7 +610,7 @@ ], "support": { "issues": "https://github.com/amphp/pipeline/issues", - "source": "https://github.com/amphp/pipeline/tree/v1.1.0" + "source": "https://github.com/amphp/pipeline/tree/v1.2.0" }, "funding": [ { @@ -577,7 +618,7 @@ "type": "github" } ], - "time": "2023-12-23T04:34:28+00:00" + "time": "2024-03-10T14:48:16+00:00" }, { "name": "amphp/process", @@ -707,16 +748,16 @@ }, { "name": "amphp/socket", - "version": "v2.2.4", + "version": "v2.3.0", "source": { "type": "git", "url": "https://github.com/amphp/socket.git", - "reference": "4223324c627cc26d44800630411e64856d3344bc" + "reference": "acc0a2f65ab498025ba5641f7cce499c4b1ed4b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/socket/zipball/4223324c627cc26d44800630411e64856d3344bc", - "reference": "4223324c627cc26d44800630411e64856d3344bc", + "url": "https://api.github.com/repos/amphp/socket/zipball/acc0a2f65ab498025ba5641f7cce499c4b1ed4b5", + "reference": "acc0a2f65ab498025ba5641f7cce499c4b1ed4b5", "shasum": "" }, "require": { @@ -779,7 +820,7 @@ ], "support": { "issues": "https://github.com/amphp/socket/issues", - "source": "https://github.com/amphp/socket/tree/v2.2.4" + "source": "https://github.com/amphp/socket/tree/v2.3.0" }, "funding": [ { @@ -787,20 +828,20 @@ "type": "github" } ], - "time": "2024-02-28T15:56:06+00:00" + "time": "2024-03-19T20:01:53+00:00" }, { "name": "amphp/sync", - "version": "v2.1.0", + "version": "v2.2.0", "source": { "type": "git", "url": "https://github.com/amphp/sync.git", - "reference": "50ddc7392cc8034b3e4798cef3cc90d3f4c0441c" + "reference": "375ef5b54a0d12c38e12728dde05a55e30f2fbec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/sync/zipball/50ddc7392cc8034b3e4798cef3cc90d3f4c0441c", - "reference": "50ddc7392cc8034b3e4798cef3cc90d3f4c0441c", + "url": "https://api.github.com/repos/amphp/sync/zipball/375ef5b54a0d12c38e12728dde05a55e30f2fbec", + "reference": "375ef5b54a0d12c38e12728dde05a55e30f2fbec", "shasum": "" }, "require": { @@ -814,7 +855,7 @@ "amphp/php-cs-fixer-config": "^2", "amphp/phpunit-util": "^3", "phpunit/phpunit": "^9", - "psalm/phar": "^5.4" + "psalm/phar": "5.23" }, "type": "library", "autoload": { @@ -854,7 +895,7 @@ ], "support": { "issues": "https://github.com/amphp/sync/issues", - "source": "https://github.com/amphp/sync/tree/v2.1.0" + "source": "https://github.com/amphp/sync/tree/v2.2.0" }, "funding": [ { @@ -862,7 +903,7 @@ "type": "github" } ], - "time": "2023-08-19T13:53:40+00:00" + "time": "2024-03-12T01:00:01+00:00" }, { "name": "amphp/windows-registry", @@ -1014,16 +1055,16 @@ }, { "name": "apimatic/jsonmapper", - "version": "3.1.2", + "version": "3.1.3", "source": { "type": "git", "url": "https://github.com/apimatic/jsonmapper.git", - "reference": "6673a946c21f2ceeec0cb60d17541c11a22bc79d" + "reference": "5fe6ee7ed1857d6fed669dde935c6c6c70b637d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/apimatic/jsonmapper/zipball/6673a946c21f2ceeec0cb60d17541c11a22bc79d", - "reference": "6673a946c21f2ceeec0cb60d17541c11a22bc79d", + "url": "https://api.github.com/repos/apimatic/jsonmapper/zipball/5fe6ee7ed1857d6fed669dde935c6c6c70b637d2", + "reference": "5fe6ee7ed1857d6fed669dde935c6c6c70b637d2", "shasum": "" }, "require": { @@ -1062,9 +1103,9 @@ "support": { "email": "mehdi.jaffery@apimatic.io", "issues": "https://github.com/apimatic/jsonmapper/issues", - "source": "https://github.com/apimatic/jsonmapper/tree/3.1.2" + "source": "https://github.com/apimatic/jsonmapper/tree/3.1.3" }, - "time": "2023-06-08T04:27:10+00:00" + "time": "2024-03-15T06:02:44+00:00" }, { "name": "apimatic/unirest-php", @@ -1343,16 +1384,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.300.13", + "version": "3.301.3", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "b1eb7307d30ebcfa4e156971f658c2d177434db3" + "reference": "6b21e34d24a73ea66492869be90443069034fdb3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/b1eb7307d30ebcfa4e156971f658c2d177434db3", - "reference": "b1eb7307d30ebcfa4e156971f658c2d177434db3", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/6b21e34d24a73ea66492869be90443069034fdb3", + "reference": "6b21e34d24a73ea66492869be90443069034fdb3", "shasum": "" }, "require": { @@ -1432,9 +1473,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.300.13" + "source": "https://github.com/aws/aws-sdk-php/tree/3.301.3" }, - "time": "2024-03-07T19:14:04+00:00" + "time": "2024-03-19T18:05:04+00:00" }, { "name": "bacon/bacon-qr-code", @@ -1851,28 +1892,28 @@ }, { "name": "composer/ca-bundle", - "version": "1.4.1", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "3ce240142f6d59b808dd65c1f52f7a1c252e6cfd" + "reference": "0c5ccfcfea312b5c5a190a21ac5cef93f74baf99" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/3ce240142f6d59b808dd65c1f52f7a1c252e6cfd", - "reference": "3ce240142f6d59b808dd65c1f52f7a1c252e6cfd", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/0c5ccfcfea312b5c5a190a21ac5cef93f74baf99", + "reference": "0c5ccfcfea312b5c5a190a21ac5cef93f74baf99", "shasum": "" }, "require": { "ext-openssl": "*", "ext-pcre": "*", - "php": "^5.3.2 || ^7.0 || ^8.0" + "php": "^7.2 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^0.12.55", + "phpstan/phpstan": "^1.10", "psr/log": "^1.0", "symfony/phpunit-bridge": "^4.2 || ^5", - "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0" + "symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0" }, "type": "library", "extra": { @@ -1907,7 +1948,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.4.1" + "source": "https://github.com/composer/ca-bundle/tree/1.5.0" }, "funding": [ { @@ -1923,7 +1964,7 @@ "type": "tidelift" } ], - "time": "2024-02-23T10:16:52+00:00" + "time": "2024-03-15T14:00:32+00:00" }, { "name": "dasprid/enum", @@ -2019,6 +2060,65 @@ }, "time": "2022-09-20T18:15:38+00:00" }, + { + "name": "dflydev/apache-mime-types", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-apache-mime-types.git", + "reference": "f30a57e59b7476e4c5270b6a0727d79c9c0eb861" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-apache-mime-types/zipball/f30a57e59b7476e4c5270b6a0727d79c9c0eb861", + "reference": "f30a57e59b7476e4c5270b6a0727d79c9c0eb861", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "require-dev": { + "twig/twig": "1.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-0": { + "Dflydev\\ApacheMimeTypes": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + } + ], + "description": "Apache MIME Types", + "keywords": [ + "apache", + "mime", + "mimetypes" + ], + "support": { + "issues": "https://github.com/dflydev/dflydev-apache-mime-types/issues", + "source": "https://github.com/dflydev/dflydev-apache-mime-types/tree/v1.0.1" + }, + "time": "2013-05-14T02:02:01+00:00" + }, { "name": "dflydev/dot-access-data", "version": "v3.0.2", @@ -2806,16 +2906,16 @@ }, { "name": "endroid/qr-code", - "version": "5.0.6", + "version": "5.0.7", "source": { "type": "git", "url": "https://github.com/endroid/qr-code.git", - "reference": "3a9cc61d2d34df93f6edc2140e7880966ee7860f" + "reference": "0cc00f0626b73bc71a1ea17af01387d0ac75e046" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/endroid/qr-code/zipball/3a9cc61d2d34df93f6edc2140e7880966ee7860f", - "reference": "3a9cc61d2d34df93f6edc2140e7880966ee7860f", + "url": "https://api.github.com/repos/endroid/qr-code/zipball/0cc00f0626b73bc71a1ea17af01387d0ac75e046", + "reference": "0cc00f0626b73bc71a1ea17af01387d0ac75e046", "shasum": "" }, "require": { @@ -2869,7 +2969,7 @@ ], "support": { "issues": "https://github.com/endroid/qr-code/issues", - "source": "https://github.com/endroid/qr-code/tree/5.0.6" + "source": "https://github.com/endroid/qr-code/tree/5.0.7" }, "funding": [ { @@ -2877,7 +2977,7 @@ "type": "github" } ], - "time": "2024-03-06T22:34:02+00:00" + "time": "2024-03-08T11:24:40+00:00" }, { "name": "eway/eway-rapid-php", @@ -3193,6 +3293,69 @@ ], "time": "2023-10-12T05:21:21+00:00" }, + { + "name": "getbrevo/brevo-php", + "version": "v1.0.2", + "source": { + "type": "git", + "url": "https://github.com/getbrevo/brevo-php.git", + "reference": "6c3286e62327277fd8445cddb057d44e850722c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/getbrevo/brevo-php/zipball/6c3286e62327277fd8445cddb057d44e850722c0", + "reference": "6c3286e62327277fd8445cddb057d44e850722c0", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "guzzlehttp/guzzle": "^7.4.0", + "php": ">=5.6" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~1.12", + "phpunit/phpunit": "^4.8", + "squizlabs/php_codesniffer": "~2.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x.x-dev" + } + }, + "autoload": { + "psr-4": { + "Brevo\\Client\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brevo Developers", + "email": "contact@brevo.com", + "homepage": "https://www.brevo.com/" + } + ], + "description": "Official Brevo provided RESTFul API V3 php library", + "homepage": "https://github.com/getbrevo/brevo-php", + "keywords": [ + "api", + "brevo", + "php", + "sdk", + "swagger" + ], + "support": { + "issues": "https://github.com/getbrevo/brevo-php/issues", + "source": "https://github.com/getbrevo/brevo-php/tree/v1.0.2" + }, + "time": "2023-07-14T10:00:50+00:00" + }, { "name": "gocardless/gocardless-pro", "version": "4.28.0", @@ -3379,16 +3542,16 @@ }, { "name": "google/apiclient-services", - "version": "v0.338.0", + "version": "v0.340.0", "source": { "type": "git", "url": "https://github.com/googleapis/google-api-php-client-services.git", - "reference": "52aeb042c8d30ac0f98f4051dd4bc523708b1306" + "reference": "c89999ea477da2b0803b2b4f14c9e7fc23b6344a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/52aeb042c8d30ac0f98f4051dd4bc523708b1306", - "reference": "52aeb042c8d30ac0f98f4051dd4bc523708b1306", + "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/c89999ea477da2b0803b2b4f14c9e7fc23b6344a", + "reference": "c89999ea477da2b0803b2b4f14c9e7fc23b6344a", "shasum": "" }, "require": { @@ -3417,9 +3580,9 @@ ], "support": { "issues": "https://github.com/googleapis/google-api-php-client-services/issues", - "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.338.0" + "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.340.0" }, - "time": "2024-03-03T00:56:15+00:00" + "time": "2024-03-17T00:56:17+00:00" }, { "name": "google/auth", @@ -4239,6 +4402,74 @@ }, "time": "2023-09-22T20:17:48+00:00" }, + { + "name": "horstoeko/orderx", + "version": "v1.0.19", + "source": { + "type": "git", + "url": "https://github.com/horstoeko/orderx.git", + "reference": "7b4ed00ca98df5a88c916733d31728a16a3845b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/horstoeko/orderx/zipball/7b4ed00ca98df5a88c916733d31728a16a3845b4", + "reference": "7b4ed00ca98df5a88c916733d31728a16a3845b4", + "shasum": "" + }, + "require": { + "adrienrn/php-mimetyper": "^0.2", + "ext-simplexml": "*", + "goetas-webservices/xsd2php-runtime": "^0.2.13", + "horstoeko/stringmanagement": "^1", + "jms/serializer": "^3", + "php": "^7.3|^7.4|^8", + "setasign/fpdf": "^1", + "setasign/fpdi": "^2", + "smalot/pdfparser": "^0", + "symfony/validator": "^5|^6", + "symfony/yaml": "^5|^6" + }, + "require-dev": { + "goetas-webservices/xsd2php": "^0", + "pdepend/pdepend": "^2", + "phploc/phploc": "^7", + "phpmd/phpmd": "^2", + "phpstan/phpstan": "^1.8", + "phpunit/phpunit": "^9", + "sebastian/phpcpd": "^6", + "squizlabs/php_codesniffer": "^3" + }, + "type": "package", + "autoload": { + "psr-4": { + "horstoeko\\orderx\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Erling", + "email": "daniel@erling.com.de", + "role": "lead" + } + ], + "description": "A library for creating and reading Order-X document", + "homepage": "https://github.com/horstoeko/orderx", + "keywords": [ + "electronic", + "order", + "order-x", + "orderx" + ], + "support": { + "issues": "https://github.com/horstoeko/orderx/issues", + "source": "https://github.com/horstoeko/orderx/tree/v1.0.19" + }, + "time": "2024-03-20T04:07:11+00:00" + }, { "name": "horstoeko/stringmanagement", "version": "v1.0.11", @@ -4295,16 +4526,16 @@ }, { "name": "horstoeko/zugferd", - "version": "v1.0.34", + "version": "v1.0.36", "source": { "type": "git", "url": "https://github.com/horstoeko/zugferd.git", - "reference": "963b8ab88374e36c056f8ebceb834075be75f0a2" + "reference": "0d15c305328c137365648fe1c34a584d877127fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/horstoeko/zugferd/zipball/963b8ab88374e36c056f8ebceb834075be75f0a2", - "reference": "963b8ab88374e36c056f8ebceb834075be75f0a2", + "url": "https://api.github.com/repos/horstoeko/zugferd/zipball/0d15c305328c137365648fe1c34a584d877127fa", + "reference": "0d15c305328c137365648fe1c34a584d877127fa", "shasum": "" }, "require": { @@ -4362,9 +4593,9 @@ ], "support": { "issues": "https://github.com/horstoeko/zugferd/issues", - "source": "https://github.com/horstoeko/zugferd/tree/v1.0.34" + "source": "https://github.com/horstoeko/zugferd/tree/v1.0.36" }, - "time": "2024-01-27T09:14:13+00:00" + "time": "2024-03-11T04:34:59+00:00" }, { "name": "http-interop/http-factory-guzzle", @@ -4523,23 +4754,23 @@ }, { "name": "imdhemy/google-play-billing", - "version": "1.5.1", + "version": "1.5.2", "source": { "type": "git", "url": "https://github.com/imdhemy/google-play-billing.git", - "reference": "bb94f3b6ddb021605815e528f31b8c930c41677c" + "reference": "7f2b032354568fa50858e0f6dd25592d975b3979" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/imdhemy/google-play-billing/zipball/bb94f3b6ddb021605815e528f31b8c930c41677c", - "reference": "bb94f3b6ddb021605815e528f31b8c930c41677c", + "url": "https://api.github.com/repos/imdhemy/google-play-billing/zipball/7f2b032354568fa50858e0f6dd25592d975b3979", + "reference": "7f2b032354568fa50858e0f6dd25592d975b3979", "shasum": "" }, "require": { "ext-json": "*", "google/auth": "^1.26", "guzzlehttp/guzzle": "^7.5.1", - "nesbot/carbon": "^2.66", + "nesbot/carbon": "^2.66|^3.0", "php": ">=8.0" }, "require-dev": { @@ -4568,9 +4799,9 @@ "description": "Google Play Billing", "support": { "issues": "https://github.com/imdhemy/google-play-billing/issues", - "source": "https://github.com/imdhemy/google-play-billing/tree/1.5.1" + "source": "https://github.com/imdhemy/google-play-billing/tree/1.5.2" }, - "time": "2023-12-15T10:25:05+00:00" + "time": "2024-03-19T17:56:34+00:00" }, { "name": "imdhemy/laravel-purchases", @@ -4861,16 +5092,16 @@ }, { "name": "jean85/pretty-package-versions", - "version": "2.0.5", + "version": "2.0.6", "source": { "type": "git", "url": "https://github.com/Jean85/pretty-package-versions.git", - "reference": "ae547e455a3d8babd07b96966b17d7fd21d9c6af" + "reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/ae547e455a3d8babd07b96966b17d7fd21d9c6af", - "reference": "ae547e455a3d8babd07b96966b17d7fd21d9c6af", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/f9fdd29ad8e6d024f52678b570e5593759b550b4", + "reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4", "shasum": "" }, "require": { @@ -4878,9 +5109,9 @@ "php": "^7.1|^8.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.17", + "friendsofphp/php-cs-fixer": "^3.2", "jean85/composer-provided-replaced-stub-package": "^1.0", - "phpstan/phpstan": "^0.12.66", + "phpstan/phpstan": "^1.4", "phpunit/phpunit": "^7.5|^8.5|^9.4", "vimeo/psalm": "^4.3" }, @@ -4914,9 +5145,9 @@ ], "support": { "issues": "https://github.com/Jean85/pretty-package-versions/issues", - "source": "https://github.com/Jean85/pretty-package-versions/tree/2.0.5" + "source": "https://github.com/Jean85/pretty-package-versions/tree/2.0.6" }, - "time": "2021-10-08T21:21:46+00:00" + "time": "2024-03-08T09:58:59+00:00" }, { "name": "jms/metadata", @@ -5295,16 +5526,16 @@ }, { "name": "laravel/framework", - "version": "v10.47.0", + "version": "v10.48.3", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "fce29b8de62733cdecbe12e3bae801f83fff2ea4" + "reference": "5791c052b41c6b593556adc687076bfbdd13c501" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/fce29b8de62733cdecbe12e3bae801f83fff2ea4", - "reference": "fce29b8de62733cdecbe12e3bae801f83fff2ea4", + "url": "https://api.github.com/repos/laravel/framework/zipball/5791c052b41c6b593556adc687076bfbdd13c501", + "reference": "5791c052b41c6b593556adc687076bfbdd13c501", "shasum": "" }, "require": { @@ -5352,6 +5583,7 @@ "conflict": { "carbonphp/carbon-doctrine-types": ">=3.0", "doctrine/dbal": ">=4.0", + "mockery/mockery": "1.6.8", "phpunit/phpunit": ">=11.0.0", "tightenco/collect": "<5.5.33" }, @@ -5497,7 +5729,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2024-03-05T15:18:36+00:00" + "time": "2024-03-15T10:17:07+00:00" }, { "name": "laravel/prompts", @@ -6292,16 +6524,16 @@ }, { "name": "league/flysystem", - "version": "3.24.0", + "version": "3.25.1", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "b25a361508c407563b34fac6f64a8a17a8819675" + "reference": "abbd664eb4381102c559d358420989f835208f18" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/b25a361508c407563b34fac6f64a8a17a8819675", - "reference": "b25a361508c407563b34fac6f64a8a17a8819675", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/abbd664eb4381102c559d358420989f835208f18", + "reference": "abbd664eb4381102c559d358420989f835208f18", "shasum": "" }, "require": { @@ -6329,7 +6561,7 @@ "friendsofphp/php-cs-fixer": "^3.5", "google/cloud-storage": "^1.23", "microsoft/azure-storage-blob": "^1.1", - "phpseclib/phpseclib": "^3.0.34", + "phpseclib/phpseclib": "^3.0.36", "phpstan/phpstan": "^1.10", "phpunit/phpunit": "^9.5.11|^10.0", "sabre/dav": "^4.6.0" @@ -6366,7 +6598,7 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.24.0" + "source": "https://github.com/thephpleague/flysystem/tree/3.25.1" }, "funding": [ { @@ -6378,20 +6610,20 @@ "type": "github" } ], - "time": "2024-02-04T12:10:17+00:00" + "time": "2024-03-16T12:53:19+00:00" }, { "name": "league/flysystem-aws-s3-v3", - "version": "3.24.0", + "version": "3.25.1", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", - "reference": "809474e37b7fb1d1f8bcc0f8a98bc1cae99aa513" + "reference": "6a5be0e6d6a93574e80805c9cc108a4b63c824d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/809474e37b7fb1d1f8bcc0f8a98bc1cae99aa513", - "reference": "809474e37b7fb1d1f8bcc0f8a98bc1cae99aa513", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/6a5be0e6d6a93574e80805c9cc108a4b63c824d8", + "reference": "6a5be0e6d6a93574e80805c9cc108a4b63c824d8", "shasum": "" }, "require": { @@ -6431,7 +6663,7 @@ "storage" ], "support": { - "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.24.0" + "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.25.1" }, "funding": [ { @@ -6443,20 +6675,20 @@ "type": "github" } ], - "time": "2024-01-26T18:43:21+00:00" + "time": "2024-03-15T19:58:44+00:00" }, { "name": "league/flysystem-local", - "version": "3.23.1", + "version": "3.25.1", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-local.git", - "reference": "b884d2bf9b53bb4804a56d2df4902bb51e253f00" + "reference": "61a6a90d6e999e4ddd9ce5adb356de0939060b92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/b884d2bf9b53bb4804a56d2df4902bb51e253f00", - "reference": "b884d2bf9b53bb4804a56d2df4902bb51e253f00", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/61a6a90d6e999e4ddd9ce5adb356de0939060b92", + "reference": "61a6a90d6e999e4ddd9ce5adb356de0939060b92", "shasum": "" }, "require": { @@ -6490,8 +6722,7 @@ "local" ], "support": { - "issues": "https://github.com/thephpleague/flysystem-local/issues", - "source": "https://github.com/thephpleague/flysystem-local/tree/3.23.1" + "source": "https://github.com/thephpleague/flysystem-local/tree/3.25.1" }, "funding": [ { @@ -6503,7 +6734,7 @@ "type": "github" } ], - "time": "2024-01-26T18:25:23+00:00" + "time": "2024-03-15T19:58:44+00:00" }, { "name": "league/fractal", @@ -6946,16 +7177,16 @@ }, { "name": "livewire/livewire", - "version": "v3.4.8", + "version": "v3.4.9", "source": { "type": "git", "url": "https://github.com/livewire/livewire.git", - "reference": "0335b8f022ac535fc3cf29fd8a8fcb275353a470" + "reference": "c65b3f0798ab2c9338213ede3588c3cdf4e6fcc0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/livewire/livewire/zipball/0335b8f022ac535fc3cf29fd8a8fcb275353a470", - "reference": "0335b8f022ac535fc3cf29fd8a8fcb275353a470", + "url": "https://api.github.com/repos/livewire/livewire/zipball/c65b3f0798ab2c9338213ede3588c3cdf4e6fcc0", + "reference": "c65b3f0798ab2c9338213ede3588c3cdf4e6fcc0", "shasum": "" }, "require": { @@ -7009,7 +7240,7 @@ "description": "A front-end framework for Laravel.", "support": { "issues": "https://github.com/livewire/livewire/issues", - "source": "https://github.com/livewire/livewire/tree/v3.4.8" + "source": "https://github.com/livewire/livewire/tree/v3.4.9" }, "funding": [ { @@ -7017,7 +7248,7 @@ "type": "github" } ], - "time": "2024-03-08T13:01:50+00:00" + "time": "2024-03-14T14:03:32+00:00" }, { "name": "maennchen/zipstream-php", @@ -7261,16 +7492,16 @@ }, { "name": "mollie/mollie-api-php", - "version": "v2.65.0", + "version": "v2.66.0", "source": { "type": "git", "url": "https://github.com/mollie/mollie-api-php.git", - "reference": "3920816c311ec785f47f160204296d1b7f918da5" + "reference": "d7d09ac62a565e818bf49d04acb2f0432da758a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mollie/mollie-api-php/zipball/3920816c311ec785f47f160204296d1b7f918da5", - "reference": "3920816c311ec785f47f160204296d1b7f918da5", + "url": "https://api.github.com/repos/mollie/mollie-api-php/zipball/d7d09ac62a565e818bf49d04acb2f0432da758a9", + "reference": "d7d09ac62a565e818bf49d04acb2f0432da758a9", "shasum": "" }, "require": { @@ -7347,9 +7578,9 @@ ], "support": { "issues": "https://github.com/mollie/mollie-api-php/issues", - "source": "https://github.com/mollie/mollie-api-php/tree/v2.65.0" + "source": "https://github.com/mollie/mollie-api-php/tree/v2.66.0" }, - "time": "2024-01-23T12:39:48+00:00" + "time": "2024-03-19T13:33:42+00:00" }, { "name": "moneyphp/money", @@ -8298,16 +8529,16 @@ }, { "name": "omnipay/common", - "version": "v3.2.1", + "version": "v3.3.0", "source": { "type": "git", "url": "https://github.com/thephpleague/omnipay-common.git", - "reference": "80545e9f4faab0efad36cc5f1e11a184dda22baf" + "reference": "2eca3823e9069e2c36b6007a090577d5584f9518" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/omnipay-common/zipball/80545e9f4faab0efad36cc5f1e11a184dda22baf", - "reference": "80545e9f4faab0efad36cc5f1e11a184dda22baf", + "url": "https://api.github.com/repos/thephpleague/omnipay-common/zipball/2eca3823e9069e2c36b6007a090577d5584f9518", + "reference": "2eca3823e9069e2c36b6007a090577d5584f9518", "shasum": "" }, "require": { @@ -8317,13 +8548,14 @@ "php-http/discovery": "^1.14", "php-http/message": "^1.5", "php-http/message-factory": "^1.1", - "symfony/http-foundation": "^2.1|^3|^4|^5|^6" + "symfony/http-foundation": "^2.1|^3|^4|^5|^6|^7" }, "require-dev": { + "http-interop/http-factory-guzzle": "^1.1", "omnipay/tests": "^4.1", "php-http/guzzle7-adapter": "^1", - "php-http/mock-client": "^1", - "squizlabs/php_codesniffer": "^3.5" + "php-http/mock-client": "^1.6", + "squizlabs/php_codesniffer": "^3.8.1" }, "suggest": { "league/omnipay": "The default Omnipay package provides a default HTTP Adapter." @@ -8379,7 +8611,7 @@ ], "support": { "issues": "https://github.com/thephpleague/omnipay-common/issues", - "source": "https://github.com/thephpleague/omnipay-common/tree/v3.2.1" + "source": "https://github.com/thephpleague/omnipay-common/tree/v3.3.0" }, "funding": [ { @@ -8387,7 +8619,7 @@ "type": "github" } ], - "time": "2023-05-30T12:44:03+00:00" + "time": "2024-03-08T11:56:40+00:00" }, { "name": "omnipay/paypal", @@ -9102,16 +9334,16 @@ }, { "name": "php-http/promise", - "version": "1.3.0", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/php-http/promise.git", - "reference": "2916a606d3b390f4e9e8e2b8dd68581508be0f07" + "reference": "fc85b1fba37c169a69a07ef0d5a8075770cc1f83" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-http/promise/zipball/2916a606d3b390f4e9e8e2b8dd68581508be0f07", - "reference": "2916a606d3b390f4e9e8e2b8dd68581508be0f07", + "url": "https://api.github.com/repos/php-http/promise/zipball/fc85b1fba37c169a69a07ef0d5a8075770cc1f83", + "reference": "fc85b1fba37c169a69a07ef0d5a8075770cc1f83", "shasum": "" }, "require": { @@ -9148,9 +9380,9 @@ ], "support": { "issues": "https://github.com/php-http/promise/issues", - "source": "https://github.com/php-http/promise/tree/1.3.0" + "source": "https://github.com/php-http/promise/tree/1.3.1" }, - "time": "2024-01-04T18:49:48+00:00" + "time": "2024-03-15T13:55:21+00:00" }, { "name": "php-jsonpointer/php-jsonpointer", @@ -10232,16 +10464,16 @@ }, { "name": "psy/psysh", - "version": "v0.12.0", + "version": "v0.12.2", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "750bf031a48fd07c673dbe3f11f72362ea306d0d" + "reference": "9185c66c2165bbf4d71de78a69dccf4974f9538d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/750bf031a48fd07c673dbe3f11f72362ea306d0d", - "reference": "750bf031a48fd07c673dbe3f11f72362ea306d0d", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/9185c66c2165bbf4d71de78a69dccf4974f9538d", + "reference": "9185c66c2165bbf4d71de78a69dccf4974f9538d", "shasum": "" }, "require": { @@ -10305,9 +10537,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.0" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.2" }, - "time": "2023-12-20T15:28:09+00:00" + "time": "2024-03-17T01:53:00+00:00" }, { "name": "pusher/pusher-php-server", @@ -11380,24 +11612,27 @@ }, { "name": "smalot/pdfparser", - "version": "v2.9.0", + "version": "v0.19.0", "source": { "type": "git", "url": "https://github.com/smalot/pdfparser.git", - "reference": "6b53144fcb24af77093d4150dd7d0dd571f25761" + "reference": "1895c17417aefa4508e35836c46da61988d61f26" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/smalot/pdfparser/zipball/6b53144fcb24af77093d4150dd7d0dd571f25761", - "reference": "6b53144fcb24af77093d4150dd7d0dd571f25761", + "url": "https://api.github.com/repos/smalot/pdfparser/zipball/1895c17417aefa4508e35836c46da61988d61f26", + "reference": "1895c17417aefa4508e35836c46da61988d61f26", "shasum": "" }, "require": { - "ext-iconv": "*", "ext-zlib": "*", - "php": ">=7.1", + "php": ">=5.6", "symfony/polyfill-mbstring": "^1.18" }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.16", + "symfony/phpunit-bridge": "^5.2" + }, "type": "library", "autoload": { "psr-0": { @@ -11425,9 +11660,9 @@ ], "support": { "issues": "https://github.com/smalot/pdfparser/issues", - "source": "https://github.com/smalot/pdfparser/tree/v2.9.0" + "source": "https://github.com/smalot/pdfparser/tree/v0.19.0" }, - "time": "2024-03-01T09:51:10+00:00" + "time": "2021-04-13T08:27:56+00:00" }, { "name": "socialiteproviders/apple", @@ -11712,16 +11947,16 @@ }, { "name": "spatie/laravel-package-tools", - "version": "1.16.3", + "version": "1.16.4", "source": { "type": "git", "url": "https://github.com/spatie/laravel-package-tools.git", - "reference": "59db18c2e20d49a0b6d447bb1c654f6c123beb9e" + "reference": "ddf678e78d7f8b17e5cdd99c0c3413a4a6592e53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/59db18c2e20d49a0b6d447bb1c654f6c123beb9e", - "reference": "59db18c2e20d49a0b6d447bb1c654f6c123beb9e", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/ddf678e78d7f8b17e5cdd99c0c3413a4a6592e53", + "reference": "ddf678e78d7f8b17e5cdd99c0c3413a4a6592e53", "shasum": "" }, "require": { @@ -11760,7 +11995,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-package-tools/issues", - "source": "https://github.com/spatie/laravel-package-tools/tree/1.16.3" + "source": "https://github.com/spatie/laravel-package-tools/tree/1.16.4" }, "funding": [ { @@ -11768,20 +12003,20 @@ "type": "github" } ], - "time": "2024-03-07T07:35:57+00:00" + "time": "2024-03-20T07:29:11+00:00" }, { "name": "spatie/php-structure-discoverer", - "version": "2.1.0", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/spatie/php-structure-discoverer.git", - "reference": "f5b3c935dda89d6c382b27e3caf348fa80bcfa88" + "reference": "24f5221641560ec0f7dce23dd814e7d555b0098b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/php-structure-discoverer/zipball/f5b3c935dda89d6c382b27e3caf348fa80bcfa88", - "reference": "f5b3c935dda89d6c382b27e3caf348fa80bcfa88", + "url": "https://api.github.com/repos/spatie/php-structure-discoverer/zipball/24f5221641560ec0f7dce23dd814e7d555b0098b", + "reference": "24f5221641560ec0f7dce23dd814e7d555b0098b", "shasum": "" }, "require": { @@ -11840,7 +12075,7 @@ ], "support": { "issues": "https://github.com/spatie/php-structure-discoverer/issues", - "source": "https://github.com/spatie/php-structure-discoverer/tree/2.1.0" + "source": "https://github.com/spatie/php-structure-discoverer/tree/2.1.1" }, "funding": [ { @@ -11848,7 +12083,7 @@ "type": "github" } ], - "time": "2024-02-16T12:42:24+00:00" + "time": "2024-03-13T16:08:30+00:00" }, { "name": "sprain/swiss-qr-bill", @@ -12034,6 +12269,75 @@ }, "time": "2023-10-16T18:04:12+00:00" }, + { + "name": "symfony/brevo-mailer", + "version": "v6.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/brevo-mailer.git", + "reference": "83db87e0f44653cd40aeef54a2f57ab6bfccadfe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/brevo-mailer/zipball/83db87e0f44653cd40aeef54a2f57ab6bfccadfe", + "reference": "83db87e0f44653cd40aeef54a2f57ab6bfccadfe", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/mailer": "^5.4.21|^6.2.7|^7.0" + }, + "conflict": { + "symfony/mime": "<6.2" + }, + "require-dev": { + "symfony/http-client": "^6.3|^7.0", + "symfony/webhook": "^6.3|^7.0" + }, + "type": "symfony-mailer-bridge", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mailer\\Bridge\\Brevo\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Pierre Tanguy", + "homepage": "https://github.com/petanguy" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Brevo Mailer Bridge", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/brevo-mailer/tree/v7.0.0-RC1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-11-06T17:20:05+00:00" + }, { "name": "symfony/console", "version": "v6.4.4", @@ -15383,7 +15687,7 @@ "version": "6.44.4", "source": { "type": "git", - "url": "git@github.com:twilio/twilio-php.git", + "url": "https://github.com/twilio/twilio-php.git", "reference": "08aad5f377e2245b9cd7508e7762d95e7392fa4d" }, "dist": { @@ -15425,6 +15729,10 @@ "sms", "twilio" ], + "support": { + "issues": "https://github.com/twilio/twilio-php/issues", + "source": "https://github.com/twilio/twilio-php/tree/6.44.4" + }, "time": "2023-02-22T19:59:53+00:00" }, { @@ -15803,23 +16111,23 @@ "packages-dev": [ { "name": "barryvdh/laravel-debugbar", - "version": "v3.10.6", + "version": "v3.12.2", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "1fcb37307ebb32207dce16fa160a92b14d8b671f" + "reference": "43555503052443964ce2c1c1f3b0378e58219eb8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/1fcb37307ebb32207dce16fa160a92b14d8b671f", - "reference": "1fcb37307ebb32207dce16fa160a92b14d8b671f", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/43555503052443964ce2c1c1f3b0378e58219eb8", + "reference": "43555503052443964ce2c1c1f3b0378e58219eb8", "shasum": "" }, "require": { "illuminate/routing": "^9|^10|^11", "illuminate/session": "^9|^10|^11", "illuminate/support": "^9|^10|^11", - "maximebf/debugbar": "~1.20.1", + "maximebf/debugbar": "~1.21.0", "php": "^8.0", "symfony/finder": "^6|^7" }, @@ -15871,7 +16179,7 @@ ], "support": { "issues": "https://github.com/barryvdh/laravel-debugbar/issues", - "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.10.6" + "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.12.2" }, "funding": [ { @@ -15883,7 +16191,7 @@ "type": "github" } ], - "time": "2024-03-01T14:41:13+00:00" + "time": "2024-03-13T09:50:34+00:00" }, { "name": "barryvdh/laravel-ide-helper", @@ -16188,16 +16496,16 @@ }, { "name": "composer/class-map-generator", - "version": "1.1.0", + "version": "1.1.1", "source": { "type": "git", "url": "https://github.com/composer/class-map-generator.git", - "reference": "953cc4ea32e0c31f2185549c7d216d7921f03da9" + "reference": "8286a62d243312ed99b3eee20d5005c961adb311" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/class-map-generator/zipball/953cc4ea32e0c31f2185549c7d216d7921f03da9", - "reference": "953cc4ea32e0c31f2185549c7d216d7921f03da9", + "url": "https://api.github.com/repos/composer/class-map-generator/zipball/8286a62d243312ed99b3eee20d5005c961adb311", + "reference": "8286a62d243312ed99b3eee20d5005c961adb311", "shasum": "" }, "require": { @@ -16241,7 +16549,7 @@ ], "support": { "issues": "https://github.com/composer/class-map-generator/issues", - "source": "https://github.com/composer/class-map-generator/tree/1.1.0" + "source": "https://github.com/composer/class-map-generator/tree/1.1.1" }, "funding": [ { @@ -16257,20 +16565,20 @@ "type": "tidelift" } ], - "time": "2023-06-30T13:58:57+00:00" + "time": "2024-03-15T12:53:41+00:00" }, { "name": "composer/pcre", - "version": "3.1.2", + "version": "3.1.3", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", - "reference": "4775f35b2d70865807c89d32c8e7385b86eb0ace" + "reference": "5b16e25a5355f1f3afdfc2f954a0a80aec4826a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/4775f35b2d70865807c89d32c8e7385b86eb0ace", - "reference": "4775f35b2d70865807c89d32c8e7385b86eb0ace", + "url": "https://api.github.com/repos/composer/pcre/zipball/5b16e25a5355f1f3afdfc2f954a0a80aec4826a8", + "reference": "5b16e25a5355f1f3afdfc2f954a0a80aec4826a8", "shasum": "" }, "require": { @@ -16312,7 +16620,7 @@ ], "support": { "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.1.2" + "source": "https://github.com/composer/pcre/tree/3.1.3" }, "funding": [ { @@ -16328,7 +16636,7 @@ "type": "tidelift" } ], - "time": "2024-03-07T15:38:35+00:00" + "time": "2024-03-19T10:26:25+00:00" }, { "name": "composer/semver", @@ -16611,16 +16919,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.51.0", + "version": "v3.52.1", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "127fa74f010da99053e3f5b62672615b72dd6efd" + "reference": "6e77207f0d851862ceeb6da63e6e22c01b1587bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/127fa74f010da99053e3f5b62672615b72dd6efd", - "reference": "127fa74f010da99053e3f5b62672615b72dd6efd", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/6e77207f0d851862ceeb6da63e6e22c01b1587bc", + "reference": "6e77207f0d851862ceeb6da63e6e22c01b1587bc", "shasum": "" }, "require": { @@ -16691,7 +16999,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.51.0" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.52.1" }, "funding": [ { @@ -16699,7 +17007,7 @@ "type": "github" } ], - "time": "2024-02-28T19:50:06+00:00" + "time": "2024-03-19T21:02:43+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -16754,25 +17062,25 @@ }, { "name": "laracasts/cypress", - "version": "3.0.1", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/laracasts/cypress.git", - "reference": "dd4e61188d4edaf65ffa18851a5df38d0fa0619a" + "reference": "449f9d69da75091c77327093e5727a5c739a4cf8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laracasts/cypress/zipball/dd4e61188d4edaf65ffa18851a5df38d0fa0619a", - "reference": "dd4e61188d4edaf65ffa18851a5df38d0fa0619a", + "url": "https://api.github.com/repos/laracasts/cypress/zipball/449f9d69da75091c77327093e5727a5c739a4cf8", + "reference": "449f9d69da75091c77327093e5727a5c739a4cf8", "shasum": "" }, "require": { - "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0", + "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", "php": "^8.0" }, "require-dev": { - "orchestra/testbench": "^6.0|^7.0|^8.0", - "phpunit/phpunit": "^8.0|^9.5.10", + "orchestra/testbench": "^6.0|^7.0|^8.0|^9.0", + "phpunit/phpunit": "^8.0|^9.5.10|^10.5", "spatie/laravel-ray": "^1.29" }, "type": "library", @@ -16807,9 +17115,9 @@ ], "support": { "issues": "https://github.com/laracasts/cypress/issues", - "source": "https://github.com/laracasts/cypress/tree/3.0.1" + "source": "https://github.com/laracasts/cypress/tree/3.0.2" }, - "time": "2023-02-16T20:00:16+00:00" + "time": "2024-03-19T14:07:37+00:00" }, { "name": "larastan/larastan", @@ -16915,16 +17223,16 @@ }, { "name": "maximebf/debugbar", - "version": "v1.20.2", + "version": "v1.21.3", "source": { "type": "git", "url": "https://github.com/maximebf/php-debugbar.git", - "reference": "484625c23a4fa4f303617f29fcacd42951c9c01d" + "reference": "0b407703b08ea0cf6ebc61e267cc96ff7000911b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/484625c23a4fa4f303617f29fcacd42951c9c01d", - "reference": "484625c23a4fa4f303617f29fcacd42951c9c01d", + "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/0b407703b08ea0cf6ebc61e267cc96ff7000911b", + "reference": "0b407703b08ea0cf6ebc61e267cc96ff7000911b", "shasum": "" }, "require": { @@ -16944,7 +17252,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.20-dev" + "dev-master": "1.21-dev" } }, "autoload": { @@ -16975,22 +17283,22 @@ ], "support": { "issues": "https://github.com/maximebf/php-debugbar/issues", - "source": "https://github.com/maximebf/php-debugbar/tree/v1.20.2" + "source": "https://github.com/maximebf/php-debugbar/tree/v1.21.3" }, - "time": "2024-02-15T10:49:09+00:00" + "time": "2024-03-12T14:23:07+00:00" }, { "name": "mockery/mockery", - "version": "1.6.7", + "version": "1.6.10", "source": { "type": "git", "url": "https://github.com/mockery/mockery.git", - "reference": "0cc058854b3195ba21dc6b1f7b1f60f4ef3a9c06" + "reference": "47065d1be1fa05def58dc14c03cf831d3884ef0b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/0cc058854b3195ba21dc6b1f7b1f60f4ef3a9c06", - "reference": "0cc058854b3195ba21dc6b1f7b1f60f4ef3a9c06", + "url": "https://api.github.com/repos/mockery/mockery/zipball/47065d1be1fa05def58dc14c03cf831d3884ef0b", + "reference": "47065d1be1fa05def58dc14c03cf831d3884ef0b", "shasum": "" }, "require": { @@ -17002,8 +17310,8 @@ "phpunit/phpunit": "<8.0" }, "require-dev": { - "phpunit/phpunit": "^8.5 || ^9.6.10", - "symplify/easy-coding-standard": "^12.0.8" + "phpunit/phpunit": "^8.5 || ^9.6.17", + "symplify/easy-coding-standard": "^12.1.14" }, "type": "library", "autoload": { @@ -17060,7 +17368,7 @@ "security": "https://github.com/mockery/mockery/security/advisories", "source": "https://github.com/mockery/mockery" }, - "time": "2023-12-10T02:24:34+00:00" + "time": "2024-03-19T16:15:45+00:00" }, { "name": "myclabs/deep-copy", @@ -17425,16 +17733,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.60", + "version": "1.10.63", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "95dcea7d6c628a3f2f56d091d8a0219485a86bbe" + "reference": "ad12836d9ca227301f5fb9960979574ed8628339" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/95dcea7d6c628a3f2f56d091d8a0219485a86bbe", - "reference": "95dcea7d6c628a3f2f56d091d8a0219485a86bbe", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ad12836d9ca227301f5fb9960979574ed8628339", + "reference": "ad12836d9ca227301f5fb9960979574ed8628339", "shasum": "" }, "require": { @@ -17483,20 +17791,20 @@ "type": "tidelift" } ], - "time": "2024-03-07T13:30:19+00:00" + "time": "2024-03-18T16:53:53+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "10.1.12", + "version": "10.1.14", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "842f72662d6b9edda84c4b6f13885fd9cd53dc63" + "reference": "e3f51450ebffe8e0efdf7346ae966a656f7d5e5b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/842f72662d6b9edda84c4b6f13885fd9cd53dc63", - "reference": "842f72662d6b9edda84c4b6f13885fd9cd53dc63", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/e3f51450ebffe8e0efdf7346ae966a656f7d5e5b", + "reference": "e3f51450ebffe8e0efdf7346ae966a656f7d5e5b", "shasum": "" }, "require": { @@ -17553,7 +17861,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.12" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.14" }, "funding": [ { @@ -17561,7 +17869,7 @@ "type": "github" } ], - "time": "2024-03-02T07:22:05+00:00" + "time": "2024-03-12T15:33:41+00:00" }, { "name": "phpunit/php-file-iterator", @@ -17808,16 +18116,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.11", + "version": "10.5.13", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "0d968f6323deb3dbfeba5bfd4929b9415eb7a9a4" + "reference": "20a63fc1c6db29b15da3bd02d4b6cf59900088a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0d968f6323deb3dbfeba5bfd4929b9415eb7a9a4", - "reference": "0d968f6323deb3dbfeba5bfd4929b9415eb7a9a4", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/20a63fc1c6db29b15da3bd02d4b6cf59900088a7", + "reference": "20a63fc1c6db29b15da3bd02d4b6cf59900088a7", "shasum": "" }, "require": { @@ -17889,7 +18197,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.11" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.13" }, "funding": [ { @@ -17905,7 +18213,7 @@ "type": "tidelift" } ], - "time": "2024-02-25T14:05:00+00:00" + "time": "2024-03-12T15:37:41+00:00" }, { "name": "sebastian/cli-parser", diff --git a/config/mail.php b/config/mail.php index 0cd235b4048f..a9a86b08acc3 100644 --- a/config/mail.php +++ b/config/mail.php @@ -28,7 +28,7 @@ return [ | sending an e-mail. You will specify which one you are using for your | mailers below. You are free to add additional mailers as required. | - | Supported: "smtp", "sendmail", "mailgun", "ses", + | Supported: "smtp", "sendmail", "mailgun", "brevo", "ses", | "postmark", "log", "array", "failover" | */ @@ -54,6 +54,10 @@ return [ 'transport' => 'mailgun', ], + 'brevo' => [ + 'transport' => 'brevo', + ], + 'postmark' => [ 'transport' => 'postmark', ], diff --git a/config/ninja.php b/config/ninja.php index 47ecd5b1aa42..35b1eb1e05e3 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -84,9 +84,15 @@ return [ 'username' => 'user@example.com', 'clientname' => 'client@example.com', 'password' => 'password', + 'gocardless' => env('GOCARDLESS_KEYS',''), + 'square' => env('SQUARE_KEYS',''), + 'eway' => env('EWAY_KEYS',''), + 'mollie', env('MOLLIE_KEYS',''), + 'paytrace' => env('PAYTRACE_KEYS',''), 'stripe' => env('STRIPE_KEYS', ''), 'paypal' => env('PAYPAL_KEYS', ''), 'ppcp' => env('PPCP_KEYS', ''), + 'forte' => env('FORTE_KEYS', ''), 'paypal_rest' => env('PAYPAL_REST_KEYS', ''), 'authorize' => env('AUTHORIZE_KEYS', ''), 'checkout' => env('CHECKOUT_KEYS', ''), @@ -94,11 +100,6 @@ return [ 'test_email' => env('TEST_EMAIL', 'test@example.com'), 'wepay' => env('WEPAY_KEYS', ''), 'braintree' => env('BRAINTREE_KEYS', ''), - 'paytrace' => [ - 'username' => env('PAYTRACE_U', ''), - 'password' => env('PAYTRACE_P', ''), - 'decrypted' => env('PAYTRACE_KEYS', ''), - ], 'mollie' => env('MOLLIE_KEYS', ''), 'square' => env('SQUARE_KEYS', ''), ], @@ -243,4 +244,6 @@ return [ 'public_key' => env('NINJA_PUBLIC_KEY', false), 'private_key' => env('NINJA_PRIVATE_KEY', false), ], + 'upload_extensions' => env('ADDITIONAL_UPLOAD_EXTENSIONS', false), + ]; diff --git a/config/open-telemetry.php b/config/open-telemetry.php deleted file mode 100644 index 355c0118e819..000000000000 --- a/config/open-telemetry.php +++ /dev/null @@ -1,98 +0,0 @@ - null, - - /* - * A driver is responsible for transmitting any measurements. - */ - 'drivers' => [ - Spatie\OpenTelemetry\Drivers\HttpDriver::class => [ - 'url' => 'http://localhost:9411/api/v2/spans', - // 'url' => 'http://localhost:4318/v1/traces' - ], - ], - - /* - * This class determines if your measurements should actually be sent - * to the reporting drivers. - */ - 'sampler' => Spatie\OpenTelemetry\Support\Samplers\AlwaysSampler::class, - - /* - * Tags can be added to any measurement. These classes will determine the - * values of the tags when a new trace starts. - */ - 'trace_tag_providers' => [ - \Spatie\OpenTelemetry\Support\TagProviders\DefaultTagsProvider::class, - ], - - /* - * Tags can be added to any measurement. These classes will determine the - * values of the tags when a new span starts. - */ - 'span_tag_providers' => [ - - ], - - 'queue' => [ - /* - * When enabled, any measurements (spans) you make in a queued job that implements - * `TraceAware` will automatically belong to the same trace that was - * started in the process that dispatched the job. - */ - 'make_queue_trace_aware' => true, - - /* - * When this is set to `false`, only jobs the implement - * `TraceAware` will be trace aware. - */ - 'all_jobs_are_trace_aware_by_default' => true, - - /* - * When set to `true` all jobs will - * automatically start a span. - */ - 'all_jobs_auto_start_a_span' => true, - - /* - * These jobs will be trace aware even if they don't - * implement the `TraceAware` interface. - */ - 'trace_aware_jobs' => [ - - ], - - /* - * These jobs will never trace aware, regardless of `all_jobs_are_trace_aware_by_default`. - */ - 'not_trace_aware_jobs' => [ - - ], - ], - - /* - * These actions can be overridden to have fine-grained control over how - * the package performs certain tasks. - * - * In most cases, you should use the default values. - */ - 'actions' => [ - 'make_queue_trace_aware' => Spatie\OpenTelemetry\Actions\MakeQueueTraceAwareAction::class, - ], - - /* - * This class determines how the package measures time. - */ - 'stopwatch' => Spatie\OpenTelemetry\Support\Stopwatch::class, - - /* - * This class generates IDs for traces and spans. - */ - 'id_generator' => Spatie\OpenTelemetry\Support\IdGenerator::class, -]; diff --git a/config/services.php b/config/services.php index e8f12e34cb4d..ae434aba6575 100644 --- a/config/services.php +++ b/config/services.php @@ -12,7 +12,7 @@ return [ |-------------------------------------------------------------------------- | | This file is for storing the credentials for third party services such - | as Mailgun, Postmark, AWS and more. This file provides the de facto + | as Mailgun, Brevo, Postmark, AWS and more. This file provides the de facto | location for this type of information, allowing packages to have | a conventional file to locate the various service credentials. | @@ -30,6 +30,10 @@ return [ ], ], + 'brevo' => [ + 'key' => env('BREVO_SECRET', ''), + ], + 'postmark' => [ 'token' => env('POSTMARK_SECRET', ''), ], @@ -67,8 +71,8 @@ return [ ], 'stripe' => [ - 'model' => App\Models\User::class, - 'key' => env('STRIPE_KEY'), + 'model' => App\Models\User::class, + 'key' => env('STRIPE_KEY'), 'secret' => env('STRIPE_SECRET'), ], diff --git a/database/migrations/2024_03_14_201844_adjust_discount_column_max_resolution.php b/database/migrations/2024_03_14_201844_adjust_discount_column_max_resolution.php index 3aa0099e3cf0..ad36915c62d2 100644 --- a/database/migrations/2024_03_14_201844_adjust_discount_column_max_resolution.php +++ b/database/migrations/2024_03_14_201844_adjust_discount_column_max_resolution.php @@ -16,7 +16,6 @@ return new class extends Migration $table->decimal('discount', 20, 6)->default(0)->change(); }); - Schema::table('credits', function (Blueprint $table) { $table->decimal('discount', 20, 6)->default(0)->change(); }); diff --git a/database/migrations/2024_03_19_346785_add_routing_id_to_vendor b/database/migrations/2024_03_19_346785_add_routing_id_to_vendor new file mode 100644 index 000000000000..bdf4223b8345 --- /dev/null +++ b/database/migrations/2024_03_19_346785_add_routing_id_to_vendor @@ -0,0 +1,42 @@ +string('routing_id')->nullable(); + }); + + \App\Models\Company::query() + ->cursor() + ->each(function ($c){ + $settings = $c->settings; + $settings->e_quote_type = 'OrderX_Comfort'; + $settings->enable_rappen_rounding = false; + + $c->settings = $settings; + $c->save(); + }); + + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + + } +}; diff --git a/lang/en/texts.php b/lang/en/texts.php index 336da1cdcf7b..9f23ff3b6f68 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -461,8 +461,8 @@ $lang = array( 'delete_token' => 'Delete Token', 'token' => 'Token', 'add_gateway' => 'Add Payment Gateway', - 'delete_gateway' => 'Delete Gateway', - 'edit_gateway' => 'Edit Gateway', + 'delete_gateway' => 'Delete Payment Gateway', + 'edit_gateway' => 'Edit Payment Gateway', 'updated_gateway' => 'Successfully updated gateway', 'created_gateway' => 'Successfully created gateway', 'deleted_gateway' => 'Successfully deleted gateway', @@ -2197,6 +2197,8 @@ $lang = array( 'encryption' => 'Encryption', 'mailgun_domain' => 'Mailgun Domain', 'mailgun_private_key' => 'Mailgun Private Key', + 'brevo_domain' => 'Brevo Domain', + 'brevo_private_key' => 'Brevo Private Key', 'send_test_email' => 'Send test email', 'select_label' => 'Select Label', 'label' => 'Label', @@ -4847,6 +4849,7 @@ $lang = array( 'email_alignment' => 'Email Alignment', 'pdf_preview_location' => 'PDF Preview Location', 'mailgun' => 'Mailgun', + 'brevo' => 'Brevo', 'postmark' => 'Postmark', 'microsoft' => 'Microsoft', 'click_plus_to_create_record' => 'Click + to create a record', @@ -5099,6 +5102,8 @@ $lang = array( 'drop_files_here' => 'Drop files here', 'upload_files' => 'Upload Files', 'download_e_invoice' => 'Download E-Invoice', + 'download_e_credit' => 'Download E-Credit', + 'download_e_quote' => 'Download E-Quote', 'triangular_tax_info' => 'Intra-community triangular transaction', 'intracommunity_tax_info' => 'Tax-free intra-community delivery', 'reverse_tax_info' => 'Please note that this supply is subject to reverse charge', @@ -5272,6 +5277,13 @@ $lang = array( 'select_email_provider' => 'Set your email as the sending user', 'purchase_order_items' => 'Purchase Order Items', 'csv_rows_length' => 'No data found in this CSV file', + 'accept_payments_online' => 'Accept Payments Online', + 'all_payment_gateways' => 'View all payment gateways', + 'product_cost' => 'Product cost', + 'enable_rappen_roudning' => 'Enable Rappen Rounding', + 'enable_rappen_rounding_help' => 'Rounds totals to nearest 5', + 'duration_words' => 'Duration in words', + 'upcoming_recurring_invoices' => 'Upcoming Recurring Invoices', ); return $lang; diff --git a/lang/fr_CA/texts.php b/lang/fr_CA/texts.php index 9faef9cfe54c..2e408bf5a729 100644 --- a/lang/fr_CA/texts.php +++ b/lang/fr_CA/texts.php @@ -460,7 +460,7 @@ $lang = array( 'edit_token' => 'Éditer le jeton', 'delete_token' => 'Supprimer le jeton', 'token' => 'Jeton', - 'add_gateway' => 'Add Payment Gateway', + 'add_gateway' => 'Ajouter une passerelle de paiement', 'delete_gateway' => 'Supprimer la passerelle', 'edit_gateway' => 'Éditer la passerelle', 'updated_gateway' => 'La passerelle a été mise à jour', @@ -2194,6 +2194,8 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette 'encryption' => 'Cryptage', 'mailgun_domain' => 'Domaine Mailgun', 'mailgun_private_key' => 'Clé privée Mailgun', + 'brevo_domain' => 'Domaine Brevo', + 'brevo_private_key' => 'Clé privée Brevo', 'send_test_email' => 'Envoyer un courriel test', 'select_label' => 'Sélectionnez le libellé', 'label' => 'Libellé', @@ -4844,6 +4846,7 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette 'email_alignment' => 'Justification du courriel', 'pdf_preview_location' => 'Emplacement de prévisualisation du PDF', 'mailgun' => 'Mailgun', + 'brevo' => 'Brevo', 'postmark' => 'Postmark', 'microsoft' => 'Microsoft', 'click_plus_to_create_record' => 'Cliquez sur + pour créer un enregistrement', @@ -5096,6 +5099,8 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette 'drop_files_here' => 'Déposez les fichiers ici', 'upload_files' => 'Téléverser les fichiers', 'download_e_invoice' => 'Télécharger la facture électronique', + 'download_e_credit' => 'Télécharger E-Credit', + 'download_e_quote' => 'Télécharger E-Quote', 'triangular_tax_info' => 'Transactions intra-communautaire triangulaire', 'intracommunity_tax_info' => 'Livraison intra-communautaure sans taxe', 'reverse_tax_info' => 'Veuillez noter que cette provision est sujette à une charge renversée', @@ -5248,9 +5253,14 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette 'payment_type_help' => 'Définit le type de paiement manuel par défaut.', 'quote_valid_until_help' => 'Le nombre de jours pour lesquels la soumission est valide', 'expense_payment_type_help' => 'Le type de paiement de dépenses par défaut à utiliser', - 'paylater' => 'Pay in 4', - 'payment_provider' => 'Payment Provider', - + 'paylater' => 'Payer en 4', + 'payment_provider' => 'Fournisseur de paiement', + 'select_email_provider' => 'Définir le courriel pour l\'envoi', + 'purchase_order_items' => 'Articles du bon d\'achat', + 'csv_rows_length' => 'Aucune donnée dans ce fichier CSV', + 'accept_payments_online' => 'Accepter les paiements en ligne', + 'all_payment_gateways' => 'Voir toutes les passerelles de paiements', + 'product_cost' => 'Coût du produit', ); return $lang; diff --git a/openapi/api-docs.yaml b/openapi/api-docs.yaml index 3d95b07ea714..593cf67846a0 100644 --- a/openapi/api-docs.yaml +++ b/openapi/api-docs.yaml @@ -6,7 +6,7 @@ info:
The Invoice Ninja API is organized around REST and returns JSON-encoded responses, and uses standard HTTP response codes, authentication, and verbs. -
+
termsOfService: 'https://invoiceninja.github.io/docs/legal/terms_of_service/#page-content' @@ -17,10 +17,9 @@ info: url: 'https://www.elastic.co/licensing/elastic-license' version: 5.8.34 servers: - - - url: 'https://demo.invoiceninja.com' - description: | - ## Demo API Server InvoiceNinja. + - url: "https://demo.invoiceninja.com" + description: | + ## Demo API Server InvoiceNinja. You can use the demo API key `TOKEN` to test the endpoints from within this API spec paths: /api/v1/activities: @@ -49,10 +48,10 @@ paths: $ref: "#/components/headers/X-RateLimit-Limit" content: application/json: - schema: + schema: type: object properties: - data: + data: type: array items: $ref: '#/components/schemas/Activity' @@ -114,9 +113,9 @@ paths: - login summary: "Attempts authentication" description: | - After authenticating with the API, the returned object is a CompanyUser object which is a bridge linking the user to the company. + After authenticating with the API, the returned object is a CompanyUser object which is a bridge linking the user to the company. - The company user object itself contains the users permissions (admin/owner or fine grained permissions) You will most likely want to + The company user object itself contains the users permissions (admin/owner or fine grained permissions) You will most likely want to also include in the response of this object both the company and the user object, this can be done by using the include parameter. /api/v1/login?include=company,user @@ -186,7 +185,7 @@ paths: - refresh summary: "Refresh data by timestamp" description: | - Refreshes the dataset. + Refreshes the dataset. This endpoint can be used if you only need to access the most recent data from a certain point in time. For example, if you only want to retrieve The most recent data from the last time you accessed the system, you would pass the query parameter ?updated_at=1676173763. (unix timestamp) @@ -299,10 +298,10 @@ paths: $ref: "#/components/headers/X-RateLimit-Limit" content: application/json: - schema: + schema: type: object properties: - data: + data: type: array items: $ref: '#/components/schemas/BankIntegration' @@ -755,10 +754,10 @@ paths: $ref: "#/components/headers/X-RateLimit-Limit" content: application/json: - schema: + schema: type: object properties: - data: + data: type: array items: $ref: '#/components/schemas/BankTransaction' @@ -1140,10 +1139,10 @@ paths: $ref: "#/components/headers/X-RateLimit-Limit" content: application/json: - schema: + schema: type: object properties: - data: + data: type: array items: $ref: '#/components/schemas/BankTransactionRule' @@ -1516,10 +1515,10 @@ paths: $ref: "#/components/headers/X-RateLimit-Limit" content: application/json: - schema: + schema: type: object properties: - data: + data: type: array items: $ref: '#/components/schemas/ClientGatewayToken' @@ -1809,10 +1808,10 @@ paths: $ref: "#/components/headers/X-RateLimit-Limit" content: application/json: - schema: + schema: type: object properties: - data: + data: type: array items: $ref: '#/components/schemas/Company' @@ -2208,10 +2207,10 @@ paths: $ref: "#/components/headers/X-RateLimit-Limit" content: application/json: - schema: + schema: type: object properties: - data: + data: type: array items: $ref: '#/components/schemas/CompanyGateway' @@ -2530,10 +2529,10 @@ paths: $ref: "#/components/headers/X-RateLimit-Limit" content: application/json: - schema: + schema: type: object properties: - data: + data: type: array items: $ref: '#/components/schemas/CompanyLedger' @@ -2617,7 +2616,7 @@ paths: $ref: "#/components/responses/422" default: $ref: "#/components/responses/default" - + /api/v1/designs: get: tags: @@ -2642,10 +2641,10 @@ paths: $ref: "#/components/headers/X-RateLimit-Limit" content: application/json: - schema: + schema: type: object properties: - data: + data: type: array items: $ref: '#/components/schemas/Design' @@ -2958,10 +2957,10 @@ paths: $ref: "#/components/headers/X-RateLimit-Limit" content: application/json: - schema: + schema: type: object properties: - data: + data: type: array items: $ref: '#/components/schemas/Document' @@ -3059,10 +3058,10 @@ paths: $ref: "#/components/headers/X-RateLimit-Limit" content: application/json: - schema: + schema: type: object properties: - data: + data: type: array items: $ref: '#/components/schemas/ExpenseCategory' @@ -3363,10 +3362,10 @@ paths: $ref: "#/components/headers/X-RateLimit-Limit" content: application/json: - schema: + schema: type: object properties: - data: + data: type: array items: $ref: '#/components/schemas/Expense' @@ -3756,10 +3755,10 @@ paths: $ref: "#/components/headers/X-RateLimit-Limit" content: application/json: - schema: + schema: type: object properties: - data: + data: type: array items: $ref: '#/components/schemas/GroupSetting' @@ -4217,7 +4216,7 @@ paths: $ref: "#/components/responses/422" default: $ref: "#/components/responses/default" - + /api/v1/claim_license: get: tags: @@ -4427,7 +4426,7 @@ paths: $ref: "#/components/responses/422" default: $ref: "#/components/responses/default" - + /api/v1/payment_terms: get: tags: @@ -4452,10 +4451,10 @@ paths: $ref: "#/components/headers/X-RateLimit-Limit" content: application/json: - schema: + schema: type: object properties: - data: + data: type: array items: $ref: '#/components/schemas/PaymentTerm' @@ -4894,10 +4893,10 @@ paths: $ref: "#/components/headers/X-RateLimit-Limit" content: application/json: - schema: + schema: type: object properties: - data: + data: type: array items: $ref: '#/components/schemas/RecurringExpense' @@ -5237,7 +5236,7 @@ paths: $ref: "#/components/responses/422" default: $ref: "#/components/responses/default" - + /api/v1/recurring_quotes: get: tags: @@ -5261,10 +5260,10 @@ paths: $ref: "#/components/headers/X-RateLimit-Limit" content: application/json: - schema: + schema: type: object properties: - data: + data: type: array items: $ref: '#/components/schemas/RecurringQuote' @@ -6165,10 +6164,10 @@ paths: $ref: "#/components/headers/X-RateLimit-Limit" content: application/json: - schema: + schema: type: object properties: - data: + data: type: array items: $ref: '#/components/schemas/Subscription' @@ -6514,10 +6513,10 @@ paths: $ref: "#/components/headers/X-RateLimit-Limit" content: application/json: - schema: + schema: type: object properties: - data: + data: type: array items: $ref: '#/components/schemas/SystemLog' @@ -6573,7 +6572,7 @@ paths: $ref: "#/components/responses/422" default: $ref: "#/components/responses/default" - + /api/v1/task_schedulers/: get: tags: @@ -6827,10 +6826,10 @@ paths: $ref: "#/components/headers/X-RateLimit-Limit" content: application/json: - schema: + schema: type: object properties: - data: + data: type: array items: $ref: '#/components/schemas/TaskStatus' @@ -7140,10 +7139,10 @@ paths: $ref: "#/components/headers/X-RateLimit-Limit" content: application/json: - schema: + schema: type: object properties: - data: + data: type: array items: $ref: '#/components/schemas/TaxRate' @@ -7457,10 +7456,10 @@ paths: $ref: "#/components/headers/X-RateLimit-Limit" content: application/json: - schema: + schema: type: object properties: - data: + data: type: array items: $ref: '#/components/schemas/CompanyToken' @@ -7765,10 +7764,10 @@ paths: $ref: "#/components/headers/X-RateLimit-Limit" content: application/json: - schema: + schema: type: object properties: - data: + data: type: array items: $ref: '#/components/schemas/User' @@ -8169,7 +8168,7 @@ paths: $ref: "#/components/responses/422" default: $ref: "#/components/responses/default" - + /api/v1/webcron: get: tags: @@ -8221,10 +8220,10 @@ paths: $ref: "#/components/headers/X-RateLimit-Limit" content: application/json: - schema: + schema: type: object properties: - data: + data: type: array items: $ref: '#/components/schemas/Webhook' @@ -8513,7 +8512,7 @@ paths: - products summary: "List products" description: | - Lists products, search and filters allow fine grained lists to be generated. + Lists products, search and filters allow fine grained lists to be generated. Query parameters can be added to perform fine grained filtering of the products list, these are handled by the ProductFilters class which defines the methods available operationId: getProducts @@ -8548,7 +8547,7 @@ paths: required: false schema: type: string - example: id|desc product_key|desc + example: id|desc product_key|desc responses: 200: description: "A list of products" @@ -8561,10 +8560,10 @@ paths: $ref: "#/components/headers/X-RateLimit-Limit" content: application/json: - schema: + schema: type: object properties: - data: + data: type: array items: $ref: '#/components/schemas/Product' @@ -8974,10 +8973,10 @@ paths: $ref: "#/components/headers/X-RateLimit-Limit" content: application/json: - schema: + schema: type: object properties: - data: + data: type: array items: $ref: '#/components/schemas/Task' @@ -9410,10 +9409,10 @@ paths: $ref: "#/components/headers/X-RateLimit-Limit" content: application/json: - schema: + schema: type: object properties: - data: + data: type: array items: $ref: '#/components/schemas/Project' @@ -9796,31 +9795,31 @@ paths: summary: 'List clients' 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:\ - + ``` /api/v1/clients?balance=gt:1000 - ``` - + ``` + You can also sort the results by adding a sort parameter. The following example will sort the results by the client name in descending order:\ - + ``` /api/v1/clients?sort=name|desc ``` You can also combine multiple filters together. The following example will return clients that have a balance greater than 1000 and are not deleted and have a name that starts with "Bob":\ - + ``` /api/v1/clients?balance=gt:1000&name=Bob* ``` If you wish to retrieve child relations, you can also combine the query parameter `?include=` with a comma separated list of relationships:\ - + ``` /api/v1/clients?include=activities,ledger,system_logs' ``` The per_page and page variables allow pagination of the list of clients. The following example will return the second page of clients with 15 clients per page:\ - + ``` /api/v1/clients?per_page=15&page=2 ``` @@ -9894,7 +9893,7 @@ paths: required: false schema: type: string - example: id|desc name|desc balance|asc + example: id|desc name|desc balance|asc responses: 200: @@ -9908,10 +9907,10 @@ paths: $ref: '#/components/headers/X-RateLimit-Limit' content: application/json: - schema: + schema: type: object properties: - data: + data: type: array items: $ref: '#/components/schemas/Client' @@ -9938,7 +9937,7 @@ paths: summary: 'Create client' description: | Adds a client to a company - + When creating (or updating) a client you must include the child contacts with all mutating requests. Client contacts cannot be modified in isolation. operationId: storeClient @@ -10203,7 +10202,7 @@ paths: application/json: schema: $ref: '#/components/schemas/GenericBulkAction' - + responses: 200: description: 'The Client listresponse' @@ -10338,7 +10337,7 @@ paths: description: | Handles merging 2 clients - The id parameter is the client that will be the primary client after the merge has completed. + The id parameter is the client that will be the primary client after the merge has completed. The mergeable_client_hashed_id is the client that will be merged into the primary client, this clients records will be updated and associated with the primary client. operationId: mergeClient @@ -10555,10 +10554,10 @@ paths: $ref: "#/components/headers/X-RateLimit-Limit" content: application/json: - schema: + schema: type: object properties: - data: + data: type: array items: $ref: '#/components/schemas/Credit' @@ -10614,7 +10613,7 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - + "/api/v1/credits/{id}": get: tags: @@ -11001,12 +11000,12 @@ paths: - name: filter in: query description: | - Searches across a range of columns including: - - amount - - date - - custom_value1 - - custom_value2 - - custom_value3 + Searches across a range of columns including: + - amount + - date + - custom_value1 + - custom_value2 + - custom_value3 - custom_value4 required: false schema: @@ -11015,7 +11014,7 @@ paths: - name: number in: query description: | - Search payments by payment number + Search payments by payment number required: false schema: type: string @@ -11026,7 +11025,7 @@ paths: required: false schema: type: string - example: id|desc number|desc balance|asc + example: id|desc number|desc balance|asc responses: 200: description: "A list of payments" @@ -11039,10 +11038,10 @@ paths: $ref: "#/components/headers/X-RateLimit-Limit" content: application/json: - schema: + schema: type: object properties: - data: + data: type: array items: $ref: '#/components/schemas/Payment' @@ -11104,7 +11103,7 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - + "/api/v1/payments/{id}": get: tags: @@ -11462,7 +11461,7 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - + "/api/v1/payments/{id}/upload": post: tags: @@ -11531,8 +11530,8 @@ paths: - invoices summary: "List invoices" description: | - Lists invoices with the option to chain multiple query parameters allowing fine grained filtering of the list. - + Lists invoices with the option to chain multiple query parameters allowing fine grained filtering of the list. + operationId: getInvoices parameters: - $ref: "#/components/parameters/X-API-TOKEN" @@ -11548,11 +11547,11 @@ paths: - name: client_status in: query description: | - A comma separated list of invoice status strings. Valid options include: + A comma separated list of invoice status strings. Valid options include: - all - - paid - - unpaid - - overdue + - paid + - unpaid + - overdue required: false schema: type: string @@ -11560,7 +11559,7 @@ paths: - name: number in: query description: | - Search invoices by invoice number + Search invoices by invoice number required: false schema: type: string @@ -11568,15 +11567,15 @@ paths: - name: filter in: query description: | - Searches across a range of columns including: - - number - - po_number - - date - - amount - - balance - - custom_value1 - - custom_value2 - - custom_value3 + Searches across a range of columns including: + - number + - po_number + - date + - amount + - balance + - custom_value1 + - custom_value2 + - custom_value3 - custom_value4 required: false schema: @@ -11612,7 +11611,7 @@ paths: required: false schema: type: string - example: id|desc number|desc balance|asc + example: id|desc number|desc balance|asc - name: private_notes in: query description: | @@ -11633,10 +11632,10 @@ paths: $ref: "#/components/headers/X-RateLimit-Limit" content: application/json: - schema: + schema: type: object properties: - data: + data: type: array items: $ref: '#/components/schemas/Invoice' @@ -11659,10 +11658,10 @@ paths: tags: - invoices summary: "Create invoice" - description: | + description: | Adds a invoice to a company - Triggered actions are available when updating or creating an invoice. + Triggered actions are available when updating or creating an invoice. These are query parameters that can be chained in order to perform additional actions on the entity, these include: ``` @@ -11766,7 +11765,7 @@ paths: description: | Handles the updating of an invoice by id. - Triggered actions are available when updating or creating an invoice. + Triggered actions are available when updating or creating an invoice. These are query parameters that can be chained in order to perform additional actions on the entity, these include: ``` @@ -11817,7 +11816,7 @@ paths: 5XX: description: 'Server error' default: - $ref: "#/components/responses/default" + $ref: "#/components/responses/default" delete: tags: - invoices @@ -11948,7 +11947,7 @@ paths: - invoices summary: "Bulk invoice actions" description: | - There are multiple actions that are available including: + There are multiple actions that are available including: operationId: bulkInvoices parameters: @@ -11966,35 +11965,35 @@ paths: action: type: string description: | - The action to be performed, options include: - - `bulk_download` - Bulk download an array of invoice PDFs (These are sent to the admin via email.) - - `download` - Download a single PDF. (Returns a single PDF object) - - `bulk_print` - Merges an array of Invoice PDFs for easy one click printing. - - `auto_bill` - Attempts to automatically bill the invoices with the payment method on file. - - `clone_to_invoice` - Returns a clone of the invoice. - - `clone_to_quote` - Returns a quote cloned using the properties of the given invoice. - - `mark_paid` - Marks an array of invoices as paid. - - `mark_sent` - Marks an array of invoices as sent. - - `restore` - Restores an array of invoices - - `delete` - Deletes an array of invoices - - `archive` - Archives an array of invoices - - `cancel` - Cancels an array of invoices - - `email` - Emails an array of invoices - - `send_email` - Emails an array of invoices. Requires additional properties to be sent. `email_type` + The action to be performed, options include: + - `bulk_download` + Bulk download an array of invoice PDFs (These are sent to the admin via email.) + - `download` + Download a single PDF. (Returns a single PDF object) + - `bulk_print` + Merges an array of Invoice PDFs for easy one click printing. + - `auto_bill` + Attempts to automatically bill the invoices with the payment method on file. + - `clone_to_invoice` + Returns a clone of the invoice. + - `clone_to_quote` + Returns a quote cloned using the properties of the given invoice. + - `mark_paid` + Marks an array of invoices as paid. + - `mark_sent` + Marks an array of invoices as sent. + - `restore` + Restores an array of invoices + - `delete` + Deletes an array of invoices + - `archive` + Archives an array of invoices + - `cancel` + Cancels an array of invoices + - `email` + Emails an array of invoices + - `send_email` + Emails an array of invoices. Requires additional properties to be sent. `email_type` ids: type: array items: @@ -12003,7 +12002,7 @@ paths: example: action: bulk_download ids: "['D2J234DFA','D2J234DFA','D2J234DFA']" - + responses: 200: description: "The Bulk Action response" @@ -12034,17 +12033,17 @@ paths: - invoices summary: "Custom invoice action" description: | - Performs a custom action on an invoice. - The current range of actions are as follows - - clone_to_invoice - - clone_to_quote - - history - - delivery_note - - mark_paid - - download - - archive - - delete - - email + Performs a custom action on an invoice. + The current range of actions are as follows + - clone_to_invoice + - clone_to_quote + - history + - delivery_note + - mark_paid + - download + - archive + - delete + - email operationId: actionInvoice parameters: - $ref: "#/components/parameters/X-API-TOKEN" @@ -12242,7 +12241,7 @@ paths: - Recurring Invoices summary: "List recurring invoices" description: | - Lists invoices with the option to chain multiple query parameters allowing fine grained filtering of the list. + Lists invoices with the option to chain multiple query parameters allowing fine grained filtering of the list. operationId: getRecurringInvoices parameters: @@ -12258,10 +12257,10 @@ paths: - name: filter in: query description: | - Searches across a range of columns including: - - custom_value1 - - custom_value2 - - custom_value3 + Searches across a range of columns including: + - custom_value1 + - custom_value2 + - custom_value3 - custom_value4 required: false schema: @@ -12270,11 +12269,11 @@ paths: - name: client_status in: query description: | - A comma separated list of invoice status strings. Valid options include: + A comma separated list of invoice status strings. Valid options include: - all - - active - - paused - - completed + - active + - paused + - completed required: false schema: type: string @@ -12285,7 +12284,7 @@ paths: required: false schema: type: string - example: id|desc number|desc balance|asc + example: id|desc number|desc balance|asc responses: 200: description: "A list of recurring_invoices" @@ -12298,10 +12297,10 @@ paths: $ref: "#/components/headers/X-RateLimit-Limit" content: application/json: - schema: + schema: type: object properties: - data: + data: type: array items: $ref: '#/components/schemas/RecurringInvoice' @@ -12356,7 +12355,7 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - + "/api/v1/recurring_invoices/{id}": get: tags: @@ -12531,7 +12530,7 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - + /api/v1/recurring_invoices/create: get: tags: @@ -12593,20 +12592,20 @@ paths: action: type: string description: | - The action to be performed, options include: - - `start` - Starts (or restarts) the recurring invoice. **note** if the recurring invoice has been stopped for a long time, it will attempt to catch back up firing a new Invoice every hour per interval that has been missed. + The action to be performed, options include: + - `start` + Starts (or restarts) the recurring invoice. **note** if the recurring invoice has been stopped for a long time, it will attempt to catch back up firing a new Invoice every hour per interval that has been missed. If you do not wish to have the recurring invoice catch up, you should set the next_send_date to the correct date you wish the recurring invoice to commence from. - - `stop` - Stops the recurring invoice. - - `send_now` - Force sends the recurring invoice - this option is only available when the recurring invoice is in a draft state. - - `restore` + - `stop` + Stops the recurring invoice. + - `send_now` + Force sends the recurring invoice - this option is only available when the recurring invoice is in a draft state. + - `restore` Restores the recurring invoice from an archived or deleted state. - - `archive` + - `archive` Archives the recurring invoice. The recurring invoice will not fire in this state. - - `delete` - Deletes a recurring invoice. + - `delete` + Deletes a recurring invoice. ids: type: array items: @@ -12819,11 +12818,11 @@ paths: - name: filter in: query description: | - Searches across a range of columns including: - - number - - custom_value1 - - custom_value2 - - custom_value3 + Searches across a range of columns including: + - number + - custom_value1 + - custom_value2 + - custom_value3 - custom_value4 required: false schema: @@ -12832,13 +12831,13 @@ paths: - name: client_status in: query description: | - A comma separated list of quote status strings. Valid options include: + A comma separated list of quote status strings. Valid options include: - all - - draft - - sent + - draft + - sent - approved - expired - - upcoming + - upcoming required: false schema: type: string @@ -12846,7 +12845,7 @@ paths: - name: number in: query description: | - Search quote by quote number + Search quote by quote number required: false schema: type: string @@ -12857,7 +12856,7 @@ paths: required: false schema: type: string - example: id|desc number|desc balance|asc + example: id|desc number|desc balance|asc responses: 200: description: "A list of quotes" @@ -12870,10 +12869,10 @@ paths: $ref: "#/components/headers/X-RateLimit-Limit" content: application/json: - schema: + schema: type: object properties: - data: + data: type: array items: $ref: '#/components/schemas/Quote' @@ -13367,10 +13366,10 @@ paths: $ref: "#/components/headers/X-RateLimit-Limit" content: application/json: - schema: + schema: type: object properties: - data: + data: type: array items: $ref: '#/components/schemas/PurchaseOrder' @@ -13425,7 +13424,7 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - + "/api/v1/purchase_orders/{id}": get: tags: @@ -13601,7 +13600,7 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - + /api/v1/purchase_orders/create: get: tags: @@ -13864,10 +13863,10 @@ paths: $ref: "#/components/headers/X-RateLimit-Limit" content: application/json: - schema: + schema: type: object properties: - data: + data: type: array items: $ref: '#/components/schemas/Vendor' @@ -14258,10 +14257,10 @@ components: schema: type: integer securitySchemes: - ApiKeyAuth: + ApiKeyAuth: type: apiKey - in: header - name: X-API-TOKEN + in: header + name: X-API-TOKEN #examples: # Client: # $ref: '#/components/schemas/Client' @@ -14362,13 +14361,13 @@ components: application/json: schema: $ref: '#/components/schemas/Error' - 400: + 400: description: 'Invalid user input' content: application/json: schema: $ref: '#/components/schemas/InvalidInputError' - 401: + 401: description: 'Authentication error' content: application/json: @@ -14411,7 +14410,7 @@ components: bank_integration_include: name: include in: query - description: Include child relations of the BankIntegration object. Format is comma separated. + description: Include child relations of the BankIntegration object. Format is comma separated. required: false schema: type: string @@ -14476,7 +14475,7 @@ components: summary: include=payment will include the payment object in the response expense: value: expense - summary: include=expense will include the expense object in the response + summary: include=expense will include the expense object in the response vendor_contact: value: vendor_contact summary: include=vendor_contact will include the vendor_contact object in the response @@ -14488,7 +14487,7 @@ components: summary: include=purchase_order will include the purchase_order object in the response task: value: task - summary: include=task will include the task object in the response + summary: include=task will include the task object in the response login_include: name: include in: query @@ -14565,16 +14564,16 @@ components: schema: type: number example: user - + ########################### Generic filters available across all filter ################################## status: name: status in: query description: | - Filter the entity based on their status. ie active / archived / deleted. Format is a comma separated string with any of the following options: + Filter the entity based on their status. ie active / archived / deleted. Format is a comma separated string with any of the following options: - active - archived - - deleted + - deleted required: false schema: type: string @@ -14702,11 +14701,151 @@ components: type: string example: '2' type: object - - + 422: + $ref: "#/components/responses/422" + default: + $ref: "#/components/responses/default" + "/api/v1/companies/{id}/edit": + get: + tags: + - companies + summary: "Shows an company for editting" + description: "Displays an company by id" + operationId: editCompany + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + - name: id + in: path + description: "The Company Hashed ID" + required: true + schema: + type: string + format: string + example: D2J234DFA + responses: + 200: + description: "Returns the company object" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + $ref: "#/components/schemas/Company" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + + 422: + $ref: "#/components/responses/422" + default: + $ref: "#/components/responses/default" + "/api/v1/companies/{id}/upload": + post: + tags: + - companies + summary: "Uploads a document to a company" + description: "Handles the uploading of a document to a company" + operationId: uploadCompanies + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + - name: id + in: path + description: "The Company Hashed ID" + required: true + schema: + type: string + format: string + example: D2J234DFA + requestBody: + description: "File Upload Body" + required: true + content: + multipart/form-data: + schema: + type: object + properties: + _method: + type: string + example: PUT + documents: + type: array + items: + description: "Array of binary documents for upload" + type: string + format: binary + responses: + 200: + description: "Returns the client object" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + $ref: "#/components/schemas/Company" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + + 422: + $ref: "#/components/responses/422" + default: + $ref: "#/components/responses/default" + "/api/v1/companies/{company}/default": + post: + tags: + - companies + summary: "Sets the company as the default company." + description: "Sets the company as the default company." + operationId: setDefaultCompany + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + - name: company + in: path + description: "The Company Hashed ID" + required: true + schema: + type: string + format: string + example: D2J234DFA + responses: + 200: + description: "Returns the company object" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + $ref: "#/components/schemas/Company" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" TaskSchedulerSchema: properties: @@ -14791,7 +14930,7 @@ components: type: string example: create_client_report type: object - + TaskStatus: properties: id: @@ -14862,9 +15001,9 @@ components: type: string example: '' type: object - + AuthenticationError: - type: object + type: object properties: message: description: 'These credentials do not match our records / Invalid Token' @@ -15088,7 +15227,7 @@ components: The tax category id for this product.' The following constants are available (default = '1') - + ``` PRODUCT_TYPE_PHYSICAL = '1' PRODUCT_TYPE_SERVICE = '2' @@ -15238,8 +15377,8 @@ components: type: integer format: int32 description: | - The quantity of the product that is currently in stock. - + The quantity of the product that is currently in stock. + **note** this field is not mutable without passing an extra query parameter which will allow modification of this value. The query parameter ?update_in_stock_quantity=true **MUST** be passed if you wish to update this value manually. @@ -15275,7 +15414,7 @@ components: The tax category id for this product.' The following constants are available (default = '1') - + ``` PRODUCT_TYPE_PHYSICAL = '1' PRODUCT_TYPE_SERVICE = '2' @@ -15463,7 +15602,7 @@ components: type: string example: PAY_101 type: object - + BankTransactionRule: properties: id: @@ -15766,8 +15905,8 @@ components: items: $ref: '#/components/schemas/FeesAndLimits' type: object - - + + CompanySettings: required: - currency_id @@ -16700,7 +16839,7 @@ components: type: string example: Opnel5aKBz readOnly: true - client_contact_id: + client_contact_id: description: 'The client contact hashed id' type: string example: Opnel5aKBz @@ -17033,7 +17172,7 @@ components: type: object InvoiceRequest: required: - - client_id + - client_id properties: id: description: 'The invoice hashed id' @@ -18710,7 +18849,7 @@ components: self::ENTITY_RECURRING_TASK => 1024, self::ENTITY_RECURRING_QUOTE => 2048, ``` - + The default per_page value is 20. example: 2048 @@ -18725,8 +18864,8 @@ components: first_month_of_year: description: "The first month for the company financial year" type: string - example: '1' - enabled_item_tax_rates: + example: '1' + enabled_item_tax_rates: description: "The number of tax rates used per item" type: integer example: 2 @@ -18741,7 +18880,7 @@ components: A flag determining whether to auto-bill clients by default values: - + - always - Always auto bill - disabled - Never auto bill - optin - Allow the client to select their auto bill status with the default being disabled @@ -18910,12 +19049,12 @@ components: invoice_task_project_header: description: "A flag determining whether to include the project header on invoices by default" type: boolean - example: true + example: true invoice_task_item_description: description: "A flag determining whether to include the item description on invoices by default" type: boolean example: true - + settings: $ref: '#/components/schemas/CompanySettings' type: object @@ -19141,7 +19280,7 @@ components: type: boolean example: true type: object - + Quote: properties: id: @@ -19593,7 +19732,7 @@ components: description: 'The subscription associated with this invoice' type: string example: Opnel5aKBz - + type: object ClientRequest: required: @@ -20996,7 +21135,7 @@ components: project_id: description: 'The associated project_id' type: string - example: 'Opnel5aKBz' + example: 'Opnel5aKBz' client_id: description: 'The client hashed id' type: string @@ -21156,7 +21295,7 @@ components: type: string example: Opnel5aKBz readOnly: true - client_contact_id: + client_contact_id: description: 'The client contact hashed id' type: string example: Opnel5aKBz @@ -21231,7 +21370,7 @@ components: The tax rate id to set on the list of products The following constants are available (default = '1') - + ``` PRODUCT_TYPE_PHYSICAL = '1' PRODUCT_TYPE_SERVICE = '2' @@ -21293,7 +21432,7 @@ components: language_id: description: 'The language id of the user' type: string - example: 1 + example: 1 verified_phone_number: description: 'Boolean flag if the user has their phone verified. Required to settings up 2FA' type: boolean @@ -21631,7 +21770,7 @@ tags: description: | Endpoint definitions for interacting with reports. externalDocs: - description: "https://invoiceninja.github.io" - url: "https://invoiceninja.github.io" + description: "https://invoiceninja.github.io" + url: "https://invoiceninja.github.io" security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] diff --git a/openapi/components/schemas/company_settings.yaml b/openapi/components/schemas/company_settings.yaml index 15dc5bf8cd38..2ee34f675f99 100644 --- a/openapi/components/schemas/company_settings.yaml +++ b/openapi/components/schemas/company_settings.yaml @@ -1,842 +1,842 @@ - CompanySettings: - required: +CompanySettings: + required: - currency_id - properties: + properties: currency_id: - description: 'The default currency id' - type: string - example: true + description: "The default currency id" + type: string + example: true timezone_id: - description: 'The timezone id' - type: string - example: '15' + description: "The timezone id" + type: string + example: "15" date_format_id: - description: 'The date format id' - type: string - example: '15' + description: "The date format id" + type: string + example: "15" military_time: - description: 'Toggles 12/24 hour time' - type: boolean - example: true + description: "Toggles 12/24 hour time" + type: boolean + example: true language_id: - description: 'The language id' - type: string - example: '1' + description: "The language id" + type: string + example: "1" show_currency_code: - description: 'Toggles whether the currency symbol or code is shown' - type: boolean - example: true + description: "Toggles whether the currency symbol or code is shown" + type: boolean + example: true payment_terms: - description: '-1 sets no payment term, 0 sets payment due immediately, positive integers indicates payment terms in days' - type: integer - example: '1' + description: "-1 sets no payment term, 0 sets payment due immediately, positive integers indicates payment terms in days" + type: integer + example: "1" company_gateway_ids: - description: 'A commad separate list of available gateways' - type: string - example: '1,2,3,4' + description: "A commad separate list of available gateways" + type: string + example: "1,2,3,4" custom_value1: - description: 'A Custom Label' - type: string - example: 'Custom Label' + description: "A Custom Label" + type: string + example: "Custom Label" custom_value2: - description: 'A Custom Label' - type: string - example: 'Custom Label' + description: "A Custom Label" + type: string + example: "Custom Label" custom_value3: - description: 'A Custom Label' - type: string - example: 'Custom Label' + description: "A Custom Label" + type: string + example: "Custom Label" custom_value4: - description: 'A Custom Label' - type: string - example: 'Custom Label' + description: "A Custom Label" + type: string + example: "Custom Label" default_task_rate: - description: 'The default task rate' - type: number - format: float - example: '10.00' + description: "The default task rate" + type: number + format: float + example: "10.00" send_reminders: - description: 'Toggles whether reminders are sent' - type: boolean - example: true + description: "Toggles whether reminders are sent" + type: boolean + example: true enable_client_portal_tasks: - description: 'Show/hide the tasks panel in the client portal' - type: boolean - example: true + description: "Show/hide the tasks panel in the client portal" + type: boolean + example: true email_style: - description: 'options include plain,light,dark,custom' - type: string - example: light + description: "options include plain,light,dark,custom" + type: string + example: light reply_to_email: - description: 'The reply to email address' - type: string - example: email@gmail.com + description: "The reply to email address" + type: string + example: email@gmail.com bcc_email: - description: 'A comma separate list of BCC emails' - type: string - example: 'email@gmail.com, contact@gmail.com' + description: "A comma separate list of BCC emails" + type: string + example: "email@gmail.com, contact@gmail.com" pdf_email_attachment: - description: 'Toggles whether to attach PDF as attachment' - type: boolean - example: true + description: "Toggles whether to attach PDF as attachment" + type: boolean + example: true ubl_email_attachment: - description: 'Toggles whether to attach UBL as attachment' - type: boolean - example: true + description: "Toggles whether to attach UBL as attachment" + type: boolean + example: true email_style_custom: - description: 'The custom template' - type: string - example: '' + description: "The custom template" + type: string + example: "" counter_number_applied: - description: 'enum when the invoice number counter is set, ie when_saved, when_sent, when_paid' - type: string - example: when_sent + description: "enum when the invoice number counter is set, ie when_saved, when_sent, when_paid" + type: string + example: when_sent quote_number_applied: - description: 'enum when the quote number counter is set, ie when_saved, when_sent' - type: string - example: when_sent + description: "enum when the quote number counter is set, ie when_saved, when_sent" + type: string + example: when_sent custom_message_dashboard: - description: 'A custom message which is displayed on the dashboard' - type: string - example: 'Please pay invoices immediately' + description: "A custom message which is displayed on the dashboard" + type: string + example: "Please pay invoices immediately" custom_message_unpaid_invoice: - description: 'A custom message which is displayed in the client portal when a client is viewing a unpaid invoice.' - type: string - example: 'Please pay invoices immediately' + description: "A custom message which is displayed in the client portal when a client is viewing a unpaid invoice." + type: string + example: "Please pay invoices immediately" custom_message_paid_invoice: - description: 'A custom message which is displayed in the client portal when a client is viewing a paid invoice.' - type: string - example: 'Thanks for paying this invoice!' + description: "A custom message which is displayed in the client portal when a client is viewing a paid invoice." + type: string + example: "Thanks for paying this invoice!" custom_message_unapproved_quote: - description: 'A custom message which is displayed in the client portal when a client is viewing a unapproved quote.' - type: string - example: 'Please approve quote' + description: "A custom message which is displayed in the client portal when a client is viewing a unapproved quote." + type: string + example: "Please approve quote" lock_invoices: - description: 'Toggles whether invoices are locked once sent and cannot be modified further' - type: boolean - example: true + description: "Toggles whether invoices are locked once sent and cannot be modified further" + type: boolean + example: true auto_archive_invoice: - description: 'Toggles whether a invoice is archived immediately following payment' - type: boolean - example: true + description: "Toggles whether a invoice is archived immediately following payment" + type: boolean + example: true auto_archive_quote: - description: 'Toggles whether a quote is archived after being converted to a invoice' - type: boolean - example: true + description: "Toggles whether a quote is archived after being converted to a invoice" + type: boolean + example: true auto_convert_quote: - description: 'Toggles whether a quote is converted to a invoice when approved' - type: boolean - example: true + description: "Toggles whether a quote is converted to a invoice when approved" + type: boolean + example: true inclusive_taxes: - description: 'Boolean flag determining whether inclusive or exclusive taxes are used' - type: boolean - example: true + description: "Boolean flag determining whether inclusive or exclusive taxes are used" + type: boolean + example: true translations: - description: 'JSON payload of customized translations' - type: object - example: '' + description: "JSON payload of customized translations" + type: object + example: "" task_number_pattern: - description: 'Allows customisation of the task number pattern' - type: string - example: '{$year}-{$counter}' + description: "Allows customisation of the task number pattern" + type: string + example: "{$year}-{$counter}" task_number_counter: - description: 'The incrementing counter for tasks' - type: integer - example: '1' + description: "The incrementing counter for tasks" + type: integer + example: "1" reminder_send_time: - description: 'Time from UTC +0 when the email will be sent to the client' - type: integer - example: '32400' + description: "Time from UTC +0 when the email will be sent to the client" + type: integer + example: "32400" expense_number_pattern: - description: 'Allows customisation of the expense number pattern' - type: string - example: '{$year}-{$counter}' + description: "Allows customisation of the expense number pattern" + type: string + example: "{$year}-{$counter}" expense_number_counter: - description: 'The incrementing counter for expenses' - type: integer - example: '1' + description: "The incrementing counter for expenses" + type: integer + example: "1" vendor_number_pattern: - description: 'Allows customisation of the vendor number pattern' - type: string - example: '{$year}-{$counter}' + description: "Allows customisation of the vendor number pattern" + type: string + example: "{$year}-{$counter}" vendor_number_counter: - description: 'The incrementing counter for vendors' - type: integer - example: '1' + description: "The incrementing counter for vendors" + type: integer + example: "1" ticket_number_pattern: - description: 'Allows customisation of the ticket number pattern' - type: string - example: '{$year}-{$counter}' + description: "Allows customisation of the ticket number pattern" + type: string + example: "{$year}-{$counter}" ticket_number_counter: - description: 'The incrementing counter for tickets' - type: integer - example: '1' + description: "The incrementing counter for tickets" + type: integer + example: "1" payment_number_pattern: - description: 'Allows customisation of the payment number pattern' - type: string - example: '{$year}-{$counter}' + description: "Allows customisation of the payment number pattern" + type: string + example: "{$year}-{$counter}" payment_number_counter: - description: 'The incrementing counter for payments' - type: integer - example: '1' + description: "The incrementing counter for payments" + type: integer + example: "1" invoice_number_pattern: - description: 'Allows customisation of the invoice number pattern' - type: string - example: '{$year}-{$counter}' + description: "Allows customisation of the invoice number pattern" + type: string + example: "{$year}-{$counter}" invoice_number_counter: - description: 'The incrementing counter for invoices' - type: integer - example: '1' + description: "The incrementing counter for invoices" + type: integer + example: "1" quote_number_pattern: - description: 'Allows customisation of the quote number pattern' - type: string - example: '{$year}-{$counter}' + description: "Allows customisation of the quote number pattern" + type: string + example: "{$year}-{$counter}" quote_number_counter: - description: 'The incrementing counter for quotes' - type: integer - example: '1' + description: "The incrementing counter for quotes" + type: integer + example: "1" client_number_pattern: - description: 'Allows customisation of the client number pattern' - type: string - example: '{$year}-{$counter}' + description: "Allows customisation of the client number pattern" + type: string + example: "{$year}-{$counter}" client_number_counter: - description: 'The incrementing counter for clients' - type: integer - example: '1' + description: "The incrementing counter for clients" + type: integer + example: "1" credit_number_pattern: - description: 'Allows customisation of the credit number pattern' - type: string - example: '{$year}-{$counter}' + description: "Allows customisation of the credit number pattern" + type: string + example: "{$year}-{$counter}" credit_number_counter: - description: 'The incrementing counter for credits' - type: integer - example: '1' + description: "The incrementing counter for credits" + type: integer + example: "1" recurring_invoice_number_prefix: - description: 'This string is prepended to the recurring invoice number' - type: string - example: R + description: "This string is prepended to the recurring invoice number" + type: string + example: R reset_counter_frequency_id: - description: 'CONSTANT which is used to apply the frequency which the counters are reset' - type: integer - example: '1' + description: "CONSTANT which is used to apply the frequency which the counters are reset" + type: integer + example: "1" reset_counter_date: - description: 'The explicit date which is used to reset counters' - type: string - example: '2019-01-01' + description: "The explicit date which is used to reset counters" + type: string + example: "2019-01-01" counter_padding: - description: 'Pads the counter with leading zeros' - type: integer - example: '1' + description: "Pads the counter with leading zeros" + type: integer + example: "1" shared_invoice_quote_counter: - description: 'Flags whether to share the counter for invoices and quotes' - type: boolean - example: true + description: "Flags whether to share the counter for invoices and quotes" + type: boolean + example: true update_products: - description: 'Determines if client fields are updated from third party APIs' - type: boolean - example: true + description: "Determines if client fields are updated from third party APIs" + type: boolean + example: true convert_products: - description: '' - type: boolean - example: true + description: "" + type: boolean + example: true fill_products: - description: 'Automatically fill products based on product_key' - type: boolean - example: true + description: "Automatically fill products based on product_key" + type: boolean + example: true invoice_terms: - description: 'The default invoice terms' - type: string - example: 'Invoice Terms are...' + description: "The default invoice terms" + type: string + example: "Invoice Terms are..." quote_terms: - description: 'The default quote terms' - type: string - example: 'Quote Terms are...' + description: "The default quote terms" + type: string + example: "Quote Terms are..." invoice_taxes: - description: 'Taxes can be applied to the invoice' - type: number - example: '1' + description: "Taxes can be applied to the invoice" + type: number + example: "1" invoice_design_id: - description: 'The default design id (invoice, quote etc)' - type: string - example: '1' + description: "The default design id (invoice, quote etc)" + type: string + example: "1" quote_design_id: - description: 'The default design id (invoice, quote etc)' - type: string - example: '1' + description: "The default design id (invoice, quote etc)" + type: string + example: "1" invoice_footer: - description: 'The default invoice footer' - type: string - example: '1' + description: "The default invoice footer" + type: string + example: "1" invoice_labels: - description: 'JSON string of invoice labels' - type: string - example: '1' + description: "JSON string of invoice labels" + type: string + example: "1" tax_rate1: - description: 'The tax rate (float)' - type: number - example: '10' + description: "The tax rate (float)" + type: number + example: "10" tax_name1: - description: 'The tax name' - type: string - example: GST + description: "The tax name" + type: string + example: GST tax_rate2: - description: 'The tax rate (float)' - type: number - example: '10' + description: "The tax rate (float)" + type: number + example: "10" tax_name2: - description: 'The tax name' - type: string - example: GST + description: "The tax name" + type: string + example: GST tax_rate3: - description: 'The tax rate (float)' - type: number - example: '10' + description: "The tax rate (float)" + type: number + example: "10" tax_name3: - description: 'The tax name' - type: string - example: GST + description: "The tax name" + type: string + example: GST payment_type_id: - description: 'The default payment type id' - type: string - example: '1' + description: "The default payment type id" + type: string + example: "1" custom_fields: - description: 'JSON string of custom fields' - type: string - example: '{}' + description: "JSON string of custom fields" + type: string + example: "{}" email_footer: - description: 'The default email footer' - type: string - example: 'A default email footer' + description: "The default email footer" + type: string + example: "A default email footer" email_sending_method: - description: 'The email driver to use to send email, options include default, gmail, client_postmark, client_mailgun, office365' - type: string - example: default + description: "The email driver to use to send email, options include default, gmail, client_postmark, client_mailgun, client_brevo, office365" + type: string + example: default gmail_sending_user_id: - description: 'The hashed_id of the user account to send email from' - type: string - example: F76sd34D + description: "The hashed_id of the user account to send email from" + type: string + example: F76sd34D email_subject_invoice: - description: '' - type: string - example: 'Your Invoice Subject' + description: "" + type: string + example: "Your Invoice Subject" email_subject_quote: - description: '' - type: string - example: 'Your Quote Subject' + description: "" + type: string + example: "Your Quote Subject" email_subject_payment: - description: '' - type: string - example: 'Your Payment Subject' + description: "" + type: string + example: "Your Payment Subject" email_template_invoice: - description: 'The full template for invoice emails' - type: string - example: '' + description: "The full template for invoice emails" + type: string + example: "" email_template_quote: - description: 'The full template for quote emails' - type: string - example: '' + description: "The full template for quote emails" + type: string + example: "" email_template_payment: - description: 'The full template for payment emails' - type: string - example: '' + description: "The full template for payment emails" + type: string + example: "" email_subject_reminder1: - description: 'Email subject for Reminder' - type: string - example: '' + description: "Email subject for Reminder" + type: string + example: "" email_subject_reminder2: - description: 'Email subject for Reminder' - type: string - example: '' + description: "Email subject for Reminder" + type: string + example: "" email_subject_reminder3: - description: 'Email subject for Reminder' - type: string - example: '' + description: "Email subject for Reminder" + type: string + example: "" email_subject_reminder_endless: - description: 'Email subject for endless reminders' - type: string - example: '' + description: "Email subject for endless reminders" + type: string + example: "" email_template_reminder1: - description: 'The full template for Reminder 1' - type: string - example: '' + description: "The full template for Reminder 1" + type: string + example: "" email_template_reminder2: - description: 'The full template for Reminder 2' - type: string - example: '' + description: "The full template for Reminder 2" + type: string + example: "" email_template_reminder3: - description: 'The full template for Reminder 3' - type: string - example: '' + description: "The full template for Reminder 3" + type: string + example: "" email_template_reminder_endless: - description: 'The full template for enless reminders' - type: string - example: '' + description: "The full template for enless reminders" + type: string + example: "" enable_portal_password: - description: 'Toggles whether a password is required to log into the client portal' - type: boolean - example: true + description: "Toggles whether a password is required to log into the client portal" + type: boolean + example: true show_accept_invoice_terms: - description: 'Toggles whether the terms dialogue is shown to the client' - type: boolean - example: true + description: "Toggles whether the terms dialogue is shown to the client" + type: boolean + example: true show_accept_quote_terms: - description: 'Toggles whether the terms dialogue is shown to the client' - type: boolean - example: true + description: "Toggles whether the terms dialogue is shown to the client" + type: boolean + example: true require_invoice_signature: - description: 'Toggles whether a invoice signature is required' - type: boolean - example: true + description: "Toggles whether a invoice signature is required" + type: boolean + example: true require_quote_signature: - description: 'Toggles whether a quote signature is required' - type: boolean - example: true + description: "Toggles whether a quote signature is required" + type: boolean + example: true name: - description: 'The company name' - type: string - example: 'Acme Co' + description: "The company name" + type: string + example: "Acme Co" company_logo: - description: 'The company logo file' - type: object - example: logo.png + description: "The company logo file" + type: object + example: logo.png website: - description: 'The company website URL' - type: string - example: www.acme.com + description: "The company website URL" + type: string + example: www.acme.com address1: - description: 'The company address line 1' - type: string - example: 'Suite 888' + description: "The company address line 1" + type: string + example: "Suite 888" address2: - description: 'The company address line 2' - type: string - example: '5 Jimbo Way' + description: "The company address line 2" + type: string + example: "5 Jimbo Way" city: - description: 'The company city' - type: string - example: Sydney + description: "The company city" + type: string + example: Sydney state: - description: 'The company state' - type: string - example: Florisa + description: "The company state" + type: string + example: Florisa postal_code: - description: 'The company zip/postal code' - type: string - example: '90210' + description: "The company zip/postal code" + type: string + example: "90210" phone: - description: 'The company phone' - type: string - example: 555-213-3948 + description: "The company phone" + type: string + example: 555-213-3948 email: - description: 'The company email' - type: string - example: joe@acme.co + description: "The company email" + type: string + example: joe@acme.co country_id: - description: 'The country ID' - type: string - example: '1' + description: "The country ID" + type: string + example: "1" vat_number: - description: 'The company VAT/TAX ID number' - type: string - example: '32 120 377 720' + description: "The company VAT/TAX ID number" + type: string + example: "32 120 377 720" page_size: - description: 'The default page size' - type: string - example: A4 + description: "The default page size" + type: string + example: A4 font_size: - description: 'The font size' - type: number - example: '9' + description: "The font size" + type: number + example: "9" primary_font: - description: 'The primary font' - type: string - example: roboto + description: "The primary font" + type: string + example: roboto secondary_font: - description: 'The secondary font' - type: string - example: roboto + description: "The secondary font" + type: string + example: roboto hide_paid_to_date: - description: 'Flags whether to hide the paid to date field' - type: boolean - example: false + description: "Flags whether to hide the paid to date field" + type: boolean + example: false embed_documents: - description: 'Toggled whether to embed documents in the PDF' - type: boolean - example: false + description: "Toggled whether to embed documents in the PDF" + type: boolean + example: false all_pages_header: - description: 'The header for the PDF' - type: boolean - example: false + description: "The header for the PDF" + type: boolean + example: false all_pages_footer: - description: 'The footer for the PDF' - type: boolean - example: false + description: "The footer for the PDF" + type: boolean + example: false document_email_attachment: - description: 'Toggles whether to attach documents in the email' - type: boolean - example: false + description: "Toggles whether to attach documents in the email" + type: boolean + example: false enable_client_portal_password: - description: 'Toggles password protection of the client portal' - type: boolean - example: false + description: "Toggles password protection of the client portal" + type: boolean + example: false enable_email_markup: - description: 'Toggles the use of markdown in emails' - type: boolean - example: false + description: "Toggles the use of markdown in emails" + type: boolean + example: false enable_client_portal_dashboard: - description: 'Toggles whether the client dashboard is shown in the client portal' - type: boolean - example: false + description: "Toggles whether the client dashboard is shown in the client portal" + type: boolean + example: false enable_client_portal: - description: 'Toggles whether the entire client portal is displayed to the client, or only the context' - type: boolean - example: false + description: "Toggles whether the entire client portal is displayed to the client, or only the context" + type: boolean + example: false email_template_statement: - description: 'The body of the email for statements' - type: string - example: 'template matter' + description: "The body of the email for statements" + type: string + example: "template matter" email_subject_statement: - description: 'The subject of the email for statements' - type: string - example: 'subject matter' + description: "The subject of the email for statements" + type: string + example: "subject matter" signature_on_pdf: - description: 'Toggles whether the signature (if available) is displayed on the PDF' - type: boolean - example: false + description: "Toggles whether the signature (if available) is displayed on the PDF" + type: boolean + example: false quote_footer: - description: 'The default quote footer' - type: string - example: 'the quote footer' + description: "The default quote footer" + type: string + example: "the quote footer" email_subject_custom1: - description: 'Custom reminder template subject' - type: string - example: 'Custom Subject 1' + description: "Custom reminder template subject" + type: string + example: "Custom Subject 1" email_subject_custom2: - description: 'Custom reminder template subject' - type: string - example: 'Custom Subject 2' + description: "Custom reminder template subject" + type: string + example: "Custom Subject 2" email_subject_custom3: - description: 'Custom reminder template subject' - type: string - example: 'Custom Subject 3' + description: "Custom reminder template subject" + type: string + example: "Custom Subject 3" email_template_custom1: - description: 'Custom reminder template body' - type: string - example: '' + description: "Custom reminder template body" + type: string + example: "" email_template_custom2: - description: 'Custom reminder template body' - type: string - example: '' + description: "Custom reminder template body" + type: string + example: "" email_template_custom3: - description: 'Custom reminder template body' - type: string - example: '' + description: "Custom reminder template body" + type: string + example: "" enable_reminder1: - description: 'Toggles whether this reminder is enabled' - type: boolean - example: false + description: "Toggles whether this reminder is enabled" + type: boolean + example: false enable_reminder2: - description: 'Toggles whether this reminder is enabled' - type: boolean - example: false + description: "Toggles whether this reminder is enabled" + type: boolean + example: false enable_reminder3: - description: 'Toggles whether this reminder is enabled' - type: boolean - example: false + description: "Toggles whether this reminder is enabled" + type: boolean + example: false num_days_reminder1: - description: 'The Reminder interval' - type: number - example: '9' + description: "The Reminder interval" + type: number + example: "9" num_days_reminder2: - description: 'The Reminder interval' - type: number - example: '9' + description: "The Reminder interval" + type: number + example: "9" num_days_reminder3: - description: 'The Reminder interval' - type: number - example: '9' + description: "The Reminder interval" + type: number + example: "9" schedule_reminder1: - description: '(enum: after_invoice_date, before_due_date, after_due_date)' - type: string - example: after_invoice_date + description: "(enum: after_invoice_date, before_due_date, after_due_date)" + type: string + example: after_invoice_date schedule_reminder2: - description: '(enum: after_invoice_date, before_due_date, after_due_date)' - type: string - example: after_invoice_date + description: "(enum: after_invoice_date, before_due_date, after_due_date)" + type: string + example: after_invoice_date schedule_reminder3: - description: '(enum: after_invoice_date, before_due_date, after_due_date)' - type: string - example: after_invoice_date + description: "(enum: after_invoice_date, before_due_date, after_due_date)" + type: string + example: after_invoice_date late_fee_amount1: - description: 'The late fee amount for reminder 1' - type: number - example: 10 + description: "The late fee amount for reminder 1" + type: number + example: 10 late_fee_amount2: - description: 'The late fee amount for reminder 2' - type: number - example: 20 + description: "The late fee amount for reminder 2" + type: number + example: 20 late_fee_amount3: - description: 'The late fee amount for reminder 2' - type: number - example: 100 + description: "The late fee amount for reminder 2" + type: number + example: 100 endless_reminder_frequency_id: - description: 'The frequency id of the endless reminder' - type: string - example: '1' + description: "The frequency id of the endless reminder" + type: string + example: "1" client_online_payment_notification: - description: 'Determines if a client should receive the notification for a online payment' - type: boolean - example: false + description: "Determines if a client should receive the notification for a online payment" + type: boolean + example: false client_manual_payment_notification: - description: 'Determines if a client should receive the notification for a manually entered payment' - type: boolean - example: false + description: "Determines if a client should receive the notification for a manually entered payment" + type: boolean + example: false enable_e_invoice: - description: 'Determines if e-invoicing is enabled' - type: boolean - example: false + description: "Determines if e-invoicing is enabled" + type: boolean + example: false default_expense_payment_type_id: - description: 'The default payment type for expenses' - type: string - example: '0' + description: "The default payment type for expenses" + type: string + example: "0" e_invoice_type: - description: 'The e-invoice type' - type: string - example: 'EN16931' + description: "The e-invoice type" + type: string + example: "EN16931" mailgun_endpoint: - description: 'The mailgun endpoint - used to determine whether US or EU endpoints are used' - type: string - example: 'api.mailgun.net or api.eu.mailgun.net' + description: "The mailgun endpoint - used to determine whether US or EU endpoints are used" + type: string + example: "api.mailgun.net or api.eu.mailgun.net" client_initiated_payments: - description: 'Determines if clients can initiate payments directly from the client portal' - type: boolean - example: false + description: "Determines if clients can initiate payments directly from the client portal" + type: boolean + example: false client_initiated_payments_minimum: - description: 'The minimum amount a client can pay' - type: number - example: 10 + description: "The minimum amount a client can pay" + type: number + example: 10 sync_invoice_quote_columns: - description: 'Determines if invoice and quote columns are synced for the PDF rendering, or if they use their own columns' - type: boolean - example: false + description: "Determines if invoice and quote columns are synced for the PDF rendering, or if they use their own columns" + type: boolean + example: false show_task_item_description: - description: 'Determines if the task item description is shown on the invoice' - type: boolean - example: false + description: "Determines if the task item description is shown on the invoice" + type: boolean + example: false allow_billable_task_items: - description: 'Determines if task items can be marked as billable' - type: boolean - example: false + description: "Determines if task items can be marked as billable" + type: boolean + example: false accept_client_input_quote_approval: - description: 'Determines if clients can approve quotes and also pass through a PO Number reference' - type: boolean - example: false + description: "Determines if clients can approve quotes and also pass through a PO Number reference" + type: boolean + example: false custom_sending_email: - description: 'When using Mailgun or Postmark, the FROM email address can be customized using this setting.' - type: string - example: 'bob@gmail.com' + description: "When using Mailgun or Postmark, the FROM email address can be customized using this setting." + type: string + example: "bob@gmail.com" show_paid_stamp: - description: 'Determines if the PAID stamp is shown on the invoice' - type: boolean - example: false + description: "Determines if the PAID stamp is shown on the invoice" + type: boolean + example: false show_shipping_address: - description: 'Determines if the shipping address is shown on the invoice' - type: boolean - example: false + description: "Determines if the shipping address is shown on the invoice" + type: boolean + example: false company_logo_size: - description: 'The size of the company logo on the PDF - percentage value between 0 and 100' - type: number - example: 100 + description: "The size of the company logo on the PDF - percentage value between 0 and 100" + type: number + example: 100 show_email_footer: - description: 'Determines if the email footer is shown on emails' - type: boolean - example: false + description: "Determines if the email footer is shown on emails" + type: boolean + example: false email_alignment: - description: 'The alignment of the email body text, options include left / center / right' - type: string - example: 'left' + description: "The alignment of the email body text, options include left / center / right" + type: string + example: "left" auto_bill_standard_invoices: - description: 'Determines if standard invoices are automatically billed when they are created or due' - type: boolean - example: false + description: "Determines if standard invoices are automatically billed when they are created or due" + type: boolean + example: false postmark_secret: - description: 'The Postmark secret API key' - type: string - example: '123456' + description: "The Postmark secret API key" + type: string + example: "123456" mailgun_secret: - description: 'The Mailgun secret API key' - type: string - example: '123456' + description: "The Mailgun secret API key" + type: string + example: "123456" mailgun_domain: - description: 'The Mailgun domain' - type: string - example: 'sandbox123456.mailgun.org' + description: "The Mailgun domain" + type: string + example: "sandbox123456.mailgun.org" send_email_on_mark_paid: - description: 'Determines if an email is sent when an invoice is marked as paid' - type: boolean - example: false + description: "Determines if an email is sent when an invoice is marked as paid" + type: boolean + example: false vendor_portal_enable_uploads: - description: 'Determines if vendors can upload files to the portal' - type: boolean - example: false + description: "Determines if vendors can upload files to the portal" + type: boolean + example: false besr_id: - description: 'The BESR ID' - type: string - example: '123456' + description: "The BESR ID" + type: string + example: "123456" qr_iban: - description: 'The IBAN for the QR code' - type: string - example: 'CH123456' + description: "The IBAN for the QR code" + type: string + example: "CH123456" email_subject_purchase_order: - description: 'The email subject for purchase orders' - type: string - example: 'Purchase Order' + description: "The email subject for purchase orders" + type: string + example: "Purchase Order" email_template_purchase_order: - description: 'The email template for purchase orders' - type: string - example: 'Please see attached your purchase order.' + description: "The email template for purchase orders" + type: string + example: "Please see attached your purchase order." require_purchase_order_signature: - description: 'Determines if a signature is required on purchase orders' - type: boolean - example: false + description: "Determines if a signature is required on purchase orders" + type: boolean + example: false purchase_order_public_notes: - description: 'The public notes for purchase orders' - type: string - example: 'Please see attached your purchase order.' + description: "The public notes for purchase orders" + type: string + example: "Please see attached your purchase order." purchase_order_terms: - description: 'The terms for purchase orders' - type: string - example: 'Please see attached your purchase order.' + description: "The terms for purchase orders" + type: string + example: "Please see attached your purchase order." purchase_order_footer: - description: 'The footer for purchase orders' - type: string - example: 'Please see attached your purchase order.' + description: "The footer for purchase orders" + type: string + example: "Please see attached your purchase order." purchase_order_design_id: - description: 'The design id for purchase orders' - type: string - example: 'hd677df' + description: "The design id for purchase orders" + type: string + example: "hd677df" purchase_order_number_pattern: - description: 'The pattern for purchase order numbers' - type: string - example: 'PO-000000' + description: "The pattern for purchase order numbers" + type: string + example: "PO-000000" purchase_order_number_counter: - description: 'The counter for purchase order numbers' - type: number - example: 1 + description: "The counter for purchase order numbers" + type: number + example: 1 page_numbering_alignment: - description: 'The alignment for page numbering: options include left / center / right' - type: string - example: 'left' + description: "The alignment for page numbering: options include left / center / right" + type: string + example: "left" page_numbering: - description: 'Determines if page numbering is enabled on Document PDFs' - type: boolean - example: false + description: "Determines if page numbering is enabled on Document PDFs" + type: boolean + example: false auto_archive_invoice_cancelled: - description: 'Determines if invoices are automatically archived when they are cancelled' - type: boolean - example: false + description: "Determines if invoices are automatically archived when they are cancelled" + type: boolean + example: false email_from_name: - description: 'The FROM name for emails when using Custom emailers' - type: string - example: 'Bob Smith' + description: "The FROM name for emails when using Custom emailers" + type: string + example: "Bob Smith" show_all_tasks_client_portal: - description: 'Determines if all tasks are shown on the client portal' - type: boolean - example: false + description: "Determines if all tasks are shown on the client portal" + type: boolean + example: false entity_send_time: - description: 'The time that emails are sent. The time is localized to the clients locale, integer values from 1 - 24' - type: integer - example: 9 + description: "The time that emails are sent. The time is localized to the clients locale, integer values from 1 - 24" + type: integer + example: 9 shared_invoice_credit_counter: - description: 'Determines if the invoice and credit counter are shared' - type: boolean - example: false + description: "Determines if the invoice and credit counter are shared" + type: boolean + example: false reply_to_name: - description: 'The reply to name for emails' - type: string - example: 'Bob Smith' + description: "The reply to name for emails" + type: string + example: "Bob Smith" hide_empty_columns_on_pdf: - description: 'Determines if empty columns are hidden on PDFs' - type: boolean - example: false + description: "Determines if empty columns are hidden on PDFs" + type: boolean + example: false enable_reminder_endless: - description: 'Determines if endless reminders are enabled' - type: boolean - example: false + description: "Determines if endless reminders are enabled" + type: boolean + example: false use_credits_payment: - description: 'Determines if credits can be used as a payment method' - type: boolean - example: false + description: "Determines if credits can be used as a payment method" + type: boolean + example: false recurring_invoice_number_pattern: - description: 'The pattern for recurring invoice numbers' - type: string - example: 'R-000000' + description: "The pattern for recurring invoice numbers" + type: string + example: "R-000000" recurring_invoice_number_counter: - description: 'The counter for recurring invoice numbers' - type: number - example: 1 + description: "The counter for recurring invoice numbers" + type: number + example: 1 client_portal_under_payment_minimum: - description: 'The minimum payment payment' - type: number - example: 10 + description: "The minimum payment payment" + type: number + example: 10 auto_bill_date: - description: 'Determines when the invoices are auto billed, options are on_send_date (when the invoice is sent) or on_due_date (when the invoice is due))' - type: string - example: 'on_send_date' + description: "Determines when the invoices are auto billed, options are on_send_date (when the invoice is sent) or on_due_date (when the invoice is due))" + type: string + example: "on_send_date" primary_color: - description: 'The primary color for the client portal / document highlights' - type: string - example: '#ffffff' + description: "The primary color for the client portal / document highlights" + type: string + example: "#ffffff" secondary_color: - description: 'The secondary color for the client portal / document highlights' - type: string - example: '#ffffff' + description: "The secondary color for the client portal / document highlights" + type: string + example: "#ffffff" client_portal_allow_under_payment: - description: 'Determines if clients can pay invoices under the invoice amount due' - type: boolean - example: false + description: "Determines if clients can pay invoices under the invoice amount due" + type: boolean + example: false client_portal_allow_over_payment: - description: 'Determines if clients can pay invoices over the invoice amount' - type: boolean - example: false + description: "Determines if clients can pay invoices over the invoice amount" + type: boolean + example: false auto_bill: - description: 'Determines how autobilling is applied for recurring invoices. off (no auto billed), always (always auto bill), optin (The user must opt in to auto billing), optout (The user must opt out of auto billing' - type: string - example: 'off' + description: "Determines how autobilling is applied for recurring invoices. off (no auto billed), always (always auto bill), optin (The user must opt in to auto billing), optout (The user must opt out of auto billing" + type: string + example: "off" client_portal_terms: - description: 'The terms which are displayed on the client portal' - type: string - example: 'Please see attached your invoice.' + description: "The terms which are displayed on the client portal" + type: string + example: "Please see attached your invoice." client_portal_privacy_policy: - description: 'The privacy policy which is displayed on the client portal' - type: string - example: 'These are the terms of use for using the client portal.' + description: "The privacy policy which is displayed on the client portal" + type: string + example: "These are the terms of use for using the client portal." client_can_register: - description: 'Determines if clients can register on the client portal' - type: boolean - example: false + description: "Determines if clients can register on the client portal" + type: boolean + example: false portal_design_id: - description: 'The design id for the client portal' - type: string - example: 'hd677df' + description: "The design id for the client portal" + type: string + example: "hd677df" late_fee_endless_percent: - description: 'The late fee percentage for endless late fees' - type: number - example: 10 + description: "The late fee percentage for endless late fees" + type: number + example: 10 late_fee_endless_amount: - description: 'The late fee amount for endless late fees' - type: number - example: 10 + description: "The late fee amount for endless late fees" + type: number + example: 10 auto_email_invoice: - description: 'Determines if invoices are automatically emailed when they are created' - type: boolean - example: false + description: "Determines if invoices are automatically emailed when they are created" + type: boolean + example: false email_signature: - description: 'The email signature for emails' - type: string - example: 'Bob Smith' + description: "The email signature for emails" + type: string + example: "Bob Smith" classification: - description: 'The classification for the company' - type: string - example: 'individual' - type: object \ No newline at end of file + description: "The classification for the company" + type: string + example: "individual" + type: object diff --git a/package-lock.json b/package-lock.json index f1466debb888..1e4593041b24 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "@invoiceninja/invoiceninja", + "name": "invoiceninja", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/resources/views/portal/ninja2020/components/livewire/pdf-slot.blade.php b/resources/views/portal/ninja2020/components/livewire/pdf-slot.blade.php index 5159e3314c1e..314f69cca8ee 100644 --- a/resources/views/portal/ninja2020/components/livewire/pdf-slot.blade.php +++ b/resources/views/portal/ninja2020/components/livewire/pdf-slot.blade.php @@ -20,6 +20,40 @@ @endif + @if($entity_type == 'credit' && $settings->enable_e_invoice) + + @endif + @if($entity_type == 'quote' && $settings->enable_e_invoice) + + @endif +{{-- Not implemented yet--}} +{{-- @if($entity_type == 'purchase_order' && $settings->enable_e_invoice) + + @endif--}} @if($html_entity_option)