Compare commits
72 Commits
develop
...
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 |
@@ -2,12 +2,6 @@ FROM adminer:latest
|
|||||||
|
|
||||||
USER root
|
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
|
USER adminer
|
||||||
CMD [ "php", "-S", "[::]:8080", "-t", "/var/www/html" ]
|
CMD [ "php", "-S", "[::]:8080", "-t", "/var/www/html" ]
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,11 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- invoiceshelf-dev
|
- invoiceshelf-dev
|
||||||
|
|
||||||
|
pdf:
|
||||||
|
image: gotenberg/gotenberg:8
|
||||||
|
networks:
|
||||||
|
- invoiceshelf-dev
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
invoiceshelf-dev:
|
invoiceshelf-dev:
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,11 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- invoiceshelf-dev
|
- invoiceshelf-dev
|
||||||
|
|
||||||
|
pdf:
|
||||||
|
image: gotenberg/gotenberg:8
|
||||||
|
networks:
|
||||||
|
- invoiceshelf-dev
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
invoiceshelf-dev:
|
invoiceshelf-dev:
|
||||||
|
|
||||||
|
|||||||
@@ -50,5 +50,10 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- invoiceshelf-dev
|
- invoiceshelf-dev
|
||||||
|
|
||||||
|
pdf:
|
||||||
|
image: gotenberg/gotenberg:8
|
||||||
|
networks:
|
||||||
|
- invoiceshelf-dev
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
invoiceshelf-dev:
|
invoiceshelf-dev:
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ APP_DEBUG=true
|
|||||||
APP_NAME="InvoiceShelf"
|
APP_NAME="InvoiceShelf"
|
||||||
APP_LOG_LEVEL=debug
|
APP_LOG_LEVEL=debug
|
||||||
APP_TIMEZONE=UTC
|
APP_TIMEZONE=UTC
|
||||||
APP_URL=http://invoiceshelf.test
|
APP_URL=
|
||||||
|
|
||||||
APP_LOCALE=en
|
APP_LOCALE=en
|
||||||
APP_FALLBACK_LOCALE=en
|
APP_FALLBACK_LOCALE=en
|
||||||
@@ -49,8 +49,11 @@ PUSHER_APP_ID=
|
|||||||
PUSHER_KEY=
|
PUSHER_KEY=
|
||||||
PUSHER_SECRET=
|
PUSHER_SECRET=
|
||||||
|
|
||||||
SANCTUM_STATEFUL_DOMAINS=invoiceshelf.test
|
|
||||||
TRUSTED_PROXIES="*"
|
TRUSTED_PROXIES="*"
|
||||||
|
|
||||||
CRON_JOB_AUTH_TOKEN=""
|
CRON_JOB_AUTH_TOKEN=""
|
||||||
LOG_STACK=single
|
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,13 +7,15 @@ on:
|
|||||||
- '**/*.md'
|
- '**/*.md'
|
||||||
- 'public/build/*.js'
|
- 'public/build/*.js'
|
||||||
- 'public/build/**/*.js'
|
- 'public/build/**/*.js'
|
||||||
|
branches-ignore:
|
||||||
|
- 'translations'
|
||||||
pull_request:
|
pull_request:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**/*.md'
|
- '**/*.md'
|
||||||
- 'public/build/*.js'
|
- 'public/build/*.js'
|
||||||
- 'public/build/**/*.js'
|
- 'public/build/**/*.js'
|
||||||
branches-ignore:
|
branches-ignore:
|
||||||
- 'l10n_master2'
|
- 'translations'
|
||||||
# Allow manually triggering the workflow.
|
# Allow manually triggering the workflow.
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
@@ -38,7 +40,7 @@ jobs:
|
|||||||
- name: Set up PHP
|
- name: Set up PHP
|
||||||
uses: shivammathur/setup-php@v2
|
uses: shivammathur/setup-php@v2
|
||||||
with:
|
with:
|
||||||
php-version: 8.2
|
php-version: 8.3
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -59,12 +61,13 @@ jobs:
|
|||||||
php-version:
|
php-version:
|
||||||
- 8.2
|
- 8.2
|
||||||
- 8.3
|
- 8.3
|
||||||
|
- 8.4
|
||||||
env:
|
env:
|
||||||
extensions: bcmath, curl, dom, gd, imagick, json, libxml, mbstring, pcntl, pdo, pdo_mysql, zip
|
extensions: bcmath, curl, dom, gd, imagick, json, libxml, mbstring, pcntl, pdo, pdo_mysql, zip
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup PHP Action
|
- name: Setup PHP Action
|
||||||
uses: shivammathur/setup-php@v2
|
uses: shivammathur/setup-php@v2
|
||||||
@@ -78,7 +81,7 @@ jobs:
|
|||||||
uses: ramsey/composer-install@v2
|
uses: ramsey/composer-install@v2
|
||||||
|
|
||||||
- name: Use Node.js 20
|
- name: Use Node.js 20
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
|
|
||||||
@@ -102,7 +105,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
uses: shivammathur/setup-php@v2
|
uses: shivammathur/setup-php@v2
|
||||||
@@ -117,7 +120,7 @@ jobs:
|
|||||||
composer-options: --no-dev
|
composer-options: --no-dev
|
||||||
|
|
||||||
- name: Use Node.js 20
|
- name: Use Node.js 20
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
|
|
||||||
|
|||||||
23970
_ide_helper.php
23970
_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;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Space\PdfTemplateUtils;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class CreateTemplateCommand extends Command
|
class CreateTemplateCommand extends Command
|
||||||
{
|
{
|
||||||
@@ -37,26 +40,53 @@ class CreateTemplateCommand extends Command
|
|||||||
public function handle(): int
|
public function handle(): int
|
||||||
{
|
{
|
||||||
$templateName = $this->argument('name');
|
$templateName = $this->argument('name');
|
||||||
$type = $this->option('type');
|
$templateType = $this->option('type');
|
||||||
|
|
||||||
if (! $type) {
|
if (! $templateType) {
|
||||||
$type = $this->choice('Create a template for?', ['invoice', 'estimate']);
|
$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.');
|
$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");
|
if (! PdfTemplateUtils::toCustomTemplateMarkupFile(
|
||||||
copy(public_path("/build/img/PDF/{$type}1.png"), public_path("/build/img/PDF/{$templateName}.png"));
|
Str::replace(
|
||||||
copy(resource_path("/static/img/PDF/{$type}1.png"), resource_path("/static/img/PDF/{$templateName}.png"));
|
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");
|
return self::FAILURE;
|
||||||
$type = ucfirst($type);
|
}
|
||||||
$this->info("{$type} Template created successfully at ".$path);
|
|
||||||
|
|
||||||
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\Console\ConfirmableTrait;
|
||||||
use Illuminate\Support\Facades\Artisan;
|
use Illuminate\Support\Facades\Artisan;
|
||||||
|
|
||||||
|
use function Laravel\Prompts\confirm;
|
||||||
|
|
||||||
class ResetApp extends Command
|
class ResetApp extends Command
|
||||||
{
|
{
|
||||||
use ConfirmableTrait;
|
use ConfirmableTrait;
|
||||||
@@ -39,30 +41,49 @@ class ResetApp extends Command
|
|||||||
*
|
*
|
||||||
* @return mixed
|
* @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
|
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');
|
Artisan::call('migrate:fresh --seed --force');
|
||||||
|
|
||||||
|
// Seed demo data
|
||||||
$this->info('Seeding database');
|
$this->info('Seeding database');
|
||||||
|
|
||||||
Artisan::call('db:seed', ['--class' => 'DemoSeeder', '--force' => true]);
|
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)) {
|
// Disable maintenance mode
|
||||||
file_put_contents($path, str_replace(
|
$this->info('Deactivating maintenance mode...');
|
||||||
'APP_DEBUG=true',
|
Artisan::call('up');
|
||||||
'APP_DEBUG=false',
|
|
||||||
file_get_contents($path)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->info('App has been reset successfully');
|
$this->info('App reset completed successfully!');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
use App\Models\Setting;
|
|
||||||
use App\Space\Updater;
|
use App\Space\Updater;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
@@ -98,7 +97,7 @@ class UpdateCommand extends Command
|
|||||||
|
|
||||||
public function getInstalledVersion()
|
public function getInstalledVersion()
|
||||||
{
|
{
|
||||||
return Setting::getSetting('version');
|
return preg_replace('~[\r\n]+~', '', File::get(base_path('version.md')));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getLatestVersionResponse()
|
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 App\Models\Setting;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
|
|
||||||
class AppVersionController extends Controller
|
class AppVersionController extends Controller
|
||||||
{
|
{
|
||||||
@@ -14,7 +15,7 @@ class AppVersionController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function __invoke(Request $request)
|
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');
|
$channel = Setting::getSetting('updater_channel');
|
||||||
if (is_null($channel)) {
|
if (is_null($channel)) {
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ class BackupsController extends ApiController
|
|||||||
$this->authorize('manage backups');
|
$this->authorize('manage backups');
|
||||||
|
|
||||||
$validated = $request->validate([
|
$validated = $request->validate([
|
||||||
'path' => ['required', new PathToZip()],
|
'path' => ['required', new PathToZip],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$backupDestination = BackupDestination::create(config('filesystems.default'), config('backup.backup.name'));
|
$backupDestination = BackupDestination::create(config('filesystems.default'), config('backup.backup.name'));
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class DownloadBackupController extends ApiController
|
|||||||
$this->authorize('manage backups');
|
$this->authorize('manage backups');
|
||||||
|
|
||||||
$validated = $request->validate([
|
$validated = $request->validate([
|
||||||
'path' => ['required', new PathToZip()],
|
'path' => ['required', new PathToZip],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$backupDestination = BackupDestination::create(config('filesystems.default'), config('backup.backup.name'));
|
$backupDestination = BackupDestination::create(config('filesystems.default'), config('backup.backup.name'));
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class CloneEstimateController extends Controller
|
|||||||
|
|
||||||
$date = Carbon::now();
|
$date = Carbon::now();
|
||||||
|
|
||||||
$serial = (new SerialNumberFormatter())
|
$serial = (new SerialNumberFormatter)
|
||||||
->setModel($estimate)
|
->setModel($estimate)
|
||||||
->setCompany($estimate->company_id)
|
->setCompany($estimate->company_id)
|
||||||
->setCustomer($estimate->customer_id)
|
->setCustomer($estimate->customer_id)
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class ConvertEstimateController extends Controller
|
|||||||
$due_date = Carbon::now()->addDays($dueDateDays)->format('Y-m-d');
|
$due_date = Carbon::now()->addDays($dueDateDays)->format('Y-m-d');
|
||||||
}
|
}
|
||||||
|
|
||||||
$serial = (new SerialNumberFormatter())
|
$serial = (new SerialNumberFormatter)
|
||||||
->setModel($invoice)
|
->setModel($invoice)
|
||||||
->setCompany($estimate->company_id)
|
->setCompany($estimate->company_id)
|
||||||
->setCustomer($estimate->customer_id)
|
->setCustomer($estimate->customer_id)
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ namespace App\Http\Controllers\V1\Admin\Estimate;
|
|||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\Estimate;
|
use App\Models\Estimate;
|
||||||
|
use App\Space\PdfTemplateUtils;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class EstimateTemplatesController extends Controller
|
class EstimateTemplatesController extends Controller
|
||||||
@@ -11,13 +13,13 @@ class EstimateTemplatesController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Handle the incoming request.
|
* Handle the incoming request.
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Http\Response
|
* @return JsonResponse
|
||||||
*/
|
*/
|
||||||
public function __invoke(Request $request)
|
public function __invoke(Request $request)
|
||||||
{
|
{
|
||||||
$this->authorize('viewAny', Estimate::class);
|
$this->authorize('viewAny', Estimate::class);
|
||||||
|
|
||||||
$estimateTemplates = Estimate::estimateTemplates();
|
$estimateTemplates = PdfTemplateUtils::getFormattedTemplates('estimate');
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'estimateTemplates' => $estimateTemplates,
|
'estimateTemplates' => $estimateTemplates,
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class NextNumberController extends Controller
|
|||||||
{
|
{
|
||||||
$key = $request->key;
|
$key = $request->key;
|
||||||
$nextNumber = null;
|
$nextNumber = null;
|
||||||
$serial = (new SerialNumberFormatter())
|
$serial = (new SerialNumberFormatter)
|
||||||
->setCompany($request->header('company'))
|
->setCompany($request->header('company'))
|
||||||
->setCustomer($request->userId);
|
->setCustomer($request->userId);
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,15 @@ class NotesController extends Controller
|
|||||||
|
|
||||||
$note = Note::create($request->getNotesPayload());
|
$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);
|
return new NoteResource($note);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,6 +77,15 @@ class NotesController extends Controller
|
|||||||
|
|
||||||
$note->update($request->getNotesPayload());
|
$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);
|
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();
|
$date = Carbon::now();
|
||||||
|
|
||||||
$serial = (new SerialNumberFormatter())
|
$serial = (new SerialNumberFormatter)
|
||||||
->setModel($invoice)
|
->setModel($invoice)
|
||||||
->setCompany($invoice->company_id)
|
->setCompany($invoice->company_id)
|
||||||
->setCustomer($invoice->customer_id)
|
->setCustomer($invoice->customer_id)
|
||||||
@@ -46,6 +46,16 @@ class CloneInvoiceController extends Controller
|
|||||||
|
|
||||||
$exchange_rate = $invoice->exchange_rate;
|
$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([
|
$newInvoice = Invoice::create([
|
||||||
'invoice_date' => $date->format('Y-m-d'),
|
'invoice_date' => $date->format('Y-m-d'),
|
||||||
'due_date' => $due_date,
|
'due_date' => $due_date,
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ namespace App\Http\Controllers\V1\Admin\Invoice;
|
|||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\Invoice;
|
use App\Models\Invoice;
|
||||||
|
use App\Space\PdfTemplateUtils;
|
||||||
|
use Illuminate\Auth\Access\AuthorizationException;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class InvoiceTemplatesController extends Controller
|
class InvoiceTemplatesController extends Controller
|
||||||
@@ -11,13 +14,16 @@ class InvoiceTemplatesController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Handle the incoming request.
|
* Handle the incoming request.
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Http\Response
|
*
|
||||||
|
* @return JsonResponse
|
||||||
|
*
|
||||||
|
* @throws AuthorizationException
|
||||||
*/
|
*/
|
||||||
public function __invoke(Request $request)
|
public function __invoke(Request $request)
|
||||||
{
|
{
|
||||||
$this->authorize('viewAny', Invoice::class);
|
$this->authorize('viewAny', Invoice::class);
|
||||||
|
|
||||||
$invoiceTemplates = Invoice::invoiceTemplates();
|
$invoiceTemplates = PdfTemplateUtils::getFormattedTemplates('invoice');
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'invoiceTemplates' => $invoiceTemplates,
|
'invoiceTemplates' => $invoiceTemplates,
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ class MailConfigurationController extends Controller
|
|||||||
'mail_mailgun_secret' => config('services.mailgun.secret'),
|
'mail_mailgun_secret' => config('services.mailgun.secret'),
|
||||||
'mail_ses_key' => config('services.ses.key'),
|
'mail_ses_key' => config('services.ses.key'),
|
||||||
'mail_ses_secret' => config('services.ses.secret'),
|
'mail_ses_secret' => config('services.ses.secret'),
|
||||||
|
'mail_ses_region' => config('services.ses.region'),
|
||||||
];
|
];
|
||||||
|
|
||||||
return response()->json($MailData);
|
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;
|
namespace App\Http\Controllers\V1\Admin\Update;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\Setting;
|
|
||||||
use App\Space\Updater;
|
use App\Space\Updater;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
|
|
||||||
class CheckVersionController extends Controller
|
class CheckVersionController extends Controller
|
||||||
{
|
{
|
||||||
@@ -26,7 +26,8 @@ class CheckVersionController extends Controller
|
|||||||
set_time_limit(600); // 10 minutes
|
set_time_limit(600); // 10 minutes
|
||||||
|
|
||||||
$channel = $request->get('channel', 'stable');
|
$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);
|
return response()->json($response);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class AppDomainController extends Controller
|
|||||||
{
|
{
|
||||||
Artisan::call('optimize:clear');
|
Artisan::call('optimize:clear');
|
||||||
|
|
||||||
$environmentManager = new EnvironmentManager();
|
$environmentManager = new EnvironmentManager;
|
||||||
|
|
||||||
$results = $environmentManager->saveDomainVariables($request);
|
$results = $environmentManager->saveDomainVariables($request);
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,10 @@ class DatabaseConfigurationController extends Controller
|
|||||||
$results = $this->environmentManager->saveDatabaseVariables($request);
|
$results = $this->environmentManager->saveDatabaseVariables($request);
|
||||||
|
|
||||||
if (array_key_exists('success', $results)) {
|
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('optimize:clear');
|
||||||
Artisan::call('config:clear');
|
Artisan::call('config:clear');
|
||||||
Artisan::call('cache:clear');
|
Artisan::call('cache:clear');
|
||||||
|
|||||||
@@ -38,6 +38,15 @@ class CompanySettingRequest extends FormRequest
|
|||||||
'carbon_date_format' => [
|
'carbon_date_format' => [
|
||||||
'required',
|
'required',
|
||||||
],
|
],
|
||||||
|
'moment_time_format' => [
|
||||||
|
'required',
|
||||||
|
],
|
||||||
|
'carbon_time_format' => [
|
||||||
|
'required',
|
||||||
|
],
|
||||||
|
'invoice_use_time' => [
|
||||||
|
'required',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ class InvoicesRequest extends FormRequest
|
|||||||
return $rules;
|
return $rules;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getInvoicePayload()
|
public function getInvoicePayload(): array
|
||||||
{
|
{
|
||||||
$company_currency = CompanySetting::getSetting('currency', $this->header('company'));
|
$company_currency = CompanySetting::getSetting('currency', $this->header('company'));
|
||||||
$current_currency = $this->currency_id;
|
$current_currency = $this->currency_id;
|
||||||
@@ -127,6 +127,8 @@ class InvoicesRequest extends FormRequest
|
|||||||
'tax_per_item' => CompanySetting::getSetting('tax_per_item', $this->header('company')) ?? 'NO ',
|
'tax_per_item' => CompanySetting::getSetting('tax_per_item', $this->header('company')) ?? 'NO ',
|
||||||
'discount_per_item' => CompanySetting::getSetting('discount_per_item', $this->header('company')) ?? 'NO',
|
'discount_per_item' => CompanySetting::getSetting('discount_per_item', $this->header('company')) ?? 'NO',
|
||||||
'due_amount' => $this->total,
|
'due_amount' => $this->total,
|
||||||
|
'sent' => (bool) $this->sent ?? false,
|
||||||
|
'viewed' => (bool) $this->viewed ?? false,
|
||||||
'exchange_rate' => $exchange_rate,
|
'exchange_rate' => $exchange_rate,
|
||||||
'base_total' => $this->total * $exchange_rate,
|
'base_total' => $this->total * $exchange_rate,
|
||||||
'base_discount_val' => $this->discount_val * $exchange_rate,
|
'base_discount_val' => $this->discount_val * $exchange_rate,
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ class NotesRequest extends FormRequest
|
|||||||
'notes' => [
|
'notes' => [
|
||||||
'required',
|
'required',
|
||||||
],
|
],
|
||||||
|
'is_default' => [
|
||||||
|
'required',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
if ($this->isMethod('PUT')) {
|
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('type', TaxType::TYPE_GENERAL)
|
||||||
->where('company_id', $this->header('company')),
|
->where('company_id', $this->header('company')),
|
||||||
],
|
],
|
||||||
'percent' => [
|
'calculation_type' => [
|
||||||
'required',
|
'required',
|
||||||
|
Rule::in(['percentage', 'fixed']),
|
||||||
|
],
|
||||||
|
'percent' => [
|
||||||
|
'nullable',
|
||||||
|
'numeric',
|
||||||
|
],
|
||||||
|
'fixed_amount' => [
|
||||||
|
'nullable',
|
||||||
|
'numeric',
|
||||||
],
|
],
|
||||||
'description' => [
|
'description' => [
|
||||||
'nullable',
|
'nullable',
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class EstimateResource extends JsonResource
|
|||||||
'status' => $this->status,
|
'status' => $this->status,
|
||||||
'reference_number' => $this->reference_number,
|
'reference_number' => $this->reference_number,
|
||||||
'tax_per_item' => $this->tax_per_item,
|
'tax_per_item' => $this->tax_per_item,
|
||||||
|
'tax_included' => $this->tax_included,
|
||||||
'discount_per_item' => $this->discount_per_item,
|
'discount_per_item' => $this->discount_per_item,
|
||||||
'notes' => $this->getNotes(),
|
'notes' => $this->getNotes(),
|
||||||
'discount' => $this->discount,
|
'discount' => $this->discount,
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class InvoiceResource extends JsonResource
|
|||||||
'status' => $this->status,
|
'status' => $this->status,
|
||||||
'paid_status' => $this->paid_status,
|
'paid_status' => $this->paid_status,
|
||||||
'tax_per_item' => $this->tax_per_item,
|
'tax_per_item' => $this->tax_per_item,
|
||||||
|
'tax_included' => $this->tax_included,
|
||||||
'discount_per_item' => $this->discount_per_item,
|
'discount_per_item' => $this->discount_per_item,
|
||||||
'notes' => $this->notes,
|
'notes' => $this->notes,
|
||||||
'discount_type' => $this->discount_type,
|
'discount_type' => $this->discount_type,
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ class NoteResource extends JsonResource
|
|||||||
'type' => $this->type,
|
'type' => $this->type,
|
||||||
'name' => $this->name,
|
'name' => $this->name,
|
||||||
'notes' => $this->notes,
|
'notes' => $this->notes,
|
||||||
|
'is_default' => $this->is_default,
|
||||||
'company' => $this->when($this->company()->exists(), function () {
|
'company' => $this->when($this->company()->exists(), function () {
|
||||||
return new CompanyResource($this->company);
|
return new CompanyResource($this->company);
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ class RecurringInvoiceResource extends JsonResource
|
|||||||
'limit_date' => $this->limit_date,
|
'limit_date' => $this->limit_date,
|
||||||
'exchange_rate' => $this->exchange_rate,
|
'exchange_rate' => $this->exchange_rate,
|
||||||
'tax_per_item' => $this->tax_per_item,
|
'tax_per_item' => $this->tax_per_item,
|
||||||
|
'tax_included' => $this->tax_included,
|
||||||
'discount_per_item' => $this->discount_per_item,
|
'discount_per_item' => $this->discount_per_item,
|
||||||
'notes' => $this->notes,
|
'notes' => $this->notes,
|
||||||
'discount_type' => $this->discount_type,
|
'discount_type' => $this->discount_type,
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ class TaxResource extends JsonResource
|
|||||||
'name' => $this->name,
|
'name' => $this->name,
|
||||||
'amount' => $this->amount,
|
'amount' => $this->amount,
|
||||||
'percent' => $this->percent,
|
'percent' => $this->percent,
|
||||||
|
'calculation_type' => $this->calculation_type,
|
||||||
|
'fixed_amount' => $this->fixed_amount,
|
||||||
'compound_tax' => $this->compound_tax,
|
'compound_tax' => $this->compound_tax,
|
||||||
'base_amount' => $this->base_amount,
|
'base_amount' => $this->base_amount,
|
||||||
'currency_id' => $this->currency_id,
|
'currency_id' => $this->currency_id,
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ class TaxTypeResource extends JsonResource
|
|||||||
'id' => $this->id,
|
'id' => $this->id,
|
||||||
'name' => $this->name,
|
'name' => $this->name,
|
||||||
'percent' => $this->percent,
|
'percent' => $this->percent,
|
||||||
|
'fixed_amount' => $this->fixed_amount,
|
||||||
|
'calculation_type' => $this->calculation_type,
|
||||||
'type' => $this->type,
|
'type' => $this->type,
|
||||||
'compound_tax' => $this->compound_tax,
|
'compound_tax' => $this->compound_tax,
|
||||||
'collective_tax' => $this->collective_tax,
|
'collective_tax' => $this->collective_tax,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use Illuminate\Contracts\Queue\ShouldQueue;
|
|||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Spatie\Backup\Config\Config;
|
||||||
use Spatie\Backup\Tasks\Backup\BackupJobFactory;
|
use Spatie\Backup\Tasks\Backup\BackupJobFactory;
|
||||||
|
|
||||||
class CreateBackupJob implements ShouldQueue
|
class CreateBackupJob implements ShouldQueue
|
||||||
@@ -41,7 +42,8 @@ class CreateBackupJob implements ShouldQueue
|
|||||||
|
|
||||||
config(['backup.backup.destination.disks' => [$prefix.$fileDisk->driver]]);
|
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')) {
|
if (! defined('SIGINT')) {
|
||||||
$backupJob->disableSignals();
|
$backupJob->disableSignals();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -227,6 +227,9 @@ class Company extends Model implements HasMedia
|
|||||||
'fiscal_year' => '1-12',
|
'fiscal_year' => '1-12',
|
||||||
'carbon_date_format' => 'Y/m/d',
|
'carbon_date_format' => 'Y/m/d',
|
||||||
'moment_date_format' => 'YYYY/MM/DD',
|
'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',
|
'notification_email' => 'noreply@invoiceshelf.com',
|
||||||
'notify_invoice_viewed' => 'NO',
|
'notify_invoice_viewed' => 'NO',
|
||||||
'notify_estimate_viewed' => 'NO',
|
'notify_estimate_viewed' => 'NO',
|
||||||
|
|||||||
@@ -3,19 +3,18 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use App;
|
use App;
|
||||||
|
use App\Facades\PDF;
|
||||||
use App\Mail\SendEstimateMail;
|
use App\Mail\SendEstimateMail;
|
||||||
use App\Services\SerialNumberFormatter;
|
use App\Services\SerialNumberFormatter;
|
||||||
|
use App\Space\PdfTemplateUtils;
|
||||||
use App\Traits\GeneratesPdfTrait;
|
use App\Traits\GeneratesPdfTrait;
|
||||||
use App\Traits\HasCustomFieldsTrait;
|
use App\Traits\HasCustomFieldsTrait;
|
||||||
use Barryvdh\DomPDF\Facade\Pdf as PDF;
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||||
use Illuminate\Support\Facades\Storage;
|
|
||||||
use Illuminate\Support\Facades\Vite;
|
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Spatie\MediaLibrary\HasMedia;
|
use Spatie\MediaLibrary\HasMedia;
|
||||||
use Spatie\MediaLibrary\InteractsWithMedia;
|
use Spatie\MediaLibrary\InteractsWithMedia;
|
||||||
@@ -227,7 +226,7 @@ class Estimate extends Model implements HasMedia
|
|||||||
|
|
||||||
$estimate = self::create($data);
|
$estimate = self::create($data);
|
||||||
$estimate->unique_hash = Hashids::connection(Estimate::class)->encode($estimate->id);
|
$estimate->unique_hash = Hashids::connection(Estimate::class)->encode($estimate->id);
|
||||||
$serial = (new SerialNumberFormatter())
|
$serial = (new SerialNumberFormatter)
|
||||||
->setModel($estimate)
|
->setModel($estimate)
|
||||||
->setCompany($estimate->company_id)
|
->setCompany($estimate->company_id)
|
||||||
->setCustomer($estimate->customer_id)
|
->setCustomer($estimate->customer_id)
|
||||||
@@ -262,7 +261,7 @@ class Estimate extends Model implements HasMedia
|
|||||||
{
|
{
|
||||||
$data = $request->getEstimatePayload();
|
$data = $request->getEstimatePayload();
|
||||||
|
|
||||||
$serial = (new SerialNumberFormatter())
|
$serial = (new SerialNumberFormatter)
|
||||||
->setModel($this)
|
->setModel($this)
|
||||||
->setCompany($this->company_id)
|
->setCompany($this->company_id)
|
||||||
->setCustomer($request->customer_id)
|
->setCustomer($request->customer_id)
|
||||||
@@ -424,11 +423,14 @@ class Estimate extends Model implements HasMedia
|
|||||||
'taxes' => $taxes,
|
'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')) {
|
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()
|
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()
|
public function getInvoiceTemplateName()
|
||||||
{
|
{
|
||||||
$templateName = Str::replace('estimate', 'invoice', $this->template_name);
|
$templateName = Str::replace('estimate', 'invoice', $this->template_name);
|
||||||
|
|
||||||
$name = [];
|
$name = [];
|
||||||
|
|
||||||
foreach (Invoice::invoiceTemplates() as $template) {
|
foreach (PdfTemplateUtils::getFormattedTemplates('invoice') as $template) {
|
||||||
$name[] = $template['name'];
|
$name[] = $template['name'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,20 +3,18 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use App;
|
use App;
|
||||||
|
use App\Facades\PDF;
|
||||||
use App\Mail\SendInvoiceMail;
|
use App\Mail\SendInvoiceMail;
|
||||||
use App\Services\SerialNumberFormatter;
|
use App\Services\SerialNumberFormatter;
|
||||||
|
use App\Space\PdfTemplateUtils;
|
||||||
use App\Traits\GeneratesPdfTrait;
|
use App\Traits\GeneratesPdfTrait;
|
||||||
use App\Traits\HasCustomFieldsTrait;
|
use App\Traits\HasCustomFieldsTrait;
|
||||||
use Barryvdh\DomPDF\Facade\Pdf as PDF;
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
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 Nwidart\Modules\Facades\Module;
|
||||||
use Spatie\MediaLibrary\HasMedia;
|
use Spatie\MediaLibrary\HasMedia;
|
||||||
use Spatie\MediaLibrary\InteractsWithMedia;
|
use Spatie\MediaLibrary\InteractsWithMedia;
|
||||||
@@ -195,6 +193,12 @@ class Invoice extends Model implements HasMedia
|
|||||||
public function getFormattedInvoiceDateAttribute($value)
|
public function getFormattedInvoiceDateAttribute($value)
|
||||||
{
|
{
|
||||||
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
|
$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);
|
return Carbon::parse($this->invoice_date)->translatedFormat($dateFormat);
|
||||||
}
|
}
|
||||||
@@ -317,7 +321,7 @@ class Invoice extends Model implements HasMedia
|
|||||||
|
|
||||||
$invoice = Invoice::create($data);
|
$invoice = Invoice::create($data);
|
||||||
|
|
||||||
$serial = (new SerialNumberFormatter())
|
$serial = (new SerialNumberFormatter)
|
||||||
->setModel($invoice)
|
->setModel($invoice)
|
||||||
->setCompany($invoice->company_id)
|
->setCompany($invoice->company_id)
|
||||||
->setCustomer($invoice->customer_id)
|
->setCustomer($invoice->customer_id)
|
||||||
@@ -358,7 +362,7 @@ class Invoice extends Model implements HasMedia
|
|||||||
|
|
||||||
public function updateInvoice($request)
|
public function updateInvoice($request)
|
||||||
{
|
{
|
||||||
$serial = (new SerialNumberFormatter())
|
$serial = (new SerialNumberFormatter)
|
||||||
->setModel($this)
|
->setModel($this)
|
||||||
->setCompany($this->company_id)
|
->setCompany($this->company_id)
|
||||||
->setCustomer($request->customer_id)
|
->setCustomer($request->customer_id)
|
||||||
@@ -517,6 +521,7 @@ class Invoice extends Model implements HasMedia
|
|||||||
|
|
||||||
public static function createTaxes($invoice, $taxes)
|
public static function createTaxes($invoice, $taxes)
|
||||||
{
|
{
|
||||||
|
|
||||||
$exchange_rate = $invoice->exchange_rate;
|
$exchange_rate = $invoice->exchange_rate;
|
||||||
|
|
||||||
foreach ($taxes as $tax) {
|
foreach ($taxes as $tax) {
|
||||||
@@ -576,11 +581,14 @@ class Invoice extends Model implements HasMedia
|
|||||||
'taxes' => $taxes,
|
'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')) {
|
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()
|
public function getEmailAttachmentSetting()
|
||||||
@@ -651,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)
|
public function addInvoicePayment($amount)
|
||||||
{
|
{
|
||||||
$this->due_amount += $amount;
|
$this->due_amount += $amount;
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ class Payment extends Model implements HasMedia
|
|||||||
$payment = Payment::create($data);
|
$payment = Payment::create($data);
|
||||||
$payment->unique_hash = Hashids::connection(Payment::class)->encode($payment->id);
|
$payment->unique_hash = Hashids::connection(Payment::class)->encode($payment->id);
|
||||||
|
|
||||||
$serial = (new SerialNumberFormatter())
|
$serial = (new SerialNumberFormatter)
|
||||||
->setModel($payment)
|
->setModel($payment)
|
||||||
->setCompany($payment->company_id)
|
->setCompany($payment->company_id)
|
||||||
->setCustomer($payment->customer_id)
|
->setCustomer($payment->customer_id)
|
||||||
@@ -215,7 +215,7 @@ class Payment extends Model implements HasMedia
|
|||||||
$invoice->subtractInvoicePayment($request->amount);
|
$invoice->subtractInvoicePayment($request->amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
$serial = (new SerialNumberFormatter())
|
$serial = (new SerialNumberFormatter)
|
||||||
->setModel($this)
|
->setModel($this)
|
||||||
->setCompany($this->company_id)
|
->setCompany($this->company_id)
|
||||||
->setCustomer($request->customer_id)
|
->setCustomer($request->customer_id)
|
||||||
@@ -452,14 +452,14 @@ class Payment extends Model implements HasMedia
|
|||||||
{
|
{
|
||||||
$invoice = Invoice::find($transaction->invoice_id);
|
$invoice = Invoice::find($transaction->invoice_id);
|
||||||
|
|
||||||
$serial = (new SerialNumberFormatter())
|
$serial = (new SerialNumberFormatter)
|
||||||
->setModel(new Payment())
|
->setModel(new Payment)
|
||||||
->setCompany($invoice->company_id)
|
->setCompany($invoice->company_id)
|
||||||
->setCustomer($invoice->customer_id)
|
->setCustomer($invoice->customer_id)
|
||||||
->setNextNumbers();
|
->setNextNumbers();
|
||||||
|
|
||||||
$data['payment_number'] = $serial->getNextNumber();
|
$data['payment_number'] = $serial->getNextNumber();
|
||||||
$data['payment_date'] = Carbon::now()->format('y-m-d');
|
$data['payment_date'] = Carbon::now();
|
||||||
$data['amount'] = $invoice->total;
|
$data['amount'] = $invoice->total;
|
||||||
$data['invoice_id'] = $invoice->id;
|
$data['invoice_id'] = $invoice->id;
|
||||||
$data['payment_method_id'] = request()->payment_method_id;
|
$data['payment_method_id'] = request()->payment_method_id;
|
||||||
|
|||||||
@@ -307,9 +307,9 @@ class RecurringInvoice extends Model
|
|||||||
|
|
||||||
public function createInvoice()
|
public function createInvoice()
|
||||||
{
|
{
|
||||||
//get invoice_number
|
// get invoice_number
|
||||||
$serial = (new SerialNumberFormatter())
|
$serial = (new SerialNumberFormatter)
|
||||||
->setModel(new Invoice())
|
->setModel(new Invoice)
|
||||||
->setCompany($this->company_id)
|
->setCompany($this->company_id)
|
||||||
->setCustomer($this->customer_id)
|
->setCustomer($this->customer_id)
|
||||||
->setNextNumbers();
|
->setNextNumbers();
|
||||||
@@ -375,7 +375,7 @@ class RecurringInvoice extends Model
|
|||||||
$invoice->addCustomFields($customField);
|
$invoice->addCustomFields($customField);
|
||||||
}
|
}
|
||||||
|
|
||||||
//send automatically
|
// send automatically
|
||||||
if ($this->send_automatically == true) {
|
if ($this->send_automatically == true) {
|
||||||
$data = [
|
$data = [
|
||||||
'body' => CompanySetting::getSetting('invoice_mail_body', $this->company_id),
|
'body' => CompanySetting::getSetting('invoice_mail_body', $this->company_id),
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class Setting extends Model
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$set = new Setting();
|
$set = new Setting;
|
||||||
$set->option = $key;
|
$set->option = $key;
|
||||||
$set->value = $setting;
|
$set->value = $setting;
|
||||||
$set->save();
|
$set->save();
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class Tax extends Model
|
|||||||
return [
|
return [
|
||||||
'amount' => 'integer',
|
'amount' => 'integer',
|
||||||
'percent' => 'float',
|
'percent' => 'float',
|
||||||
|
'fixed_amount' => 'integer',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ class TaxType extends Model
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'percent' => 'float',
|
'percent' => 'float',
|
||||||
|
'fixed_amount' => 'integer',
|
||||||
'compound_tax' => 'boolean',
|
'compound_tax' => 'boolean',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,10 +65,10 @@ class Transaction extends Model
|
|||||||
|
|
||||||
public function isExpired()
|
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);
|
$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')) {
|
if ($checkExpiryLinks == 'YES' && $this->status == self::SUCCESS && Carbon::now()->format('Y-m-d') > $expiryDate->format('Y-m-d')) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class CustomerMailResetPasswordNotification extends ResetPassword
|
|||||||
{
|
{
|
||||||
$link = url("/{$notifiable->company->slug}/customer/reset/password/".$this->token);
|
$link = url("/{$notifiable->company->slug}/customer/reset/password/".$this->token);
|
||||||
|
|
||||||
return (new MailMessage())
|
return (new MailMessage)
|
||||||
->subject('Reset Password Notification')
|
->subject('Reset Password Notification')
|
||||||
->line('Hello! You are receiving this email because we received a password reset request for your account.')
|
->line('Hello! You are receiving this email because we received a password reset request for your account.')
|
||||||
->action('Reset Password', $link)
|
->action('Reset Password', $link)
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class MailResetPasswordNotification extends ResetPassword
|
|||||||
{
|
{
|
||||||
$link = url('/reset-password/'.$this->token);
|
$link = url('/reset-password/'.$this->token);
|
||||||
|
|
||||||
return (new MailMessage())
|
return (new MailMessage)
|
||||||
->subject('Reset Password Notification')
|
->subject('Reset Password Notification')
|
||||||
->line('Hello! You are receiving this email because we received a password reset request for your account.')
|
->line('Hello! You are receiving this email because we received a password reset request for your account.')
|
||||||
->action('Reset Password', $link)
|
->action('Reset Password', $link)
|
||||||
|
|||||||
@@ -46,6 +46,15 @@ class SettingsPolicy
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function managePDFConfig(User $user)
|
||||||
|
{
|
||||||
|
if ($user->isOwner()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public function manageSettings(User $user)
|
public function manageSettings(User $user)
|
||||||
{
|
{
|
||||||
if ($user->isOwner()) {
|
if ($user->isOwner()) {
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ use Illuminate\Support\Facades\Broadcast;
|
|||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
use Silber\Bouncer\Database\Models as BouncerModels;
|
use Silber\Bouncer\Database\Models as BouncerModels;
|
||||||
use Silber\Bouncer\Database\Role;
|
use Silber\Bouncer\Database\Role;
|
||||||
|
use View;
|
||||||
|
|
||||||
class AppServiceProvider extends ServiceProvider
|
class AppServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
@@ -58,8 +59,16 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
|
|
||||||
Gate::policy(Role::class, RolePolicy::class);
|
Gate::policy(Role::class, RolePolicy::class);
|
||||||
|
|
||||||
|
View::addNamespace('pdf_templates', storage_path('app/templates/pdf'));
|
||||||
|
|
||||||
$this->bootAuth();
|
$this->bootAuth();
|
||||||
$this->bootBroadcast();
|
$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()
|
public function addMenus()
|
||||||
{
|
{
|
||||||
//main menu
|
// main menu
|
||||||
\Menu::make('main_menu', function ($menu) {
|
\Menu::make('main_menu', function ($menu) {
|
||||||
foreach (config('invoiceshelf.main_menu') as $data) {
|
foreach (config('invoiceshelf.main_menu') as $data) {
|
||||||
$this->generateMenu($menu, $data);
|
$this->generateMenu($menu, $data);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
//setting menu
|
// setting menu
|
||||||
\Menu::make('setting_menu', function ($menu) {
|
\Menu::make('setting_menu', function ($menu) {
|
||||||
foreach (config('invoiceshelf.setting_menu') as $data) {
|
foreach (config('invoiceshelf.setting_menu') as $data) {
|
||||||
$this->generateMenu($menu, $data);
|
$this->generateMenu($menu, $data);
|
||||||
@@ -118,6 +127,7 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
Gate::define('manage backups', [SettingsPolicy::class, 'manageBackups']);
|
Gate::define('manage backups', [SettingsPolicy::class, 'manageBackups']);
|
||||||
Gate::define('manage file disk', [SettingsPolicy::class, 'manageFileDisk']);
|
Gate::define('manage file disk', [SettingsPolicy::class, 'manageFileDisk']);
|
||||||
Gate::define('manage email config', [SettingsPolicy::class, 'manageEmailConfig']);
|
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('manage notes', [NotePolicy::class, 'manageNotes']);
|
||||||
Gate::define('view notes', [NotePolicy::class, 'viewNotes']);
|
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;
|
namespace App\Rules\Backup;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
use Illuminate\Contracts\Validation\ValidationRule;
|
use Illuminate\Contracts\Validation\ValidationRule;
|
||||||
|
|
||||||
class BackupDisk implements ValidationRule
|
class BackupDisk implements ValidationRule
|
||||||
@@ -18,8 +19,6 @@ class BackupDisk implements ValidationRule
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Run the validation rule.
|
* Run the validation rule.
|
||||||
*
|
|
||||||
* @param \Closure $fail
|
|
||||||
*/
|
*/
|
||||||
public function validate(string $attribute, mixed $value, Closure $fail): void
|
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class Module
|
|||||||
{
|
{
|
||||||
static::$scripts[$name] = $path;
|
static::$scripts[$name] = $path;
|
||||||
|
|
||||||
return new static();
|
return new static;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -50,7 +50,7 @@ class Module
|
|||||||
{
|
{
|
||||||
static::$styles[$name] = $path;
|
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\DiskEnvironmentRequest;
|
||||||
use App\Http\Requests\DomainEnvironmentRequest;
|
use App\Http\Requests\DomainEnvironmentRequest;
|
||||||
use App\Http\Requests\MailEnvironmentRequest;
|
use App\Http\Requests\MailEnvironmentRequest;
|
||||||
|
use App\Http\Requests\PDFConfigurationRequest;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Support\Facades\Artisan;
|
use Illuminate\Support\Facades\Artisan;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
@@ -94,7 +95,6 @@ class EnvironmentManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
return $str;
|
return $str;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -104,14 +104,22 @@ class EnvironmentManager
|
|||||||
*/
|
*/
|
||||||
public function saveDatabaseVariables(DatabaseEnvironmentRequest $request)
|
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 = [
|
$dbEnv = [
|
||||||
'APP_URL' => $request->get('app_url'),
|
'APP_URL' => $appUrl,
|
||||||
'APP_LOCALE' => $request->get('app_locale'),
|
'APP_LOCALE' => $request->get('app_locale'),
|
||||||
'DB_CONNECTION' => $request->get('database_connection'),
|
'DB_CONNECTION' => $request->get('database_connection'),
|
||||||
'SANCTUM_STATEFUL_DOMAINS' => $request->get('app_domain'),
|
'SESSION_DOMAIN' => $sessionDomain,
|
||||||
'SESSION_DOMAIN' => explode(':', $request->get('app_domain'))[0],
|
|
||||||
];
|
];
|
||||||
|
if ($sanctumDomain !== null) {
|
||||||
|
$dbEnv['SANCTUM_STATEFUL_DOMAINS'] = $sanctumDomain;
|
||||||
|
}
|
||||||
if ($dbEnv['DB_CONNECTION'] != 'sqlite') {
|
if ($dbEnv['DB_CONNECTION'] != 'sqlite') {
|
||||||
if ($request->has('database_username') && $request->has('database_password')) {
|
if ($request->has('database_username') && $request->has('database_password')) {
|
||||||
$dbEnv['DB_HOST'] = $request->get('database_hostname');
|
$dbEnv['DB_HOST'] = $request->get('database_hostname');
|
||||||
@@ -225,7 +233,6 @@ class EnvironmentManager
|
|||||||
try {
|
try {
|
||||||
|
|
||||||
$this->updateEnv($mailEnv);
|
$this->updateEnv($mailEnv);
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
return [
|
return [
|
||||||
'error' => 'mail_variables_save_error',
|
'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
|
* Returns the mail configuration
|
||||||
*
|
*
|
||||||
@@ -296,6 +357,7 @@ class EnvironmentManager
|
|||||||
'MAIL_FROM_NAME' => $request->get('from_name'),
|
'MAIL_FROM_NAME' => $request->get('from_name'),
|
||||||
'SES_KEY' => $request->get('mail_ses_key'),
|
'SES_KEY' => $request->get('mail_ses_key'),
|
||||||
'SES_SECRET' => $request->get('mail_ses_secret'),
|
'SES_SECRET' => $request->get('mail_ses_secret'),
|
||||||
|
'SES_REGION' => $request->get('mail_ses_region'),
|
||||||
];
|
];
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -315,7 +377,6 @@ class EnvironmentManager
|
|||||||
];
|
];
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $mailEnv;
|
return $mailEnv;
|
||||||
@@ -333,7 +394,6 @@ class EnvironmentManager
|
|||||||
try {
|
try {
|
||||||
|
|
||||||
$this->updateEnv($diskEnv);
|
$this->updateEnv($diskEnv);
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
return [
|
return [
|
||||||
'error' => 'disk_variables_save_error',
|
'error' => 'disk_variables_save_error',
|
||||||
@@ -410,10 +470,16 @@ class EnvironmentManager
|
|||||||
public function saveDomainVariables(DomainEnvironmentRequest $request)
|
public function saveDomainVariables(DomainEnvironmentRequest $request)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->updateEnv([
|
[$sanctumDomain, $sessionDomain] = $this->getDomains(
|
||||||
'SANCTUM_STATEFUL_DOMAINS' => $request->get('app_domain'),
|
$request->get('app_domain')
|
||||||
'SESSION_DOMAIN' => explode(':', $request->get('app_domain'))[0],
|
);
|
||||||
]);
|
$domainEnv = [
|
||||||
|
'SESSION_DOMAIN' => $sessionDomain,
|
||||||
|
];
|
||||||
|
if ($sanctumDomain !== null) {
|
||||||
|
$domainEnv['SANCTUM_STATEFUL_DOMAINS'] = $sanctumDomain;
|
||||||
|
}
|
||||||
|
$this->updateEnv($domainEnv);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
return [
|
return [
|
||||||
'error' => 'domain_verification_failed',
|
'error' => 'domain_verification_failed',
|
||||||
@@ -449,9 +515,29 @@ class EnvironmentManager
|
|||||||
}
|
}
|
||||||
$formatted .= $current.$this->delimiter;
|
$formatted .= $current.$this->delimiter;
|
||||||
$previous = $current;
|
$previous = $current;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
file_put_contents($this->envPath, trim($formatted));
|
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);
|
File::makeDirectory($temp_extract_dir);
|
||||||
}
|
}
|
||||||
// Unzip the file
|
// Unzip the file
|
||||||
$zip = new ZipArchive();
|
$zip = new ZipArchive;
|
||||||
|
|
||||||
if ($zip->open($zip_file_path)) {
|
if ($zip->open($zip_file_path)) {
|
||||||
$zip->extractTo($temp_extract_dir);
|
$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);
|
File::makeDirectory($temp_extract_dir);
|
||||||
}
|
}
|
||||||
// Unzip the file
|
// Unzip the file
|
||||||
$zip = new ZipArchive();
|
$zip = new ZipArchive;
|
||||||
|
|
||||||
if ($zip->open($zip_file_path)) {
|
if ($zip->open($zip_file_path)) {
|
||||||
$zip->extractTo($temp_extract_dir);
|
$zip->extractTo($temp_extract_dir);
|
||||||
|
|||||||
@@ -111,9 +111,9 @@ trait GeneratesPdfTrait
|
|||||||
public function getFieldsArray()
|
public function getFieldsArray()
|
||||||
{
|
{
|
||||||
$customer = $this->customer;
|
$customer = $this->customer;
|
||||||
$shippingAddress = $customer->shippingAddress ?? new Address();
|
$shippingAddress = $customer->shippingAddress ?? new Address;
|
||||||
$billingAddress = $customer->billingAddress ?? new Address();
|
$billingAddress = $customer->billingAddress ?? new Address;
|
||||||
$companyAddress = $this->company->address ?? new Address();
|
$companyAddress = $this->company->address ?? new Address;
|
||||||
|
|
||||||
$fields = [
|
$fields = [
|
||||||
'{SHIPPING_ADDRESS_NAME}' => $shippingAddress->name,
|
'{SHIPPING_ADDRESS_NAME}' => $shippingAddress->name,
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||||
\Illuminate\Auth\Middleware\Authorize::class,
|
\Illuminate\Auth\Middleware\Authorize::class,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
})
|
})
|
||||||
->withExceptions(function (Exceptions $exceptions) {
|
->withExceptions(function (Exceptions $exceptions) {
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -5,4 +5,5 @@ return [
|
|||||||
App\Providers\RouteServiceProvider::class,
|
App\Providers\RouteServiceProvider::class,
|
||||||
App\Providers\DropboxServiceProvider::class,
|
App\Providers\DropboxServiceProvider::class,
|
||||||
App\Providers\ViewServiceProvider::class,
|
App\Providers\ViewServiceProvider::class,
|
||||||
|
App\Providers\PDFServiceProvider::class,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -9,40 +9,41 @@
|
|||||||
"type": "project",
|
"type": "project",
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.2",
|
"php": "^8.2",
|
||||||
"aws/aws-sdk-php": "^3.297",
|
"aws/aws-sdk-php": "^3.336",
|
||||||
"barryvdh/laravel-dompdf": "^2.1",
|
"barryvdh/laravel-dompdf": "^v3.0",
|
||||||
"doctrine/dbal": "^4.0",
|
"doctrine/dbal": "^4.2",
|
||||||
"dragonmantank/cron-expression": "^3.3",
|
"dragonmantank/cron-expression": "^v3.4",
|
||||||
"guzzlehttp/guzzle": "^7.2",
|
"guzzlehttp/guzzle": "^7.9",
|
||||||
|
"gotenberg/gotenberg-php": "^2.8",
|
||||||
"invoiceshelf/modules": "^1.0.0",
|
"invoiceshelf/modules": "^1.0.0",
|
||||||
"jasonmccreary/laravel-test-assertions": "^2.4",
|
"jasonmccreary/laravel-test-assertions": "^v2.4",
|
||||||
"laravel/framework": "^11.3",
|
"laravel/framework": "^12.0",
|
||||||
"laravel/helpers": "^1.7",
|
"laravel/helpers": "^1.7",
|
||||||
"laravel/sanctum": "^4.0",
|
"laravel/sanctum": "^4.0",
|
||||||
"laravel/tinker": "^2.9",
|
"laravel/tinker": "^2.10.1",
|
||||||
"laravel/ui": "^4.4",
|
"laravel/ui": "^4.6",
|
||||||
"lavary/laravel-menu": "^1.8",
|
"lavary/laravel-menu": "^1.8",
|
||||||
"league/flysystem-aws-s3-v3": "^3.23",
|
"league/flysystem-aws-s3-v3": "^3.29",
|
||||||
"predis/predis": "^2.2",
|
"predis/predis": "^2.3",
|
||||||
"silber/bouncer": "v1.0.2",
|
"silber/bouncer": "v1.0.3",
|
||||||
"spatie/flysystem-dropbox": "^3.0",
|
"spatie/flysystem-dropbox": "^3.0",
|
||||||
"spatie/laravel-backup": "^8.6",
|
"spatie/laravel-backup": "^9.2.9",
|
||||||
"spatie/laravel-medialibrary": "^11.3",
|
"spatie/laravel-medialibrary": "^11.11",
|
||||||
"vinkla/hashids": "^12.0"
|
"vinkla/hashids": "^13.0.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
|
"barryvdh/laravel-ide-helper": "^3.5",
|
||||||
|
"beyondcode/laravel-dump-server": "^2.0.0",
|
||||||
"fakerphp/faker": "^1.23",
|
"fakerphp/faker": "^1.23",
|
||||||
"laravel/pint": "^1.13",
|
"laravel/pint": "^1.13",
|
||||||
"laravel/sail": "^1.26",
|
"laravel/sail": "^1.41",
|
||||||
"mockery/mockery": "^1.6",
|
"mockery/mockery": "^1.6",
|
||||||
"nunomaduro/collision": "^8.0",
|
"nunomaduro/collision": "^8.6",
|
||||||
"phpunit/phpunit": "^10.5",
|
"pestphp/pest": "^3.8",
|
||||||
"spatie/laravel-ignition": "^2.4",
|
"pestphp/pest-plugin-faker": "^3.0",
|
||||||
"barryvdh/laravel-ide-helper": "^3.0",
|
"pestphp/pest-plugin-laravel": "^3.1",
|
||||||
"beyondcode/laravel-dump-server": "^2.0",
|
"phpunit/phpunit": "^11.5.3",
|
||||||
"pestphp/pest": "^2.33",
|
"spatie/laravel-ignition": "^2.9"
|
||||||
"pestphp/pest-plugin-faker": "^2.0",
|
|
||||||
"pestphp/pest-plugin-laravel": "^2.2"
|
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
@@ -60,31 +61,41 @@
|
|||||||
"Tests\\": "tests/"
|
"Tests\\": "tests/"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"minimum-stability": "dev",
|
|
||||||
"prefer-stable": true,
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"post-autoload-dump": [
|
"post-autoload-dump": [
|
||||||
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
|
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
|
||||||
"@php artisan package:discover --ansi"
|
"@php artisan package:discover --ansi"
|
||||||
],
|
],
|
||||||
|
"post-update-cmd": [
|
||||||
|
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
|
||||||
|
],
|
||||||
"post-root-package-install": [
|
"post-root-package-install": [
|
||||||
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
|
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
|
||||||
],
|
],
|
||||||
"post-create-project-cmd": [
|
"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": {
|
"config": {
|
||||||
"optimize-autoloader": true,
|
"optimize-autoloader": true,
|
||||||
"preferred-install": "dist",
|
"preferred-install": "dist",
|
||||||
"sort-packages": true,
|
"sort-packages": true,
|
||||||
"allow-plugins": {
|
"allow-plugins": {
|
||||||
"pestphp/pest-plugin": true
|
"pestphp/pest-plugin": true,
|
||||||
|
"php-http/discovery": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"extra": {
|
"minimum-stability": "stable",
|
||||||
"laravel": {
|
"prefer-stable": true
|
||||||
"dont-discover": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
3753
composer.lock
generated
3753
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,6 @@
|
|||||||
return [
|
return [
|
||||||
|
|
||||||
'backup' => [
|
'backup' => [
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The name of this application. You can use this name to monitor
|
* The name of this application. You can use this name to monitor
|
||||||
* the backups.
|
* the backups.
|
||||||
@@ -11,9 +10,7 @@ return [
|
|||||||
'name' => env('APP_NAME', 'laravel-backup'),
|
'name' => env('APP_NAME', 'laravel-backup'),
|
||||||
|
|
||||||
'source' => [
|
'source' => [
|
||||||
|
|
||||||
'files' => [
|
'files' => [
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The list of directories and files that will be included in the backup.
|
* 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.
|
* Determines if it should avoid unreadable folders.
|
||||||
*/
|
*/
|
||||||
'ignore_unreadable_directories' => false,
|
'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
|
* Out of the box Laravel-backup supplies
|
||||||
* Spatie\DbDumper\Compressors\GzipCompressor::class.
|
* Spatie\DbDumper\Compressors\GzipCompressor::class.
|
||||||
@@ -91,7 +95,53 @@ return [
|
|||||||
*/
|
*/
|
||||||
'database_dump_compressor' => null,
|
'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' => [
|
'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.
|
* The filename prefix used for the backup zip file.
|
||||||
@@ -110,24 +160,49 @@ return [
|
|||||||
* The directory where the temporary files will be stored.
|
* The directory where the temporary files will be stored.
|
||||||
*/
|
*/
|
||||||
'temporary_directory' => storage_path('app/backup-temp'),
|
'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'.
|
* 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
|
* 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' => [
|
||||||
|
|
||||||
'notifications' => [
|
'notifications' => [
|
||||||
\Spatie\Backup\Notifications\Notifications\BackupHasFailed::class => [],
|
\Spatie\Backup\Notifications\Notifications\BackupHasFailedNotification::class => ['mail'],
|
||||||
\Spatie\Backup\Notifications\Notifications\UnhealthyBackupWasFound::class => [],
|
\Spatie\Backup\Notifications\Notifications\UnhealthyBackupWasFoundNotification::class => ['mail'],
|
||||||
\Spatie\Backup\Notifications\Notifications\CleanupHasFailed::class => [],
|
\Spatie\Backup\Notifications\Notifications\CleanupHasFailedNotification::class => ['mail'],
|
||||||
\Spatie\Backup\Notifications\Notifications\BackupWasSuccessful::class => [],
|
\Spatie\Backup\Notifications\Notifications\BackupWasSuccessfulNotification::class => ['mail'],
|
||||||
\Spatie\Backup\Notifications\Notifications\HealthyBackupWasFound::class => [],
|
\Spatie\Backup\Notifications\Notifications\HealthyBackupWasFoundNotification::class => ['mail'],
|
||||||
\Spatie\Backup\Notifications\Notifications\CleanupWasSuccessful::class => [],
|
\Spatie\Backup\Notifications\Notifications\CleanupWasSuccessfulNotification::class => ['mail'],
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -156,7 +231,20 @@ return [
|
|||||||
'username' => null,
|
'username' => null,
|
||||||
|
|
||||||
'icon' => 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,
|
'strategy' => \Spatie\Backup\Tasks\Cleanup\Strategies\DefaultStrategy::class,
|
||||||
|
|
||||||
'default_strategy' => [
|
'default_strategy' => [
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The number of days for which backups must be kept.
|
* The number of days for which backups must be kept.
|
||||||
*/
|
*/
|
||||||
'keep_all_backups_for_days' => 7,
|
'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,
|
'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,
|
'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,
|
'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,
|
'keep_yearly_backups_for_years' => 2,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* After cleaning up the backups remove the oldest backup until
|
* After cleaning up the backups remove the oldest backup until
|
||||||
* this amount of megabytes has been reached.
|
* this amount of megabytes has been reached.
|
||||||
|
* Set null for unlimited size.
|
||||||
*/
|
*/
|
||||||
'delete_oldest_backups_when_using_more_megabytes_than' => 5000,
|
'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,
|
* If pdflib present in web server and auto or selected explicitly above,
|
||||||
* a real license code must exist!
|
* 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.
|
* html target media view which should be rendered into pdf.
|
||||||
|
|||||||
@@ -55,6 +55,18 @@ return [
|
|||||||
'driver' => 'local',
|
'driver' => 'local',
|
||||||
'root' => resource_path('views'),
|
'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' => '',
|
'group' => '',
|
||||||
'name' => 'Company information',
|
'name' => 'Company information',
|
||||||
'link' => '/admin/settings/company-info',
|
'link' => '/admin/settings/company-info',
|
||||||
'icon' => 'OfficeBuildingIcon',
|
'icon' => 'BuildingOfficeIcon',
|
||||||
'owner_only' => true,
|
'owner_only' => true,
|
||||||
'ability' => '',
|
'ability' => '',
|
||||||
'model' => '',
|
'model' => '',
|
||||||
@@ -151,7 +151,17 @@ return [
|
|||||||
'group' => '',
|
'group' => '',
|
||||||
'name' => 'Customization',
|
'name' => 'Customization',
|
||||||
'link' => '/admin/settings/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,
|
'owner_only' => true,
|
||||||
'ability' => '',
|
'ability' => '',
|
||||||
'model' => '',
|
'model' => '',
|
||||||
@@ -171,7 +181,7 @@ return [
|
|||||||
'group' => '',
|
'group' => '',
|
||||||
'name' => 'Exchange Rate Provider',
|
'name' => 'Exchange Rate Provider',
|
||||||
'link' => '/admin/settings/exchange-rate-provider',
|
'link' => '/admin/settings/exchange-rate-provider',
|
||||||
'icon' => 'CashIcon',
|
'icon' => 'BanknotesIcon',
|
||||||
'owner_only' => false,
|
'owner_only' => false,
|
||||||
'ability' => 'view-exchange-rate-provider',
|
'ability' => 'view-exchange-rate-provider',
|
||||||
'model' => ExchangeRateProvider::class,
|
'model' => ExchangeRateProvider::class,
|
||||||
@@ -221,7 +231,7 @@ return [
|
|||||||
'group' => '',
|
'group' => '',
|
||||||
'name' => 'Notes',
|
'name' => 'Notes',
|
||||||
'link' => '/admin/settings/notes',
|
'link' => '/admin/settings/notes',
|
||||||
'icon' => 'ClipboardCheckIcon',
|
'icon' => 'ClipboardDocumentCheckIcon',
|
||||||
'owner_only' => false,
|
'owner_only' => false,
|
||||||
'ability' => 'view-all-notes',
|
'ability' => 'view-all-notes',
|
||||||
'model' => Note::class,
|
'model' => Note::class,
|
||||||
@@ -231,7 +241,7 @@ return [
|
|||||||
'group' => '',
|
'group' => '',
|
||||||
'name' => 'Expense Category',
|
'name' => 'Expense Category',
|
||||||
'link' => '/admin/settings/expense-category',
|
'link' => '/admin/settings/expense-category',
|
||||||
'icon' => 'ClipboardListIcon',
|
'icon' => 'ClipboardDocumentListIcon',
|
||||||
'owner_only' => false,
|
'owner_only' => false,
|
||||||
'ability' => 'view-expense',
|
'ability' => 'view-expense',
|
||||||
'model' => Expense::class,
|
'model' => Expense::class,
|
||||||
@@ -241,7 +251,7 @@ return [
|
|||||||
'group' => '',
|
'group' => '',
|
||||||
'name' => 'Mail Configuration',
|
'name' => 'Mail Configuration',
|
||||||
'link' => '/admin/settings/mail-configuration',
|
'link' => '/admin/settings/mail-configuration',
|
||||||
'icon' => 'MailIcon',
|
'icon' => 'EnvelopeIcon',
|
||||||
'owner_only' => true,
|
'owner_only' => true,
|
||||||
'ability' => '',
|
'ability' => '',
|
||||||
'model' => '',
|
'model' => '',
|
||||||
@@ -261,7 +271,7 @@ return [
|
|||||||
'group' => '',
|
'group' => '',
|
||||||
'name' => 'Backup',
|
'name' => 'Backup',
|
||||||
'link' => '/admin/settings/backup',
|
'link' => '/admin/settings/backup',
|
||||||
'icon' => 'DatabaseIcon',
|
'icon' => 'CircleStackIcon',
|
||||||
'owner_only' => true,
|
'owner_only' => true,
|
||||||
'ability' => '',
|
'ability' => '',
|
||||||
'model' => '',
|
'model' => '',
|
||||||
@@ -271,7 +281,7 @@ return [
|
|||||||
'group' => '',
|
'group' => '',
|
||||||
'name' => 'Update App',
|
'name' => 'Update App',
|
||||||
'link' => '/admin/settings/update-app',
|
'link' => '/admin/settings/update-app',
|
||||||
'icon' => 'RefreshIcon',
|
'icon' => 'ArrowPathIcon',
|
||||||
'owner_only' => true,
|
'owner_only' => true,
|
||||||
'ability' => '',
|
'ability' => '',
|
||||||
'model' => '',
|
'model' => '',
|
||||||
@@ -366,7 +376,7 @@ return [
|
|||||||
'title' => 'navigation.modules',
|
'title' => 'navigation.modules',
|
||||||
'group' => 3,
|
'group' => 3,
|
||||||
'link' => '/admin/modules',
|
'link' => '/admin/modules',
|
||||||
'icon' => 'PuzzleIcon',
|
'icon' => 'PuzzlePieceIcon',
|
||||||
'name' => 'Modules',
|
'name' => 'Modules',
|
||||||
'owner_only' => true,
|
'owner_only' => true,
|
||||||
'ability' => '',
|
'ability' => '',
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ return [
|
|||||||
'-m 6', // for the slowest compression method in order to get the best compression.
|
'-m 6', // for the slowest compression method in order to get the best compression.
|
||||||
'-pass 10', // for maximizing the amount of analysis pass.
|
'-pass 10', // for maximizing the amount of analysis pass.
|
||||||
'-mt', // multithreading for some speed improvements.
|
'-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 => [
|
Spatie\ImageOptimizer\Optimizers\Avifenc::class => [
|
||||||
'-a cq-level=23', // constant quality level, lower values mean better quality and greater file size (0-63).
|
'-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/sass/module' => 'Resources/sass/module.scss',
|
||||||
'resources/scripts/stores/sample-store' => 'Resources/scripts/stores/sample-store.js',
|
'resources/scripts/stores/sample-store' => 'Resources/scripts/stores/sample-store.js',
|
||||||
'resources/scripts/views/SamplePage' => 'Resources/scripts/views/SamplePage.vue',
|
'resources/scripts/views/SamplePage' => 'Resources/scripts/views/SamplePage.vue',
|
||||||
'lang/en' => 'Lang/en.json',
|
'resources/locales/en' => 'Resources/locales/en.json',
|
||||||
'lang/locales' => 'Lang/locales.js',
|
'resources/locales/locales' => 'Resources/locales/locales.js',
|
||||||
'package' => 'package.json',
|
'package' => 'package.json',
|
||||||
'postcss.config' => 'postcss.config.js',
|
'postcss.config' => 'postcss.config.js',
|
||||||
'tailwind.config' => 'tailwind.config.js',
|
'tailwind.config' => 'tailwind.config.js',
|
||||||
@@ -63,8 +63,8 @@ return [
|
|||||||
'MODULE_NAMESPACE',
|
'MODULE_NAMESPACE',
|
||||||
'PROVIDER_NAMESPACE',
|
'PROVIDER_NAMESPACE',
|
||||||
],
|
],
|
||||||
'assets/scripts/module' => ['LOWER_NAME'],
|
'resources/scripts/module' => ['LOWER_NAME'],
|
||||||
'assets/scripts/stores/sample-store' => ['LOWER_NAME'],
|
'resources/scripts/stores/sample-store' => ['LOWER_NAME'],
|
||||||
'vite.config' => ['LOWER_NAME'],
|
'vite.config' => ['LOWER_NAME'],
|
||||||
],
|
],
|
||||||
'gitkeep' => true,
|
'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),
|
'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
|
public function definition(): array
|
||||||
{
|
{
|
||||||
$sequenceNumber = (new SerialNumberFormatter())
|
$sequenceNumber = (new SerialNumberFormatter)
|
||||||
->setModel(new Estimate())
|
->setModel(new Estimate)
|
||||||
->setCompany(User::find(1)->companies()->first()->id)
|
->setCompany(User::find(1)->companies()->first()->id)
|
||||||
->setNextNumbers();
|
->setNextNumbers();
|
||||||
|
|
||||||
@@ -93,6 +93,7 @@ class EstimateFactory extends Factory
|
|||||||
return $estimate['discount_type'] == 'percentage' ? (($estimate['discount_val'] * $estimate['total']) / 100) : $estimate['discount_val'];
|
return $estimate['discount_type'] == 'percentage' ? (($estimate['discount_val'] * $estimate['total']) / 100) : $estimate['discount_val'];
|
||||||
},
|
},
|
||||||
'tax_per_item' => 'YES',
|
'tax_per_item' => 'YES',
|
||||||
|
'tax_included' => false,
|
||||||
'discount_per_item' => 'No',
|
'discount_per_item' => 'No',
|
||||||
'tax' => $this->faker->randomDigitNotNull(),
|
'tax' => $this->faker->randomDigitNotNull(),
|
||||||
'notes' => $this->faker->text(80),
|
'notes' => $this->faker->text(80),
|
||||||
|
|||||||
@@ -78,8 +78,8 @@ class InvoiceFactory extends Factory
|
|||||||
*/
|
*/
|
||||||
public function definition(): array
|
public function definition(): array
|
||||||
{
|
{
|
||||||
$sequenceNumber = (new SerialNumberFormatter())
|
$sequenceNumber = (new SerialNumberFormatter)
|
||||||
->setModel(new Invoice())
|
->setModel(new Invoice)
|
||||||
->setCompany(User::find(1)->companies()->first()->id)
|
->setCompany(User::find(1)->companies()->first()->id)
|
||||||
->setNextNumbers();
|
->setNextNumbers();
|
||||||
|
|
||||||
@@ -93,6 +93,7 @@ class InvoiceFactory extends Factory
|
|||||||
'template_name' => 'invoice1',
|
'template_name' => 'invoice1',
|
||||||
'status' => Invoice::STATUS_DRAFT,
|
'status' => Invoice::STATUS_DRAFT,
|
||||||
'tax_per_item' => 'NO',
|
'tax_per_item' => 'NO',
|
||||||
|
'tax_included' => false,
|
||||||
'discount_per_item' => 'NO',
|
'discount_per_item' => 'NO',
|
||||||
'paid_status' => Invoice::STATUS_UNPAID,
|
'paid_status' => Invoice::STATUS_UNPAID,
|
||||||
'company_id' => User::find(1)->companies()->first()->id,
|
'company_id' => User::find(1)->companies()->first()->id,
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ class NoteFactory extends Factory
|
|||||||
'name' => $this->faker->word(),
|
'name' => $this->faker->word(),
|
||||||
'notes' => $this->faker->text(),
|
'notes' => $this->faker->text(),
|
||||||
'company_id' => User::find(1)->companies()->first()->id,
|
'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
|
public function definition(): array
|
||||||
{
|
{
|
||||||
$sequenceNumber = (new SerialNumberFormatter())
|
$sequenceNumber = (new SerialNumberFormatter)
|
||||||
->setModel(new Payment())
|
->setModel(new Payment)
|
||||||
->setCompany(User::find(1)->companies()->first()->id)
|
->setCompany(User::find(1)->companies()->first()->id)
|
||||||
->setNextNumbers();
|
->setNextNumbers();
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ class RecurringInvoiceFactory extends Factory
|
|||||||
'send_automatically' => false,
|
'send_automatically' => false,
|
||||||
'status' => $this->faker->randomElement(['COMPLETED', 'ON_HOLD', 'ACTIVE']),
|
'status' => $this->faker->randomElement(['COMPLETED', 'ON_HOLD', 'ACTIVE']),
|
||||||
'tax_per_item' => 'NO',
|
'tax_per_item' => 'NO',
|
||||||
|
'tax_included' => false,
|
||||||
'discount_per_item' => 'NO',
|
'discount_per_item' => 'NO',
|
||||||
'sub_total' => $this->faker->randomDigitNotNull(),
|
'sub_total' => $this->faker->randomDigitNotNull(),
|
||||||
'total' => $this->faker->randomDigitNotNull(),
|
'total' => $this->faker->randomDigitNotNull(),
|
||||||
|
|||||||
@@ -22,8 +22,10 @@ class TaxTypeFactory extends Factory
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'name' => $this->faker->word(),
|
'name' => $this->faker->word(),
|
||||||
|
'calculation_type' => 'percentage',
|
||||||
'company_id' => User::find(1)->companies()->first()->id,
|
'company_id' => User::find(1)->companies()->first()->id,
|
||||||
'percent' => $this->faker->numberBetween($min = 0, $max = 100),
|
'percent' => $this->faker->numberBetween($min = 0, $max = 100),
|
||||||
|
'fixed_amount' => null,
|
||||||
'description' => $this->faker->text(),
|
'description' => $this->faker->text(),
|
||||||
'compound_tax' => 0,
|
'compound_tax' => 0,
|
||||||
'collective_tax' => 0,
|
'collective_tax' => 0,
|
||||||
|
|||||||
@@ -24,7 +24,5 @@ class AddUserIdToExpensesTable extends Migration
|
|||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function down()
|
public function down() {}
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use Illuminate\Database\Migrations\Migration;
|
|||||||
use Illuminate\Database\Schema\Blueprint;
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
return new class() extends Migration
|
return new class extends Migration
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Run the migrations.
|
* 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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('tax_types', function (Blueprint $table) {
|
||||||
|
$table->enum('calculation_type', ['percentage', 'fixed'])->default('percentage')->after('name');
|
||||||
|
$table->integer('fixed_amount')->nullable()->after('percent');
|
||||||
|
$table->decimal('percent', 5, 2)->nullable()->change();
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('taxes', function (Blueprint $table) {
|
||||||
|
$table->enum('calculation_type', ['percentage', 'fixed'])->default('percentage')->after('name');
|
||||||
|
$table->integer('fixed_amount')->nullable()->after('percent');
|
||||||
|
$table->decimal('percent', 5, 2)->nullable()->change();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::table('tax_types', function (Blueprint $table) {
|
||||||
|
$table->dropColumn(['calculation_type', 'fixed_amount']);
|
||||||
|
$table->decimal('percent', 5, 2)->change();
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('taxes', function (Blueprint $table) {
|
||||||
|
$table->dropColumn(['calculation_type', 'fixed_amount']);
|
||||||
|
$table->decimal('percent', 5, 2)->change();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -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('notes', function (Blueprint $table) {
|
||||||
|
$table->boolean('is_default')->default(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('notes', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('is_default');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -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('invoice_items', function (Blueprint $table) {
|
||||||
|
$table->bigInteger('price')->change();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('invoice_items', function (Blueprint $table) {
|
||||||
|
$table->unsignedBigInteger('price')->change();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -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->boolean('tax_included')->default(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('invoices', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('tax_included');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -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('estimates', function (Blueprint $table) {
|
||||||
|
$table->boolean('tax_included')->default(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('estimates', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('tax_included');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -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('recurring_invoices', function (Blueprint $table) {
|
||||||
|
$table->boolean('tax_included')->default(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('recurring_invoices', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('tax_included');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user