Commit Graph

194 Commits

Author SHA1 Message Date
Darko Gjorgjijoski
8e7c48f532 Move BackupsController and UpdateController to Admin/ namespace directly
Remove single-file Backup/ and Update/ subdirectories. These controllers
now sit alongside CompaniesController, UsersController, etc. in Admin/.
2026-04-03 21:49:30 +02:00
Darko Gjorgjijoski
20ace694fe Fix UpdateController auth: use Bouncer ability instead of company owner check
ensureOwner() checked isOwner() which only verifies company ownership,
not super admin status. Replace with authorize('manage update app')
which uses the proper Bouncer ability gate for platform administration.
2026-04-03 21:45:40 +02:00
Darko Gjorgjijoski
3f5accc0f0 Consolidate Admin/Update: 8 controllers into 1 UpdateController
Merge 7 single-action pipeline controllers (checkVersion, download,
unzip, copy, delete, migrate, finish) into UpdateController with named
methods. Remove dead UpdateController that duplicated the same logic
but wasn't referenced in routes. Extract shared owner check into
private ensureOwner() helper. Route URLs unchanged.
2026-04-03 21:42:45 +02:00
Darko Gjorgjijoski
7bb6d9bcc3 Consolidate Admin/Settings: merge GetSettingsController + UpdateSettingsController into SettingsController 2026-04-03 21:21:13 +02:00
Darko Gjorgjijoski
142899cfd7 Consolidate Admin/Backup: merge ApiController and DownloadBackupController into BackupsController
Inline the respondSuccess() helper, add download() method. Remove the
unnecessary ApiController base class and DownloadBackupController.
2026-04-03 21:18:45 +02:00
Darko Gjorgjijoski
d505677a74 Consolidate Admin/Modules: 10 single-action controllers into 2
ModulesController: index, show, checkToken, enable, disable
ModuleInstallationController: download, upload, unzip, copy, complete
2026-04-03 21:16:18 +02:00
Darko Gjorgjijoski
85b62dfdf8 Refactor exchange rate providers into driver-based architecture
Replace duplicated switch/case blocks across 4 methods with a clean
abstract driver pattern:

- ExchangeRateDriver (abstract): defines getExchangeRate(),
  getSupportedCurrencies(), validateConnection()
- CurrencyFreakDriver, CurrencyLayerDriver, OpenExchangeRateDriver,
  CurrencyConverterDriver: concrete implementations
- ExchangeRateDriverFactory: resolves driver name to class, with
  register() method for module extensibility

Delete ExchangeRateProvidersTrait — all logic now lives in driver
classes and ExchangeRateProviderService. Adding a new exchange rate
provider only requires implementing ExchangeRateDriver and calling
ExchangeRateDriverFactory::register() in a module service provider.
2026-04-03 20:24:03 +02:00
Darko Gjorgjijoski
8f29e8f5de Extract business logic from remaining models to services
New services:
- ExchangeRateProviderService: CRUD, API status checks, currency converter
  URL resolution (extracted 122 lines from ExchangeRateProvider model)
- FileDiskService: create, update, setAsDefault, validateCredentials
  (extracted 97 lines from FileDisk model)
- ItemService: create/update with tax handling (extracted from Item model)
- TransactionService: create/complete/fail (extracted from Transaction model)
- CustomFieldService: create/update with slug generation (extracted from
  CustomField model)

Controllers updated to use constructor-injected services:
ExchangeRateProviderController, DiskController, ItemsController,
CustomFieldsController.
2026-04-03 19:32:37 +02:00
Darko Gjorgjijoski
ece6ce737b Rename Services/Installation to Services/Setup to match controllers 2026-04-03 19:23:32 +02:00
Darko Gjorgjijoski
4f47db9258 Move Mobile/AuthController to Company/Auth and remove Mobile namespace
The Mobile namespace only contained an API auth controller (Sanctum token
login/logout/check) that is not mobile-specific. Relocated to
Company/Auth/AuthController alongside the other auth controllers.
2026-04-03 19:19:09 +02:00
Darko Gjorgjijoski
64c481e963 Rename controller namespaces: drop V1 prefix, clarify roles
V1/Admin     -> Company       (company-scoped controllers)
V1/SuperAdmin -> Admin        (platform-wide admin controllers)
V1/Customer  -> CustomerPortal (customer-facing portal)
V1/Installation -> Setup      (installation wizard)
V1/PDF       -> Pdf           (consistent casing)
V1/Modules   -> Modules       (drop V1 prefix)
V1/Webhook   -> Webhook       (drop V1 prefix)

