diff --git a/app/Http/Controllers/Admin/BackupsController.php b/app/Http/Controllers/Admin/BackupsController.php index 099bffeb..7b27234b 100644 --- a/app/Http/Controllers/Admin/BackupsController.php +++ b/app/Http/Controllers/Admin/BackupsController.php @@ -68,7 +68,6 @@ class BackupsController extends Controller $this->authorize('manage backups'); $data = $request->all(); - $data['company'] = $request->header('company'); dispatch(new CreateBackupJob($data))->onQueue(config('backup.queue.name')); diff --git a/app/Models/User.php b/app/Models/User.php index 8a5b8220..898a5264 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -257,6 +257,12 @@ class User extends Authenticatable implements HasMedia $query->wherePhone($filters->get('phone')); } + if ($filters->get('role')) { + $query->whereHas('roles', function ($q) use ($filters) { + $q->where('roles.id', $filters->get('role')); + }); + } + if ($filters->get('orderByField') || $filters->get('orderBy')) { $field = $filters->get('orderByField') ? $filters->get('orderByField') : 'name'; $orderBy = $filters->get('orderBy') ? $filters->get('orderBy') : 'asc'; diff --git a/app/Services/Backup/BackupConfigurationFactory.php b/app/Services/Backup/BackupConfigurationFactory.php index cb5f258a..e2fd9acd 100644 --- a/app/Services/Backup/BackupConfigurationFactory.php +++ b/app/Services/Backup/BackupConfigurationFactory.php @@ -2,19 +2,14 @@ namespace App\Services\Backup; -use App\Models\CompanySetting; use App\Models\FileDisk; use Exception; use Spatie\Backup\Config\Config; class BackupConfigurationFactory { - public static function make($data = []): Config + public static function make(array $data = []): Config { - if (blank($data['company'] ?? null)) { - throw new Exception('The Company ID is missig'); - } - if (blank($data['file_disk_id'] ?? null)) { throw new Exception('No file disk selected'); } @@ -27,12 +22,6 @@ class BackupConfigurationFactory config(['backup.backup.destination.disks' => [$prefix.$fileDisk->driver]]); - $companyNotificationEmail = CompanySetting::getSetting('notification_email', $data['company']); - - if ($companyNotificationEmail) { - config(['backup.notifications.mail.to' => $companyNotificationEmail]); - } - $config = Config::fromArray(config('backup')); return $config; diff --git a/config/invoiceshelf.php b/config/invoiceshelf.php index d8515cfb..e3ee9a5c 100644 --- a/config/invoiceshelf.php +++ b/config/invoiceshelf.php @@ -9,7 +9,6 @@ use App\Models\Invoice; use App\Models\Item; use App\Models\Note; use App\Models\Payment; -use App\Models\RecurringInvoice; use App\Models\TaxType; return [ @@ -164,7 +163,7 @@ return [ 'title' => 'settings.roles.title', 'group' => '', 'name' => 'Company Roles', - 'link' => '/admin/settings/roles-settings', + 'link' => '/admin/settings/roles', 'icon' => 'UserGroupIcon', 'owner_only' => true, 'ability' => '', @@ -174,7 +173,7 @@ return [ 'title' => 'settings.menu_title.exchange_rate', 'group' => '', 'name' => 'Exchange Rate Provider', - 'link' => '/admin/settings/exchange-rate-provider', + 'link' => '/admin/settings/exchange-rate', 'icon' => 'BanknotesIcon', 'owner_only' => false, 'ability' => 'view-exchange-rate-provider', @@ -204,7 +203,7 @@ return [ 'title' => 'settings.menu_title.payment_modes', 'group' => '', 'name' => 'Payment modes', - 'link' => '/admin/settings/payment-mode', + 'link' => '/admin/settings/payment-modes', 'icon' => 'CreditCardIcon', 'owner_only' => false, 'ability' => 'view-payment', @@ -234,7 +233,7 @@ return [ 'title' => 'settings.menu_title.expense_category', 'group' => '', 'name' => 'Expense Category', - 'link' => '/admin/settings/expense-category', + 'link' => '/admin/settings/expense-categories', 'icon' => 'ClipboardDocumentListIcon', 'owner_only' => false, 'ability' => 'view-expense', @@ -244,7 +243,7 @@ return [ 'title' => 'settings.mail.company_mail_config', 'group' => '', 'name' => 'Mail Configuration', - 'link' => '/admin/settings/mail-configuration', + 'link' => '/admin/settings/mail-config', 'icon' => 'EnvelopeIcon', 'owner_only' => true, 'ability' => '', @@ -306,16 +305,6 @@ return [ 'ability' => 'view-invoice', 'model' => Invoice::class, ], - [ - 'title' => 'navigation.recurring-invoices', - 'group' => 2, - 'link' => '/admin/recurring-invoices', - 'icon' => 'DocumentTextIcon', - 'name' => 'Recurring Invoices', - 'owner_only' => false, - 'ability' => 'view-recurring-invoice', - 'model' => RecurringInvoice::class, - ], [ 'title' => 'navigation.payments', 'group' => 2, diff --git a/lang/en.json b/lang/en.json index d85f1036..211dd8f2 100644 --- a/lang/en.json +++ b/lang/en.json @@ -102,6 +102,7 @@ "street_2": "Street 2", "action_failed": "Action Failed", "retry": "Retry", + "unable_to_load_pdf": "Unable to load document preview", "choose_note": "Choose Note", "no_note_found": "No Note Found", "insert_note": "Insert Note", @@ -451,6 +452,7 @@ "update_expense": "Update Expense", "edit_invoice": "Edit Invoice", "new_invoice": "New Invoice", + "one_time": "One-time", "save_invoice": "Save Invoice", "update_invoice": "Update Invoice", "add_new_tax": "Add New Tax", @@ -550,6 +552,7 @@ "update_expense": "Update Expense", "edit_invoice": "Edit Recurring Invoice", "new_invoice": "New Recurring Invoice", + "recurring": "Recurring", "send_automatically": "Send Automatically", "send_automatically_desc": "Enable this, if you would like to send the invoice automatically to the customer when its created.", "save_invoice": "Save Recurring Invoice", @@ -594,7 +597,13 @@ "every_hour": "Every Hour", "every_2_hour": "Every 2 Hour", "every_day_at_midnight": "Every day at midnight", + "every_day": "Every Day", "every_week": "Every Week", + "every_2_weeks": "Every 2 Weeks", + "every_month": "Every Month", + "every_2_months": "Every 2 Months", + "every_quarter": "Every 3 Months (Quarterly)", + "every_year": "Every Year", "every_15_days_at_midnight": "Every 15 days at midnight", "on_the_first_day_of_every_month_at_midnight": "On the first day of every month at 00:00", "every_6_month": "Every 6 Month", @@ -613,7 +622,8 @@ "none": "None", "date": "Date", "count": "Count" - } + }, + "make_recurring": "Make Recurring" }, "payments": { "title": "Payments", @@ -771,6 +781,7 @@ "date_of_creation": "Date Of Creation", "action": "Action", "invite_member": "Invite Member", + "select_role": "Select Role", "add_member": "Add Member", "save_member": "Save Member", "update_member": "Update Member", @@ -968,6 +979,7 @@ "updated_message": "Company information updated successfully", "delete_company": "Delete Company", "delete_company_description": "Once you delete your company, you will lose all the data and files associated with it permanently.", + "danger_zone": "Danger Zone", "are_you_absolutely_sure": "Are you absolutely sure?", "delete_company_modal_desc": "This action cannot be undone. This will permanently delete {company} and all of its associated data.", "delete_company_modal_label": "Please type {company} to confirm" @@ -1188,6 +1200,7 @@ "description": "Manage the roles & permissions of this company", "save": "Save", "add_new_role": "Add New Role", + "system_role": "System Role", "role_name": "Role Name", "added_on": "Added on", "add_role": "Add Role", diff --git a/resources/scripts-v2/api/index.ts b/resources/scripts-v2/api/index.ts index b3937745..7d5a4abc 100644 --- a/resources/scripts-v2/api/index.ts +++ b/resources/scripts-v2/api/index.ts @@ -27,6 +27,7 @@ export { mailService, pdfService, diskService, + updateService, } from './services' // Re-export all service types @@ -55,7 +56,9 @@ export type { FrequencyDateResponse, CustomerListParams, CustomerListResponse, - CustomerStatsData, + CustomerStatsChartData, + CustomerStatsParams, + CustomerStatsResponse, PaymentListParams, PaymentListResponse, SendPaymentPayload, @@ -103,6 +106,7 @@ export type { ModuleInstallPayload, ModuleCheckResponse, Backup, + BackupListResponse, CreateBackupPayload, DeleteBackupParams, MailConfig, @@ -119,5 +123,11 @@ export type { GotenbergConfig, Disk, DiskDriversResponse, + DiskDriverValue, CreateDiskPayload, + CheckUpdateResponse, + UpdateRelease, + UpdateDownloadResponse, + UpdateStepResponse, + FinishUpdatePayload, } from './services' diff --git a/resources/scripts-v2/api/services/auth.service.ts b/resources/scripts-v2/api/services/auth.service.ts index eb5694c5..eb68a21f 100644 --- a/resources/scripts-v2/api/services/auth.service.ts +++ b/resources/scripts-v2/api/services/auth.service.ts @@ -36,6 +36,11 @@ export interface RegisterWithInvitationPayload { email: string password: string password_confirmation: string + invitation_token: string +} + +export interface RegisterWithInvitationResponse { + type: string token: string } @@ -74,8 +79,8 @@ export const authService = { return data }, - async registerWithInvitation(payload: RegisterWithInvitationPayload): Promise> { - const { data } = await client.post(API.REGISTER_WITH_INVITATION, payload) + async registerWithInvitation(payload: RegisterWithInvitationPayload): Promise { + const { data } = await client.post(API.REGISTER_WITH_INVITATION, payload) return data }, } diff --git a/resources/scripts-v2/api/services/backup.service.ts b/resources/scripts-v2/api/services/backup.service.ts index d6f86922..7c417454 100644 --- a/resources/scripts-v2/api/services/backup.service.ts +++ b/resources/scripts-v2/api/services/backup.service.ts @@ -1,28 +1,35 @@ import { client } from '../client' import { API } from '../endpoints' -import type { ApiResponse, ListParams } from '@v2/types/api' export interface Backup { - id: number - disk: string path: string created_at: string - file_size: string + size: string +} + +export interface BackupListResponse { + backups: Backup[] + disks: string[] + error?: string + error_message?: string } export interface CreateBackupPayload { - option: 'full' | 'database' | 'files' - selected_disk: string | null + option: 'full' | 'only-db' | 'only-files' + file_disk_id: number } export interface DeleteBackupParams { disk: string - path?: string - file_name?: string + path: string + file_disk_id?: number } export const backupService = { - async list(params?: ListParams): Promise> { + async list(params: { + disk: string + file_disk_id?: number + }): Promise { const { data } = await client.get(API.BACKUPS, { params }) return data }, @@ -37,7 +44,11 @@ export const backupService = { return data }, - async download(params: { disk: string; path?: string; file_name?: string }): Promise { + async download(params: { + disk: string + path: string + file_disk_id?: number + }): Promise { const { data } = await client.get(API.DOWNLOAD_BACKUP, { params, responseType: 'blob', diff --git a/resources/scripts-v2/api/services/bootstrap.service.ts b/resources/scripts-v2/api/services/bootstrap.service.ts index d8c799ff..c9a241b3 100644 --- a/resources/scripts-v2/api/services/bootstrap.service.ts +++ b/resources/scripts-v2/api/services/bootstrap.service.ts @@ -8,9 +8,10 @@ import type { Ability } from '@v2/types/domain/role' export interface MenuItem { title: string name: string - route: string + link: string icon: string group: string + group_label?: string ability?: string } @@ -27,6 +28,7 @@ export interface BootstrapResponse { config: Record global_settings: Record modules: string[] + admin_mode?: boolean pending_invitations?: Array<{ token: string company_name: string diff --git a/resources/scripts-v2/api/services/custom-field.service.ts b/resources/scripts-v2/api/services/custom-field.service.ts index 2c3b95ac..b7a224f4 100644 --- a/resources/scripts-v2/api/services/custom-field.service.ts +++ b/resources/scripts-v2/api/services/custom-field.service.ts @@ -17,6 +17,7 @@ export interface CreateCustomFieldPayload { is_required?: boolean options?: Array<{ name: string }> | string[] | null order?: number | null + default_answer?: string | null } export const customFieldService = { diff --git a/resources/scripts-v2/api/services/customer.service.ts b/resources/scripts-v2/api/services/customer.service.ts index 89f82eb7..f305fd2a 100644 --- a/resources/scripts-v2/api/services/customer.service.ts +++ b/resources/scripts-v2/api/services/customer.service.ts @@ -24,16 +24,28 @@ export interface CustomerListResponse { meta: CustomerListMeta } -export interface CustomerStatsData { - id: number - name: string - email: string | null - total_invoices: number - total_estimates: number - total_payments: number - total_expenses: number - total_amount_due: number - total_paid: number +export interface CustomerStatsChartData { + salesTotal: number + totalReceipts: number + totalExpenses: number + netProfit: number + expenseTotals: number[] + netProfits: number[] + months: string[] + receiptTotals: number[] + invoiceTotals: number[] +} + +export interface CustomerStatsParams { + previous_year?: boolean + this_year?: boolean +} + +export interface CustomerStatsResponse { + data: Customer + meta: { + chartData: CustomerStatsChartData + } } export const customerService = { @@ -62,7 +74,10 @@ export const customerService = { return data }, - async getStats(id: number, params?: Record): Promise> { + async getStats( + id: number, + params?: CustomerStatsParams + ): Promise { const { data } = await client.get(`${API.CUSTOMER_STATS}/${id}/stats`, { params }) return data }, diff --git a/resources/scripts-v2/api/services/disk.service.ts b/resources/scripts-v2/api/services/disk.service.ts index 78f23937..30f26b1e 100644 --- a/resources/scripts-v2/api/services/disk.service.ts +++ b/resources/scripts-v2/api/services/disk.service.ts @@ -1,54 +1,59 @@ import { client } from '../client' import { API } from '../endpoints' -import type { ApiResponse, ListParams } from '@v2/types/api' +import type { ApiResponse, ListParams, PaginatedResponse } from '@v2/types/api' + +export type DiskDriverValue = + | 'local' + | 's3' + | 's3compat' + | 'doSpaces' + | 'dropbox' export interface Disk { id: number name: string - driver: string + type: string + driver: DiskDriverValue set_as_default: boolean - credentials: Record - created_at: string - updated_at: string + credentials: Record | string | null + company_id?: number | null } export interface DiskDriversResponse { - drivers: string[] - [key: string]: unknown + drivers: Array<{ + name: string + value: DiskDriverValue + }> + default: DiskDriverValue | string } export interface CreateDiskPayload { name: string - selected_driver: string - // S3/S3-compat/DOSpaces fields - key?: string - secret?: string - region?: string - bucket?: string - root?: string - endpoint?: string - // Dropbox fields - token?: string - app?: string + driver: DiskDriverValue + credentials?: Record | string + set_as_default?: boolean } export const diskService = { - async list(params?: ListParams): Promise> { + async list(params?: ListParams): Promise> { const { data } = await client.get(API.DISKS, { params }) return data }, - async get(disk: string): Promise> { + async get(disk: DiskDriverValue): Promise> { const { data } = await client.get(`${API.DISKS}/${disk}`) return data }, - async create(payload: CreateDiskPayload): Promise { + async create(payload: CreateDiskPayload): Promise> { const { data } = await client.post(API.DISKS, payload) return data }, - async update(id: number, payload: Partial): Promise> { + async update( + id: number, + payload: Partial + ): Promise> { const { data } = await client.put(`${API.DISKS}/${id}`, payload) return data }, diff --git a/resources/scripts-v2/api/services/index.ts b/resources/scripts-v2/api/services/index.ts index 8e09e39a..f0338e37 100644 --- a/resources/scripts-v2/api/services/index.ts +++ b/resources/scripts-v2/api/services/index.ts @@ -23,6 +23,7 @@ export { backupService } from './backup.service' export { mailService } from './mail.service' export { pdfService } from './pdf.service' export { diskService } from './disk.service' +export { updateService } from './update.service' // Re-export service types for convenience export type { LoginPayload, LoginResponse, ForgotPasswordPayload, ResetPasswordPayload, RegisterWithInvitationPayload } from './auth.service' @@ -30,7 +31,13 @@ export type { BootstrapResponse, MenuItem, CurrentCompanyResponse } from './boot export type { InvoiceListParams, InvoiceListResponse, SendInvoicePayload, InvoiceStatusPayload, InvoiceTemplatesResponse } from './invoice.service' export type { EstimateListParams, EstimateListResponse, SendEstimatePayload, EstimateStatusPayload, EstimateTemplatesResponse } from './estimate.service' export type { RecurringInvoiceListParams, RecurringInvoiceListResponse, FrequencyDateParams, FrequencyDateResponse } from './recurring-invoice.service' -export type { CustomerListParams, CustomerListResponse, CustomerStatsData } from './customer.service' +export type { + CustomerListParams, + CustomerListResponse, + CustomerStatsChartData, + CustomerStatsParams, + CustomerStatsResponse, +} from './customer.service' export type { PaymentListParams, PaymentListResponse, SendPaymentPayload, CreatePaymentMethodPayload } from './payment.service' export type { ExpenseListParams, ExpenseListResponse, CreateExpenseCategoryPayload } from './expense.service' export type { ItemListParams, ItemListResponse, CreateItemPayload, CreateUnitPayload } from './item.service' @@ -46,7 +53,8 @@ export type { CustomFieldListParams, CreateCustomFieldPayload } from './custom-f export type { CreateNotePayload } from './note.service' export type { CreateExchangeRateProviderPayload, BulkUpdatePayload, ExchangeRateResponse, ActiveProviderResponse } from './exchange-rate.service' export type { Module, ModuleInstallPayload, ModuleCheckResponse } from './module.service' -export type { Backup, CreateBackupPayload, DeleteBackupParams } from './backup.service' +export type { Backup, BackupListResponse, CreateBackupPayload, DeleteBackupParams } from './backup.service' export type { MailConfig, MailConfigResponse, MailDriver, SmtpConfig, MailgunConfig, SesConfig, TestMailPayload } from './mail.service' export type { PdfConfig, PdfConfigResponse, PdfDriver, DomPdfConfig, GotenbergConfig } from './pdf.service' -export type { Disk, DiskDriversResponse, CreateDiskPayload } from './disk.service' +export type { Disk, DiskDriversResponse, DiskDriverValue, CreateDiskPayload } from './disk.service' +export type { CheckUpdateResponse, UpdateRelease, UpdateDownloadResponse, UpdateStepResponse, FinishUpdatePayload } from './update.service' diff --git a/resources/scripts-v2/api/services/invoice.service.ts b/resources/scripts-v2/api/services/invoice.service.ts index e6f73e52..2d86b169 100644 --- a/resources/scripts-v2/api/services/invoice.service.ts +++ b/resources/scripts-v2/api/services/invoice.service.ts @@ -30,10 +30,12 @@ export interface InvoiceListResponse { export interface SendInvoicePayload { id: number - subject?: string - body?: string - from?: string - to?: string + subject?: string | null + body?: string | null + from?: string | null + to?: string | null + cc?: string | null + bcc?: string | null } export interface InvoiceStatusPayload { @@ -43,6 +45,12 @@ export interface InvoiceStatusPayload { export interface SendPreviewParams { id: number + from?: string | null + to?: string | null + cc?: string | null + bcc?: string | null + subject?: string | null + body?: string | null } export interface InvoiceTemplate { diff --git a/resources/scripts-v2/api/services/mail.service.ts b/resources/scripts-v2/api/services/mail.service.ts index 3145bbe5..7f90f0ff 100644 --- a/resources/scripts-v2/api/services/mail.service.ts +++ b/resources/scripts-v2/api/services/mail.service.ts @@ -1,10 +1,7 @@ import { client } from '../client' import { API } from '../endpoints' -export interface MailDriver { - name: string - value: string -} +export type MailDriver = string export interface SmtpConfig { mail_driver: string @@ -46,6 +43,8 @@ export interface MailConfigResponse { export interface TestMailPayload { to: string + subject: string + message: string } export const mailService = { diff --git a/resources/scripts-v2/api/services/member.service.ts b/resources/scripts-v2/api/services/member.service.ts index 1bfdb4ae..a91c83bb 100644 --- a/resources/scripts-v2/api/services/member.service.ts +++ b/resources/scripts-v2/api/services/member.service.ts @@ -31,7 +31,7 @@ export interface UpdateMemberPayload { export interface InviteMemberPayload { email: string - role?: string + role_id: number | null } export interface DeleteMembersPayload { diff --git a/resources/scripts-v2/api/services/pdf.service.ts b/resources/scripts-v2/api/services/pdf.service.ts index 480942b3..c5ac7cc5 100644 --- a/resources/scripts-v2/api/services/pdf.service.ts +++ b/resources/scripts-v2/api/services/pdf.service.ts @@ -1,10 +1,7 @@ import { client } from '../client' import { API } from '../endpoints' -export interface PdfDriver { - name: string - value: string -} +export type PdfDriver = string export interface DomPdfConfig { pdf_driver: string diff --git a/resources/scripts-v2/api/services/setting.service.ts b/resources/scripts-v2/api/services/setting.service.ts index 8c153c5f..4b4411c1 100644 --- a/resources/scripts-v2/api/services/setting.service.ts +++ b/resources/scripts-v2/api/services/setting.service.ts @@ -28,7 +28,7 @@ export interface NumberPlaceholdersParams { } export interface NumberPlaceholder { - description: string + name: string value: string } @@ -102,7 +102,7 @@ export const settingService = { }, // App Version - async getAppVersion(): Promise<{ version: string }> { + async getAppVersion(): Promise<{ version: string; channel: string }> { const { data } = await client.get(API.APP_VERSION) return data }, diff --git a/resources/scripts-v2/api/services/update.service.ts b/resources/scripts-v2/api/services/update.service.ts new file mode 100644 index 00000000..f68aac94 --- /dev/null +++ b/resources/scripts-v2/api/services/update.service.ts @@ -0,0 +1,74 @@ +import { client } from '../client' +import { API } from '../endpoints' + +export interface UpdateRelease { + version: string + description?: string | null + changelog?: string | null + extensions?: Record + min_php_version?: string | null + deleted_files?: string | string[] | null +} + +export interface CheckUpdateResponse { + success?: boolean + release: UpdateRelease | null + is_minor?: boolean +} + +export interface UpdateDownloadResponse { + success: boolean + path?: string | boolean | Record | null +} + +export interface UpdateStepResponse { + success: boolean + path?: string | boolean | Record | null + error?: string | boolean + data?: Record +} + +export interface FinishUpdatePayload { + installed: string + version: string +} + +export const updateService = { + async check(channel: 'stable' | 'insider' = 'stable'): Promise { + const { data } = await client.get(API.CHECK_UPDATE, { + params: { channel }, + }) + + return data + }, + + async download(payload: { version: string }): Promise { + const { data } = await client.post(API.UPDATE_DOWNLOAD, payload) + return data + }, + + async unzip(payload: { path: string }): Promise { + const { data } = await client.post(API.UPDATE_UNZIP, payload) + return data + }, + + async copy(payload: { path: string }): Promise { + const { data } = await client.post(API.UPDATE_COPY, payload) + return data + }, + + async delete(payload: { deleted_files?: string | string[] | null }): Promise { + const { data } = await client.post(API.UPDATE_DELETE, payload) + return data + }, + + async migrate(): Promise { + const { data } = await client.post(API.UPDATE_MIGRATE) + return data + }, + + async finish(payload: FinishUpdatePayload): Promise { + const { data } = await client.post(API.UPDATE_FINISH, payload) + return data + }, +} diff --git a/resources/scripts-v2/components/base/BaseCard.vue b/resources/scripts-v2/components/base/BaseCard.vue index f27969da..d9f97df8 100644 --- a/resources/scripts-v2/components/base/BaseCard.vue +++ b/resources/scripts-v2/components/base/BaseCard.vue @@ -1,5 +1,5 @@