diff --git a/app/Helpers/SwissQr/SwissQrGenerator.php b/app/Helpers/SwissQr/SwissQrGenerator.php index 17fdb5610f1c..0301de5e1afc 100644 --- a/app/Helpers/SwissQr/SwissQrGenerator.php +++ b/app/Helpers/SwissQr/SwissQrGenerator.php @@ -178,13 +178,13 @@ class SwissQrGenerator if(is_iterable($qrBill->getViolations())) { foreach ($qrBill->getViolations() as $key => $violation) { - nlog("qr"); - nlog($violation); + // nlog("qr"); + // nlog($violation); } } - nlog($e->getMessage()); + // nlog($e->getMessage()); return ''; // return $e->getMessage(); diff --git a/app/Http/Requests/Credit/StoreCreditRequest.php b/app/Http/Requests/Credit/StoreCreditRequest.php index 9baf0c706f44..df8afc4a8799 100644 --- a/app/Http/Requests/Credit/StoreCreditRequest.php +++ b/app/Http/Requests/Credit/StoreCreditRequest.php @@ -76,6 +76,7 @@ class StoreCreditRequest extends Request $rules['tax_name2'] = 'bail|sometimes|string|nullable'; $rules['tax_name3'] = 'bail|sometimes|string|nullable'; $rules['exchange_rate'] = 'bail|sometimes|numeric'; + $rules['amount'] = ['sometimes', 'bail', 'numeric', 'max:99999999999999']; if ($this->invoice_id) { $rules['invoice_id'] = new ValidInvoiceCreditRule(); @@ -101,6 +102,7 @@ class StoreCreditRequest extends Request $input = $this->decodePrimaryKeys($input); $input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : []; + $input['amount'] = $this->entityTotalAmount($input['line_items']); if (array_key_exists('exchange_rate', $input) && is_null($input['exchange_rate'])) { $input['exchange_rate'] = 1; diff --git a/app/Http/Requests/Credit/UpdateCreditRequest.php b/app/Http/Requests/Credit/UpdateCreditRequest.php index 9dafd1583eb3..e069624d29f7 100644 --- a/app/Http/Requests/Credit/UpdateCreditRequest.php +++ b/app/Http/Requests/Credit/UpdateCreditRequest.php @@ -76,6 +76,7 @@ class UpdateCreditRequest extends Request $rules['tax_name2'] = 'bail|sometimes|string|nullable'; $rules['tax_name3'] = 'bail|sometimes|string|nullable'; $rules['exchange_rate'] = 'bail|sometimes|numeric'; + $rules['amount'] = ['sometimes', 'bail', 'numeric', 'max:99999999999999']; return $rules; } @@ -92,6 +93,8 @@ class UpdateCreditRequest extends Request if (isset($input['line_items'])) { $input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : []; + $input['amount'] = $this->entityTotalAmount($input['line_items']); + } if (array_key_exists('exchange_rate', $input) && is_null($input['exchange_rate'])) { diff --git a/app/Http/Requests/Invoice/StoreInvoiceRequest.php b/app/Http/Requests/Invoice/StoreInvoiceRequest.php index f91faa9cc1f6..94f47c68a067 100644 --- a/app/Http/Requests/Invoice/StoreInvoiceRequest.php +++ b/app/Http/Requests/Invoice/StoreInvoiceRequest.php @@ -77,9 +77,11 @@ class StoreInvoiceRequest extends Request $rules['exchange_rate'] = 'bail|sometimes|numeric'; $rules['partial'] = 'bail|sometimes|nullable|numeric|gte:0'; $rules['partial_due_date'] = ['bail', 'sometimes', 'exclude_if:partial,0', Rule::requiredIf(fn () => $this->partial > 0), 'date']; + $rules['amount'] = ['sometimes', 'bail', 'numeric', 'max:99999999999999']; + + // $rules['amount'] = ['sometimes', 'bail', 'max:99999999999999']; // $rules['due_date'] = ['bail', 'sometimes', 'nullable', 'after:partial_due_date', Rule::requiredIf(fn () => strlen($this->partial_due_date) > 1), 'date']; - return $rules; } @@ -89,17 +91,18 @@ class StoreInvoiceRequest extends Request $input = $this->decodePrimaryKeys($input); + $input['amount'] = 0; + $input['balance'] = 0; + if (isset($input['line_items']) && is_array($input['line_items'])) { $input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : []; + $input['amount'] = $this->entityTotalAmount($input['line_items']); } if(isset($input['partial']) && $input['partial'] == 0) { $input['partial_due_date'] = null; } - $input['amount'] = 0; - $input['balance'] = 0; - if (array_key_exists('tax_rate1', $input) && is_null($input['tax_rate1'])) { $input['tax_rate1'] = 0; } diff --git a/app/Http/Requests/Invoice/UpdateInvoiceRequest.php b/app/Http/Requests/Invoice/UpdateInvoiceRequest.php index f21feb1933f6..dc79843df353 100644 --- a/app/Http/Requests/Invoice/UpdateInvoiceRequest.php +++ b/app/Http/Requests/Invoice/UpdateInvoiceRequest.php @@ -77,6 +77,8 @@ class UpdateInvoiceRequest extends Request $rules['status_id'] = 'bail|sometimes|not_in:5'; //do not allow cancelled invoices to be modfified. $rules['exchange_rate'] = 'bail|sometimes|numeric'; $rules['partial'] = 'bail|sometimes|nullable|numeric'; + $rules['amount'] = ['sometimes', 'bail', 'numeric', 'max:99999999999999']; + // $rules['partial_due_date'] = ['bail', 'sometimes', 'exclude_if:partial,0', Rule::requiredIf(fn () => $this->partial > 0), 'date', 'before:due_date']; // $rules['due_date'] = ['bail', 'sometimes', 'nullable', 'after:partial_due_date', Rule::requiredIf(fn () => strlen($this->partial_due_date) > 1), 'date']; @@ -97,6 +99,7 @@ class UpdateInvoiceRequest extends Request if (isset($input['line_items']) && is_array($input['line_items'])) { $input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : []; + $input['amount'] = $this->entityTotalAmount($input['line_items']); } if (array_key_exists('documents', $input)) { diff --git a/app/Http/Requests/PurchaseOrder/StorePurchaseOrderRequest.php b/app/Http/Requests/PurchaseOrder/StorePurchaseOrderRequest.php index d21c73cbf68d..aca3320712ef 100644 --- a/app/Http/Requests/PurchaseOrder/StorePurchaseOrderRequest.php +++ b/app/Http/Requests/PurchaseOrder/StorePurchaseOrderRequest.php @@ -70,6 +70,8 @@ class StorePurchaseOrderRequest extends Request $rules['status_id'] = 'nullable|integer|in:1,2,3,4,5'; $rules['exchange_rate'] = 'bail|sometimes|numeric'; + $rules['amount'] = ['sometimes', 'bail', 'numeric', 'max:99999999999999']; + return $rules; } @@ -79,16 +81,18 @@ class StorePurchaseOrderRequest extends Request $input = $this->decodePrimaryKeys($input); + $input['amount'] = 0; + $input['balance'] = 0; + if(isset($input['partial']) && $input['partial'] == 0) { $input['partial_due_date'] = null; } if (isset($input['line_items']) && is_array($input['line_items'])) { $input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : []; - } + $input['amount'] = $this->entityTotalAmount($input['line_items']); - $input['amount'] = 0; - $input['balance'] = 0; + } if (array_key_exists('exchange_rate', $input) && is_null($input['exchange_rate'])) { $input['exchange_rate'] = 1; diff --git a/app/Http/Requests/PurchaseOrder/UpdatePurchaseOrderRequest.php b/app/Http/Requests/PurchaseOrder/UpdatePurchaseOrderRequest.php index a9be48fb7f18..cdee35d9a6a8 100644 --- a/app/Http/Requests/PurchaseOrder/UpdatePurchaseOrderRequest.php +++ b/app/Http/Requests/PurchaseOrder/UpdatePurchaseOrderRequest.php @@ -71,6 +71,7 @@ class UpdatePurchaseOrderRequest extends Request $rules['status_id'] = 'sometimes|integer|in:1,2,3,4,5'; $rules['exchange_rate'] = 'bail|sometimes|numeric'; + $rules['amount'] = ['sometimes', 'bail', 'numeric', 'max:99999999999999']; return $rules; } @@ -89,6 +90,7 @@ class UpdatePurchaseOrderRequest extends Request if (isset($input['line_items']) && is_array($input['line_items'])) { $input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : []; + $input['amount'] = $this->entityTotalAmount($input['line_items']); } if (array_key_exists('exchange_rate', $input) && is_null($input['exchange_rate'])) { diff --git a/app/Http/Requests/Quote/StoreQuoteRequest.php b/app/Http/Requests/Quote/StoreQuoteRequest.php index 3fa933356469..c335351ae247 100644 --- a/app/Http/Requests/Quote/StoreQuoteRequest.php +++ b/app/Http/Requests/Quote/StoreQuoteRequest.php @@ -66,6 +66,7 @@ class StoreQuoteRequest extends Request $rules['line_items'] = 'array'; $rules['partial_due_date'] = ['bail', 'sometimes', 'exclude_if:partial,0', Rule::requiredIf(fn () => $this->partial > 0), 'date', 'before:due_date', 'after_or_equal:date']; $rules['due_date'] = ['bail', 'sometimes', 'nullable', 'after:partial_due_date', Rule::requiredIf(fn () => strlen($this->partial_due_date) > 1), 'date']; + $rules['amount'] = ['sometimes', 'bail', 'numeric', 'max:99999999999999']; return $rules; } @@ -79,10 +80,14 @@ class StoreQuoteRequest extends Request $input = $this->decodePrimaryKeys($input); - $input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : []; $input['amount'] = 0; $input['balance'] = 0; + if (isset($input['line_items']) && is_array($input['line_items'])) { + $input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : []; + $input['amount'] = $this->entityTotalAmount($input['line_items']); + } + if (array_key_exists('exchange_rate', $input) && is_null($input['exchange_rate'])) { $input['exchange_rate'] = 1; } diff --git a/app/Http/Requests/Quote/UpdateQuoteRequest.php b/app/Http/Requests/Quote/UpdateQuoteRequest.php index 4a8b386c9206..7fd459c30161 100644 --- a/app/Http/Requests/Quote/UpdateQuoteRequest.php +++ b/app/Http/Requests/Quote/UpdateQuoteRequest.php @@ -65,6 +65,7 @@ class UpdateQuoteRequest extends Request $rules['partial_due_date'] = ['bail', 'sometimes', 'exclude_if:partial,0', Rule::requiredIf(fn () => $this->partial > 0), 'date', 'before:due_date']; $rules['due_date'] = ['bail', 'sometimes', 'nullable', 'after:partial_due_date', 'after_or_equal:date', Rule::requiredIf(fn () => strlen($this->partial_due_date) > 1), 'date']; + $rules['amount'] = ['sometimes', 'bail', 'numeric', 'max:99999999999999']; return $rules; } @@ -79,6 +80,7 @@ class UpdateQuoteRequest extends Request if (isset($input['line_items'])) { $input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : []; + $input['amount'] = $this->entityTotalAmount($input['line_items']); } if (array_key_exists('documents', $input)) { diff --git a/app/Http/Requests/RecurringInvoice/StoreRecurringInvoiceRequest.php b/app/Http/Requests/RecurringInvoice/StoreRecurringInvoiceRequest.php index e6319c7bea94..5b56080959e0 100644 --- a/app/Http/Requests/RecurringInvoice/StoreRecurringInvoiceRequest.php +++ b/app/Http/Requests/RecurringInvoice/StoreRecurringInvoiceRequest.php @@ -79,6 +79,8 @@ class StoreRecurringInvoiceRequest extends Request $rules['exchange_rate'] = 'bail|sometimes|numeric'; $rules['next_send_date'] = 'bail|required|date|after:yesterday'; + $rules['amount'] = ['sometimes', 'bail', 'numeric', 'max:99999999999999']; + return $rules; } @@ -145,6 +147,7 @@ class StoreRecurringInvoiceRequest extends Request } $input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : []; + $input['amount'] = $this->entityTotalAmount($input['line_items']); if (isset($input['auto_bill'])) { $input['auto_bill_enabled'] = $this->setAutoBillFlag($input['auto_bill']); diff --git a/app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php b/app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php index ee4c01c3766b..9e6238570955 100644 --- a/app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php +++ b/app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php @@ -72,6 +72,7 @@ class UpdateRecurringInvoiceRequest extends Request $rules['tax_name3'] = 'bail|sometimes|string|nullable'; $rules['exchange_rate'] = 'bail|sometimes|numeric'; $rules['next_send_date'] = 'bail|required|date|after:yesterday'; + $rules['amount'] = ['sometimes', 'bail', 'numeric', 'max:99999999999999']; return $rules; } @@ -126,6 +127,7 @@ class UpdateRecurringInvoiceRequest extends Request if (isset($input['line_items'])) { $input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : []; + $input['amount'] = $this->entityTotalAmount($input['line_items']); } if (array_key_exists('auto_bill', $input) && isset($input['auto_bill'])) { diff --git a/app/Models/Activity.php b/app/Models/Activity.php index 588d466efdaa..56eb2d490f76 100644 --- a/app/Models/Activity.php +++ b/app/Models/Activity.php @@ -258,6 +258,9 @@ class Activity extends StaticModel public const PAYMENT_EMAILED = 138; public const VENDOR_NOTIFICATION_EMAIL = 139; + + public const EMAIL_STATEMENT = 140; + protected $casts = [ 'is_system' => 'boolean', @@ -469,6 +472,8 @@ class Activity extends StaticModel ':adjustment' => $translation = [substr($variable, 1) => [ 'label' => Number::formatMoney($this?->payment?->refunded, $this?->payment?->client ?? $this->company) ?? '', 'hashed_id' => '']], ':ip' => $translation = [ 'ip' => $this->ip ?? ''], ':contact' => $translation = $this->resolveContact(), + ':notes' => $translation = [ 'notes' => $this->notes ?? ''], + default => $translation = [], }; diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 6c1c1958515a..b7f1a37159e5 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -97,6 +97,7 @@ use App\Events\RecurringQuote\RecurringQuoteWasCreated; use App\Events\RecurringQuote\RecurringQuoteWasDeleted; use App\Events\RecurringQuote\RecurringQuoteWasRestored; use App\Events\RecurringQuote\RecurringQuoteWasUpdated; +use App\Events\Statement\StatementWasEmailed; use App\Events\Subscription\SubscriptionWasArchived; use App\Events\Subscription\SubscriptionWasCreated; use App\Events\Subscription\SubscriptionWasDeleted; @@ -222,6 +223,7 @@ use App\Listeners\RecurringQuote\RecurringQuoteDeletedActivity; use App\Listeners\RecurringQuote\RecurringQuoteRestoredActivity; use App\Listeners\RecurringQuote\UpdateRecurringQuoteActivity; use App\Listeners\SendVerificationNotification; +use App\Listeners\Statement\StatementEmailedActivity; use App\Listeners\User\ArchivedUserActivity; use App\Listeners\User\CreatedUserActivity; use App\Listeners\User\DeletedUserActivity; @@ -577,6 +579,9 @@ class EventServiceProvider extends ServiceProvider RecurringInvoiceWasRestored::class => [ RecurringInvoiceRestoredActivity::class, ], + StatementWasEmailed::class => [ + StatementEmailedActivity::class, + ], TaskWasCreated::class => [ CreatedTaskActivity::class, ], diff --git a/app/Services/Client/ClientService.php b/app/Services/Client/ClientService.php index 31e6cc524f7f..b792d44bf35f 100644 --- a/app/Services/Client/ClientService.php +++ b/app/Services/Client/ClientService.php @@ -11,6 +11,7 @@ namespace App\Services\Client; +use App\Utils\Ninja; use App\Utils\Number; use App\Models\Client; use App\Models\Credit; @@ -23,6 +24,7 @@ use App\Services\Email\EmailObject; use App\Utils\Traits\GeneratesCounter; use Illuminate\Mail\Mailables\Address; use Illuminate\Database\QueryException; +use App\Events\Statement\StatementWasEmailed; class ClientService { @@ -275,6 +277,9 @@ class ClientService $email_object = $this->buildStatementMailableData($pdf); Email::dispatch($email_object, $this->client->company); + + event(new StatementWasEmailed($this->client, $this->client->company, $this->client_end_date, Ninja::eventVars())); + } /** @@ -307,8 +312,8 @@ class ClientService $email_object->attachments = [['file' => base64_encode($pdf), 'name' => ctrans('texts.statement') . ".pdf"]]; $email_object->client_id = $this->client->id; $email_object->entity_class = Invoice::class; - $email_object->entity_id = $invoice->id ?? null; - $email_object->invitation_id = $invoice->invitations->first()->id ?? null; + $email_object->entity_id = $invoice?->id ?? null; + $email_object->invitation_id = $invoice?->invitations?->first()?->id ?? null; $email_object->email_template_subject = 'email_subject_statement'; $email_object->email_template_body = 'email_template_statement'; $email_object->variables = [ diff --git a/app/Services/Scheduler/EmailStatementService.php b/app/Services/Scheduler/EmailStatementService.php index eb4e288f2e60..1111090a8c07 100644 --- a/app/Services/Scheduler/EmailStatementService.php +++ b/app/Services/Scheduler/EmailStatementService.php @@ -44,6 +44,8 @@ class EmailStatementService $query->cursor() ->each(function ($_client) { + + /**@var \App\Models\Client $_client */ $this->client = $_client; //work out the date range diff --git a/app/Utils/Traits/CleanLineItems.php b/app/Utils/Traits/CleanLineItems.php index a2457c4056b9..9d20511e5770 100644 --- a/app/Utils/Traits/CleanLineItems.php +++ b/app/Utils/Traits/CleanLineItems.php @@ -90,4 +90,18 @@ trait CleanLineItems return $item; } + + private function entityTotalAmount($items) + { + $total = 0; + + foreach($items as $item) + { + $total += ($item['cost'] * $item['quantity']); + } + + nlog($total); + + return $total; + } } diff --git a/lang/en/texts.php b/lang/en/texts.php index a0544942f12f..aaf8fee7a0b9 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -5303,6 +5303,7 @@ $lang = array( 'always_show_required_fields' => 'Allows show required fields form', 'always_show_required_fields_help' => 'Displays the required fields form always at checkout', 'advanced_cards' => 'Advanced Cards', + 'activity_140' => 'Statement sent to :client - [:notes]', ); return $lang;