The V1 prefix served no purpose - API versioning is in the route prefix
(/api/v1/), not the controller namespace. "Admin" was misleading for
company-scoped controllers. "SuperAdmin" is now simply "Admin" for
platform administration.
2026-04-03 19:15:20 +02:00
Darko Gjorgjijoski
0aaf0419c3 Reorganize Admin/General: 14 controllers down to 6
Move global reference data to SuperAdmin:
- CountriesController, CurrenciesController (not company-scoped)

Merge exchange rate operations into ExchangeRateProviderController:
- GetAllUsedCurrenciesController -> usedCurrenciesWithoutRate()
- BulkExchangeRateController -> bulkUpdate()

Consolidate single-action controllers:
- DateFormatsController + TimeFormatsController + TimezonesController -> FormatsController
- NextNumberController + NumberPlaceholdersController -> SerialNumberController
- SearchUsersController merged into SearchController::users()
2026-04-03 19:03:57 +02:00
Darko Gjorgjijoski
c0454613a8 Move customer stats logic from CustomerStatsController to CustomerService 2026-04-03 18:10:59 +02:00
Darko Gjorgjijoski
92872e7e1c Merge ShowReceiptController and UploadReceiptController into ExpensesController 2026-04-03 18:07:07 +02:00
Darko Gjorgjijoski
2191417151 Consolidate ExchangeRate single-action controllers into ExchangeRateProviderController
Merge 4 invocable controllers (GetActiveProvider, GetExchangeRate,
GetSupportedCurrencies, GetUsedCurrencies) as methods on the existing
resource controller: activeProvider(), getRate(), supportedCurrencies(),
usedCurrencies().
2026-04-03 18:03:24 +02:00
Darko Gjorgjijoski
5f389ea3b0 Consolidate single-action controllers into resource controllers
Merge 11 single-action controllers into their parent resource controllers:
- Invoice: send, sendPreview, clone, changeStatus -> InvoicesController
- Estimate: send, sendPreview, clone, convertToInvoice, changeStatus -> EstimatesController
- Payment: send, sendPreview -> PaymentsController

Extract clone and convert business logic from controllers into services:
- InvoiceService: add clone(), changeStatus()
- EstimateService: add clone(), convertToInvoice(), changeStatus()

Previously this logic was inlined in controllers (~80-90 lines each).
2026-04-03 17:55:46 +02:00
Darko Gjorgjijoski
f76f351244 Merge CompanyController into CompaniesController as show() method 2026-04-03 17:45:20 +02:00
Darko Gjorgjijoski
1ca915a0a3 Split CompanyController and introduce standalone User Settings page
Backend:
- Extract user profile methods (show, update, uploadAvatar) from
  CompanyController into new UserProfileController
- CompanyController now only handles company concerns (updateCompany,
  uploadCompanyLogo)
- Remove Account Settings from setting_menu config

Frontend:
- New /admin/user-settings page with 3 tabs: General, Profile Photo,
  Security (password change)
- User dropdown now links to /admin/user-settings instead of
  /admin/settings/account-settings
- Settings sidebar defaults to Company Information as first item
- Remove old monolithic AccountSetting.vue
2026-04-03 17:35:41 +02:00
Darko Gjorgjijoski
6b5e4878fb Consolidate Admin Settings controllers: merge Get/Update pairs
Merge GetCompanySettingsController + UpdateCompanySettingsController into
CompanySettingsController with show() and update() methods.

Merge GetUserSettingsController + UpdateUserSettingsController into
UserSettingsController with show() and update() methods.

