diff --git a/app/Http/Controllers/LicenseController.php b/app/Http/Controllers/LicenseController.php
index 88e04574d928..851f5281b418 100644
--- a/app/Http/Controllers/LicenseController.php
+++ b/app/Http/Controllers/LicenseController.php
@@ -11,11 +11,13 @@
namespace App\Http\Controllers;
+use stdClass;
+use Carbon\Carbon;
use App\Models\Account;
use App\Utils\CurlUtils;
-use Carbon\Carbon;
use Illuminate\Http\Response;
-use stdClass;
+use Illuminate\Support\Facades\Http;
+use Illuminate\Http\Request;
class LicenseController extends BaseController
{
@@ -78,7 +80,7 @@ class LicenseController extends BaseController
* ),
* )
*/
- public function index()
+ public function indexx()
{
$this->checkLicense();
@@ -147,6 +149,58 @@ class LicenseController extends BaseController
return response()->json($error, 400);
}
+ public function v5ClaimLicense(Request $request)
+ {
+ $this->checkLicense();
+
+ /* Catch claim license requests */
+ if (config('ninja.environment') == 'selfhost' && request()->has('license_key')) {
+
+ // $response = Http::get( "http://ninja.test:8000/claim_license", [
+ $response = Http::get( "https://invoicing.co/claim_license", [
+ 'license_key' => $request->input('license_key'),
+ 'product_id' => 3,
+ ]);
+
+ if($response->successful()) {
+
+ $payload = $response->json();
+
+ $account = auth()->user()->account;
+
+ $account->plan_term = Account::PLAN_TERM_YEARLY;
+ $account->plan_expires = Carbon::parse($payload['expires'])->addYear()->format('Y-m-d');
+ $account->plan = Account::PLAN_WHITE_LABEL;
+ $account->save();
+
+ $error = [
+ 'message' => trans('texts.bought_white_label'),
+ 'errors' => new \stdClass,
+ ];
+
+ return response()->json($error, 200);
+ }else {
+
+ $error = [
+ 'message' => trans('texts.white_label_license_error'),
+ 'errors' => new stdClass,
+ ];
+
+ return response()->json($error, 400);
+ }
+
+ }
+
+ $error = [
+ 'message' => ctrans('texts.invoice_license_or_environment', ['environment' => config('ninja.environment')]),
+ 'errors' => new stdClass,
+ ];
+
+ return response()->json($error, 400);
+
+ }
+
+
private function checkLicense()
{
$account = auth()->user()->account;
@@ -156,5 +210,6 @@ class LicenseController extends BaseController
$account->plan_expires = null;
$account->save();
}
+
}
}
diff --git a/app/Http/Livewire/BillingPortalPurchase.php b/app/Http/Livewire/BillingPortalPurchase.php
index 8b7f79a5628f..b0df3019ca18 100644
--- a/app/Http/Livewire/BillingPortalPurchase.php
+++ b/app/Http/Livewire/BillingPortalPurchase.php
@@ -11,22 +11,24 @@
namespace App\Http\Livewire;
-use App\DataMapper\ClientSettings;
+use App\Utils\Ninja;
+use App\Models\Client;
+use App\Models\Invoice;
+use Livewire\Component;
+use App\Libraries\MultiDB;
+use Illuminate\Support\Str;
+use App\Models\Subscription;
+use App\Models\ClientContact;
use App\Factory\ClientFactory;
use App\Jobs\Mail\NinjaMailerJob;
+use App\DataMapper\ClientSettings;
use App\Jobs\Mail\NinjaMailerObject;
-use App\Libraries\MultiDB;
-use App\Mail\ContactPasswordlessLogin;
-use App\Models\Client;
-use App\Models\ClientContact;
-use App\Models\Invoice;
-use App\Models\Subscription;
-use App\Repositories\ClientContactRepository;
-use App\Repositories\ClientRepository;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
-use Illuminate\Support\Str;
-use Livewire\Component;
+use App\Mail\ContactPasswordlessLogin;
+use App\Repositories\ClientRepository;
+use App\Repositories\ClientContactRepository;
+use App\Services\Subscription\SubscriptionService;
class BillingPortalPurchase extends Component
{
@@ -168,7 +170,7 @@ class BillingPortalPurchase extends Component
/**
* Instance of company.
*
- * @var Company
+ * @var \App\Models\Company
*/
public $company;
@@ -396,12 +398,19 @@ class BillingPortalPurchase extends Component
->adjustInventory()
->save();
+ $context = 'purchase';
+
+ // if(Ninja::isHosted() && $this->subscription->service()->recurring_products()->first()?->id == SubscriptionService::WHITE_LABEL) {
+ if(Ninja::isHosted() && $this->subscription->service()->recurring_products()->first()?->product_key == 'whitelabel') {
+ $context = 'whitelabel';
+ }
+
Cache::put($this->hash, [
'subscription_id' => $this->subscription->hashed_id,
'email' => $this->email ?? $this->contact->email,
'client_id' => $this->contact->client->hashed_id,
'invoice_id' => $this->invoice->hashed_id,
- 'context' => 'purchase',
+ 'context' => $context,
'campaign' => $this->campaign,
], now()->addMinutes(60));
diff --git a/app/Http/Requests/Payment/StorePaymentRequest.php b/app/Http/Requests/Payment/StorePaymentRequest.php
index f31e1cf8d3ff..d796a082454d 100644
--- a/app/Http/Requests/Payment/StorePaymentRequest.php
+++ b/app/Http/Requests/Payment/StorePaymentRequest.php
@@ -70,7 +70,6 @@ class StorePaymentRequest extends Request
if (isset($input['credits']) && is_array($input['credits']) !== false) {
foreach ($input['credits'] as $key => $value) {
if (array_key_exists('credit_id', $input['credits'][$key])) {
- // $input['credits'][$key]['credit_id'] = $value['credit_id'];
$input['credits'][$key]['credit_id'] = $this->decodePrimaryKey($value['credit_id']);
$credits_total += $value['amount'];
diff --git a/app/Http/Requests/Payment/UpdatePaymentRequest.php b/app/Http/Requests/Payment/UpdatePaymentRequest.php
index 8f24eb3f6518..1d87ef164542 100644
--- a/app/Http/Requests/Payment/UpdatePaymentRequest.php
+++ b/app/Http/Requests/Payment/UpdatePaymentRequest.php
@@ -36,7 +36,7 @@ class UpdatePaymentRequest extends Request
public function rules()
{
$rules = [
- 'invoices' => ['array', new PaymentAppliedValidAmount, new ValidCreditsPresentRule($this->all())],
+ 'invoices' => ['array', new PaymentAppliedValidAmount($this->all()), new ValidCreditsPresentRule($this->all())],
'invoices.*.invoice_id' => 'distinct',
];
@@ -87,6 +87,7 @@ class UpdatePaymentRequest extends Request
}
}
}
+
$this->replace($input);
}
diff --git a/app/Http/Requests/Request.php b/app/Http/Requests/Request.php
index 5553feddf077..789d723938c7 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|max:20000';
+ protected $file_validation = 'sometimes|file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx,webp|max:20000';
/**
* Get the validation rules that apply to the request.
*
diff --git a/app/Http/ValidationRules/PaymentAppliedValidAmount.php b/app/Http/ValidationRules/PaymentAppliedValidAmount.php
index 332bcc97e210..88432bd8f2a8 100644
--- a/app/Http/ValidationRules/PaymentAppliedValidAmount.php
+++ b/app/Http/ValidationRules/PaymentAppliedValidAmount.php
@@ -11,6 +11,7 @@
namespace App\Http\ValidationRules;
+use App\Models\Invoice;
use App\Models\Payment;
use App\Utils\Traits\MakesHash;
use Illuminate\Contracts\Validation\Rule;
@@ -22,6 +23,12 @@ class PaymentAppliedValidAmount implements Rule
{
use MakesHash;
+ private $message;
+
+ public function __construct(private array $input)
+ {
+ $this->input = $input;
+ }
/**
* @param string $attribute
* @param mixed $value
@@ -29,6 +36,8 @@ class PaymentAppliedValidAmount implements Rule
*/
public function passes($attribute, $value)
{
+ $this->message = ctrans('texts.insufficient_applied_amount_remaining');
+
return $this->calculateAmounts();
}
@@ -37,13 +46,14 @@ class PaymentAppliedValidAmount implements Rule
*/
public function message()
{
- return ctrans('texts.insufficient_applied_amount_remaining');
+ return $this->message;
}
private function calculateAmounts() :bool
{
$payment = Payment::withTrashed()->whereId($this->decodePrimaryKey(request()->segment(4)))->company()->first();
-
+ $inv_collection = Invoice::withTrashed()->whereIn('id', array_column($this->input['invoices'], 'invoice_id'))->get();
+
if (! $payment) {
return false;
}
@@ -68,13 +78,26 @@ class PaymentAppliedValidAmount implements Rule
}
}
- if (request()->input('invoices') && is_array(request()->input('invoices'))) {
- foreach (request()->input('invoices') as $invoice) {
+ if (isset($this->input['invoices']) && is_array($this->input['invoices'])) {
+ foreach ($this->input['invoices'] as $invoice) {
$invoice_amounts += $invoice['amount'];
+
+ $inv = $inv_collection->firstWhere('id', $invoice['invoice_id']);
+
+ if($inv->balance < $invoice['amount']) {
+
+ $this->message = 'Amount cannot be greater than invoice balance';
+
+ return false;
+ }
+
}
}
- // nlog("{round($payment_amounts,3)} >= {round($invoice_amounts,3)}");
- return round($payment_amounts, 3) >= round($invoice_amounts, 3);
+ if(round($payment_amounts, 3) >= round($invoice_amounts, 3)) {
+ return true;
+ }
+
+ return false;
}
}
diff --git a/app/Listeners/Payment/PaymentBalanceActivity.php b/app/Listeners/Payment/PaymentBalanceActivity.php
new file mode 100644
index 000000000000..23d021e1c12c
--- /dev/null
+++ b/app/Listeners/Payment/PaymentBalanceActivity.php
@@ -0,0 +1,49 @@
+company->db);
+
+ $event->payment->client->service()->updatePaymentBalance();
+
+ }
+
+ public function middleware($event): array
+ {
+ return [(new WithoutOverlapping($event->payment->client->id))];
+ }
+}
diff --git a/app/Listeners/Payment/PaymentEmailFailureActivity.php b/app/Listeners/Payment/PaymentEmailFailureActivity.php
index c478d013ccdb..bcf9ca95983c 100644
--- a/app/Listeners/Payment/PaymentEmailFailureActivity.php
+++ b/app/Listeners/Payment/PaymentEmailFailureActivity.php
@@ -19,8 +19,6 @@ class PaymentEmailFailureActivity implements ShouldQueue
{
use UserNotifies;
- public $delay = 5;
-
/**
* Create the event listener.
*
diff --git a/app/Listeners/Payment/PaymentEmailedActivity.php b/app/Listeners/Payment/PaymentEmailedActivity.php
index 52ee83b3feaa..af6701c4d486 100644
--- a/app/Listeners/Payment/PaymentEmailedActivity.php
+++ b/app/Listeners/Payment/PaymentEmailedActivity.php
@@ -17,7 +17,6 @@ use Illuminate\Contracts\Queue\ShouldQueue;
class PaymentEmailedActivity implements ShouldQueue
{
- public $delay = 5;
use UserNotifies;
diff --git a/app/Models/Payment.php b/app/Models/Payment.php
index 1a6e79830707..b6f8296a4c3d 100644
--- a/app/Models/Payment.php
+++ b/app/Models/Payment.php
@@ -299,11 +299,14 @@ class Payment extends BaseModel
public function translatedType()
{
- if (! $this->type) {
+ if (! $this->type_id) {
return '';
}
- return ctrans('texts.payment_type_'.$this->type->name);
+ $pt = new PaymentType();
+
+ return $pt->name($this->type_id);
+
}
public function gateway_type()
diff --git a/app/Models/PaymentType.php b/app/Models/PaymentType.php
index 298d17e53e33..9170b3d53d21 100644
--- a/app/Models/PaymentType.php
+++ b/app/Models/PaymentType.php
@@ -74,6 +74,48 @@ class PaymentType extends StaticModel
const KLARNA = 47;
const Interac_E_Transfer = 48;
+ public array $type_names = [
+ self::CREDIT => 'payment_type_Credit',
+ self::ACH => 'payment_type_ACH',
+ self::VISA => 'payment_type_Visa Card',
+ self::MASTERCARD => 'payment_type_MasterCard',
+ self::AMERICAN_EXPRESS => 'payment_type_American Express',
+ self::DISCOVER => 'payment_type_Discover Card',
+ self::DINERS => 'payment_type_Diners Card',
+ self::EUROCARD => 'payment_type_EuroCard',
+ self::NOVA => 'payment_type_Nova',
+ self::CREDIT_CARD_OTHER => 'payment_type_Credit Card Other',
+ self::PAYPAL => 'payment_type_PayPal',
+ self::CHECK => 'payment_type_Check',
+ self::CARTE_BLANCHE => 'payment_type_Carte Blanche',
+ self::UNIONPAY => 'payment_type_UnionPay',
+ self::JCB => 'payment_type_JCB',
+ self::LASER => 'payment_type_Laser',
+ self::MAESTRO => 'payment_type_Maestro',
+ self::SOLO => 'payment_type_Solo',
+ self::SWITCH => 'payment_type_Switch',
+ self::ALIPAY => 'payment_type_Alipay',
+ self::SOFORT => 'payment_type_Sofort',
+ self::SEPA => 'payment_type_SEPA',
+ self::GOCARDLESS => 'payment_type_GoCardless',
+ self::CRYPTO => 'payment_type_Crypto',
+ self::MOLLIE_BANK_TRANSFER => 'payment_type_Mollie Bank Transfer',
+ self::KBC => 'payment_type_KBC/CBC',
+ self::BANCONTACT => 'payment_type_Bancontact',
+ self::IDEAL => 'payment_type_iDEAL',
+ self::HOSTED_PAGE => 'payment_type_Hosted Page',
+ self::GIROPAY => 'payment_type_GiroPay',
+ self::PRZELEWY24 => 'payment_type_Przelewy24',
+ self::EPS => 'payment_type_EPS',
+ self::DIRECT_DEBIT => 'payment_type_Direct Debit',
+ self::BECS => 'payment_type_BECS',
+ self::ACSS => 'payment_type_ACSS',
+ self::INSTANT_BANK_PAY => 'payment_type_Instant Bank Pay',
+ self::FPX => 'fpx',
+ self::KLARNA => 'payment_type_Klarna',
+ self::Interac_E_Transfer => 'payment_type_Interac E Transfer',
+ ];
+
public static function parseCardType($cardName)
{
$cardTypes = [
@@ -106,4 +148,12 @@ class PaymentType extends StaticModel
return self::CREDIT_CARD_OTHER;
}
}
+
+ public function name($id)
+ {
+ if(isset($this->type_names[$id]))
+ return ctrans("texts.".$this->type_names[$id]);
+
+ return ctrans('texts.manual_entry');
+ }
}
diff --git a/app/Models/User.php b/app/Models/User.php
index 55065731c875..fc57697d5e10 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -182,8 +182,8 @@ class User extends Authenticatable implements MustVerifyEmail
'accepted_terms_version',
'oauth_user_id',
'oauth_provider_id',
- // 'oauth_user_token',
- // 'oauth_user_refresh_token',
+ 'oauth_user_token',
+ 'oauth_user_refresh_token',
'custom_value1',
'custom_value2',
'custom_value3',
diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php
index a9434ea7f937..a672bafc963b 100644
--- a/app/Providers/EventServiceProvider.php
+++ b/app/Providers/EventServiceProvider.php
@@ -11,259 +11,258 @@
namespace App\Providers;
-use App\Events\Account\AccountCreated;
-use App\Events\Client\ClientWasArchived;
-use App\Events\Client\ClientWasCreated;
-use App\Events\Client\ClientWasDeleted;
-use App\Events\Client\ClientWasRestored;
-use App\Events\Client\ClientWasUpdated;
-use App\Events\Client\DesignWasDeleted;
-use App\Events\Client\DesignWasRestored;
-use App\Events\Client\DesignWasUpdated;
-use App\Events\Company\CompanyDocumentsDeleted;
-use App\Events\Contact\ContactLoggedIn;
-use App\Events\Credit\CreditWasArchived;
-use App\Events\Credit\CreditWasCreated;
-use App\Events\Credit\CreditWasDeleted;
-use App\Events\Credit\CreditWasEmailed;
-use App\Events\Credit\CreditWasEmailedAndFailed;
-use App\Events\Credit\CreditWasMarkedSent;
-use App\Events\Credit\CreditWasRestored;
-use App\Events\Credit\CreditWasUpdated;
-use App\Events\Credit\CreditWasViewed;
-use App\Events\Design\DesignWasArchived;
-use App\Events\Expense\ExpenseWasArchived;
-use App\Events\Expense\ExpenseWasCreated;
-use App\Events\Expense\ExpenseWasDeleted;
-use App\Events\Expense\ExpenseWasRestored;
-use App\Events\Expense\ExpenseWasUpdated;
-use App\Events\Invoice\InvoiceReminderWasEmailed;
-use App\Events\Invoice\InvoiceWasArchived;
-use App\Events\Invoice\InvoiceWasCancelled;
-use App\Events\Invoice\InvoiceWasCreated;
-use App\Events\Invoice\InvoiceWasDeleted;
-use App\Events\Invoice\InvoiceWasEmailed;
-use App\Events\Invoice\InvoiceWasEmailedAndFailed;
-use App\Events\Invoice\InvoiceWasMarkedSent;
-use App\Events\Invoice\InvoiceWasPaid;
-use App\Events\Invoice\InvoiceWasRestored;
-use App\Events\Invoice\InvoiceWasReversed;
-use App\Events\Invoice\InvoiceWasUpdated;
-use App\Events\Invoice\InvoiceWasViewed;
-use App\Events\Misc\InvitationWasViewed;
-use App\Events\Payment\PaymentWasArchived;
-use App\Events\Payment\PaymentWasCreated;
-use App\Events\Payment\PaymentWasDeleted;
-use App\Events\Payment\PaymentWasEmailed;
-use App\Events\Payment\PaymentWasEmailedAndFailed;
-use App\Events\Payment\PaymentWasRefunded;
-use App\Events\Payment\PaymentWasRestored;
-use App\Events\Payment\PaymentWasUpdated;
-use App\Events\Payment\PaymentWasVoided;
-use App\Events\PurchaseOrder\PurchaseOrderWasAccepted;
-use App\Events\PurchaseOrder\PurchaseOrderWasArchived;
-use App\Events\PurchaseOrder\PurchaseOrderWasCreated;
-use App\Events\PurchaseOrder\PurchaseOrderWasDeleted;
-use App\Events\PurchaseOrder\PurchaseOrderWasEmailed;
-use App\Events\PurchaseOrder\PurchaseOrderWasRestored;
-use App\Events\PurchaseOrder\PurchaseOrderWasUpdated;
-use App\Events\PurchaseOrder\PurchaseOrderWasViewed;
-use App\Events\Quote\QuoteWasApproved;
-use App\Events\Quote\QuoteWasArchived;
-use App\Events\Quote\QuoteWasCreated;
-use App\Events\Quote\QuoteWasDeleted;
-use App\Events\Quote\QuoteWasEmailed;
-use App\Events\Quote\QuoteWasRestored;
-use App\Events\Quote\QuoteWasUpdated;
-use App\Events\Quote\QuoteWasViewed;
-use App\Events\RecurringExpense\RecurringExpenseWasArchived;
-use App\Events\RecurringExpense\RecurringExpenseWasCreated;
-use App\Events\RecurringExpense\RecurringExpenseWasDeleted;
-use App\Events\RecurringExpense\RecurringExpenseWasRestored;
-use App\Events\RecurringExpense\RecurringExpenseWasUpdated;
-use App\Events\RecurringInvoice\RecurringInvoiceWasArchived;
-use App\Events\RecurringInvoice\RecurringInvoiceWasCreated;
-use App\Events\RecurringInvoice\RecurringInvoiceWasDeleted;
-use App\Events\RecurringInvoice\RecurringInvoiceWasRestored;
-use App\Events\RecurringInvoice\RecurringInvoiceWasUpdated;
-use App\Events\RecurringQuote\RecurringQuoteWasArchived;
-use App\Events\RecurringQuote\RecurringQuoteWasCreated;
-use App\Events\RecurringQuote\RecurringQuoteWasDeleted;
-use App\Events\RecurringQuote\RecurringQuoteWasRestored;
-use App\Events\RecurringQuote\RecurringQuoteWasUpdated;
-use App\Events\Subscription\SubscriptionWasArchived;
-use App\Events\Subscription\SubscriptionWasCreated;
-use App\Events\Subscription\SubscriptionWasDeleted;
-use App\Events\Subscription\SubscriptionWasRestored;
-use App\Events\Subscription\SubscriptionWasUpdated;
-use App\Events\Task\TaskWasArchived;
-use App\Events\Task\TaskWasCreated;
-use App\Events\Task\TaskWasDeleted;
-use App\Events\Task\TaskWasRestored;
-use App\Events\Task\TaskWasUpdated;
-use App\Events\User\UserLoggedIn;
-use App\Events\User\UserWasArchived;
-use App\Events\User\UserWasCreated;
-use App\Events\User\UserWasDeleted;
-use App\Events\User\UserWasRestored;
-use App\Events\User\UserWasUpdated;
-use App\Events\Vendor\VendorWasArchived;
-use App\Events\Vendor\VendorWasCreated;
-use App\Events\Vendor\VendorWasDeleted;
-use App\Events\Vendor\VendorWasRestored;
-use App\Events\Vendor\VendorWasUpdated;
-use App\Listeners\Activity\ArchivedClientActivity;
-use App\Listeners\Activity\ClientUpdatedActivity;
-use App\Listeners\Activity\CreatedClientActivity;
-use App\Listeners\Activity\CreatedCreditActivity;
-use App\Listeners\Activity\CreatedExpenseActivity;
-use App\Listeners\Activity\CreatedQuoteActivity;
-use App\Listeners\Activity\CreatedSubscriptionActivity;
-use App\Listeners\Activity\CreatedTaskActivity;
-use App\Listeners\Activity\CreatedVendorActivity;
-use App\Listeners\Activity\CreditArchivedActivity;
-use App\Listeners\Activity\DeleteClientActivity;
-use App\Listeners\Activity\DeleteCreditActivity;
-use App\Listeners\Activity\ExpenseArchivedActivity;
-use App\Listeners\Activity\ExpenseDeletedActivity;
-use App\Listeners\Activity\ExpenseRestoredActivity;
-use App\Listeners\Activity\ExpenseUpdatedActivity;
-use App\Listeners\Activity\PaymentArchivedActivity;
-use App\Listeners\Activity\PaymentCreatedActivity;
-use App\Listeners\Activity\PaymentDeletedActivity;
-use App\Listeners\Activity\PaymentRefundedActivity;
-use App\Listeners\Activity\PaymentUpdatedActivity;
-use App\Listeners\Activity\PaymentVoidedActivity;
-use App\Listeners\Activity\QuoteUpdatedActivity;
-use App\Listeners\Activity\RestoreClientActivity;
-use App\Listeners\Activity\SubscriptionArchivedActivity;
-use App\Listeners\Activity\SubscriptionDeletedActivity;
-use App\Listeners\Activity\SubscriptionRestoredActivity;
-use App\Listeners\Activity\SubscriptionUpdatedActivity;
-use App\Listeners\Activity\TaskArchivedActivity;
-use App\Listeners\Activity\TaskDeletedActivity;
-use App\Listeners\Activity\TaskRestoredActivity;
-use App\Listeners\Activity\TaskUpdatedActivity;
-use App\Listeners\Activity\UpdatedCreditActivity;
-use App\Listeners\Activity\VendorArchivedActivity;
-use App\Listeners\Activity\VendorDeletedActivity;
-use App\Listeners\Activity\VendorRestoredActivity;
-use App\Listeners\Activity\VendorUpdatedActivity;
-use App\Listeners\Contact\UpdateContactLastLogin;
-use App\Listeners\Credit\CreditCreatedNotification;
-use App\Listeners\Credit\CreditEmailedNotification;
-use App\Listeners\Credit\CreditRestoredActivity;
-use App\Listeners\Credit\CreditViewedActivity;
-use App\Listeners\Document\DeleteCompanyDocuments;
-use App\Listeners\Invoice\CreateInvoiceActivity;
-use App\Listeners\Invoice\CreateInvoicePdf;
-use App\Listeners\Invoice\InvoiceArchivedActivity;
-use App\Listeners\Invoice\InvoiceCancelledActivity;
-use App\Listeners\Invoice\InvoiceCreatedNotification;
-use App\Listeners\Invoice\InvoiceDeletedActivity;
-use App\Listeners\Invoice\InvoiceEmailActivity;
-use App\Listeners\Invoice\InvoiceEmailedNotification;
-use App\Listeners\Invoice\InvoiceEmailFailedActivity;
-use App\Listeners\Invoice\InvoiceFailedEmailNotification;
-use App\Listeners\Invoice\InvoicePaidActivity;
-use App\Listeners\Invoice\InvoiceReminderEmailActivity;
-use App\Listeners\Invoice\InvoiceRestoredActivity;
-use App\Listeners\Invoice\InvoiceReversedActivity;
-use App\Listeners\Invoice\InvoiceViewedActivity;
-use App\Listeners\Invoice\UpdateInvoiceActivity;
-use App\Listeners\Mail\MailSentListener;
-use App\Listeners\Misc\InvitationViewedListener;
-use App\Listeners\Payment\PaymentEmailedActivity;
-use App\Listeners\Payment\PaymentEmailFailureActivity;
-use App\Listeners\Payment\PaymentNotification;
-use App\Listeners\Payment\PaymentRestoredActivity;
-use App\Listeners\PurchaseOrder\CreatePurchaseOrderActivity;
-use App\Listeners\PurchaseOrder\PurchaseOrderAcceptedActivity;
-use App\Listeners\PurchaseOrder\PurchaseOrderAcceptedListener;
-use App\Listeners\PurchaseOrder\PurchaseOrderArchivedActivity;
-use App\Listeners\PurchaseOrder\PurchaseOrderCreatedListener;
-use App\Listeners\PurchaseOrder\PurchaseOrderDeletedActivity;
-use App\Listeners\PurchaseOrder\PurchaseOrderEmailActivity;
-use App\Listeners\PurchaseOrder\PurchaseOrderEmailedNotification;
-use App\Listeners\PurchaseOrder\PurchaseOrderRestoredActivity;
-use App\Listeners\PurchaseOrder\PurchaseOrderViewedActivity;
-use App\Listeners\PurchaseOrder\UpdatePurchaseOrderActivity;
-use App\Listeners\Quote\QuoteApprovedActivity;
-use App\Listeners\Quote\QuoteApprovedNotification;
-use App\Listeners\Quote\QuoteApprovedWebhook;
-use App\Listeners\Quote\QuoteArchivedActivity;
-use App\Listeners\Quote\QuoteCreatedNotification;
-use App\Listeners\Quote\QuoteDeletedActivity;
-use App\Listeners\Quote\QuoteEmailActivity;
-use App\Listeners\Quote\QuoteEmailedNotification;
-use App\Listeners\Quote\QuoteRestoredActivity;
-use App\Listeners\Quote\QuoteViewedActivity;
-use App\Listeners\Quote\ReachWorkflowSettings;
-use App\Listeners\RecurringExpense\CreatedRecurringExpenseActivity;
-use App\Listeners\RecurringExpense\RecurringExpenseArchivedActivity;
-use App\Listeners\RecurringExpense\RecurringExpenseDeletedActivity;
-use App\Listeners\RecurringExpense\RecurringExpenseRestoredActivity;
-use App\Listeners\RecurringExpense\RecurringExpenseUpdatedActivity;
-use App\Listeners\RecurringInvoice\CreateRecurringInvoiceActivity;
-use App\Listeners\RecurringInvoice\RecurringInvoiceArchivedActivity;
-use App\Listeners\RecurringInvoice\RecurringInvoiceDeletedActivity;
-use App\Listeners\RecurringInvoice\RecurringInvoiceRestoredActivity;
-use App\Listeners\RecurringInvoice\UpdateRecurringInvoiceActivity;
-use App\Listeners\RecurringQuote\CreateRecurringQuoteActivity;
-use App\Listeners\RecurringQuote\RecurringQuoteArchivedActivity;
-use App\Listeners\RecurringQuote\RecurringQuoteDeletedActivity;
-use App\Listeners\RecurringQuote\RecurringQuoteRestoredActivity;
-use App\Listeners\RecurringQuote\UpdateRecurringQuoteActivity;
-use App\Listeners\SendVerificationNotification;
-use App\Listeners\User\ArchivedUserActivity;
-use App\Listeners\User\CreatedUserActivity;
-use App\Listeners\User\DeletedUserActivity;
-use App\Listeners\User\RestoredUserActivity;
-use App\Listeners\User\UpdatedUserActivity;
-use App\Listeners\User\UpdateUserLastLogin;
-use App\Models\Account;
+use App\Models\Task;
+use App\Models\User;
+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\Credit;
+use App\Models\Vendor;
+use App\Models\Account;
+use App\Models\Company;
use App\Models\Expense;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\Product;
use App\Models\Project;
use App\Models\Proposal;
-use App\Models\PurchaseOrder;
-use App\Models\Quote;
+use App\Models\CompanyToken;
use App\Models\Subscription;
-use App\Models\Task;
-use App\Models\User;
-use App\Models\Vendor;
+use App\Models\ClientContact;
+use App\Models\PurchaseOrder;
use App\Models\VendorContact;
-use App\Observers\AccountObserver;
-use App\Observers\ClientContactObserver;
+use App\Models\CompanyGateway;
+use App\Observers\TaskObserver;
+use App\Observers\UserObserver;
+use App\Observers\QuoteObserver;
+use App\Events\User\UserLoggedIn;
use App\Observers\ClientObserver;
-use App\Observers\CompanyGatewayObserver;
-use App\Observers\CompanyObserver;
-use App\Observers\CompanyTokenObserver;
use App\Observers\CreditObserver;
+use App\Observers\VendorObserver;
+use App\Observers\AccountObserver;
+use App\Observers\CompanyObserver;
use App\Observers\ExpenseObserver;
use App\Observers\InvoiceObserver;
use App\Observers\PaymentObserver;
use App\Observers\ProductObserver;
use App\Observers\ProjectObserver;
+use App\Events\Task\TaskWasCreated;
+use App\Events\Task\TaskWasDeleted;
+use App\Events\Task\TaskWasUpdated;
+use App\Events\User\UserWasCreated;
+use App\Events\User\UserWasDeleted;
+use App\Events\User\UserWasUpdated;
use App\Observers\ProposalObserver;
-use App\Observers\PurchaseOrderObserver;
-use App\Observers\QuoteObserver;
+use App\Events\Quote\QuoteWasViewed;
+use App\Events\Task\TaskWasArchived;
+use App\Events\Task\TaskWasRestored;
+use App\Events\User\UserWasArchived;
+use App\Events\User\UserWasRestored;
+use App\Events\Quote\QuoteWasCreated;
+use App\Events\Quote\QuoteWasDeleted;
+use App\Events\Quote\QuoteWasEmailed;
+use App\Events\Quote\QuoteWasUpdated;
+use App\Events\Account\AccountCreated;
+use App\Events\Credit\CreditWasViewed;
+use App\Events\Invoice\InvoiceWasPaid;
+use App\Events\Quote\QuoteWasApproved;
+use App\Events\Quote\QuoteWasArchived;
+use App\Events\Quote\QuoteWasRestored;
+use App\Events\Client\ClientWasCreated;
+use App\Events\Client\ClientWasDeleted;
+use App\Events\Client\ClientWasUpdated;
+use App\Events\Client\DesignWasDeleted;
+use App\Events\Client\DesignWasUpdated;
+use App\Events\Contact\ContactLoggedIn;
+use App\Events\Credit\CreditWasCreated;
+use App\Events\Credit\CreditWasDeleted;
+use App\Events\Credit\CreditWasEmailed;
+use App\Events\Credit\CreditWasUpdated;
+use App\Events\Vendor\VendorWasCreated;
+use App\Events\Vendor\VendorWasDeleted;
+use App\Events\Vendor\VendorWasUpdated;
+use App\Observers\CompanyTokenObserver;
use App\Observers\SubscriptionObserver;
-use App\Observers\TaskObserver;
-use App\Observers\UserObserver;
-use App\Observers\VendorContactObserver;
-use App\Observers\VendorObserver;
-use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
-use Illuminate\Mail\Events\MessageSending;
use Illuminate\Mail\Events\MessageSent;
+use App\Events\Client\ClientWasArchived;
+use App\Events\Client\ClientWasRestored;
+use App\Events\Client\DesignWasRestored;
+use App\Events\Credit\CreditWasArchived;
+use App\Events\Credit\CreditWasRestored;
+use App\Events\Design\DesignWasArchived;
+use App\Events\Invoice\InvoiceWasViewed;
+use App\Events\Misc\InvitationWasViewed;
+use App\Events\Payment\PaymentWasVoided;
+use App\Events\Vendor\VendorWasArchived;
+use App\Events\Vendor\VendorWasRestored;
+use App\Listeners\Mail\MailSentListener;
+use App\Observers\ClientContactObserver;
+use App\Observers\PurchaseOrderObserver;
+use App\Observers\VendorContactObserver;
+use App\Events\Expense\ExpenseWasCreated;
+use App\Events\Expense\ExpenseWasDeleted;
+use App\Events\Expense\ExpenseWasUpdated;
+use App\Events\Invoice\InvoiceWasCreated;
+use App\Events\Invoice\InvoiceWasDeleted;
+use App\Events\Invoice\InvoiceWasEmailed;
+use App\Events\Invoice\InvoiceWasUpdated;
+use App\Events\Payment\PaymentWasCreated;
+use App\Events\Payment\PaymentWasDeleted;
+use App\Events\Payment\PaymentWasEmailed;
+use App\Events\Payment\PaymentWasUpdated;
+use App\Observers\CompanyGatewayObserver;
+use App\Events\Credit\CreditWasMarkedSent;
+use App\Events\Expense\ExpenseWasArchived;
+use App\Events\Expense\ExpenseWasRestored;
+use App\Events\Invoice\InvoiceWasArchived;
+use App\Events\Invoice\InvoiceWasRestored;
+use App\Events\Invoice\InvoiceWasReversed;
+use App\Events\Payment\PaymentWasArchived;
+use App\Events\Payment\PaymentWasRefunded;
+use App\Events\Payment\PaymentWasRestored;
+use Illuminate\Mail\Events\MessageSending;
+use App\Events\Invoice\InvoiceWasCancelled;
+use App\Listeners\Invoice\CreateInvoicePdf;
+use App\Listeners\Quote\QuoteEmailActivity;
+use App\Listeners\User\CreatedUserActivity;
+use App\Listeners\User\DeletedUserActivity;
+use App\Listeners\User\UpdatedUserActivity;
+use App\Listeners\User\UpdateUserLastLogin;
+use App\Events\Invoice\InvoiceWasMarkedSent;
+use App\Listeners\Quote\QuoteViewedActivity;
+use App\Listeners\User\ArchivedUserActivity;
+use App\Listeners\User\RestoredUserActivity;
+use App\Listeners\Quote\QuoteApprovedWebhook;
+use App\Listeners\Quote\QuoteDeletedActivity;
+use App\Listeners\Credit\CreditViewedActivity;
+use App\Listeners\Invoice\InvoicePaidActivity;
+use App\Listeners\Payment\PaymentNotification;
+use App\Listeners\Quote\QuoteApprovedActivity;
+use App\Listeners\Quote\QuoteArchivedActivity;
+use App\Listeners\Quote\QuoteRestoredActivity;
+use App\Listeners\Quote\ReachWorkflowSettings;
+use App\Events\Company\CompanyDocumentsDeleted;
+use App\Listeners\Activity\CreatedTaskActivity;
+use App\Listeners\Activity\TaskDeletedActivity;
+use App\Listeners\Activity\TaskUpdatedActivity;
+use App\Listeners\Invoice\InvoiceEmailActivity;
+use App\Listeners\SendVerificationNotification;
+use App\Events\Credit\CreditWasEmailedAndFailed;
+use App\Listeners\Activity\CreatedQuoteActivity;
+use App\Listeners\Activity\DeleteClientActivity;
+use App\Listeners\Activity\DeleteCreditActivity;
+use App\Listeners\Activity\QuoteUpdatedActivity;
+use App\Listeners\Activity\TaskArchivedActivity;
+use App\Listeners\Activity\TaskRestoredActivity;
+use App\Listeners\Credit\CreditRestoredActivity;
+use App\Listeners\Invoice\CreateInvoiceActivity;
+use App\Listeners\Invoice\InvoiceViewedActivity;
+use App\Listeners\Invoice\UpdateInvoiceActivity;
+use App\Listeners\Misc\InvitationViewedListener;
+use App\Events\Invoice\InvoiceReminderWasEmailed;
+use App\Listeners\Activity\ClientUpdatedActivity;
+use App\Listeners\Activity\CreatedClientActivity;
+use App\Listeners\Activity\CreatedCreditActivity;
+use App\Listeners\Activity\CreatedVendorActivity;
+use App\Listeners\Activity\PaymentVoidedActivity;
+use App\Listeners\Activity\RestoreClientActivity;
+use App\Listeners\Activity\UpdatedCreditActivity;
+use App\Listeners\Activity\VendorDeletedActivity;
+use App\Listeners\Activity\VendorUpdatedActivity;
+use App\Listeners\Contact\UpdateContactLastLogin;
+use App\Listeners\Invoice\InvoiceDeletedActivity;
+use App\Listeners\Payment\PaymentBalanceActivity;
+use App\Listeners\Quote\QuoteCreatedNotification;
+use App\Listeners\Quote\QuoteEmailedNotification;
+use App\Events\Invoice\InvoiceWasEmailedAndFailed;
+use App\Events\Payment\PaymentWasEmailedAndFailed;
+use App\Listeners\Activity\ArchivedClientActivity;
+use App\Listeners\Activity\CreatedExpenseActivity;
+use App\Listeners\Activity\CreditArchivedActivity;
+use App\Listeners\Activity\ExpenseDeletedActivity;
+use App\Listeners\Activity\ExpenseUpdatedActivity;
+use App\Listeners\Activity\PaymentCreatedActivity;
+use App\Listeners\Activity\PaymentDeletedActivity;
+use App\Listeners\Activity\PaymentUpdatedActivity;
+use App\Listeners\Activity\VendorArchivedActivity;
+use App\Listeners\Activity\VendorRestoredActivity;
+use App\Listeners\Document\DeleteCompanyDocuments;
+use App\Listeners\Invoice\InvoiceArchivedActivity;
+use App\Listeners\Invoice\InvoiceRestoredActivity;
+use App\Listeners\Invoice\InvoiceReversedActivity;
+use App\Listeners\Payment\PaymentRestoredActivity;
+use App\Listeners\Quote\QuoteApprovedNotification;
+use App\Events\Subscription\SubscriptionWasCreated;
+use App\Events\Subscription\SubscriptionWasDeleted;
+use App\Events\Subscription\SubscriptionWasUpdated;
+use App\Listeners\Activity\ExpenseArchivedActivity;
+use App\Listeners\Activity\ExpenseRestoredActivity;
+use App\Listeners\Activity\PaymentArchivedActivity;
+use App\Listeners\Activity\PaymentRefundedActivity;
+use App\Listeners\Credit\CreditCreatedNotification;
+use App\Listeners\Credit\CreditEmailedNotification;
+use App\Listeners\Invoice\InvoiceCancelledActivity;
+use App\Events\PurchaseOrder\PurchaseOrderWasViewed;
+use App\Events\Subscription\SubscriptionWasArchived;
+use App\Events\Subscription\SubscriptionWasRestored;
+use App\Events\PurchaseOrder\PurchaseOrderWasCreated;
+use App\Events\PurchaseOrder\PurchaseOrderWasDeleted;
+use App\Events\PurchaseOrder\PurchaseOrderWasEmailed;
+use App\Events\PurchaseOrder\PurchaseOrderWasUpdated;
+use App\Listeners\Invoice\InvoiceCreatedNotification;
+use App\Listeners\Invoice\InvoiceEmailedNotification;
+use App\Listeners\Invoice\InvoiceEmailFailedActivity;
+use App\Events\PurchaseOrder\PurchaseOrderWasAccepted;
+use App\Events\PurchaseOrder\PurchaseOrderWasArchived;
+use App\Events\PurchaseOrder\PurchaseOrderWasRestored;
+use App\Events\RecurringQuote\RecurringQuoteWasCreated;
+use App\Events\RecurringQuote\RecurringQuoteWasDeleted;
+use App\Events\RecurringQuote\RecurringQuoteWasUpdated;
+use App\Listeners\Activity\CreatedSubscriptionActivity;
+use App\Listeners\Activity\SubscriptionDeletedActivity;
+use App\Listeners\Activity\SubscriptionUpdatedActivity;
+use App\Listeners\Invoice\InvoiceReminderEmailActivity;
+use App\Events\RecurringQuote\RecurringQuoteWasArchived;
+use App\Events\RecurringQuote\RecurringQuoteWasRestored;
+use App\Listeners\Activity\SubscriptionArchivedActivity;
+use App\Listeners\Activity\SubscriptionRestoredActivity;
+use App\Listeners\Invoice\InvoiceFailedEmailNotification;
+use App\Events\RecurringExpense\RecurringExpenseWasCreated;
+use App\Events\RecurringExpense\RecurringExpenseWasDeleted;
+use App\Events\RecurringExpense\RecurringExpenseWasUpdated;
+use App\Events\RecurringInvoice\RecurringInvoiceWasCreated;
+use App\Events\RecurringInvoice\RecurringInvoiceWasDeleted;
+use App\Events\RecurringInvoice\RecurringInvoiceWasUpdated;
+use App\Listeners\PurchaseOrder\PurchaseOrderEmailActivity;
+use App\Events\RecurringExpense\RecurringExpenseWasArchived;
+use App\Events\RecurringExpense\RecurringExpenseWasRestored;
+use App\Events\RecurringInvoice\RecurringInvoiceWasArchived;
+use App\Events\RecurringInvoice\RecurringInvoiceWasRestored;
+use App\Listeners\PurchaseOrder\CreatePurchaseOrderActivity;
+use App\Listeners\PurchaseOrder\PurchaseOrderViewedActivity;
+use App\Listeners\PurchaseOrder\UpdatePurchaseOrderActivity;
+use App\Listeners\PurchaseOrder\PurchaseOrderCreatedListener;
+use App\Listeners\PurchaseOrder\PurchaseOrderDeletedActivity;
+use App\Listeners\PurchaseOrder\PurchaseOrderAcceptedActivity;
+use App\Listeners\PurchaseOrder\PurchaseOrderAcceptedListener;
+use App\Listeners\PurchaseOrder\PurchaseOrderArchivedActivity;
+use App\Listeners\PurchaseOrder\PurchaseOrderRestoredActivity;
+use App\Listeners\RecurringQuote\CreateRecurringQuoteActivity;
+use App\Listeners\RecurringQuote\UpdateRecurringQuoteActivity;
+use App\Listeners\RecurringQuote\RecurringQuoteDeletedActivity;
+use App\Listeners\RecurringQuote\RecurringQuoteArchivedActivity;
+use App\Listeners\RecurringQuote\RecurringQuoteRestoredActivity;
+use App\Listeners\PurchaseOrder\PurchaseOrderEmailedNotification;
+use App\Listeners\RecurringInvoice\CreateRecurringInvoiceActivity;
+use App\Listeners\RecurringInvoice\UpdateRecurringInvoiceActivity;
+use App\Listeners\RecurringExpense\CreatedRecurringExpenseActivity;
+use App\Listeners\RecurringExpense\RecurringExpenseDeletedActivity;
+use App\Listeners\RecurringExpense\RecurringExpenseUpdatedActivity;
+use App\Listeners\RecurringInvoice\RecurringInvoiceDeletedActivity;
+use App\Listeners\RecurringExpense\RecurringExpenseArchivedActivity;
+use App\Listeners\RecurringExpense\RecurringExpenseRestoredActivity;
+use App\Listeners\RecurringInvoice\RecurringInvoiceArchivedActivity;
+use App\Listeners\RecurringInvoice\RecurringInvoiceRestoredActivity;
+use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider
{
@@ -305,24 +304,30 @@ class EventServiceProvider extends ServiceProvider
PaymentWasCreated::class => [
PaymentCreatedActivity::class,
PaymentNotification::class,
+ PaymentBalanceActivity::class,
],
PaymentWasDeleted::class => [
PaymentDeletedActivity::class,
+ PaymentBalanceActivity::class,
],
PaymentWasArchived::class => [
PaymentArchivedActivity::class,
],
PaymentWasUpdated::class => [
PaymentUpdatedActivity::class,
+ PaymentBalanceActivity::class,
],
PaymentWasRefunded::class => [
PaymentRefundedActivity::class,
+ PaymentBalanceActivity::class,
],
PaymentWasVoided::class => [
PaymentVoidedActivity::class,
+ PaymentBalanceActivity::class,
],
PaymentWasRestored::class => [
PaymentRestoredActivity::class,
+ PaymentBalanceActivity::class,
],
// Clients
ClientWasCreated::class => [
diff --git a/app/Services/Client/ClientService.php b/app/Services/Client/ClientService.php
index 5a88bf755bc0..dd5d944285e5 100644
--- a/app/Services/Client/ClientService.php
+++ b/app/Services/Client/ClientService.php
@@ -14,6 +14,7 @@ namespace App\Services\Client;
use App\Utils\Number;
use App\Models\Client;
use App\Models\Credit;
+use App\Models\Payment;
use App\Services\Email\Email;
use App\Utils\Traits\MakesDates;
use Illuminate\Support\Facades\DB;
@@ -75,6 +76,23 @@ class ClientService
return $this;
}
+ public function updatePaymentBalance()
+ {
+ $amount = Payment::where('client_id', $this->client->id)
+ ->where('is_deleted', 0)
+ ->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment::STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])
+ ->sum(DB::Raw('amount - refunded - applied'));
+
+ DB::connection(config('database.default'))->transaction(function () use ($amount) {
+ $this->client = Client::withTrashed()->where('id', $this->client->id)->lockForUpdate()->first();
+ $this->client->payment_balance = $amount;
+ $this->client->saveQuietly();
+ }, 2);
+
+ return $this;
+ }
+
+
public function adjustCreditBalance(float $amount)
{
$this->client->credit_balance += $amount;
diff --git a/app/Services/Payment/DeletePayment.php b/app/Services/Payment/DeletePayment.php
index b327287fcb60..eec109408172 100644
--- a/app/Services/Payment/DeletePayment.php
+++ b/app/Services/Payment/DeletePayment.php
@@ -74,9 +74,11 @@ class DeletePayment
if ($this->payment->invoices()->exists()) {
$this->payment->invoices()->each(function ($paymentable_invoice) {
+
$net_deletable = $paymentable_invoice->pivot->amount - $paymentable_invoice->pivot->refunded;
$this->_paid_to_date_deleted += $net_deletable;
+ $paymentable_invoice = $paymentable_invoice->fresh();
nlog("net deletable amount - refunded = {$net_deletable}");
@@ -92,17 +94,18 @@ class DeletePayment
->updateInvoiceBalance($net_deletable, "Adjusting invoice {$paymentable_invoice->number} due to deletion of Payment {$this->payment->number}")
->save();
- $client = $this->payment
- ->client
- ->service()
- ->updateBalanceAndPaidToDate($net_deletable, $net_deletable*-1)
- ->save();
+ $this->payment
+ ->client
+ ->service()
+ ->updateBalanceAndPaidToDate($net_deletable, $net_deletable*-1)
+ ->save();
if ($paymentable_invoice->balance == $paymentable_invoice->amount) {
$paymentable_invoice->service()->setStatus(Invoice::STATUS_SENT)->save();
} else {
$paymentable_invoice->service()->setStatus(Invoice::STATUS_PARTIAL)->save();
}
+
} else {
$paymentable_invoice->restore();
$paymentable_invoice->service()
diff --git a/app/Services/Pdf/PdfBuilder.php b/app/Services/Pdf/PdfBuilder.php
index c57fef889f33..eb2feebc3bf7 100644
--- a/app/Services/Pdf/PdfBuilder.php
+++ b/app/Services/Pdf/PdfBuilder.php
@@ -244,7 +244,7 @@ class PdfBuilder
$element['elements'][] = ['element' => 'td', 'content' => $invoice->number];
$element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($payment->date, $this->service->config->date_format, $this->service->config->locale) ?: ' '];
- $element['elements'][] = ['element' => 'td', 'content' => $payment->type ? $payment->type->name : ctrans('texts.manual_entry')];
+ $element['elements'][] = ['element' => 'td', 'content' => $payment->translatedType()];
$element['elements'][] = ['element' => 'td', 'content' => $this->service->config->formatMoney($payment->pivot->amount) ?: ' '];
$tbody[] = $element;
@@ -279,7 +279,7 @@ class PdfBuilder
return [
['element' => 'p', 'content' => \sprintf('%s: %s', ctrans('texts.amount_paid'), $this->service->config->formatMoney($this->payment_amount_total))],
- ['element' => 'p', 'content' => \sprintf('%s: %s', ctrans('texts.payment_method'), $payment->type ? $payment->type->name : ctrans('texts.manual_entry'))],
+ ['element' => 'p', 'content' => \sprintf('%s: %s', ctrans('texts.payment_method'), $payment->translatedType())],
['element' => 'p', 'content' => \sprintf('%s: %s', ctrans('texts.payment_date'), $this->translateDate($payment->date, $this->service->config->date_format, $this->service->config->locale) ?: ' ')],
];
}
diff --git a/app/Services/PdfMaker/Design.php b/app/Services/PdfMaker/Design.php
index a2d4927a6ebd..7bc942eb51b5 100644
--- a/app/Services/PdfMaker/Design.php
+++ b/app/Services/PdfMaker/Design.php
@@ -601,7 +601,7 @@ class Design extends BaseDesign
$element['elements'][] = ['element' => 'td', 'content' => $invoice->number];
$element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($payment->date, $this->client->date_format(), $this->client->locale()) ?: ' '];
- $element['elements'][] = ['element' => 'td', 'content' => $payment->type ? $payment->type->name : ctrans('texts.manual_entry')];
+ $element['elements'][] = ['element' => 'td', 'content' => $payment->translatedType()];
$element['elements'][] = ['element' => 'td', 'content' => Number::formatMoney($payment->pivot->amount, $this->client) ?: ' '];
$tbody[] = $element;
diff --git a/app/Services/Subscription/SubscriptionService.php b/app/Services/Subscription/SubscriptionService.php
index c0a6292b9bd3..2bc4060aadb0 100644
--- a/app/Services/Subscription/SubscriptionService.php
+++ b/app/Services/Subscription/SubscriptionService.php
@@ -58,7 +58,7 @@ class SubscriptionService
/** @var subscription */
private $subscription;
- private const WHITE_LABEL = 4316;
+ public const WHITE_LABEL = 4316;
private float $credit_payments = 0;
@@ -169,9 +169,24 @@ class SubscriptionService
//send license to the user.
$invoice = $payment_hash->fee_invoice;
$license_key = Str::uuid()->toString();
- $invoice->public_notes = $license_key;
- $invoice->save();
+ $invoice->footer = ctrans('texts.white_label_body',['license_key' => $license_key]);
+
+ $recurring_invoice = $this->convertInvoiceToRecurring($payment_hash->payment->client_id);
+
+ $recurring_invoice_repo = new RecurringInvoiceRepository();
+ $recurring_invoice = $recurring_invoice_repo->save([], $recurring_invoice);
+ $recurring_invoice->auto_bill = $this->subscription->auto_bill;
+
+ /* Start the recurring service */
+ $recurring_invoice->service()
+ ->start()
+ ->save();
+
+ //update the invoice and attach to the recurring invoice!!!!!
+ $invoice->recurring_id = $recurring_invoice->id;
+ $invoice->is_proforma = false;
$invoice->service()->touchPdf();
+ $invoice->save();
$contact = $invoice->client->contacts()->whereNotNull('email')->first();
@@ -183,16 +198,20 @@ class SubscriptionService
$license->is_claimed = 1;
$license->transaction_reference = $payment_hash?->payment?->transaction_reference ?: ' ';
$license->product_id = self::WHITE_LABEL;
+ $license->recurring_invoice_id = $recurring_invoice->id;
$license->save();
+ $invitation = $invoice->invitations()->first();
+
$email_object = new EmailObject;
- $email_object->to = $contact->email;
+ $email_object->to = [$contact->email];
$email_object->subject = ctrans('texts.white_label_link') . " " .ctrans('texts.payment_subject');
$email_object->body = ctrans('texts.white_label_body',['license_key' => $license_key]);
$email_object->client_id = $invoice->client_id;
$email_object->client_contact_id = $contact->id;
- $email_object->invitation_key = $invoice->invitations()->first()->invitation_key;
+ $email_object->invitation_key = $invitation->invitation_key;
+ $email_object->invitation_id = $invitation->id;
$email_object->entity_id = $invoice->id;
$email_object->entity_class = Invoice::class;
$email_object->user_id = $invoice->user_id;
diff --git a/composer.lock b/composer.lock
index d2bf544f4253..c450d418938f 100644
--- a/composer.lock
+++ b/composer.lock
@@ -380,16 +380,16 @@
},
{
"name": "aws/aws-sdk-php",
- "version": "3.261.6",
+ "version": "3.261.8",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
- "reference": "63b03ec821473861af58439f1a35173e904d0f8c"
+ "reference": "9c38c82b7d3fb2b15957e71cae3957450e131ed8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/63b03ec821473861af58439f1a35173e904d0f8c",
- "reference": "63b03ec821473861af58439f1a35173e904d0f8c",
+ "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/9c38c82b7d3fb2b15957e71cae3957450e131ed8",
+ "reference": "9c38c82b7d3fb2b15957e71cae3957450e131ed8",
"shasum": ""
},
"require": {
@@ -468,9 +468,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.261.6"
+ "source": "https://github.com/aws/aws-sdk-php/tree/3.261.8"
},
- "time": "2023-03-07T19:21:12+00:00"
+ "time": "2023-03-09T19:23:27+00:00"
},
{
"name": "bacon/bacon-qr-code",
@@ -2171,16 +2171,16 @@
},
{
"name": "google/apiclient-services",
- "version": "v0.289.1",
+ "version": "v0.289.0",
"source": {
"type": "git",
"url": "https://github.com/googleapis/google-api-php-client-services.git",
- "reference": "df7e6cbab08f60509b3f360d8286c194ad2930e2"
+ "reference": "937f83a927db2d09db7eebb69ce2ac4114559bd7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/df7e6cbab08f60509b3f360d8286c194ad2930e2",
- "reference": "df7e6cbab08f60509b3f360d8286c194ad2930e2",
+ "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/937f83a927db2d09db7eebb69ce2ac4114559bd7",
+ "reference": "937f83a927db2d09db7eebb69ce2ac4114559bd7",
"shasum": ""
},
"require": {
@@ -2209,9 +2209,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.289.1"
+ "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.289.0"
},
- "time": "2023-03-01T17:20:18+00:00"
+ "time": "2023-02-26T01:10:11+00:00"
},
{
"name": "google/auth",
@@ -2605,16 +2605,16 @@
},
{
"name": "guzzlehttp/psr7",
- "version": "2.4.3",
+ "version": "2.4.4",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
- "reference": "67c26b443f348a51926030c83481b85718457d3d"
+ "reference": "3cf1b6d4f0c820a2cf8bcaec39fc698f3443b5cf"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/psr7/zipball/67c26b443f348a51926030c83481b85718457d3d",
- "reference": "67c26b443f348a51926030c83481b85718457d3d",
+ "url": "https://api.github.com/repos/guzzle/psr7/zipball/3cf1b6d4f0c820a2cf8bcaec39fc698f3443b5cf",
+ "reference": "3cf1b6d4f0c820a2cf8bcaec39fc698f3443b5cf",
"shasum": ""
},
"require": {
@@ -2704,7 +2704,7 @@
],
"support": {
"issues": "https://github.com/guzzle/psr7/issues",
- "source": "https://github.com/guzzle/psr7/tree/2.4.3"
+ "source": "https://github.com/guzzle/psr7/tree/2.4.4"
},
"funding": [
{
@@ -2720,7 +2720,7 @@
"type": "tidelift"
}
],
- "time": "2022-10-26T14:07:24+00:00"
+ "time": "2023-03-09T13:19:02+00:00"
},
{
"name": "guzzlehttp/uri-template",
@@ -14427,16 +14427,16 @@
},
{
"name": "myclabs/deep-copy",
- "version": "1.11.0",
+ "version": "1.11.1",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
- "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614"
+ "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614",
- "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c",
+ "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c",
"shasum": ""
},
"require": {
@@ -14474,7 +14474,7 @@
],
"support": {
"issues": "https://github.com/myclabs/DeepCopy/issues",
- "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0"
+ "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1"
},
"funding": [
{
@@ -14482,7 +14482,7 @@
"type": "tidelift"
}
],
- "time": "2022-03-03T13:19:32+00:00"
+ "time": "2023-03-08T13:26:56+00:00"
},
{
"name": "netresearch/jsonmapper",
@@ -15020,16 +15020,16 @@
},
{
"name": "phpstan/phpstan",
- "version": "1.10.5",
+ "version": "1.10.6",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
- "reference": "1fb6f494d82455151ecf15c5c191923f5d84324e"
+ "reference": "50d089a3e0904b0fe7e2cf2d4fd37d427d64235a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan/zipball/1fb6f494d82455151ecf15c5c191923f5d84324e",
- "reference": "1fb6f494d82455151ecf15c5c191923f5d84324e",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/50d089a3e0904b0fe7e2cf2d4fd37d427d64235a",
+ "reference": "50d089a3e0904b0fe7e2cf2d4fd37d427d64235a",
"shasum": ""
},
"require": {
@@ -15059,7 +15059,7 @@
],
"support": {
"issues": "https://github.com/phpstan/phpstan/issues",
- "source": "https://github.com/phpstan/phpstan/tree/1.10.5"
+ "source": "https://github.com/phpstan/phpstan/tree/1.10.6"
},
"funding": [
{
@@ -15075,7 +15075,7 @@
"type": "tidelift"
}
],
- "time": "2023-03-07T16:48:45+00:00"
+ "time": "2023-03-09T16:55:12+00:00"
},
{
"name": "phpunit/php-code-coverage",
@@ -15397,16 +15397,16 @@
},
{
"name": "phpunit/phpunit",
- "version": "9.6.4",
+ "version": "9.6.5",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "9125ee085b6d95e78277dc07aa1f46f9e0607b8d"
+ "reference": "86e761949019ae83f49240b2f2123fb5ab3b2fc5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9125ee085b6d95e78277dc07aa1f46f9e0607b8d",
- "reference": "9125ee085b6d95e78277dc07aa1f46f9e0607b8d",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/86e761949019ae83f49240b2f2123fb5ab3b2fc5",
+ "reference": "86e761949019ae83f49240b2f2123fb5ab3b2fc5",
"shasum": ""
},
"require": {
@@ -15439,8 +15439,8 @@
"sebastian/version": "^3.0.2"
},
"suggest": {
- "ext-soap": "*",
- "ext-xdebug": "*"
+ "ext-soap": "To be able to generate mocks based on WSDL files",
+ "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage"
},
"bin": [
"phpunit"
@@ -15479,7 +15479,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
- "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.4"
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.5"
},
"funding": [
{
@@ -15495,7 +15495,7 @@
"type": "tidelift"
}
],
- "time": "2023-02-27T13:06:37+00:00"
+ "time": "2023-03-09T06:34:10+00:00"
},
{
"name": "sebastian/cli-parser",
diff --git a/database/seeders/RandomDataSeeder.php b/database/seeders/RandomDataSeeder.php
index 60fbf0707813..502f0987c253 100644
--- a/database/seeders/RandomDataSeeder.php
+++ b/database/seeders/RandomDataSeeder.php
@@ -11,37 +11,41 @@
namespace Database\Seeders;
-use App\DataMapper\ClientSettings;
-use App\DataMapper\CompanySettings;
-use App\Events\Payment\PaymentWasCreated;
-use App\Helpers\Invoice\InvoiceSum;
-use App\Helpers\Invoice\InvoiceSumInclusive;
-use App\Models\Account;
+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\Credit;
-use App\Models\GroupSetting;
+use App\Models\Vendor;
+use App\Models\Account;
+use App\Models\Company;
use App\Models\Invoice;
use App\Models\Payment;
+use App\Models\Product;
use App\Models\PaymentHash;
use App\Models\PaymentType;
-use App\Models\Product;
-use App\Models\Quote;
-use App\Models\RecurringInvoice;
-use App\Models\User;
-use App\Repositories\CreditRepository;
-use App\Repositories\InvoiceRepository;
-use App\Repositories\QuoteRepository;
-use App\Utils\Ninja;
-use Illuminate\Database\Eloquent\Model;
-use Illuminate\Database\Seeder;
-use Illuminate\Support\Facades\Cache;
-use Illuminate\Support\Facades\Hash;
-use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
+use App\Models\CompanyToken;
+use App\Models\GroupSetting;
+use App\Models\ClientContact;
+use App\Models\VendorContact;
+use App\Models\CompanyGateway;
+use Illuminate\Database\Seeder;
+use App\Models\RecurringInvoice;
+use App\DataMapper\ClientSettings;
+use App\DataMapper\CompanySettings;
+use App\Helpers\Invoice\InvoiceSum;
+use Illuminate\Support\Facades\Hash;
+use App\Repositories\QuoteRepository;
+use Illuminate\Support\Facades\Cache;
+use App\Repositories\CreditRepository;
+use Illuminate\Support\Facades\Schema;
+use App\Repositories\InvoiceRepository;
+use Illuminate\Database\Eloquent\Model;
+use App\Events\Payment\PaymentWasCreated;
+use App\Helpers\Invoice\InvoiceSumInclusive;
+use App\Models\BankIntegration;
+use App\Models\BankTransaction;
class RandomDataSeeder extends Seeder
{
@@ -117,6 +121,57 @@ class RandomDataSeeder extends Seeder
'settings' => null,
]);
+ $permission_users = [
+ 'permissions',
+ 'products',
+ 'invoices',
+ 'quotes',
+ 'clients',
+ 'vendors',
+ 'tasks',
+ 'expenses',
+ 'projects',
+ 'credits',
+ 'payments',
+ 'bank_transactions',
+ 'purchase_orders',
+ ];
+
+ foreach ($permission_users as $p_user) {
+
+ $user = User::firstOrNew([
+ 'email' => "{$p_user}@example.com",
+ ]);
+
+ $user->first_name = ucfirst($p_user);
+ $user->last_name = 'Example';
+ $user->password = Hash::make('password');
+ $user->account_id = $account->id;
+ $user->email_verified_at = now();
+ $user->save();
+
+ $company_token = CompanyToken::create([
+ 'user_id' => $user->id,
+ 'company_id' => $company->id,
+ 'account_id' => $account->id,
+ 'name' => 'test token',
+ 'token' => \Illuminate\Support\Str::random(64),
+ ]);
+
+ $user->companies()->attach($company->id, [
+ 'account_id' => $account->id,
+ 'is_owner' => 0,
+ 'is_admin' => 0,
+ 'is_locked' => 0,
+ 'notifications' => CompanySettings::notificationDefaults(),
+ 'permissions' => '',
+ 'settings' => null,
+ ]);
+
+ $user = null;
+ }
+
+
$user = User::firstOrNew([
'email' => 'user@example.com',
]);
@@ -147,35 +202,6 @@ class RandomDataSeeder extends Seeder
]);
- $user = User::firstOrNew([
- 'email' => 'permissions@example.com',
- ]);
-
- $user->first_name = 'Permissions';
- $user->last_name = 'Example';
- $user->password = Hash::make('password');
- $user->account_id = $account->id;
- $user->email_verified_at = now();
- $user->save();
-
- $company_token = CompanyToken::create([
- 'user_id' => $user->id,
- 'company_id' => $company->id,
- 'account_id' => $account->id,
- 'name' => 'test token',
- 'token' => \Illuminate\Support\Str::random(64),
- ]);
-
- $user->companies()->attach($company->id, [
- 'account_id' => $account->id,
- 'is_owner' => 0,
- 'is_admin' => 0,
- 'is_locked' => 0,
- 'notifications' => CompanySettings::notificationDefaults(),
- 'permissions' => '',
- 'settings' => null,
- ]);
-
$client = Client::factory()->create([
'user_id' => $user->id,
@@ -195,6 +221,28 @@ class RandomDataSeeder extends Seeder
'password' => Hash::make('password'),
]);
+
+
+ $vendor = Vendor::factory()->create([
+ 'user_id' => $user->id,
+ 'company_id' => $company->id,
+ 'name' => 'cypress'
+ ]);
+
+ $vendor->number = $vendor->getNextVendorNumber($vendor);
+ $vendor->save();
+
+ VendorContact::factory()->create([
+ 'user_id' => $user->id,
+ 'vendor_id' => $vendor->id,
+ 'company_id' => $company->id,
+ 'is_primary' => 1,
+ 'email' => 'cypress_vendor@example.com',
+ 'password' => Hash::make('password'),
+ ]);
+
+
+
/* Product Factory */
Product::factory()->count(2)->create(['user_id' => $user->id, 'company_id' => $company->id]);
@@ -304,6 +352,18 @@ class RandomDataSeeder extends Seeder
'name' => 'Default Client Settings',
]);
+ $bi = BankIntegration::factory()->create([
+ 'account_id' => $account->id,
+ 'user_id' => $user->id,
+ 'company_id' => $company->id,
+ ]);
+
+ BankTransaction::factory()->create([
+ 'bank_integration_id' => $bi->id,
+ 'user_id' => $user->id,
+ 'company_id' => $company->id,
+ ]);
+
if (config('ninja.testvars.stripe')) {
$cg = new CompanyGateway;
$cg->company_id = $company->id;
diff --git a/lang/en/texts.php b/lang/en/texts.php
index 523d085ed967..7ab2aa9f4780 100644
--- a/lang/en/texts.php
+++ b/lang/en/texts.php
@@ -2251,7 +2251,7 @@ $LANG = array(
'invalid_file' => 'Invalid file type',
'add_documents_to_invoice' => 'Add Documents to Invoice',
'mark_expense_paid' => 'Mark paid',
- 'white_label_license_error' => 'Failed to validate the license, check storage/logs/laravel-error.log for more details.',
+ 'white_label_license_error' => 'Failed to validate the license, either expired or excessive activations. Email contact@invoiceninja.com for more information.',
'plan_price' => 'Plan Price',
'wrong_confirmation' => 'Incorrect confirmation code',
'oauth_taken' => 'The account is already registered',
@@ -4853,7 +4853,6 @@ $LANG = array(
'cash_vs_accrual_help' => 'Turn on for accrual reporting, turn off for cash basis reporting.',
'expense_paid_report' => 'Expensed reporting',
'expense_paid_report_help' => 'Turn on for reporting all expenses, turn off for reporting only paid expenses',
- 'payment_type_Klarna' => 'Klarna',
'online_payment_email_help' => 'Send an email when an online payment is made',
'manual_payment_email_help' => 'Send an email when manually entering a payment',
'mark_paid_payment_email_help' => 'Send an email when marking an invoice as paid',
@@ -5015,7 +5014,9 @@ $LANG = array(
'no_assigned_tasks' => 'No billable tasks for this project',
'authorization_failure' => 'Insufficient permissions to perform this action',
'authorization_sms_failure' => 'Please verify your account to send emails.',
- 'white_label_body' => 'Thank you for purchasing a white label license. Your license key is :license_key.',
+ 'white_label_body' => 'Thank you for purchasing a white label license.
Your license key is:
:license_key',
+ 'payment_type_Klarna' => 'Klarna',
+ 'payment_type_Interac E Transfer' => 'Interac E Transfer',
);
diff --git a/resources/views/portal/ninja2020/payments/show.blade.php b/resources/views/portal/ninja2020/payments/show.blade.php
index 546f55a89dee..80c443d700ad 100644
--- a/resources/views/portal/ninja2020/payments/show.blade.php
+++ b/resources/views/portal/ninja2020/payments/show.blade.php
@@ -49,16 +49,16 @@
@endif
- @if(!empty($payment->type?->name) && !is_null($payment->type?->name))
+