42 Commits
2.2.1 ... 2.3.1

Author SHA1 Message Date
Darko Gjorgjijoski
88650c2f3e Bump version 2026-04-05 12:34:24 +02:00
Darko Gjorgjijoski
ee76f31138 Add log mail driver support to frontend
The default mail driver in config/mail.php is 'log', which had no
matching Vue component, causing the mail configuration step in the
install wizard (and settings page) to render empty.
2026-04-05 12:33:14 +02:00
Darko Gjorgjijoski
e1af9f56c4 Docker optimizations 2026-04-05 12:07:47 +02:00
mchev
fdd860c381 Merge pull request #612 from mchev/master
Ensure public/storage symlink exists in Docker production entrypoint
2026-04-04 18:57:09 +02:00
Darko Gjorgjijoski
5ff051fbb5 Bump version from 2.2.1 to 2.3.0 2026-04-04 17:12:13 +02:00
Darko Gjorgjijoski
0d7059fcf6 Fix logout/re-login CSRF mismatch and stale token issues
Cherry-picked from v3.0 branch. Three fixes:
1. Refresh CSRF cookie after logout (auth.js)
2. Clear auth.token and selectedCompany from localStorage on logout (auth.js)
3. Invalidate session and regenerate CSRF token on server-side logout (web.php)