Absorb GetCompanyMailConfigurationController into
CompanyMailConfigurationController as getDefaultConfig() method.

Removes 5 single-action controllers, down to 4 Settings controllers.
2026-04-03 17:18:48 +02:00
Darko Gjorgjijoski
bbf46577dc Move global admin controllers from Admin to SuperAdmin namespace
Backup, Update, Modules, and global Settings controllers (mail config,
PDF config, disk management, global settings) are application-wide features
not scoped to a company. Move them from Admin/ to SuperAdmin/ to match the
v3.0 UI structure where these live under Administration.

Company-scoped settings controllers remain in Admin/Settings/.
2026-04-03 16:52:18 +02:00
Darko Gjorgjijoski
129028518d Consolidate PDF classes under app/Services/Pdf with consistent naming
Split PDFService.php (3 classes + 2 interfaces in one file) into separate
files. Move GotenbergPDFDriver from app/Services/PDFDrivers/ into
app/Services/Pdf/. Normalize casing from ALL-CAPS PDF to Pdf throughout:
facade, provider, service, driver factory, and Gotenberg driver.

Fix PaymentService using Barryvdh DomPDF facade directly instead of the
app's PDF facade (bypassed the driver factory). Report controllers also
updated to use the app facade.
2026-04-03 16:18:25 +02:00
Darko Gjorgjijoski
e0b8b86e06 Rename SerialNumberFormatter to SerialNumberService for consistency 2026-04-03 16:09:22 +02:00
Darko Gjorgjijoski
0ce88ab817 Remove app/Space folder and extract model business logic into services
Relocate all 14 files from the catch-all app/Space namespace into proper
locations: data providers to Support/Formatters, installation utilities to
Services/Installation, PDF utils to Services/Pdf, module/update classes to
Services/Module and Services/Update, SiteApi trait to Traits, and helpers
to Support.

Extract ~1,400 lines of business logic from 8 fat models (Invoice, Payment,
Estimate, RecurringInvoice, Company, Customer, Expense, User) into 9 new
service classes with constructor injection. Controllers now depend on
services instead of calling static model methods. Shared item/tax creation
logic consolidated into DocumentItemService.
2026-04-03 15:37:22 +02:00
Darko Gjorgjijoski
23ff69026e Merge branch 'master' into v3.0 2026-04-03 14:36:24 +02:00
Darko Gjorgjijoski
7d9fdb79cc Scope users listing and search to current company (#607)
Add scopeWhereCompany() to User model using whereHas through the
user_company pivot table. Apply it in UsersController::index() and
SearchController so users only see members of their current company.

Previously, the users page showed ALL users across all companies.

Ref #574
2026-04-03 14:34:33 +02:00
Darko Gjorgjijoski
3d871604ae Add company ownership check to clone endpoints (#606)
Verify the source record belongs to the current company before cloning.
Previously, users could clone invoices/estimates from other companies,
leaking sensitive data (amounts, customer details, items, taxes, notes).

The view policy already includes hasCompany() check, so authorizing
view on the source record gates both ability and company ownership.

Ref #574
2026-04-03 14:32:12 +02:00
Darko Gjorgjijoski
1adebe85b9 Scope all bulk deletes to current company and fix inverted ownership transfer (#605)
Bulk delete: filter IDs through whereCompany() before deleting in all
controllers (Invoices, Payments, Items, Expenses, Estimates, Recurring
Invoices). Previously, any user could delete records from other companies
by providing cross-company IDs.

Transfer ownership: fix inverted hasCompany() check that allowed
transferring company ownership to users who do NOT belong to the company,
while blocking users who DO belong.

Ref #567
2026-04-03 14:16:42 +02:00
Darko Gjorgjijoski
defbfc6406 Fix CustomerPolicy missing hasCompany() check (IDOR) (#604)
* Fix CustomerPolicy missing hasCompany() check (cross-company IDOR)

Add $user->hasCompany($customer->company_id) check to view, update,
delete, restore, and forceDelete methods in CustomerPolicy, matching
the pattern used by all other policies (InvoicePolicy, PaymentPolicy,
EstimatePolicy, etc.).

Without this check, a user in Company A with view-customer ability
could access customers belonging to Company B by providing the target
customer's ID.

Add cross-company authorization tests to verify the fix.

Closes #565

* Scope bulk delete to current company to prevent cross-company deletion

Filter customer IDs through whereCompany() before passing to
deleteCustomers(), ensuring users cannot delete customers belonging
to other companies via the bulk delete endpoint.
2026-04-03 13:56:34 +02:00
Darko Gjorgjijoski
9432da467e Add super-admin Administration section and restructure global vs company settings
- Add Administration sidebar section (super-admin only) with Companies, Users, and Global Settings pages
- Add super-admin middleware, controllers, and API routes under /api/v1/super-admin/
- Allow super-admins to manage all companies and users across tenants
- Add user impersonation with short-lived tokens, audit logging, and UI banner
- Move system-level settings (Mail, PDF, Backup, Update, File Disk) from per-company to Administration > Global Settings
- Convert save_pdf_to_disk from CompanySetting to global Setting
- Add per-company mail configuration overrides (optional, falls back to global)
- Add CompanyMailConfigService to apply company mail config before sending emails
2026-04-03 10:35:40 +02:00
Darko Gjorgjijoski
a38f09cf7b Installer reliability improvements (#593)
* docs: add CLAUDE.md for Claude Code guidance

* fix: handle missing settings table in installation middlewares

RedirectIfInstalled crashed with "no such table: settings" when the
database_created marker file existed but the database was empty.
Changed to use isDbCreated() which verifies actual tables, and added
try-catch around Setting queries in both middlewares.

* feat: pre-select database driver from env in installation wizard

The database step now reads DB_CONNECTION from the environment and
pre-selects the matching driver on load, including correct defaults
for hostname and port.

* feat: pre-select mail driver and config from env in installation wizard

The email step now fetches the current mail configuration on load
instead of hardcoding the driver to 'mail'. SMTP fields fall back
to Laravel config values from the environment.

* refactor: remove file-based DB marker in favor of direct DB checks

The database_created marker file was a second source of truth that
could drift out of sync with the actual database. InstallUtils now
checks the database directly via Schema::hasTable which is cached
per-request and handles all error cases gracefully.
2026-04-02 14:48:08 +02:00
mchev
80889293bf Merge pull request #508 from alexdev01012020/email-backup
Overrite the email notification for backup
2026-03-27 08:04:02 +01:00
mchev
3bae2c282c Merge pull request #576 from csalzano/fix/remote-disk-backup-listing
Fix remote disk backups never appear in backup listing
2026-03-26 09:16:13 +01:00
Corey Salzano
14b5aaa0c9 Runs pint 2026-03-25 09:09:33 -04:00
mchev
c901114fc0 Pint 2026-03-21 18:59:53 +01:00
mchev
186ab35fd4 Laravel 13 upgrade, updates and fixes 2026-03-21 18:53:33 +01:00
Corey Salzano
3c31baf20d fix(backup): remote disk backups never appear in backup listing
Three bugs prevented backups stored on remote disks (Dropbox, S3, etc.)
from ever appearing in the Settings > Backup listing:

1. Typo in BackupSetting.vue: `filed_disk_id` was sent to the API
   instead of `file_disk_id`, so the backend never received the selected
   disk ID and always fell back to the local filesystem.

2. Wrong default disk selection in loadDisksData(): `set_as_default == 0`
   selected the first disk that is NOT default (local_public), instead of
   the disk that IS default. Changed to `set_as_default == 1` with a
   fallback to the first disk.

3. BackupsController::index() did not call setConfig() on the FileDisk
   before querying backups, so even when the correct file_disk_id arrived
   it still read from the default local filesystem. Added the same disk
   bootstrap logic already present in CreateBackupJob and destroy().

Made-with: Cursor
2026-03-17 19:32:40 -04:00
Alex
a386fd19fc Merge remote-tracking branch 'upstream/master' into email-backup 2026-02-15 12:37:13 +02:00
Devstack
af205acb75 Fix aggregates for customers using withSum() (SQL-portable, no DB mode tweaks) (#486)
* Update CustomersController.php

Fix: replace GROUP BY + SUM join with withSum() to avoid ONLY_FULL_GROUP_BY; no API changes (same aliases)

* Update CustomersController.php

style: apply Laravel Pint formatting
2026-01-01 22:43:52 +01:00
Alex
d2953e9409 Overrite the email notification for backup 2025-11-05 19:14:55 +02:00
Darko Gjorgjijoski
18d63a3375 Configurations cleanup & database configurations for mail and pdfs (#479)
* Move Mail, PDF configuration to Database, standardize configurations

* Set default currency to USD on install

* Pint code
2025-09-19 15:42:53 +02:00
Darko Gjorgjijoski
f1635bcef8 Fix SQLite docker build related issues (#458) 2025-09-01 02:42:07 +02:00
Darko Gjorgjijoski
bae8dbe083 Upgrade mail configuration (#455)
* Upgrade the mail configuration

* Update mail configuration to match Laravel 12

* Update mail configuration to properly set none or null

* Pint code

* Upgrade Symfony Mailers
2025-08-31 03:04:31 +02:00
lupus
d5137e393d Fix: Use amounts in base currency for customer charts (#403)
Previously, the customer chart used the total/amount fields to calculate net profits/expenses/etc.
If the currency the expense (for example) was created in differed from the base currency of the company, the chart would display wrong amounts.
This change addresses the issue by always using the base currency field.
2025-08-30 12:30:49 +02:00
Darko Gjorgjijoski
a40bf5840d Dynamically load language files (#446) 2025-08-28 15:19:51 +02:00
Loduis Madariaga Barrios
8e96d3e972 fix(csrf-token): add leading dot to session domain cookie. (#224)
* fix(csrf-token): add leading dot to session domain cookie.

* refactor: remove generate key, upgrade axios and keep session domain in null.

* refactor: fix PSR-12 code styles for PHP 8.2 compatibility.

---------

Co-authored-by: Darko Gjorgjijoski <5760249+gdarko@users.noreply.github.com>
2025-08-28 09:44:34 +02:00
Tim van Osch
bf40f792c2 Feat(Gotenberg): Opt-in alternative pdf generation for modern CSS (#184)
* WIP(gotenberg): add pdf generation abstraction and UI

* feat(pdf): settings validate(clien+server) & save

* fix(gotenberg): Use correct default papersize
chore(gotengberg): Remove unused GOTENBERG_MARGINS env from .env

* style(gotenberg): fix linter/styling issues

* fix(pdf): use pdf config policy

* fix: revert accidental capitalization in mail config vue

* Update composer, remove whitespace typo

* Fix small typos

* fix cookie/env issue

* Add gotenberg to .dev, move admin menu item up
2025-05-04 02:10:15 +02:00
Fabio Ribeiro
8a9392e400 Fix: AWS SES Mailer (#365)
As reported on issue #357, the aws ses configuration was not able to
store because of the missing `ses` service config. Additionally was
added a `AWS Region` field to be used by the `ses`.

closes #357
2025-05-02 11:16:31 +02:00
Yannic Inselmann
b32c334a71 feat: default notes (#263)
* feat: default notes

* feat: include default invoice note in recurring invoice

* feat: use default export in tw config

* fix: test and naming

* fix: consistent ui for switch in note modal

* feat: little text improvements
2025-04-05 12:01:06 +02:00
mchev
2aa17513e1 Check version number on version.md file (#280) 2025-04-05 10:16:23 +02:00
Darko Gjorgjijoski
d862ee05e9 Refactor Custom Invoice/Estimate PDF Templates (#277)
* Add utility class for managing templates

* Register custom pdf template views location

* Update the make:template command to make use of PdfTemplateUtils

* Update PDF invoice/estimate template controllers

* Register pdf_templates filesystem disk

* Remove unused leftovers

* Reformat with pint
2025-01-13 01:20:13 +01:00