Compare commits
511 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90cb83be90 | ||
|
|
1d2cfb2bd7 | ||
|
|
9b00ca926a | ||
|
|
c389fa7026 | ||
|
|
515756f08a | ||
|
|
50e79da171 | ||
|
|
b0d6a9ce5b | ||
|
|
480d18d5ba | ||
|
|
9c8d0ba3cd | ||
|
|
cf62014bc5 | ||
|
|
30c302d1b6 | ||
|
|
fef994b99e | ||
|
|
32bddeca44 | ||
|
|
d61a3d89b7 | ||
|
|
54a9e63851 | ||
|
|
94140c173a | ||
|
|
71d047dd53 | ||
|
|
f721cdbe64 | ||
|
|
60e21cc1be | ||
|
|
b41249eda6 | ||
|
|
51e111f973 | ||
|
|
897c134ee3 | ||
|
|
77700a6a0d | ||
|
|
136e8bf98b | ||
|
|
36c5d915f6 | ||
|
|
e63b3cb76a | ||
|
|
a5b0348da3 | ||
|
|
a272956df2 | ||
|
|
d619522e2a | ||
|
|
22178f9476 | ||
|
|
64ac8299e4 | ||
|
|
ff888e8cf2 | ||
|
|
b65c501f02 | ||
|
|
f78443e6d9 | ||
|
|
5159288bb8 | ||
|
|
4e04e2af28 | ||
|
|
d3e44b779c | ||
|
|
1d49f1fe21 | ||
|
|
f395434f2a | ||
|
|
c975045c35 | ||
|
|
ec70871181 | ||
|
|
c90eac806f | ||
|
|
0ef5eb24d8 | ||
|
|
b645356122 | ||
|
|
f3927861bc | ||
|
|
20f8c7e1e3 | ||
|
|
2bdb26dd06 | ||
|
|
191b4f3f5d | ||
|
|
7dc1929430 | ||
|
|
71acb1189e | ||
|
|
5630193dfe | ||
|
|
2f7e91b46d | ||
|
|
7fdc8e0d00 | ||
|
|
3fed6b7771 | ||
|
|
dd2d10a54f | ||
|
|
ba0260cff5 | ||
|
|
2d08738ae6 | ||
|
|
7184f0bcdc | ||
|
|
a553bb8dff | ||
|
|
cbac71435e | ||
|
|
98d62fa340 | ||
|
|
49ec098b11 | ||
|
|
79179b923e | ||
|
|
64e7b1d4df | ||
|
|
b4e1067b15 | ||
|
|
d659d6dd90 | ||
|
|
fb294a7f7e | ||
|
|
eaaf954244 | ||
|
|
be4f184d97 | ||
|
|
10f1ac93ac | ||
|
|
e6915f36a5 | ||
|
|
ce18449cc7 | ||
|
|
cd421c4558 | ||
|
|
25dca51b5e | ||
|
|
3d69498c6e | ||
|
|
2aa850439c | ||
|
|
4756dd58f9 | ||
|
|
6d4d639c65 | ||
|
|
d08b6e874f | ||
|
|
65cf2752a6 | ||
|
|
d911c24421 | ||
|
|
3afc70bfec | ||
|
|
e3637197d7 | ||
|
|
3542d7fffb | ||
|
|
7fce31e82e | ||
|
|
59a1596732 | ||
|
|
63eeb28bc4 | ||
|
|
60e0154ac6 | ||
|
|
b94e4c602e | ||
|
|
c608336c04 | ||
|
|
2e90506a5e | ||
|
|
94624e6719 | ||
|
|
84774d208c | ||
|
|
1cba176139 | ||
|
|
6c759a8844 | ||
|
|
a631681f73 | ||
|
|
e1f15277d6 | ||
|
|
7e4593cd89 | ||
|
|
d32ac5327c | ||
|
|
e72d8ecfa2 | ||
|
|
9a5e429942 | ||
|
|
20ecad0774 | ||
|
|
044220d570 | ||
|
|
ac13be9e01 | ||
|
|
66117cd456 | ||
|
|
87eb98ca20 | ||
|
|
3b67e2a8c8 | ||
|
|
99237898de | ||
|
|
d5d917e3f8 | ||
|
|
14b67a2053 | ||
|
|
6546b41e26 | ||
|
|
b31fa80b5a | ||
|
|
e42964ffaf | ||
|
|
4c2a4fd142 | ||
|
|
8aa44f6607 | ||
|
|
3efb8cf252 | ||
|
|
aca292b737 | ||
|
|
7d268604b7 | ||
|
|
c488602b74 | ||
|
|
65795b1271 | ||
|
|
5baef3f69a | ||
|
|
0ff600fde7 | ||
|
|
67b2758d3f | ||
|
|
4e67b8b4cd | ||
|
|
5a1cdf8dfa | ||
|
|
5eb3f89650 | ||
|
|
8770f0d289 | ||
|
|
905da88348 | ||
|
|
e5716ae6c0 | ||
|
|
df32d61552 | ||
|
|
2abc7f570b | ||
|
|
4bc41fbf8e | ||
|
|
f594eea8e8 | ||
|
|
239c3e5d8d | ||
|
|
350de90af0 | ||
|
|
b99c40714f | ||
|
|
9197973eb4 | ||
|
|
79edec14c8 | ||
|
|
9962cc731b | ||
|
|
a3abff8766 | ||
|
|
825eb85d1c | ||
|
|
e50766f471 | ||
|
|
738bd0427e | ||
|
|
71146c9c94 | ||
|
|
d77e7622f7 | ||
|
|
3afeed6868 | ||
|
|
57185f1917 | ||
|
|
360f47754b | ||
|
|
cde03d132b | ||
|
|
fc4cbf335a | ||
|
|
623dd69182 | ||
|
|
248f95b295 | ||
|
|
a941cb387e | ||
|
|
b245147844 | ||
|
|
53e3b9a940 | ||
|
|
ee92e15668 | ||
|
|
fa5281f5e5 | ||
|
|
15020e201b | ||
|
|
5c48a83705 | ||
|
|
3cc3548f69 | ||
|
|
f7d1d95e3a | ||
|
|
8b7b4b138e | ||
|
|
d994994ac5 | ||
|
|
309cb5f1f7 | ||
|
|
692594fe64 | ||
|
|
68295d6bcc | ||
|
|
d84af6be3a | ||
|
|
39adbf3367 | ||
|
|
251043137d | ||
|
|
2f9749ce03 | ||
|
|
fdc9d6b4ff | ||
|
|
bcab38b364 | ||
|
|
c9bf7131b5 | ||
|
|
dbbd84b222 | ||
|
|
4234e50408 | ||
|
|
67a7778ce5 | ||
|
|
b7eb94548f | ||
|
|
ab2063ab2f | ||
|
|
20de4ede89 | ||
|
|
f4714e6603 | ||
|
|
f24c270139 | ||
|
|
18cde4bc5f | ||
|
|
95989143f9 | ||
|
|
02ef83213a | ||
|
|
12c885a377 | ||
|
|
c469ef6671 | ||
|
|
b26399713d | ||
|
|
14f8150546 | ||
|
|
b4b28385e5 | ||
|
|
e83eb6d657 | ||
|
|
0a793d898a | ||
|
|
de40281e22 | ||
|
|
00718d15df | ||
|
|
38ee8352d8 | ||
|
|
def4d1aa9f | ||
|
|
d35b5f66ad | ||
|
|
40bb6ad9ab | ||
|
|
39a58f909e | ||
|
|
aa9af574b6 | ||
|
|
b541e56071 | ||
|
|
c1ec590782 | ||
|
|
f3dcd2924c | ||
|
|
61d3737816 | ||
|
|
40005f3f25 | ||
|
|
7968b2bb5f | ||
|
|
ae52ea5971 | ||
|
|
fdaf513267 | ||
|
|
715998e67a | ||
|
|
033bb5c957 | ||
|
|
f9df879a3e | ||
|
|
1a1865045d | ||
|
|
6188b4d9cb | ||
|
|
fc35c00603 | ||
|
|
7aad5ae124 | ||
|
|
6f15447c5f | ||
|
|
9017ff4344 | ||
|
|
86993ba456 | ||
|
|
9ccfc96055 | ||
|
|
53e40a4e6a | ||
|
|
72d8c567bd | ||
|
|
587c3fd2e9 | ||
|
|
52d15bc388 | ||
|
|
ec6b3a00cc | ||
|
|
716b818a41 | ||
|
|
eb6cddeb08 | ||
|
|
12c928c5a0 | ||
|
|
a94808cf65 | ||
|
|
1254ba0ee0 | ||
|
|
8351d5a9fb | ||
|
|
a42597098f | ||
|
|
666a9398d3 | ||
|
|
4019ff08d6 | ||
|
|
31d8ecc2d0 | ||
|
|
d24d2e4b93 | ||
|
|
c4b147d7c8 | ||
|
|
cdc054a10f | ||
|
|
d2e9d3d8b4 | ||
|
|
cadd1a3b44 | ||
|
|
a86c5006af | ||
|
|
146df11c1f | ||
|
|
3190e5ac4d | ||
|
|
33c1c7e646 | ||
|
|
08efcc0342 | ||
|
|
d8a0462e1b | ||
|
|
3ab4d62bdf | ||
|
|
3e0b7a0659 | ||
|
|
5561b9dc05 | ||
|
|
69beb3b05d | ||
|
|
71d7b3217a | ||
|
|
6c54a7fa2b | ||
|
|
0749bc570c | ||
|
|
f39efb939b | ||
|
|
964be94407 | ||
|
|
303bfce10f | ||
|
|
8e223c83a5 | ||
|
|
c19cebef80 | ||
|
|
fac2c80fe7 | ||
|
|
b6e6518492 | ||
|
|
d5ea6fec60 | ||
|
|
0bdda6a529 | ||
|
|
38c017fdf8 | ||
|
|
aff75e1d59 | ||
|
|
0d1c279f64 | ||
|
|
0962e0e301 | ||
|
|
5d6b86b6c2 | ||
|
|
f6315cb640 | ||
|
|
5ecd4da153 | ||
|
|
d79c936875 | ||
|
|
ac49832ffd | ||
|
|
0621c215e7 | ||
|
|
2384e3d3e8 | ||
|
|
9afd6c6fc2 | ||
|
|
4c87d6864a | ||
|
|
42ada7e3f8 | ||
|
|
1e10bc3613 | ||
|
|
f34df17d36 | ||
|
|
a3029c4c74 | ||
|
|
bac27c8609 | ||
|
|
e0cda24c09 | ||
|
|
49242a1469 | ||
|
|
266abc8dd1 | ||
|
|
2e1a49a0cf | ||
|
|
7cbda60f46 | ||
|
|
fe563fd423 | ||
|
|
ab652c6ad0 | ||
|
|
4d4b318674 | ||
|
|
7f3ebffb36 | ||
|
|
1f81f03e45 | ||
|
|
608734b652 | ||
|
|
d7d40172cf | ||
|
|
406445dbb3 | ||
|
|
8ee1240cf7 | ||
|
|
d7f5657dda | ||
|
|
40cca935dd | ||
|
|
48fb1d0bff | ||
|
|
b35c20e511 | ||
|
|
fd443cc4c2 | ||
|
|
4b3d3d4460 | ||
|
|
bac1ed5e0c | ||
|
|
286a1a608f | ||
|
|
f45f1895a8 | ||
|
|
bee9547a87 | ||
|
|
382593a8a2 | ||
|
|
1f1acaa500 | ||
|
|
743dca7335 | ||
|
|
5a8148c09f | ||
|
|
5fdb21cbf9 | ||
|
|
59ad5724d5 | ||
|
|
ab620c00fc | ||
|
|
edd57e1ca5 | ||
|
|
485b46f235 | ||
|
|
674e5e253f | ||
|
|
d4724b417c | ||
|
|
eaa261dad8 | ||
|
|
db0c5fc578 | ||
|
|
e16900ef11 | ||
|
|
56a9bc2bdd | ||
|
|
33f70de451 | ||
|
|
d36cb2fafa | ||
|
|
eea9015cd3 | ||
|
|
aac4d8308d | ||
|
|
0b8b4a7505 | ||
|
|
602a15f648 | ||
|
|
0c7620a381 | ||
|
|
4323968240 | ||
|
|
3239a1c041 | ||
|
|
7ee7146d00 | ||
|
|
f11503567e | ||
|
|
5c61c1c0f4 | ||
|
|
30b5b33d0d | ||
|
|
38875f719a | ||
|
|
bb3603acd3 | ||
|
|
ca6e981f29 | ||
|
|
5a33c42278 | ||
|
|
fceb82ea51 | ||
|
|
43f38d9d84 | ||
|
|
6bb4bc3e69 | ||
|
|
927cd5a9a6 | ||
|
|
be00050615 | ||
|
|
3308df7fff | ||
|
|
f157b76b5f | ||
|
|
70efb7a7f2 | ||
|
|
fc26e77c95 | ||
|
|
c3a0c2a465 | ||
|
|
f6fced6b82 | ||
|
|
b4f83a5755 | ||
|
|
9d5c3bd951 | ||
|
|
8bb70c7e9a | ||
|
|
3f25b62ab6 | ||
|
|
d9a3b5453a | ||
|
|
be96bef3f0 | ||
|
|
89a243f88c | ||
|
|
9070dfe55b | ||
|
|
67044f9dd3 | ||
|
|
bde276ad67 | ||
|
|
65b30189b3 | ||
|
|
78f1df21ae | ||
|
|
17cddb902c | ||
|
|
bf7142ce43 | ||
|
|
ff455c8ed9 | ||
|
|
8020204c8d | ||
|
|
a500bfb583 | ||
|
|
6365187a1c | ||
|
|
9db94c9354 | ||
|
|
c4d2c31e06 | ||
|
|
1a554ea1e3 | ||
|
|
fa5cb690ba | ||
|
|
cf873bd197 | ||
|
|
9085a27361 | ||
|
|
2a8d2c7f5e | ||
|
|
ac11681073 | ||
|
|
1ab5bc1f6b | ||
|
|
6defaebf34 | ||
|
|
ae8457c588 | ||
|
|
e31ab11364 | ||
|
|
24daf955d8 | ||
|
|
c2dfc3055c | ||
|
|
b67b97430c | ||
|
|
0dfaf043bf | ||
|
|
6b4d8fba4a | ||
|
|
e702f24937 | ||
|
|
d9d949666f | ||
|
|
755c1f75d1 | ||
|
|
8a5621396b | ||
|
|
618743ea1a | ||
|
|
69e8f9a2f3 | ||
|
|
17c36b351d | ||
|
|
893d200e2c | ||
|
|
a9b511738f | ||
|
|
2ec4d6702c | ||
|
|
202d644d53 | ||
|
|
6ebc492454 | ||
|
|
22bc25253e | ||
|
|
5959eb8eb5 | ||
|
|
0524ac764d | ||
|
|
e6e690b32f | ||
|
|
e763a2b233 | ||
|
|
29ed0dd94b | ||
|
|
fbb5826178 | ||
|
|
0054616020 | ||
|
|
416da8b98d | ||
|
|
8ce81d15ba | ||
|
|
f222390ce8 | ||
|
|
49c4aa6a75 | ||
|
|
d6f2859d3d | ||
|
|
c0300c9e0d | ||
|
|
36290336c3 | ||
|
|
20c12533f8 | ||
|
|
34600446d9 | ||
|
|
96991da621 | ||
|
|
a4a757f288 | ||
|
|
0ee357e97a | ||
|
|
c1fa41ee30 | ||
|
|
195074a4b0 | ||
|
|
790a2a8a2d | ||
|
|
16623103cd | ||
|
|
3cc9a580e2 | ||
|
|
8c8a91ba4d | ||
|
|
caa3a34a4a | ||
|
|
4ee0512827 | ||
|
|
ba8c0eb035 | ||
|
|
3cc9eb0bec | ||
|
|
bbb72875e3 | ||
|
|
f2416b3b91 | ||
|
|
23571848d5 | ||
|
|
5b76c0a2d1 | ||
|
|
9943b89b2d | ||
|
|
5f998b0ea8 | ||
|
|
2a290765e9 | ||
|
|
b0e680ee5c | ||
|
|
989f114154 | ||
|
|
6a41952552 | ||
|
|
cb37141643 | ||
|
|
ff064367d6 | ||
|
|
ec2aedbba0 | ||
|
|
161f160ba7 | ||
|
|
36ab20ae3a | ||
|
|
3a45a48a1a | ||
|
|
e3dcf4aa47 | ||
|
|
91079b66d6 | ||
|
|
099e61343d | ||
|
|
72769deacc | ||
|
|
6656bf3ab2 | ||
|
|
1a5f89a905 | ||
|
|
6c5af5c593 | ||
|
|
a6a4eac8fd | ||
|
|
a83f441b98 | ||
|
|
003a5be614 | ||
|
|
c6a6926b7f | ||
|
|
5e7ee641a6 | ||
|
|
d41e0b4759 | ||
|
|
73da9d4acc | ||
|
|
14684c8ba7 | ||
|
|
9332d00b69 | ||
|
|
53fdf2434a | ||
|
|
ef1d55f1fe | ||
|
|
3b5bfd4eaf | ||
|
|
3733583eef | ||
|
|
89c21aa943 | ||
|
|
24d5d387ff | ||
|
|
b070782dd2 | ||
|
|
9db21e6f3c | ||
|
|
40621823dc | ||
|
|
a1d781c435 | ||
|
|
72831b0cef | ||
|
|
d543cfcc3f | ||
|
|
b818ff95b6 | ||
|
|
02fb5cede8 | ||
|
|
28d66567b4 | ||
|
|
b88ea8aa2c | ||
|
|
7546978f2b | ||
|
|
5f576a8852 | ||
|
|
fb62be10cb | ||
|
|
4a28d9d455 | ||
|
|
ecaf16f9bd | ||
|
|
736f1683d1 | ||
|
|
1a6e940888 | ||
|
|
4426be8303 | ||
|
|
ef3d1cb966 | ||
|
|
6c1bf7534a | ||
|
|
27f0fb9474 | ||
|
|
9dd1462d65 | ||
|
|
3e8565bd70 | ||
|
|
fd1c73c472 | ||
|
|
5e269ed07a | ||
|
|
31a0641943 | ||
|
|
4d471bccaf | ||
|
|
2d4769137f | ||
|
|
6fddd3a815 | ||
|
|
3a12da73bf | ||
|
|
fa45a02134 | ||
|
|
88b972be4b | ||
|
|
02b78055d4 | ||
|
|
16cc898e6a | ||
|
|
08be751aa4 | ||
|
|
738b531925 | ||
|
|
155f107238 | ||
|
|
ea8f94bb51 | ||
|
|
cd8b7649a1 | ||
|
|
48c3b5fcff | ||
|
|
a0b85b491f | ||
|
|
143b65ad9f | ||
|
|
ad973919ad | ||
|
|
6b21e0bb8e | ||
|
|
b8ec0f6cab | ||
|
|
1476f12a8d | ||
|
|
8677a72b7c | ||
|
|
dc49fad81a | ||
|
|
aefe133ba0 | ||
|
|
0fc66b0f02 | ||
|
|
5ccf7369ca |
385 changed files with 93789 additions and 60656 deletions
41
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
41
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: triage
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**What version of Invoice Ninja are you running? ie v4.5.25 / v5.0.30**
|
||||
|
||||
**What environment are you running?**
|
||||
Docker
|
||||
Shared Hosting
|
||||
ZIP
|
||||
Other
|
||||
|
||||
**Have you checked log files (storage/logs/) Please provide redacted output**
|
||||
|
||||
**Have you searched existing issues?**
|
||||
|
||||
**Have you reported this to Slack/forum before posting?**
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**Steps To Reproduce**
|
||||
Please list the steps to reproduce the issue
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
||||
Note: Before posting don't forget to check our "Troubleshooting" category in the [docs](https://invoiceninja.github.io/docs/self-host-troubleshooting/) (https://invoiceninja.github.io/docs/self-host-troubleshooting/).
|
||||
|
||||
**(v5) Can you replicate the issue on our demo site? https://demo.invoiceninja.com**
|
||||
24
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
24
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: feature request
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**What version of Invoice Ninja are you running? ie v4.5 / v5**
|
||||
|
||||
**What environment are you running?**
|
||||
Docker
|
||||
Shared Hosting
|
||||
ZIP
|
||||
Other
|
||||
|
||||
**Have you searched existing issues/requests?**
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your request/question.
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the request/question here.
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
|
|
@ -9,7 +9,10 @@
|
|||
/public/packages
|
||||
/public/vendor
|
||||
/resources/assets/bower
|
||||
/storage
|
||||
/storage/*.key
|
||||
/storage/documents
|
||||
/storage/import
|
||||
/storage/migrations
|
||||
/bootstrap/compiled.php
|
||||
/bootstrap/environment.php
|
||||
/vendor
|
||||
|
|
@ -39,3 +42,5 @@ tests/_support/_generated/
|
|||
/c3.php
|
||||
|
||||
_ide_helper.php
|
||||
storage/version.txt
|
||||
storage/framework/.DS_Store
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
language: php
|
||||
|
||||
services:
|
||||
- mysql
|
||||
|
||||
sudo: true
|
||||
|
||||
# Prevent tests from taking more than 50 minutes
|
||||
|
|
@ -7,6 +10,7 @@ group: deprecated-2017Q4
|
|||
|
||||
php:
|
||||
- 7.2
|
||||
- 7.3
|
||||
|
||||
addons:
|
||||
hosts:
|
||||
|
|
@ -26,7 +30,7 @@ env:
|
|||
before_install:
|
||||
# set GitHub token and update composer
|
||||
- if [ -n "$GH_TOKEN" ]; then composer config github-oauth.github.com ${GH_TOKEN}; fi;
|
||||
- composer self-update && composer -V
|
||||
- composer self-update 1.10.19 && composer -V
|
||||
# - export USE_ZEND_ALLOC=0
|
||||
- rvm use 1.9.3 --install --fuzzy
|
||||
|
||||
|
|
|
|||
4
CODE_OF_CONDUCT.md
Normal file
4
CODE_OF_CONDUCT.md
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# Invoice Ninja Code of Conduct
|
||||
|
||||
The development team has invested a tremendous amount of time and energy into this project. While we appreciate that bugs can be frustrating we ask that our community refrain from insults and snide remarks. We're happy to provide support to both our hosted and selfhosted communities but ask that feedback is always polite.
|
||||
|
||||
46
README.md
46
README.md
|
|
@ -9,39 +9,38 @@
|
|||
|
||||
## [Hosted](https://www.invoiceninja.com) | [Self-Hosted](https://www.invoiceninja.org)
|
||||
|
||||
### We're on Slack, join us at [slack.invoiceninja.com](http://slack.invoiceninja.com)
|
||||
### We're on Slack, join us at [slack.invoiceninja.com](http://slack.invoiceninja.com) or if you like [StackOverflow](https://stackoverflow.com/tags/invoice-ninja/)
|
||||
|
||||
All Pro and Enterprise features from the hosted app are included in the open-source code. We offer a $20 per year white-label license to remove our branding for personal use.
|
||||
Just make sure to add the `invoice-ninja` tag to your question.
|
||||
|
||||
#### Note: v5 is now tagged Stable! To upgrade from v4 you need to [install v5](https://invoiceninja.github.io/docs/self-host-installation/) as a separate app and then use the migration tool in the latest version of v4 on Settings > Account Management.
|
||||
|
||||
All Pro and Enterprise features from the hosted app are included in the open-source code. We offer a $30 per year white-label license to remove the Invoice Ninja branding from client facing parts of the app.
|
||||
|
||||
The self-host zip includes all third party libraries whereas downloading the code from GitHub requires using Composer to install the dependencies.
|
||||
|
||||
* [Features](https://www.invoiceninja.com/invoicing-features/)
|
||||
* [Videos](https://www.youtube.com/channel/UCXAHcBvhW05PDtWYIq7WDFA/videos)
|
||||
* [User Guide](https://invoice-ninja.readthedocs.io/en/latest/)
|
||||
* [User Guide](https://docs.invoiceninja.com/)
|
||||
* [Support Forum](https://www.invoiceninja.com/forums/forum/support/)
|
||||
* [Roadmap](https://trello.com/b/63BbiVVe/)
|
||||
* [StackOverflow](https://stackoverflow.com/tags/invoice-ninja/)
|
||||
|
||||
## Affiliates Programs
|
||||
* Referral Program (we pay you)
|
||||
* $100 per sign up paid over 3 years - [Learn more](https://www.invoiceninja.com/referral-program/)
|
||||
* White-Label Reseller (you pay us)
|
||||
* Hosted: $500 annually and either 10% of revenue or $1/user/month
|
||||
* Self-Hosted: Contact us for volume license pricing
|
||||
## Referral Program
|
||||
* Earn 50% of Pro & Enterprise Plans up to 4 years - [Learn more](https://www.invoiceninja.com/referral-program/)
|
||||
|
||||
## Mobile Apps
|
||||
* Current: [github.com/invoiceninja/flutter-mobile](https://github.com/invoiceninja/flutter-mobile)
|
||||
* [iPhone](https://itunes.apple.com/us/app/invoice-ninja/id1435514417?ls=1&mt=8)
|
||||
* [Android](https://play.google.com/store/apps/details?id=com.invoiceninja.flutter)
|
||||
* Legacy
|
||||
* [iPhone](https://itunes.apple.com/us/app/invoice-ninja/id1220337560?ls=1&mt=8)
|
||||
* [Android](https://play.google.com/store/apps/details?id=com.invoiceninja.invoiceninja)
|
||||
## Mobile App
|
||||
* [iPhone](https://itunes.apple.com/us/app/invoice-ninja/id1435514417?ls=1&mt=8)
|
||||
* [Android](https://play.google.com/store/apps/details?id=com.invoiceninja.flutter)
|
||||
* [Source Code](https://github.com/invoiceninja/flutter-mobile)
|
||||
|
||||
## Installation Options
|
||||
* [Ansible](https://github.com/invoiceninja/ansible-installer)
|
||||
* [Self-Host Zip](https://invoice-ninja.readthedocs.io/en/latest/install.html)
|
||||
* [Self-Host Zip](https://docs.invoiceninja.com/install.html)
|
||||
* [Docker File](https://hub.docker.com/r/invoiceninja/invoiceninja/)
|
||||
* [Cloudron](https://cloudron.io/store/com.invoiceninja.cloudronapp.html)
|
||||
* [Softaculous](https://www.softaculous.com/apps/ecommerce/Invoice_Ninja)
|
||||
* [Lando](https://github.com/invoiceninja/invoiceninja/issues/2880)
|
||||
* [Yunohost](https://github.com/YunoHost-Apps/invoiceninja_ynh)
|
||||
|
||||
## Recommended Providers
|
||||
* [Stripe](https://stripe.com/)
|
||||
|
|
@ -56,6 +55,8 @@ The self-host zip includes all third party libraries whereas downloading the cod
|
|||
## Third Party Modules
|
||||
* [Event Scheduler](https://github.com/cytech/Scheduler-InvoiceNinja)
|
||||
* [Manufacturer Module](https://github.com/dicarlosystems/manufacturer-invoiceninja)
|
||||
* [Point of Sale](https://github.com/dicarlosystems/pointofsale-invoiceninja)
|
||||
* [Invoice Design Import/Export](https://github.com/feyst/invoicedesignexport)
|
||||
|
||||
> Feel free to email us for help if you're working on a module, we're happy to provide developer support.
|
||||
|
||||
|
|
@ -65,7 +66,7 @@ The self-host zip includes all third party libraries whereas downloading the cod
|
|||
* [Shopping Cart](https://github.com/Scifabric/invoiceninjashoppingcart)
|
||||
|
||||
## Third Party Developers
|
||||
* [Some Techie](https://www.sometechie.com/customize-invoice-ninja/)
|
||||
* [Bold Compass](https://boldcompass.com/customize-invoice-ninja/)
|
||||
|
||||
## Contributing
|
||||
All contributors are welcome!
|
||||
|
|
@ -80,12 +81,15 @@ For information on how contribute to Invoice Ninja, please see our [contributing
|
|||
* [Troels Liebe Bentsen](https://github.com/tlbdk)
|
||||
* [Jeramy Simpson](https://github.com/JeramyMywork) - [MyWork](https://www.mywork.com.au)
|
||||
* [Sigitas Limontas](https://lt.linkedin.com/in/sigitaslimontas)
|
||||
* [Joshua Dwire](https://github.com/joshuadwire) - [Some Techie](https://www.sometechie.com)
|
||||
* [Joshua Dwire](https://github.com/joshuadwire) - [Bold Compass](https://boldcompass.com/)
|
||||
* [Holger Lösken](https://github.com/codedge) - [codedge](http://codedge.de)
|
||||
* [Samuel Laulhau](https://github.com/lalop) - [Lalop](http://lalop.co/)
|
||||
* [Alexander Zamponi](https://github.com/alexz707)
|
||||
* [Matthieu Calie](https://github.com/Matth--)
|
||||
* [Suhas Sunil Gaikwad](https://github.com/Suhas-Gaikwad) - (Security)
|
||||
* [Christopher Di Carlo](https://github.com/dicarlosystems) - [Di Carlo Systems Inc.](https://www.dicarlosystems.ca)
|
||||
* [Kristian Feldsam](https://github.com/feldsam) - [FeldHost™](https://www.feldhost.net)
|
||||
* [Suhas Sunil Gaikwad](https://github.com/Suhas-Gaikwad)
|
||||
* [Mike Skaggs](https://github.com/titan-fail)
|
||||
|
||||
## License
|
||||
Invoice Ninja is released under the Attribution Assurance License.
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@ class CalculatePayouts extends Command
|
|||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->info('Running CalculatePayouts...');
|
||||
$type = strtolower($this->option('type'));
|
||||
|
||||
switch ($type) {
|
||||
|
|
@ -61,7 +60,6 @@ class CalculatePayouts extends Command
|
|||
$userMap = [];
|
||||
|
||||
foreach ($servers as $server) {
|
||||
$this->info('Processing users: ' . $server->name);
|
||||
config(['database.default' => $server->name]);
|
||||
|
||||
$users = User::where('referral_code', '!=', '')
|
||||
|
|
@ -72,7 +70,6 @@ class CalculatePayouts extends Command
|
|||
}
|
||||
|
||||
foreach ($servers as $server) {
|
||||
$this->info('Processing companies: ' . $server->name);
|
||||
config(['database.default' => $server->name]);
|
||||
|
||||
$companies = Company::where('referral_code', '!=', '')
|
||||
|
|
@ -80,19 +77,27 @@ class CalculatePayouts extends Command
|
|||
->whereNotNull('payment_id')
|
||||
->get();
|
||||
|
||||
$this->info('User,Client,Date,Amount,Reference');
|
||||
|
||||
foreach ($companies as $company) {
|
||||
if (!isset($userMap[$company->referral_code])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$user = $userMap[$company->referral_code];
|
||||
$payment = $company->payment;
|
||||
|
||||
if ($payment) {
|
||||
$client = $payment->client;
|
||||
|
||||
$this->info("User: $user");
|
||||
$this->info("Client: " . $client->getDisplayName());
|
||||
|
||||
foreach ($client->payments as $payment) {
|
||||
$amount = $payment->getCompletedAmount();
|
||||
$this->info("Date: $payment->payment_date, Amount: $amount, Reference: $payment->transaction_reference");
|
||||
$this->info('"' . $user . '",' .
|
||||
'"' . $client->getDisplayName() . '",' .
|
||||
$payment->payment_date . ',' .
|
||||
$amount . ',' .
|
||||
$payment->transaction_reference
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ class ChargeRenewalInvoices extends Command
|
|||
$this->paymentService = $paymentService;
|
||||
}
|
||||
|
||||
public function fire()
|
||||
public function handle()
|
||||
{
|
||||
$this->info(date('r').' ChargeRenewalInvoices...');
|
||||
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ class CheckData extends Command
|
|||
protected $log = '';
|
||||
protected $isValid = true;
|
||||
|
||||
public function fire()
|
||||
public function handle()
|
||||
{
|
||||
$this->logMessage(date('Y-m-d h:i:s') . ' Running CheckData...');
|
||||
|
||||
|
|
@ -75,6 +75,8 @@ class CheckData extends Command
|
|||
config(['database.default' => $database]);
|
||||
}
|
||||
|
||||
$this->checkContacts();
|
||||
|
||||
if (! $this->option('client_id')) {
|
||||
$this->checkBlankInvoiceHistory();
|
||||
$this->checkPaidToDate();
|
||||
|
|
@ -82,21 +84,20 @@ class CheckData extends Command
|
|||
}
|
||||
|
||||
//$this->checkInvoices();
|
||||
$this->checkTranslations();
|
||||
$this->checkInvoiceBalances();
|
||||
$this->checkClientBalances();
|
||||
$this->checkContacts();
|
||||
$this->checkUserAccounts();
|
||||
//$this->checkLogoFiles();
|
||||
|
||||
if (! $this->option('client_id')) {
|
||||
$this->checkOAuth();
|
||||
$this->checkInvitations();
|
||||
//$this->checkInvitations();
|
||||
$this->checkAccountData();
|
||||
$this->checkLookupData();
|
||||
$this->checkFailedJobs();
|
||||
}
|
||||
|
||||
$this->checkTranslations();
|
||||
$this->logMessage('Done: ' . strtoupper($this->isValid ? RESULT_SUCCESS : RESULT_FAILURE));
|
||||
$errorEmail = env('ERROR_EMAIL');
|
||||
|
||||
|
|
@ -130,6 +131,7 @@ class CheckData extends Command
|
|||
$this->logMessage($language->locale . ' is invalid: ' . $text);
|
||||
}
|
||||
|
||||
/*
|
||||
preg_match('/(.script)/', strtolower($text), $matches);
|
||||
if (count($matches)) {
|
||||
foreach ($matches as $match) {
|
||||
|
|
@ -141,6 +143,7 @@ class CheckData extends Command
|
|||
break;
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -427,7 +430,7 @@ class CheckData extends Command
|
|||
$queueDB = config('queue.connections.database.connection');
|
||||
$count = DB::connection($queueDB)->table('failed_jobs')->count();
|
||||
|
||||
if ($count > 0) {
|
||||
if ($count > 25) {
|
||||
$this->isValid = false;
|
||||
}
|
||||
|
||||
|
|
@ -676,6 +679,8 @@ class CheckData extends Command
|
|||
|
||||
foreach ($clients as $client) {
|
||||
$this->logMessage("=== Company: {$client->company_id} Account:{$client->account_id} Client:{$client->id} Balance:{$client->balance} Actual Balance:{$client->actual_balance} ===");
|
||||
|
||||
/*
|
||||
$foundProblem = false;
|
||||
$lastBalance = 0;
|
||||
$lastAdjustment = 0;
|
||||
|
|
@ -838,6 +843,7 @@ class CheckData extends Command
|
|||
->where('id', $client->id)
|
||||
->update($data);
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class CreateLuisData extends Command
|
|||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function fire()
|
||||
public function handle()
|
||||
{
|
||||
$this->fakerField = $this->argument('faker_field');
|
||||
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ class CreateTestData extends Command
|
|||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function fire()
|
||||
public function handle()
|
||||
{
|
||||
if (Utils::isNinjaProd()) {
|
||||
$this->info('Unable to run in production');
|
||||
|
|
@ -133,7 +133,7 @@ class CreateTestData extends Command
|
|||
|
||||
$this->createInvoices($client);
|
||||
$this->createInvoices($client, true);
|
||||
$this->createTasks($client);
|
||||
// $this->createTasks($client);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
115
app/Console/Commands/ExportMigrations.php
Normal file
115
app/Console/Commands/ExportMigrations.php
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Libraries\Utils;
|
||||
use App\Models\User;
|
||||
use App\Traits\GenerateMigrationResources;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class ExportMigrations extends Command
|
||||
{
|
||||
use GenerateMigrationResources;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'migrations:export {--user=} {--random=}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Export account migrations to folder.';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->info('Note: Migrations will be stored inside of (storage/migrations) folder.');
|
||||
|
||||
if($this->option('user')) {
|
||||
$record = User::findOrFail($this->option('user'));
|
||||
return $this->export($record);
|
||||
}
|
||||
|
||||
if($this->option('random')){
|
||||
|
||||
User::all()->random(200)->each(function ($user){
|
||||
$this->export($user);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$users = User::all();
|
||||
|
||||
foreach($users as $user) {
|
||||
Auth::login($user);
|
||||
$this->export($user);
|
||||
}
|
||||
}
|
||||
|
||||
private function export($user)
|
||||
{
|
||||
$this->account = $user->account;
|
||||
|
||||
$date = date('Y-m-d');
|
||||
$accountKey = $this->account->account_key;
|
||||
|
||||
$output = fopen('php://output', 'w') or Utils::fatalError();
|
||||
|
||||
$fileName = "{$accountKey}-{$date}-invoiceninja";
|
||||
|
||||
$data['data'] = [
|
||||
'account' => $this->getAccount(),
|
||||
'company' => $this->getCompany(),
|
||||
'users' => $this->getUsers(),
|
||||
'tax_rates' => $this->getTaxRates(),
|
||||
'payment_terms' => $this->getPaymentTerms(),
|
||||
'clients' => $this->getClients(),
|
||||
'company_gateways' => $this->getCompanyGateways(),
|
||||
'client_gateway_tokens' => $this->getClientGatewayTokens(),
|
||||
'vendors' => $this->getVendors(),
|
||||
'projects' => $this->getProjects(),
|
||||
'products' => $this->getProducts(),
|
||||
'credits' => $this->getCreditsNotes(),
|
||||
'invoices' => $this->getInvoices(),
|
||||
'recurring_invoices' => $this->getRecurringInvoices(),
|
||||
'quotes' => $this->getQuotes(),
|
||||
'payments' => array_merge($this->getPayments(), $this->getCredits()),
|
||||
'documents' => $this->getDocuments(),
|
||||
'expense_categories' => $this->getExpenseCategories(),
|
||||
'task_statuses' => $this->getTaskStatuses(),
|
||||
'expenses' => $this->getExpenses(),
|
||||
'tasks' => $this->getTasks(),
|
||||
'documents' => $this->getDocuments(),
|
||||
];
|
||||
|
||||
$file = storage_path("migrations/{$fileName}.zip");
|
||||
|
||||
$zip = new \ZipArchive();
|
||||
$zip->open($file, \ZipArchive::CREATE | \ZipArchive::OVERWRITE);
|
||||
$zip->addFromString('migration.json', json_encode($data, JSON_PRETTY_PRINT));
|
||||
$zip->close();
|
||||
|
||||
$this->info('User with id #' . $user->id . ' exported.');
|
||||
}
|
||||
}
|
||||
121
app/Console/Commands/MobileLocalization.php
Normal file
121
app/Console/Commands/MobileLocalization.php
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\DbServer;
|
||||
use App\Models\User;
|
||||
use App\Models\Company;
|
||||
use App\Libraries\CurlUtils;
|
||||
|
||||
class MobileLocalization extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'ninja:mobile-localization {--type=}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Generate mobile localization resources';
|
||||
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$type = strtolower($this->option('type'));
|
||||
|
||||
switch ($type) {
|
||||
case 'laravel':
|
||||
$this->laravelResources();
|
||||
break;
|
||||
default:
|
||||
$this->flutterResources();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function laravelResources()
|
||||
{
|
||||
$resources = $this->getResources();
|
||||
|
||||
foreach ($resources as $key => $val) {
|
||||
$transKey = "texts.{$key}";
|
||||
if (trans($transKey) == $transKey) {
|
||||
echo "'$key' => '$val',\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function flutterResources()
|
||||
{
|
||||
$languages = cache('languages');
|
||||
$resources = $this->getResources();
|
||||
|
||||
foreach ($languages as $language) {
|
||||
if ($language->locale == 'en') {
|
||||
continue;
|
||||
}
|
||||
|
||||
echo "'{$language->locale}': {\n";
|
||||
|
||||
foreach ($resources as $key => $val) {
|
||||
$text = trim(addslashes(trans("texts.{$key}", [], $language->locale)));
|
||||
if (substr($text, 0, 6) == 'texts.') {
|
||||
$text = $resources->$key;
|
||||
}
|
||||
|
||||
$text = str_replace(array('<b>', '</b>'), '', $text);
|
||||
$text = str_replace(array('<i>', '</i>'), '', $text);
|
||||
$text = str_replace(array('<strong>', '</strong>'), '', $text);
|
||||
|
||||
echo "'$key': '$text',\n";
|
||||
}
|
||||
|
||||
echo "},\n";
|
||||
}
|
||||
}
|
||||
|
||||
private function getResources()
|
||||
{
|
||||
$url = 'https://raw.githubusercontent.com/invoiceninja/flutter-client/develop/lib/utils/i18n.dart';
|
||||
$data = CurlUtils::get($url);
|
||||
|
||||
$start = strpos($data, 'do not remove comment') + 25;
|
||||
$end = strpos($data, '},', $start);
|
||||
$data = substr($data, $start, $end - $start - 5);
|
||||
|
||||
$data = str_replace("\n", "", $data);
|
||||
$data = str_replace("\"", "\'", $data);
|
||||
$data = str_replace("'", "\"", $data);
|
||||
|
||||
return json_decode('{' . rtrim($data, ',') . '}');
|
||||
}
|
||||
|
||||
protected function getOptions()
|
||||
{
|
||||
return [
|
||||
['type', null, InputOption::VALUE_OPTIONAL, 'Type', null],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -21,7 +21,7 @@ class PruneData extends Command
|
|||
*/
|
||||
protected $description = 'Delete inactive accounts';
|
||||
|
||||
public function fire()
|
||||
public function handle()
|
||||
{
|
||||
$this->info(date('r').' Running PruneData...');
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ class RemoveOrphanedDocuments extends Command
|
|||
*/
|
||||
protected $description = 'Removes old documents not associated with an expense or invoice';
|
||||
|
||||
public function fire()
|
||||
public function handle()
|
||||
{
|
||||
$this->info(date('r').' Running RemoveOrphanedDocuments...');
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ class ResetData extends Command
|
|||
*/
|
||||
protected $description = 'Reset data';
|
||||
|
||||
public function fire()
|
||||
public function handle()
|
||||
{
|
||||
$this->info(date('r') . ' Running ResetData...');
|
||||
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ class SendRecurringInvoices extends Command
|
|||
$this->recurringExpenseRepo = $recurringExpenseRepo;
|
||||
}
|
||||
|
||||
public function fire()
|
||||
public function handle()
|
||||
{
|
||||
$this->info(date('r') . ' Running SendRecurringInvoices...');
|
||||
|
||||
|
|
@ -81,8 +81,8 @@ class SendRecurringInvoices extends Command
|
|||
$invoices = Invoice::with('account.timezone', 'invoice_items', 'client', 'user')
|
||||
->whereRaw('is_deleted IS FALSE AND deleted_at IS NULL AND is_recurring IS TRUE AND is_public IS TRUE AND frequency_id > 0 AND start_date <= ? AND (end_date IS NULL OR end_date >= ?)', [$today, $today])
|
||||
->orderBy('id', 'asc')
|
||||
->get();
|
||||
$this->info(date('r ') . $invoices->count() . ' recurring invoice(s) found');
|
||||
->cursor();
|
||||
$this->info(date('r ') . ' Recurring invoice(s) found');
|
||||
|
||||
foreach ($invoices as $recurInvoice) {
|
||||
$shouldSendToday = $recurInvoice->shouldSendToday();
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ class SendReminders extends Command
|
|||
$this->userMailer = $userMailer;
|
||||
}
|
||||
|
||||
public function fire()
|
||||
public function handle()
|
||||
{
|
||||
$this->info(date('r') . ' Running SendReminders...');
|
||||
|
||||
|
|
@ -125,7 +125,7 @@ class SendReminders extends Command
|
|||
private function chargeLateFees()
|
||||
{
|
||||
$accounts = $this->accountRepo->findWithFees();
|
||||
$this->info(date('r ') . $accounts->count() . ' accounts found with fees');
|
||||
$this->info(date('r ') . $accounts->count() . ' accounts found with fees enabled');
|
||||
|
||||
foreach ($accounts as $account) {
|
||||
if (! $account->hasFeature(FEATURE_EMAIL_TEMPLATES_REMINDERS)) {
|
||||
|
|
@ -152,7 +152,7 @@ class SendReminders extends Command
|
|||
private function sendReminderEmails()
|
||||
{
|
||||
$accounts = $this->accountRepo->findWithReminders();
|
||||
$this->info(date('r ') . count($accounts) . ' accounts found with reminders');
|
||||
$this->info(date('r ') . count($accounts) . ' accounts found with reminders enabled');
|
||||
|
||||
foreach ($accounts as $account) {
|
||||
if (! $account->hasFeature(FEATURE_EMAIL_TEMPLATES_REMINDERS)) {
|
||||
|
|
@ -211,8 +211,8 @@ class SendReminders extends Command
|
|||
// send email as user
|
||||
auth()->onceUsingId($user->id);
|
||||
|
||||
$report = dispatch(new RunReport($scheduledReport->user, $reportType, $config, true));
|
||||
$file = dispatch(new ExportReportResults($scheduledReport->user, $config['export_format'], $reportType, $report->exportParams));
|
||||
$report = dispatch_now(new RunReport($scheduledReport->user, $reportType, $config, true));
|
||||
$file = dispatch_now(new ExportReportResults($scheduledReport->user, $config['export_format'], $reportType, $report->exportParams));
|
||||
|
||||
if ($file) {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ class SendRenewalInvoices extends Command
|
|||
$this->accountRepo = $repo;
|
||||
}
|
||||
|
||||
public function fire()
|
||||
public function handle()
|
||||
{
|
||||
$this->info(date('r').' Running SendRenewalInvoices...');
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class TestOFX extends Command
|
|||
$this->bankAccountService = $bankAccountService;
|
||||
}
|
||||
|
||||
public function fire()
|
||||
public function handle()
|
||||
{
|
||||
$this->info(date('r').' Running TestOFX...');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ class UpdateKey extends Command
|
|||
*/
|
||||
protected $description = 'Update application key';
|
||||
|
||||
public function fire()
|
||||
public function handle()
|
||||
{
|
||||
$this->info(date('r') . ' Running UpdateKey...');
|
||||
|
||||
|
|
|
|||
2
app/Console/Commands/stubs/command.stub
Executable file → Normal file
2
app/Console/Commands/stubs/command.stub
Executable file → Normal file
|
|
@ -37,7 +37,7 @@ class $CLASS$ extends Command
|
|||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function fire()
|
||||
public function handle()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
|
|
|||
0
app/Console/Commands/stubs/composer.stub
Executable file → Normal file
0
app/Console/Commands/stubs/composer.stub
Executable file → Normal file
0
app/Console/Commands/stubs/controller-plain.stub
Executable file → Normal file
0
app/Console/Commands/stubs/controller-plain.stub
Executable file → Normal file
0
app/Console/Commands/stubs/controller.stub
Executable file → Normal file
0
app/Console/Commands/stubs/controller.stub
Executable file → Normal file
0
app/Console/Commands/stubs/event.stub
Executable file → Normal file
0
app/Console/Commands/stubs/event.stub
Executable file → Normal file
0
app/Console/Commands/stubs/job.stub
Executable file → Normal file
0
app/Console/Commands/stubs/job.stub
Executable file → Normal file
0
app/Console/Commands/stubs/json.stub
Executable file → Normal file
0
app/Console/Commands/stubs/json.stub
Executable file → Normal file
0
app/Console/Commands/stubs/listener.stub
Executable file → Normal file
0
app/Console/Commands/stubs/listener.stub
Executable file → Normal file
0
app/Console/Commands/stubs/mail.stub
Executable file → Normal file
0
app/Console/Commands/stubs/mail.stub
Executable file → Normal file
0
app/Console/Commands/stubs/middleware.stub
Executable file → Normal file
0
app/Console/Commands/stubs/middleware.stub
Executable file → Normal file
0
app/Console/Commands/stubs/migration/add.stub
Executable file → Normal file
0
app/Console/Commands/stubs/migration/add.stub
Executable file → Normal file
0
app/Console/Commands/stubs/migration/create.stub
Executable file → Normal file
0
app/Console/Commands/stubs/migration/create.stub
Executable file → Normal file
0
app/Console/Commands/stubs/migration/delete.stub
Executable file → Normal file
0
app/Console/Commands/stubs/migration/delete.stub
Executable file → Normal file
0
app/Console/Commands/stubs/migration/drop.stub
Executable file → Normal file
0
app/Console/Commands/stubs/migration/drop.stub
Executable file → Normal file
0
app/Console/Commands/stubs/migration/plain.stub
Executable file → Normal file
0
app/Console/Commands/stubs/migration/plain.stub
Executable file → Normal file
0
app/Console/Commands/stubs/model.stub
Executable file → Normal file
0
app/Console/Commands/stubs/model.stub
Executable file → Normal file
0
app/Console/Commands/stubs/notification.stub
Executable file → Normal file
0
app/Console/Commands/stubs/notification.stub
Executable file → Normal file
0
app/Console/Commands/stubs/provider.stub
Executable file → Normal file
0
app/Console/Commands/stubs/provider.stub
Executable file → Normal file
0
app/Console/Commands/stubs/request.stub
Executable file → Normal file
0
app/Console/Commands/stubs/request.stub
Executable file → Normal file
0
app/Console/Commands/stubs/route-provider.stub
Executable file → Normal file
0
app/Console/Commands/stubs/route-provider.stub
Executable file → Normal file
0
app/Console/Commands/stubs/routes.stub
Executable file → Normal file
0
app/Console/Commands/stubs/routes.stub
Executable file → Normal file
0
app/Console/Commands/stubs/scaffold/config.stub
Executable file → Normal file
0
app/Console/Commands/stubs/scaffold/config.stub
Executable file → Normal file
0
app/Console/Commands/stubs/scaffold/provider.stub
Executable file → Normal file
0
app/Console/Commands/stubs/scaffold/provider.stub
Executable file → Normal file
0
app/Console/Commands/stubs/seeder.stub
Executable file → Normal file
0
app/Console/Commands/stubs/seeder.stub
Executable file → Normal file
0
app/Console/Commands/stubs/start.stub
Executable file → Normal file
0
app/Console/Commands/stubs/start.stub
Executable file → Normal file
0
app/Console/Commands/stubs/views.stub
Executable file → Normal file
0
app/Console/Commands/stubs/views.stub
Executable file → Normal file
0
app/Console/Commands/stubs/views/master.stub
Executable file → Normal file
0
app/Console/Commands/stubs/views/master.stub
Executable file → Normal file
|
|
@ -21,6 +21,7 @@ class Kernel extends ConsoleKernel
|
|||
'App\Console\Commands\PruneData',
|
||||
'App\Console\Commands\CreateTestData',
|
||||
'App\Console\Commands\CreateLuisData',
|
||||
'App\Console\Commands\MobileLocalization',
|
||||
'App\Console\Commands\SendRenewalInvoices',
|
||||
'App\Console\Commands\ChargeRenewalInvoices',
|
||||
'App\Console\Commands\SendReminders',
|
||||
|
|
@ -30,6 +31,7 @@ class Kernel extends ConsoleKernel
|
|||
'App\Console\Commands\InitLookup',
|
||||
'App\Console\Commands\CalculatePayouts',
|
||||
'App\Console\Commands\UpdateKey',
|
||||
'App\Console\Commands\ExportMigrations',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -202,9 +202,9 @@ if (! defined('APP_NAME')) {
|
|||
define('IMPORT_PANCAKE', 'Pancake');
|
||||
|
||||
define('MAX_NUM_CLIENTS', 100);
|
||||
define('MAX_NUM_CLIENTS_PRO', 20000);
|
||||
define('MAX_NUM_CLIENTS_PRO', 40000);
|
||||
define('MAX_NUM_CLIENTS_LEGACY', 500);
|
||||
define('MAX_INVOICE_AMOUNT', 1000000000);
|
||||
define('MAX_INVOICE_AMOUNT', 10000000000);
|
||||
define('LEGACY_CUTOFF', 57800);
|
||||
define('ERROR_DELAY', 3);
|
||||
|
||||
|
|
@ -361,7 +361,7 @@ if (! defined('APP_NAME')) {
|
|||
define('NINJA_APP_URL', env('NINJA_APP_URL', 'https://app.invoiceninja.com'));
|
||||
define('NINJA_DOCS_URL', env('NINJA_DOCS_URL', 'https://invoice-ninja.readthedocs.io/en/latest'));
|
||||
define('NINJA_DATE', '2000-01-01');
|
||||
define('NINJA_VERSION', '4.5.5' . env('NINJA_VERSION_SUFFIX'));
|
||||
define('NINJA_VERSION', '4.5.40' . env('NINJA_VERSION_SUFFIX'));
|
||||
define('NINJA_TERMS_VERSION', '1.0.1');
|
||||
|
||||
define('SOCIAL_LINK_FACEBOOK', env('SOCIAL_LINK_FACEBOOK', 'https://www.facebook.com/invoiceninja'));
|
||||
|
|
@ -371,9 +371,9 @@ if (! defined('APP_NAME')) {
|
|||
define('NINJA_FORUM_URL', env('NINJA_FORUM_URL', 'https://www.invoiceninja.com/forums/forum/support/'));
|
||||
define('NINJA_CONTACT_URL', env('NINJA_CONTACT_URL', 'https://www.invoiceninja.com/contact/'));
|
||||
define('NINJA_FROM_EMAIL', env('NINJA_FROM_EMAIL', 'maildelivery@invoiceninja.com'));
|
||||
define('NINJA_IOS_APP_URL', 'https://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=1220337560&mt=8');
|
||||
define('NINJA_ANDROID_APP_URL', 'https://play.google.com/store/apps/details?id=com.invoiceninja.invoiceninja');
|
||||
define('RELEASES_URL', env('RELEASES_URL', 'https://trello.com/b/63BbiVVe/invoice-ninja'));
|
||||
define('NINJA_IOS_APP_URL', 'https://itunes.apple.com/us/app/invoice-ninja/id1435514417?ls=1&mt=8');
|
||||
define('NINJA_ANDROID_APP_URL', 'https://play.google.com/store/apps/details?id=com.invoiceninja.flutter');
|
||||
define('RELEASES_URL', env('RELEASES_URL', 'https://github.com/invoiceninja/invoiceninja/releases'));
|
||||
define('ZAPIER_URL', env('ZAPIER_URL', 'https://zapier.com/zapbook/invoice-ninja'));
|
||||
define('OUTDATE_BROWSER_URL', env('OUTDATE_BROWSER_URL', 'http://browsehappy.com/'));
|
||||
define('PDFMAKE_DOCS', env('PDFMAKE_DOCS', 'http://pdfmake.org/playground.html'));
|
||||
|
|
@ -414,12 +414,12 @@ if (! defined('APP_NAME')) {
|
|||
define('INVOICE_DESIGNS_AFFILIATE_KEY', 'T3RS74');
|
||||
define('SELF_HOST_AFFILIATE_KEY', '8S69AD');
|
||||
|
||||
define('PLAN_PRICE_PRO_MONTHLY', env('PLAN_PRICE_PRO_MONTHLY', 8));
|
||||
define('PLAN_PRICE_ENTERPRISE_MONTHLY_2', env('PLAN_PRICE_ENTERPRISE_MONTHLY_2', 12));
|
||||
define('PLAN_PRICE_ENTERPRISE_MONTHLY_5', env('PLAN_PRICE_ENTERPRISE_MONTHLY_5', 18));
|
||||
define('PLAN_PRICE_ENTERPRISE_MONTHLY_10', env('PLAN_PRICE_ENTERPRISE_MONTHLY_10', 24));
|
||||
define('PLAN_PRICE_ENTERPRISE_MONTHLY_20', env('PLAN_PRICE_ENTERPRISE_MONTHLY_20', 36));
|
||||
define('WHITE_LABEL_PRICE', env('WHITE_LABEL_PRICE', 20));
|
||||
define('PLAN_PRICE_PRO_MONTHLY', env('PLAN_PRICE_PRO_MONTHLY', 10));
|
||||
define('PLAN_PRICE_ENTERPRISE_MONTHLY_2', env('PLAN_PRICE_ENTERPRISE_MONTHLY_2', 14));
|
||||
define('PLAN_PRICE_ENTERPRISE_MONTHLY_5', env('PLAN_PRICE_ENTERPRISE_MONTHLY_5', 26));
|
||||
define('PLAN_PRICE_ENTERPRISE_MONTHLY_10', env('PLAN_PRICE_ENTERPRISE_MONTHLY_10', 36));
|
||||
define('PLAN_PRICE_ENTERPRISE_MONTHLY_20', env('PLAN_PRICE_ENTERPRISE_MONTHLY_20', 44));
|
||||
define('WHITE_LABEL_PRICE', env('WHITE_LABEL_PRICE', 30));
|
||||
define('INVOICE_DESIGNS_PRICE', env('INVOICE_DESIGNS_PRICE', 10));
|
||||
|
||||
define('USER_TYPE_SELF_HOST', 'SELF_HOST');
|
||||
|
|
@ -695,7 +695,7 @@ if (! defined('APP_NAME')) {
|
|||
}
|
||||
|
||||
// include modules in translations
|
||||
function mtrans($entityType, $text = false)
|
||||
function mtrans($entityType, $text = false, $replace = [])
|
||||
{
|
||||
if (! $text) {
|
||||
$text = $entityType;
|
||||
|
|
@ -704,7 +704,7 @@ if (! defined('APP_NAME')) {
|
|||
// check if this has been translated in a module language file
|
||||
if (! Utils::isNinjaProd() && $module = Module::find($entityType)) {
|
||||
$key = "{$module->getLowerName()}::texts.{$text}";
|
||||
$value = trans($key);
|
||||
$value = trans($key, $replace);
|
||||
if ($key != $value) {
|
||||
return $value;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ namespace App\Http\Controllers;
|
|||
use App\Events\UserSignedUp;
|
||||
use App\Http\Requests\RegisterRequest;
|
||||
use App\Http\Requests\UpdateAccountRequest;
|
||||
use App\Models\Company;
|
||||
use App\Models\Account;
|
||||
use App\Models\User;
|
||||
use App\Ninja\OAuth\OAuth;
|
||||
|
|
@ -14,6 +15,7 @@ use App\Ninja\Transformers\UserAccountTransformer;
|
|||
use App\Services\AuthService;
|
||||
use Auth;
|
||||
use Cache;
|
||||
use Carbon;
|
||||
use Exception;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
|
@ -261,6 +263,17 @@ class AccountApiController extends BaseAPIController
|
|||
$oAuth = new OAuth();
|
||||
$user = $oAuth->getProvider($provider)->getTokenResponse($token);
|
||||
|
||||
/*
|
||||
if ($user->google_2fa_secret && strpos($request->token_name, 'invoice-ninja-') !== false) {
|
||||
$secret = \Crypt::decrypt($user->google_2fa_secret);
|
||||
if (! $request->one_time_password) {
|
||||
return $this->errorResponse(['message' => 'OTP_REQUIRED'], 401);
|
||||
} elseif (! \Google2FA::verifyKey($secret, $request->one_time_password)) {
|
||||
return $this->errorResponse(['message' => 'Invalid one time password'], 401);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
if ($user) {
|
||||
Auth::login($user);
|
||||
return $this->processLogin($request);
|
||||
|
|
@ -276,4 +289,49 @@ class AccountApiController extends BaseAPIController
|
|||
|
||||
}
|
||||
|
||||
public function upgrade(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
$account = $user->account;
|
||||
$company = $account->company;
|
||||
$orderId = $request->order_id;
|
||||
$timestamp = $request->timestamp;
|
||||
$productId = $request->product_id;
|
||||
|
||||
if ($company->app_store_order_id) {
|
||||
return '{"message":"error"}';
|
||||
}
|
||||
|
||||
if ($productId == 'v1_pro_yearly') {
|
||||
$company->plan = PLAN_PRO;
|
||||
$company->num_users = 1;
|
||||
$company->plan_price = PLAN_PRICE_PRO_MONTHLY * 10;
|
||||
} else if ($productId == 'v1_enterprise_2_yearly') {
|
||||
$company->plan = PLAN_ENTERPRISE;
|
||||
$company->num_users = 2;
|
||||
$company->plan_price = PLAN_PRICE_ENTERPRISE_MONTHLY_2 * 10;
|
||||
} else if ($productId == 'v1_enterprise_5_yearly') {
|
||||
$company->plan = PLAN_ENTERPRISE;
|
||||
$company->num_users = 5;
|
||||
$company->plan_price = PLAN_PRICE_ENTERPRISE_MONTHLY_5 * 10;
|
||||
} else if ($productId == 'v1_enterprise_10_yearly') {
|
||||
$company->plan = PLAN_ENTERPRISE;
|
||||
$company->num_users = 10;
|
||||
$company->plan_price = PLAN_PRICE_ENTERPRISE_MONTHLY_10 * 10;
|
||||
} else if ($productId == 'v1_enterprise_20_yearly') {
|
||||
$company->plan = PLAN_ENTERPRISE;
|
||||
$company->num_users = 20;
|
||||
$company->plan_price = PLAN_PRICE_ENTERPRISE_MONTHLY_20 * 10;
|
||||
}
|
||||
|
||||
$company->app_store_order_id = $orderId;
|
||||
$company->plan_term = PLAN_TERM_YEARLY;
|
||||
$company->plan_started = $company->plan_started ?: date('Y-m-d');
|
||||
$company->plan_paid = date('Y-m-d');
|
||||
$company->plan_expires = Carbon::now()->addYear()->format('Y-m-d');
|
||||
$company->trial_plan = null;
|
||||
$company->save();
|
||||
|
||||
return '{"message":"success"}';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -950,6 +950,7 @@ class AccountController extends BaseController
|
|||
{
|
||||
$account = Auth::user()->account;
|
||||
|
||||
$account->show_product_notes = Input::get('show_product_notes') ? true : false;
|
||||
$account->fill_products = Input::get('fill_products') ? true : false;
|
||||
$account->update_products = Input::get('update_products') ? true : false;
|
||||
$account->convert_products = Input::get('convert_products') ? true : false;
|
||||
|
|
|
|||
|
|
@ -44,6 +44,10 @@ class AppController extends BaseController
|
|||
return Redirect::to('/');
|
||||
}
|
||||
|
||||
if (file_exists(base_path() . '/.env')) {
|
||||
exit('Error: app is already configured, backup then delete the .env file to re-run the setup');
|
||||
}
|
||||
|
||||
return View::make('setup');
|
||||
}
|
||||
|
||||
|
|
@ -131,7 +135,6 @@ class AppController extends BaseController
|
|||
|
||||
Cache::flush();
|
||||
Artisan::call('db:seed', ['--force' => true, '--class' => 'UpdateSeeder']);
|
||||
Artisan::call('optimize', ['--force' => true]);
|
||||
|
||||
if (! Account::count()) {
|
||||
$firstName = trim(Input::get('first_name'));
|
||||
|
|
@ -269,7 +272,6 @@ class AppController extends BaseController
|
|||
if (Industry::count() == 0) {
|
||||
Artisan::call('db:seed', ['--force' => true]);
|
||||
}
|
||||
Artisan::call('optimize', ['--force' => true]);
|
||||
} catch (Exception $e) {
|
||||
Utils::logError($e);
|
||||
|
||||
|
|
@ -307,7 +309,6 @@ class AppController extends BaseController
|
|||
Artisan::call('route:clear');
|
||||
Artisan::call('view:clear');
|
||||
Artisan::call('config:clear');
|
||||
Artisan::call('optimize', ['--force' => true]);
|
||||
Auth::logout();
|
||||
Cache::flush();
|
||||
Session::flush();
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ class AuthController extends Controller
|
|||
*/
|
||||
public function oauthLogin($provider, Request $request)
|
||||
{
|
||||
return $this->authService->execute($provider, $request->has('code'));
|
||||
return $this->authService->execute($provider, $request->filled('code'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ class CalendarController extends BaseController
|
|||
public function loadEvents()
|
||||
{
|
||||
if (auth()->user()->account->hasFeature(FEATURE_REPORTS)) {
|
||||
$events = dispatch(new GenerateCalendarEvents());
|
||||
$events = dispatch_now(new GenerateCalendarEvents());
|
||||
} else {
|
||||
$events = [];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ class ClientApiController extends BaseAPIController
|
|||
public function index()
|
||||
{
|
||||
$clients = Client::scope()
|
||||
->orderBy('created_at', 'desc')
|
||||
->orderBy('updated_at', 'desc')
|
||||
->withTrashed();
|
||||
|
||||
if ($email = Input::get('email')) {
|
||||
|
|
|
|||
|
|
@ -175,11 +175,9 @@ class LoginController extends Controller
|
|||
*/
|
||||
public function getLogoutWrapper(Request $request)
|
||||
{
|
||||
$contactKey = session('contact_key');
|
||||
|
||||
self::logout($request);
|
||||
|
||||
return redirect('/client/dashboard/' . $contactKey);
|
||||
return redirect('/client/login?account_key=' . $request->account_key);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -260,7 +260,7 @@ class ClientController extends BaseController
|
|||
}
|
||||
|
||||
if (request()->json) {
|
||||
return dispatch(new GenerateStatementData($client, request()->all()));
|
||||
return dispatch_now(new GenerateStatementData($client, request()->all()));
|
||||
}
|
||||
|
||||
$data = [
|
||||
|
|
@ -276,14 +276,14 @@ class ClientController extends BaseController
|
|||
|
||||
public function getEmailHistory()
|
||||
{
|
||||
$history = dispatch(new LoadPostmarkHistory(request()->email));
|
||||
$history = dispatch_now(new LoadPostmarkHistory(request()->email));
|
||||
|
||||
return response()->json($history);
|
||||
}
|
||||
|
||||
public function reactivateEmail()
|
||||
{
|
||||
$result = dispatch(new ReactivatePostmarkEmail(request()->bounce_id));
|
||||
$result = dispatch_now(new ReactivatePostmarkEmail(request()->bounce_id));
|
||||
|
||||
return response()->json($result);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -136,6 +136,7 @@ class ClientPortalController extends BaseController
|
|||
}
|
||||
}
|
||||
|
||||
if (! Input::has('phantomjs')) {
|
||||
if ($wepayGateway = $account->getGatewayConfig(GATEWAY_WEPAY)) {
|
||||
$data['enableWePayACH'] = $wepayGateway->getAchEnabled();
|
||||
}
|
||||
|
|
@ -143,6 +144,7 @@ class ClientPortalController extends BaseController
|
|||
//$data['enableStripeSources'] = $stripeGateway->getAlipayEnabled();
|
||||
$data['enableStripeSources'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$showApprove = $invoice->quote_invoice_id ? false : true;
|
||||
if ($invoice->invoice_status_id >= INVOICE_STATUS_APPROVED) {
|
||||
|
|
@ -1033,7 +1035,7 @@ class ClientPortalController extends BaseController
|
|||
}
|
||||
|
||||
if (request()->json) {
|
||||
return dispatch(new GenerateStatementData($client, request()->all(), $contact));
|
||||
return dispatch_now(new GenerateStatementData($client, request()->all(), $contact));
|
||||
}
|
||||
|
||||
$data = [
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ class ClientPortalProposalController extends BaseController
|
|||
|
||||
$proposal = $invitation->proposal;
|
||||
|
||||
$pdf = dispatch(new ConvertProposalToPdf($proposal));
|
||||
$pdf = dispatch_now(new ConvertProposalToPdf($proposal));
|
||||
|
||||
$this->downloadResponse($proposal->getFilename(), $pdf);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ class CreditApiController extends BaseAPIController
|
|||
$credits = Credit::scope()
|
||||
->withTrashed()
|
||||
->with(['client'])
|
||||
->orderBy('created_at', 'desc');
|
||||
->orderBy('updated_at', 'desc');
|
||||
|
||||
return $this->listResponse($credits);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ class DocumentAPIController extends BaseAPIController
|
|||
{
|
||||
$entity = $request->entity();
|
||||
|
||||
$this->documentRepo->delete($entity);
|
||||
$entity->delete();
|
||||
|
||||
return $this->itemResponse($entity);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ class ExpenseApiController extends BaseAPIController
|
|||
$expenses = Expense::scope()
|
||||
->withTrashed()
|
||||
->with('client', 'invoice', 'vendor', 'expense_category')
|
||||
->orderBy('created_at', 'desc');
|
||||
->orderBy('updated_at', 'desc');
|
||||
|
||||
return $this->listResponse($expenses);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ class InvoiceApiController extends BaseAPIController
|
|||
$invoices = Invoice::scope()
|
||||
->withTrashed()
|
||||
->with('invoice_items', 'client')
|
||||
->orderBy('created_at', 'desc');
|
||||
->orderBy('updated_at', 'desc');
|
||||
|
||||
// Filter by invoice number
|
||||
if ($invoiceNumber = Input::get('invoice_number')) {
|
||||
|
|
@ -73,11 +73,11 @@ class InvoiceApiController extends BaseAPIController
|
|||
$invoices->where('invoice_status_id', '>=', $statusId);
|
||||
}
|
||||
|
||||
if (request()->has('is_recurring')) {
|
||||
if (request()->filled('is_recurring')) {
|
||||
$invoices->where('is_recurring', '=', request()->is_recurring);
|
||||
}
|
||||
|
||||
if (request()->has('invoice_type_id')) {
|
||||
if (request()->filled('invoice_type_id')) {
|
||||
$invoices->where('invoice_type_id', '=', request()->invoice_type_id);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -192,6 +192,10 @@ class InvoiceController extends BaseController
|
|||
}
|
||||
}
|
||||
|
||||
if (Auth::user()->registered && ! Auth::user()->confirmed) {
|
||||
session()->flash('warning', trans('texts.confirmation_required', ['link' => link_to('/resend_confirmation', trans('texts.click_here'))]));
|
||||
}
|
||||
|
||||
return View::make('invoices.edit', $data);
|
||||
}
|
||||
|
||||
|
|
@ -302,7 +306,7 @@ class InvoiceController extends BaseController
|
|||
|
||||
// Check for any taxes which have been deleted
|
||||
$taxRateOptions = $account->present()->taxRateOptions;
|
||||
if ($invoice->exists) {
|
||||
if ($invoice->exists && !$invoice->deleted_at) {
|
||||
foreach ($invoice->getTaxes() as $key => $rate) {
|
||||
$key = '0 ' . $key; // mark it as a standard exclusive rate option
|
||||
if (isset($taxRateOptions[$key])) {
|
||||
|
|
|
|||
297
app/Http/Controllers/Migration/StepsController.php
Normal file
297
app/Http/Controllers/Migration/StepsController.php
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Migration;
|
||||
|
||||
use App\Http\Controllers\BaseController;
|
||||
use App\Http\Requests\MigrationAuthRequest;
|
||||
use App\Http\Requests\MigrationCompaniesRequest;
|
||||
use App\Http\Requests\MigrationEndpointRequest;
|
||||
use App\Http\Requests\MigrationTypeRequest;
|
||||
use App\Libraries\Utils;
|
||||
use App\Models\Account;
|
||||
use App\Services\Migration\AuthService;
|
||||
use App\Services\Migration\CompanyService;
|
||||
use App\Services\Migration\CompleteService;
|
||||
use App\Traits\GenerateMigrationResources;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class StepsController extends BaseController
|
||||
{
|
||||
use GenerateMigrationResources;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('migration');
|
||||
}
|
||||
|
||||
private $access = [
|
||||
'auth' => [
|
||||
'steps' => ['MIGRATION_TYPE'],
|
||||
'redirect' => '/migration/start',
|
||||
],
|
||||
'endpoint' => [
|
||||
'steps' => ['MIGRATION_TYPE'],
|
||||
'redirect' => '/migration/start',
|
||||
],
|
||||
'companies' => [
|
||||
'steps' => ['MIGRATION_TYPE', 'MIGRATION_ACCOUNT_TOKEN'],
|
||||
'redirect' => '/migration/auth',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
*/
|
||||
public function start()
|
||||
{
|
||||
return view('migration.start');
|
||||
}
|
||||
|
||||
public function import()
|
||||
{
|
||||
return view('migration.import');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
*/
|
||||
public function download()
|
||||
{
|
||||
return view('migration.download');
|
||||
}
|
||||
|
||||
public function handleType(MigrationTypeRequest $request)
|
||||
{
|
||||
session()->put('MIGRATION_TYPE', $request->option);
|
||||
|
||||
if ($request->option == 0) {
|
||||
|
||||
session()->put('MIGRATION_ENDPOINT', 'https://invoicing.co');
|
||||
|
||||
return redirect(
|
||||
url('/migration/auth')
|
||||
);
|
||||
|
||||
// return redirect(
|
||||
// url('/migration/endpoint')
|
||||
// );
|
||||
}
|
||||
|
||||
return redirect(
|
||||
url('/migration/endpoint')
|
||||
);
|
||||
}
|
||||
|
||||
public function endpoint()
|
||||
{
|
||||
if ($this->shouldGoBack('endpoint')) {
|
||||
return redirect(
|
||||
url($this->access['endpoint']['redirect'])
|
||||
);
|
||||
}
|
||||
|
||||
return view('migration.endpoint');
|
||||
}
|
||||
|
||||
public function handleEndpoint(MigrationEndpointRequest $request)
|
||||
{
|
||||
if ($this->shouldGoBack('endpoint')) {
|
||||
return redirect(
|
||||
url($this->access['endpoint']['redirect'])
|
||||
);
|
||||
}
|
||||
|
||||
session()->put('MIGRATION_ENDPOINT', rtrim($request->endpoint,'/'));
|
||||
|
||||
return redirect(
|
||||
url('/migration/auth')
|
||||
);
|
||||
}
|
||||
|
||||
public function auth()
|
||||
{
|
||||
if ($this->shouldGoBack('auth')) {
|
||||
return redirect(
|
||||
url($this->access['auth']['redirect'])
|
||||
);
|
||||
}
|
||||
|
||||
return view('migration.auth');
|
||||
}
|
||||
|
||||
public function handleAuth(MigrationAuthRequest $request)
|
||||
{
|
||||
if ($this->shouldGoBack('auth')) {
|
||||
return redirect(
|
||||
url($this->access['auth']['redirect'])
|
||||
);
|
||||
}
|
||||
|
||||
if (auth()->user()->email !== $request->email) {
|
||||
return back()->with('responseErrors', [trans('texts.cross_migration_message')]);
|
||||
}
|
||||
|
||||
$authentication = (new AuthService($request->email, $request->password, $request->has('api_secret') ? $request->api_secret : null))
|
||||
->endpoint(session('MIGRATION_ENDPOINT'))
|
||||
->start();
|
||||
|
||||
if ($authentication->isSuccessful()) {
|
||||
session()->put('MIGRATION_ACCOUNT_TOKEN', $authentication->getAccountToken());
|
||||
session()->put('MIGRAITON_API_SECRET', $authentication->getApiSecret());
|
||||
|
||||
return redirect(
|
||||
url('/migration/companies')
|
||||
);
|
||||
}
|
||||
|
||||
return back()->with('responseErrors', $authentication->getErrors());
|
||||
}
|
||||
|
||||
public function companies()
|
||||
{
|
||||
if ($this->shouldGoBack('companies')) {
|
||||
return redirect(
|
||||
url($this->access['companies']['redirect'])
|
||||
);
|
||||
}
|
||||
|
||||
$companyService = (new CompanyService())
|
||||
->start();
|
||||
|
||||
if ($companyService->isSuccessful()) {
|
||||
return view('migration.companies', ['companies' => $companyService->getCompanies()]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Oops, looks like something failed. Please try again.'
|
||||
], 500);
|
||||
}
|
||||
|
||||
public function handleCompanies(MigrationCompaniesRequest $request)
|
||||
{
|
||||
if ($this->shouldGoBack('companies')) {
|
||||
return redirect(
|
||||
url($this->access['companies']['redirect'])
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$migrationData = $this->generateMigrationData($request->all());
|
||||
|
||||
$completeService = (new CompleteService(session('MIGRATION_ACCOUNT_TOKEN')))
|
||||
->data($migrationData)
|
||||
->endpoint(session('MIGRATION_ENDPOINT'))
|
||||
->start();
|
||||
}
|
||||
finally {
|
||||
|
||||
if ($completeService->isSuccessful()) {
|
||||
return view('migration.completed');
|
||||
}
|
||||
|
||||
return view('migration.completed', ['customMessage' => $completeService->getErrors()[0]]);
|
||||
}
|
||||
}
|
||||
|
||||
public function completed()
|
||||
{
|
||||
return view('migration.completed');
|
||||
}
|
||||
|
||||
/**
|
||||
* ==================================
|
||||
* Rest of functions that are used as 'actions', not controller methods.
|
||||
* ==================================
|
||||
*/
|
||||
|
||||
public function shouldGoBack(string $step)
|
||||
{
|
||||
$redirect = true;
|
||||
|
||||
foreach ($this->access[$step]['steps'] as $step) {
|
||||
if (session()->has($step)) {
|
||||
$redirect = false;
|
||||
} else {
|
||||
$redirect = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $redirect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle data downloading for the migration.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generateMigrationData(array $data): array
|
||||
{
|
||||
set_time_limit(0);
|
||||
|
||||
$migrationData = [];
|
||||
|
||||
foreach ($data['companies'] as $company) {
|
||||
$account = Account::where('account_key', $company['id'])->firstOrFail();
|
||||
|
||||
$this->account = $account;
|
||||
|
||||
$date = date('Y-m-d');
|
||||
$accountKey = $this->account->account_key;
|
||||
|
||||
$output = fopen('php://output', 'w') or Utils::fatalError();
|
||||
|
||||
$fileName = "{$accountKey}-{$date}-invoiceninja";
|
||||
|
||||
$localMigrationData['data'] = [
|
||||
'account' => $this->getAccount(),
|
||||
'company' => $this->getCompany(),
|
||||
'users' => $this->getUsers(),
|
||||
'tax_rates' => $this->getTaxRates(),
|
||||
'payment_terms' => $this->getPaymentTerms(),
|
||||
'clients' => $this->getClients(),
|
||||
'company_gateways' => $this->getCompanyGateways(),
|
||||
'client_gateway_tokens' => $this->getClientGatewayTokens(),
|
||||
'vendors' => $this->getVendors(),
|
||||
'projects' => $this->getProjects(),
|
||||
'products' => $this->getProducts(),
|
||||
'credits' => $this->getCreditsNotes(),
|
||||
'invoices' => $this->getInvoices(),
|
||||
'recurring_invoices' => $this->getRecurringInvoices(),
|
||||
'quotes' => $this->getQuotes(),
|
||||
'payments' => array_merge($this->getPayments(), $this->getCredits()),
|
||||
'documents' => $this->getDocuments(),
|
||||
'expense_categories' => $this->getExpenseCategories(),
|
||||
'task_statuses' => $this->getTaskStatuses(),
|
||||
'expenses' => $this->getExpenses(),
|
||||
'tasks' => $this->getTasks(),
|
||||
'documents' => $this->getDocuments(),
|
||||
'ninja_tokens' => $this->getNinjaToken(),
|
||||
];
|
||||
|
||||
$localMigrationData['force'] = array_key_exists('force', $company);
|
||||
|
||||
Storage::makeDirectory('migrations');
|
||||
$file = Storage::path("migrations/{$fileName}.zip");
|
||||
|
||||
//$file = storage_path("migrations/{$fileName}.zip");
|
||||
|
||||
ksort($localMigrationData);
|
||||
|
||||
$zip = new \ZipArchive();
|
||||
$zip->open($file, \ZipArchive::CREATE | \ZipArchive::OVERWRITE);
|
||||
$zip->addFromString('migration.json', json_encode($localMigrationData, JSON_PRETTY_PRINT));
|
||||
$zip->close();
|
||||
|
||||
$localMigrationData['file'] = $file;
|
||||
|
||||
$migrationData[] = $localMigrationData;
|
||||
}
|
||||
|
||||
return $migrationData;
|
||||
|
||||
// header('Content-Type: application/zip');
|
||||
// header('Content-Length: ' . filesize($file));
|
||||
// header("Content-Disposition: attachment; filename={$fileName}.zip");
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ use App\Models\Payment;
|
|||
use App\Models\PaymentMethod;
|
||||
use App\Models\Product;
|
||||
use App\Ninja\Mailers\UserMailer;
|
||||
use App\Ninja\PaymentDrivers\PaymentActionRequiredException;
|
||||
use App\Ninja\Repositories\ClientRepository;
|
||||
use App\Ninja\Repositories\InvoiceRepository;
|
||||
use App\Services\InvoiceService;
|
||||
|
|
@ -124,11 +125,18 @@ class OnlinePaymentController extends BaseController
|
|||
*
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function doPayment(CreateOnlinePaymentRequest $request, $invitationKey, $gatewayTypeAlias = false)
|
||||
public function doPayment(
|
||||
CreateOnlinePaymentRequest $request,
|
||||
$invitationKey,
|
||||
$gatewayTypeAlias = false,
|
||||
$sourceId = false
|
||||
)
|
||||
{
|
||||
$invitation = $request->invitation;
|
||||
|
||||
if ($gatewayTypeAlias) {
|
||||
if ($gatewayTypeAlias == GATEWAY_TYPE_TOKEN) {
|
||||
$gatewayTypeId = $gatewayTypeAlias;
|
||||
} elseif ($gatewayTypeAlias) {
|
||||
$gatewayTypeId = GatewayType::getIdFromAlias($gatewayTypeAlias);
|
||||
} else {
|
||||
$gatewayTypeId = Session::get($invitation->id . 'gateway_type');
|
||||
|
|
@ -141,7 +149,16 @@ class OnlinePaymentController extends BaseController
|
|||
}
|
||||
|
||||
try {
|
||||
$paymentDriver->completeOnsitePurchase($request->all());
|
||||
// Load the payment method to charge.
|
||||
// Currently only hit for saved cards that still require 3D secure verification.
|
||||
$paymentMethod = null;
|
||||
if ($sourceId) {
|
||||
$paymentMethod = PaymentMethod::clientId($invitation->invoice->client_id)
|
||||
->wherePublicId($sourceId)
|
||||
->firstOrFail();
|
||||
}
|
||||
|
||||
$paymentDriver->completeOnsitePurchase($request->all(), $paymentMethod);
|
||||
|
||||
if (request()->capture) {
|
||||
return redirect('/client/dashboard')->withMessage(trans('texts.updated_payment_details'));
|
||||
|
|
@ -152,6 +169,8 @@ class OnlinePaymentController extends BaseController
|
|||
}
|
||||
|
||||
return $this->completePurchase($invitation);
|
||||
} catch (PaymentActionRequiredException $exception) {
|
||||
return $paymentDriver->startStepTwo($exception->getData());
|
||||
} catch (Exception $exception) {
|
||||
return $this->error($paymentDriver, $exception, true);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ class PaymentApiController extends BaseAPIController
|
|||
$payments = Payment::scope()
|
||||
->withTrashed()
|
||||
->with(['invoice'])
|
||||
->orderBy('created_at', 'desc');
|
||||
->orderBy('updated_at', 'desc');
|
||||
|
||||
return $this->listResponse($payments);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -218,7 +218,7 @@ class PaymentController extends BaseController
|
|||
Session::flash('message', trans($credit ? 'texts.created_payment_and_credit' : 'texts.created_payment'));
|
||||
}
|
||||
|
||||
return redirect()->to($payment->client->getRoute() . '#payments');
|
||||
return url($payment->client->getRoute());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ class ProductApiController extends BaseAPIController
|
|||
{
|
||||
$products = Product::scope()
|
||||
->withTrashed()
|
||||
->orderBy('created_at', 'desc');
|
||||
->orderBy('updated_at', 'desc');
|
||||
|
||||
return $this->listResponse($products);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ class ProjectApiController extends BaseAPIController
|
|||
{
|
||||
$projects = Project::scope()
|
||||
->withTrashed()
|
||||
->orderBy('created_at', 'desc');
|
||||
->orderBy('updated_at', 'desc');
|
||||
|
||||
return $this->listResponse($projects);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ class ProjectController extends BaseController
|
|||
{
|
||||
$account = auth()->user()->account;
|
||||
$project = $request->entity();
|
||||
$chartData = dispatch(new GenerateProjectChartData($project));
|
||||
$chartData = dispatch_now(new GenerateProjectChartData($project));
|
||||
|
||||
$data = [
|
||||
'account' => auth()->user()->account,
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ class ProposalController extends BaseController
|
|||
{
|
||||
$proposal = $request->entity();
|
||||
|
||||
$pdf = dispatch(new ConvertProposalToPdf($proposal));
|
||||
$pdf = dispatch_now(new ConvertProposalToPdf($proposal));
|
||||
|
||||
$this->downloadResponse($proposal->getFilename(), $pdf);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ class QuoteApiController extends InvoiceApiController
|
|||
->withTrashed()
|
||||
->quotes()
|
||||
->with('invoice_items', 'client')
|
||||
->orderBy('created_at', 'desc');
|
||||
->orderBy('updated_at', 'desc');
|
||||
|
||||
return $this->listResponse($invoices);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ class ReportController extends BaseController
|
|||
} else {
|
||||
$reportType = ENTITY_INVOICE;
|
||||
$dateField = FILTER_INVOICE_DATE;
|
||||
$startDate = Utils::today(false)->modify('-3 month');
|
||||
$startDate = Utils::today(false)->modify('-1 month');
|
||||
$endDate = Utils::today(false);
|
||||
}
|
||||
|
||||
|
|
@ -111,11 +111,11 @@ class ReportController extends BaseController
|
|||
'start_date' => $params['startDate'],
|
||||
'end_date' => $params['endDate'],
|
||||
];
|
||||
$report = dispatch(new RunReport(auth()->user(), $reportType, $config, $isExport));
|
||||
$report = dispatch_now(new RunReport(auth()->user(), $reportType, $config, $isExport));
|
||||
$params = array_merge($params, $report->exportParams);
|
||||
switch ($action) {
|
||||
case 'export':
|
||||
return dispatch(new ExportReportResults(auth()->user(), $format, $reportType, $params))->export($format);
|
||||
return dispatch_now(new ExportReportResults(auth()->user(), $format, $reportType, $params))->export($format);
|
||||
break;
|
||||
case 'schedule':
|
||||
self::schedule($params, $config);
|
||||
|
|
@ -190,7 +190,7 @@ class ReportController extends BaseController
|
|||
|
||||
public function loadEmailReport($startDate, $endDate)
|
||||
{
|
||||
$data = dispatch(new LoadPostmarkStats($startDate, $endDate));
|
||||
$data = dispatch_now(new LoadPostmarkStats($startDate, $endDate));
|
||||
|
||||
return response()->json($data);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,8 +45,8 @@ class TaskApiController extends BaseAPIController
|
|||
{
|
||||
$tasks = Task::scope()
|
||||
->withTrashed()
|
||||
->with('client', 'invoice', 'project')
|
||||
->orderBy('created_at', 'desc');
|
||||
->with('client', 'invoice', 'project', 'task_status')
|
||||
->orderBy('updated_at', 'desc');
|
||||
|
||||
return $this->listResponse($tasks);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ class TaskKanbanController extends BaseController
|
|||
$origSortOrder = $status->sort_order;
|
||||
$newSortOrder = request('sort_order');
|
||||
|
||||
if (request()->has('sort_order') && $newSortOrder != $origSortOrder) {
|
||||
if (request()->filled('sort_order') && $newSortOrder != $origSortOrder) {
|
||||
TaskStatus::scope()
|
||||
->where('sort_order', '>', $origSortOrder)
|
||||
->decrement('sort_order');
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ class VendorApiController extends BaseAPIController
|
|||
{
|
||||
$vendors = Vendor::scope()
|
||||
->withTrashed()
|
||||
->orderBy('created_at', 'desc');
|
||||
->orderBy('updated_at', 'desc');
|
||||
|
||||
return $this->listResponse($vendors);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,5 +63,6 @@ class Kernel extends HttpKernel
|
|||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
||||
'lookup' => \App\Http\Middleware\DatabaseLookup::class,
|
||||
'permissions.required' => \App\Http\Middleware\PermissionsRequired::class,
|
||||
'migration' => \App\Http\Middleware\EligibleForMigration::class,
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,7 +71,9 @@ class ApiCheck
|
|||
return $next($request);
|
||||
}
|
||||
|
||||
if (! Utils::hasFeature(FEATURE_API) && ! $hasApiSecret) {
|
||||
$isMobileApp = strpos(array_get($_SERVER, 'HTTP_USER_AGENT'), '(dart:io)') !== false;
|
||||
|
||||
if (! Utils::hasFeature(FEATURE_API) && ! $hasApiSecret && ! $isMobileApp) {
|
||||
$error['error'] = ['message' => 'API requires pro plan'];
|
||||
|
||||
return Response::json($error, 403, $headers);
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ class Authenticate
|
|||
Session::put('contact_key', $contact->contact_key);
|
||||
}
|
||||
if (! $contact) {
|
||||
return \Redirect::to('client/session_expired');
|
||||
return \Redirect::to('client/login');
|
||||
}
|
||||
|
||||
$account = $contact->account;
|
||||
|
|
|
|||
24
app/Http/Middleware/EligibleForMigration.php
Normal file
24
app/Http/Middleware/EligibleForMigration.php
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
|
||||
class EligibleForMigration
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
if (auth()->user()->eligibleForMigration()) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
return redirect('/settings/account_management');
|
||||
}
|
||||
}
|
||||
|
|
@ -72,8 +72,8 @@ class StartupCheck
|
|||
$file = storage_path() . '/version.txt';
|
||||
$version = @file_get_contents($file);
|
||||
if ($version != NINJA_VERSION) {
|
||||
if (version_compare(phpversion(), '7.0.0', '<')) {
|
||||
dd('Please update PHP to >= 7.0.0');
|
||||
if (version_compare(phpversion(), '7.1.0', '<')) {
|
||||
dd('Please update PHP to >= 7.1.0');
|
||||
}
|
||||
$handle = fopen($file, 'w');
|
||||
fwrite($handle, NINJA_VERSION);
|
||||
|
|
@ -95,7 +95,7 @@ class StartupCheck
|
|||
Session::put(SESSION_COUNTER, ++$count);
|
||||
|
||||
if (Utils::isNinja()) {
|
||||
if ($coupon = request()->coupon) {
|
||||
if (($coupon = request()->coupon) && ! $company->hasActivePlan()) {
|
||||
if ($code = config('ninja.coupon_50_off')) {
|
||||
if (hash_equals($coupon, $code)) {
|
||||
$company->applyDiscount(.5);
|
||||
|
|
|
|||
|
|
@ -42,7 +42,11 @@ class CreateOnlinePaymentRequest extends Request
|
|||
$input['invitation'] = $invitation;
|
||||
|
||||
if ($gatewayTypeAlias = request()->gateway_type) {
|
||||
if ($gatewayTypeAlias != GATEWAY_TYPE_TOKEN) {
|
||||
$input['gateway_type'] = GatewayType::getIdFromAlias($gatewayTypeAlias);
|
||||
} else {
|
||||
$input['gateway_type'] = $gatewayTypeAlias;
|
||||
}
|
||||
} else {
|
||||
$input['gateway_type'] = session($invitation->id . 'gateway_type');
|
||||
}
|
||||
|
|
|
|||
31
app/Http/Requests/MigrationAuthRequest.php
Normal file
31
app/Http/Requests/MigrationAuthRequest.php
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class MigrationAuthRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'email' => 'required|email',
|
||||
'password' => 'required',
|
||||
];
|
||||
}
|
||||
}
|
||||
30
app/Http/Requests/MigrationCompaniesRequest.php
Normal file
30
app/Http/Requests/MigrationCompaniesRequest.php
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class MigrationCompaniesRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'companies' => 'required',
|
||||
];
|
||||
}
|
||||
}
|
||||
30
app/Http/Requests/MigrationEndpointRequest.php
Normal file
30
app/Http/Requests/MigrationEndpointRequest.php
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class MigrationEndpointRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'endpoint' => 'required|url',
|
||||
];
|
||||
}
|
||||
}
|
||||
30
app/Http/Requests/MigrationTypeRequest.php
Normal file
30
app/Http/Requests/MigrationTypeRequest.php
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class MigrationTypeRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'option' => 'required|in:0,1',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -39,23 +39,4 @@ class RegisterRequest extends Request
|
|||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
public function response(array $errors)
|
||||
{
|
||||
/* If the user is not validating from a mobile app - pass through parent::response */
|
||||
if (! isset($this->req->api_secret)) {
|
||||
return parent::response($errors);
|
||||
}
|
||||
|
||||
/* If the user is validating from a mobile app - pass through first error string and return error */
|
||||
foreach ($errors as $error) {
|
||||
foreach ($error as $key => $value) {
|
||||
$message['error'] = ['message' => $value];
|
||||
$message = json_encode($message, JSON_PRETTY_PRINT);
|
||||
$headers = Utils::getApiHeaders();
|
||||
|
||||
return Response::make($message, 400, $headers);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@
|
|||
namespace App\Http\Requests;
|
||||
|
||||
use App\Libraries\Utils;
|
||||
use Illuminate\Contracts\Validation\Validator;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Response;
|
||||
|
||||
// https://laracasts.com/discuss/channels/general-discussion/laravel-5-modify-input-before-validation/replies/34366
|
||||
|
|
@ -54,22 +56,20 @@ abstract class Request extends FormRequest
|
|||
return $this->all();
|
||||
}
|
||||
|
||||
public function response(array $errors)
|
||||
protected function failedValidation(Validator $validator)
|
||||
{
|
||||
/* If the user is not validating from a mobile app - pass through parent::response */
|
||||
if (! request()->api_secret) {
|
||||
return parent::response($errors);
|
||||
if ( ! request()->api_secret) {
|
||||
parent::failedValidation($validator);
|
||||
}
|
||||
|
||||
/* If the user is validating from a mobile app - pass through first error string and return error */
|
||||
foreach ($errors as $error) {
|
||||
foreach ($error as $key => $value) {
|
||||
if ($value = $validator->getMessageBag()->first()) {
|
||||
$message['error'] = ['message' => $value];
|
||||
$message = json_encode($message, JSON_PRETTY_PRINT);
|
||||
$headers = Utils::getApiHeaders();
|
||||
|
||||
return Response::make($message, 400, $headers);
|
||||
}
|
||||
throw new ValidationException($validator, Response::make($message, 400, $headers));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ class SaveClientPortalSettings extends Request
|
|||
} else {
|
||||
$iframeURL = substr(strtolower($input['iframe_url']), 0, MAX_IFRAME_URL_LENGTH);
|
||||
$iframeURL = preg_replace('/[^a-zA-Z0-9_\-\:\/\.]/', '', $iframeURL);
|
||||
$input['iframe_url'] = rtrim($iframeURL, '/');
|
||||
$input['iframe_url'] = $iframeURL;
|
||||
$input['subdomain'] = null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ class GenerateStatementData
|
|||
$item->product_key = $ageGroups['age_group_0'];
|
||||
$item->notes = $ageGroups['age_group_30'];
|
||||
$item->custom_value1 = $ageGroups['age_group_60'];
|
||||
$item->custom_value1 = $ageGroups['age_group_90'];
|
||||
$item->custom_value2 = $ageGroups['age_group_90'];
|
||||
$item->cost = $ageGroups['age_group_120'];
|
||||
$item->invoice_item_type_id = 4;
|
||||
$data->push($item);
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Jobs\Job;
|
||||
use App\Libraries\CurlUtils;
|
||||
use Utils;
|
||||
|
||||
class ConvertProposalToPdf extends Job
|
||||
{
|
||||
|
|
@ -14,11 +14,36 @@ class ConvertProposalToPdf extends Job
|
|||
|
||||
public function handle()
|
||||
{
|
||||
$proposal = $this->proposal;
|
||||
$url = $proposal->getHeadlessLink();
|
||||
if (! env('PHANTOMJS_CLOUD_KEY') && ! env('PHANTOMJS_BIN_PATH')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Utils::isTravis()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$proposal = $this->proposal;
|
||||
$link = $proposal->getLink(true, true);
|
||||
$phantomjsSecret = env('PHANTOMJS_SECRET');
|
||||
$phantomjsLink = sprintf('%s?phantomjs=true&phantomjs_secret=%s', $link, $phantomjsSecret);
|
||||
$filename = sprintf('%s/storage/app/%s.pdf', base_path(), strtolower(str_random(RANDOM_KEY_LENGTH)));
|
||||
$pdf = CurlUtils::renderPDF($url, $filename);
|
||||
|
||||
try {
|
||||
$pdf = CurlUtils::renderPDF($phantomjsLink, $filename);
|
||||
|
||||
if (! $pdf && ($key = env('PHANTOMJS_CLOUD_KEY'))) {
|
||||
$url = "http://api.phantomjscloud.com/api/browser/v2/{$key}/?request=%7Burl:%22{$link}?phantomjs=true%26phantomjs_secret={$phantomjsSecret}%22,renderType:%22pdf%22%7D";
|
||||
$pdf = CurlUtils::get($url);
|
||||
}
|
||||
} catch (\Exception $exception) {
|
||||
Utils::logError("PhantomJS - Failed to load {$phantomjsLink}: {$exception->getMessage()}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $pdf || strlen($pdf) < 200) {
|
||||
Utils::logError("PhantomJS - Invalid response {$phantomjsLink}: {$pdf}");
|
||||
return false;
|
||||
}
|
||||
|
||||
return $pdf;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,6 +69,16 @@ class LoadPostmarkHistory extends Job
|
|||
$details = $this->postmark->getOutboundMessageDetails($message['MessageID']);
|
||||
$str .= sprintf('<b>%s</b><br/>', $details['subject']);
|
||||
|
||||
$event = $details['messageevents'][0];
|
||||
$str .= sprintf('%s | %s<br/>', $event['Type'], $this->account->getDateTime($event['ReceivedAt'], true));
|
||||
if ($message = $event['Details']['DeliveryMessage']) {
|
||||
$str .= sprintf('<span class="text-muted">%s</span><br/>', $message);
|
||||
}
|
||||
if ($server = $event['Details']['DestinationServer']) {
|
||||
$str .= sprintf('<span class="text-muted">%s</span><br/>', $server);
|
||||
}
|
||||
|
||||
/*
|
||||
if (count($details['messageevents'])) {
|
||||
$event = $details['messageevents'][0];
|
||||
$str .= sprintf('%s | %s<br/>', $event['Type'], $this->account->getDateTime($event['ReceivedAt'], true));
|
||||
|
|
@ -81,6 +91,7 @@ class LoadPostmarkHistory extends Job
|
|||
} else {
|
||||
$str .= trans('texts.processing') . '...';
|
||||
}
|
||||
*/
|
||||
|
||||
$str .= '<p/>';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -989,6 +989,38 @@ class Utils
|
|||
return EVENT_CREATE_PAYMENT;
|
||||
} elseif ($eventName == 'create_vendor') {
|
||||
return EVENT_CREATE_VENDOR;
|
||||
} elseif ($eventName == 'update_quote') {
|
||||
return EVENT_UPDATE_QUOTE;
|
||||
} elseif ($eventName == 'delete_quote') {
|
||||
return EVENT_DELETE_QUOTE;
|
||||
} elseif ($eventName == 'update_invoice') {
|
||||
return EVENT_UPDATE_INVOICE;
|
||||
} elseif ($eventName == 'delete_invoice') {
|
||||
return EVENT_DELETE_INVOICE;
|
||||
} elseif ($eventName == 'update_client') {
|
||||
return EVENT_UPDATE_CLIENT;
|
||||
} elseif ($eventName == 'delete_client') {
|
||||
return EVENT_DELETE_CLIENT;
|
||||
} elseif ($eventName == 'delete_payment') {
|
||||
return EVENT_DELETE_PAYMENT;
|
||||
} elseif ($eventName == 'update_vendor') {
|
||||
return EVENT_UPDATE_VENDOR;
|
||||
} elseif ($eventName == 'delete_vendor') {
|
||||
return EVENT_DELETE_VENDOR;
|
||||
} elseif ($eventName == 'create_expense') {
|
||||
return EVENT_CREATE_EXPENSE;
|
||||
} elseif ($eventName == 'update_expense') {
|
||||
return EVENT_UPDATE_EXPENSE;
|
||||
} elseif ($eventName == 'delete_expense') {
|
||||
return EVENT_DELETE_EXPENSE;
|
||||
} elseif ($eventName == 'create_task') {
|
||||
return EVENT_CREATE_TASK;
|
||||
} elseif ($eventName == 'update_task') {
|
||||
return EVENT_UPDATE_TASK;
|
||||
} elseif ($eventName == 'delete_task') {
|
||||
return EVENT_DELETE_TASK;
|
||||
} elseif ($eventName == 'approve_quote') {
|
||||
return EVENT_APPROVE_QUOTE;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1199,6 +1231,7 @@ class Utils
|
|||
return '';
|
||||
}
|
||||
|
||||
$link = e($link);
|
||||
$title = $link;
|
||||
if (substr($link, 0, 4) != 'http') {
|
||||
$link = 'http://' . $link;
|
||||
|
|
|
|||
|
|
@ -263,7 +263,7 @@ class SubscriptionListener
|
|||
$jsonData = $manager->createData($resource)->toArray();
|
||||
|
||||
// For legacy Zapier support
|
||||
if (isset($jsonData['client_id'])) {
|
||||
if (isset($jsonData['client_id']) && $jsonData['client_id'] != 0) {
|
||||
$jsonData['client_name'] = $entity->client->getDisplayName();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -245,37 +245,91 @@ class Account extends Eloquent
|
|||
];
|
||||
|
||||
public static $customLabels = [
|
||||
'address1',
|
||||
'address2',
|
||||
'amount',
|
||||
'amount_paid',
|
||||
'balance',
|
||||
'balance_due',
|
||||
'blank',
|
||||
'city_state_postal',
|
||||
'client_name',
|
||||
'company_name',
|
||||
'contact_name',
|
||||
'country',
|
||||
'credit_card',
|
||||
'credit_date',
|
||||
'credit_issued_to',
|
||||
'credit_note',
|
||||
'credit_number',
|
||||
'credit_to',
|
||||
'custom_value1',
|
||||
'custom_value2',
|
||||
'date',
|
||||
'delivery_note',
|
||||
'description',
|
||||
'details',
|
||||
'discount',
|
||||
'due_date',
|
||||
'gateway_fee_item',
|
||||
'email',
|
||||
'from',
|
||||
'gateway_fee_description',
|
||||
'gateway_fee_discount_description',
|
||||
'gateway_fee_item',
|
||||
'hours',
|
||||
'id_number',
|
||||
'invoice',
|
||||
'invoice_date',
|
||||
'invoice_due_date',
|
||||
'invoice_issued_to',
|
||||
'invoice_no',
|
||||
'invoice_number',
|
||||
'invoice_to',
|
||||
'invoice_total',
|
||||
'item',
|
||||
'line_total',
|
||||
'method',
|
||||
'outstanding',
|
||||
'paid_to_date',
|
||||
'partial_due',
|
||||
'payment_date',
|
||||
'phone',
|
||||
'po_number',
|
||||
'postal_city_state',
|
||||
'product_key',
|
||||
'quantity',
|
||||
'quote',
|
||||
'quote_date',
|
||||
'quote_due_date',
|
||||
'quote_issued_to',
|
||||
'quote_no',
|
||||
'quote_number',
|
||||
'quote_to',
|
||||
'rate',
|
||||
'reference',
|
||||
'service',
|
||||
'statement',
|
||||
'statement_date',
|
||||
'statement_issued_to',
|
||||
'statement_to',
|
||||
'subtotal',
|
||||
'surcharge',
|
||||
'tax',
|
||||
'tax_invoice',
|
||||
'tax_quote',
|
||||
'taxes',
|
||||
'terms',
|
||||
'to',
|
||||
'total',
|
||||
'unit_cost',
|
||||
'valid_until',
|
||||
'vat_number',
|
||||
'website',
|
||||
'work_phone',
|
||||
'your_credit',
|
||||
'your_invoice',
|
||||
'your_quote',
|
||||
'your_statement',
|
||||
];
|
||||
|
||||
public static $customMessageTypes = [
|
||||
|
|
@ -368,6 +422,14 @@ class Account extends Eloquent
|
|||
return $this->hasMany('App\Models\TaxRate');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function task_statuses()
|
||||
{
|
||||
return $this->hasMany('App\Models\TaskStatus')->orderBy('sort_order');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ class AccountGatewaySettings extends EntityModel
|
|||
|
||||
public function hasTaxes()
|
||||
{
|
||||
return floatval($this->fee_tax_rate1) || floatval($this->fee_tax_rate1);
|
||||
return floatval($this->fee_tax_rate1) || floatval($this->fee_tax_rate2);
|
||||
}
|
||||
|
||||
public function feesToString()
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@ class Activity extends Eloquent
|
|||
$user = $this->user;
|
||||
$invoice = $this->invoice;
|
||||
$contactId = $this->contact_id;
|
||||
$contact = $this->contact;
|
||||
$payment = $this->payment;
|
||||
$credit = $this->credit;
|
||||
$expense = $this->expense;
|
||||
|
|
@ -126,7 +127,7 @@ class Activity extends Eloquent
|
|||
'user' => $isSystem ? '<i>' . trans('texts.system') . '</i>' : e($user->getDisplayName()),
|
||||
'invoice' => $invoice ? link_to($invoice->getRoute(), $invoice->getDisplayName()) : null,
|
||||
'quote' => $invoice ? link_to($invoice->getRoute(), $invoice->getDisplayName()) : null,
|
||||
'contact' => $contactId ? link_to($client->getRoute(), $client->getDisplayName()) : e($user->getDisplayName()),
|
||||
'contact' => $contactId ? link_to($client->getRoute(), $contact->getDisplayName()) : e($user->getDisplayName()),
|
||||
'payment' => $payment ? e($payment->transaction_reference) : null,
|
||||
'payment_amount' => $payment ? $account->formatMoney($payment->amount, $payment) : null,
|
||||
'adjustment' => $this->adjustment ? $account->formatMoney($this->adjustment, $this) : null,
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ class Company extends Eloquent
|
|||
|
||||
public function hasActivePlan()
|
||||
{
|
||||
return Carbon::parse($this->plan_expires) >= Carbon::today();
|
||||
return $this->plan_expires && Carbon::parse($this->plan_expires) >= Carbon::today();
|
||||
}
|
||||
|
||||
public function hasExpiredPlan($plan)
|
||||
|
|
@ -202,7 +202,7 @@ class Company extends Eloquent
|
|||
$this->promo_expires = date_create()->modify('3 days')->format('Y-m-d');
|
||||
}
|
||||
|
||||
public function applyFreeYear()
|
||||
public function applyFreeYear($numYears = 1)
|
||||
{
|
||||
if ($this->plan_started && $this->plan_started != '0000-00-00') {
|
||||
return;
|
||||
|
|
@ -213,7 +213,7 @@ class Company extends Eloquent
|
|||
$this->plan_price = PLAN_PRICE_PRO_MONTHLY;
|
||||
$this->plan_started = date_create()->format('Y-m-d');
|
||||
$this->plan_paid = date_create()->format('Y-m-d');
|
||||
$this->plan_expires = date_create()->modify('1 year')->format('Y-m-d');
|
||||
$this->plan_expires = date_create()->modify($numYears . ' year')->format('Y-m-d');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue