93 Commits

Author SHA1 Message Date
Darko Gjorgjijoski
697c1af065 Move base-select into base 2025-08-28 12:02:39 +02:00
Darko Gjorgjijoski
70bfe1b688 Merge remote-tracking branch 'origin/master' into frontend-performance-improvements 2025-08-28 11:13:45 +02:00
Fabio Ribeiro
d69a56e2d5 feat: Tax included (#370)
* feat: Tax included

* Added a toggle switch in tax settings to enable the feature.
* Database migration adding tax_included field into estimates, invoices
  and recurring invoices table.
* Toggle switch to enable and store the tax_included by estimates,
  invoices and recurring invoices.
* In case of tax included enabled, total taxes will be recalculated and
  the invoices, estimates and recurring invoices total won't be sum with
  taxes.
* Apply tax included when discount_per_item/tax_per_item item is enabled.
* Custom component to show the net total when tax included is enabled.
* Update invoice and estimates pdfs with net total.

* chore: Tax included by default

A switch button inside the tax settings to enable the tax included by
default in invoices, estimates and recurring invoices.
2025-08-28 10:28:24 +02:00
Darko Gjorgjijoski
08e1bb2e22 Exclude .git directory from backups (#445)
* Exclude .git directory from backups

* Fix formatting
2025-08-28 10:02:06 +02:00
Darko Gjorgjijoski
29c15116bc Fix Adminer docker build (#444) 2025-08-28 10:01:50 +02:00
Loduis Madariaga Barrios
8e96d3e972 fix(csrf-token): add leading dot to session domain cookie. (#224)
* fix(csrf-token): add leading dot to session domain cookie.

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

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

---------

Co-authored-by: Darko Gjorgjijoski <5760249+gdarko@users.noreply.github.com>
2025-08-28 09:44:34 +02:00
Fabio Ribeiro
bf0d98c69c fix: Backup Job (#375)
Since `laravel-backup` major version was updated 8 to 9, the backup
ability was compromised, the main reason is the change on the method
contract from `BackupFactory` that now the `createFromArray` no longer
exists.
2025-06-11 23:23:19 +02:00
Fabio Ribeiro
73d4ac1eb1 fix: Payment confirmation error (#376)
Error caused when using Payments module, when try Stripe redirects back
to InvoiceShelf, and the module calls the InvoiceShelf `generatePayment`.

Relates #369
2025-06-11 22:56:56 +02:00
Leo
e832c7661a Fix: Heroicons v1 leftovers (#374)
Change the getOrderBy's button. 
Using Heroicons v2

Change the SortAscendingIcon to BarsArrowUpIcon
Change the SortDescendingIcon to BarsArrowDownIcon
2025-06-11 22:48:01 +02:00
Fabio Ribeiro
b962bc9227 fix: Create item with tax (#377)
When the `tax-per-item` is enabled and also has a tax added, during the
item creation, the price + tax returns an amount as float making the
database fail. To fix this was necessary to round the result.
2025-05-22 10:48:34 +02:00
Darko Gjorgjijoski
d70c04b841 Merge branch 'master' into frontend-performance-improvements 2025-05-05 02:24:27 +02:00
mchev
6d14dce668 Improving github workflow (#278)
* Improving workflow, updating dependencies and test pint with PHP 8.3, adding php 8.4 on tests

* Fix cache issue

* Not caching since it is not working
2025-05-04 15:22:43 +02:00
mchev
1ff220f0d8 Upgrading to Laravel 12 (#346)
* Upgrading to Laravel 12

* Upgrade lockfile

* Keep the old local filesystem driver base path

---------

Co-authored-by: Darko Gjorgjijoski <5760249+gdarko@users.noreply.github.com>
Co-authored-by: Darko Gjorgjijoski <dg@darkog.com>
2025-05-04 11:04:01 +02:00
Darko Gjorgjijoski
2e77a76c7b New Crowdin updates (#345)
* New translations en.json (Portuguese, Brazilian)

* Update source file en.json

* New translations en.json (Czech)

* New translations en.json (Indonesian)

* New translations en.json (Lithuanian)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Romanian)

* New translations en.json (French)

* New translations en.json (Spanish)

* New translations en.json (Arabic)

* New translations en.json (Bulgarian)

* New translations en.json (German)

* New translations en.json (Greek)

* New translations en.json (Finnish)

* New translations en.json (Italian)

* New translations en.json (Japanese)

* New translations en.json (Macedonian)

* New translations en.json (Dutch)

* New translations en.json (Polish)

* New translations en.json (Russian)

* New translations en.json (Slovak)

* New translations en.json (Slovenian)

* New translations en.json (Swedish)

* New translations en.json (Turkish)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Traditional)

* New translations en.json (Vietnamese)

* New translations en.json (Persian)

* New translations en.json (Thai)

* New translations en.json (Croatian)

* New translations en.json (Latvian)

* New translations en.json (Hindi)

* New translations en.json (Serbian (Latin))

* New translations en.json (Serbian (Latin))

* New translations en.json (Ukrainian)

* New translations en.json (Ukrainian)

* New translations en.json (Ukrainian)

* New translations en.json (Ukrainian)

* New translations en.json (Ukrainian)

* New translations en.json (Ukrainian)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Turkish)

* New translations en.json (Romanian)

* New translations en.json (Indonesian)

* New translations en.json (Russian)

* New translations en.json (Turkish)

* New translations en.json (Romanian)

* New translations en.json (Indonesian)

* New translations en.json (Russian)

* New translations en.json (French)

* New translations en.json (Spanish)

* New translations en.json (Arabic)

* New translations en.json (Bulgarian)

* New translations en.json (Czech)

* New translations en.json (German)

* New translations en.json (Greek)

* New translations en.json (Finnish)

* New translations en.json (Italian)

* New translations en.json (Japanese)

* New translations en.json (Lithuanian)

* New translations en.json (Macedonian)

* New translations en.json (Dutch)

* New translations en.json (Polish)

* New translations en.json (Slovak)

* New translations en.json (Slovenian)

* New translations en.json (Swedish)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Traditional)

* New translations en.json (Vietnamese)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Persian)

* New translations en.json (Thai)

* New translations en.json (Croatian)

* New translations en.json (Latvian)

* New translations en.json (Hindi)

* New translations en.json (Serbian (Latin))

* Update source file en.json

* New translations en.json (Turkish)

* New translations en.json (Romanian)

* New translations en.json (Indonesian)

* New translations en.json (Russian)

* New translations en.json (French)

* New translations en.json (Spanish)

* New translations en.json (Arabic)

* New translations en.json (Bulgarian)

* New translations en.json (Czech)

* New translations en.json (German)

* New translations en.json (Greek)

* New translations en.json (Finnish)

* New translations en.json (Italian)

* New translations en.json (Japanese)

* New translations en.json (Lithuanian)

* New translations en.json (Macedonian)

* New translations en.json (Dutch)

* New translations en.json (Polish)

* New translations en.json (Slovak)

* New translations en.json (Slovenian)

* New translations en.json (Swedish)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Traditional)

* New translations en.json (Vietnamese)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Persian)

* New translations en.json (Thai)

* New translations en.json (Croatian)

* New translations en.json (Latvian)

* New translations en.json (Hindi)

* New translations en.json (Serbian (Latin))
2025-05-04 02:50:56 +02:00
mchev
bf5b544ca3 Adding Flat Tax support with fixed amount (#253)
* Possibility to set a fixed amount on tax types settings

* Pint and manage flat taxes on items

* Fix display errors and handle global taxes

* Tests

* Pint with PHP 8.2 cause with PHP 8.3 version it cause workflow error

* Merging percent and fixed amount into one column

* Now display the currency on SelectTaxPopup on fixed taxes
2025-05-04 02:24:56 +02:00
Darko Gjorgjijoski
546f75d3a6 Pint updated files (#367) 2025-05-04 02:23:51 +02:00
Tim van Osch
bf40f792c2 Feat(Gotenberg): Opt-in alternative pdf generation for modern CSS (#184)
* WIP(gotenberg): add pdf generation abstraction and UI

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

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

* style(gotenberg): fix linter/styling issues

* fix(pdf): use pdf config policy

* fix: revert accidental capitalization in mail config vue

* Update composer, remove whitespace typo

* Fix small typos

* fix cookie/env issue

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

closes #357
2025-05-02 11:16:31 +02:00
mchev
14bfaff30b Fix security alert on axios 0.29 (#347) 2025-05-02 10:49:16 +02:00
Yannic Inselmann
b32c334a71 feat: default notes (#263)
* feat: default notes

* feat: include default invoice note in recurring invoice

* feat: use default export in tw config

* fix: test and naming

* fix: consistent ui for switch in note modal

* feat: little text improvements
2025-04-05 12:01:06 +02:00
mchev
2aa17513e1 Check version number on version.md file (#280) 2025-04-05 10:16:23 +02:00
mchev
ba243b28a9 Upgrade to Heroicons v2 (#281) 2025-04-05 02:11:12 +02:00
mchev
1bb65f420c Fix negative values on item price (#335)
* Fix negative values on item price

* Remove console log
2025-04-05 00:43:34 +02:00
Darko Gjorgjijoski
23a99758d2 New Crowdin updates (#308)
* New translations en.json (Chinese Traditional)

* New translations en.json (Czech)

* New translations en.json (Slovak)

* New translations en.json (Czech)

* New translations en.json (Czech)

* New translations en.json (French)

* New translations en.json (Czech)

* New translations en.json (Romanian)

* New translations en.json (Spanish)

* New translations en.json (Arabic)

* New translations en.json (Bulgarian)

* New translations en.json (German)

* New translations en.json (Greek)

* New translations en.json (Indonesian)

* New translations en.json (Chinese Traditional)

* New translations en.json (Slovak)

* New translations en.json (Finnish)

* New translations en.json (Italian)

* New translations en.json (Japanese)

* New translations en.json (Lithuanian)

* New translations en.json (Macedonian)

* New translations en.json (Dutch)

* New translations en.json (Polish)

* New translations en.json (Russian)

* New translations en.json (Slovenian)

* New translations en.json (Swedish)

* New translations en.json (Turkish)

* New translations en.json (Ukrainian)

* New translations en.json (Vietnamese)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Persian)

* New translations en.json (Thai)

* New translations en.json (Croatian)

* New translations en.json (Latvian)

* New translations en.json (Hindi)

* New translations en.json (Serbian (Latin))

* Update source file en.json

* New translations en.json (Spanish)

* New translations en.json (Japanese)

* New translations en.json (Polish)

* New translations en.json (Romanian)

* New translations en.json (Chinese Traditional)

* New translations en.json (Chinese Traditional)

* New translations en.json (Macedonian)

* New translations en.json (Indonesian)

* New translations en.json (Russian)

* New translations en.json (Russian)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Russian)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Russian)

* New translations en.json (Russian)

* New translations en.json (Swedish)

* New translations en.json (Arabic)

* New translations en.json (Czech)

* New translations en.json (Indonesian)

* New translations en.json (Indonesian)

* New translations en.json (Lithuanian)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Czech)

* New translations en.json (Indonesian)

* New translations en.json (Lithuanian)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Romanian)

* New translations en.json (French)

* New translations en.json (Spanish)

* New translations en.json (Arabic)

* New translations en.json (Bulgarian)

* New translations en.json (German)

* New translations en.json (Greek)

* New translations en.json (Finnish)

* New translations en.json (Italian)

* New translations en.json (Japanese)

* New translations en.json (Macedonian)

* New translations en.json (Dutch)

* New translations en.json (Polish)

* New translations en.json (Russian)

* New translations en.json (Slovak)

* New translations en.json (Slovenian)

* New translations en.json (Swedish)

* New translations en.json (Turkish)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Traditional)

* New translations en.json (Vietnamese)

* New translations en.json (Persian)

* New translations en.json (Thai)

* New translations en.json (Croatian)

* New translations en.json (Latvian)

* New translations en.json (Hindi)

* New translations en.json (Serbian (Latin))

* Update source file en.json

* New translations en.json (Spanish)
2025-04-05 00:42:24 +02:00
Rihards Simanovičs
139f8e2d13 Update Github issuepr templates (#341)
* chore(github): update bug report template

* chore(github): update feature request template

* chore(github): update code quality template

* chore(github): update issue selection menu config

* chore(github): update PR template

* chore(github): update PR template typo

* chore(github): remove one checklist item from PR template

* chore(github): update bug report to add a Docker check

* chore(github): update template formatting and links

* chore(github): final spell and grammar check of all issues and PR templates
2025-04-04 11:57:07 +02:00
Vid Čufar
ac1a582ecf fix: capitalise 'date' in PDF invoice due date label (#320) 2025-04-04 11:42:24 +02:00
mchev
6b168f36ec [HOTFIX] Customers names on table list issue #210 (#310)
* Fix customer dropdown

* Fix #250
2025-02-17 12:11:10 +01:00
Rihards Simanovičs
50b647e26e Merge pull request #318 from Pureball/translation
fix: en translation spelling
2025-02-17 10:45:46 +00:00
Pureball
57776b7c7d fix: en translation spelling 2025-02-17 10:40:38 +01:00
Darko Gjorgjijoski
8c343b4b92 New Crowdin updates (#304)
* New translations en.json (Spanish)

* New translations en.json (French)

* New translations en.json (Indonesian)

* New translations en.json (Indonesian)
2025-02-05 11:10:06 +01:00
Darko Gjorgjijoski
a60cac3e66 Update version.md 2025-02-05 11:04:44 +01:00
mchev
6a0d3a3bcc Fix customer dropdown (#307) 2025-02-05 11:04:17 +01:00
Darko Gjorgjijoski
3e52b84fa8 New Crowdin updates (#279)
* New translations en.json (Japanese)

* New translations en.json (Japanese)

* New translations en.json (Japanese)

* New translations en.json (Spanish)

* New translations en.json (Indonesian)

* New translations en.json (Indonesian)

* New translations en.json (Indonesian)

* New translations en.json (Japanese)

* New translations en.json (Japanese)

* New translations en.json (German)

* New translations en.json (Japanese)

* New translations en.json (Japanese)

* New translations en.json (Japanese)
2025-02-01 10:41:49 +01:00
Darko Gjorgjijoski
d1f45a790b Bump version 2025-02-01 10:41:19 +01:00
Darko Gjorgjijoski
bec0c058a8 Bump version 2025-01-13 02:14:23 +01:00
Darko Gjorgjijoski
d862ee05e9 Refactor Custom Invoice/Estimate PDF Templates (#277)
* Add utility class for managing templates

* Register custom pdf template views location

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

* Update PDF invoice/estimate template controllers

* Register pdf_templates filesystem disk

* Remove unused leftovers

* Reformat with pint
2025-01-13 01:20:13 +01:00
Darko Gjorgjijoski
12d9d6c801 Regenerate IDE Helper Assets (#274)
* Add back laravel/ui package

* Fix wrong Closure import

* Regenerate ide-helper files
2025-01-12 20:48:06 +01:00
Darko Gjorgjijoski
e9e52c60a7 Reformat with pint 2025-01-12 18:37:08 +01:00
Darko Gjorgjijoski
34b9f52af7 Upgrade Laravel and other third-party packages 2025-01-12 18:22:51 +01:00
Darko Gjorgjijoski
8b49332453 Remove unused imports 2025-01-12 18:10:04 +01:00
Darko Gjorgjijoski
8160b53689 Ditch global components 2025-01-12 17:53:44 +01:00
Darko Gjorgjijoski
fcf64c0b26 Upgrade Vite, Vue and other thrid-party packages 2025-01-12 17:38:54 +01:00
Darko Gjorgjijoski
7e2d257f7d Update caniuse-lite package 2025-01-12 17:11:50 +01:00
Darko Gjorgjijoski
c617f7d169 Fix: PDF Template command (#272)
* Fix `make:template` command

* Fix issue related to Vite assets

* Reformat code

---------

Co-authored-by: Steven Rombauts <steven@kotuha.be>
2025-01-12 16:19:54 +01:00
Darko Gjorgjijoski
1804481fc6 New Crowdin updates (#262)
* New translations en.json (Indonesian)

* New translations en.json (Turkish)

* New translations en.json (Hindi)

* New translations en.json (Hindi)

* New translations en.json (Persian)

* New translations en.json (Chinese Traditional)

* Update source file en.json

* New translations en.json (Dutch)

* New translations en.json (Czech)

* New translations en.json (Indonesian)

* New translations en.json (Turkish)

* New translations en.json (Hindi)

* New translations en.json (Persian)

* New translations en.json (Chinese Traditional)

* New translations en.json (Romanian)

* New translations en.json (French)

* New translations en.json (Spanish)

* New translations en.json (Arabic)

* New translations en.json (Bulgarian)

* New translations en.json (German)

* New translations en.json (Greek)

* New translations en.json (Finnish)

* New translations en.json (Italian)

* New translations en.json (Japanese)

* New translations en.json (Lithuanian)

* New translations en.json (Macedonian)

* New translations en.json (Polish)

* New translations en.json (Russian)

* New translations en.json (Slovak)

* New translations en.json (Slovenian)

* New translations en.json (Swedish)

* New translations en.json (Ukrainian)

* New translations en.json (Vietnamese)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Thai)

* New translations en.json (Croatian)

* New translations en.json (Latvian)

* New translations en.json (Serbian (Latin))

* New translations en.json (Dutch)

* New translations en.json (Czech)

* New translations en.json (Indonesian)

* New translations en.json (Turkish)

* New translations en.json (Hindi)

* New translations en.json (Persian)

* New translations en.json (Chinese Traditional)

* New translations en.json (Romanian)

* New translations en.json (French)

* New translations en.json (Spanish)

* New translations en.json (Arabic)

* New translations en.json (Bulgarian)

* New translations en.json (German)

* New translations en.json (Greek)

* New translations en.json (Finnish)

* New translations en.json (Italian)

* New translations en.json (Japanese)

* New translations en.json (Lithuanian)

* New translations en.json (Macedonian)

* New translations en.json (Polish)

* New translations en.json (Russian)

* New translations en.json (Slovak)

* New translations en.json (Slovenian)

* New translations en.json (Swedish)

* New translations en.json (Ukrainian)

* New translations en.json (Vietnamese)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Thai)

* New translations en.json (Croatian)

* New translations en.json (Latvian)

* New translations en.json (Serbian (Latin))

* Update source file en.json
2025-01-12 15:09:28 +01:00
mchev
9bed81fe8f Handle demo version of the app (#256) 2025-01-12 13:56:52 +01:00
Darko Gjorgjijoski
f52b73f517 Invoice time support (#269)
* Changed invoice date to datetime

* Fixed code style errors

* Update TimeFormatsController.php

* Update TimeFormatter.php

* Update TimeFormatsController namespace

* Fix missing comma in language file

* Fix formatting

---------

Co-authored-by: troky <troky2001@yahoo.com>
2025-01-12 13:32:47 +01:00
Mohamed Safouan Besrour
32e03b98a3 Update CurrenciesTableSeeder.php (#258) 2025-01-12 11:42:12 +01:00
mchev
3f560a1de2 Add Amount Paid and Amount Due to Invoice PDF (#248)
* Display payment status on invoices

* Update en.json
2025-01-12 11:36:36 +01:00
mchev
ef9adbd6c9 Adding sent and viewed field to payload (#247) 2025-01-12 11:27:29 +01:00
Loduis Madariaga Barrios
969cbe9ad8 fix: update company info on array of companies. (#227) 2025-01-12 11:02:58 +01:00
Darko Gjorgjijoski
6031f0dbdd Module paths (#268)
* refactor: Update file paths for stub files for module creation (#144)

---------

Co-authored-by: Dominik Hendrix <dominik.hendrix@g-fittings.com>

* Revert unrelated change

---------

Co-authored-by: Dominik Hendrix <all@dhendrix.de>
Co-authored-by: Dominik Hendrix <dominik.hendrix@g-fittings.com>
2025-01-12 10:49:04 +01:00
Loduis Madariaga Barrios
06a71fc7b3 fix: Corrects and simplifies password visibility icon logic. (#222) 2025-01-12 10:48:34 +01:00
Darko Gjorgjijoski
fd1c7be1f8 New Crowdin updates (#241)
* New translations en.json (Slovak)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (French)

* New translations en.json (Spanish)

* New translations en.json (Spanish)

* New translations en.json (Indonesian)

* New translations en.json (German)

* New translations en.json (Arabic)

* New translations en.json (Arabic)

* New translations en.json (Japanese)

* New translations en.json (Japanese)

* New translations en.json (Dutch)

* New translations en.json (Czech)

* New translations en.json (Czech)
2025-01-05 12:06:14 +01:00
Darko Gjorgjijoski
c466d51fe0 New Crowdin updates (#235)
* New translations en.json (Swedish)

* New translations en.json (Croatian)

* New translations en.json (Russian)

* New translations en.json (French)

* New translations en.json (Ukrainian)

* New translations en.json (Slovak)

* New translations en.json (Romanian)

* New translations en.json (Spanish)

* New translations en.json (Arabic)

* New translations en.json (Bulgarian)

* New translations en.json (Czech)

* New translations en.json (German)

* New translations en.json (Greek)

* New translations en.json (Finnish)

* New translations en.json (Italian)

* New translations en.json (Japanese)

* New translations en.json (Lithuanian)

* New translations en.json (Macedonian)

* New translations en.json (Dutch)

* New translations en.json (Polish)

* New translations en.json (Slovenian)

* New translations en.json (Turkish)

* New translations en.json (Chinese Traditional)

* New translations en.json (Vietnamese)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Indonesian)

* New translations en.json (Persian)

* New translations en.json (Thai)

* New translations en.json (Latvian)

* New translations en.json (Hindi)

* New translations en.json (Serbian (Latin))
2024-12-03 18:39:00 +01:00
OniriCorpe
e8dccb178b Specifying that a custom frequency should be in the cron format (#231) 2024-12-03 15:27:21 +01:00
Darko Gjorgjijoski
f5b36d7e6c New Crowdin updates (#232)
* New translations en.json (French)

* New translations en.json (Croatian)

* New translations en.json (Ukrainian)

* New translations en.json (Ukrainian)

* New translations en.json (Ukrainian)

* New translations en.json (Slovak)
2024-12-03 15:27:09 +01:00
OniriCorpe
a32bbb6268 Fixes receipt view (#234)
The #185 modifications were also necessary here
2024-12-03 15:26:05 +01:00
Darko Gjorgjijoski
9599814ae5 New Crowdin updates (#225)
* New translations en.json (Swedish)

* New translations en.json (Croatian)

* New translations en.json (Swedish)

* New translations en.json (Swedish)

* New translations en.json (Swedish)

* New translations en.json (Croatian)

* New translations en.json (Russian)

* New translations en.json (French)

* New translations en.json (French)
2024-11-29 21:29:22 +01:00
Darko Gjorgjijoski
0d14da93d0 New Crowdin updates (#221)
* New translations en.json (Swedish)

* New translations en.json (Swedish)

* New translations en.json (Greek)

* New translations en.json (Greek)
2024-11-18 01:21:13 +01:00
Loduis Madariaga Barrios
298c170867 feat(infrastructure): improve htaccess configuration. (#214) 2024-11-17 00:18:35 +01:00
Rihards Simanovičs
b49228eabf chore: Update Issue Template (#215)
* chore: update issue template

Signed-off-by: Rihards Simanovics <rihards.s@griffin-web.studio>

* chore: correct spelling in issue template

Signed-off-by: Rihards Simanovics <rihards.s@griffin-web.studio>

* fix: adjust render for some textarea fields and add a reproduction step

* fix: remove render from markdown fields

looks like github gets confused as all textarea fields are markdown by the default

* style: add comments and correct labels

* feat(github): add code quality issue template

* refactor: remove direct references to Files

From template fields, descriptions, and placeholders

* refactor(github): convert feature request template to issue form

---------

Signed-off-by: Rihards Simanovics <rihards.s@griffin-web.studio>
2024-11-17 00:18:18 +01:00
Darko Gjorgjijoski
99ada6fbcc New Crowdin updates (#217)
* New translations en.json (Japanese)

* New translations en.json (Japanese)
2024-11-17 00:17:37 +01:00
Darko Gjorgjijoski
ed4a59574c Customer tax id validation (#220)
* Remove tax_id validation

* Remove validation leftovers
2024-11-17 00:17:25 +01:00
Darko Gjorgjijoski
be2e1df442 Add 'Database Overwrtie' during install for SQLite type (#219) 2024-11-17 00:07:44 +01:00
Darko Gjorgjijoski
ddca5a7b6d New translations en.json (Dutch) (#211) 2024-11-11 12:24:48 +01:00
Darko Gjorgjijoski
51d001aa0f New Crowdin updates (#209)
* New translations en.json (Romanian)

* New translations en.json (French)

* New translations en.json (Spanish)

* New translations en.json (Arabic)

* New translations en.json (Bulgarian)

* New translations en.json (Czech)

* New translations en.json (German)

* New translations en.json (Greek)

* New translations en.json (Finnish)

* New translations en.json (Italian)

* New translations en.json (Japanese)

* New translations en.json (Lithuanian)

* New translations en.json (Macedonian)

* New translations en.json (Dutch)

* New translations en.json (Polish)

* New translations en.json (Russian)

* New translations en.json (Slovak)

* New translations en.json (Slovenian)

* New translations en.json (Swedish)

* New translations en.json (Turkish)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Traditional)

* New translations en.json (Vietnamese)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Indonesian)

* New translations en.json (Persian)

* New translations en.json (Thai)

* New translations en.json (Croatian)

* New translations en.json (Latvian)

* New translations en.json (Hindi)

* New translations en.json (Serbian (Latin))

* New translations en.json (Turkish)
2024-11-09 21:39:07 +01:00
Darko Gjorgjijoski
5d817b5c45 Ignore the translations branch from CI 2024-11-09 15:22:23 +01:00
Ali Çömez | Slaweally
ac2c73829c Update tr.json (#205) 2024-11-09 15:01:57 +01:00
Loduis Madariaga Barrios
317f763a85 chore(wizard): add missing translations for Spanish version in setup wizard (#207) 2024-11-09 14:38:20 +01:00
Joël Kuijper
e112ed5875 Add missing dutch translations (#208) 2024-11-09 14:38:02 +01:00
Darko Gjorgjijoski
84c10264ed Add missing strings lost in merge (#200)
Issue #199
2024-11-02 21:47:22 +01:00
Darko Gjorgjijoski
b143642056 New Crowdin updates (#188)
* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Croatian)

* New translations en.json (Croatian)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Croatian)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Spanish)

* New translations en.json (Japanese)

* New translations en.json (Japanese)

* New translations en.json (German)

* New translations en.json (Japanese)

* New translations en.json (Spanish)

* New translations en.json (Croatian)

* New translations en.json (Spanish)

* New translations en.json (Japanese)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (German)

* New translations en.json (Croatian)

* New translations en.json (Romanian)

* New translations en.json (French)

* New translations en.json (Arabic)

* New translations en.json (Bulgarian)

* New translations en.json (Czech)

* New translations en.json (Greek)

* New translations en.json (Finnish)

* New translations en.json (Italian)

* New translations en.json (Lithuanian)

* New translations en.json (Macedonian)

* New translations en.json (Dutch)

* New translations en.json (Polish)

* New translations en.json (Russian)

* New translations en.json (Slovak)

* New translations en.json (Slovenian)

* New translations en.json (Swedish)

* New translations en.json (Turkish)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Traditional)

* New translations en.json (Vietnamese)

* New translations en.json (Indonesian)

* New translations en.json (Persian)

* New translations en.json (Thai)

* New translations en.json (Latvian)

* New translations en.json (Hindi)

* New translations en.json (Serbian (Latin))

* Update source file en.json
2024-11-02 12:49:04 +01:00
mchev
967c225df9 Merge pull request #198 from mchev/invoice_cancellation
Support for Zero and Negative Item Quantities on Invoices
2024-11-02 12:20:55 +01:00
mchev
e1a0a2d8e4 Merge pull request #138 from IDerr/develop
Add VAT and Tax into PDF Trait
2024-11-02 12:19:43 +01:00
mchev
748168ffa5 Merge pull request #189 from loduis/master
enhance(wizard): update translations, refine icons, and add automated…
2024-11-02 10:36:52 +01:00
mchev
4db243e136 Merge branch 'master' into master 2024-11-02 10:31:53 +01:00
mchev
bc0c8d5348 Merge pull request #166 from mchev/customer_tax_id
Include a Tax ID field in both customer creation and invoices
2024-11-02 10:28:49 +01:00
mchev
6583569b1c Merge pull request #190 from mchev/issue_71
Refactor editor and allow links
2024-11-02 10:28:30 +01:00
mchev
134c99369e Merge pull request #186 from mchev/issue_181
Fix table data not refreshing properly (keys)
2024-11-02 10:28:12 +01:00
Loduis Madariaga
f2ae4e17c8 enhance(wizard): update translations, refine icons, and add automated requirement verification 2024-10-17 07:24:17 -05:00
Martin Chevignard
4ff62c0adf Refactor editor and allow links 2024-10-17 11:16:11 +02:00
Darko Gjorgjijoski
2604db32ce New Crowdin updates (#154)
* New translations en.json (Spanish)

* New translations en.json (Finnish)

* New translations en.json (French)

* New translations en.json (German)

* New translations en.json (Romanian)

* New translations en.json (Arabic)

* New translations en.json (Bulgarian)

* New translations en.json (Czech)

* New translations en.json (Greek)

* New translations en.json (Italian)

* New translations en.json (Japanese)

* New translations en.json (Lithuanian)

* New translations en.json (Macedonian)

* New translations en.json (Dutch)

* New translations en.json (Polish)

* New translations en.json (Russian)

* New translations en.json (Slovak)

* New translations en.json (Slovenian)

* New translations en.json (Swedish)

* New translations en.json (Turkish)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Traditional)

* New translations en.json (Vietnamese)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Indonesian)

* New translations en.json (Persian)

* New translations en.json (Thai)

* New translations en.json (Croatian)

* New translations en.json (Latvian)

* New translations en.json (Hindi)

* New translations en.json (Serbian (Latin))

* New translations en.json (Russian)

* New translations en.json (Spanish)

* New translations en.json (Spanish)

* New translations en.json (Japanese)

* New translations en.json (Japanese)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Japanese)

* New translations en.json (Japanese)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Spanish)

* New translations en.json (Japanese)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Slovak)

* New translations en.json (Swedish)

* New translations en.json (Vietnamese)

* New translations en.json (Romanian)

* New translations en.json (French)

* New translations en.json (Arabic)

* New translations en.json (Bulgarian)

* New translations en.json (Czech)

* New translations en.json (German)

* New translations en.json (Greek)

* New translations en.json (Finnish)

* New translations en.json (Italian)

* New translations en.json (Lithuanian)

* New translations en.json (Macedonian)

* New translations en.json (Dutch)

* New translations en.json (Polish)

* New translations en.json (Russian)

* New translations en.json (Slovenian)

* New translations en.json (Turkish)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Traditional)

* New translations en.json (Indonesian)

* New translations en.json (Persian)

* New translations en.json (Thai)

* New translations en.json (Croatian)

* New translations en.json (Latvian)

* New translations en.json (Hindi)

* New translations en.json (Serbian (Latin))

* Update source file en.json

* New translations en.json (Japanese)

* New translations en.json (Japanese)
2024-10-16 15:18:26 +02:00
Darko Gjorgjijoski
01d32d50b5 Exclude crowdin branch from workflows 2024-10-16 00:54:02 +02:00
SalvaGR
0c86563375 fix: missing values in en.json translation (#179) 2024-10-15 20:53:08 +02:00
mchev
33c2949a7b Fix carbon int val (#185) 2024-10-15 20:51:17 +02:00
mchev
547a2be0a3 Update readme.md 2024-10-15 19:14:55 +02:00
Martin Chevignard
59b43fa258 Public Invoice View fix 2024-10-15 17:55:56 +02:00
Martin Chevignard
168b741936 Upadate filters with laravel best practices 2024-10-15 16:20:04 +02:00
SpeedX
022769ba38 Fix Logo render in Payment PDF (#162)
* Fix logo render in Payment PDF

* Fix CSS to make it consistent with other templates
2024-10-14 11:47:10 +02:00
mchev
068f48568e Fix carbon dates on custom fields value (#178) 2024-10-11 13:17:01 +02:00
Martin Chevignard
03b9defeb1 Customers tax id field 2024-10-04 12:07:29 +02:00
mchev
dd98df1c77 Fix long text on dropdown items (#157) 2024-09-30 20:15:56 +02:00
429 changed files with 37212 additions and 17614 deletions

View File

@@ -2,12 +2,6 @@ FROM adminer:latest
USER root
RUN set -x && \
apt update && \
apt install curl -y && \
cd /var/www/html/plugins-enabled && \
curl -O https://gist.githubusercontent.com/gdarko/00af6e9a754f09c3f81cd3c606c33311/raw/d5f6a30f00edecf30a5d380340d9dae79a3b7352/login-password-less.php
USER adminer
CMD [ "php", "-S", "[::]:8080", "-t", "/var/www/html" ]

View File

@@ -63,6 +63,11 @@ services:
networks:
- invoiceshelf-dev
pdf:
image: gotenberg/gotenberg:8
networks:
- invoiceshelf-dev
networks:
invoiceshelf-dev:

View File

@@ -62,6 +62,11 @@ services:
networks:
- invoiceshelf-dev
pdf:
image: gotenberg/gotenberg:8
networks:
- invoiceshelf-dev
networks:
invoiceshelf-dev:

View File

@@ -50,5 +50,10 @@ services:
networks:
- invoiceshelf-dev
pdf:
image: gotenberg/gotenberg:8
networks:
- invoiceshelf-dev
networks:
invoiceshelf-dev:

View File

@@ -4,7 +4,7 @@ APP_DEBUG=true
APP_NAME="InvoiceShelf"
APP_LOG_LEVEL=debug
APP_TIMEZONE=UTC
APP_URL=http://invoiceshelf.test
APP_URL=
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
@@ -49,8 +49,11 @@ PUSHER_APP_ID=
PUSHER_KEY=
PUSHER_SECRET=
SANCTUM_STATEFUL_DOMAINS=invoiceshelf.test
TRUSTED_PROXIES="*"
CRON_JOB_AUTH_TOKEN=""
LOG_STACK=single
PDF_DRIVER=dompdf
GOTENBERG_HOST=
GOTENBERG_PAPERSIZE=

View File

View File

@@ -1,26 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Please complete the following information:**
- InvoiceShelf version:
- PHP version:
- Database type and version:
**Optional info**
- OS: [e.g. Ubuntu]
- Browser: [e.g. chrome, safari]

237
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,237 @@
name: Bug report
description: Template for bug reports
labels: ['bug', 'triage']
type: Bug
projects: ['InvoiceShelf/2']
assignees:
- rihards-simanovics
body:
# Ask user to confirm they've tried to fix or research the issue before
# posting a bug report.
- type: checkboxes
id: confirm-read-documentation
attributes:
label: Issue filing prerequisites
description: 'Prior to filing an issue please confirm that:'
options:
- label: I've checked the [documentation](https://docs.invoiceshelf.com/).
required: true
- label: I've looked for similar issues both Open and Closed.
required: true
- label: >-
I've tried clearing both cache and cookies in my browser or tried
opening the app in the Incognito/InPrivate window.
required: true
# Thank users for taking time to submit a bug report
- type: markdown
attributes:
value: >-
If you did all of the above, we would first like to thank you for taking
the time to fill out this bug report. It will help us tremendously to
find and fix the issue sooner!
# Description of the bug
- type: textarea
id: bug-description
attributes:
label: Describe the bug
description: A clear and concise description of what the bug is.
placeholder: >-
When doing `x`, `y`, and `z` in that order, an error occurs, but when `x`,
`z` and then `y` is done, the error does not occur.
validations:
required: true
# Reproduction steps
- type: textarea
id: reproduce
attributes:
label: Steps to Reproduce the issue
description: A clear step-by-step explanation of how to reproduce your issue
placeholder: >-
## Produces an error:
1. Do X;
2. Do Y;
3. Do Z;
## Works fine:
4. Do X;
5. Do Z;
6. Do Y.
validations:
required: true
# Expected Behaviour
- type: textarea
id: expected-behaviour
attributes:
label: Expected behaviour
description: A clear and concise description of what you expected to happen.
placeholder: Doing `x`, `y`, and `z` in that order should not generate an error.
validations:
required: true
# Actual Behaviour
- type: textarea
id: actual-behaviour
attributes:
label: Actual behaviour
description: A clear and concise description of what actually happens.
placeholder: Doing `x`, `y`, and `z` in that order generates an error.
validations:
required: true
# Section break
- type: markdown
attributes:
value: |-
## Environment
Now let's collect some information about your environment.
# Use Docker?
- type: checkboxes
id: confirm-docker-install
attributes:
label: Docker
description: 'Please note that unless the issue is with the app itself, you should file a bug report under the [docker](https://github.com/InvoiceShelf/docker) repository.'
options:
- label: App running in Docker Container.
- label: Docker container running behind Reverse proxy.
# App Version
- type: input
id: invoiceshelf-version
attributes:
label: InvoiceShelf version
placeholder: v0.0.0
validations:
required: true
# PHP Version
- type: input
id: php-version
attributes:
label: PHP version
placeholder: v0.0.0
validations:
required: true
# DB Type
- type: input
id: database-type
attributes:
label: Database type
placeholder: MariaDB / MySQL / PostgreSQL / SQLite
validations:
required: true
# DB Version
- type: input
id: db-version
attributes:
label: Database version
placeholder: v0.0.0
validations:
required: true
# Web Browser
- type: input
id: web-browser
attributes:
label: Web Browser
placeholder: 'Firefox / Safari / or any other (Chromium-based browser)'
# OS Version
- type: input
id: server-os
attributes:
label: Server OS
description: If Linux, please make sure to provide both the distro name and its version
placeholder: Windows / Linux (e.g. Ubuntu 24.04)
# Associated Logs
- type: markdown
attributes:
value: >-
## Logs
Last but not least, could you please take some time to get us the logs
for:
# Log Reverse proxy
- type: textarea
id: log-rev-proxy
attributes:
label: Reverse-proxy logs
description: >-
Please provide logs from your Apache, Nginx, Traefik, or any other
reverse proxy application.
placeholder: >-
2023-04-12 10:15:32 [NGINX] 172.16.0.5 - - [12/Apr/2023:10:15:32 +0000] "GET /index.html HTTP/1.1" 200 1024 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"
2023-04-12 10:15:33 [NGINX] 172.16.0.7 - - [12/Apr/2023:10:15:33 +0000] "POST /login HTTP/1.1" 302 - "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/603.3.8 (KHTML, like Gecko) Version/10.1.2 Safari/603.3.8"
2023-04-12 10:15:34 [NGINX] 172.16.0.9 - - [12/Apr/2023:10:15:34 +0000] "GET /about HTTP/1.1" 200 2048 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36"
2023-04-12 10:15:35 [NGINX] 172.16.0.11 - - [12/Apr/2023:10:15:35 +0000] "PUT /api/v1/users/123 HTTP/1.1" 200 - "https://example.com/profile" "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1"
2023-04-12 10:15:36 [NGINX] 172.16.0.13 - - [12/Apr/2023:10:15:36 +0000] "DELETE /api/v1/products/456 HTTP/1.1" 204 - "-" "Dalvik/2.1.0 (Linux; U; Android 10; Pixel 4 Build/QD1A.190821.014.C2)"
2023-04-12 10:15:37 [NGINX] 172.16.0.15 - - [12/Apr/2023:10:15:37 +0000] "GET /easter-egg HTTP/1.1" 200 42 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.3"
render: irc logs
# PHP/Laravel Logs
- type: textarea
id: log-laravel
attributes:
label: Laravel/PHP logs
description: Please provide logs from either PHP or Laravel or both.
placeholder: >-
[2015-02-04 10:00:00] production.INFO: Laravel 5.0 released. The world rejoices.
[2015-12-03 12:00:00] production.INFO: PHP 7.0 introduced scalar type declarations. Minds blown.
[2016-05-15 14:00:00] production.INFO: Composer installed new dependencies. Dependency hell avoided.
[2017-08-20 16:00:00] production.INFO: Artisan command executed: migrate. Smooth sailing.
[2018-01-10 09:00:00] production.INFO: User login successful.
[2018-01-10 09:05:00] production.ERROR: Database connection failed. Did you try turning it off and on again?
[2018-01-10 09:10:00] production.WARNING: Deprecated function used in UserController.php. Time to refactor, again.
[2018-01-10 09:15:00] production.ERROR: Uncaught Exception: Division by zero. Oops, maths is hard.
[2018-01-10 09:20:00] production.INFO: User logout successful. See you later, alligator.
[2019-03-25 11:00:00] production.INFO: Cache cleared. Fresh start!
[2019-06-30 13:00:00] production.ERROR: Syntax error. Missing semicolon strikes again.
[2020-09-10 15:00:00] production.INFO: User registered. Welcome aboard!
[2021-11-05 17:00:00] production.WARNING: Low disk space. Time to clean up.
[2022-12-20 19:00:00] production.INFO: Server rebooted. All systems go.
[2024-11-14 18:56:20] production.INFO: Unexpected item in the bagging area. Please wait for assistance.
render: irc logs
# Special thanks
- type: markdown
attributes:
value: >-
This template was generated with [Issue Forms
Creator](https://issue-forms-creator.netlify.app)

View File

@@ -0,0 +1,94 @@
# Big thanks to https://github.com/files-community/Files/ for a template of the code_quality_issue
name: Code Quality Issue
description: Create a code quality issue to help InvoiceShelf keep a clean codebase
labels:
- code quality
- triage
projects: ['InvoiceShelf/2']
assignees:
- rihards-simanovics
body:
# Tip to warn of checking for existing issues
- type: markdown
attributes:
value: >
> [!TIP]
> Have you checked for similar code quality issues? There's a
possibility your suggestion is already being tracked. Please do a
thorough search before creating a new issue.
# Ask user to confirm they've tried to fix or research the issue before
# posting a bug report.
- type: checkboxes
id: confirm-read-documentation
attributes:
label: Issue filing prerequisites
description: 'Prior to filing an issue please confirm that:'
options:
- label: I've checked the [documentation](https://docs.invoiceshelf.com/).
required: true
- label: I've looked for similar issues both Open and Closed.
required: true
- label: >-
I've tried clearing both cache and cookies in my browser or tried
opening the app in the Incognito/InPrivate window.
required: true
# Issue body
- type: textarea
id: description
attributes:
label: Description
description: A clear and concise description of what the code quality issue is.
validations:
required: true
# Related code
- type: textarea
id: code-in-question
attributes:
label: The code in question
description: >-
A list of the different InvoiceShelf and/or areas of the code concerned by the issue.
validations:
required: true
# Gains
- type: textarea
id: gains
attributes:
label: Gains
description: What would fixing this code quality issue bring to the source code?
placeholder: >-
- e.g. Better readability.
- e.g. Uncoupling concepts X and Y.
- e.g. Clarifying the responsibility of class C.
validations:
required: true
# Requirements
- type: textarea
id: requirements
attributes:
label: Requirements
description: Describe all the requirements to solve the code quality issue.
placeholder: >-
- e.g. Using a specific design pattern.
- e.g. Separating Interface I into three new interfaces I1, I2 and I3.
- e.g. Regrouping the duplicated process into a new helper.
# Remarks
- type: textarea
id: comments
attributes:
label: Comments
description: >-
Additional information, comments or screenshots about the code quality
issue.

11
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: Documentation
url: https://docs.invoiceshelf.com/
about: App documentation.
- name: Translation issue
url: https://crowdin.com/project/invoiceshelf
about: Help improve translations on Crowdin.
- name: Support, Question & Discussion - Official Discord Server
url: https://discord.gg/hwg2FtwWHW
about: Post your questions and join the discussion. You can participate in chats on every channel.

View File

@@ -1,17 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

View File

@@ -0,0 +1,96 @@
# Big thanks to https://github.com/files-community/Files/ for a template of the feature_request
name: Feature Request
labels:
- feature request
- triage
type: Feature
projects: ['InvoiceShelf/2']
assignees:
- rihards-simanovics
description: >-
This project thrives from differentiation from competing apps. Suggest an idea
for InvoiceShelf.
body:
# Tip to warn of checking for existing issues
- type: markdown
attributes:
value: >
> [!TIP]
>Have you checked for similar feature requests? There's a possibility
your suggestion is already being tracked. Please do a thorough search
before creating a new issue.
# Ask user to confirm they've tried to fix or research the issue before
# posting a bug report.
- type: checkboxes
id: confirm-read-documentation
attributes:
label: Issue filing prerequisites
description: 'Prior to filing an issue please confirm that:'
options:
- label: I've checked the [documentation](https://docs.invoiceshelf.com/).
required: true
- label: I've looked for similar issues with feature requests both Open and Closed.
required: true
- label: >-
I've tried clearing both cache and cookies in my browser or tried
opening the app in the Incognito/InPrivate window.
required: true
# Description
- type: textarea
id: what-feature
attributes:
label: What feature or improvement do you think would benefit InvoiceShelf?
description: Please include your use case
placeholder: >-
I propose that feature J be implemented as I believe it will greatly benefit the application.
validations:
required: true
# Tooltip about Requirements
- type: markdown
attributes:
value: >
---
Please include a list of changes required to make this improvement. A good
rule of thumb is to start your proposal with no more than 7 high-level
requirements.
# Requirements
- type: textarea
id: requirements
attributes:
label: Requirements
description: Describe all the requirements to make your idea happen.
placeholder: >-
- This proposal will accomplish X
- This proposal will accomplish Y
- This proposal will accomplish Z
validations:
required: true
# InvoiceShelf Version
- type: input
id: invoiceshelf_version
attributes:
label: App Version
description: Which version of InvoiceShelf are you currently using?
placeholder: v0.0.0
validations:
required: true
# Additional Comments
- type: textarea
id: comments
attributes:
label: Comments
description: >-
Additional information, comments or screenshots about the feature
request.

View File

@@ -0,0 +1,17 @@
## Pre-review request checklist
- [ ] (\*) I have listed all changes in the `Changes` section.
- [ ] (opt) I have listed all issues this PR addresses in the `Issues` section [with the issue/discussion IDs](https://docs.github.com/en/issues/tracking-your-work-with-issues/using-issues/linking-a-pull-request-to-an-issue).
- [ ] (\*) I have tested my changes.
- [ ] (\*) I have considered backwards compatibility.
## Changes
- Added feature X
- Removed Code Y
- Refactored code Z
## Issues
- fixes #
- closes #

View File

@@ -7,11 +7,15 @@ on:
- '**/*.md'
- 'public/build/*.js'
- 'public/build/**/*.js'
branches-ignore:
- 'translations'
pull_request:
paths-ignore:
- '**/*.md'
- 'public/build/*.js'
- 'public/build/**/*.js'
branches-ignore:
- 'translations'
# Allow manually triggering the workflow.
workflow_dispatch:
@@ -36,7 +40,7 @@ jobs:
- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.2
php-version: 8.3
- name: Checkout code
uses: actions/checkout@v4
@@ -57,12 +61,13 @@ jobs:
php-version:
- 8.2
- 8.3
- 8.4
env:
extensions: bcmath, curl, dom, gd, imagick, json, libxml, mbstring, pcntl, pdo, pdo_mysql, zip
steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup PHP Action
uses: shivammathur/setup-php@v2
@@ -76,7 +81,7 @@ jobs:
uses: ramsey/composer-install@v2
- name: Use Node.js 20
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 20
@@ -100,7 +105,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
@@ -115,7 +120,7 @@ jobs:
composer-options: --no-dev
- name: Use Node.js 20
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 20

File diff suppressed because it is too large Load Diff

1629
_ide_helper_models.php Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -2,8 +2,11 @@
namespace App\Console\Commands;
use App\Space\PdfTemplateUtils;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
class CreateTemplateCommand extends Command
{
@@ -37,26 +40,53 @@ class CreateTemplateCommand extends Command
public function handle(): int
{
$templateName = $this->argument('name');
$type = $this->option('type');
$templateType = $this->option('type');
if (! $type) {
$type = $this->choice('Create a template for?', ['invoice', 'estimate']);
if (! $templateType) {
$templateType = $this->choice('Create a template for?', ['invoice', 'estimate']);
}
if (Storage::disk('views')->exists("/app/pdf/{$type}/{$templateName}.blade.php")) {
if (PdfTemplateUtils::customTemplateFileExists($templateType, sprintf('%s.blade.php', $templateName))) {
$this->info('Template with given name already exists.');
return 0;
return self::INVALID;
}
Storage::disk('views')->copy("/app/pdf/{$type}/{$type}1.blade.php", "/app/pdf/{$type}/{$templateName}.blade.php");
copy(public_path("/build/img/PDF/{$type}1.png"), public_path("/build/img/PDF/{$templateName}.png"));
copy(resource_path("/static/img/PDF/{$type}1.png"), resource_path("/static/img/PDF/{$templateName}.png"));
if (! PdfTemplateUtils::toCustomTemplateMarkupFile(
Str::replace(
sprintf('app.pdf.%s', $templateType),
sprintf('pdf_templates::%s', $templateType),
Storage::disk('views')->get("/app/pdf/{$templateType}/{$templateType}1.blade.php"),
),
$templateType,
$templateName
)) {
$this->error(sprintf('Unable to create %s template.', ucfirst($templateType)));
$path = resource_path("views/app/pdf/{$type}/{$templateName}.blade.php");
$type = ucfirst($type);
$this->info("{$type} Template created successfully at ".$path);
return self::FAILURE;
}
return 0;
PdfTemplateUtils::toCustomTemplateImageFile(
File::get(resource_path("static/img/PDF/{$templateType}1.png")),
$templateType,
$templateName,
);
if (! PdfTemplateUtils::customTemplateFileExists($templateType, 'partials/table.blade.php')) {
PdfTemplateUtils::toCustomTemplateFile(
Storage::disk('views')->get("/app/pdf/{$templateType}/partials/table.blade.php"),
$templateType,
'partials/table.blade.php'
);
}
$this->info(
sprintf('%s Template created successfully at %s',
ucfirst($templateType),
PdfTemplateUtils::getCustomTemplateFilePath($templateType, sprintf('%s.blade.php', $templateName))
)
);
return self::SUCCESS;
}
}

View File

@@ -6,6 +6,8 @@ use Illuminate\Console\Command;
use Illuminate\Console\ConfirmableTrait;
use Illuminate\Support\Facades\Artisan;
use function Laravel\Prompts\confirm;
class ResetApp extends Command
{
use ConfirmableTrait;
@@ -39,30 +41,49 @@ class ResetApp extends Command
*
* @return mixed
*/
/**
* Execute the console command to reset the application.
*
* This will:
* 1. Enable maintenance mode to prevent access during reset
* 2. Fresh migrate the database with initial seeds
* 3. Seed demo data using DemoSeeder
* 4. Clear all application caches
* 5. Disable maintenance mode
*
* The --force flag can be used to skip confirmation prompt.
*/
public function handle(): void
{
if (! $this->confirmToProceed()) {
return;
if (! $this->option('force')) {
if (! confirm('Are you sure you want to reset the application?')) {
$this->components->error('Reset cancelled');
return;
}
}
$this->info('Running migrate:fresh');
// Enable maintenance mode to prevent access during reset
$this->info('Activating maintenance mode...');
Artisan::call('down');
// Fresh migrate database and run initial seeds
$this->info('Running migrate:fresh');
Artisan::call('migrate:fresh --seed --force');
// Seed demo data
$this->info('Seeding database');
Artisan::call('db:seed', ['--class' => 'DemoSeeder', '--force' => true]);
$path = base_path('.env');
// Clear all application caches
$this->info('Clearing cache...');
Artisan::call('optimize:clear');
if (file_exists($path)) {
file_put_contents($path, str_replace(
'APP_DEBUG=true',
'APP_DEBUG=false',
file_get_contents($path)
));
}
// Disable maintenance mode
$this->info('Deactivating maintenance mode...');
Artisan::call('up');
$this->info('App has been reset successfully');
$this->info('App reset completed successfully!');
}
}

View File

@@ -2,7 +2,6 @@
namespace App\Console\Commands;
use App\Models\Setting;
use App\Space\Updater;
use Illuminate\Console\Command;
@@ -98,7 +97,7 @@ class UpdateCommand extends Command
public function getInstalledVersion()
{
return Setting::getSetting('version');
return preg_replace('~[\r\n]+~', '', File::get(base_path('version.md')));
}
public function getLatestVersionResponse()

16
app/Facades/PDF.php Normal file
View File

@@ -0,0 +1,16 @@
<?php
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
/**
* @method static \Psr\Http\Message\ResponseInterface loadView(string $template)
*/
class PDF extends Facade
{
protected static function getFacadeAccessor()
{
return 'pdf.driver';
}
}

View File

@@ -4,6 +4,7 @@ namespace App\Http\Controllers;
use App\Models\Setting;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\File;
class AppVersionController extends Controller
{
@@ -14,7 +15,7 @@ class AppVersionController extends Controller
*/
public function __invoke(Request $request)
{
$version = Setting::getSetting('version');
$version = preg_replace('~[\r\n]+~', '', File::get(base_path('version.md')));
$channel = Setting::getSetting('updater_channel');
if (is_null($channel)) {

View File

@@ -80,7 +80,7 @@ class BackupsController extends ApiController
$this->authorize('manage backups');
$validated = $request->validate([
'path' => ['required', new PathToZip()],
'path' => ['required', new PathToZip],
]);
$backupDestination = BackupDestination::create(config('filesystems.default'), config('backup.backup.name'));

View File

@@ -18,7 +18,7 @@ class DownloadBackupController extends ApiController
$this->authorize('manage backups');
$validated = $request->validate([
'path' => ['required', new PathToZip()],
'path' => ['required', new PathToZip],
]);
$backupDestination = BackupDestination::create(config('filesystems.default'), config('backup.backup.name'));

View File

@@ -24,7 +24,7 @@ class CloneEstimateController extends Controller
$date = Carbon::now();
$serial = (new SerialNumberFormatter())
$serial = (new SerialNumberFormatter)
->setModel($estimate)
->setCompany($estimate->company_id)
->setCustomer($estimate->customer_id)

View File

@@ -42,7 +42,7 @@ class ConvertEstimateController extends Controller
$due_date = Carbon::now()->addDays($dueDateDays)->format('Y-m-d');
}
$serial = (new SerialNumberFormatter())
$serial = (new SerialNumberFormatter)
->setModel($invoice)
->setCompany($estimate->company_id)
->setCustomer($estimate->customer_id)

View File

@@ -4,6 +4,8 @@ namespace App\Http\Controllers\V1\Admin\Estimate;
use App\Http\Controllers\Controller;
use App\Models\Estimate;
use App\Space\PdfTemplateUtils;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class EstimateTemplatesController extends Controller
@@ -11,13 +13,13 @@ class EstimateTemplatesController extends Controller
/**
* Handle the incoming request.
*
* @return \Illuminate\Http\Response
* @return JsonResponse
*/
public function __invoke(Request $request)
{
$this->authorize('viewAny', Estimate::class);
$estimateTemplates = Estimate::estimateTemplates();
$estimateTemplates = PdfTemplateUtils::getFormattedTemplates('estimate');
return response()->json([
'estimateTemplates' => $estimateTemplates,

View File

@@ -20,7 +20,7 @@ class NextNumberController extends Controller
{
$key = $request->key;
$nextNumber = null;
$serial = (new SerialNumberFormatter())
$serial = (new SerialNumberFormatter)
->setCompany($request->header('company'))
->setCustomer($request->userId);

View File

@@ -41,6 +41,15 @@ class NotesController extends Controller
$note = Note::create($request->getNotesPayload());
if ($note->is_default) {
Note::where('id', '!=', $note->id)
->where('type', $note->type)
->where('is_default', true)
->update([
'is_default' => false,
]);
}
return new NoteResource($note);
}
@@ -68,6 +77,15 @@ class NotesController extends Controller
$note->update($request->getNotesPayload());
if ($note->is_default) {
Note::where('id', '!=', $note->id)
->where('type', $note->type)
->where('is_default', true)
->update([
'is_default' => false,
]);
}
return new NoteResource($note);
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Http\Controllers\V1\Admin\General;
use App\Http\Controllers\Controller;
use App\Space\TimeFormatter;
use Illuminate\Http\Request;
class TimeFormatsController extends Controller
{
/**
* Handle the incoming request.
*
* @return \Illuminate\Http\Response
*/
public function __invoke(Request $request)
{
return response()->json([
'time_formats' => TimeFormatter::get_list(),
]);
}
}

View File

@@ -24,7 +24,7 @@ class CloneInvoiceController extends Controller
$date = Carbon::now();
$serial = (new SerialNumberFormatter())
$serial = (new SerialNumberFormatter)
->setModel($invoice)
->setCompany($invoice->company_id)
->setCustomer($invoice->customer_id)
@@ -46,6 +46,16 @@ class CloneInvoiceController extends Controller
$exchange_rate = $invoice->exchange_rate;
$dateFormat = 'Y-m-d';
$invoiceTimeEnabled = CompanySetting::getSetting(
'invoice_use_time',
$request->header('company')
);
if ($invoiceTimeEnabled === 'YES') {
$dateFormat .= ' H:i';
}
$newInvoice = Invoice::create([
'invoice_date' => $date->format('Y-m-d'),
'due_date' => $due_date,

View File

@@ -4,6 +4,9 @@ namespace App\Http\Controllers\V1\Admin\Invoice;
use App\Http\Controllers\Controller;
use App\Models\Invoice;
use App\Space\PdfTemplateUtils;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class InvoiceTemplatesController extends Controller
@@ -11,13 +14,16 @@ class InvoiceTemplatesController extends Controller
/**
* Handle the incoming request.
*
* @return \Illuminate\Http\Response
*
* @return JsonResponse
*
* @throws AuthorizationException
*/
public function __invoke(Request $request)
{
$this->authorize('viewAny', Invoice::class);
$invoiceTemplates = Invoice::invoiceTemplates();
$invoiceTemplates = PdfTemplateUtils::getFormattedTemplates('invoice');
return response()->json([
'invoiceTemplates' => $invoiceTemplates,

View File

@@ -21,12 +21,11 @@ class InvoicesController extends Controller
{
$this->authorize('viewAny', Invoice::class);
$limit = $request->has('limit') ? $request->limit : 10;
$limit = $request->input('limit', 10);
$invoices = Invoice::whereCompany()
->join('customers', 'customers.id', '=', 'invoices.customer_id')
->applyFilters($request->all())
->select('invoices.*', 'customers.name')
->with('customer')
->latest()
->paginateData($limit);

View File

@@ -58,6 +58,7 @@ class MailConfigurationController extends Controller
'mail_mailgun_secret' => config('services.mailgun.secret'),
'mail_ses_key' => config('services.ses.key'),
'mail_ses_secret' => config('services.ses.secret'),
'mail_ses_region' => config('services.ses.region'),
];
return response()->json($MailData);

View File

@@ -0,0 +1,54 @@
<?php
namespace App\Http\Controllers\V1\Admin\Settings;
use App\Http\Controllers\Controller;
use App\Http\Requests\PDFConfigurationRequest;
use App\Space\EnvironmentManager;
class PDFConfigurationController extends Controller
{
/**
* @var EnvironmentManager
*/
protected $environmentManager;
public function __construct(EnvironmentManager $environmentManager)
{
$this->environmentManager = $environmentManager;
}
public function getDrivers()
{
$this->authorize('manage pdf config');
$drivers = [
'dompdf',
'gotenberg',
];
return response()->json($drivers);
}
public function getEnvironment()
{
$this->authorize('manage pdf config');
$config = [
'pdf_driver' => config('pdf.driver'),
'gotenberg_host' => config('pdf.gotenberg.host'),
'gotenberg_margins' => config('pdf.gotenberg.margins'),
'gotenberg_papersize' => config('pdf.gotenberg.papersize'),
];
return response()->json($config);
}
public function saveEnvironment(PDFConfigurationRequest $request)
{
$this->authorize('manage pdf config');
$results = $this->environmentManager->savePDFVariables($request);
return response()->json($results);
}
}

View File

@@ -3,9 +3,9 @@
namespace App\Http\Controllers\V1\Admin\Update;
use App\Http\Controllers\Controller;
use App\Models\Setting;
use App\Space\Updater;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\File;
class CheckVersionController extends Controller
{
@@ -26,7 +26,8 @@ class CheckVersionController extends Controller
set_time_limit(600); // 10 minutes
$channel = $request->get('channel', 'stable');
$response = Updater::checkForUpdate(Setting::getSetting('version'), $channel);
$version = preg_replace('~[\r\n]+~', '', File::get(base_path('version.md')));
$response = Updater::checkForUpdate($version, $channel);
return response()->json($response);
}

View File

@@ -13,7 +13,7 @@ class AppDomainController extends Controller
{
Artisan::call('optimize:clear');
$environmentManager = new EnvironmentManager();
$environmentManager = new EnvironmentManager;
$results = $environmentManager->saveDomainVariables($request);

View File

@@ -29,7 +29,10 @@ class DatabaseConfigurationController extends Controller
$results = $this->environmentManager->saveDatabaseVariables($request);
if (array_key_exists('success', $results)) {
Artisan::call('key:generate --force');
// Automatically regenerating the key is disabled to prevent complications in the wizard process.
// This can cause issues with the CSRF token, resulting in "Token Mismatch" or "Invalid CSRF Token" errors.
// It is recommended that the user manually generates the key before running the wizard to ensure application security and stability.
// Artisan::call('key:generate --force');
Artisan::call('optimize:clear');
Artisan::call('config:clear');
Artisan::call('cache:clear');

View File

@@ -38,6 +38,15 @@ class CompanySettingRequest extends FormRequest
'carbon_date_format' => [
'required',
],
'moment_time_format' => [
'required',
],
'carbon_time_format' => [
'required',
],
'invoice_use_time' => [
'required',
],
];
}
}

View File

@@ -49,8 +49,10 @@ class CustomerRequest extends FormRequest
'prefix' => [
'nullable',
],
'tax_id' => [
'nullable',
],
'enable_portal' => [
'boolean',
],
'currency_id' => [
@@ -133,6 +135,7 @@ class CustomerRequest extends FormRequest
'password',
'phone',
'prefix',
'tax_id',
'company_name',
'contact_name',
'website',

View File

@@ -49,11 +49,10 @@ class InvoicesRequest extends FormRequest
'required',
],
'sub_total' => [
'integer',
'numeric',
'required',
],
'total' => [
'integer',
'numeric',
'max:999999999999',
'required',
@@ -83,7 +82,7 @@ class InvoicesRequest extends FormRequest
'required',
],
'items.*.price' => [
'integer',
'numeric',
'required',
],
];
@@ -112,7 +111,7 @@ class InvoicesRequest extends FormRequest
return $rules;
}
public function getInvoicePayload()
public function getInvoicePayload(): array
{
$company_currency = CompanySetting::getSetting('currency', $this->header('company'));
$current_currency = $this->currency_id;
@@ -128,6 +127,8 @@ class InvoicesRequest extends FormRequest
'tax_per_item' => CompanySetting::getSetting('tax_per_item', $this->header('company')) ?? 'NO ',
'discount_per_item' => CompanySetting::getSetting('discount_per_item', $this->header('company')) ?? 'NO',
'due_amount' => $this->total,
'sent' => (bool) $this->sent ?? false,
'viewed' => (bool) $this->viewed ?? false,
'exchange_rate' => $exchange_rate,
'base_total' => $this->total * $exchange_rate,
'base_discount_val' => $this->discount_val * $exchange_rate,

View File

@@ -33,6 +33,9 @@ class NotesRequest extends FormRequest
'notes' => [
'required',
],
'is_default' => [
'required',
],
];
if ($this->isMethod('PUT')) {

View File

@@ -0,0 +1,57 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class PDFConfigurationRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
switch ($this->get('pdf_driver')) {
case 'dompdf':
return [
'pdf_driver' => [
'required',
'string',
],
];
break;
case 'gotenberg':
return [
'pdf_driver' => [
'required',
'string',
],
'gotenberg_host' => [
'required',
'url',
],
'gotenberg_papersize' => [
function ($attribute, $value, $fail) {
($attribute); // unused
$reg = "/^\d+(pt|px|pc|mm|cm|in) \d+(pt|px|pc|mm|cm|in)$/";
if (! preg_match($reg, $value)) {
$fail('Invalid papersize, must be in format "210mm 297mm". Accepts: pt,px,pc,mm,cm,in');
}
},
],
];
break;
}
throw new \InvalidArgumentException('Invalid PDFDriver requested');
}
}

View File

@@ -28,8 +28,17 @@ class TaxTypeRequest extends FormRequest
->where('type', TaxType::TYPE_GENERAL)
->where('company_id', $this->header('company')),
],
'percent' => [
'calculation_type' => [
'required',
Rule::in(['percentage', 'fixed']),
],
'percent' => [
'nullable',
'numeric',
],
'fixed_amount' => [
'nullable',
'numeric',
],
'description' => [
'nullable',

View File

@@ -3,6 +3,7 @@
namespace App\Http\Resources;
use App\Models\CompanySetting;
use Carbon\Carbon;
use Illuminate\Http\Resources\Json\JsonResource;
class CustomFieldValueResource extends JsonResource
@@ -48,11 +49,11 @@ class CustomFieldValueResource extends JsonResource
}
if ($key == 'date_time_answer') {
return $answer->format('Y-m-d H:i');
return Carbon::parse($answer)->format('Y-m-d H:i');
}
if ($key == 'date_answer') {
return $answer->format(CompanySetting::getSetting('carbon_date_format', $this->company_id));
return Carbon::parse($answer)->format(CompanySetting::getSetting('carbon_date_format', $this->company_id));
}
return $answer;

View File

@@ -30,6 +30,7 @@ class CustomerResource extends JsonResource
'formatted_created_at' => $this->formattedCreatedAt,
'avatar' => $this->avatar,
'prefix' => $this->prefix,
'tax_id' => $this->tax_id,
'billing' => $this->when($this->billingAddress()->exists(), function () {
return new AddressResource($this->billingAddress);
}),

View File

@@ -35,6 +35,7 @@ class CustomerResource extends JsonResource
'due_amount' => $this->due_amount,
'base_due_amount' => $this->base_due_amount,
'prefix' => $this->prefix,
'tax_id' => $this->tax_id,
'billing' => $this->when($this->billingAddress()->exists(), function () {
return new AddressResource($this->billingAddress);
}),

View File

@@ -21,6 +21,7 @@ class EstimateResource extends JsonResource
'status' => $this->status,
'reference_number' => $this->reference_number,
'tax_per_item' => $this->tax_per_item,
'tax_included' => $this->tax_included,
'discount_per_item' => $this->discount_per_item,
'notes' => $this->getNotes(),
'discount' => $this->discount,

View File

@@ -22,6 +22,7 @@ class InvoiceResource extends JsonResource
'status' => $this->status,
'paid_status' => $this->paid_status,
'tax_per_item' => $this->tax_per_item,
'tax_included' => $this->tax_included,
'discount_per_item' => $this->discount_per_item,
'notes' => $this->notes,
'discount_type' => $this->discount_type,

View File

@@ -18,6 +18,7 @@ class NoteResource extends JsonResource
'type' => $this->type,
'name' => $this->name,
'notes' => $this->notes,
'is_default' => $this->is_default,
'company' => $this->when($this->company()->exists(), function () {
return new CompanyResource($this->company);
}),

View File

@@ -32,6 +32,7 @@ class RecurringInvoiceResource extends JsonResource
'limit_date' => $this->limit_date,
'exchange_rate' => $this->exchange_rate,
'tax_per_item' => $this->tax_per_item,
'tax_included' => $this->tax_included,
'discount_per_item' => $this->discount_per_item,
'notes' => $this->notes,
'discount_type' => $this->discount_type,

View File

@@ -25,6 +25,8 @@ class TaxResource extends JsonResource
'name' => $this->name,
'amount' => $this->amount,
'percent' => $this->percent,
'calculation_type' => $this->calculation_type,
'fixed_amount' => $this->fixed_amount,
'compound_tax' => $this->compound_tax,
'base_amount' => $this->base_amount,
'currency_id' => $this->currency_id,

View File

@@ -17,6 +17,8 @@ class TaxTypeResource extends JsonResource
'id' => $this->id,
'name' => $this->name,
'percent' => $this->percent,
'fixed_amount' => $this->fixed_amount,
'calculation_type' => $this->calculation_type,
'type' => $this->type,
'compound_tax' => $this->compound_tax,
'collective_tax' => $this->collective_tax,

View File

@@ -8,6 +8,7 @@ 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
@@ -41,7 +42,8 @@ class CreateBackupJob implements ShouldQueue
config(['backup.backup.destination.disks' => [$prefix.$fileDisk->driver]]);
$backupJob = BackupJobFactory::createFromArray(config('backup'));
$config = Config::fromArray(config('backup'));
$backupJob = BackupJobFactory::createFromConfig($config);
if (! defined('SIGINT')) {
$backupJob->disableSignals();
}

View File

@@ -227,6 +227,9 @@ class Company extends Model implements HasMedia
'fiscal_year' => '1-12',
'carbon_date_format' => 'Y/m/d',
'moment_date_format' => 'YYYY/MM/DD',
'carbon_time_format' => 'H:i',
'moment_time_format' => 'HH:mm',
'invoice_use_time' => 'NO',
'notification_email' => 'noreply@invoiceshelf.com',
'notify_invoice_viewed' => 'NO',
'notify_estimate_viewed' => 'NO',

View File

@@ -20,10 +20,10 @@ class EmailLog extends Model
public function isExpired()
{
$linkexpiryDays = CompanySetting::getSetting('link_expiry_days', $this->mailable()->get()->toArray()[0]['company_id']);
$linkExpiryDays = (int) CompanySetting::getSetting('link_expiry_days', $this->mailable()->get()->toArray()[0]['company_id']);
$checkExpiryLinks = CompanySetting::getSetting('automatically_expire_public_links', $this->mailable()->get()->toArray()[0]['company_id']);
$expiryDate = $this->created_at->addDays($linkexpiryDays);
$expiryDate = $this->created_at->addDays($linkExpiryDays);
if ($checkExpiryLinks == 'YES' && Carbon::now()->format('Y-m-d') > $expiryDate->format('Y-m-d')) {
return true;

View File

@@ -3,19 +3,18 @@
namespace App\Models;
use App;
use App\Facades\PDF;
use App\Mail\SendEstimateMail;
use App\Services\SerialNumberFormatter;
use App\Space\PdfTemplateUtils;
use App\Traits\GeneratesPdfTrait;
use App\Traits\HasCustomFieldsTrait;
use Barryvdh\DomPDF\Facade\Pdf as PDF;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Vite;
use Illuminate\Support\Str;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
@@ -227,7 +226,7 @@ class Estimate extends Model implements HasMedia
$estimate = self::create($data);
$estimate->unique_hash = Hashids::connection(Estimate::class)->encode($estimate->id);
$serial = (new SerialNumberFormatter())
$serial = (new SerialNumberFormatter)
->setModel($estimate)
->setCompany($estimate->company_id)
->setCustomer($estimate->customer_id)
@@ -262,7 +261,7 @@ class Estimate extends Model implements HasMedia
{
$data = $request->getEstimatePayload();
$serial = (new SerialNumberFormatter())
$serial = (new SerialNumberFormatter)
->setModel($this)
->setCompany($this->company_id)
->setCustomer($request->customer_id)
@@ -424,11 +423,14 @@ class Estimate extends Model implements HasMedia
'taxes' => $taxes,
]);
$template = PdfTemplateUtils::findFormattedTemplate('estimate', $estimateTemplate, '');
$templatePath = $template['custom'] ? sprintf('pdf_templates::estimate.%s', $estimateTemplate) : sprintf('app.pdf.estimate.%s', $estimateTemplate);
if (request()->has('preview')) {
return view('app.pdf.estimate.'.$estimateTemplate);
return view($templatePath);
}
return PDF::loadView('app.pdf.estimate.'.$estimateTemplate);
return PDF::loadView($templatePath);
}
public function getCompanyAddress()
@@ -499,27 +501,13 @@ class Estimate extends Model implements HasMedia
];
}
public static function estimateTemplates()
{
$templates = Storage::disk('views')->files('/app/pdf/estimate');
$estimateTemplates = [];
foreach ($templates as $key => $template) {
$templateName = Str::before(basename($template), '.blade.php');
$estimateTemplates[$key]['name'] = $templateName;
$estimateTemplates[$key]['path'] = Vite::asset('resources/static/img/PDF/'.$templateName.'.png');
}
return $estimateTemplates;
}
public function getInvoiceTemplateName()
{
$templateName = Str::replace('estimate', 'invoice', $this->template_name);
$name = [];
foreach (Invoice::invoiceTemplates() as $template) {
foreach (PdfTemplateUtils::getFormattedTemplates('invoice') as $template) {
$name[] = $template['name'];
}

View File

@@ -3,20 +3,18 @@
namespace App\Models;
use App;
use App\Facades\PDF;
use App\Mail\SendInvoiceMail;
use App\Services\SerialNumberFormatter;
use App\Space\PdfTemplateUtils;
use App\Traits\GeneratesPdfTrait;
use App\Traits\HasCustomFieldsTrait;
use Barryvdh\DomPDF\Facade\Pdf as PDF;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Vite;
use Illuminate\Support\Str;
use Nwidart\Modules\Facades\Module;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
@@ -195,6 +193,12 @@ class Invoice extends Model implements HasMedia
public function getFormattedInvoiceDateAttribute($value)
{
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
$timeFormat = CompanySetting::getSetting('carbon_time_format', $this->company_id);
$invoiceTimeEnabled = CompanySetting::getSetting('invoice_use_time', $this->company_id);
if ($invoiceTimeEnabled === 'YES') {
$dateFormat .= ' '.$timeFormat;
}
return Carbon::parse($this->invoice_date)->translatedFormat($dateFormat);
}
@@ -248,53 +252,34 @@ class Invoice extends Model implements HasMedia
public function scopeApplyFilters($query, array $filters)
{
$filters = collect($filters);
$filters = collect($filters)->filter()->all();
if ($filters->get('search')) {
$query->whereSearch($filters->get('search'));
}
if ($filters->get('status')) {
if (
$filters->get('status') == self::STATUS_UNPAID ||
$filters->get('status') == self::STATUS_PARTIALLY_PAID ||
$filters->get('status') == self::STATUS_PAID
) {
$query->wherePaidStatus($filters->get('status'));
} elseif ($filters->get('status') == 'DUE') {
$query->whereDueStatus($filters->get('status'));
} else {
$query->whereStatus($filters->get('status'));
}
}
if ($filters->get('paid_status')) {
$query->wherePaidStatus($filters->get('status'));
}
if ($filters->get('invoice_id')) {
$query->whereInvoice($filters->get('invoice_id'));
}
if ($filters->get('invoice_number')) {
$query->whereInvoiceNumber($filters->get('invoice_number'));
}
if ($filters->get('from_date') && $filters->get('to_date')) {
$start = Carbon::createFromFormat('Y-m-d', $filters->get('from_date'));
$end = Carbon::createFromFormat('Y-m-d', $filters->get('to_date'));
return $query->when($filters['search'] ?? null, function ($query, $search) {
$query->whereSearch($search);
})->when($filters['status'] ?? null, function ($query, $status) {
match ($status) {
self::STATUS_UNPAID, self::STATUS_PARTIALLY_PAID, self::STATUS_PAID => $query->wherePaidStatus($status),
'DUE' => $query->whereDueStatus($status),
default => $query->whereStatus($status),
};
})->when($filters['paid_status'] ?? null, function ($query, $paidStatus) {
$query->wherePaidStatus($paidStatus);
})->when($filters['invoice_id'] ?? null, function ($query, $invoiceId) {
$query->whereInvoice($invoiceId);
})->when($filters['invoice_number'] ?? null, function ($query, $invoiceNumber) {
$query->whereInvoiceNumber($invoiceNumber);
})->when(($filters['from_date'] ?? null) && ($filters['to_date'] ?? null), function ($query) use ($filters) {
$start = Carbon::parse($filters['from_date']);
$end = Carbon::parse($filters['to_date']);
$query->invoicesBetween($start, $end);
}
if ($filters->get('customer_id')) {
$query->whereCustomer($filters->get('customer_id'));
}
if ($filters->get('orderByField') || $filters->get('orderBy')) {
$field = $filters->get('orderByField') ? $filters->get('orderByField') : 'sequence_number';
$orderBy = $filters->get('orderBy') ? $filters->get('orderBy') : 'desc';
$query->whereOrder($field, $orderBy);
}
})->when($filters['customer_id'] ?? null, function ($query, $customerId) {
$query->where('customer_id', $customerId);
})->when($filters['orderByField'] ?? null, function ($query, $orderByField) use ($filters) {
$orderBy = $filters['orderBy'] ?? 'desc';
$query->orderBy($orderByField, $orderBy);
}, function ($query) {
$query->orderBy('sequence_number', 'desc');
});
}
public function scopeWhereInvoice($query, $invoice_id)
@@ -336,7 +321,7 @@ class Invoice extends Model implements HasMedia
$invoice = Invoice::create($data);
$serial = (new SerialNumberFormatter())
$serial = (new SerialNumberFormatter)
->setModel($invoice)
->setCompany($invoice->company_id)
->setCustomer($invoice->customer_id)
@@ -377,7 +362,7 @@ class Invoice extends Model implements HasMedia
public function updateInvoice($request)
{
$serial = (new SerialNumberFormatter())
$serial = (new SerialNumberFormatter)
->setModel($this)
->setCompany($this->company_id)
->setCustomer($request->customer_id)
@@ -393,7 +378,7 @@ class Invoice extends Model implements HasMedia
return 'customer_cannot_be_changed_after_payment_is_added';
}
if ($request->total < $total_paid_amount) {
if ($request->total >= 0 && $request->total < $total_paid_amount) {
return 'total_invoice_amount_must_be_more_than_paid_amount';
}
@@ -536,6 +521,7 @@ class Invoice extends Model implements HasMedia
public static function createTaxes($invoice, $taxes)
{
$exchange_rate = $invoice->exchange_rate;
foreach ($taxes as $tax) {
@@ -595,11 +581,14 @@ class Invoice extends Model implements HasMedia
'taxes' => $taxes,
]);
$template = PdfTemplateUtils::findFormattedTemplate('invoice', $invoiceTemplate, '');
$templatePath = $template['custom'] ? sprintf('pdf_templates::invoice.%s', $invoiceTemplate) : sprintf('app.pdf.invoice.%s', $invoiceTemplate);
if (request()->has('preview')) {
return view('app.pdf.invoice.'.$invoiceTemplate);
return view($templatePath);
}
return PDF::loadView('app.pdf.invoice.'.$invoiceTemplate);
return PDF::loadView($templatePath);
}
public function getEmailAttachmentSetting()
@@ -670,20 +659,6 @@ class Invoice extends Model implements HasMedia
];
}
public static function invoiceTemplates()
{
$templates = Storage::disk('views')->files('/app/pdf/invoice');
$invoiceTemplates = [];
foreach ($templates as $key => $template) {
$templateName = Str::before(basename($template), '.blade.php');
$invoiceTemplates[$key]['name'] = $templateName;
$invoiceTemplates[$key]['path'] = Vite::asset('resources/static/img/PDF/'.$templateName.'.png');
}
return $invoiceTemplates;
}
public function addInvoicePayment($amount)
{
$this->due_amount += $amount;

View File

@@ -163,7 +163,7 @@ class Payment extends Model implements HasMedia
$payment = Payment::create($data);
$payment->unique_hash = Hashids::connection(Payment::class)->encode($payment->id);
$serial = (new SerialNumberFormatter())
$serial = (new SerialNumberFormatter)
->setModel($payment)
->setCompany($payment->company_id)
->setCustomer($payment->customer_id)
@@ -215,7 +215,7 @@ class Payment extends Model implements HasMedia
$invoice->subtractInvoicePayment($request->amount);
}
$serial = (new SerialNumberFormatter())
$serial = (new SerialNumberFormatter)
->setModel($this)
->setCompany($this->company_id)
->setCustomer($request->customer_id)
@@ -452,14 +452,14 @@ class Payment extends Model implements HasMedia
{
$invoice = Invoice::find($transaction->invoice_id);
$serial = (new SerialNumberFormatter())
->setModel(new Payment())
$serial = (new SerialNumberFormatter)
->setModel(new Payment)
->setCompany($invoice->company_id)
->setCustomer($invoice->customer_id)
->setNextNumbers();
$data['payment_number'] = $serial->getNextNumber();
$data['payment_date'] = Carbon::now()->format('y-m-d');
$data['payment_date'] = Carbon::now();
$data['amount'] = $invoice->total;
$data['invoice_id'] = $invoice->id;
$data['payment_method_id'] = request()->payment_method_id;

View File

@@ -307,9 +307,9 @@ class RecurringInvoice extends Model
public function createInvoice()
{
//get invoice_number
$serial = (new SerialNumberFormatter())
->setModel(new Invoice())
// get invoice_number
$serial = (new SerialNumberFormatter)
->setModel(new Invoice)
->setCompany($this->company_id)
->setCustomer($this->customer_id)
->setNextNumbers();
@@ -375,7 +375,7 @@ class RecurringInvoice extends Model
$invoice->addCustomFields($customField);
}
//send automatically
// send automatically
if ($this->send_automatically == true) {
$data = [
'body' => CompanySetting::getSetting('invoice_mail_body', $this->company_id),

View File

@@ -22,7 +22,7 @@ class Setting extends Model
return;
}
$set = new Setting();
$set = new Setting;
$set->option = $key;
$set->value = $setting;
$set->save();

View File

@@ -21,6 +21,7 @@ class Tax extends Model
return [
'amount' => 'integer',
'percent' => 'float',
'fixed_amount' => 'integer',
];
}

View File

@@ -19,6 +19,7 @@ class TaxType extends Model
{
return [
'percent' => 'float',
'fixed_amount' => 'integer',
'compound_tax' => 'boolean',
];
}

View File

@@ -65,10 +65,10 @@ class Transaction extends Model
public function isExpired()
{
$linkexpiryDays = CompanySetting::getSetting('link_expiry_days', $this->company_id);
$linkExpiryDays = (int) CompanySetting::getSetting('link_expiry_days', $this->company_id);
$checkExpiryLinks = CompanySetting::getSetting('automatically_expire_public_links', $this->company_id);
$expiryDate = $this->updated_at->addDays($linkexpiryDays);
$expiryDate = $this->updated_at->addDays($linkExpiryDays);
if ($checkExpiryLinks == 'YES' && $this->status == self::SUCCESS && Carbon::now()->format('Y-m-d') > $expiryDate->format('Y-m-d')) {
return true;

View File

@@ -40,7 +40,7 @@ class CustomerMailResetPasswordNotification extends ResetPassword
{
$link = url("/{$notifiable->company->slug}/customer/reset/password/".$this->token);
return (new MailMessage())
return (new MailMessage)
->subject('Reset Password Notification')
->line('Hello! You are receiving this email because we received a password reset request for your account.')
->action('Reset Password', $link)

View File

@@ -40,7 +40,7 @@ class MailResetPasswordNotification extends ResetPassword
{
$link = url('/reset-password/'.$this->token);
return (new MailMessage())
return (new MailMessage)
->subject('Reset Password Notification')
->line('Hello! You are receiving this email because we received a password reset request for your account.')
->action('Reset Password', $link)

View File

@@ -46,6 +46,15 @@ class SettingsPolicy
return false;
}
public function managePDFConfig(User $user)
{
if ($user->isOwner()) {
return true;
}
return false;
}
public function manageSettings(User $user)
{
if ($user->isOwner()) {

View File

@@ -25,6 +25,7 @@ use Illuminate\Support\Facades\Broadcast;
use Illuminate\Support\ServiceProvider;
use Silber\Bouncer\Database\Models as BouncerModels;
use Silber\Bouncer\Database\Role;
use View;
class AppServiceProvider extends ServiceProvider
{
@@ -58,8 +59,16 @@ class AppServiceProvider extends ServiceProvider
Gate::policy(Role::class, RolePolicy::class);
View::addNamespace('pdf_templates', storage_path('app/templates/pdf'));
$this->bootAuth();
$this->bootBroadcast();
// In demo mode, prevent all outgoing emails and notifications
if (config('app.env') === 'demo') {
\Illuminate\Support\Facades\Mail::fake();
\Illuminate\Support\Facades\Notification::fake();
}
}
/**
@@ -72,14 +81,14 @@ class AppServiceProvider extends ServiceProvider
public function addMenus()
{
//main menu
// main menu
\Menu::make('main_menu', function ($menu) {
foreach (config('invoiceshelf.main_menu') as $data) {
$this->generateMenu($menu, $data);
}
});
//setting menu
// setting menu
\Menu::make('setting_menu', function ($menu) {
foreach (config('invoiceshelf.setting_menu') as $data) {
$this->generateMenu($menu, $data);
@@ -118,6 +127,7 @@ class AppServiceProvider extends ServiceProvider
Gate::define('manage backups', [SettingsPolicy::class, 'manageBackups']);
Gate::define('manage file disk', [SettingsPolicy::class, 'manageFileDisk']);
Gate::define('manage email config', [SettingsPolicy::class, 'manageEmailConfig']);
Gate::define('manage pdf config', [SettingsPolicy::class, 'managePDFConfig']);
Gate::define('manage notes', [NotePolicy::class, 'manageNotes']);
Gate::define('view notes', [NotePolicy::class, 'viewNotes']);

View File

@@ -0,0 +1,13 @@
<?php
namespace App\Providers;
use App\Services\PDFService;
use Illuminate\Support\ServiceProvider;
class PDFServiceProvider extends ServiceProvider
{
public $bindings = [
'pdf.driver' => PDFService::class,
];
}

View File

@@ -2,6 +2,7 @@
namespace App\Rules\Backup;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
class BackupDisk implements ValidationRule
@@ -18,8 +19,6 @@ class BackupDisk implements ValidationRule
/**
* Run the validation rule.
*
* @param \Closure $fail
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{

View File

@@ -36,7 +36,7 @@ class Module
{
static::$scripts[$name] = $path;
return new static();
return new static;
}
/**
@@ -50,7 +50,7 @@ class Module
{
static::$styles[$name] = $path;
return new static();
return new static;
}
/**

View File

@@ -0,0 +1,59 @@
<?php
namespace App\Services\PDFDrivers;
use Gotenberg\Gotenberg;
use Gotenberg\Stream;
use Illuminate\Http\Response;
class GotenbergPDFResponse
{
/** @var \Psr\Http\Message\ResponseInterface */
protected $response;
public function __construct($stream)
{
$this->response = $stream;
}
public function stream(string $filename = 'document.pdf'): Response
{
$output = $this->response->getBody();
return new Response($output, 200, [
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'inline; filename="'.$filename.'"',
]);
}
public function output(): string
{
return $this->response->getBody()->getContents();
}
}
class GotenbergPDFDriver
{
public function loadView(string $viewname): GotenbergPDFResponse
{
$papersize = explode(' ', config('pdf.gotenberg.papersize'));
if (count($papersize) != 2) {
throw new \InvalidArgumentException('Invalid Gotenberg Papersize specified');
}
$host = config('pdf.gotenberg.host');
$request = Gotenberg::chromium($host)
->pdf()
->margins(0, 0, 0, 0) // Margins can be set using CSS
->paperSize($papersize[0], $papersize[1])
->html(
Stream::string(
'document.html',
view($viewname)->render(),
)
);
$result = Gotenberg::send($request);
return new GotenbergPDFResponse($result);
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace App\Services;
/*
* Two options:
* - Barryvdh\DomPDF\Facade\Pdf
* - Gotenberg
*/
use App;
use App\Services\PDFDrivers\GotenbergPDFDriver;
use Illuminate\Http\Response;
interface ResponseStream
{
public function stream(string $filename): Response;
public function output(): string;
}
interface PDFDriver
{
public function loadView(string $template): ResponseStream;
}
class PDFDriverFactory
{
public static function create(string $driver)
{
return match ($driver) {
'dompdf' => App::make('dompdf.wrapper'),
'gotenberg' => new GotenbergPDFDriver,
default => throw new \InvalidArgumentException('Invalid PDFDriver requested')
};
}
}
class PDFService
{
public static function loadView(string $template)
{
$driver = config('pdf.driver');
return PDFDriverFactory::create($driver)->loadView($template);
}
}

View File

@@ -6,6 +6,7 @@ use App\Http\Requests\DatabaseEnvironmentRequest;
use App\Http\Requests\DiskEnvironmentRequest;
use App\Http\Requests\DomainEnvironmentRequest;
use App\Http\Requests\MailEnvironmentRequest;
use App\Http\Requests\PDFConfigurationRequest;
use Exception;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;
@@ -94,7 +95,6 @@ class EnvironmentManager
}
return $str;
}
/**
@@ -104,14 +104,22 @@ class EnvironmentManager
*/
public function saveDatabaseVariables(DatabaseEnvironmentRequest $request)
{
$appUrl = $request->get('app_url');
if ($appUrl !== config('app.url')) {
config(['app.url' => $appUrl]);
}
[$sanctumDomain, $sessionDomain] = $this->getDomains(
$request->getHttpHost()
);
$dbEnv = [
'APP_URL' => $request->get('app_url'),
'APP_URL' => $appUrl,
'APP_LOCALE' => $request->get('app_locale'),
'DB_CONNECTION' => $request->get('database_connection'),
'SANCTUM_STATEFUL_DOMAINS' => $request->get('app_domain'),
'SESSION_DOMAIN' => explode(':', $request->get('app_domain'))[0],
'SESSION_DOMAIN' => $sessionDomain,
];
if ($sanctumDomain !== null) {
$dbEnv['SANCTUM_STATEFUL_DOMAINS'] = $sanctumDomain;
}
if ($dbEnv['DB_CONNECTION'] != 'sqlite') {
if ($request->has('database_username') && $request->has('database_password')) {
$dbEnv['DB_HOST'] = $request->get('database_hostname');
@@ -225,7 +233,6 @@ class EnvironmentManager
try {
$this->updateEnv($mailEnv);
} catch (Exception $e) {
return [
'error' => 'mail_variables_save_error',
@@ -237,6 +244,60 @@ class EnvironmentManager
];
}
/**
* Save the pdf generation content to the .env file.
*
* @return array
*/
public function savePDFVariables(PDFConfigurationRequest $request)
{
$pdfEnv = $this->getPDFConfiguration($request);
try {
$this->updateEnv($pdfEnv);
} catch (Exception $e) {
return [
'error' => 'pdf_variables_save_error',
];
}
return [
'success' => 'pdf_variables_save_successfully',
];
}
/**
* Returns the pdf configuration
*
* @param PDFConfigurationRequest $request
* @return array
*/
private function getPDFConfiguration($request)
{
$pdfEnv = [];
$driver = $request->get('pdf_driver');
switch ($driver) {
case 'dompdf':
$pdfEnv = [
'PDF_DRIVER' => $request->get('pdf_driver'),
];
break;
case 'gotenberg':
$pdfEnv = [
'PDF_DRIVER' => $request->get('pdf_driver'),
'GOTENBERG_HOST' => $request->get('gotenberg_host'),
'GOTENBERG_MARGINS' => $request->get('gotenberg_margins'),
'GOTENBERG_PAPERSIZE' => $request->get('gotenberg_papersize'),
];
break;
}
return $pdfEnv;
}
/**
* Returns the mail configuration
*
@@ -296,6 +357,7 @@ class EnvironmentManager
'MAIL_FROM_NAME' => $request->get('from_name'),
'SES_KEY' => $request->get('mail_ses_key'),
'SES_SECRET' => $request->get('mail_ses_secret'),
'SES_REGION' => $request->get('mail_ses_region'),
];
break;
@@ -315,7 +377,6 @@ class EnvironmentManager
];
break;
}
return $mailEnv;
@@ -333,7 +394,6 @@ class EnvironmentManager
try {
$this->updateEnv($diskEnv);
} catch (Exception $e) {
return [
'error' => 'disk_variables_save_error',
@@ -410,10 +470,16 @@ class EnvironmentManager
public function saveDomainVariables(DomainEnvironmentRequest $request)
{
try {
$this->updateEnv([
'SANCTUM_STATEFUL_DOMAINS' => $request->get('app_domain'),
'SESSION_DOMAIN' => explode(':', $request->get('app_domain'))[0],
]);
[$sanctumDomain, $sessionDomain] = $this->getDomains(
$request->get('app_domain')
);
$domainEnv = [
'SESSION_DOMAIN' => $sessionDomain,
];
if ($sanctumDomain !== null) {
$domainEnv['SANCTUM_STATEFUL_DOMAINS'] = $sanctumDomain;
}
$this->updateEnv($domainEnv);
} catch (Exception $e) {
return [
'error' => 'domain_verification_failed',
@@ -449,9 +515,29 @@ class EnvironmentManager
}
$formatted .= $current.$this->delimiter;
$previous = $current;
}
file_put_contents($this->envPath, trim($formatted));
}
private function getDomains(string $requestDomain): array
{
$appUrl = config('app.url');
$port = parse_url($appUrl, PHP_URL_PORT);
$currentDomain = parse_url($appUrl, PHP_URL_HOST).(
$port ? ':'.$port : ''
);
$requestHost = parse_url($requestDomain, PHP_URL_HOST) ?: $requestDomain;
$isSame = $currentDomain === $requestDomain;
return [
$isSame && env('SANCTUM_STATEFUL_DOMAINS', false) === false ?
null : $requestDomain,
$isSame && env('SESSION_DOMAIN', false) === null ?
null : $requestHost,
];
}
}

View File

@@ -153,7 +153,7 @@ class ModuleInstaller
File::makeDirectory($temp_extract_dir);
}
// Unzip the file
$zip = new ZipArchive();
$zip = new ZipArchive;
if ($zip->open($zip_file_path)) {
$zip->extractTo($temp_extract_dir);

View File

@@ -0,0 +1,140 @@
<?php
namespace App\Space;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
class PdfTemplateUtils
{
/**
* Find the formatted template
*
* @param string $imageFormat
* @return array|null
*/
public static function findFormattedTemplate($templateType, $templateName, $imageFormat = 'base64')
{
foreach (array_reverse(self::getFormattedTemplates($templateType, $imageFormat)) as $formattedTemplate) {
if ($formattedTemplate['name'] === $templateName) {
return $formattedTemplate;
}
}
return null;
}
/**
* Return the available formatted template paths
*
* @param string $imageFormat
* @return array|array[]
*/
public static function getFormattedTemplates($templateType, $imageFormat = 'base64')
{
$files_native = array_map(function ($file) {
return [
'path' => $file,
'custom' => false,
];
}, Storage::disk('views')->files(sprintf('/app/pdf/%s', $templateType)));
$files_custom = array_map(function ($file) {
return [
'path' => $file,
'custom' => true,
];
}, Storage::disk('pdf_templates')->files(sprintf('/%s', $templateType)));
$files = array_merge($files_native, $files_custom);
$files = array_filter($files, function ($file) {
return Str::endsWith($file['path'], '.blade.php');
});
return array_map(function ($file) use ($templateType, $imageFormat) {
$templateName = Str::before(basename($file['path']), '.blade.php');
if ($file['custom']) {
$imagePath = self::getCustomTemplateFilePath($templateType, sprintf('%s.png', $templateName));
$isCustomTemplate = true;
} else {
$imagePath = resource_path('static/img/PDF/'.$templateName.'.png');
$isCustomTemplate = false;
}
if (empty($imageFormat)) {
$imageValue = '';
} elseif ($imageFormat == 'path') {
$imageValue = $imagePath;
} else {
$imageValue = File::exists($imagePath) ? ImageUtils::toBase64Src($imagePath) : '';
}
return [
'name' => $templateName,
'path' => $imageValue,
'custom' => $isCustomTemplate,
];
}, $files);
}
/**
* Returns custom template path
*
* @param string $fileName
*/
public static function getCustomTemplateFilePath($templateType, $fileName = ''): string
{
$path = ! empty($fileName) ? sprintf('/%s/%s', $templateType, $fileName) : sprintf('/%s/', $templateType);
return Storage::disk('pdf_templates')->path($path);
}
/**
* Check if custom template exists.
*
* @param $templateName
* @return string
*/
public static function customTemplateFileExists($templateType, $fileName)
{
return Storage::disk('pdf_templates')->exists(sprintf('/%s/%s', $templateType, $fileName));
}
/**
* Save template markup file
*
* @return bool|string
*/
public static function toCustomTemplateMarkupFile($contents, $templateType, $templateName)
{
return self::toCustomTemplateFile($contents, $templateType, $templateName.'.blade.php');
}
/**
* Save template image file
*
*
* @return bool|string
*/
public static function toCustomTemplateImageFile($contents, $templateType, $templateName, $imageType = 'png')
{
return self::toCustomTemplateFile($contents, $templateType, $templateName.'.'.$imageType);
}
/**
* Save file contents into a template file of specific template type.
*
*
* @return bool|string
*/
public static function toCustomTemplateFile($contents, $templateType, $fileName)
{
return Storage::disk('pdf_templates')->put(
sprintf('/%s/%s', $templateType, $fileName),
$contents
);
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace App\Space;
use Carbon\Carbon;
class TimeFormatter
{
protected static $formats = [
[
'carbon_format' => 'H:i',
'moment_format' => 'HH:mm',
],
[
'carbon_format' => 'g:i a',
'moment_format' => 'h:mm a',
],
];
public static function get_list()
{
$new = [];
foreach (static::$formats as $format) {
$new[] = [
'display_time' => Carbon::now()->format($format['carbon_format']),
'carbon_format_value' => $format['carbon_format'],
'moment_format_value' => $format['moment_format'],
];
}
return $new;
}
}

View File

@@ -93,7 +93,7 @@ class Updater
File::makeDirectory($temp_extract_dir);
}
// Unzip the file
$zip = new ZipArchive();
$zip = new ZipArchive;
if ($zip->open($zip_file_path)) {
$zip->extractTo($temp_extract_dir);

View File

@@ -111,9 +111,9 @@ trait GeneratesPdfTrait
public function getFieldsArray()
{
$customer = $this->customer;
$shippingAddress = $customer->shippingAddress ?? new Address();
$billingAddress = $customer->billingAddress ?? new Address();
$companyAddress = $this->company->address ?? new Address();
$shippingAddress = $customer->shippingAddress ?? new Address;
$billingAddress = $customer->billingAddress ?? new Address;
$companyAddress = $this->company->address ?? new Address;
$fields = [
'{SHIPPING_ADDRESS_NAME}' => $shippingAddress->name,
@@ -140,11 +140,14 @@ trait GeneratesPdfTrait
'{COMPANY_ADDRESS_STREET_2}' => $companyAddress->address_street_2,
'{COMPANY_PHONE}' => $companyAddress->phone,
'{COMPANY_ZIP_CODE}' => $companyAddress->zip,
'{COMPANY_VAT}' => $this->company->vat_id,
'{COMPANY_TAX}' => $this->company->tax_id,
'{CONTACT_DISPLAY_NAME}' => $customer->name,
'{PRIMARY_CONTACT_NAME}' => $customer->contact_name,
'{CONTACT_EMAIL}' => $customer->email,
'{CONTACT_PHONE}' => $customer->phone,
'{CONTACT_WEBSITE}' => $customer->website,
'{CONTACT_TAX_ID}' => __('pdf_tax_id').': '.$customer->tax_id,
];
$customFields = $this->fields;

View File

@@ -67,6 +67,7 @@ return Application::configure(basePath: dirname(__DIR__))
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Auth\Middleware\Authorize::class,
]);
})
->withExceptions(function (Exceptions $exceptions) {
//

View File

@@ -5,4 +5,5 @@ return [
App\Providers\RouteServiceProvider::class,
App\Providers\DropboxServiceProvider::class,
App\Providers\ViewServiceProvider::class,
App\Providers\PDFServiceProvider::class,
];

View File

@@ -9,40 +9,41 @@
"type": "project",
"require": {
"php": "^8.2",
"aws/aws-sdk-php": "^3.297",
"barryvdh/laravel-dompdf": "^2.1",
"doctrine/dbal": "^4.0",
"dragonmantank/cron-expression": "^3.3",
"guzzlehttp/guzzle": "^7.2",
"aws/aws-sdk-php": "^3.336",
"barryvdh/laravel-dompdf": "^v3.0",
"doctrine/dbal": "^4.2",
"dragonmantank/cron-expression": "^v3.4",
"guzzlehttp/guzzle": "^7.9",
"gotenberg/gotenberg-php": "^2.8",
"invoiceshelf/modules": "^1.0.0",
"jasonmccreary/laravel-test-assertions": "^2.4",
"laravel/framework": "^11.3",
"jasonmccreary/laravel-test-assertions": "^v2.4",
"laravel/framework": "^12.0",
"laravel/helpers": "^1.7",
"laravel/sanctum": "^4.0",
"laravel/tinker": "^2.9",
"laravel/ui": "^4.4",
"laravel/tinker": "^2.10.1",
"laravel/ui": "^4.6",
"lavary/laravel-menu": "^1.8",
"league/flysystem-aws-s3-v3": "^3.23",
"predis/predis": "^2.2",
"silber/bouncer": "v1.0.2",
"league/flysystem-aws-s3-v3": "^3.29",
"predis/predis": "^2.3",
"silber/bouncer": "v1.0.3",
"spatie/flysystem-dropbox": "^3.0",
"spatie/laravel-backup": "^8.6",
"spatie/laravel-medialibrary": "^11.3",
"vinkla/hashids": "^12.0"
"spatie/laravel-backup": "^9.2.9",
"spatie/laravel-medialibrary": "^11.11",
"vinkla/hashids": "^13.0.0"
},
"require-dev": {
"barryvdh/laravel-ide-helper": "^3.5",
"beyondcode/laravel-dump-server": "^2.0.0",
"fakerphp/faker": "^1.23",
"laravel/pint": "^1.13",
"laravel/sail": "^1.26",
"laravel/sail": "^1.41",
"mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.0",
"phpunit/phpunit": "^10.5",
"spatie/laravel-ignition": "^2.4",
"barryvdh/laravel-ide-helper": "^3.0",
"beyondcode/laravel-dump-server": "^2.0",
"pestphp/pest": "^2.33",
"pestphp/pest-plugin-faker": "^2.0",
"pestphp/pest-plugin-laravel": "^2.2"
"nunomaduro/collision": "^8.6",
"pestphp/pest": "^3.8",
"pestphp/pest-plugin-faker": "^3.0",
"pestphp/pest-plugin-laravel": "^3.1",
"phpunit/phpunit": "^11.5.3",
"spatie/laravel-ignition": "^2.9"
},
"autoload": {
"psr-4": {
@@ -60,31 +61,41 @@
"Tests\\": "tests/"
}
},
"minimum-stability": "dev",
"prefer-stable": true,
"scripts": {
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi"
],
"post-update-cmd": [
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
],
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"@php artisan key:generate --ansi"
"@php artisan key:generate --ansi",
"@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",
"@php artisan migrate --graceful --ansi"
],
"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"
]
},
"extra": {
"laravel": {
"dont-discover": []
}
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true,
"allow-plugins": {
"pestphp/pest-plugin": true
"pestphp/pest-plugin": true,
"php-http/discovery": true
}
},
"extra": {
"laravel": {
"dont-discover": []
}
}
"minimum-stability": "stable",
"prefer-stable": true
}

3755
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,6 @@
return [
'backup' => [
/*
* The name of this application. You can use this name to monitor
* the backups.
@@ -11,9 +10,7 @@ return [
'name' => env('APP_NAME', 'laravel-backup'),
'source' => [
'files' => [
/*
* The list of directories and files that will be included in the backup.
*/
@@ -41,6 +38,13 @@ return [
* Determines if it should avoid unreadable folders.
*/
'ignore_unreadable_directories' => false,
/*
* This path is used to make directories in resulting zip-file relative
* Set to `null` to include complete absolute path
* Example: base_path()
*/
'relative_path' => null,
],
/*
@@ -79,7 +83,7 @@ return [
],
/*
* The database dump can be compressed to decrease diskspace usage.
* The database dump can be compressed to decrease disk space usage.
*
* Out of the box Laravel-backup supplies
* Spatie\DbDumper\Compressors\GzipCompressor::class.
@@ -91,7 +95,53 @@ return [
*/
'database_dump_compressor' => null,
/*
* If specified, the database dumped file name will contain a timestamp (e.g.: 'Y-m-d-H-i-s').
*/
'database_dump_file_timestamp_format' => null,
/*
* The base of the dump filename, either 'database' or 'connection'
*
* If 'database' (default), the dumped filename will contain the database name.
* If 'connection', the dumped filename will contain the connection name.
*/
'database_dump_filename_base' => 'database',
/*
* The file extension used for the database dump files.
*
* If not specified, the file extension will be .archive for MongoDB and .sql for all other databases
* The file extension should be specified without a leading .
*/
'database_dump_file_extension' => '',
'destination' => [
/*
* The compression algorithm to be used for creating the zip archive.
*
* If backing up only database, you may choose gzip compression for db dump and no compression at zip.
*
* Some common algorithms are listed below:
* ZipArchive::CM_STORE (no compression at all; set 0 as compression level)
* ZipArchive::CM_DEFAULT
* ZipArchive::CM_DEFLATE
* ZipArchive::CM_BZIP2
* ZipArchive::CM_XZ
*
* For more check https://www.php.net/manual/zip.constants.php and confirm it's supported by your system.
*/
'compression_method' => ZipArchive::CM_DEFAULT,
/*
* The compression level corresponding to the used algorithm; an integer between 0 and 9.
*
* Check supported levels for the chosen algorithm, usually 1 means the fastest and weakest compression,
* while 9 the slowest and strongest one.
*
* Setting of 0 for some algorithms may switch to the strongest compression.
*/
'compression_level' => 9,
/*
* The filename prefix used for the backup zip file.
@@ -110,24 +160,49 @@ return [
* The directory where the temporary files will be stored.
*/
'temporary_directory' => storage_path('app/backup-temp'),
/*
* The password to be used for archive encryption.
* Set to `null` to disable encryption.
*/
'password' => env('BACKUP_ARCHIVE_PASSWORD'),
/*
* The encryption algorithm to be used for archive encryption.
* You can set it to `null` or `false` to disable encryption.
*
* When set to 'default', we'll use ZipArchive::EM_AES_256 if it is
* available on your system.
*/
'encryption' => 'default',
/*
* The number of attempts, in case the backup command encounters an exception
*/
'tries' => 1,
/*
* The number of seconds to wait before attempting a new backup if the previous try failed
* Set to `0` for none
*/
'retry_delay' => 0,
],
/*
* You can get notified when specific events occur. Out of the box you can use 'mail' and 'slack'.
* For Slack you need to install guzzlehttp/guzzle and laravel/slack-notification-channel.
* For Slack you need to install laravel/slack-notification-channel.
*
* You can also use your own notification classes, just make sure the class is named after one of
* the `Spatie\Backup\Events` classes.
* the `Spatie\Backup\Notifications\Notifications` classes.
*/
'notifications' => [
'notifications' => [
\Spatie\Backup\Notifications\Notifications\BackupHasFailed::class => [],
\Spatie\Backup\Notifications\Notifications\UnhealthyBackupWasFound::class => [],
\Spatie\Backup\Notifications\Notifications\CleanupHasFailed::class => [],
\Spatie\Backup\Notifications\Notifications\BackupWasSuccessful::class => [],
\Spatie\Backup\Notifications\Notifications\HealthyBackupWasFound::class => [],
\Spatie\Backup\Notifications\Notifications\CleanupWasSuccessful::class => [],
\Spatie\Backup\Notifications\Notifications\BackupHasFailedNotification::class => ['mail'],
\Spatie\Backup\Notifications\Notifications\UnhealthyBackupWasFoundNotification::class => ['mail'],
\Spatie\Backup\Notifications\Notifications\CleanupHasFailedNotification::class => ['mail'],
\Spatie\Backup\Notifications\Notifications\BackupWasSuccessfulNotification::class => ['mail'],
\Spatie\Backup\Notifications\Notifications\HealthyBackupWasFoundNotification::class => ['mail'],
\Spatie\Backup\Notifications\Notifications\CleanupWasSuccessfulNotification::class => ['mail'],
],
/*
@@ -156,7 +231,20 @@ return [
'username' => null,
'icon' => null,
],
'discord' => [
'webhook_url' => '',
/*
* If this is an empty string, the name field on the webhook will be used.
*/
'username' => '',
/*
* If this is an empty string, the avatar on the webhook will be used.
*/
'avatar_url' => '',
],
],
@@ -200,42 +288,55 @@ return [
'strategy' => \Spatie\Backup\Tasks\Cleanup\Strategies\DefaultStrategy::class,
'default_strategy' => [
/*
* The number of days for which backups must be kept.
*/
'keep_all_backups_for_days' => 7,
/*
* The number of days for which daily backups must be kept.
* After the "keep_all_backups_for_days" period is over, the most recent backup
* of that day will be kept. Older backups within the same day will be removed.
* If you create backups only once a day, no backups will be removed yet.
*/
'keep_daily_backups_for_days' => 16,
/*
* The number of weeks for which one weekly backup must be kept.
* After the "keep_daily_backups_for_days" period is over, the most recent backup
* of that week will be kept. Older backups within the same week will be removed.
* If you create backups only once a week, no backups will be removed yet.
*/
'keep_weekly_backups_for_weeks' => 8,
/*
* The number of months for which one monthly backup must be kept.
* After the "keep_weekly_backups_for_weeks" period is over, the most recent backup
* of that month will be kept. Older backups within the same month will be removed.
*/
'keep_monthly_backups_for_months' => 4,
/*
* The number of years for which one yearly backup must be kept.
* After the "keep_monthly_backups_for_months" period is over, the most recent backup
* of that year will be kept. Older backups within the same year will be removed.
*/
'keep_yearly_backups_for_years' => 2,
/*
* After cleaning up the backups remove the oldest backup until
* this amount of megabytes has been reached.
* Set null for unlimited size.
*/
'delete_oldest_backups_when_using_more_megabytes_than' => 5000,
],
],
'queue' => [
'name' => env('BACKUP_QUEUE_NAME', 'backup'),
/*
* The number of attempts, in case the cleanup command encounters an exception
*/
'tries' => 1,
/*
* The number of seconds to wait before attempting a new cleanup if the previous try failed
* Set to `0` for none
*/
'retry_delay' => 0,
],
];

View File

@@ -119,7 +119,7 @@ return [
* If pdflib present in web server and auto or selected explicitly above,
* a real license code must exist!
*/
//"DOMPDF_PDFLIB_LICENSE" => "your license key here",
// "DOMPDF_PDFLIB_LICENSE" => "your license key here",
/**
* html target media view which should be rendered into pdf.

View File

@@ -55,6 +55,18 @@ return [
'driver' => 'local',
'root' => resource_path('views'),
],
'pdf_templates' => [
'driver' => 'local',
'root' => storage_path('app/templates/pdf'),
],
'local' => [
'driver' => 'local',
'root' => storage_path('app'),
'throw' => false,
'report' => false,
],
],
];

View File

@@ -131,7 +131,7 @@ return [
'group' => '',
'name' => 'Company information',
'link' => '/admin/settings/company-info',
'icon' => 'OfficeBuildingIcon',
'icon' => 'BuildingOfficeIcon',
'owner_only' => true,
'ability' => '',
'model' => '',
@@ -151,7 +151,17 @@ return [
'group' => '',
'name' => 'Customization',
'link' => '/admin/settings/customization',
'icon' => 'PencilAltIcon',
'icon' => 'PencilSquareIcon',
'owner_only' => true,
'ability' => '',
'model' => '',
],
[
'title' => 'settings.menu_title.pdf_generation',
'group' => '',
'name' => 'PDF Generation',
'link' => '/admin/settings/pdf-generation',
'icon' => 'DocumentIcon',
'owner_only' => true,
'ability' => '',
'model' => '',
@@ -171,7 +181,7 @@ return [
'group' => '',
'name' => 'Exchange Rate Provider',
'link' => '/admin/settings/exchange-rate-provider',
'icon' => 'CashIcon',
'icon' => 'BanknotesIcon',
'owner_only' => false,
'ability' => 'view-exchange-rate-provider',
'model' => ExchangeRateProvider::class,
@@ -221,7 +231,7 @@ return [
'group' => '',
'name' => 'Notes',
'link' => '/admin/settings/notes',
'icon' => 'ClipboardCheckIcon',
'icon' => 'ClipboardDocumentCheckIcon',
'owner_only' => false,
'ability' => 'view-all-notes',
'model' => Note::class,
@@ -231,7 +241,7 @@ return [
'group' => '',
'name' => 'Expense Category',
'link' => '/admin/settings/expense-category',
'icon' => 'ClipboardListIcon',
'icon' => 'ClipboardDocumentListIcon',
'owner_only' => false,
'ability' => 'view-expense',
'model' => Expense::class,
@@ -241,7 +251,7 @@ return [
'group' => '',
'name' => 'Mail Configuration',
'link' => '/admin/settings/mail-configuration',
'icon' => 'MailIcon',
'icon' => 'EnvelopeIcon',
'owner_only' => true,
'ability' => '',
'model' => '',
@@ -261,7 +271,7 @@ return [
'group' => '',
'name' => 'Backup',
'link' => '/admin/settings/backup',
'icon' => 'DatabaseIcon',
'icon' => 'CircleStackIcon',
'owner_only' => true,
'ability' => '',
'model' => '',
@@ -271,7 +281,7 @@ return [
'group' => '',
'name' => 'Update App',
'link' => '/admin/settings/update-app',
'icon' => 'RefreshIcon',
'icon' => 'ArrowPathIcon',
'owner_only' => true,
'ability' => '',
'model' => '',
@@ -366,7 +376,7 @@ return [
'title' => 'navigation.modules',
'group' => 3,
'link' => '/admin/modules',
'icon' => 'PuzzleIcon',
'icon' => 'PuzzlePieceIcon',
'name' => 'Modules',
'owner_only' => true,
'ability' => '',

View File

@@ -136,7 +136,7 @@ return [
'-m 6', // for the slowest compression method in order to get the best compression.
'-pass 10', // for maximizing the amount of analysis pass.
'-mt', // multithreading for some speed improvements.
'-q 90', //quality factor that brings the least noticeable changes.
'-q 90', // quality factor that brings the least noticeable changes.
],
Spatie\ImageOptimizer\Optimizers\Avifenc::class => [
'-a cq-level=23', // constant quality level, lower values mean better quality and greater file size (0-63).

View File

@@ -39,8 +39,8 @@ return [
'resources/sass/module' => 'Resources/sass/module.scss',
'resources/scripts/stores/sample-store' => 'Resources/scripts/stores/sample-store.js',
'resources/scripts/views/SamplePage' => 'Resources/scripts/views/SamplePage.vue',
'lang/en' => 'Lang/en.json',
'lang/locales' => 'Lang/locales.js',
'resources/locales/en' => 'Resources/locales/en.json',
'resources/locales/locales' => 'Resources/locales/locales.js',
'package' => 'package.json',
'postcss.config' => 'postcss.config.js',
'tailwind.config' => 'tailwind.config.js',
@@ -63,8 +63,8 @@ return [
'MODULE_NAMESPACE',
'PROVIDER_NAMESPACE',
],
'assets/scripts/module' => ['LOWER_NAME'],
'assets/scripts/stores/sample-store' => ['LOWER_NAME'],
'resources/scripts/module' => ['LOWER_NAME'],
'resources/scripts/stores/sample-store' => ['LOWER_NAME'],
'vite.config' => ['LOWER_NAME'],
],
'gitkeep' => true,

14
config/pdf.php Normal file
View File

@@ -0,0 +1,14 @@
<?php
return [
'driver' => env('PDF_DRIVER', 'gotenberg'),
'gotenberg' => [
'host' => env('GOTENBERG_HOST', 'http://pdf:3000'),
'papersize' => env('GOTENBERG_PAPERSIZE', '210mm 297mm'),
],
'dompdf' => [],
];

View File

@@ -1,48 +0,0 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Stateful Domains
|--------------------------------------------------------------------------
|
| Requests from the following domains / hosts will receive stateful API
| authentication cookies. Typically, these should include your local
| and production domains which access your API via a frontend SPA.
|
*/
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', 'localhost,127.0.0.1,127.0.0.1:8000,::1')),
/*
|--------------------------------------------------------------------------
| Expiration Minutes
|--------------------------------------------------------------------------
|
| This value controls the number of minutes until an issued token will be
| considered expired. If this value is null, personal access tokens do
| not expire. This won't tweak the lifetime of first-party sessions.
|
*/
'expiration' => null,
/*
|--------------------------------------------------------------------------
| Sanctum Middleware
|--------------------------------------------------------------------------
|
| When authenticating your first-party SPA with Sanctum you may need to
| customize some of the middleware Sanctum uses while processing the
| request. You may change the middleware listed below as required.
|
*/
'middleware' => [
'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class,
'encrypt_cookies' => Illuminate\Cookie\Middleware\EncryptCookies::class,
'validate_csrf_token' => Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
],
];

View File

@@ -48,4 +48,10 @@ return [
'auth_token' => env('CRON_JOB_AUTH_TOKEN', 0),
],
'ses' => [
'key' => env('SES_KEY'),
'secret' => env('SES_SECRET'),
'region' => env('SES_REGION', 'us-east-1'),
],
];

View File

@@ -68,8 +68,8 @@ class EstimateFactory extends Factory
*/
public function definition(): array
{
$sequenceNumber = (new SerialNumberFormatter())
->setModel(new Estimate())
$sequenceNumber = (new SerialNumberFormatter)
->setModel(new Estimate)
->setCompany(User::find(1)->companies()->first()->id)
->setNextNumbers();
@@ -93,6 +93,7 @@ class EstimateFactory extends Factory
return $estimate['discount_type'] == 'percentage' ? (($estimate['discount_val'] * $estimate['total']) / 100) : $estimate['discount_val'];
},
'tax_per_item' => 'YES',
'tax_included' => false,
'discount_per_item' => 'No',
'tax' => $this->faker->randomDigitNotNull(),
'notes' => $this->faker->text(80),

View File

@@ -78,8 +78,8 @@ class InvoiceFactory extends Factory
*/
public function definition(): array
{
$sequenceNumber = (new SerialNumberFormatter())
->setModel(new Invoice())
$sequenceNumber = (new SerialNumberFormatter)
->setModel(new Invoice)
->setCompany(User::find(1)->companies()->first()->id)
->setNextNumbers();
@@ -93,6 +93,7 @@ class InvoiceFactory extends Factory
'template_name' => 'invoice1',
'status' => Invoice::STATUS_DRAFT,
'tax_per_item' => 'NO',
'tax_included' => false,
'discount_per_item' => 'NO',
'paid_status' => Invoice::STATUS_UNPAID,
'company_id' => User::find(1)->companies()->first()->id,

View File

@@ -25,6 +25,7 @@ class NoteFactory extends Factory
'name' => $this->faker->word(),
'notes' => $this->faker->text(),
'company_id' => User::find(1)->companies()->first()->id,
'is_default' => $this->faker->boolean(),
];
}
}

View File

@@ -24,8 +24,8 @@ class PaymentFactory extends Factory
*/
public function definition(): array
{
$sequenceNumber = (new SerialNumberFormatter())
->setModel(new Payment())
$sequenceNumber = (new SerialNumberFormatter)
->setModel(new Payment)
->setCompany(User::find(1)->companies()->first()->id)
->setNextNumbers();

View File

@@ -26,6 +26,7 @@ class RecurringInvoiceFactory extends Factory
'send_automatically' => false,
'status' => $this->faker->randomElement(['COMPLETED', 'ON_HOLD', 'ACTIVE']),
'tax_per_item' => 'NO',
'tax_included' => false,
'discount_per_item' => 'NO',
'sub_total' => $this->faker->randomDigitNotNull(),
'total' => $this->faker->randomDigitNotNull(),

View File

@@ -22,8 +22,10 @@ class TaxTypeFactory extends Factory
{
return [
'name' => $this->faker->word(),
'calculation_type' => 'percentage',
'company_id' => User::find(1)->companies()->first()->id,
'percent' => $this->faker->numberBetween($min = 0, $max = 100),
'fixed_amount' => null,
'description' => $this->faker->text(),
'compound_tax' => 0,
'collective_tax' => 0,

View File

@@ -24,7 +24,5 @@ class AddUserIdToExpensesTable extends Migration
*
* @return void
*/
public function down()
{
}
public function down() {}
}

View File

@@ -4,7 +4,7 @@ use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class() extends Migration
return new class extends Migration
{
/**
* Run the migrations.

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('invoices', function (Blueprint $table) {
$table->datetime('invoice_date')->change();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('invoices', function (Blueprint $table) {
$table->date('invoice_date')->change();
});
}
};

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