Without these, logging out and back in as a different user would fail
with CSRF token mismatch and 401 Unauthenticated errors because the
browser held stale session cookies and localStorage tokens.
2026-04-03 23:53:56 +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
25986b7bd5 Update IDE helpers, Tailwind skill to v4, and refresh dependencies 2026-04-02 21:03:55 +02:00
Darko Gjorgjijoski
751bd4a1c8 Upgrade ChartJS from v2 to v4 (#603) 2026-04-02 20:02:49 +02:00
Darko Gjorgjijoski
0e313b80ca Upgrade @vueuse/core from v12 to v14 (#602) 2026-04-02 18:09:43 +02:00
Darko Gjorgjijoski
5014a75fbc Upgrade eslint tooling to v10 and fix linting bugs (#601)
- Upgrade eslint 9→10, eslint-config-prettier 9→10, eslint-plugin-vue 9→10
- Upgrade @types/node 20→24
- Migrate from legacy .eslintrc.mjs to flat config eslint.config.mjs
- Remove --ext flag from npm test script (dropped in eslint 10)
- Fix vue/no-ref-as-operand: add missing .value to ref assignments (5 files)
- Fix vue/return-in-computed-property: add default returns (2 files)
- Fix vue/no-side-effects-in-computed-properties: move mutation to watcher
- Fix vue/no-dupe-keys: remove ref shadowing prop in DomPDFDriver
- Fix vue/no-deprecated-slot-attribute: migrate to v-slot syntax (3 files)
- Fix vue/require-valid-default-prop: use factory function for array default
- Fix vue/no-unused-vars: remove unused slot destructure
- Disable vue/no-mutating-props (false positive for Pinia store props)
2026-04-02 17:33:18 +02:00
Darko Gjorgjijoski
9e5b9fdaad Upgrade vue-flatpickr-component from v11 to v12 (#600) 2026-04-02 17:17:17 +02:00
Darko Gjorgjijoski
08dfe62312 Standardize Node.js version to 24 (#599)
Update Node.js from 20 to 24 across CI workflows, Dockerfiles,
package.json engines field, and add .node-version file for consistent
local development.
2026-04-02 17:08:39 +02:00
Darko Gjorgjijoski
f623cd0179 Upgrade vue-router from v4 to v5 (#598)
- Migrate beforeEach navigation guard from next() callback to return-based
  API, preparing for vue-router v6 where next() is removed
2026-04-02 16:47:16 +02:00
mchev
77fd96d499 Merge branch 'master' of https://github.com/mchev/InvoiceShelf 2026-04-02 16:43:57 +02:00
mchev
2e4e19dfc5 Remove testing image 2026-04-02 16:43:22 +02:00
mchev
76c02be219 Merge branch 'InvoiceShelf:master' into master 2026-04-02 16:42:13 +02:00
Darko Gjorgjijoski
414531524c Remove unused cross-env dependency 2026-04-02 16:39:10 +02:00
Darko Gjorgjijoski
d75a957183 Upgrade Tiptap from v2 to v3 (#597)
- Upgrade @tiptap/core, starter-kit, vue-3, pm, extension-text-align to v3
- Remove @tiptap/extension-link (now bundled in StarterKit v3)
- Move Link config into StarterKit.configure()
2026-04-02 16:35:43 +02:00
Darko Gjorgjijoski
63d3a7fc8e Skip PHP CI jobs when only non-PHP files change
Replace workflow-level paths-ignore with per-job filtering using
dorny/paths-filter. PHP lint and test jobs now only run when PHP-related
files (app/, config/, database/, routes/, tests/, composer.*, phpunit.xml)
are modified.
2026-04-02 16:35:07 +02:00
Darko Gjorgjijoski
0be747a483 Pin axios to 1.14.0
Avoid possible supply chain attacks in future caused by compromised
author account or even rogue author.
2026-04-02 16:14:47 +02:00
Darko Gjorgjijoski
3ceb08bc31 Upgrade Pinia from v2 to v3 (#596)
Migrate all 37 store definitions from the deprecated object-with-id
signature to the string-id-first signature required by Pinia 3:

  defineStore({ id: 'name', ... }) → defineStore('name', { ... })
2026-04-02 16:12:11 +02:00
Darko Gjorgjijoski
ad5a7e51b9 Upgrade to Vite 8 and Tailwind CSS 4 (#595)
- Vite 6 → 8 (Rolldown bundler), laravel-vite-plugin 1 → 3, @vitejs/plugin-vue 5 → 6
- Tailwind CSS 3 → 4 with CSS-based config (@theme, @plugin, @utility)
- Add @tailwindcss/vite plugin, remove postcss/autoprefixer/sass
- Convert SCSS files to plain CSS (resources/sass → resources/css)
- Migrate tailwind.config.js to CSS @theme directives
- Rename deprecated utility classes (shadow-sm→shadow-xs, outline-none→outline-hidden,
  rounded-sm→rounded-xs, bg-gradient-to→bg-linear-to, ring→ring-3)
- Migrate opacity utilities to color modifiers (bg-opacity, text-opacity,
  border-opacity, ring-opacity → color/N syntax)
- Update primary color CSS vars to full rgb() values for TW4 color-mix()
- Fix border-l color specificity for sidebar navigation (TW4 default border
  color changed from gray-200 to currentColor)
- Fix invalid border color classes (border-grey-light, border-modal-bg, border--200)
- Add @reference directive for @apply in Vue component style blocks
- Convert Vue component <style lang="scss"> blocks to plain CSS
2026-04-02 15:59:15 +02:00
Darko Gjorgjijoski
691178857f Add HTTP client wrapper and upgrade Axios to v1 (#594)
* refactor: add HTTP client wrapper and upgrade axios to v1

Introduce a thin HTTP wrapper (resources/scripts/http) that centralizes
axios configuration, interceptors, and auth header injection. All 43
files now import from the wrapper instead of axios directly, making
future library swaps a single-file change. Upgrade axios from 0.30.0
to 1.14.0.

* fix: restore window.Ls assignment removed during axios refactor

company.js uses window.Ls.set() to persist selected company,
which broke after the axios plugin (that set window.Ls) was deleted.
2026-04-02 15:08:23 +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
de4ba6bba0 Fix storage link on docker 2026-04-02 11:56:16 +02:00
mchev
375cfc6b18 Merge pull request #591 from mchev/588
Fix PDF notes line breaks
2026-04-01 21:38:14 +02:00
mchev
aa88dc340d Closes #588 2026-04-01 21:30:32 +02:00
mchev
7004bf375e Merge pull request #587 from rihards-simanovics/rihards-simanovics/issue586
Fix docker containers failing to create SQL backups due to missing OS dependencies
2026-03-27 08:15:43 +01: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
Rihards Simanovics
d754c4d29e fix: return missing development docker sql dependencies
Fixes #586
2026-03-27 00:18:15 +00:00
Rihards Simanovics
8de32e27d3 fix: return missing production docker sql dependencies
Fixes #586
2026-03-27 00:17:56 +00: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
Corey Salzano
aafcf147cf Updates the "create backup" test to handle the disk prefix. 2026-03-24 23:00:47 -04: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
Alex
e5f6984078 Apply review feedback 2026-02-06 16:17:57 +02:00
Alex
d2953e9409 Overrite the email notification for backup 2025-11-05 19:14:55 +02:00
193 changed files with 17463 additions and 10010 deletions

View File

@@ -10,7 +10,7 @@ metadata:
## Documentation
Use `search-docs` for detailed Tailwind CSS v3 patterns and documentation.
Use `search-docs` for detailed Tailwind CSS v4 patterns and documentation.
## Basic Usage
@@ -18,22 +18,55 @@ Use `search-docs` for detailed Tailwind CSS v3 patterns and documentation.
- Offer to extract repeated patterns into components that match the project's conventions (e.g., Blade, JSX, Vue).
- Consider class placement, order, priority, and defaults. Remove redundant classes, add classes to parent or child elements carefully to reduce repetition, and group elements logically.
## Tailwind CSS v3 Specifics
## Tailwind CSS v4 Specifics
- Always use Tailwind CSS v3 and verify you're using only classes it supports.
- Configuration is done in the `tailwind.config.js` file.
- Import using `@tailwind` directives:
- Always use Tailwind CSS v4 and avoid deprecated utilities.
- `corePlugins` is not supported in Tailwind v4.
<!-- v3 Import Syntax -->
### CSS-First Configuration
In Tailwind v4, configuration is CSS-first using the `@theme` directive — no separate `tailwind.config.js` file is needed:
<!-- CSS-First Config -->
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
@theme {
--color-brand: oklch(0.72 0.11 178);
}
```
### Import Syntax
In Tailwind v4, import Tailwind with a regular CSS `@import` statement instead of the `@tailwind` directives used in v3:
<!-- v4 Import Syntax -->
```diff
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
+ @import "tailwindcss";
```
### Replaced Utilities
Tailwind v4 removed deprecated utilities. Use the replacements shown below. Opacity values remain numeric.
| Deprecated | Replacement |
|------------|-------------|
| bg-opacity-* | bg-black/* |
| text-opacity-* | text-black/* |
| border-opacity-* | border-black/* |
| divide-opacity-* | divide-black/* |
| ring-opacity-* | ring-black/* |
| placeholder-opacity-* | placeholder-black/* |
| flex-shrink-* | shrink-* |
| flex-grow-* | grow-* |
| overflow-ellipsis | text-ellipsis |
| decoration-slice | box-decoration-slice |
| decoration-clone | box-decoration-clone |
## Spacing
When listing items, use gap utilities for spacing; don't use margins.
Use `gap` utilities instead of margins for spacing between siblings:
<!-- Gap Utilities -->
```html
@@ -77,15 +110,10 @@ If existing pages and components support dark mode, new pages and components mus
</div>
```
## Verification
1. Check browser for visual rendering
2. Test responsive breakpoints
3. Verify dark mode if project uses it
## Common Pitfalls
- Using deprecated v3 utilities (bg-opacity-*, flex-shrink-*, etc.)
- Using `@tailwind` directives instead of `@import "tailwindcss"`
- Trying to use `tailwind.config.js` instead of CSS `@theme` directive
- Using margins for spacing between siblings instead of gap utilities
- Forgetting to add dark mode variants when the project uses dark mode
- Not checking existing project conventions before adding new utilities
- Overusing inline styles when Tailwind classes would suffice
- Forgetting to add dark mode variants when the project uses dark mode

View File

@@ -1,14 +0,0 @@
// .eslintrc.js
module.exports = {
extends: [
// add more generic rulesets here, such as:
// 'eslint:recommended',
"plugin:vue/vue3-recommended",
"prettier",
],
rules: {
// override/add rules settings here, such as:
// 'vue/no-unused-vars': 'error'
},
};

View File

@@ -3,28 +3,41 @@ name: Check
# Run this workflow every time a new commit pushed to your repository
on:
push:
paths-ignore:
- '.github/**/*'
- '**/*.md'
- 'public/build/*.js'
- 'public/build/**/*.js'
- 'docker/**/*'
- 'resources/scripts/**/*'
tags-ignore:
- "*"
branches-ignore:
- 'translations'
pull_request:
paths-ignore:
- '**/*.md'
- 'public/build/*.js'
- 'public/build/**/*.js'
branches-ignore:
- 'translations'
# Allow manually triggering the workflow.
workflow_dispatch:
jobs:
changes:
name: 🔍 Detect changes
runs-on: ubuntu-latest
outputs:
php: ${{ steps.filter.outputs.php }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Check for file changes
uses: dorny/paths-filter@v3
id: filter
with:
filters: |
php:
- 'app/**'
- 'config/**'
- 'database/**'
- 'routes/**'
- 'tests/**'
- 'composer.json'
- 'composer.lock'
- 'phpunit.xml'
kill_previous:
name: 0⃣ Kill previous runs
runs-on: ubuntu-latest
@@ -41,6 +54,8 @@ jobs:
runs-on: ubuntu-latest
needs:
- kill_previous
- changes
if: needs.changes.outputs.php == 'true'
steps:
- name: Set up PHP
uses: shivammathur/setup-php@v2
@@ -60,6 +75,8 @@ jobs:
name: 2⃣ PHP ${{ matrix.php-version }} Tests
needs:
- php_syntax_errors
- changes
if: needs.changes.outputs.php == 'true'
runs-on: ubuntu-latest
strategy:
matrix:
@@ -83,10 +100,10 @@ jobs:
- name: Install Composer dependencies
uses: ramsey/composer-install@v2
- name: Use Node.js 20
- name: Use Node.js 24
uses: actions/setup-node@v4
with:
node-version: 20
node-version: 24
- name: Install
run: npm install

View File

@@ -61,10 +61,10 @@ jobs:
- name: Install Composer dependencies
uses: ramsey/composer-install@v2
- name: Use Node.js 20
- name: Use Node.js 24
uses: actions/setup-node@v4
with:
node-version: 20
node-version: 24
- name: Install
run: npm install
@@ -100,10 +100,10 @@ jobs:
with:
composer-options: --no-dev
- name: Use Node.js 20
- name: Use Node.js 24
uses: actions/setup-node@v4
with:
node-version: 20
node-version: 24
- name: Install
run: npm install

1
.node-version Normal file
View File

@@ -0,0 +1 @@
24

5909
.phpstorm.meta.php Normal file

File diff suppressed because it is too large Load Diff

80
CLAUDE.md Normal file
View File

@@ -0,0 +1,80 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
InvoiceShelf is an open-source invoicing and expense tracking application built with Laravel 13 (PHP 8.4) and Vue 3. It supports multi-company tenancy, customer portals, recurring invoices, and PDF generation.
## Common Commands
### Development
```bash
composer run dev # Starts PHP server, queue listener, log tail, and Vite dev server concurrently
npm run dev # Vite dev server only
npm run build # Production frontend build
```
### Testing
```bash
php artisan test --compact # Run all tests
php artisan test --compact --filter=testName # Run specific test
./vendor/bin/pest --stop-on-failure # Run via Pest directly
make test # Makefile shortcut
```
Tests use SQLite in-memory DB, configured in `phpunit.xml`. Tests seed via `DatabaseSeeder` + `DemoSeeder` in `beforeEach`. Authenticate with `Sanctum::actingAs()` and set the `company` header.
### Code Style
```bash
vendor/bin/pint --dirty --format agent # Fix style on modified PHP files
vendor/bin/pint --test # Check style without fixing (CI uses this)
```
### Artisan Generators
Always use `php artisan make:*` with `--no-interaction` to create new files (models, controllers, migrations, tests, etc.).
## Architecture
### Multi-Tenancy
Every major model has a `company_id` foreign key. The `CompanyMiddleware` sets the active company from the `company` request header. Bouncer authorization is scoped to the company level via `DefaultScope` (`app/Bouncer/Scopes/DefaultScope.php`).
### Authentication
Three guards: `web` (session), `api` (Sanctum tokens for `/api/v1/`), `customer` (session for customer portal). API routes use `auth:sanctum` middleware; customer portal uses `auth:customer`.
### Routing
- **API**: All endpoints under `/api/v1/` in `routes/api.php`, grouped with `auth:sanctum`, `company`, and `bouncer` middleware
- **Web**: `routes/web.php` serves PDF endpoints, auth pages, and catch-all SPA routes (`/admin/{vue?}`, `/{company:slug}/customer/{vue?}`)
### Frontend
- Entry point: `resources/scripts/main.js`
- Vue Router: `resources/scripts/admin/admin-router.js` (admin), `resources/scripts/customer/customer-router.js` (customer portal)
- State: Pinia stores in `resources/scripts/admin/stores/`
- Path aliases: `@` = `resources/`, `$fonts`, `$images` for static assets
- Vite dev server expects `invoiceshelf.test` hostname
### Backend Patterns
- **Authorization**: Silber/Bouncer with policies in `app/Policies/`. Controllers use `$this->authorize()`.
- **Validation**: Form Request classes, never inline validation
- **API responses**: Eloquent API Resources in `app/Http/Resources/`
- **PDF generation**: DomPDF (`GeneratesPdfTrait`) or Gotenberg
- **Email**: Mailable classes with `EmailLog` tracking
- **File storage**: Spatie MediaLibrary, supports local/S3/Dropbox
- **Serial numbers**: `SerialNumberFormatter` service
- **Company settings**: `CompanySetting` model (key-value per company)
### Database
Supports MySQL, PostgreSQL, and SQLite. Prefer Eloquent over raw queries. Use `Model::query()` instead of `DB::`. Use eager loading to prevent N+1 queries.
## Code Conventions
- PHP: snake_case, constructor property promotion, explicit return types, PHPDoc blocks over inline comments
- JS: camelCase
- Always check sibling files for patterns before creating new ones
- Use `config()` helper, never `env()` outside config files
- Every change must have tests (feature tests preferred over unit tests)
- Run `vendor/bin/pint --dirty --format agent` after modifying PHP files
## CI Pipeline
GitHub Actions (`check.yaml`): runs Pint style check, then builds frontend and runs Pest tests on PHP 8.4.

File diff suppressed because it is too large Load Diff

View File

@@ -24,7 +24,7 @@ class ResetApp extends Command
*
* @var string
*/
protected $description = 'Clean database, database_created and public/storage folder';
protected $description = 'Clean database and public/storage folder';
/**
* Create a new command instance.

View File

@@ -5,6 +5,7 @@
namespace App\Http\Controllers\V1\Admin\Backup;
use App\Jobs\CreateBackupJob;
use App\Models\FileDisk;
use App\Rules\Backup\PathToZip;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
@@ -27,6 +28,16 @@ class BackupsController extends ApiController
$configuredBackupDisks = config('backup.backup.destination.disks');
try {
if ($request->file_disk_id) {
$fileDisk = FileDisk::find($request->file_disk_id);
if ($fileDisk) {
$fileDisk->setConfig();
$prefix = env('DYNAMIC_DISK_PREFIX', 'temp_');
config(['backup.backup.destination.disks' => [$prefix.$fileDisk->driver]]);
$configuredBackupDisks = config('backup.backup.destination.disks');
}
}
$backupDestination = BackupDestination::create(config('filesystems.default'), config('backup.backup.name'));
$backups = Cache::remember("backups-{$request->file_disk_id}", now()->addSeconds(4), function () use ($backupDestination) {
@@ -65,7 +76,10 @@ class BackupsController extends ApiController
{
$this->authorize('manage backups');
dispatch(new CreateBackupJob($request->all()))->onQueue(config('backup.queue.name'));
$data = $request->all();
$data['company'] = $request->header('company');
dispatch(new CreateBackupJob($data))->onQueue(config('backup.queue.name'));
return $this->respondSuccess();
}

View File

@@ -61,10 +61,10 @@ class CompaniesController extends Controller
$company = Company::find($request->header('company'));
$this->authorize('transfer company ownership', $company);
if ($user->hasCompany($company->id)) {
if (! $user->hasCompany($company->id)) {
return response()->json([
'success' => false,
'message' => 'User does not belongs to this company.',
'message' => 'User does not belong to this company.',
]);
}

View File

@@ -92,7 +92,11 @@ class CustomersController extends Controller
{
$this->authorize('delete multiple customers');
Customer::deleteCustomers($request->ids);
$ids = Customer::whereCompany()
->whereIn('id', $request->ids)
->pluck('id');
Customer::deleteCustomers($ids);
return response()->json([
'success' => true,

View File

@@ -21,6 +21,7 @@ class CloneEstimateController extends Controller
*/
public function __invoke(Request $request, Estimate $estimate)
{
$this->authorize('view', $estimate);
$this->authorize('create', Estimate::class);
$date = Carbon::now();

View File

@@ -68,7 +68,11 @@ class EstimatesController extends Controller
{
$this->authorize('delete multiple estimates');
Estimate::destroy($request->ids);
$ids = Estimate::whereCompany()
->whereIn('id', $request->ids)
->pluck('id');
Estimate::destroy($ids);
return response()->json([
'success' => true,

View File

@@ -81,7 +81,11 @@ class ExpensesController extends Controller
{
$this->authorize('delete multiple expenses');
Expense::destroy($request->ids);
$ids = Expense::whereCompany()
->whereIn('id', $request->ids)
->pluck('id');
Expense::destroy($ids);
return response()->json([
'success' => true,

View File

@@ -25,7 +25,8 @@ class SearchController extends Controller
->paginate(10);
if ($user->isOwner()) {
$users = User::applyFilters($request->only(['search']))
$users = User::whereCompany()
->applyFilters($request->only(['search']))
->latest()
->paginate(10);
}

View File

@@ -21,6 +21,7 @@ class CloneInvoiceController extends Controller
*/
public function __invoke(Request $request, Invoice $invoice)
{
$this->authorize('view', $invoice);
$this->authorize('create', Invoice::class);
$date = Carbon::now();

View File

@@ -100,7 +100,11 @@ class InvoicesController extends Controller
{
$this->authorize('delete multiple invoices');
Invoice::deleteInvoices($request->ids);
$ids = Invoice::whereCompany()
->whereIn('id', $request->ids)
->pluck('id');
Invoice::deleteInvoices($ids);
return response()->json([
'success' => true,

View File

@@ -90,7 +90,11 @@ class ItemsController extends Controller
{
$this->authorize('delete multiple items');
Item::destroy($request->ids);
$ids = Item::whereCompany()
->whereIn('id', $request->ids)
->pluck('id');
Item::destroy($ids);
return response()->json([
'success' => true,

View File

@@ -73,7 +73,11 @@ class PaymentsController extends Controller
{
$this->authorize('delete multiple payments');
Payment::deletePayments($request->ids);
$ids = Payment::whereCompany()
->whereIn('id', $request->ids)
->pluck('id');
Payment::deletePayments($ids);
return response()->json([
'success' => true,

View File

@@ -84,7 +84,11 @@ class RecurringInvoiceController extends Controller
{
$this->authorize('delete multiple recurring invoices');
RecurringInvoice::deleteRecurringInvoice($request->ids);
$ids = RecurringInvoice::whereCompany()
->whereIn('id', $request->ids)
->pluck('id');
RecurringInvoice::deleteRecurringInvoice($ids);
return response()->json([
'success' => true,

View File

@@ -165,11 +165,11 @@ class MailConfigurationController extends Controller
switch ($driver) {
case 'smtp':
$MailData = array_merge($MailData, [
'mail_host' => $mailSettings['mail_host'] ?? '',
'mail_port' => $mailSettings['mail_port'] ?? '',
'mail_username' => $mailSettings['mail_username'] ?? '',
'mail_password' => $mailSettings['mail_password'] ?? '',
'mail_encryption' => $mailSettings['mail_encryption'] ?? 'none',
'mail_host' => $mailSettings['mail_host'] ?? config('mail.mailers.smtp.host', ''),
'mail_port' => $mailSettings['mail_port'] ?? config('mail.mailers.smtp.port', ''),
'mail_username' => $mailSettings['mail_username'] ?? config('mail.mailers.smtp.username', ''),
'mail_password' => $mailSettings['mail_password'] ?? config('mail.mailers.smtp.password', ''),
'mail_encryption' => $mailSettings['mail_encryption'] ?? config('mail.mailers.smtp.encryption', 'none'),
'mail_scheme' => $mailSettings['mail_scheme'] ?? '',
'mail_url' => $mailSettings['mail_url'] ?? '',
'mail_timeout' => $mailSettings['mail_timeout'] ?? '',

View File

@@ -25,14 +25,15 @@ class UsersController extends Controller
$user = $request->user();
$users = User::applyFilters($request->all())
$users = User::whereCompany()
->applyFilters($request->all())
->where('id', '<>', $user->id)
->latest()
->paginate($limit);
return UserResource::collection($users)
->additional(['meta' => [
'user_total_count' => User::count(),
'user_total_count' => User::whereCompany()->count(),
]]);
}

View File

@@ -48,8 +48,9 @@ class DatabaseConfigurationController extends Controller
public function getDatabaseEnvironment(Request $request)
{
$databaseData = [];
$connection = $request->connection ?? config('database.default');
switch ($request->connection) {
switch ($connection) {
case 'sqlite':
$databaseData = [
'database_connection' => 'sqlite',

View File

@@ -3,7 +3,6 @@
namespace App\Http\Controllers\V1\Installation;
use App\Http\Controllers\Controller;
use App\Space\InstallUtils;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
@@ -16,10 +15,6 @@ class FinishController extends Controller
*/
public function __invoke(Request $request)
{
if (! InstallUtils::createDbMarker()) {
\Log::error('Install: Unable to create db marker.');
}
return response()->json(['success' => true]);
}
}

View File

@@ -17,7 +17,7 @@ class OnboardingWizardController extends Controller
*/
public function getStep(Request $request)
{
if (! InstallUtils::dbMarkerExists()) {
if (! InstallUtils::isDbCreated()) {
return response()->json([
'profile_complete' => 0,
'profile_language' => 'en',

View File

@@ -17,7 +17,11 @@ class InstallationMiddleware
*/
public function handle(Request $request, Closure $next): Response
{
if (! InstallUtils::isDbCreated() || Setting::getSetting('profile_complete') !== 'COMPLETED') {
try {
if (! InstallUtils::isDbCreated() || Setting::getSetting('profile_complete') !== 'COMPLETED') {
return redirect('/installation');
}
} catch (\Exception $e) {
return redirect('/installation');
}

View File

@@ -17,9 +17,13 @@ class RedirectIfInstalled
*/
public function handle(Request $request, Closure $next): Response
{
if (InstallUtils::dbMarkerExists()) {
if (Setting::getSetting('profile_complete') === 'COMPLETED') {
return redirect('login');
if (InstallUtils::isDbCreated()) {
try {
if (Setting::getSetting('profile_complete') === 'COMPLETED') {
return redirect('login');
}
} catch (\Exception $e) {
// Settings table may not exist yet during installation
}
}

View File

@@ -2,13 +2,12 @@
namespace App\Jobs;
use App\Models\FileDisk;
use App\Space\BackupConfigurationFactory;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Spatie\Backup\Config\Config;
use Spatie\Backup\Tasks\Backup\BackupJobFactory;
class CreateBackupJob implements ShouldQueue
@@ -25,7 +24,7 @@ class CreateBackupJob implements ShouldQueue
*
* @return void
*/
public function __construct($data = '')
public function __construct($data = [])
{
$this->data = $data;
}
@@ -35,14 +34,7 @@ class CreateBackupJob implements ShouldQueue
*/
public function handle(): void
{
$fileDisk = FileDisk::find($this->data['file_disk_id']);
$fileDisk->setConfig();
$prefix = env('DYNAMIC_DISK_PREFIX', 'temp_');
config(['backup.backup.destination.disks' => [$prefix.$fileDisk->driver]]);
$config = Config::fromArray(config('backup'));
$config = BackupConfigurationFactory::make($this->data);
$backupJob = BackupJobFactory::createFromConfig($config);
if (! defined('SIGINT')) {
$backupJob->disableSignals();

View File

@@ -213,6 +213,13 @@ class User extends Authenticatable implements HasMedia
return $query->where('email', 'LIKE', '%'.$email.'%');
}
public function scopeWhereCompany($query)
{
return $query->whereHas('companies', function ($q) {
$q->where('company_id', request()->header('company'));
});
}
public function scopePaginateData($query, $limit)
{
if ($limit == 'all') {

View File

@@ -32,7 +32,7 @@ class CustomerPolicy
*/
public function view(User $user, Customer $customer): bool
{
if (BouncerFacade::can('view-customer', $customer)) {
if (BouncerFacade::can('view-customer', $customer) && $user->hasCompany($customer->company_id)) {
return true;
}
@@ -60,7 +60,7 @@ class CustomerPolicy
*/
public function update(User $user, Customer $customer): bool
{
if (BouncerFacade::can('edit-customer', $customer)) {
if (BouncerFacade::can('edit-customer', $customer) && $user->hasCompany($customer->company_id)) {
return true;
}
@@ -74,7 +74,7 @@ class CustomerPolicy
*/
public function delete(User $user, Customer $customer): bool
{
if (BouncerFacade::can('delete-customer', $customer)) {
if (BouncerFacade::can('delete-customer', $customer) && $user->hasCompany($customer->company_id)) {
return true;
}
@@ -88,7 +88,7 @@ class CustomerPolicy
*/
public function restore(User $user, Customer $customer): bool
{
if (BouncerFacade::can('delete-customer', $customer)) {
if (BouncerFacade::can('delete-customer', $customer) && $user->hasCompany($customer->company_id)) {
return true;
}
@@ -102,7 +102,7 @@ class CustomerPolicy
*/
public function forceDelete(User $user, Customer $customer): bool
{
if (BouncerFacade::can('delete-customer', $customer)) {
if (BouncerFacade::can('delete-customer', $customer) && $user->hasCompany($customer->company_id)) {
return true;
}

View File

@@ -0,0 +1,40 @@
<?php
namespace App\Space;
use App\Models\CompanySetting;
use App\Models\FileDisk;
use Exception;
use Spatie\Backup\Config\Config;
class BackupConfigurationFactory
{
public static function make($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');
}
$fileDisk = FileDisk::find($data['file_disk_id']);
$fileDisk->setConfig();
$prefix = env('DYNAMIC_DISK_PREFIX', 'temp_');
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;
}
}

View File

@@ -5,8 +5,6 @@ namespace App\Space;
use App\Models\Setting;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Log;
use League\Flysystem\FilesystemException;
class InstallUtils
{
@@ -17,7 +15,7 @@ class InstallUtils
*/
public static function isDbCreated()
{
return self::dbMarkerExists() && self::tableExists('users');
return self::tableExists('users');
}
/**
@@ -44,54 +42,6 @@ class InstallUtils
return $cache[$table];
}
/**
* Check if database created marker exists
*
* @return bool
*/
public static function dbMarkerExists()
{
try {
return \Storage::disk('local')->has('database_created');
} catch (FilesystemException $e) {
Log::error('Unable to verify db marker: '.$e->getMessage());
}
return false;
}
/**
* Creates the database marker
*
* @return bool
*/
public static function createDbMarker()
{
try {
return \Storage::disk('local')->put('database_created', time());
} catch (\Exception $e) {
Log::error('Unable to create db marker: '.$e->getMessage());
}
return false;
}
/**
* Deletes the database marker
*
* @return bool
*/
public static function deleteDbMarker()
{
try {
return \Storage::disk('local')->delete('database_created');
} catch (\Exception $e) {
Log::error('Unable to delete db marker: '.$e->getMessage());
}
return false;
}
/**
* Set the app version
*

View File

@@ -19,6 +19,9 @@ final class PdfHtmlSanitizer
return '';
}
// Legacy/invalid `</br>` is dropped by libxml and collapses lines in PDF output; normalize to `<br />`.
$html = str_replace('</br>', '<br />', $html);
$allowedTags = '<br><br/><p><b><strong><i><em><u><ol><ul><li><table><tr><td><th><thead><tbody><tfoot><h1><h2><h3><h4><blockquote>';
$html = strip_tags($html, $allowedTags);

View File

@@ -180,7 +180,7 @@ trait GeneratesPdfTrait
$str = str_replace('<p>', '', $str);
$str = str_replace('</p>', '</br>', $str);
$str = str_replace('</p>', '<br />', $str);
return $str;
}

View File

@@ -9,15 +9,12 @@
"type": "project",
"require": {
"php": "^8.4",
"aws/aws-sdk-php": "^3.336",
"barryvdh/laravel-dompdf": "^v3.0",
"doctrine/dbal": "^4.2",
"dragonmantank/cron-expression": "^v3.4",
"gotenberg/gotenberg-php": "^2.8",
"guzzlehttp/guzzle": "^7.9",
"hashids/hashids": "^5.0",
"invoiceshelf/modules": "^1.0.0",
"jasonmccreary/laravel-test-assertions": "^v2.4",
"laravel/framework": "^13.0",
"laravel/helpers": "^1.7",
"laravel/sanctum": "^4.0",
@@ -36,6 +33,7 @@
"require-dev": {
"barryvdh/laravel-ide-helper": "^3.5",
"fakerphp/faker": "^1.23",
"jasonmccreary/laravel-test-assertions": "^2.9",
"laravel/boost": "^2.3",
"laravel/pint": "^1.13",
"laravel/sail": "^1.41",
@@ -83,7 +81,11 @@
"dev": [
"Composer\\Config::disableProcessTimeout",
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite"
]
],
"ide-helper": [
"@php artisan ide-helper:generate",
"@php artisan ide-helper:meta"
]
},
"extra": {
"laravel": {

3915
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -37,6 +37,15 @@ return [
'report' => false,
],
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'visibility' => 'public',
'throw' => false,
'report' => false,
],
's3' => [
'driver' => 's3',
'key' => env('AWS_KEY'),

View File

@@ -8,7 +8,6 @@ use App\Models\CompanySetting;
use App\Models\Customer;
use App\Models\Setting;
use App\Models\User;
use App\Space\InstallUtils;
use Illuminate\Database\Seeder;
use Silber\Bouncer\BouncerFacade;
@@ -71,8 +70,5 @@ class DemoSeeder extends Seeder
// Mark profile setup as complete
Setting::setSetting('profile_complete', 'COMPLETED');
// Create installation marker
InstallUtils::createDbMarker();
}
}

View File

@@ -1,11 +1,11 @@
FROM --platform=$BUILDPLATFORM node AS static_builder
FROM --platform=$BUILDPLATFORM node:24 AS static_builder
WORKDIR /var/www/html
COPY . /var/www/html
RUN yarn && yarn build
FROM serversideup/php:8.4-fpm-alpine AS base
USER root
RUN apk add --no-cache sqlite
RUN apk add --no-cache bash nano mariadb-client postgresql-client sqlite
RUN install-php-extensions exif
RUN install-php-extensions pgsql
RUN install-php-extensions sqlite3

View File

@@ -1,11 +1,11 @@
FROM --platform=$BUILDPLATFORM node:20 AS static_builder
FROM --platform=$BUILDPLATFORM node:24 AS static_builder
WORKDIR /var/www/html
COPY . /var/www/html
RUN yarn && yarn build
FROM serversideup/php:8.4-fpm-nginx-alpine AS base
USER root
RUN apk add --no-cache bash nano
RUN apk add --no-cache bash nano mariadb-client postgresql-client sqlite
RUN install-php-extensions exif
RUN install-php-extensions pgsql
RUN install-php-extensions sqlite3
@@ -19,6 +19,10 @@ FROM serversideup/php:8.4-fpm-nginx-alpine AS base
RUN install-php-extensions intl
RUN install-php-extensions curl
# Copy entrypoint and inject script, and make sure they are executable
COPY --chmod=755 docker/production/inject.sh /inject.sh
COPY --chmod=755 docker/production/entrypoint.d/ /etc/entrypoint.d/
FROM base AS production
ENV AUTORUN_ENABLED=true
ENV PHP_OPCACHE_ENABLE=1
@@ -36,7 +40,3 @@ FROM base AS production
COPY --from=static_builder --chown=www-data:www-data /var/www/html/public /var/www/html/public
COPY --chown=www-data:www-data . /var/www/html
RUN composer install --prefer-dist --no-dev --optimize-autoloader
# Copy entrypoint and inject script, and make sure they are executable
COPY --chmod=755 docker/production/inject.sh /inject.sh
COPY --chmod=755 docker/production/entrypoint.d/ /etc/entrypoint.d/

View File

@@ -33,8 +33,15 @@ if [ "$DB_CONNECTION" = "sqlite" ] || [ -z "$DB_CONNECTION" ]; then
chown www-data:www-data "$DB_DATABASE"
fi
echo "**** Setting up artisan permissions ****"
echo "**** Setting up folder permissions ****"
chmod +x artisan
chown -R www-data:www-data storage bootstrap/cache
chmod -R 775 storage bootstrap/cache
if [ ! -L /var/www/html/public/storage ]; then
echo "**** Creating storage symlink (public/storage) ****"
./artisan storage:link --force -n || true
fi
if ! grep -q "APP_KEY" /var/www/html/.env
then

13
eslint.config.mjs Normal file
View File

@@ -0,0 +1,13 @@
import pluginVue from 'eslint-plugin-vue'
import eslintConfigPrettier from 'eslint-config-prettier'
export default [
...pluginVue.configs['flat/recommended'],
eslintConfigPrettier,
{
files: ['resources/scripts/**/*.{js,vue}'],
rules: {
'vue/no-mutating-props': 'off',
},
},
]

View File

@@ -1,64 +1,61 @@
{
"private": true,
"engines": {
"node": ">=24"
},
"type": "module",
"scripts": {
"dev": "vite",
"watch": "vite build --watch",
"build": "vite build",
"serve": "vite preview",
"test": "eslint ./resources/scripts --ext .js,.vue"
"test": "eslint ./resources/scripts"
},
"devDependencies": {
"@rvxlab/tailwind-plugin-ios-full-height": "^1.1.0",
"@tailwindcss/aspect-ratio": "^0.4.2",
"@tailwindcss/forms": "^0.5.10",
"@tailwindcss/typography": "^0.5.16",
"@vitejs/plugin-vue": "^5.2.1",
"@tailwindcss/vite": "^4.0.0",
"@vitejs/plugin-vue": "^6.0.0",
"@vue/compiler-sfc": "^3.5.13",
"autoprefixer": "^10.4.20",
"cross-env": "^7.0.3",
"eslint": "^9.18.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-vue": "^9.32.0",
"postcss": "^8.4.49",
"eslint": "^10.0.0",
"eslint-config-prettier": "^10.0.0",
"eslint-plugin-vue": "^10.0.0",
"prettier": "^3.4.2",
"sass": "^1.83.1",
"tailwind-scrollbar": "^3.1.0",
"tailwindcss": "^3.4.17"
"tailwind-scrollbar": "^4.0.0",
"tailwindcss": "^4.0.0"
},
"dependencies": {
"@headlessui/vue": "^1.7.23",
"@heroicons/vue": "^2.2.0",
"@popperjs/core": "^2.11.8",
"@stripe/stripe-js": "^2.4.0",
"@tiptap/core": "^2.11.2",
"@tiptap/extension-link": "^2.11.2",
"@tiptap/extension-text-align": "^2.11.2",
"@tiptap/pm": "^2.11.2",
"@tiptap/starter-kit": "^2.11.2",
"@tiptap/vue-3": "^2.11.2",
"@types/node": "^20.11.9",
"@tiptap/core": "^3.0.0",
"@tiptap/extension-text-align": "^3.0.0",
"@tiptap/pm": "^3.0.0",
"@tiptap/starter-kit": "^3.0.0",
"@tiptap/vue-3": "^3.0.0",
"@types/node": "^24.0.0",
"@vuelidate/components": "^1.2.6",
"@vuelidate/core": "^2.0.3",
"@vuelidate/validators": "^2.0.4",
"@vueuse/core": "^12.4.0",
"axios": "0.30.0",
"chart.js": "^2.9.4",
"@vueuse/core": "^14.0.0",
"axios": "1.14.0",
"chart.js": "^4.5.1",
"guid": "^0.0.12",
"laravel-vite-plugin": "^1.1.1",
"laravel-vite-plugin": "^3.0.0",
"lodash": "^4.17.21",
"maska": "^3.0.4",
"mini-svg-data-uri": "^1.4.4",
"moment": "^2.30.1",
"path": "^0.12.7",
"pinia": "^2.3.0",
"pinia": "^3.0.0",
"v-money3": "^3.24.1",
"v-tooltip": "^4.0.0-beta.17",
"vite": "^6.0.7",
"vite": "^8.0.0",
"vue": "^3.5",
"vue-flatpickr-component": "^11.0.5",
"vue-flatpickr-component": "^12.0.0",
"vue-i18n": "^11.0.1",
"vue-router": "^4.5.0",
"vue-router": "^5.0.0",
"vuedraggable": "^4.1.0"
}
}
}

View File

@@ -1,7 +0,0 @@
// postcss.config.js
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@@ -0,0 +1,56 @@
.pace {
-webkit-pointer-events: none;
pointer-events: none;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.pace-inactive {
display: none;
}
.pace .pace-progress {
background: #3f39ad;
position: fixed;
z-index: 2000;
top: 0;
right: 100%;
width: 100%;
height: 2px;
}
.pace .pace-progress-inner {
display: block;
position: absolute;
right: 0px;
width: 100px;
height: 100%;
box-shadow: 0 0 10px #5851d8, 0 0 5px #5851d8;
opacity: 1;
transform: rotate(3deg) translate(0px, -4px);
}
.pace .pace-activity {
display: block;
position: fixed;
z-index: 2000;
top: 15px;
right: 15px;
width: 14px;
height: 14px;
border: solid 2px transparent;
border-top-color: #5851d8;
border-left-color: #5851d8;
border-radius: 10px;
animation: pace-spinner 400ms linear infinite;
}
@keyframes pace-spinner {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

View File

@@ -80,18 +80,16 @@
}
&.popover {
$color: #f9f9f9;
.popover-inner {
background: $color;
background: #f9f9f9;
color: black;
padding: 24px;
border-radius: 5px;
box-shadow: 0 5px 30px rgba(black, .1);
box-shadow: 0 5px 30px rgba(0, 0, 0, 0.1);
}
.popover-arrow {
border-color: $color;
border-color: #f9f9f9;
}
}

98
resources/css/invoiceshelf.css vendored Normal file
View File

@@ -0,0 +1,98 @@
@import "tailwindcss";
@import "./themes.css";
@import "./components/animation.css";
@import "./components/pace-loader.css";
@import "./components/v-tooltips.css";
@plugin "@tailwindcss/forms";
@plugin "@tailwindcss/typography";
@plugin "tailwind-scrollbar";
@source "../scripts/**/*.vue";
@source "../scripts/**/*.js";
@source "../../resources/views/**/*.php";
@theme inline {
--color-primary-50: var(--color-primary-50);
--color-primary-100: var(--color-primary-100);
--color-primary-200: var(--color-primary-200);
--color-primary-300: var(--color-primary-300);
--color-primary-400: var(--color-primary-400);
--color-primary-500: var(--color-primary-500);
--color-primary-600: var(--color-primary-600);
--color-primary-700: var(--color-primary-700);
--color-primary-800: var(--color-primary-800);
--color-primary-900: var(--color-primary-900);
--color-primary-950: var(--color-primary-950);
--color-black: #040405;
}
@theme {
--spacing-88: 22rem;
--font-base: Poppins, sans-serif;
}
@utility bg-multiselect-caret {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='h-5 w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath fill-rule='evenodd' d='M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z' clip-rule='evenodd'/%3E%3C/svg%3E");
}
@utility bg-multiselect-spinner {
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 512 512' fill='%234a3dff' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M456.433 371.72l-27.79-16.045c-7.192-4.152-10.052-13.136-6.487-20.636 25.82-54.328 23.566-118.602-6.768-171.03-30.265-52.529-84.802-86.621-144.76-91.424C262.35 71.922 256 64.953 256 56.649V24.56c0-9.31 7.916-16.609 17.204-15.96 81.795 5.717 156.412 51.902 197.611 123.408 41.301 71.385 43.99 159.096 8.042 232.792-4.082 8.369-14.361 11.575-22.424 6.92z'/%3E%3C/svg%3E");
}
@utility bg-multiselect-remove {
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 320 512' fill='white' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z'/%3E%3C/svg%3E");
}
@font-face {
font-family: "Poppins";
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("$fonts/Poppins-Black.ttf") format("truetype");
}
@font-face {
font-family: "Poppins";
font-style: normal;
font-weight: 300;
font-display: swap;
src: url("$fonts/Poppins-Light.ttf") format("truetype");
}
@font-face {
font-family: "Poppins";
font-style: normal;
font-weight: 500;
font-display: swap;
src: url("$fonts/Poppins-Medium.ttf") format("truetype");
}
@font-face {
font-family: "Poppins";
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("$fonts/Poppins-Regular.ttf") format("truetype");
}
@font-face {
font-family: "Poppins";
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("$fonts/Poppins-SemiBold.ttf") format("truetype");
}
body {
min-height: 100vh;
min-height: -webkit-fill-available;
}
html {
height: -webkit-fill-available;
}
.h-screen-ios {
height: -webkit-fill-available;
}

13
resources/css/themes.css vendored Normal file
View File

@@ -0,0 +1,13 @@
:root {
--color-primary-50: rgb(241, 243, 255);
--color-primary-100: rgb(229, 231, 255);
--color-primary-200: rgb(206, 209, 255);
--color-primary-300: rgb(167, 170, 255);
--color-primary-400: rgb(118, 117, 255);
--color-primary-500: rgb(74, 61, 255);
--color-primary-600: rgb(51, 22, 255);
--color-primary-700: rgb(33, 4, 253);
--color-primary-800: rgb(27, 3, 211);
--color-primary-900: rgb(25, 5, 173);
--color-primary-950: rgb(9, 0, 118);
}

View File

@@ -1,112 +0,0 @@
$pace-loader-color: #5851d8;
.pace {
-webkit-pointer-events: none;
pointer-events: none;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.pace-inactive {
display: none;
}
.pace .pace-progress {
background: darken($pace-loader-color, 10%);
position: fixed;
z-index: 2000;
top: 0;
right: 100%;
width: 100%;
height: 2px;
}
.pace .pace-progress-inner {
display: block;
position: absolute;
right: 0px;
width: 100px;
height: 100%;
box-shadow: 0 0 10px $pace-loader-color, 0 0 5px $pace-loader-color;
opacity: 1;
-webkit-transform: rotate(3deg) translate(0px, -4px);
-moz-transform: rotate(3deg) translate(0px, -4px);
-ms-transform: rotate(3deg) translate(0px, -4px);
-o-transform: rotate(3deg) translate(0px, -4px);
transform: rotate(3deg) translate(0px, -4px);
}
.pace .pace-activity {
display: block;
position: fixed;
z-index: 2000;
top: 15px;
right: 15px;
width: 14px;
height: 14px;
border: solid 2px transparent;
border-top-color: $pace-loader-color;
border-left-color: $pace-loader-color;
border-radius: 10px;
-webkit-animation: pace-spinner 400ms linear infinite;
-moz-animation: pace-spinner 400ms linear infinite;
-ms-animation: pace-spinner 400ms linear infinite;
-o-animation: pace-spinner 400ms linear infinite;
animation: pace-spinner 400ms linear infinite;
}
@-webkit-keyframes pace-spinner {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@-moz-keyframes pace-spinner {
0% {
-moz-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-moz-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@-o-keyframes pace-spinner {
0% {
-o-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-o-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@-ms-keyframes pace-spinner {
0% {
-ms-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-ms-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes pace-spinner {
0% {
transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
transform: rotate(360deg);
}
}

View File

@@ -1,61 +0,0 @@
@use "themes.scss";
@use "components/animation.scss";
@tailwind base;
@tailwind components;
@tailwind utilities;
@font-face {
font-family: "Poppins";
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("$fonts/Poppins-Black.ttf") format("truetype");
}
@font-face {
font-family: "Poppins";
font-style: normal;
font-weight: 300;
font-display: swap;
src: url("$fonts/Poppins-Light.ttf") format("truetype");
}
@font-face {
font-family: "Poppins";
font-style: normal;
font-weight: 500;
font-display: swap;
src: url("$fonts/Poppins-Medium.ttf") format("truetype");
}
@font-face {
font-family: "Poppins";
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("$fonts/Poppins-Regular.ttf") format("truetype");
}
@font-face {
font-family: "Poppins";
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("$fonts/Poppins-SemiBold.ttf") format("truetype");
}
body {
min-height: 100vh;
min-height: -webkit-fill-available;
}
html {
height: -webkit-fill-available;
}
.h-screen-ios {
height: -webkit-fill-available;
}

View File

@@ -1,13 +0,0 @@
:root {
--color-primary-50: 241, 243, 255;
--color-primary-100: 229, 231, 255;
--color-primary-200: 206, 209, 255;
--color-primary-300: 167, 170, 255;
--color-primary-400: 118, 117, 255;
--color-primary-500: 74, 61, 255;
--color-primary-600: 51, 22, 255;
--color-primary-700: 33, 4, 253;
--color-primary-800: 27, 3, 211;
--color-primary-900: 25, 5, 173;
--color-primary-950: 9, 0, 118;
}

View File

@@ -6,7 +6,7 @@
px-4
py-2
rounded-lg
bg-opacity-40 bg-gray-300
bg-gray-300/40
whitespace-nowrap
flex-col
mt-1
@@ -33,7 +33,7 @@
h-full
inset-y-0
cursor-pointer
focus:outline-none
focus:outline-hidden
text-primary-500
"
width="37"

View File

@@ -4,14 +4,14 @@
<Popover v-slot="{ isOpen }">
<PopoverButton
v-if="userStore.hasAbilities(abilities.VIEW_NOTE)"
:class="isOpen ? '' : 'text-opacity-90'"
:class="isOpen ? '' : ''"
class="
flex
items-center
z-10
font-medium
text-primary-400
focus:outline-none focus:border-none
focus:outline-hidden focus:border-none
"
@click="fetchInitialData"
>
@@ -50,7 +50,7 @@
overflow-hidden
rounded-md
shadow-lg
ring-1 ring-black ring-opacity-5
ring-1 ring-black/5
"
>
<div class="relative grid bg-white">
@@ -117,7 +117,7 @@
py-3
bg-gray-200
border-none
outline-none
outline-hidden
"
@click="openNoteModal"
>

View File

@@ -5,7 +5,7 @@
</template>
<script setup>
import Chart from 'chart.js'
import { Chart } from 'chart.js/auto'
import { ref, reactive, computed, onMounted, watchEffect, inject } from 'vue'
import { useCompanyStore } from '@/scripts/admin/stores/company'
@@ -65,19 +65,21 @@ onMounted(() => {
let options = reactive({
responsive: true,
maintainAspectRatio: false,
tooltips: {
enabled: true,
callbacks: {
label: function (tooltipItem, data) {
return utils.formatMoney(
Math.round(tooltipItem.value * 100),
defaultCurrency.value
)
plugins: {
tooltip: {
enabled: true,
callbacks: {
label: function (context) {
return utils.formatMoney(
Math.round(context.parsed.y * 100),
defaultCurrency.value
)
},
},
},
},
legend: {
display: false,
legend: {
display: false,
},
},
})
@@ -87,7 +89,7 @@ onMounted(() => {
{
label: 'Sales',
fill: false,
lineTension: 0.3,
tension: 0.3,
backgroundColor: 'rgba(230, 254, 249)',
borderColor: '#040405',
borderCapStyle: 'butt',
@@ -108,7 +110,7 @@ onMounted(() => {
{
label: 'Receipts',
fill: false,
lineTension: 0.3,
tension: 0.3,
backgroundColor: 'rgba(230, 254, 249)',
borderColor: 'rgb(2, 201, 156)',
borderCapStyle: 'butt',
@@ -129,7 +131,7 @@ onMounted(() => {
{
label: 'Expenses',
fill: false,
lineTension: 0.3,
tension: 0.3,
backgroundColor: 'rgba(245, 235, 242)',
borderColor: 'rgb(255,0,0)',
borderCapStyle: 'butt',
@@ -150,7 +152,7 @@ onMounted(() => {
{
label: 'Net Income',
fill: false,
lineTension: 0.3,
tension: 0.3,
backgroundColor: 'rgba(236, 235, 249)',
borderColor: 'rgba(88, 81, 216, 1)',
borderCapStyle: 'butt',
@@ -190,8 +192,6 @@ function update() {
(expense) => expense / 100
)
myLineChart.data.datasets[3].data = props.income.map((_i) => _i / 100)
myLineChart.update({
lazy: true,
})
myLineChart.update('none')
}
</script>

View File

@@ -11,7 +11,7 @@
}}
</p>
<form action="" @submit.prevent="submitBulkUpdate">
<form id="bulk-update-form" action="" @submit.prevent="submitBulkUpdate">
<ValidateEach
v-for="(c, i) in exchangeRateStore.bulkCurrencies"
:key="i"
@@ -50,22 +50,21 @@
</BaseInputGroup>
</template>
</ValidateEach>
</form>
<template #footer>
<div
slot="footer"
class="
z-0
flex
justify-end
mt-4
pt-4
border-t border-gray-200 border-solid border-modal-bg
"
>
<BaseButton :loading="isSaving" variant="primary" type="submit">
<BaseButton :loading="isSaving" variant="primary" type="submit" form="bulk-update-form">
{{ $t('general.save') }}
</BaseButton>
</div>
</form>
</template>
</BaseCard>
</template>

View File

@@ -143,7 +143,7 @@
<BaseContentPlaceholders v-if="loading">
<BaseContentPlaceholdersText
:lines="1"
class="w-24 h-8 border rounded-md"
class="w-24 h-8 border border-gray-200 rounded-md"
/>
</BaseContentPlaceholders>

View File

@@ -42,7 +42,7 @@
<template v-if="userStore.hasAbilities(ability)" #action>
<button
type="button"
class="flex items-center justify-center w-full px-2 py-2 bg-gray-200 border-none outline-none cursor-pointer "
class="flex items-center justify-center w-full px-2 py-2 bg-gray-200 border-none outline-hidden cursor-pointer "
@click="openTaxModal"
>
<BaseIcon name="CheckCircleIcon" class="h-5 text-primary-400" />
@@ -106,7 +106,7 @@ const props = defineProps({
},
taxes: {
type: Array,
default: [],
default: () => [],
},
total: {
type: Number,

View File

@@ -100,7 +100,7 @@
<BaseContentPlaceholders v-if="isLoading">
<BaseContentPlaceholdersText
:lines="1"
class="w-24 h-8 border rounded-md"
class="w-24 h-8 border border-gray-200 rounded-md"
/>
</BaseContentPlaceholders>
<div v-else class="flex" style="width: 140px" role="group">

View File

@@ -11,7 +11,7 @@
<BaseIcon
v-tooltip="{ content: 'Fetch Latest Exchange rate' }"
name="ArrowPathIcon"
:class="`h-4 w-4 text-primary-500 cursor-pointer outline-none ${
:class="`h-4 w-4 text-primary-500 cursor-pointer outline-hidden ${
isFetching
? ' animate-spin rotate-180 cursor-not-allowed pointer-events-none '
: ''

View File

@@ -79,6 +79,7 @@ const addressData = computed(() => {
address: pick(address, ['address_street_1', 'city', 'state', 'zip']),
}
}
return null
})
const isAddressAvailable = computed(() => {

View File

@@ -2,14 +2,14 @@
<div class="w-full mt-4 tax-select">
<Popover v-slot="{ isOpen }" class="relative">
<PopoverButton
:class="isOpen ? '' : 'text-opacity-90'"
:class="isOpen ? '' : ''"
class="
flex
items-center
text-sm
font-medium
text-primary-400
focus:outline-none focus:border-none
focus:outline-hidden focus:border-none
"
>
<BaseIcon
@@ -39,7 +39,7 @@
overflow-hidden
rounded-md
shadow-lg
ring-1 ring-black ring-opacity-5
ring-1 ring-black/5
"
>
<!-- Tax Search Input -->
@@ -142,7 +142,7 @@
py-3
bg-gray-200
border-none
outline-none
outline-hidden
"
@click="openTaxTypeModal"
>

View File

@@ -53,7 +53,7 @@
flex
justify-end
p-4
border-t border-gray-200 border-solid border-modal-bg
border-t border-gray-200 border-solid
"
>
<BaseButton

View File

@@ -98,7 +98,7 @@
</BaseInputGrid>
</div>
<div class="z-0 flex justify-end p-4 bg-gray-50 border-modal-bg">
<div class="z-0 flex justify-end p-4 bg-gray-50 border-t border-gray-200">
<BaseButton
class="mr-3 text-sm"
variant="primary-outline"

View File

@@ -38,7 +38,7 @@
</BaseInputGroup>
</div>
<div class="z-0 flex justify-end p-4 bg-gray-50 border-modal-bg">
<div class="z-0 flex justify-end p-4 bg-gray-50 border-t border-gray-200">
<BaseButton
class="mr-3 text-sm"
variant="primary-outline"

View File

@@ -21,7 +21,7 @@
>
<template #default="slotProps">
<div
class="z-0 flex justify-end p-4 border-t border-solid border-gray-light"
class="z-0 flex justify-end p-4 border-t border-solid border-gray-200"
>
<BaseButton
class="mr-3 text-sm"

View File

@@ -37,7 +37,7 @@
flex
justify-end
p-4
border-t border-gray-200 border-solid border-modal-bg
border-t border-gray-200 border-solid
"
>
<BaseButton

View File

@@ -75,7 +75,7 @@ _ />
justify-end
px-4
py-4
border-t border-solid border-gray-light
border-t border-solid border-gray-200
"
>
<BaseButton
@@ -280,7 +280,7 @@ function closeNoteModal() {
}
</script>
<style lang="scss">
<style>
.note-modal {
.header-editior .editor-menu-bar {
margin-left: 0.5px;

View File

@@ -122,7 +122,7 @@
flex
justify-end
p-4
border-t border-solid border--200 border-modal-bg
border-t border-solid border-gray-200
"
>
<BaseButton

View File

@@ -106,7 +106,7 @@
flex
justify-end
p-4
border-t border-solid border--200 border-modal-bg
border-t border-solid border-gray-200
"
>
<BaseButton

View File

@@ -116,7 +116,6 @@
<script setup>
import { computed, onMounted, reactive, ref } from 'vue'
import axios from 'axios'
import { useModalStore } from '@/scripts/stores/modal'
import { useTaxTypeStore } from '@/scripts/admin/stores/tax-type'
import { useGlobalStore } from '@/scripts/admin/stores/global'

View File

@@ -179,7 +179,7 @@
flex
justify-end
p-4
border-t border-solid border-gray-light border-modal-bg
border-t border-solid border-gray-200
"
>
<BaseButton

View File

@@ -168,5 +168,5 @@ const loginPageLogo = computed(() => {
})
</script>
<style lang="scss" scoped>
<style scoped>
</style>

View File

@@ -12,7 +12,7 @@
px-4
py-3
md:h-16 md:px-8
bg-gradient-to-r
bg-linear-to-r
from-primary-500
to-primary-400
"

View File

@@ -15,7 +15,7 @@
leave-from="opacity-100"
leave-to="opacity-0"
>
<DialogOverlay class="fixed inset-0 bg-gray-600 bg-opacity-75" />
<DialogOverlay class="fixed inset-0 bg-gray-600/75" />
</TransitionChild>
<TransitionChild
@@ -47,7 +47,7 @@
h-10
ml-1
rounded-full
focus:outline-none
focus:outline-hidden
focus:ring-2
focus:ring-inset
focus:ring-white
@@ -82,9 +82,9 @@
:to="item.link"
:class="[
hasActiveUrl(item.link)
? 'text-primary-500 border-primary-500 bg-gray-100 '
: 'text-black',
'cursor-pointer px-0 pl-4 py-3 border-transparent flex items-center border-l-4 border-solid text-sm not-italic font-medium',
? 'text-primary-500 border-l-primary-500 bg-gray-100 '
: 'text-black border-l-transparent',
'cursor-pointer px-0 pl-4 py-3 flex items-center border-l-4 border-solid text-sm not-italic font-medium',
]"
@click="globalStore.setSidebarVisibility(false)"
>
@@ -136,8 +136,8 @@
:to="item.link"
:class="[
hasActiveUrl(item.link)
? 'text-primary-500 border-primary-500 bg-gray-100 '
: 'text-black',
? 'text-primary-500 border-l-primary-500 bg-gray-100 '
: 'text-black border-l-transparent',
'cursor-pointer px-0 pl-6 hover:bg-gray-50 py-3 group flex items-center border-l-4 border-solid text-sm not-italic font-medium',
]"
>

View File

@@ -1,4 +1,4 @@
import axios from 'axios'
import http from '@/scripts/http'
import { defineStore } from 'pinia'
import { useNotificationStore } from '@/scripts/stores/notification'
import { handleError } from '@/scripts/helpers/error-handling'
@@ -7,8 +7,7 @@ export const useAuthStore = (useWindow = false) => {
const defineStoreFunc = useWindow ? window.pinia.defineStore : defineStore
const { global } = window.i18n
return defineStoreFunc({
id: 'auth',
return defineStoreFunc('auth', {
state: () => ({
status: '',
@@ -22,9 +21,9 @@ export const useAuthStore = (useWindow = false) => {
actions: {
login(data) {
return new Promise((resolve, reject) => {
axios.get('/sanctum/csrf-cookie').then((response) => {
http.get('/sanctum/csrf-cookie').then((response) => {
if (response) {
axios
http
.post('/login', data)
.then((response) => {
resolve(response)
@@ -45,22 +44,31 @@ export const useAuthStore = (useWindow = false) => {
logout() {
return new Promise((resolve, reject) => {
axios
http
.post('/auth/logout')
.then((response) => {
.then(async (response) => {
const notificationStore = useNotificationStore()
notificationStore.showNotification({
type: 'success',
message: 'Logged out successfully.',
})
// Clear stored auth data so next login doesn't send stale tokens
window.Ls.remove('auth.token')
window.Ls.remove('selectedCompany')
// Refresh CSRF token so next login works cleanly
await http.get('/sanctum/csrf-cookie').catch(() => {})
window.router.push('/login')
// resetStore.clearPinia()
resolve(response)
})
.catch((err) => {
handleError(err)
window.router.push('/')
window.Ls.remove('auth.token')
window.Ls.remove('selectedCompany')
http.get('/sanctum/csrf-cookie').catch(() => {})
window.router.push('/login')
reject(err)
})
})

View File

@@ -1,4 +1,4 @@
import axios from 'axios'
import http from '@/scripts/http'
import { defineStore } from 'pinia'
import { useNotificationStore } from '@/scripts/stores/notification'
import { handleError } from '@/scripts/helpers/error-handling'
@@ -7,9 +7,7 @@ export const useBackupStore = (useWindow = false) => {
const defineStoreFunc = useWindow ? window.pinia.defineStore : defineStore
const { global } = window.i18n
return defineStoreFunc({
id: 'backup',
return defineStoreFunc('backup', {
state: () => ({
backups: [],
currentBackupData: {
@@ -21,7 +19,7 @@ export const useBackupStore = (useWindow = false) => {
actions: {
fetchBackups(params) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/backups`, { params })
.then((response) => {
this.backups = response.data.data
@@ -36,7 +34,7 @@ export const useBackupStore = (useWindow = false) => {
createBackup(data) {
return new Promise((resolve, reject) => {
axios
http
.post(`/api/v1/backups`, data)
.then((response) => {
const notificationStore = useNotificationStore()
@@ -55,7 +53,7 @@ export const useBackupStore = (useWindow = false) => {
removeBackup(params) {
return new Promise((resolve, reject) => {
axios
http
.delete(`/api/v1/backups/${params.disk}`, { params })
.then((response) => {
const notificationStore = useNotificationStore()

View File

@@ -1,4 +1,4 @@
import axios from 'axios'
import http from '@/scripts/http'
import { defineStore } from 'pinia'
import { useNotificationStore } from '@/scripts/stores/notification'
import { handleError } from '@/scripts/helpers/error-handling'
@@ -7,9 +7,7 @@ export const useCategoryStore = (useWindow = false) => {
const defineStoreFunc = useWindow ? window.pinia.defineStore : defineStore
const { global } = window.i18n
return defineStoreFunc({
id: 'category',
return defineStoreFunc('category', {
state: () => ({
categories: [],
currentCategory: {
@@ -27,7 +25,7 @@ export const useCategoryStore = (useWindow = false) => {
actions: {
fetchCategories(params) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/categories`, { params })
.then((response) => {
this.categories = response.data.data
@@ -42,7 +40,7 @@ export const useCategoryStore = (useWindow = false) => {
fetchCategory(id) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/categories/${id}`)
.then((response) => {
this.currentCategory = response.data.data
@@ -57,7 +55,7 @@ export const useCategoryStore = (useWindow = false) => {
addCategory(data) {
return new Promise((resolve, reject) => {
axios
http
.post('/api/v1/categories', data)
.then((response) => {
this.categories.push(response.data.data)
@@ -77,7 +75,7 @@ export const useCategoryStore = (useWindow = false) => {
updateCategory(data) {
return new Promise((resolve, reject) => {
axios
http
.put(`/api/v1/categories/${data.id}`, data)
.then((response) => {
if (response.data) {
@@ -104,7 +102,7 @@ export const useCategoryStore = (useWindow = false) => {
deleteCategory(id) {
return new Promise((resolve) => {
axios
http
.delete(`/api/v1/categories/${id}`)
.then((response) => {
let index = this.categories.findIndex(

View File

@@ -1,4 +1,4 @@
import axios from 'axios'
import http from '@/scripts/http'
import { defineStore } from 'pinia'
import { useNotificationStore } from '@/scripts/stores/notification'
import { handleError } from '@/scripts/helpers/error-handling'
@@ -8,9 +8,7 @@ export const useCompanyStore = (useWindow = false) => {
const defineStoreFunc = useWindow ? window.pinia.defineStore : defineStore
const { global } = window.i18n
return defineStoreFunc({
id: 'company',
return defineStoreFunc('company', {
state: () => ({
companies: [],
selectedCompany: null,
@@ -26,7 +24,7 @@ export const useCompanyStore = (useWindow = false) => {
fetchBasicMailConfig() {
return new Promise((resolve, reject) => {
axios
http
.get('/api/v1/company/mail/config')
.then((response) => {
resolve(response)
@@ -40,7 +38,7 @@ export const useCompanyStore = (useWindow = false) => {
updateCompany(data) {
return new Promise((resolve, reject) => {
axios
http
.put('/api/v1/company', data)
.then((response) => {
const notificationStore = useNotificationStore()
@@ -66,7 +64,7 @@ export const useCompanyStore = (useWindow = false) => {
updateCompanyLogo(data) {
return new Promise((resolve, reject) => {
axios
http
.post('/api/v1/company/upload-logo', data)
.then((response) => {
resolve(response)
@@ -80,7 +78,7 @@ export const useCompanyStore = (useWindow = false) => {
addNewCompany(data) {
return new Promise((resolve, reject) => {
axios
http
.post('/api/v1/companies', data)
.then((response) => {
const notificationStore = useNotificationStore()
@@ -99,7 +97,7 @@ export const useCompanyStore = (useWindow = false) => {
fetchCompany(params) {
return new Promise((resolve, reject) => {
axios
http
.get('/api/v1/current-company', params)
.then((response) => {
Object.assign(this.companyForm, response.data.data.address)
@@ -115,7 +113,7 @@ export const useCompanyStore = (useWindow = false) => {
fetchUserCompanies() {
return new Promise((resolve, reject) => {
axios
http
.get('/api/v1/companies')
.then((response) => {
resolve(response)
@@ -129,7 +127,7 @@ export const useCompanyStore = (useWindow = false) => {
fetchCompanySettings(settings) {
return new Promise((resolve, reject) => {
axios
http
.get('/api/v1/company/settings', {
params: {
settings,
@@ -147,7 +145,7 @@ export const useCompanyStore = (useWindow = false) => {
updateCompanySettings({ data, message }) {
return new Promise((resolve, reject) => {
axios
http
.post('/api/v1/company/settings', data)
.then((response) => {
Object.assign(this.selectedCompanySettings, data.settings)
@@ -172,7 +170,7 @@ export const useCompanyStore = (useWindow = false) => {
deleteCompany(data) {
return new Promise((resolve, reject) => {
axios
http
.post(`/api/v1/companies/delete`, data)
.then((response) => {
resolve(response)

View File

@@ -1,4 +1,4 @@
import axios from 'axios'
import http from '@/scripts/http'
import { defineStore } from 'pinia'
import { useNotificationStore } from '@/scripts/stores/notification'
import customFieldStub from '@/scripts/admin/stub/custom-field'
@@ -10,9 +10,7 @@ export const useCustomFieldStore = (useWindow = false) => {
const defineStoreFunc = useWindow ? window.pinia.defineStore : defineStore
const { global } = window.i18n
return defineStoreFunc({
id: 'custom-field',
return defineStoreFunc('custom-field', {
state: () => ({
customFields: [],
isRequestOngoing: false,
@@ -41,7 +39,7 @@ export const useCustomFieldStore = (useWindow = false) => {
fetchCustomFields(params) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/custom-fields`, { params })
.then((response) => {
this.customFields = response.data.data
@@ -63,7 +61,7 @@ export const useCustomFieldStore = (useWindow = false) => {
this.isRequestOngoing = true
axios
http
.get(`/api/v1/custom-fields`, { params })
.then((response) => {
this.customFields = response.data.data
@@ -80,7 +78,7 @@ export const useCustomFieldStore = (useWindow = false) => {
fetchCustomField(id) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/custom-fields/${id}`)
.then((response) => {
this.currentCustomField = response.data.data
@@ -107,7 +105,7 @@ export const useCustomFieldStore = (useWindow = false) => {
addCustomField(params) {
const notificationStore = useNotificationStore()
return new Promise((resolve, reject) => {
axios
http
.post(`/api/v1/custom-fields`, params)
.then((response) => {
let data = {
@@ -139,7 +137,7 @@ export const useCustomFieldStore = (useWindow = false) => {
const notificationStore = useNotificationStore()
return new Promise((resolve, reject) => {
axios
http
.put(`/api/v1/custom-fields/${params.id}`, params)
.then((response) => {
let data = {
@@ -174,7 +172,7 @@ export const useCustomFieldStore = (useWindow = false) => {
deleteCustomFields(id) {
const notificationStore = useNotificationStore()
return new Promise((resolve, reject) => {
axios
http
.delete(`/api/v1/custom-fields/${id}`)
.then((response) => {
let index = this.customFields.findIndex(

View File

@@ -1,4 +1,4 @@
import axios from 'axios'
import http from '@/scripts/http'
import { defineStore } from 'pinia'
import { useRoute } from 'vue-router'
import { handleError } from '@/scripts/helpers/error-handling'
@@ -12,8 +12,7 @@ export const useCustomerStore = (useWindow = false) => {
const defineStoreFunc = useWindow ? window.pinia.defineStore : defineStore
const { global } = window.i18n
return defineStoreFunc({
id: 'customer',
return defineStoreFunc('customer', {
state: () => ({
customers: [],
totalCustomers: 0,
@@ -75,7 +74,7 @@ export const useCustomerStore = (useWindow = false) => {
fetchCustomers(params) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/customers`, { params })
.then((response) => {
this.customers = response.data.data
@@ -92,7 +91,7 @@ export const useCustomerStore = (useWindow = false) => {
fetchViewCustomer(params) {
return new Promise((resolve, reject) => {
this.isFetchingViewData = true
axios
http
.get(`/api/v1/customers/${params.id}/stats`, { params })
.then((response) => {
@@ -112,7 +111,7 @@ export const useCustomerStore = (useWindow = false) => {
fetchCustomer(id) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/customers/${id}`)
.then((response) => {
Object.assign(this.currentCustomer, response.data.data)
@@ -129,7 +128,7 @@ export const useCustomerStore = (useWindow = false) => {
addCustomer(data) {
return new Promise((resolve, reject) => {
axios
http
.post('/api/v1/customers', data)
.then((response) => {
this.customers.push(response.data.data)
@@ -151,7 +150,7 @@ export const useCustomerStore = (useWindow = false) => {
updateCustomer(data) {
return new Promise((resolve, reject) => {
axios
http
.put(`/api/v1/customers/${data.id}`, data)
.then((response) => {
if (response.data) {
@@ -177,7 +176,7 @@ export const useCustomerStore = (useWindow = false) => {
deleteCustomer(id) {
const notificationStore = useNotificationStore()
return new Promise((resolve, reject) => {
axios
http
.post(`/api/v1/customers/delete`, id)
.then((response) => {
let index = this.customers.findIndex(
@@ -201,7 +200,7 @@ export const useCustomerStore = (useWindow = false) => {
const notificationStore = useNotificationStore()
return new Promise((resolve, reject) => {
axios
http
.post(`/api/v1/customers/delete`, { ids: this.selectedCustomers })
.then((response) => {
this.selectedCustomers.forEach((customer) => {

View File

@@ -1,4 +1,4 @@
import axios from 'axios'
import http from '@/scripts/http'
import { defineStore } from 'pinia'
import { useGlobalStore } from '@/scripts/admin/stores/global'
import { handleError } from '@/scripts/helpers/error-handling'
@@ -7,9 +7,7 @@ export const useDashboardStore = (useWindow = false) => {
const defineStoreFunc = useWindow ? window.pinia.defineStore : defineStore
const { global } = window.i18n
return defineStoreFunc({
id: 'dashboard',
return defineStoreFunc('dashboard', {
state: () => ({
stats: {
totalAmountDue: 0,
@@ -40,7 +38,7 @@ export const useDashboardStore = (useWindow = false) => {
actions: {
loadData(params) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/dashboard`, { params })
.then((response) => {
// Stats

View File

@@ -1,4 +1,4 @@
import axios from 'axios'
import http from '@/scripts/http'
import { defineStore } from 'pinia'
import { useNotificationStore } from '@/scripts/stores/notification'
import { handleError } from '@/scripts/helpers/error-handling'
@@ -7,9 +7,7 @@ export const useDiskStore = (useWindow = false) => {
const defineStoreFunc = useWindow ? window.pinia.defineStore : defineStore
const { global } = window.i18n
return defineStoreFunc({
id: 'disk',
return defineStoreFunc('disk', {
state: () => ({
disks: [],
diskDrivers: [],
@@ -71,7 +69,7 @@ export const useDiskStore = (useWindow = false) => {
actions: {
fetchDiskEnv(data) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/disks/${data.disk}`)
.then((response) => {
resolve(response)
@@ -85,7 +83,7 @@ export const useDiskStore = (useWindow = false) => {
fetchDisks(params) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/disks`, { params })
.then((response) => {
this.disks = response.data.data
@@ -100,7 +98,7 @@ export const useDiskStore = (useWindow = false) => {
fetchDiskDrivers() {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/disk/drivers`)
.then((response) => {
this.diskConfigData = response.data
@@ -116,7 +114,7 @@ export const useDiskStore = (useWindow = false) => {
deleteFileDisk(id) {
return new Promise((resolve, reject) => {
axios
http
.delete(`/api/v1/disks/${id}`)
.then((response) => {
if (response.data.success) {
@@ -141,7 +139,7 @@ export const useDiskStore = (useWindow = false) => {
updateDisk(data) {
return new Promise((resolve, reject) => {
axios
http
.put(`/api/v1/disks/${data.id}`, data)
.then((response) => {
if (response.data) {
@@ -166,7 +164,7 @@ export const useDiskStore = (useWindow = false) => {
createDisk(data) {
return new Promise((resolve, reject) => {
axios
http
.post(`/api/v1/disks`, data)
.then((response) => {
if (response.data) {

View File

@@ -1,4 +1,4 @@
import axios from 'axios'
import http from '@/scripts/http'
import moment from 'moment'
import Guid from 'guid'
import _ from 'lodash'
@@ -20,9 +20,7 @@ export const useEstimateStore = (useWindow = false) => {
const defineStoreFunc = useWindow ? window.pinia.defineStore : defineStore
const { global } = window.i18n
return defineStoreFunc({
id: 'estimate',
return defineStoreFunc('estimate', {
state: () => ({
templates: [],
@@ -100,7 +98,7 @@ export const useEstimateStore = (useWindow = false) => {
previewEstimate(params) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/estimates/${params.id}/send/preview`, { params })
.then((response) => {
resolve(response)
@@ -114,7 +112,7 @@ export const useEstimateStore = (useWindow = false) => {
fetchEstimates(params) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/estimates`, { params })
.then((response) => {
this.estimates = response.data.data
@@ -130,7 +128,7 @@ export const useEstimateStore = (useWindow = false) => {
getNextNumber(params, setState = false) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/next-number?key=estimate`, { params })
.then((response) => {
if (setState) {
@@ -147,7 +145,7 @@ export const useEstimateStore = (useWindow = false) => {
fetchEstimate(id) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/estimates/${id}`)
.then((response) => {
this.setEstimateData(response.data.data)
@@ -222,7 +220,7 @@ export const useEstimateStore = (useWindow = false) => {
const notificationStore = useNotificationStore()
return new Promise((resolve, reject) => {
axios
http
.post(`/api/v1/estimates/${data.id}/send`, data)
.then((response) => {
if (!data.is_preview) {
@@ -242,7 +240,7 @@ export const useEstimateStore = (useWindow = false) => {
addEstimate(data) {
return new Promise((resolve, reject) => {
axios
http
.post('/api/v1/estimates', data)
.then((response) => {
this.estimates = [...this.estimates, response.data.estimate]
@@ -266,7 +264,7 @@ export const useEstimateStore = (useWindow = false) => {
const notificationStore = useNotificationStore()
return new Promise((resolve, reject) => {
axios
http
.post(`/api/v1/estimates/delete`, id)
.then((response) => {
let index = this.estimates.findIndex(
@@ -292,7 +290,7 @@ export const useEstimateStore = (useWindow = false) => {
const notificationStore = useNotificationStore()
return new Promise((resolve, reject) => {
axios
http
.post(`/api/v1/estimates/delete`, { ids: this.selectedEstimates })
.then((response) => {
this.selectedEstimates.forEach((estimate) => {
@@ -318,7 +316,7 @@ export const useEstimateStore = (useWindow = false) => {
updateEstimate(data) {
return new Promise((resolve, reject) => {
axios
http
.put(`/api/v1/estimates/${data.id}`, data)
.then((response) => {
let pos = this.estimates.findIndex(
@@ -341,7 +339,7 @@ export const useEstimateStore = (useWindow = false) => {
cloneEstimate(data) {
return new Promise((resolve, reject) => {
axios
http
.post(`/api/v1/estimates/${data.id}/clone`, data)
.then((response) => {
const notificationStore = useNotificationStore()
@@ -360,7 +358,7 @@ export const useEstimateStore = (useWindow = false) => {
markAsAccepted(data) {
return new Promise((resolve, reject) => {
axios
http
.post(`/api/v1/estimates/${data.id}/status`, data)
.then((response) => {
let pos = this.estimates.findIndex(
@@ -387,7 +385,7 @@ export const useEstimateStore = (useWindow = false) => {
markAsRejected(data) {
return new Promise((resolve, reject) => {
axios
http
.post(`/api/v1/estimates/${data.id}/status`, data)
.then((response) => {
const notificationStore = useNotificationStore()
@@ -407,7 +405,7 @@ export const useEstimateStore = (useWindow = false) => {
markAsSent(data) {
return new Promise((resolve, reject) => {
axios
http
.post(`/api/v1/estimates/${data.id}/status`, data)
.then((response) => {
let pos = this.estimates.findIndex(
@@ -435,7 +433,7 @@ export const useEstimateStore = (useWindow = false) => {
convertToInvoice(id) {
const notificationStore = useNotificationStore()
return new Promise((resolve, reject) => {
axios
http
.post(`/api/v1/estimates/${id}/convert-to-invoice`)
.then((response) => {
notificationStore.showNotification({
@@ -453,7 +451,7 @@ export const useEstimateStore = (useWindow = false) => {
searchEstimate(data) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/estimates?${data}`)
.then((response) => {
resolve(response)
@@ -487,7 +485,7 @@ export const useEstimateStore = (useWindow = false) => {
selectCustomer(id) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/customers/${id}`)
.then((response) => {
this.newEstimate.customer = response.data.data
@@ -502,7 +500,7 @@ export const useEstimateStore = (useWindow = false) => {
},
fetchEstimateTemplates(params) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/estimates/templates`, { params })
.then((response) => {
this.templates = response.data.estimateTemplates

View File

@@ -1,4 +1,4 @@
import axios from 'axios'
import http from '@/scripts/http'
import { defineStore } from 'pinia'
import { useNotificationStore } from '@/scripts/stores/notification'
import { handleError } from '@/scripts/helpers/error-handling'
@@ -8,9 +8,7 @@ export const useExchangeRateStore = (useWindow = false) => {
const { global } = window.i18n
const notificationStore = useNotificationStore()
return defineStoreFunc({
id: 'exchange-rate',
return defineStoreFunc('exchange-rate', {
state: () => ({
supportedCurrencies: [],
drivers: [],
@@ -37,7 +35,7 @@ export const useExchangeRateStore = (useWindow = false) => {
actions: {
fetchProviders(params) {
return new Promise((resolve, reject) => {
axios
http
.get('/api/v1/exchange-rate-providers', { params })
.then((response) => {
this.providers = response.data.data
@@ -52,7 +50,7 @@ export const useExchangeRateStore = (useWindow = false) => {
fetchDefaultProviders() {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/config?key=exchange_rate_drivers`)
.then((response) => {
this.drivers = response.data.exchange_rate_drivers
@@ -68,7 +66,7 @@ export const useExchangeRateStore = (useWindow = false) => {
fetchProvider(id) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/exchange-rate-providers/${id}`)
.then((response) => {
this.currentExchangeRate = response.data.data
@@ -84,7 +82,7 @@ export const useExchangeRateStore = (useWindow = false) => {
addProvider(data) {
return new Promise((resolve, reject) => {
axios
http
.post('/api/v1/exchange-rate-providers', data)
.then((response) => {
notificationStore.showNotification({
@@ -104,7 +102,7 @@ export const useExchangeRateStore = (useWindow = false) => {
updateProvider(data) {
return new Promise((resolve, reject) => {
axios
http
.put(`/api/v1/exchange-rate-providers/${data.id}`, data)
.then((response) => {
notificationStore.showNotification({
@@ -122,7 +120,7 @@ export const useExchangeRateStore = (useWindow = false) => {
deleteExchangeRate(id) {
return new Promise((resolve, reject) => {
axios
http
.delete(`/api/v1/exchange-rate-providers/${id}`)
.then((response) => {
let index = this.drivers.findIndex((driver) => driver.id === id)
@@ -148,7 +146,7 @@ export const useExchangeRateStore = (useWindow = false) => {
},
fetchCurrencies(params) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/supported-currencies`, { params })
.then((response) => {
this.supportedCurrencies = response.data.supportedCurrencies
@@ -163,7 +161,7 @@ export const useExchangeRateStore = (useWindow = false) => {
},
fetchActiveCurrency(params) {
return new Promise((resolve, reject) => {
axios
http
.get('/api/v1/used-currencies', { params })
.then((response) => {
this.activeUsedCurrencies = response.data.activeUsedCurrencies
@@ -178,7 +176,7 @@ export const useExchangeRateStore = (useWindow = false) => {
},
fetchBulkCurrencies() {
return new Promise((resolve, reject) => {
axios
http
.get('/api/v1/currencies/used')
.then((response) => {
this.bulkCurrencies = response.data.currencies.map((_m) => {
@@ -196,7 +194,7 @@ export const useExchangeRateStore = (useWindow = false) => {
},
updateBulkExchangeRate(data) {
return new Promise((resolve, reject) => {
axios
http
.post('/api/v1/currencies/bulk-update-exchange-rate', data)
.then((response) => {
resolve(response)
@@ -209,7 +207,7 @@ export const useExchangeRateStore = (useWindow = false) => {
},
getCurrentExchangeRate(currencyId) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/currencies/${currencyId}/exchange-rate`)
.then((response) => {
resolve(response)
@@ -221,7 +219,7 @@ export const useExchangeRateStore = (useWindow = false) => {
},
getCurrencyConverterServers() {
return new Promise((resolve, reject) => {
axios
http
.get('/api/v1/config?key=currency_converter_servers')
.then((response) => {
resolve(response)
@@ -234,7 +232,7 @@ export const useExchangeRateStore = (useWindow = false) => {
},
checkForActiveProvider(currency_id) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/currencies/${currency_id}/active-provider`)
.then((response) => {
resolve(response)

View File

@@ -1,4 +1,4 @@
import axios from 'axios'
import http from '@/scripts/http'
import { defineStore } from 'pinia'
import { handleError } from '@/scripts/helpers/error-handling'
import { useNotificationStore } from '@/scripts/stores/notification'
@@ -9,9 +9,7 @@ export const useExpenseStore = (useWindow = false) => {
const defineStoreFunc = useWindow ? window.pinia.defineStore : defineStore
const { global } = window.i18n
return defineStoreFunc({
id: 'expense',
return defineStoreFunc('expense', {
state: () => ({
expenses: [],
totalExpenses: 0,
@@ -38,7 +36,7 @@ export const useExpenseStore = (useWindow = false) => {
fetchExpenses(params) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/expenses`, { params })
.then((response) => {
this.expenses = response.data.data
@@ -54,7 +52,7 @@ export const useExpenseStore = (useWindow = false) => {
fetchExpense(id) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/expenses/${id}`)
.then((response) => {
if (response.data) {
@@ -97,7 +95,7 @@ export const useExpenseStore = (useWindow = false) => {
const formData = utils.toFormData(data)
return new Promise((resolve, reject) => {
axios
http
.post('/api/v1/expenses', formData)
.then((response) => {
this.expenses.push(response.data)
@@ -127,7 +125,7 @@ export const useExpenseStore = (useWindow = false) => {
formData.append('is_attachment_receipt_removed', isAttachmentReceiptRemoved)
return new Promise((resolve) => {
axios.post(`/api/v1/expenses/${id}`, formData).then((response) => {
http.post(`/api/v1/expenses/${id}`, formData).then((response) => {
let pos = this.expenses.findIndex(
(expense) => expense.id === response.data.id
)
@@ -175,7 +173,7 @@ export const useExpenseStore = (useWindow = false) => {
const notificationStore = useNotificationStore()
return new Promise((resolve, reject) => {
axios
http
.post(`/api/v1/expenses/delete`, id)
.then((response) => {
let index = this.expenses.findIndex(
@@ -200,7 +198,7 @@ export const useExpenseStore = (useWindow = false) => {
const notificationStore = useNotificationStore()
return new Promise((resolve, reject) => {
axios
http
.post(`/api/v1/expenses/delete`, { ids: this.selectedExpenses })
.then((response) => {
this.selectedExpenses.forEach((expense) => {
@@ -223,7 +221,7 @@ export const useExpenseStore = (useWindow = false) => {
},
fetchPaymentModes(params) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/payment-methods`, { params })
.then((response) => {
this.paymentModes = response.data.data

View File

@@ -1,4 +1,4 @@
import axios from 'axios'
import http from '@/scripts/http'
import { defineStore } from 'pinia'
import { useCompanyStore } from './company'
import { useUserStore } from './user'
@@ -11,8 +11,7 @@ export const useGlobalStore = (useWindow = false) => {
const defineStoreFunc = useWindow ? window.pinia.defineStore : defineStore
const { global } = window.i18n
return defineStoreFunc({
id: 'global',
return defineStoreFunc('global', {
state: () => ({
// Global Configuration
config: null,
@@ -48,7 +47,7 @@ export const useGlobalStore = (useWindow = false) => {
actions: {
bootstrap() {
return new Promise((resolve, reject) => {
axios
http
.get('/api/v1/bootstrap')
.then(async (response) => {
const companyStore = useCompanyStore()
@@ -123,7 +122,7 @@ export const useGlobalStore = (useWindow = false) => {
resolve(this.currencies)
} else {
this.areCurrenciesLoading = true
axios
http
.get('/api/v1/currencies')
.then((response) => {
this.currencies = response.data.data.filter((currency) => {
@@ -143,7 +142,7 @@ export const useGlobalStore = (useWindow = false) => {
fetchConfig(params) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/config`, { params })
.then((response) => {
if (response.data.languages) {
@@ -165,7 +164,7 @@ export const useGlobalStore = (useWindow = false) => {
if (this.dateFormats.length) {
resolve(this.dateFormats)
} else {
axios
http
.get('/api/v1/date/formats')
.then((response) => {
this.dateFormats = response.data.date_formats
@@ -184,7 +183,7 @@ export const useGlobalStore = (useWindow = false) => {
if (this.timeFormats.length) {
resolve(this.timeFormats)
} else {
axios
http
.get('/api/v1/time/formats')
.then((response) => {
this.timeFormats = response.data.time_formats
@@ -203,7 +202,7 @@ export const useGlobalStore = (useWindow = false) => {
if (this.timeZones.length) {
resolve(this.timeZones)
} else {
axios
http
.get('/api/v1/timezones')
.then((response) => {
this.timeZones = response.data.time_zones
@@ -222,7 +221,7 @@ export const useGlobalStore = (useWindow = false) => {
if (this.countries.length) {
resolve(this.countries)
} else {
axios
http
.get('/api/v1/countries')
.then((response) => {
this.countries = response.data.data
@@ -238,7 +237,7 @@ export const useGlobalStore = (useWindow = false) => {
fetchPlaceholders(params) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/number-placeholders`, { params })
.then((response) => {
resolve(response)
@@ -260,7 +259,7 @@ export const useGlobalStore = (useWindow = false) => {
updateGlobalSettings({ data, message }) {
return new Promise((resolve, reject) => {
axios
http
.post('/api/v1/settings', data)
.then((response) => {
Object.assign(this.globalSettings, data.settings)

View File

@@ -1,4 +1,4 @@
import axios from 'axios'
import http from '@/scripts/http'
import { defineStore } from 'pinia'
import { useCompanyStore } from './company'
import { handleError } from '@/scripts/helpers/error-handling'
@@ -8,9 +8,7 @@ export const useInstallationStore = (useWindow = false) => {
const { global } = window.i18n
const companyStore = useCompanyStore()
return defineStoreFunc({
id: 'installation',
return defineStoreFunc('installation', {
state: () => ({
currentDataBaseData: {
database_connection: 'mysql',
@@ -28,7 +26,7 @@ export const useInstallationStore = (useWindow = false) => {
actions: {
fetchInstallationLanguages() {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/installation/languages`)
.then((response) => {
resolve(response)
@@ -42,7 +40,7 @@ export const useInstallationStore = (useWindow = false) => {
fetchInstallationRequirements() {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/installation/requirements`)
.then((response) => {
resolve(response)
@@ -56,7 +54,7 @@ export const useInstallationStore = (useWindow = false) => {
fetchInstallationStep() {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/installation/wizard-step`)
.then((response) => {
resolve(response)
@@ -70,7 +68,7 @@ export const useInstallationStore = (useWindow = false) => {
addInstallationStep(data) {
return new Promise((resolve, reject) => {
axios
http
.post(`/api/v1/installation/wizard-step`, data)
.then((response) => {
resolve(response)
@@ -84,7 +82,7 @@ export const useInstallationStore = (useWindow = false) => {
addInstallationLanguage(data) {
return new Promise((resolve, reject) => {
axios
http
.post(`/api/v1/installation/wizard-language`, data)
.then((response) => {
resolve(response)
@@ -98,7 +96,7 @@ export const useInstallationStore = (useWindow = false) => {
fetchInstallationPermissions() {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/installation/permissions`)
.then((response) => {
resolve(response)
@@ -112,7 +110,7 @@ export const useInstallationStore = (useWindow = false) => {
fetchInstallationDatabase(params) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/installation/database/config`, { params })
.then((response) => {
resolve(response)
@@ -126,7 +124,7 @@ export const useInstallationStore = (useWindow = false) => {
addInstallationDatabase(data) {
return new Promise((resolve, reject) => {
axios
http
.post(`/api/v1/installation/database/config`, data)
.then((response) => {
resolve(response)
@@ -140,7 +138,7 @@ export const useInstallationStore = (useWindow = false) => {
addInstallationFinish() {
return new Promise((resolve, reject) => {
axios
http
.post(`/api/v1/installation/finish`)
.then((response) => {
resolve(response)
@@ -154,7 +152,7 @@ export const useInstallationStore = (useWindow = false) => {
setInstallationDomain(data) {
return new Promise((resolve, reject) => {
axios
http
.put(`/api/v1/installation/set-domain`, data)
.then((response) => {
resolve(response)
@@ -168,9 +166,9 @@ export const useInstallationStore = (useWindow = false) => {
installationLogin() {
return new Promise((resolve, reject) => {
axios.get('/sanctum/csrf-cookie').then((response) => {
http.get('/sanctum/csrf-cookie').then((response) => {
if (response) {
axios
http
.post('/api/v1/installation/login')
.then((response) => {
companyStore.setSelectedCompany(response.data.company)
@@ -187,7 +185,7 @@ export const useInstallationStore = (useWindow = false) => {
checkAuthenticated() {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/auth/check`)
.then((response) => {
resolve(response)

View File

@@ -1,4 +1,4 @@
import axios from 'axios'
import http from '@/scripts/http'
import moment from 'moment'
import Guid from 'guid'
import _ from 'lodash'
@@ -22,8 +22,7 @@ export const useInvoiceStore = (useWindow = false) => {
const { global } = window.i18n
const notificationStore = useNotificationStore()
return defineStoreFunc({
id: 'invoice',
return defineStoreFunc('invoice', {
state: () => ({
templates: [],
invoices: [],
@@ -108,7 +107,7 @@ export const useInvoiceStore = (useWindow = false) => {
previewInvoice(params) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/invoices/${params.id}/send/preview`, { params })
.then((response) => {
resolve(response)
@@ -122,7 +121,7 @@ export const useInvoiceStore = (useWindow = false) => {
fetchInvoices(params) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/invoices`, { params })
.then((response) => {
this.invoices = response.data.data
@@ -138,7 +137,7 @@ export const useInvoiceStore = (useWindow = false) => {
fetchInvoice(id) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/invoices/${id}`)
.then((response) => {
this.setInvoiceData(response.data.data)
@@ -204,7 +203,7 @@ export const useInvoiceStore = (useWindow = false) => {
sendInvoice(data) {
return new Promise((resolve, reject) => {
axios
http
.post(`/api/v1/invoices/${data.id}/send`, data)
.then((response) => {
notificationStore.showNotification({
@@ -222,7 +221,7 @@ export const useInvoiceStore = (useWindow = false) => {
addInvoice(data) {
return new Promise((resolve, reject) => {
axios
http
.post('/api/v1/invoices', data)
.then((response) => {
this.invoices = [...this.invoices, response.data.invoice]
@@ -243,7 +242,7 @@ export const useInvoiceStore = (useWindow = false) => {
deleteInvoice(id) {
return new Promise((resolve, reject) => {
axios
http
.post(`/api/v1/invoices/delete`, id)
.then((response) => {
let index = this.invoices.findIndex(
@@ -266,7 +265,7 @@ export const useInvoiceStore = (useWindow = false) => {
deleteMultipleInvoices(id) {
return new Promise((resolve, reject) => {
axios
http
.post(`/api/v1/invoices/delete`, { ids: this.selectedInvoices })
.then((response) => {
this.selectedInvoices.forEach((invoice) => {
@@ -292,7 +291,7 @@ export const useInvoiceStore = (useWindow = false) => {
updateInvoice(data) {
return new Promise((resolve, reject) => {
axios
http
.put(`/api/v1/invoices/${data.id}`, data)
.then((response) => {
let pos = this.invoices.findIndex(
@@ -316,7 +315,7 @@ export const useInvoiceStore = (useWindow = false) => {
cloneInvoice(data) {
return new Promise((resolve, reject) => {
axios
http
.post(`/api/v1/invoices/${data.id}/clone`, data)
.then((response) => {
notificationStore.showNotification({
@@ -334,7 +333,7 @@ export const useInvoiceStore = (useWindow = false) => {
markAsSent(data) {
return new Promise((resolve, reject) => {
axios
http
.post(`/api/v1/invoices/${data.id}/status`, data)
.then((response) => {
let pos = this.invoices.findIndex(
@@ -360,7 +359,7 @@ export const useInvoiceStore = (useWindow = false) => {
getNextNumber(params, setState = false) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/next-number?key=invoice`, { params })
.then((response) => {
if (setState) {
@@ -377,7 +376,7 @@ export const useInvoiceStore = (useWindow = false) => {
searchInvoice(data) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/invoices?${data}`)
.then((response) => {
resolve(response)
@@ -411,7 +410,7 @@ export const useInvoiceStore = (useWindow = false) => {
selectCustomer(id) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/customers/${id}`)
.then((response) => {
this.newInvoice.customer = response.data.data
@@ -427,7 +426,7 @@ export const useInvoiceStore = (useWindow = false) => {
fetchInvoiceTemplates(params) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/invoices/templates`, { params })
.then((response) => {
this.templates = response.data.invoiceTemplates

View File

@@ -1,4 +1,4 @@
import axios from 'axios'
import http from '@/scripts/http'
import { defineStore } from 'pinia'
import { useNotificationStore } from '@/scripts/stores/notification'
import { handleError } from '@/scripts/helpers/error-handling'
@@ -7,8 +7,7 @@ export const useItemStore = (useWindow = false) => {
const defineStoreFunc = useWindow ? window.pinia.defineStore : defineStore
const { global } = window.i18n
return defineStoreFunc({
id: 'item',
return defineStoreFunc('item', {
state: () => ({
items: [],
totalItems: 0,
@@ -45,7 +44,7 @@ export const useItemStore = (useWindow = false) => {
},
fetchItems(params) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/items`, { params })
.then((response) => {
this.items = response.data.data
@@ -62,7 +61,7 @@ export const useItemStore = (useWindow = false) => {
fetchItem(id) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/items/${id}`)
.then((response) => {
if (response.data) {
@@ -79,7 +78,7 @@ export const useItemStore = (useWindow = false) => {
addItem(data) {
return new Promise((resolve, reject) => {
axios
http
.post('/api/v1/items', data)
.then((response) => {
const notificationStore = useNotificationStore()
@@ -102,7 +101,7 @@ export const useItemStore = (useWindow = false) => {
updateItem(data) {
return new Promise((resolve, reject) => {
axios
http
.put(`/api/v1/items/${data.id}`, data)
.then((response) => {
if (response.data) {
@@ -133,7 +132,7 @@ export const useItemStore = (useWindow = false) => {
const notificationStore = useNotificationStore()
return new Promise((resolve, reject) => {
axios
http
.post(`/api/v1/items/delete`, id)
.then((response) => {
let index = this.items.findIndex((item) => item.id === id)
@@ -157,7 +156,7 @@ export const useItemStore = (useWindow = false) => {
const notificationStore = useNotificationStore()
return new Promise((resolve, reject) => {
axios
http
.post(`/api/v1/items/delete`, { ids: this.selectedItems })
.then((response) => {
this.selectedItems.forEach((item) => {
@@ -205,7 +204,7 @@ export const useItemStore = (useWindow = false) => {
const notificationStore = useNotificationStore()
return new Promise((resolve, reject) => {
axios
http
.post(`/api/v1/units`, data)
.then((response) => {
this.itemUnits.push(response.data.data)
@@ -239,7 +238,7 @@ export const useItemStore = (useWindow = false) => {
const notificationStore = useNotificationStore()
return new Promise((resolve, reject) => {
axios
http
.put(`/api/v1/units/${data.id}`, data)
.then((response) => {
let pos = this.itemUnits.findIndex(
@@ -274,7 +273,7 @@ export const useItemStore = (useWindow = false) => {
fetchItemUnits(params) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/units`, { params })
.then((response) => {
this.itemUnits = response.data.data
@@ -289,7 +288,7 @@ export const useItemStore = (useWindow = false) => {
fetchItemUnit(id) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/units/${id}`)
.then((response) => {
this.currentItemUnit = response.data.data
@@ -306,7 +305,7 @@ export const useItemStore = (useWindow = false) => {
const notificationStore = useNotificationStore()
return new Promise((resolve, reject) => {
axios
http
.delete(`/api/v1/units/${id}`)
.then((response) => {
if (!response.data.error) {

View File

@@ -1,4 +1,4 @@
import axios from 'axios'
import http from '@/scripts/http'
import { defineStore } from 'pinia'
import { useNotificationStore } from '@/scripts/stores/notification'
import { handleError } from '@/scripts/helpers/error-handling'
@@ -7,9 +7,7 @@ export const useMailDriverStore = (useWindow = false) => {
const defineStoreFunc = useWindow ? window.pinia.defineStore : defineStore
const { global } = window.i18n
return defineStoreFunc({
id: 'mail-driver',
return defineStoreFunc('mail-driver', {
state: () => ({
mailConfigData: null,
mail_driver: 'smtp',
@@ -57,7 +55,7 @@ export const useMailDriverStore = (useWindow = false) => {
actions: {
fetchMailDrivers() {
return new Promise((resolve, reject) => {
axios
http
.get('/api/v1/mail/drivers')
.then((response) => {
if (response.data) {
@@ -74,7 +72,7 @@ export const useMailDriverStore = (useWindow = false) => {
fetchMailConfig() {
return new Promise((resolve, reject) => {
axios
http
.get('/api/v1/mail/config')
.then((response) => {
if (response.data) {
@@ -92,7 +90,7 @@ export const useMailDriverStore = (useWindow = false) => {
updateMailConfig(data) {
return new Promise((resolve, reject) => {
axios
http
.post('/api/v1/mail/config', data)
.then((response) => {
const notificationStore = useNotificationStore()
@@ -118,7 +116,7 @@ export const useMailDriverStore = (useWindow = false) => {
sendTestMail(data) {
return new Promise((resolve, reject) => {
axios
http
.post('/api/v1/mail/test', data)
.then((response) => {
const notificationStore = useNotificationStore()

View File

@@ -1,4 +1,4 @@
import axios from 'axios'
import http from '@/scripts/http'
import { defineStore } from 'pinia'
import { handleError } from '@/scripts/helpers/error-handling'
import { useNotificationStore } from '@/scripts/stores/notification'
@@ -7,9 +7,7 @@ export const useModuleStore = (useWindow = false) => {
const defineStoreFunc = useWindow ? window.pinia.defineStore : defineStore
const { global } = window.i18n
return defineStoreFunc({
id: 'modules',
return defineStoreFunc('modules', {
state: () => ({
currentModule: {},
modules: [],
@@ -27,7 +25,7 @@ export const useModuleStore = (useWindow = false) => {
actions: {
fetchModules(params) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/modules`)
.then((response) => {
this.modules = response.data.data
@@ -43,7 +41,7 @@ export const useModuleStore = (useWindow = false) => {
fetchModule(id) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/modules/${id}`)
.then((response) => {
if (response.data.error === 'invalid_token') {
@@ -67,7 +65,7 @@ export const useModuleStore = (useWindow = false) => {
checkApiToken(token) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/modules/check?api_token=${token}`)
.then((response) => {
const notificationStore = useNotificationStore()
@@ -88,7 +86,7 @@ export const useModuleStore = (useWindow = false) => {
disableModule(module) {
return new Promise((resolve, reject) => {
axios
http
.post(`/api/v1/modules/${module}/disable`)
.then((response) => {
const notificationStore = useNotificationStore()
@@ -109,7 +107,7 @@ export const useModuleStore = (useWindow = false) => {
enableModule(module) {
return new Promise((resolve, reject) => {
axios
http
.post(`/api/v1/modules/${module}/enable`)
.then((response) => {
const notificationStore = useNotificationStore()

View File

@@ -1,4 +1,4 @@
import axios from 'axios'
import http from '@/scripts/http'
import { defineStore } from 'pinia'
import { handleError } from '@/scripts/helpers/error-handling'
@@ -6,9 +6,7 @@ export const useNotesStore = (useWindow = false) => {
const defineStoreFunc = useWindow ? window.pinia.defineStore : defineStore
const { global } = window.i18n
return defineStoreFunc({
id: 'notes',
return defineStoreFunc('notes', {
state: () => ({
notes: [],
currentNote: {
@@ -40,7 +38,7 @@ export const useNotesStore = (useWindow = false) => {
fetchNotes(params) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/notes`, { params })
.then((response) => {
this.notes = response.data.data
@@ -55,7 +53,7 @@ export const useNotesStore = (useWindow = false) => {
fetchNote(id) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/notes/${id}`)
.then((response) => {
this.currentNote = response.data.data
@@ -70,7 +68,7 @@ export const useNotesStore = (useWindow = false) => {
addNote(data) {
return new Promise((resolve, reject) => {
axios
http
.post('/api/v1/notes', data)
.then((response) => {
this.notes.push(response.data)
@@ -85,7 +83,7 @@ export const useNotesStore = (useWindow = false) => {
updateNote(data) {
return new Promise((resolve, reject) => {
axios
http
.put(`/api/v1/notes/${data.id}`, data)
.then((response) => {
if (response.data) {
@@ -105,7 +103,7 @@ export const useNotesStore = (useWindow = false) => {
deleteNote(id) {
return new Promise((resolve, reject) => {
axios
http
.delete(`/api/v1/notes/${id}`)
.then((response) => {
let index = this.notes.findIndex((note) => note.id === id)

View File

@@ -1,4 +1,4 @@
import axios from 'axios'
import http from '@/scripts/http'
import moment from 'moment'
import { defineStore } from 'pinia'
import { useRoute } from 'vue-router'
@@ -12,9 +12,7 @@ export const usePaymentStore = (useWindow = false) => {
const defineStoreFunc = useWindow ? window.pinia.defineStore : defineStore
const { global } = window.i18n
return defineStoreFunc({
id: 'payment',
return defineStoreFunc('payment', {
state: () => ({
payments: [],
paymentTotalCount: 0,
@@ -99,7 +97,7 @@ export const usePaymentStore = (useWindow = false) => {
fetchPayments(params) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/payments`, { params })
.then((response) => {
this.payments = response.data.data
@@ -115,7 +113,7 @@ export const usePaymentStore = (useWindow = false) => {
fetchPayment(id) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/payments/${id}`)
.then((response) => {
Object.assign(this.currentPayment, response.data.data)
@@ -130,7 +128,7 @@ export const usePaymentStore = (useWindow = false) => {
addPayment(data) {
return new Promise((resolve, reject) => {
axios
http
.post('/api/v1/payments', data)
.then((response) => {
this.payments.push(response.data)
@@ -150,7 +148,7 @@ export const usePaymentStore = (useWindow = false) => {
updatePayment(data) {
return new Promise((resolve, reject) => {
axios
http
.put(`/api/v1/payments/${data.id}`, data)
.then((response) => {
if (response.data) {
@@ -179,7 +177,7 @@ export const usePaymentStore = (useWindow = false) => {
const notificationStore = useNotificationStore()
return new Promise((resolve, reject) => {
axios
http
.post(`/api/v1/payments/delete`, id)
.then((response) => {
let index = this.payments.findIndex(
@@ -203,7 +201,7 @@ export const usePaymentStore = (useWindow = false) => {
deleteMultiplePayments() {
const notificationStore = useNotificationStore()
return new Promise((resolve, reject) => {
axios
http
.post(`/api/v1/payments/delete`, { ids: this.selectedPayments })
.then((response) => {
this.selectedPayments.forEach((payment) => {
@@ -260,7 +258,7 @@ export const usePaymentStore = (useWindow = false) => {
searchPayment(params) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/payments`, { params })
.then((response) => {
this.payments = response.data
@@ -275,7 +273,7 @@ export const usePaymentStore = (useWindow = false) => {
previewPayment(params) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/payments/${params.id}/send/preview`, { params })
.then((response) => {
resolve(response)
@@ -289,7 +287,7 @@ export const usePaymentStore = (useWindow = false) => {
sendEmail(data) {
return new Promise((resolve, reject) => {
axios
http
.post(`/api/v1/payments/${data.id}/send`, data)
.then((response) => {
const notificationStore = useNotificationStore()
@@ -308,7 +306,7 @@ export const usePaymentStore = (useWindow = false) => {
getNextNumber(params, setState = false) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/next-number?key=payment`, { params })
.then((response) => {
if (setState) {
@@ -331,7 +329,7 @@ export const usePaymentStore = (useWindow = false) => {
fetchPaymentModes(params) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/payment-methods`, { params })
.then((response) => {
this.paymentModes = response.data.data
@@ -346,7 +344,7 @@ export const usePaymentStore = (useWindow = false) => {
fetchPaymentMode(id) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/payment-methods/${id}`)
.then((response) => {
this.currentPaymentMode = response.data.data
@@ -362,7 +360,7 @@ export const usePaymentStore = (useWindow = false) => {
addPaymentMode(data) {
const notificationStore = useNotificationStore()
return new Promise((resolve, reject) => {
axios
http
.post(`/api/v1/payment-methods`, data)
.then((response) => {
this.paymentModes.push(response.data.data)
@@ -382,7 +380,7 @@ export const usePaymentStore = (useWindow = false) => {
updatePaymentMode(data) {
const notificationStore = useNotificationStore()
return new Promise((resolve, reject) => {
axios
http
.put(`/api/v1/payment-methods/${data.id}`, data)
.then((response) => {
if (response.data) {
@@ -410,7 +408,7 @@ export const usePaymentStore = (useWindow = false) => {
const notificationStore = useNotificationStore()
return new Promise((resolve, reject) => {
axios
http
.delete(`/api/v1/payment-methods/${id}`)
.then((response) => {
let index = this.paymentModes.findIndex(

View File

@@ -1,15 +1,13 @@
const { defineStore } = window.pinia
import { handleError } from '@/scripts/helpers/error-handling'
import { useNotificationStore } from '@/scripts/stores/notification'
import axios from 'axios'
import http from '@/scripts/http'
export const usePDFDriverStore = (useWindow = false) => {
const defineStoreFunc = useWindow ? window.pinia.defineStore : defineStore
const { global } = window.i18n
return defineStoreFunc({
id: 'pdf-driver',
return defineStoreFunc('pdf-driver', {
state: () => ({
pdfDriverConfig: null,
pdf_driver: 'dompdf',
@@ -28,7 +26,7 @@ export const usePDFDriverStore = (useWindow = false) => {
actions: {
async fetchDrivers() {
try {
const response = await axios.get('/api/v1/pdf/drivers')
const response = await http.get('/api/v1/pdf/drivers')
this.pdf_drivers = response.data
} catch (err) {
handleError(err)
@@ -37,7 +35,7 @@ export const usePDFDriverStore = (useWindow = false) => {
},
async fetchConfig() {
try {
const response = await axios.get('/api/v1/pdf/config')
const response = await http.get('/api/v1/pdf/config')
this.pdfDriverConfig = response.data
this.pdf_driver = response.data.pdf_driver
} catch (err) {
@@ -47,7 +45,7 @@ export const usePDFDriverStore = (useWindow = false) => {
},
async updateConfig(data) {
try {
const response = await axios.post('/api/v1/pdf/config', data)
const response = await http.post('/api/v1/pdf/config', data)
const notificationStore = useNotificationStore()
if (response.data.success) {
notificationStore.showNotification({

View File

@@ -1,5 +1,5 @@
import { defineStore } from 'pinia'
import axios from 'axios'
import http from '@/scripts/http'
import recurringInvoiceStub from '@/scripts/admin/stub/recurring-invoice'
import recurringInvoiceItemStub from '@/scripts/admin/stub/recurring-invoice-item'
import TaxStub from '../stub/tax'
@@ -20,9 +20,7 @@ export const useRecurringInvoiceStore = (useWindow = false) => {
const defineStoreFunc = useWindow ? window.pinia.defineStore : defineStore
const { global } = window.i18n
return defineStoreFunc({
id: 'recurring-invoice',
return defineStoreFunc('recurring-invoice', {
state: () => ({
templates: [],
recurringInvoices: [],
@@ -162,7 +160,7 @@ export const useRecurringInvoiceStore = (useWindow = false) => {
addRecurringInvoice(data) {
return new Promise((resolve, reject) => {
axios
http
.post('/api/v1/recurring-invoices', data)
.then((response) => {
this.recurringInvoices = [
@@ -187,7 +185,7 @@ export const useRecurringInvoiceStore = (useWindow = false) => {
fetchRecurringInvoice(id) {
return new Promise((resolve, reject) => {
this.isFetchingViewData = true
axios
http
.get(`/api/v1/recurring-invoices/${id}`)
.then((response) => {
Object.assign(this.newRecurringInvoice, response.data.data)
@@ -207,7 +205,7 @@ export const useRecurringInvoiceStore = (useWindow = false) => {
updateRecurringInvoice(data) {
return new Promise((resolve, reject) => {
axios
http
.put(`/api/v1/recurring-invoices/${data.id}`, data)
.then((response) => {
resolve(response)
@@ -234,7 +232,7 @@ export const useRecurringInvoiceStore = (useWindow = false) => {
selectCustomer(id) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/customers/${id}`)
.then((response) => {
this.newRecurringInvoice.customer = response.data.data
@@ -250,7 +248,7 @@ export const useRecurringInvoiceStore = (useWindow = false) => {
searchRecurringInvoice(data) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/recurring-invoices?${data}`)
.then((response) => {
resolve(response)
@@ -264,7 +262,7 @@ export const useRecurringInvoiceStore = (useWindow = false) => {
fetchRecurringInvoices(params) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/recurring-invoices`, { params })
.then((response) => {
this.recurringInvoices = response.data.data
@@ -282,7 +280,7 @@ export const useRecurringInvoiceStore = (useWindow = false) => {
deleteRecurringInvoice(id) {
return new Promise((resolve, reject) => {
axios
http
.post(`/api/v1/recurring-invoices/delete`, id)
.then((response) => {
let index = this.recurringInvoices.findIndex(
@@ -304,7 +302,7 @@ export const useRecurringInvoiceStore = (useWindow = false) => {
if (id) {
ids = [id]
}
axios
http
.post(`/api/v1/recurring-invoices/delete`, {
ids: ids,
})
@@ -492,7 +490,7 @@ export const useRecurringInvoiceStore = (useWindow = false) => {
fetchRecurringInvoiceFrequencyDate(params) {
return new Promise((resolve, reject) => {
axios
http
.get('/api/v1/recurring-invoice-frequency', { params })
.then((response) => {
this.newRecurringInvoice.next_invoice_at =

View File

@@ -1,4 +1,3 @@
import axios from 'axios'
import { defineStore } from 'pinia'
import { useBackupStore } from './backup'
import { useCategoryStore } from './category'
@@ -30,8 +29,7 @@ export const useResetStore = (useWindow = false) => {
const defineStoreFunc = useWindow ? window.pinia.defineStore : defineStore
const { global } = window.i18n
return defineStoreFunc({
id: 'reset',
return defineStoreFunc('reset', {
actions: {
clearPinia() {
const backupStore = useBackupStore()

View File

@@ -1,4 +1,4 @@
import axios from 'axios'
import http from '@/scripts/http'
import { defineStore } from 'pinia'
import { useNotificationStore } from '@/scripts/stores/notification'
import _ from 'lodash'
@@ -8,8 +8,7 @@ export const useRoleStore = (useWindow = false) => {
const defineStoreFunc = useWindow ? window.pinia.defineStore : defineStore
const { global } = window.i18n
return defineStoreFunc({
id: 'role',
return defineStoreFunc('role', {
state: () => ({
roles: [],
allAbilities: [],
@@ -38,7 +37,7 @@ export const useRoleStore = (useWindow = false) => {
actions: {
fetchRoles(params) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/roles`, { params })
.then((response) => {
this.roles = response.data.data
@@ -53,7 +52,7 @@ export const useRoleStore = (useWindow = false) => {
fetchRole(id) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/roles/${id}`)
.then((response) => {
this.currentRole.name = response.data.data.name
@@ -81,7 +80,7 @@ export const useRoleStore = (useWindow = false) => {
addRole(data) {
const notificationStore = useNotificationStore()
return new Promise((resolve, reject) => {
axios
http
.post('/api/v1/roles', data)
.then((response) => {
this.roles.push(response.data.role)
@@ -101,7 +100,7 @@ export const useRoleStore = (useWindow = false) => {
updateRole(data) {
const notificationStore = useNotificationStore()
return new Promise((resolve, reject) => {
axios
http
.put(`/api/v1/roles/${data.id}`, data)
.then((response) => {
if (response.data) {
@@ -128,7 +127,7 @@ export const useRoleStore = (useWindow = false) => {
if (this.allAbilities.length) {
resolve(this.allAbilities)
} else {
axios
http
.get(`/api/v1/abilities`, { params })
.then((response) => {
this.allAbilities = response.data.abilities
@@ -146,7 +145,7 @@ export const useRoleStore = (useWindow = false) => {
deleteRole(id) {
const notificationStore = useNotificationStore()
return new Promise((resolve, reject) => {
axios
http
.delete(`/api/v1/roles/${id}`)
.then((response) => {
let index = this.roles.findIndex((role) => role.id === id)

View File

@@ -1,4 +1,4 @@
import axios from 'axios'
import http from '@/scripts/http'
import { defineStore } from 'pinia'
import { useNotificationStore } from '@/scripts/stores/notification'
import { handleError } from '@/scripts/helpers/error-handling'
@@ -7,9 +7,7 @@ export const useTaxTypeStore = (useWindow = false) => {
const defineStoreFunc = useWindow ? window.pinia.defineStore : defineStore
const { global } = window.i18n
return defineStoreFunc({
id: 'taxType',
return defineStoreFunc('taxType', {
state: () => ({
taxTypes: [],
currentTaxType: {
@@ -44,7 +42,7 @@ export const useTaxTypeStore = (useWindow = false) => {
fetchTaxTypes(params) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/tax-types`, { params })
.then((response) => {
this.taxTypes = response.data.data
@@ -59,7 +57,7 @@ export const useTaxTypeStore = (useWindow = false) => {
fetchTaxType(id) {
return new Promise((resolve, reject) => {
axios
http
.get(`/api/v1/tax-types/${id}`)
.then((response) => {
this.currentTaxType = response.data.data
@@ -75,7 +73,7 @@ export const useTaxTypeStore = (useWindow = false) => {
addTaxType(data) {
const notificationStore = useNotificationStore()
return new Promise((resolve, reject) => {
axios
http
.post('/api/v1/tax-types', data)
.then((response) => {
this.taxTypes.push(response.data.data)
@@ -95,7 +93,7 @@ export const useTaxTypeStore = (useWindow = false) => {
updateTaxType(data) {
const notificationStore = useNotificationStore()
return new Promise((resolve, reject) => {
axios
http
.put(`/api/v1/tax-types/${data.id}`, data)
.then((response) => {
if (response.data) {
@@ -119,7 +117,7 @@ export const useTaxTypeStore = (useWindow = false) => {
fetchSalesTax(data) {
return new Promise((resolve, reject) => {
axios
http
.post('/api/m/sales-tax-us/current-tax', data)
.then((response) => {
if (response.data) {
@@ -141,7 +139,7 @@ export const useTaxTypeStore = (useWindow = false) => {
deleteTaxType(id) {
return new Promise((resolve, reject) => {
axios
http
.delete(`/api/v1/tax-types/${id}`)
.then((response) => {
if (response.data.success) {

Some files were not shown because too many files have changed in this diff Show More