diff --git a/app/app/Enums/InvoiceType.php b/app/app/Enums/InvoiceType.php new file mode 100644 index 000000000..34356070b --- /dev/null +++ b/app/app/Enums/InvoiceType.php @@ -0,0 +1,25 @@ + $templates) { if (in_array($templateName, $templates, true)) { - return isset($user->$category) && (bool) $user->$category === false; + $attributeName = 'email_mute_' . $category; + return isset($user->$attributeName) && (bool) $user->$attributeName === false; } } return false; @@ -264,7 +265,7 @@ public static function sendWithSwift($subject, $to, $template, $data) { */ public static function sendEmail($subject, $to, $template, $data, $mailLib='SWIFT') { $user = NULL; - if (!empty($data['user']) && $data['user'] instanceof Model) { + if (!empty($data['user']) && $data['user']) { $user = $data['user']; } diff --git a/app/app/Helpers/InvoiceHelper.php b/app/app/Helpers/InvoiceHelper.php index 76d10ec38..9c5e786dd 100755 --- a/app/app/Helpers/InvoiceHelper.php +++ b/app/app/Helpers/InvoiceHelper.php @@ -87,8 +87,8 @@ public static function generatePrettyInvoice($user, $workspace, $invoice) $accountName = self::placeholderIfEmpty($user->company_name, "N/A"); $taxNumber = self::placeholderIfEmpty($user->tax_number); if (is_null($tax)) { - $tax1 = "N/A"; - $taxPercentage = "N/A"; + $tax1 = NULL; + $taxPercentage = NULL; } else { $tax1 = $tax['name']; $taxPercentage = sprintf("%d%%", $tax->tax_percentage); @@ -196,4 +196,27 @@ public static function generatePrettyInvoice($user, $workspace, $invoice) // $pdf = PDF::loadView('pdf.invoice_new', $mergedValues); return $pdf; } + + public static function generateAccountNumber() + { + $timestamp = time(); + $randomPart = str_pad(mt_rand(0, 9999), 4, '0', STR_PAD_LEFT); + $accountNumber = 'ACC' . substr($timestamp, -6) . $randomPart; + + // Ensure uniqueness + $maxAttempts = 10; + $attempt = 0; + while ($attempt < $maxAttempts) { + if (!DB::table('workspaces')->where('account_no', $accountNumber)->exists()) { + return $accountNumber; + } + $randomPart = str_pad(mt_rand(0, 9999), 4, '0', STR_PAD_LEFT); + $accountNumber = 'ACC' . substr($timestamp, -6) . $randomPart; + $attempt++; + } + + // Fallback: use UUID-based approach + return 'ACC' . strtoupper(substr(str_replace('-', '', uniqid('', true)), 0, 10)); + } + } diff --git a/app/app/Http/Controllers/EmailController.php b/app/app/Http/Controllers/EmailController.php index ee7f02923..aa4138aa1 100755 --- a/app/app/Http/Controllers/EmailController.php +++ b/app/app/Http/Controllers/EmailController.php @@ -28,41 +28,42 @@ class EmailController extends BaseController { private function createOption($key, $optionsRecord) { + + $labelKey = str_replace("email_mute_", "", $key); return [ 'name' => $key, - 'label' => str_replace("_", " ", ucwords($key)), + 'label' => str_replace("_", " ", ucwords($labelKey)), 'enabled' => $optionsRecord->{$key} ]; } /** - * Show the application dashboard to the user. + * Show email unsubscribe options page. * * @return Response */ - public function unsubscribe() + public function unsubscribe(Request $request) { - // todo: get user from jwt token - //$user = WorkspaceUser::user(); - $user = WorkspaceUser::all()[0]; - $optionsRecord = UserEmailOption::where('user_id', $user->id)->first(); + $token = $request->query('token'); + $user = User::where('unsubscribe_token', $token)->first(); + $emailOptions = [ - 'auditing' => $this->createOption('auditing', $optionsRecord), - 'account_changes' => $this->createOption('account_changes', $optionsRecord), - 'workspace_changes' => $this->createOption('workspace_changes', $optionsRecord), - 'system_status_updates' => $this->createOption('system_status_updates', $optionsRecord), - 'debugger' => $this->createOption('debugger', $optionsRecord) + 'email_mute_auditing' => $this->createOption('email_mute_auditing', $user), + 'email_mute_account_changes' => $this->createOption('email_mute_account_changes', $user), + 'email_mute_workspace_changes' => $this->createOption('email_mute_workspace_changes', $user), + 'email_mute_system_status_updates' => $this->createOption('email_mute_system_status_updates', $user), + 'email_mute_debugger' => $this->createOption('email_mute_debugger', $user) ]; + return view('pages.email_unsubscribe', compact('user', 'emailOptions')); } public function unsubscribe_update(Request $request) { // todo: get user from jwt token - //$user = WorkspaceUser::user(); - $user = WorkspaceUser::all()[0]; - $emailOptions = UserEmailOption::where('user_id', $user->id)->first(); + $token = $request->query('token'); + $user = User::where('unsubscribe_token', $token)->first(); $updatedOptions = $this->processEmailOptions( $request->all() ); - $emailOptions->update( $updatedOptions ); + $user->update( $updatedOptions ); $request->session()->flash('status', 'Email options were updated successfully.'); return redirect("/email/unsubscribe"); } diff --git a/app/app/Http/Controllers/RegisterController.php b/app/app/Http/Controllers/RegisterController.php index 0ab4c31c3..a9d4c9f8c 100755 --- a/app/app/Http/Controllers/RegisterController.php +++ b/app/app/Http/Controllers/RegisterController.php @@ -75,6 +75,8 @@ public function register(Request $request) $planKey = $data['plan'] ?? 'pay-as-you-go'; $mainRouter = SIPRouter::getMainRouter(); $servicePlan = ServicePlan::where('key_name', $planKey)->firstOrFail(); + $accountNo = InvoiceHelper::generateAccountNumber(); + $workspace = Workspace::create([ 'creator_id' => $user->id, @@ -83,7 +85,8 @@ public function register(Request $request) 'api_secret' => MainHelper::createAPISecret(), 'plan' => $planKey, 'trial_mode' => TRUE, - 'default_router_id' => $mainRouter->id + 'default_router_id' => $mainRouter->id, + 'account_no' => $accountNo ]); PlanUsagePeriod::create([ diff --git a/app/database/migrations/2026_06_26_181258_add_email_subscribing_opts_to_users.php b/app/database/migrations/2026_06_26_181258_add_email_subscribing_opts_to_users.php new file mode 100644 index 000000000..b5f519b8d --- /dev/null +++ b/app/database/migrations/2026_06_26_181258_add_email_subscribing_opts_to_users.php @@ -0,0 +1,41 @@ +boolean('email_mute_auditing')->default(TRUE); + $table->boolean('email_mute_account_changes')->default(TRUE); + $table->boolean('email_mute_workspace_changes')->default(TRUE); + $table->boolean('email_mute_system_status_updates')->default(TRUE); + $table->boolean('email_mute_debugger')->default(TRUE); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function (Blueprint $table) { + // + $table->dropColumn('email_mute_auditing'); + $table->dropColumn('email_mute_account_changes'); + $table->dropColumn('email_mute_workspace_changes'); + $table->dropColumn('email_mute_system_status_updates'); + $table->dropColumn('email_mute_debugger'); + }); + } +} diff --git a/app/database/migrations/2026_06_26_200147_add_invoice_type.php b/app/database/migrations/2026_06_26_200147_add_invoice_type.php new file mode 100644 index 000000000..32208f3fd --- /dev/null +++ b/app/database/migrations/2026_06_26_200147_add_invoice_type.php @@ -0,0 +1,32 @@ +string('invoice_type')->default('RECURRING_BILL'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users_invoices', function (Blueprint $table) { + $table->dropColumn('invoice_type'); + }); + } +} diff --git a/app/generate_monthly_invoice.php b/app/generate_monthly_invoice.php index e2acac8ce..0dff4ea71 100644 --- a/app/generate_monthly_invoice.php +++ b/app/generate_monthly_invoice.php @@ -16,7 +16,7 @@ $month = new DateTime(); $month->modify('first day of this month'); $end = new DateTime(); -$end->modify('last day of this month'); +$end->modify('last day of next month'); $invoiceSubtotal = 100*100; $callCosts = 100*20; $recordingCosts = 100*20; diff --git a/app/resources/views/pdf/pretty_monthly_invoice.blade.php b/app/resources/views/pdf/pretty_monthly_invoice.blade.php index 28345fdf8..3d19291b3 100755 --- a/app/resources/views/pdf/pretty_monthly_invoice.blade.php +++ b/app/resources/views/pdf/pretty_monthly_invoice.blade.php @@ -111,7 +111,6 @@ } .status-pill { - display: inline-block; padding: 6px 12px; border-radius: 16px; background: #fff7ed; @@ -337,21 +336,23 @@ - +
-
+
Invoice
{{$site}} monthly statement
-
{{$statusText}}
+
+ {{$statusText}} +
 
- +
- @@ -459,10 +460,12 @@ - - - - + @if (!empty($vars['tax_name']) && !empty($vars['tax_percentage'])) + + + + + @endif
Invoice Number @@ -368,7 +369,7 @@ @endif + Total Due {{MainHelper::toDollars($vars['invoice_amount'])}} Total excluding tax {{MainHelper::toDollars($vars['invoice_amount_no_tax'])}}
{{$vars['tax_name']}} {{$vars['tax_percentage']}}{{MainHelper::toDollars($vars['tax_amount'])}}
{{$vars['tax_name']}} {{$vars['tax_percentage']}}{{MainHelper::toDollars($vars['tax_amount'])}}
Total payment due {{MainHelper::toDollars($vars['invoice_amount'])}}