diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index d662e4d2da..ad1109a26b 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -13,7 +13,7 @@ jobs: strategy: matrix: operating-system: ['ubuntu-20.04', 'ubuntu-22.04'] - php-versions: ['8.1.11'] + php-versions: ['8.1'] phpunit-versions: ['latest'] env: diff --git a/VERSION.txt b/VERSION.txt index fb0a65a36d..aa3706ff60 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.5.46 \ No newline at end of file +5.5.47 \ No newline at end of file diff --git a/app/Filters/BankTransactionFilters.php b/app/Filters/BankTransactionFilters.php index e7cff05fee..8bcfc55f15 100644 --- a/app/Filters/BankTransactionFilters.php +++ b/app/Filters/BankTransactionFilters.php @@ -152,7 +152,22 @@ class BankTransactionFilters extends QueryFilters public function sort(string $sort) : Builder { $sort_col = explode('|', $sort); + + if(!is_array($sort_col)) + return $this->builder; + if($sort_col[0] == 'deposit') + return $this->builder->where('base_type', 'CREDIT')->orderBy('amount', $sort_col[1]); + + if($sort_col[0] == 'withdrawal') + return $this->builder->where('base_type', 'DEBIT')->orderBy('amount', $sort_col[1]); + + if($sort_col[0] == 'status') + $sort_col[0] = 'status_id'; + + if(in_array($sort_col[0],['invoices','expense'])) + return $this->builder; + return $this->builder->orderBy($sort_col[0], $sort_col[1]); } diff --git a/app/Http/Livewire/BillingPortalPurchasev2.php b/app/Http/Livewire/BillingPortalPurchasev2.php index 79bf70f3bd..dd8e89b7a4 100644 --- a/app/Http/Livewire/BillingPortalPurchasev2.php +++ b/app/Http/Livewire/BillingPortalPurchasev2.php @@ -11,6 +11,7 @@ namespace App\Http\Livewire; +use App\DataMapper\ClientSettings; use App\Factory\ClientFactory; use App\Jobs\Mail\NinjaMailerJob; use App\Jobs\Mail\NinjaMailerObject; @@ -19,15 +20,17 @@ use App\Mail\ContactPasswordlessLogin; use App\Models\Client; use App\Models\ClientContact; use App\Models\Invoice; +use App\Models\RecurringInvoice; use App\Models\Subscription; use App\Repositories\ClientContactRepository; use App\Repositories\ClientRepository; +use App\Utils\Number; use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Validator; use Illuminate\Support\Str; -use App\DataMapper\ClientSettings; use Livewire\Component; class BillingPortalPurchasev2 extends Component @@ -179,6 +182,13 @@ class BillingPortalPurchasev2 extends Component */ public $campaign; + public $bundle; + public $recurring_products; + public $products; + public $optional_recurring_products; + public $optional_products; + public $total; + public function mount() { MultiDB::setDb($this->company->db); @@ -196,46 +206,156 @@ class BillingPortalPurchasev2 extends Component elseif(strlen($this->subscription->promo_code) == 0 && $this->subscription->promo_discount > 0){ $this->price = $this->subscription->promo_price; } + + $this->recurring_products = $this->subscription->service()->recurring_products(); + $this->products = $this->subscription->service()->products(); + $this->optional_recurring_products = $this->subscription->service()->optional_recurring_products(); + $this->optional_products = $this->subscription->service()->optional_products(); + + // $this->buildBundle(); + $this->bundle = collect(); + } - public function updatingData() + public function buildBundle() { - nlog('updating'); - // nlog($this->data); + $this->bundle = collect(); + + $data = $this->data; + + foreach($this->recurring_products as $key => $p) + { + + $qty = isset($data[$key]['recurring_qty']) ? $data[$key]['recurring_qty'] : 1; + $total = $p->price * $qty; + + $this->bundle->push([ + 'product' => nl2br(substr($p->notes, 0, 50)), + 'price' => Number::formatMoney($total, $this->subscription->company).' / '. RecurringInvoice::frequencyForKey($this->subscription->frequency_id), + 'total' => $total, + 'qty' => $qty, + 'is_recurring' => true + ]); + } + + foreach($this->products as $key => $p) + { + + $qty = 1; + $total = $p->price * $qty; + + $this->bundle->push([ + 'product' => nl2br(substr($p->notes, 0, 50)), + 'price' => Number::formatMoney($total, $this->subscription->company), + 'total' => $total, + 'qty' => $qty, + 'is_recurring' => false + ]); + } + + foreach($this->data as $key => $value) + { + if(isset($this->data[$key]['optional_recurring_qty'])) + { + $p = $this->optional_recurring_products->first(function ($v,$k) use($key){ + return $k == $key; + }); + + $qty = isset($this->data[$key]['optional_recurring_qty']) ? $this->data[$key]['optional_recurring_qty'] : 0; + $total = $p->price * $qty; + + if($qty == 0) + return; + + + $this->bundle->push([ + 'product' => nl2br(substr($p->notes, 0, 50)), + 'price' => Number::formatMoney($total, $this->subscription->company).' / '. RecurringInvoice::frequencyForKey($this->subscription->frequency_id), + 'total' => $total, + 'qty' => $qty, + 'is_recurring' => true + ]); + + } + + if(isset($this->data[$key]['optional_qty'])) + { + $p = $this->optional_products->first(function ($v,$k) use($key){ + return $k == $key; + }); + + $qty = isset($this->data[$key]['optional_qty']) ? $this->data[$key]['optional_qty'] : 0; + $total = $p->price * $qty; + + if($qty == 0) + return; + + $this->bundle->push([ + 'product' => nl2br(substr($p->notes, 0, 50)), + 'price' => Number::formatMoney($total, $this->subscription->company), + 'total' => $total, + 'qty' => $qty, + 'is_recurring' => false + ]); + + } + + + } + + $this->total = Number::formatMoney($this->bundle->sum('total'), $this->subscription->company); + + return $this; } public function updatedData() { - nlog('updated'); - nlog($this->data); - $validatedData = $this->validate(); - nlog( $validatedData ); + } + + public function updating($prop) + { + // $this->resetValidation($prop); + // $this->resetErrorBag($prop); } public function updated($propertyName) { - nlog("validating {$propertyName}"); - $this->errors = $this->validateOnly($propertyName); + $x = $this->validateOnly($propertyName, $this->rules(), [], $this->attributes()); - nlog($this->errors); - $validatedData = $this->validate(); - nlog( $validatedData ); + // // $validatedData = $this->validate(); + $this->buildBundle(); + + // $order_validator = Validator::make($this->all(), $this->rules(), [], $this->attributes()); } public function rules() { - $rules = [ - 'email' => ['required', 'email'], - 'data' => ['required', 'array'], - 'data.*.recurring_qty' => ['required', 'between:100,1000'], - 'data.*.optional_recurring_qty' => ['required', 'between:100,1000'], - 'data.*.optional_qty' => ['required', 'between:100,1000'], + $rules = [ + 'data.*.recurring_qty' => 'numeric|between:0,1000', + 'data.*.optional_recurring_qty' => 'numeric|between:0,1000', + 'data.*.optional_qty' => 'numeric|between:0,1000', ]; return $rules; } + public function attributes() + { + $attributes = [ + 'data.*.recurring_qty' => 'recurring_qty', + 'data.*.optional_recurring_qty' => 'optional_recurring_qty', + 'data.*.optional_qty' => 'optional_qty', + ]; + + return $attributes; + } + + public function store() + { + + } + /** * Handle user authentication * diff --git a/app/Jobs/Bank/MatchBankTransactions.php b/app/Jobs/Bank/MatchBankTransactions.php index e52cc7f39a..bc6473d0d8 100644 --- a/app/Jobs/Bank/MatchBankTransactions.php +++ b/app/Jobs/Bank/MatchBankTransactions.php @@ -389,6 +389,7 @@ class MatchBankTransactions implements ShouldQueue $this->bt->invoice_ids = collect($invoices)->pluck('hashed_id')->implode(','); $this->bt->status_id = BankTransaction::STATUS_CONVERTED; + $this->bt->payment_id = $payment->id; $this->bt->save(); } diff --git a/app/Listeners/Quote/QuoteViewedActivity.php b/app/Listeners/Quote/QuoteViewedActivity.php index 0a25130e68..777f86bfbb 100644 --- a/app/Listeners/Quote/QuoteViewedActivity.php +++ b/app/Listeners/Quote/QuoteViewedActivity.php @@ -50,7 +50,7 @@ class QuoteViewedActivity implements ShouldQueue $fields->user_id = $event->invitation->quote->user_id; $fields->company_id = $event->invitation->company_id; $fields->activity_type_id = Activity::VIEW_QUOTE; - $fields->client_id = $event->invitation->client_id; + $fields->client_id = $event->invitation->quote->client_id; $fields->client_contact_id = $event->invitation->client_contact_id; $fields->invitation_id = $event->invitation->id; $fields->quote_id = $event->invitation->quote_id; diff --git a/app/Models/Company.php b/app/Models/Company.php index 37d4814e4d..230b603992 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -126,6 +126,8 @@ class Company extends BaseModel 'report_include_deleted', 'invoice_task_lock', 'use_vendor_currency', + 'convert_payment_currency', + 'convert_expense_currency', ]; protected $hidden = [ diff --git a/app/PaymentDrivers/Stripe/Jobs/PaymentIntentWebhook.php b/app/PaymentDrivers/Stripe/Jobs/PaymentIntentWebhook.php index d3a8ad40e7..e6971ae79a 100644 --- a/app/PaymentDrivers/Stripe/Jobs/PaymentIntentWebhook.php +++ b/app/PaymentDrivers/Stripe/Jobs/PaymentIntentWebhook.php @@ -154,31 +154,6 @@ class PaymentIntentWebhook implements ShouldQueue 'card_details' => isset($charge['payment_method_details']['card']['brand']) ? $charge['payment_method_details']['card']['brand'] : PaymentType::CREDIT_CARD_OTHER ]; - if(isset($pi['allowed_source_types']) && in_array('card', $pi['allowed_source_types'])) - { - - $invoice = Invoice::with('client')->find($payment_hash->fee_invoice_id); - $client = $invoice->client; - - $this->updateCreditCardPayment($payment_hash, $client, $meta); - } - elseif(isset($pi['payment_method_types']) && in_array('card', $pi['payment_method_types'])) - { - - $invoice = Invoice::with('client')->find($payment_hash->fee_invoice_id); - $client = $invoice->client; - - $this->updateCreditCardPayment($payment_hash, $client, $meta); - } - elseif(isset($pi['payment_method_types']) && in_array('us_bank_account', $pi['payment_method_types'])) - { - - $invoice = Invoice::with('client')->find($payment_hash->fee_invoice_id); - $client = $invoice->client; - - $this->updateAchPayment($payment_hash, $client, $meta); - } - SystemLogger::dispatch( ['response' => $this->stripe_request, 'data' => []], SystemLog::CATEGORY_GATEWAY_RESPONSE, @@ -188,6 +163,39 @@ class PaymentIntentWebhook implements ShouldQueue $company, ); + if(isset($pi['allowed_source_types']) && in_array('card', $pi['allowed_source_types'])) + { + + $invoice = Invoice::with('client')->withTrashed()->find($payment_hash->fee_invoice_id); + $client = $invoice->client; + + if($invoice->is_deleted) + return; + + $this->updateCreditCardPayment($payment_hash, $client, $meta); + } + elseif(isset($pi['payment_method_types']) && in_array('card', $pi['payment_method_types'])) + { + + $invoice = Invoice::with('client')->withTrashed()->find($payment_hash->fee_invoice_id); + $client = $invoice->client; + + if($invoice->is_deleted) + return; + + $this->updateCreditCardPayment($payment_hash, $client, $meta); + } + elseif(isset($pi['payment_method_types']) && in_array('us_bank_account', $pi['payment_method_types'])) + { + + $invoice = Invoice::with('client')->withTrashed()->find($payment_hash->fee_invoice_id); + $client = $invoice->client; + + if($invoice->is_deleted) + return; + + $this->updateAchPayment($payment_hash, $client, $meta); + } } diff --git a/app/Transformers/CompanyTransformer.php b/app/Transformers/CompanyTransformer.php index 1b47cdbd5b..b2732d87af 100644 --- a/app/Transformers/CompanyTransformer.php +++ b/app/Transformers/CompanyTransformer.php @@ -190,6 +190,8 @@ class CompanyTransformer extends EntityTransformer 'report_include_deleted' => (bool) $company->report_include_deleted, 'invoice_task_lock' => (bool) $company->invoice_task_lock, 'use_vendor_currency' => (bool) $company->use_vendor_currency, + 'convert_payment_currency' => (bool) $company->convert_payment_currency, + 'convert_expense_currency' => (bool) $company->convert_expense_currency, ]; } diff --git a/app/Utils/Traits/GeneratesCounter.php b/app/Utils/Traits/GeneratesCounter.php index 604caa1174..1ea15dde0c 100644 --- a/app/Utils/Traits/GeneratesCounter.php +++ b/app/Utils/Traits/GeneratesCounter.php @@ -60,7 +60,7 @@ trait GeneratesCounter $counter_entity = $client; } elseif ((strpos($pattern, 'groupCounter') !== false) || (strpos($pattern, 'group_counter') !== false)) { - if (property_exists($client->group_settings, $counter_string)) { + if (property_exists($client, 'group_settings') && property_exists($client->group_settings, $counter_string)) { $counter = $client->group_settings->{$counter_string}; } else { $counter = 1; @@ -751,7 +751,7 @@ trait GeneratesCounter $replace[] = $client->number; $search[] = '{$client_id_number}'; - $replace[] = $client->id_number; + $replace[] = $client->id_number ?: $client->number; $search[] = '{$clientIdNumber}'; $replace[] = $client->id_number ?: $client->number; diff --git a/config/ninja.php b/config/ninja.php index ac016b05b7..8c09c1c8ec 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -14,8 +14,8 @@ return [ 'require_https' => env('REQUIRE_HTTPS', true), 'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'), - 'app_version' => '5.5.46', - 'app_tag' => '5.5.46', + 'app_version' => '5.5.47', + 'app_tag' => '5.5.47', 'minimum_client_version' => '5.0.16', 'terms_version' => '1.0.1', 'api_secret' => env('API_SECRET', ''), diff --git a/database/migrations/2022_12_07_024625_add_properties_to_companies_table.php b/database/migrations/2022_12_07_024625_add_properties_to_companies_table.php new file mode 100644 index 0000000000..78336ab3e2 --- /dev/null +++ b/database/migrations/2022_12_07_024625_add_properties_to_companies_table.php @@ -0,0 +1,31 @@ +boolean('convert_payment_currency')->default(false); + $table->boolean('convert_expense_currency')->default(false); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + + } +}; diff --git a/resources/views/portal/ninja2020/components/livewire/billing-portal-purchasev2.blade.php b/resources/views/portal/ninja2020/components/livewire/billing-portal-purchasev2.blade.php index 5300edf2ba..07d2096e31 100644 --- a/resources/views/portal/ninja2020/components/livewire/billing-portal-purchasev2.blade.php +++ b/resources/views/portal/ninja2020/components/livewire/billing-portal-purchasev2.blade.php @@ -7,10 +7,12 @@ {{ $subscription->name }} +
+