Compare commits
93 Commits
2.0.0
...
frontend-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
697c1af065 | ||
|
|
70bfe1b688 | ||
|
|
d69a56e2d5 | ||
|
|
08e1bb2e22 | ||
|
|
29c15116bc | ||
|
|
8e96d3e972 | ||
|
|
bf0d98c69c | ||
|
|
73d4ac1eb1 | ||
|
|
e832c7661a | ||
|
|
b962bc9227 | ||
|
|
d70c04b841 | ||
|
|
6d14dce668 | ||
|
|
1ff220f0d8 | ||
|
|
2e77a76c7b | ||
|
|
bf5b544ca3 | ||
|
|
546f75d3a6 | ||
|
|
bf40f792c2 | ||
|
|
8a9392e400 | ||
|
|
14bfaff30b | ||
|
|
b32c334a71 | ||
|
|
2aa17513e1 | ||
|
|
ba243b28a9 | ||
|
|
1bb65f420c | ||
|
|
23a99758d2 | ||
|
|
139f8e2d13 | ||
|
|
ac1a582ecf | ||
|
|
6b168f36ec | ||
|
|
50b647e26e | ||
|
|
57776b7c7d | ||
|
|
8c343b4b92 | ||
|
|
a60cac3e66 | ||
|
|
6a0d3a3bcc | ||
|
|
3e52b84fa8 | ||
|
|
d1f45a790b | ||
|
|
bec0c058a8 | ||
|
|
d862ee05e9 | ||
|
|
12d9d6c801 | ||
|
|
e9e52c60a7 | ||
|
|
34b9f52af7 | ||
|
|
8b49332453 | ||
|
|
8160b53689 | ||
|
|
fcf64c0b26 | ||
|
|
7e2d257f7d | ||
|
|
c617f7d169 | ||
|
|
1804481fc6 | ||
|
|
9bed81fe8f | ||
|
|
f52b73f517 | ||
|
|
32e03b98a3 | ||
|
|
3f560a1de2 | ||
|
|
ef9adbd6c9 | ||
|
|
969cbe9ad8 | ||
|
|
6031f0dbdd | ||
|
|
06a71fc7b3 | ||
|
|
fd1c7be1f8 | ||
|
|
c466d51fe0 | ||
|
|
e8dccb178b | ||
|
|
f5b36d7e6c | ||
|
|
a32bbb6268 | ||
|
|
9599814ae5 | ||
|
|
0d14da93d0 | ||
|
|
298c170867 | ||
|
|
b49228eabf | ||
|
|
99ada6fbcc | ||
|
|
ed4a59574c | ||
|
|
be2e1df442 | ||
|
|
ddca5a7b6d | ||
|
|
51d001aa0f | ||
|
|
5d817b5c45 | ||
|
|
ac2c73829c | ||
|
|
317f763a85 | ||
|
|
e112ed5875 | ||
|
|
84c10264ed | ||
|
|
b143642056 | ||
|
|
967c225df9 | ||
|
|
e1a0a2d8e4 | ||
|
|
748168ffa5 | ||
|
|
4db243e136 | ||
|
|
bc0c8d5348 | ||
|
|
6583569b1c | ||
|
|
134c99369e | ||
|
|
f2ae4e17c8 | ||
|
|
4ff62c0adf | ||
|
|
2604db32ce | ||
|
|
01d32d50b5 | ||
|
|
0c86563375 | ||
|
|
33c2949a7b | ||
|
|
547a2be0a3 | ||
|
|
59b43fa258 | ||
|
|
168b741936 | ||
|
|
022769ba38 | ||
|
|
068f48568e | ||
|
|
03b9defeb1 | ||
|
|
dd98df1c77 |
@@ -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" ]
|
||||
|
||||
|
||||
@@ -63,6 +63,11 @@ services:
|
||||
networks:
|
||||
- invoiceshelf-dev
|
||||
|
||||
pdf:
|
||||
image: gotenberg/gotenberg:8
|
||||
networks:
|
||||
- invoiceshelf-dev
|
||||
|
||||
networks:
|
||||
invoiceshelf-dev:
|
||||
|
||||
|
||||
@@ -62,6 +62,11 @@ services:
|
||||
networks:
|
||||
- invoiceshelf-dev
|
||||
|
||||
pdf:
|
||||
image: gotenberg/gotenberg:8
|
||||
networks:
|
||||
- invoiceshelf-dev
|
||||
|
||||
networks:
|
||||
invoiceshelf-dev:
|
||||
|
||||
|
||||
@@ -50,5 +50,10 @@ services:
|
||||
networks:
|
||||
- invoiceshelf-dev
|
||||
|
||||
pdf:
|
||||
image: gotenberg/gotenberg:8
|
||||
networks:
|
||||
- invoiceshelf-dev
|
||||
|
||||
networks:
|
||||
invoiceshelf-dev:
|
||||
|
||||
@@ -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=
|
||||
|
||||
0
.eslintrc.mjs → .eslintrc.js
vendored
0
.eslintrc.mjs → .eslintrc.js
vendored
26
.github/ISSUE_TEMPLATE/bug_report.md
vendored
26
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -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
237
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal 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)
|
||||
94
.github/ISSUE_TEMPLATE/code_quality_issue.yml
vendored
Normal file
94
.github/ISSUE_TEMPLATE/code_quality_issue.yml
vendored
Normal 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
11
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal 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.
|
||||
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -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.
|
||||
96
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
96
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal 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.
|
||||
17
.github/PULL_REQUEST_TEMPLATE/pull_request_template.md
vendored
Normal file
17
.github/PULL_REQUEST_TEMPLATE/pull_request_template.md
vendored
Normal 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 #
|
||||
15
.github/workflows/check.yaml
vendored
15
.github/workflows/check.yaml
vendored
@@ -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
|
||||
|
||||
|
||||
27208
_ide_helper.php
27208
_ide_helper.php
File diff suppressed because it is too large
Load Diff
1629
_ide_helper_models.php
Normal file
1629
_ide_helper_models.php
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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!');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
16
app/Facades/PDF.php
Normal 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';
|
||||
}
|
||||
}
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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'));
|
||||
|
||||
@@ -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'));
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ class AppDomainController extends Controller
|
||||
{
|
||||
Artisan::call('optimize:clear');
|
||||
|
||||
$environmentManager = new EnvironmentManager();
|
||||
$environmentManager = new EnvironmentManager;
|
||||
|
||||
$results = $environmentManager->saveDomainVariables($request);
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -38,6 +38,15 @@ class CompanySettingRequest extends FormRequest
|
||||
'carbon_date_format' => [
|
||||
'required',
|
||||
],
|
||||
'moment_time_format' => [
|
||||
'required',
|
||||
],
|
||||
'carbon_time_format' => [
|
||||
'required',
|
||||
],
|
||||
'invoice_use_time' => [
|
||||
'required',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -33,6 +33,9 @@ class NotesRequest extends FormRequest
|
||||
'notes' => [
|
||||
'required',
|
||||
],
|
||||
'is_default' => [
|
||||
'required',
|
||||
],
|
||||
];
|
||||
|
||||
if ($this->isMethod('PUT')) {
|
||||
|
||||
57
app/Http/Requests/PDFConfigurationRequest.php
Normal file
57
app/Http/Requests/PDFConfigurationRequest.php
Normal 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');
|
||||
}
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}),
|
||||
|
||||
@@ -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);
|
||||
}),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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'];
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -22,7 +22,7 @@ class Setting extends Model
|
||||
return;
|
||||
}
|
||||
|
||||
$set = new Setting();
|
||||
$set = new Setting;
|
||||
$set->option = $key;
|
||||
$set->value = $setting;
|
||||
$set->save();
|
||||
|
||||
@@ -21,6 +21,7 @@ class Tax extends Model
|
||||
return [
|
||||
'amount' => 'integer',
|
||||
'percent' => 'float',
|
||||
'fixed_amount' => 'integer',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ class TaxType extends Model
|
||||
{
|
||||
return [
|
||||
'percent' => 'float',
|
||||
'fixed_amount' => 'integer',
|
||||
'compound_tax' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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']);
|
||||
|
||||
|
||||
13
app/Providers/PDFServiceProvider.php
Normal file
13
app/Providers/PDFServiceProvider.php
Normal 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,
|
||||
];
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
59
app/Services/PDFDrivers/GotenbergPDFDriver.php
Normal file
59
app/Services/PDFDrivers/GotenbergPDFDriver.php
Normal 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);
|
||||
}
|
||||
}
|
||||
47
app/Services/PDFService.php
Normal file
47
app/Services/PDFService.php
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
140
app/Space/PdfTemplateUtils.php
Normal file
140
app/Space/PdfTemplateUtils.php
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
||||
34
app/Space/TimeFormatter.php
Normal file
34
app/Space/TimeFormatter.php
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -67,6 +67,7 @@ return Application::configure(basePath: dirname(__DIR__))
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
\Illuminate\Auth\Middleware\Authorize::class,
|
||||
]);
|
||||
|
||||
})
|
||||
->withExceptions(function (Exceptions $exceptions) {
|
||||
//
|
||||
|
||||
@@ -5,4 +5,5 @@ return [
|
||||
App\Providers\RouteServiceProvider::class,
|
||||
App\Providers\DropboxServiceProvider::class,
|
||||
App\Providers\ViewServiceProvider::class,
|
||||
App\Providers\PDFServiceProvider::class,
|
||||
];
|
||||
|
||||
@@ -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
3755
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -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,
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -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' => '',
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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
14
config/pdf.php
Normal 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' => [],
|
||||
|
||||
];
|
||||
@@ -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,
|
||||
],
|
||||
|
||||
];
|
||||
@@ -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'),
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -24,7 +24,5 @@ class AddUserIdToExpensesTable extends Migration
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
}
|
||||
public function down() {}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user