Compare commits

..

188 Commits

Author SHA1 Message Date
dea73bcdf8 Refactor mail-sender 2023-03-17 18:54:58 +05:30
aececb8575 refactor mail sender 2023-03-16 11:48:15 +05:30
2bea727d19 fix migration and api changes 2023-03-14 17:45:58 +05:30
aede1f76d0 connect mail sender with api 2023-03-14 12:59:58 +05:30
959aa257b4 add mail sender in setting 2023-03-11 18:58:59 +05:30
b4aa254b68 add mail-sender abilities 2023-03-11 15:31:05 +05:30
c1f2af5174 add mail sender crud 2023-03-11 12:39:48 +05:30
05d5ce26fd Adds Zip to the required PHP Extension check (#1146)
* Ignore .DS_Store

* Checks for required ZIP extension
2023-02-19 11:42:34 +05:30
393fe20010 Fix incorrect invoice creation message (#1109)
Creation message now includes the views disk in its path.
2022-12-17 18:20:51 +05:30
57bdbd2897 feat: front-end files bulding (#1098)
Co-authored-by: Aramayis <>
2022-12-17 18:19:49 +05:30
7447cc24f9 feat: uffizzi integration (#1091)
Co-authored-by: Aramayis <>
2022-11-21 18:35:59 +05:30
889d22d92c Update php version (#1083)
Version 7.4 no longer works when running docker-compose/setup.sh. Updated to 8.1 in Dockerfile. Issue is now resolved and Crater sets up as expected without version conflicts.
2022-11-03 19:29:16 +05:30
bc8f2cd484 fix tax issue (#953)
* fix tax issue

* remove console log

* update cs fixer package
2022-10-26 19:51:36 +05:30
4e47f58bad fixed - No query results for model [Crater\Models\Currency] (#1070)
* fixed report pdf issue

* Removed telescope service provider file
2022-10-26 19:33:25 +05:30
d8c429912e fix composer issue with pest 2022-10-17 13:04:27 +05:30
0aaf0e7e75 Adding object-fit rules so thumbnail image does not appear stretched. (#1065) 2022-10-13 23:44:39 +05:30
3d0b89bb4d bug: fix missing hyphen in setup.sh (#1067)
Co-authored-by: Aramayis <>
2022-10-13 23:40:07 +05:30
38c4b9ebce Patch setup.sh script to deploy without plugins error. (#1034) 2022-09-13 14:52:51 +05:30
7be59e78e0 Formatting + Netherlands rename (#973)
* Formatting + Netherlands rename

* Revert change
2022-07-09 20:03:08 +05:30
204483836a 🌐Update: support Thai language (#768)
* add thai language config

* update thai translation

* update thai translation

* add THSarabunNew fonts to using in pdf

* update: pdf file to support thai language by checking isLocale('th') and include thai font-family

* update: thai translation

* remove the index.php file (not used)

* move THSarabunNew fonts to resoureces/static/fonts

* Add .gitkeep to storage/fonts to pre-build the fonts directory

Co-authored-by: Ritthikrai (Champ) Wiengchai <ritthikrai.w@aware.co.th>
2022-06-15 18:17:36 +05:30
33bc9ded65 Bump guzzlehttp/guzzle from 7.4.1 to 7.4.3 (#936)
Bumps [guzzlehttp/guzzle](https://github.com/guzzle/guzzle) from 7.4.1 to 7.4.3.
- [Release notes](https://github.com/guzzle/guzzle/releases)
- [Changelog](https://github.com/guzzle/guzzle/blob/master/CHANGELOG.md)
- [Commits](https://github.com/guzzle/guzzle/compare/7.4.1...7.4.3)

---
updated-dependencies:
- dependency-name: guzzlehttp/guzzle
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-05 14:10:27 +05:30
4271ef451e Php 8 compliant (#914) 2022-06-05 14:09:42 +05:30
6eb44fba93 Update cron.dockerfile (#949) 2022-06-05 14:09:11 +05:30
96e7300583 Make unit clearable (#918) 2022-05-28 11:55:09 +05:30
a479d966d1 Update en.json (#925)
Corrected sentence case from "Expiry date" to "Expiry Date"
2022-05-28 11:54:32 +05:30
bca2794c4c New Crowdin updates (#924)
* New translations en.json (Spanish)

* New translations en.json (Portuguese, Brazilian)
2022-05-24 12:54:42 +05:30
cb88c19059 New Crowdin updates (#910)
* New translations en.json (Lithuanian)

* New translations en.json (Greek)

* New translations en.json (Greek)

* New translations en.json (Greek)
2022-05-03 16:34:03 +05:30
946c7efab4 Also replace variables in subject (#893) 2022-04-25 18:02:21 +05:30
d7b1d15f93 New Crowdin updates (#877)
* New translations en.json (Hindi)

* New translations en.json (Hindi)

* New translations en.json (Hindi)

* New translations en.json (Hindi)

* New translations en.json (Hindi)

* New translations en.json (Russian)

* New translations en.json (Russian)

* New translations en.json (German)

* New translations en.json (Russian)

* New translations en.json (Spanish)

* New translations en.json (Spanish)

* New translations en.json (Czech)

* New translations en.json (Czech)

* New translations en.json (Spanish)

* New translations en.json (Spanish)

* New translations en.json (French)

* New translations en.json (French)

* New translations en.json (German)

* New translations en.json (Indonesian)

* New translations en.json (Indonesian)

* New translations en.json (Indonesian)

* New translations en.json (Indonesian)
2022-04-25 14:19:41 +05:30
94e1efe115 New Crowdin updates (#875)
* New translations en.json (Hindi)

* New translations en.json (Hindi)
2022-04-01 17:32:59 +05:30
b0e38b74e9 Bump guzzlehttp/psr7 from 2.1.0 to 2.2.1 (#871)
Bumps [guzzlehttp/psr7](https://github.com/guzzle/psr7) from 2.1.0 to 2.2.1.
- [Release notes](https://github.com/guzzle/psr7/releases)
- [Changelog](https://github.com/guzzle/psr7/blob/master/CHANGELOG.md)
- [Commits](https://github.com/guzzle/psr7/compare/2.1.0...2.2.1)

---
updated-dependencies:
- dependency-name: guzzlehttp/psr7
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-01 17:32:43 +05:30
157559cc05 Improve invoice 1 template css (#872)
There is no class "header" that's why I removed it. Also I fixed some whitespace issues
2022-04-01 13:50:27 +05:30
9d1484d62d Completed missing french translations (#852) 2022-04-01 10:10:18 +05:30
654dd9e64d New Crowdin updates (#874)
* New translations en.json (Hindi)

* New translations en.json (Hindi)
2022-04-01 10:09:02 +05:30
7cde971f8b Module upload validation (#857)
https://huntr.dev/bounties/cb9a0393-be34-4021-a06c-00c7791c7622/
2022-03-29 12:55:35 +05:30
4e7441a5cf add default support for Central African currency (#865) 2022-03-28 13:58:43 +05:30
b714833b06 New Crowdin updates (#862)
* New translations en.json (Russian)

* New translations en.json (Russian)

* New translations en.json (Dutch)

* New translations en.json (Dutch)
2022-03-28 13:58:23 +05:30
88035ea490 Expense attachment validation fix (#855)
https://huntr.dev/bounties/4d7d4fc9-e0cf-42d3-b89c-6ea57a769045/
2022-03-22 16:58:55 +05:30
80847529fa New Crowdin updates (#833)
* New translations en.json (Turkish)

* New translations en.json (Turkish)

* New translations en.json (Turkish)

* New translations en.json (Spanish)

* New translations en.json (Indonesian)
2022-03-20 21:06:58 +05:30
18507ddb6f new build 606 2022-03-06 12:39:21 +05:30
7275f3dd47 Merge branch 'fix-latest-issues' into 'master'
Fix tax per item issue & check currency key

See merge request mohit.panjvani/crater-web!1460
2022-03-06 07:02:58 +00:00
e31f947aba fix conflict 2022-03-06 12:32:24 +05:30
7ba120db31 Merge branch 'fix-invoice-item-create-issue' into 'master'
fix item is not created in invoice->add new item modal

See merge request mohit.panjvani/crater-web!1462
2022-03-06 06:59:07 +00:00
bc73ed8c9e fix conflict 2022-03-06 12:28:51 +05:30
b1689dd2c6 Merge branch 'master' of https://github.com/bytefury/crater 2022-03-06 12:25:44 +05:30
f6c59b7423 New Crowdin updates (#817)
* New translations en.json (Finnish)

* New translations en.json (Italian)

* New translations en.json (Vietnamese)
2022-03-06 10:05:31 +05:30
2cadcad485 Fix currency settings error (#821)
* Fixed issue with currency error on change after transactions

* organized imports
2022-03-06 09:53:31 +05:30
04deade111 fix item is not created in invoice->add new item modal 2022-03-04 17:15:23 +05:30
ee0632f0d3 Merge branch 'fix-default-language-filter-issue' into 'master'
fixed search filter is not working

See merge request mohit.panjvani/crater-web!1461
2022-03-04 10:47:23 +00:00
f6771dafd3 fixed search filter is not working 2022-03-04 15:00:56 +05:30
fadef0ea07 Fix tax per item issue & check currency key 2022-03-04 12:08:03 +05:30
c07e44520a typo: somthing -> something (#820) 2022-03-04 08:21:17 +05:30
6552c4edd6 New translations en.json (Finnish) (#816) 2022-03-03 23:10:11 +05:30
5183a825e6 New Crowdin updates (#815)
* New translations en.json (Romanian)

* New translations en.json (Slovak)

* New translations en.json (Hindi)

* New translations en.json (Latvian)

* New translations en.json (Croatian)

* New translations en.json (Persian)

* New translations en.json (Indonesian)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Vietnamese)

* New translations en.json (Chinese Traditional)

* New translations en.json (Turkish)

* New translations en.json (Swedish)

* New translations en.json (Slovenian)

* New translations en.json (Russian)

* New translations en.json (French)

* New translations en.json (Portuguese)

* New translations en.json (Polish)

* New translations en.json (Dutch)

* New translations en.json (Lithuanian)

* New translations en.json (Japanese)

* New translations en.json (Italian)

* New translations en.json (Finnish)

* New translations en.json (Greek)

* New translations en.json (German)

* New translations en.json (Czech)

* New translations en.json (Arabic)

* New translations en.json (Spanish)

* New translations en.json (Serbian (Latin))
2022-03-03 20:39:44 +05:30
83a7c97e9e fix tests 2022-03-03 20:30:05 +05:30
eea3925fcd new build 605 2022-03-03 17:03:40 +05:30
9a0de9f64f update message and formatting 2022-03-03 17:02:38 +05:30
a23644c9d8 Merge branch 'master' 2022-03-03 16:57:59 +05:30
b884001e87 update comments 2022-03-03 16:57:52 +05:30
bb54edc147 Merge branch 'fix-issue-6.5' into 'master'
Fix issue 6.5

See merge request mohit.panjvani/crater-web!1459
2022-03-03 11:27:12 +00:00
25c43ab4d2 Fix condition 2022-03-03 15:10:53 +05:30
388d00241b Fix recurring invoice condition 2022-03-03 14:07:19 +05:30
69d8c95557 Minor fixes 2022-03-03 13:25:12 +05:30
ea9748ca68 Endpoint to check company currency transaction 2022-03-03 12:56:46 +05:30
66a5501bd2 Solve due date issue in recurring invoice 2022-03-03 12:56:10 +05:30
c897521137 Added preview to invoice, estimate & payment pdfs 2022-03-03 12:55:57 +05:30
adf4b3a74d Merge branch 'change-frequency-label' into 'master'
change label

See merge request mohit.panjvani/crater-web!1458
2022-03-03 07:25:45 +00:00
cc737593b7 Added migration to Change over due status 2022-03-03 12:55:15 +05:30
9356c309a2 change label 2022-03-03 12:49:29 +05:30
35da80103e new build 605 2022-03-03 11:43:03 +05:30
99f553f1c3 fix formatting issues and create update migration 2022-03-03 11:32:30 +05:30
3f4305c7cb Merge branch 'master' of https://github.com/bytefury/crater 2022-03-03 11:30:31 +05:30
a678b4d272 New Crowdin translation updates (#796)
* New translations en.json (Polish)

* New translations en.json (Italian)

* New translations en.json (Italian)

* New translations en.json (Romanian)

* New translations en.json (Slovak)

* New translations en.json (Hindi)

* New translations en.json (Latvian)

* New translations en.json (Croatian)

* New translations en.json (Persian)

* New translations en.json (Indonesian)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Vietnamese)

* New translations en.json (Chinese Traditional)

* New translations en.json (Turkish)

* New translations en.json (Swedish)

* New translations en.json (Slovenian)

* New translations en.json (Russian)

* New translations en.json (French)

* New translations en.json (Portuguese)

* New translations en.json (Polish)

* New translations en.json (Dutch)

* New translations en.json (Lithuanian)

* New translations en.json (Japanese)

* New translations en.json (Italian)

* New translations en.json (Finnish)

* New translations en.json (Greek)

* New translations en.json (German)

* New translations en.json (Czech)

* New translations en.json (Arabic)

* New translations en.json (Spanish)

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

* Update source file en.json

* New translations en.json (German)

* New translations en.json (Vietnamese)

* New translations en.json (Italian)

* New translations en.json (Croatian)
2022-03-03 11:27:04 +05:30
0c83456866 Merge branch 'add-overdue' into 'master'
add overdue

See merge request mohit.panjvani/crater-web!1457
2022-03-03 05:38:56 +00:00
2cb51b84c7 add overdue 2022-03-02 18:09:41 +05:30
3908878109 Merge branch 'fix-delete-condition' into 'master'
Fix transaction delete issue

See merge request mohit.panjvani/crater-web!1456
2022-03-02 07:28:40 +00:00
6970a4f243 Merge branch 'dialog-issue-fix' into 'master'
fix responsive issue

See merge request mohit.panjvani/crater-web!1455
2022-03-02 07:24:38 +00:00
5720803116 Fix transaction delete issue 2022-03-02 12:17:31 +05:30
351e6617d9 fix responsive issue 2022-02-25 12:02:36 +05:30
ffa7906382 Merge branch 'document-title' into 'master'
add admin_document_title

See merge request mohit.panjvani/crater-web!1447
2022-02-24 12:40:33 +00:00
2a43ec9e9d fix minor issues 2022-02-24 11:29:32 +05:30
48f9da14ba remove unwanted code 2022-02-24 11:06:20 +05:30
4153d10a0a Merge branch 'fix-v6.4-issues' into 'master'
fix invoice status condition
2022-02-24 05:22:32 +00:00
1322ed15dc - fix invoice status not updating issue
- date range selection issues on report for non-english languages
- fix invoice all tab issue (always showing draft even in all tab)
2022-02-24 05:22:32 +00:00
a452ec5eaf add migration 2022-02-23 18:34:12 +05:30
b819307612 refactor helpers 2022-02-23 17:58:07 +05:30
7202fdcbf2 new build 2022-02-22 12:31:28 +05:30
c28fc073e4 fix responsive cropping issue for ios 2022-02-22 12:29:37 +05:30
439fc4e002 add fields 2022-02-21 18:34:04 +05:30
ca15751a5f Merge branch 'master' 2022-02-21 13:34:54 +05:30
50637f5f7c Merge branch 'master' of https://github.com/bytefury/crater 2022-02-21 13:34:32 +05:30
e3219baad2 Merge branch 'fix-pdf-guard-issue' into 'master'
fix guard issue

See merge request mohit.panjvani/crater-web!1449
2022-02-21 07:57:25 +00:00
8de8a07e3e fix guard issue 2022-02-21 13:20:58 +05:30
ab28ba38ce Merge branch 'master' 2022-02-21 10:48:54 +05:30
7a27317f1a Merge branch 'fix-master-issues' into 'master'
Fix master issues

See merge request mohit.panjvani/crater-web!1446
2022-02-19 09:50:46 +00:00
ab153963e4 Fix master issues 2022-02-19 09:50:46 +00:00
65dd1eca01 change default currency to USD 2022-02-19 13:49:38 +05:30
bed05fc21f Arrange Invoice, Recurring Invoices and Estimate Tab groups for a more natural flow (#803) 2022-02-19 09:59:37 +05:30
0578122fc3 add admin_document_title 2022-02-18 12:21:13 +05:30
ce3db7d0c6 Update readme.md (#802)
Fix API Documentation URL
2022-02-18 06:56:27 +05:30
854ae10198 fix formatting 2022-02-17 14:10:36 +05:30
2383e89daa new build 604 2022-02-17 13:48:06 +05:30
6529fa1892 Merge branch 'fix-send-invoice-issue' into 'master'
fix send invoices and payments issues

See merge request mohit.panjvani/crater-web!1443
2022-02-17 08:15:40 +00:00
eef74fd2fc Merge branch 'change-logout-request' into 'master'
fixed customer logout error

See merge request mohit.panjvani/crater-web!1445
2022-02-17 08:14:43 +00:00
2f69b6fa71 fixed customer logout error 2022-02-17 12:39:10 +05:30
7170fb0cef fix send invoices and payments issues 2022-02-16 18:59:49 +05:30
db7a084a19 Merge branch 'set-default-country' into 'master'
set "United States" as default country for company in installation wizard

See merge request mohit.panjvani/crater-web!1442
2022-02-16 09:09:03 +00:00
e24a89fe39 set "United States" as default country for company in installtion wizard 2022-02-16 12:05:08 +05:30
6a3e9e132f fix formatting 2022-02-16 11:19:30 +05:30
68575b69b9 new build 603 2022-02-16 10:47:32 +05:30
8da5f99511 Merge branch 'master' 2022-02-16 10:44:08 +05:30
912dbdc1c5 Merge branch 'fix_all_customer_load' into 'master'
Fix all customer load

See merge request mohit.panjvani/crater-web!1435
2022-02-16 05:13:53 +00:00
771d396bfb Merge branch 'master' of gitlab.com:mohit.panjvani/crater-web into fix_all_customer_load 2022-02-15 18:41:57 +05:30
30dc428b1a apply scroll to load data changes in customer and fixed search with scroll to load data issue in estimate, invoice, recurring invoice and payments 2022-02-15 18:41:05 +05:30
6f7555bdce add 603 update migration 2022-02-15 17:30:33 +05:30
13cbb5439f fix search pagination 2022-02-15 13:01:29 +05:30
a0cd12913d fix template selection issue when image is not loaded 2022-02-15 12:16:01 +05:30
67e93dcb00 Merge branch 'mark-as-default-template' into 'master'
added mark as default changes in estimate and invoice template

See merge request mohit.panjvani/crater-web!1440
2022-02-15 06:16:57 +00:00
7427f8a4ac new build 2022-02-15 11:44:39 +05:30
10ceaa0e2c Merge branch 'master' 2022-02-15 11:40:59 +05:30
15256a19db Merge branch 'fix-download-expense-issue' into 'master'
fixed uploaded attachment not removed

See merge request mohit.panjvani/crater-web!1441
2022-02-15 06:10:04 +00:00
8ba84f68c7 fixed uploaded attachment not removed 2022-02-15 06:10:04 +00:00
9e23f9b9b1 fix search issues 2022-02-15 11:32:30 +05:30
7f4cdfffc6 New Crowdin updates (#791)
* New translations en.json (Romanian)

* New translations en.json (Slovak)

* New translations en.json (Hindi)

* New translations en.json (Latvian)

* New translations en.json (Croatian)

* New translations en.json (Persian)

* New translations en.json (Indonesian)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Vietnamese)

* New translations en.json (Chinese Traditional)

* New translations en.json (Turkish)

* New translations en.json (Swedish)

* New translations en.json (Slovenian)

* New translations en.json (Russian)

* New translations en.json (French)

* New translations en.json (Portuguese)

* New translations en.json (Polish)

* New translations en.json (Dutch)

* New translations en.json (Lithuanian)

* New translations en.json (Japanese)

* New translations en.json (Italian)

* New translations en.json (Finnish)

* New translations en.json (Greek)

* New translations en.json (German)

* New translations en.json (Czech)

* New translations en.json (Arabic)

* New translations en.json (Spanish)

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

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Portuguese)

* New translations en.json (German)
2022-02-15 11:03:54 +05:30
c9d6824f8c Fixed the "Download Expenses File" URL (#785)
* Fixed the download PDF URL

Added the "/reports" prefix to the "Download PDF" Button of the Create.vue file. Now the download redirect works correctly.

Fixed with @SpyrosKoustas.

* Fixed the missing "/reports" prefix

Added the missing "/reports" prefix so that the project must not be rebuilt.

Fixed with @Dimitris-Provatas

Co-authored-by: Spyros Georg Koustas <spyros.koustas@hotmail.com>
2022-02-15 11:00:31 +05:30
84f5890294 Merge branch 'fix_all_customer_load' 2022-02-15 10:51:54 +05:30
ab8089fe98 Merge branch 'master' into fix_all_customer_load 2022-02-15 10:45:50 +05:30
383ba65d7c Merge branch 'master' of https://github.com/bytefury/crater 2022-02-15 10:45:08 +05:30
f34dac9d3f Merge branch 'fix-recurring-invoice-issue' into 'master'
fixed send invoice modal not open in recurring invoice

See merge request mohit.panjvani/crater-web!1439
2022-02-15 05:13:40 +00:00
c3965613b5 Merge branch 'change-logout-request' into 'master'
change from "GET" to "POST" request for logout user or customer

See merge request mohit.panjvani/crater-web!1438
2022-02-15 05:13:22 +00:00
c07e309918 rename variable name 2022-02-14 15:16:26 +05:30
35e71ec110 fix select cutoff issue on invoice create 2022-02-14 14:28:28 +05:30
82dbee4794 Update Crowdin configuration file 2022-02-14 12:08:04 +05:30
d883e89819 fix formatting 2022-02-14 11:24:59 +05:30
00133d66c2 added mark as default changes in estimate and invoice template 2022-02-11 12:23:36 +05:30
e8a88dbad3 change customer logout route method 2022-02-11 09:51:41 +05:30
2b7028b7c8 changed route method 2022-02-11 09:39:11 +05:30
513d43d92f Merge branch 'master' 2022-02-10 11:50:40 +05:30
b781f11175 Merge branch 'master' of https://github.com/bytefury/crater 2022-02-10 11:50:13 +05:30
ca1aa604e3 fixed send modal not open in recurring invoice 2022-02-08 16:27:26 +05:30
e1e1157f2d change from "GET" to "POST" request for logout user or customer 2022-02-08 12:38:52 +05:30
3f2c774f91 refactor and apply scroll to load data in estimates, invoices and payments 2022-02-08 11:57:47 +05:30
9448677dad #773 PDF download naming (#778) 2022-02-08 07:40:14 +05:30
9709489c66 Merge branch 'fix-dashboard-issue' into 'master'
fix dashboard issue

See merge request mohit.panjvani/crater-web!1437
2022-02-07 12:28:53 +00:00
2fcc87180f fix dashboard issue 2022-02-05 18:16:10 +05:30
0c77562d0e minor refactor 2022-02-05 13:31:06 +05:30
5f53138dc5 fix: Poppins semi-bold font 404 error (#771) 2022-02-04 18:18:14 +05:30
2019e9be03 Merge branch 'text-align-extension' into 'master'
Add extension-text-align

See merge request mohit.panjvani/crater-web!1434
2022-02-03 13:36:54 +00:00
938e6f2a9f Merge branch 'fix-expense-receipt' into 'master'
fix receipt not uploading in expense

See merge request mohit.panjvani/crater-web!1436
2022-02-03 12:51:02 +00:00
c82dc94252 fix receipt not uploading in expense 2022-02-03 11:39:18 +05:30
c6b36d78b9 updated customer and category added in first in dropdown list 2022-02-02 18:57:38 +05:30
1d402e0143 add scroll to load invoices and fixed limited category and customer issue in payment and expense 2022-02-02 18:51:32 +05:30
75a0ed1ffc Add extension-text-align 2022-02-02 16:30:19 +05:30
577c015d14 Merge branch 'remove-link' into 'master'
remove links

See merge request mohit.panjvani/crater-web!1428
2022-02-02 09:33:41 +00:00
760f4566aa Customer view field resolve fix (#757)
* fixed customer view field resolve

* fixed customer view field resolve estimate payment
2022-01-29 00:07:26 +05:30
d7ec554eba Merge branch 'admin-portal-logo' into 'master'
add admin portal logo

See merge request mohit.panjvani/crater-web!1427
2022-01-28 07:11:54 +00:00
c194e98a7b add admin portal logo 2022-01-28 07:11:54 +00:00
6a34708a37 Update french translation (#723)
Missing translations
2022-01-27 17:24:03 +05:30
ab863a8d88 Added Czech language (#702) 2022-01-27 17:23:37 +05:30
f594556de0 Merge branch 'invoice-custom-field' into 'master'
fix custom field invoice issue

See merge request mohit.panjvani/crater-web!1432
2022-01-27 10:27:31 +00:00
30f36461c2 fix custom field invoice issue 2022-01-27 15:04:32 +05:30
980de6d492 remove links 2022-01-20 16:26:09 +05:30
ff3cd0f7b9 Customer avatar validation (#732)
* Customer avatar validation

https://huntr.dev/bounties/19f3e5f7-b419-44b1-9c37-7e4404cbec94/

* Customer avatar validation test

https://huntr.dev/bounties/19f3e5f7-b419-44b1-9c37-7e4404cbec94/
2022-01-19 17:08:34 +05:30
323b7d8ea6 Adds Nepali Rupee as currency (#675)
Co-authored-by: Ayush Jha <ayush.jha@wmcglobal.com>
2022-01-18 21:42:18 +05:30
dc7282d6e9 Added Macedonian Denar currency (#736)
* Add Macedonian Denar currency

* Typo fix

* Swap currency symbol
2022-01-18 21:35:18 +05:30
85836c3f9c Update readme.md (#719) 2022-01-13 21:48:16 +05:30
221184df41 new version 2022-01-13 18:19:56 +05:30
f313a8c164 new build 2022-01-13 18:07:39 +05:30
8f4fea3811 Merge branch 'error-message' into 'master'
add error message

See merge request mohit.panjvani/crater-web!1424
2022-01-13 11:52:23 +00:00
dca97e80e7 add error message 2022-01-13 17:19:27 +05:30
9e6b4bd862 Merge branch 'payment-method-delete' into 'master'
solve payment method delete issue

See merge request mohit.panjvani/crater-web!1423
2022-01-13 07:23:19 +00:00
55beed430a solve payment method delete issue 2022-01-13 12:25:46 +05:30
dd324c8bb6 solve payment method delete issue 2022-01-13 12:23:26 +05:30
e3f3809f2d fix formatting errors 2022-01-12 21:12:54 +05:30
d877b16776 add 601 migration 2022-01-12 18:59:35 +05:30
eac1bbb622 sync languages with crowdin 2022-01-12 17:58:25 +05:30
4ef5e7e54b Merge branch 'master' of https://github.com/bytefury/crater 2022-01-12 17:39:26 +05:30
e2bb414efe Fix HTML injection exploit (#682)
Escape html special characters from the $fields array to prevent html injection in the generated pdfs.
2022-01-12 17:39:15 +05:30
941bc4bdb8 Update dompdf version 2022-01-12 11:44:45 +00:00
a14655d73b Update dompdf version 2022-01-12 11:44:45 +00:00
d303b1a71c Patch on French translation (#711)
* Update fr.json

A few updates on some French translation typos (caps mostly).
"Item>Price" also needed to be more accurate, so just added "Unit price".

* Update fr.json

* Clarified pdf_price_label as it was ambiguous
* Translated PDF reports strings
* Few typos & missing words
2022-01-12 13:16:12 +05:30
f677a54c2a fix logo path 2022-01-12 12:33:42 +05:30
37fa96b29a Merge branch 'route' into 'master'
route issue fix

See merge request mohit.panjvani/crater-web!1417
2022-01-11 12:25:42 +00:00
147be77859 route issue fix 2022-01-11 17:27:55 +05:30
6ef2553423 Merge branch 'master' 2022-01-11 16:59:23 +05:30
22f6a48b5b fix csfixer warnings 2022-01-11 16:54:15 +05:30
897d759758 Merge branch 'payment-issue' into 'master'
solve payment issue

See merge request mohit.panjvani/crater-web!1415
2022-01-11 10:45:43 +00:00
bcd80377cf solve payment issue 2022-01-11 15:06:07 +05:30
474 changed files with 36691 additions and 9705 deletions

161
.github/workflows/uffizzi-build.yml vendored Normal file
View File

@ -0,0 +1,161 @@
name: Build PR Image
on:
pull_request:
types: [opened,synchronize,reopened,closed]
jobs:
build-application:
name: Build and Push `application`
runs-on: ubuntu-latest
if: ${{ github.event_name != 'pull_request' || github.event.action != 'closed' }}
outputs:
tags: ${{ steps.meta.outputs.tags }}
steps:
- name: Checkout git repo
uses: actions/checkout@v3
- name: Generate UUID image name
id: uuid
run: echo "UUID_TAG_APP=$(uuidgen)" >> $GITHUB_ENV
- name: Docker metadata
id: meta
uses: docker/metadata-action@v3
with:
images: registry.uffizzi.com/${{ env.UUID_TAG_APP }}
tags: type=raw,value=60d
- name: Build and Push Image to registry.uffizzi.com ephemeral registry
uses: docker/build-push-action@v2
with:
push: true
context: ./
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
file: ./uffizzi/Dockerfile
build-nginx:
needs:
- build-application
name: Build and Push `nginx`
runs-on: ubuntu-latest
if: ${{ github.event_name != 'pull_request' || github.event.action != 'closed' }}
outputs:
tags: ${{ steps.meta.outputs.tags }}
steps:
- name: Checkout git repo
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Generate UUID image name
id: uuid
run: echo "UUID_TAG_NGINX=$(uuidgen)" >> $GITHUB_ENV
- name: Docker metadata
id: meta
uses: docker/metadata-action@v3
with:
images: registry.uffizzi.com/${{ env.UUID_TAG_NGINX }}
tags: type=raw,value=60d
- name: Build and Push Image to Uffizzi ephemeral registry
uses: docker/build-push-action@v2
with:
push: true
context: ./
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
file: ./uffizzi/nginx/Dockerfile
build-args: |
BASE_IMAGE=${{ needs.build-application.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-crond:
name: Build and Push `crond`
runs-on: ubuntu-latest
if: ${{ github.event_name != 'pull_request' || github.event.action != 'closed' }}
outputs:
tags: ${{ steps.meta.outputs.tags }}
steps:
- name: Checkout git repo
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Generate UUID image name
id: uuid
run: echo "UUID_TAG_CROND=$(uuidgen)" >> $GITHUB_ENV
- name: Docker metadata
id: meta
uses: docker/metadata-action@v3
with:
images: registry.uffizzi.com/${{ env.UUID_TAG_CROND }}
tags: type=raw,value=60d
- name: Build and Push Image to registry.uffizzi.com ephemeral registry
uses: docker/build-push-action@v2
with:
push: true
context: ./
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
file: ./uffizzi/crond/Dockerfile
cache-from: type=gha
cache-to: type=gha,mode=max
render-compose-file:
name: Render Docker Compose File
# Pass output of this workflow to another triggered by `workflow_run` event.
runs-on: ubuntu-latest
outputs:
compose-file-cache-key: ${{ steps.hash.outputs.hash }}
needs:
- build-application
- build-nginx
- build-crond
steps:
- name: Checkout git repo
uses: actions/checkout@v3
- name: Render Compose File
run: |
APP_IMAGE=$(echo ${{ needs.build-application.outputs.tags }})
export APP_IMAGE
NGINX_IMAGE=$(echo ${{ needs.build-nginx.outputs.tags }})
export NGINX_IMAGE
CROND_IMAGE=$(echo ${{ needs.build-crond.outputs.tags }})
export CROND_IMAGE
# Render simple template from environment variables.
envsubst < ./uffizzi/docker-compose.uffizzi.yml > docker-compose.rendered.yml
cat docker-compose.rendered.yml
- name: Upload Rendered Compose File as Artifact
uses: actions/upload-artifact@v3
with:
name: preview-spec
path: docker-compose.rendered.yml
retention-days: 2
- name: Serialize PR Event to File
run: |
cat << EOF > event.json
${{ toJSON(github.event) }}
EOF
- name: Upload PR Event as Artifact
uses: actions/upload-artifact@v3
with:
name: preview-spec
path: event.json
retention-days: 2
delete-preview:
name: Call for Preview Deletion
runs-on: ubuntu-latest
if: ${{ github.event.action == 'closed' }}
steps:
# If this PR is closing, we will not render a compose file nor pass it to the next workflow.
- name: Serialize PR Event to File
run: echo '${{ toJSON(github.event) }}' > event.json
- name: Upload PR Event as Artifact
uses: actions/upload-artifact@v3
with:
name: preview-spec
path: event.json
retention-days: 2

84
.github/workflows/uffizzi-preview.yml vendored Normal file
View File

@ -0,0 +1,84 @@
name: Deploy Uffizzi Preview
on:
workflow_run:
workflows:
- "Build PR Image"
types:
- completed
jobs:
cache-compose-file:
name: Cache Compose File
runs-on: ubuntu-latest
outputs:
compose-file-cache-key: ${{ env.COMPOSE_FILE_HASH }}
pr-number: ${{ env.PR_NUMBER }}
steps:
- name: 'Download artifacts'
# Fetch output (zip archive) from the workflow run that triggered this workflow.
uses: actions/github-script@v6
with:
script: |
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.payload.workflow_run.id,
});
let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => {
return artifact.name == "preview-spec"
})[0];
let download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
});
let fs = require('fs');
fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/preview-spec.zip`, Buffer.from(download.data));
- name: 'Unzip artifact'
run: unzip preview-spec.zip
- name: Read Event into ENV
run: |
echo 'EVENT_JSON<<EOF' >> $GITHUB_ENV
cat event.json >> $GITHUB_ENV
echo 'EOF' >> $GITHUB_ENV
- name: Hash Rendered Compose File
id: hash
# If the previous workflow was triggered by a PR close event, we will not have a compose file artifact.
if: ${{ fromJSON(env.EVENT_JSON).action != 'closed' }}
run: echo "COMPOSE_FILE_HASH=$(md5sum docker-compose.rendered.yml | awk '{ print $1 }')" >> $GITHUB_ENV
- name: Cache Rendered Compose File
if: ${{ fromJSON(env.EVENT_JSON).action != 'closed' }}
uses: actions/cache@v3
with:
path: docker-compose.rendered.yml
key: ${{ env.COMPOSE_FILE_HASH }}
- name: Read PR Number From Event Object
id: pr
run: echo "PR_NUMBER=${{ fromJSON(env.EVENT_JSON).number }}" >> $GITHUB_ENV
- name: DEBUG - Print Job Outputs
if: ${{ runner.debug }}
run: |
echo "PR number: ${{ env.PR_NUMBER }}"
echo "Compose file hash: ${{ env.COMPOSE_FILE_HASH }}"
cat event.json
deploy-uffizzi-preview:
name: Use Remote Workflow to Preview on Uffizzi
needs:
- cache-compose-file
uses: UffizziCloud/preview-action/.github/workflows/reusable.yaml@v2.6.1
with:
# If this workflow was triggered by a PR close event, cache-key will be an empty string
# and this reusable workflow will delete the preview deployment.
compose-file-cache-key: ${{ needs.cache-compose-file.outputs.compose-file-cache-key }}
compose-file-cache-path: docker-compose.rendered.yml
server: https://app.uffizzi.com/
pr-number: ${{ needs.cache-compose-file.outputs.pr-number }}
permissions:
contents: read
pull-requests: write
id-token: write

4
.gitignore vendored
View File

@ -14,3 +14,7 @@ Homestead.yaml
/.vscode
/docker-compose/db/data/
.gitkeep
/public/docs
/.scribe
!storage/fonts/.gitkeep
.DS_Store

View File

@ -1,4 +1,4 @@
FROM php:7.4-fpm
FROM php:8.1-fpm
# Arguments defined in docker-compose.yml
ARG user

View File

@ -40,10 +40,13 @@ class CheckInvoiceStatus extends Command
public function handle()
{
$date = Carbon::now();
$invoices = Invoice::where('status', '<>', Invoice::STATUS_COMPLETED)->whereDate('due_date', '<', $date)->get();
$invoices = Invoice::whereNotIn('status', [Invoice::STATUS_COMPLETED, Invoice::STATUS_DRAFT])
->where('overdue', false)
->whereDate('due_date', '<', $date)
->get();
foreach ($invoices as $invoice) {
$invoice->status = Invoice::STATUS_OVERDUE;
$invoice->overdue = true;
printf("Invoice %s is OVERDUE \n", $invoice->invoice_number);
$invoice->save();
}

View File

@ -55,7 +55,7 @@ class CreateTemplateCommand extends Command
copy(public_path("/build/img/PDF/{$type}1.png"), public_path("/build/img/PDF/{$templateName}.png"));
copy(resource_path("/static/img/PDF/{$type}1.png"), resource_path("/static/img/PDF/{$templateName}.png"));
$path = resource_path("app/pdf/{$type}/{$templateName}.blade.php");
$path = resource_path("views/app/pdf/{$type}/{$templateName}.blade.php");
$type = ucfirst($type);
$this->info("{$type} Template created successfully at ".$path);

View File

@ -2,6 +2,7 @@
namespace Crater\Console;
use Crater\Models\CompanySetting;
use Crater\Models\RecurringInvoice;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
@ -37,9 +38,11 @@ class Kernel extends ConsoleKernel
$recurringInvoices = RecurringInvoice::where('status', 'ACTIVE')->get();
foreach ($recurringInvoices as $recurringInvoice) {
$timeZone = CompanySetting::getSetting('time_zone', $recurringInvoice->company_id);
$schedule->call(function () use ($recurringInvoice) {
$recurringInvoice->generateInvoice();
})->cron($recurringInvoice->frequency);
})->cron($recurringInvoice->frequency)->timezone($timeZone);
}
}
}

View File

@ -12,6 +12,7 @@ use Crater\Models\Expense;
use Crater\Models\Invoice;
use Crater\Models\Payment;
use Illuminate\Http\Request;
use Silber\Bouncer\BouncerFacade;
class DashboardController extends Controller
{
@ -151,8 +152,8 @@ class DashboardController extends Controller
'total_invoice_count' => $total_invoice_count,
'total_estimate_count' => $total_estimate_count,
'recent_due_invoices' => $recent_due_invoices,
'recent_estimates' => $recent_estimates,
'recent_due_invoices' => BouncerFacade::can('view-invoice', Invoice::class) ? $recent_due_invoices : [],
'recent_estimates' => BouncerFacade::can('view-estimate', Estimate::class) ? $recent_estimates : [],
'chart_data' => $chart_data,

View File

@ -3,7 +3,7 @@
namespace Crater\Http\Controllers\V1\Admin\Expense;
use Crater\Http\Controllers\Controller;
use Crater\Http\Requests\ExpenseRequest;
use Crater\Http\Requests\UploadExpenseReceiptRequest;
use Crater\Models\Expense;
class UploadReceiptController extends Controller
@ -15,7 +15,7 @@ class UploadReceiptController extends Controller
* @param Expense $expense
* @return \Illuminate\Http\JsonResponse
*/
public function __invoke(ExpenseRequest $request, Expense $expense)
public function __invoke(UploadExpenseReceiptRequest $request, Expense $expense)
{
$this->authorize('update', $expense);

View File

@ -49,7 +49,16 @@ class BootstrapController extends Controller
BouncerFacade::refreshFor($current_user);
$global_settings = Setting::getSettings(['api_token', 'admin_portal_theme']);
$global_settings = Setting::getSettings([
'api_token',
'admin_portal_theme',
'admin_portal_logo',
'login_page_logo',
'login_page_heading',
'login_page_description',
'admin_page_title',
'copyright_text'
]);
return response()->json([
'current_user' => new UserResource($current_user),

View File

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

View File

@ -0,0 +1,24 @@
<?php
namespace Crater\Http\Controllers\V1\Admin\MailSender;
use Crater\Http\Controllers\Controller;
use Crater\Http\Resources\MailSenderResource;
use Crater\Models\MailSender;
use Illuminate\Http\Request;
class GetAllMailSendersController extends Controller
{
/**
* Handle the incoming request.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function __invoke(Request $request)
{
$mailSenders = MailSender::whereCompany()->get();
return MailSenderResource::collection($mailSenders);
}
}

View File

@ -0,0 +1,98 @@
<?php
namespace Crater\Http\Controllers\V1\Admin\MailSender;
use Crater\Http\Controllers\Controller;
use Crater\Http\Requests\MailSenderRequest;
use Crater\Http\Resources\MailSenderResource;
use Crater\Models\MailSender;
use Illuminate\Http\Request;
class MailSenderController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index(Request $request)
{
$this->authorize('viewAny', MailSender::class);
$limit = $request->has('limit') ? $request->limit : 10;
$mailSenders = MailSender::whereCompany()
->applyFilters($request->all())
->paginateData($limit);
return (MailSenderResource::collection($mailSenders))
->additional(['meta' => [
'mail_sender_total_count' => MailSender::whereCompany()->count(),
]]);
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(MailSenderRequest $request)
{
$this->authorize('create', MailSender::class);
$mailSender = MailSender::createFromRequest($request);
return new MailSenderResource($mailSender);
}
/**
* Display the specified resource.
*
* @param \Crater\Models\SenderMail $senderMail
* @return \Illuminate\Http\Response
*/
public function show(MailSender $mailSender)
{
$this->authorize('view', $mailSender);
return new MailSenderResource($mailSender);
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param \Crater\Models\SenderMail $senderMail
* @return \Illuminate\Http\Response
*/
public function update(MailSenderRequest $request, MailSender $mailSender)
{
$this->authorize('update', $mailSender);
$mailSender->updateFromRequest($request);
return new MailSenderResource($mailSender);
}
/**
* Remove the specified resource from storage.
*
* @param \Crater\Models\SenderMail $senderMail
* @return \Illuminate\Http\Response
*/
public function destroy(MailSender $mailSender)
{
$this->authorize('delete', $mailSender);
if ($mailSender->is_default) {
return respondJson('You can\'t remove default mail sender.', 'You can\'t remove default mail sender.');
}
$mailSender->delete();
return response()->json([
'success' => true,
]);
}
}

View File

@ -3,18 +3,18 @@
namespace Crater\Http\Controllers\V1\Admin\Modules;
use Crater\Http\Controllers\Controller;
use Crater\Http\Requests\UnzipUpdateRequest;
use Crater\Space\ModuleInstaller;
use Illuminate\Http\Request;
class UnzipModuleController extends Controller
{
/**
* Handle the incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Crater\Http\Requests\UnzipUpdateRequest $request
* @return \Illuminate\Http\Response
*/
public function __invoke(Request $request)
public function __invoke(UnzipUpdateRequest $request)
{
$this->authorize('manage modules');

View File

@ -3,18 +3,18 @@
namespace Crater\Http\Controllers\V1\Admin\Modules;
use Crater\Http\Controllers\Controller;
use Crater\Http\Requests\UploadModuleRequest;
use Crater\Space\ModuleInstaller;
use Illuminate\Http\Request;
class UploadModuleController extends Controller
{
/**
* Handle the incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Crater\Http\Requests\UploadModuleRequest $request
* @return \Illuminate\Http\Response
*/
public function __invoke(Request $request)
public function __invoke(UploadModuleRequest $request)
{
$this->authorize('manage modules');

View File

@ -84,12 +84,14 @@ class PaymentMethodsController extends Controller
{
$this->authorize('delete', $paymentMethod);
$payments = $paymentMethod->payments;
if ($payments->count() > 0) {
if ($paymentMethod->payments()->exists()) {
return respondJson('payments_attached', 'Payments Attached.');
}
if ($paymentMethod->expenses()->exists()) {
return respondJson('expenses_attached', 'Expenses Attached.');
}
$paymentMethod->delete();
return response()->json([

View File

@ -2,24 +2,25 @@
namespace Crater\Http\Controllers\V1\Admin\Report;
use PDF;
use Carbon\Carbon;
use Crater\Http\Controllers\Controller;
use Crater\Models\Company;
use Crater\Models\CompanySetting;
use Crater\Models\Currency;
use Crater\Models\Customer;
use Illuminate\Http\Request;
use Crater\Models\CompanySetting;
use Illuminate\Support\Facades\App;
use PDF;
use Crater\Http\Controllers\Controller;
class CustomerSalesReportController extends Controller
{
/**
* Handle the incoming request.
*
* @param \Illuminate\Http\Request $request
* @param string $hash
* @return \Illuminate\Http\JsonResponse
*/
* Handle the incoming request.
*
* @param \Illuminate\Http\Request $request
* @param string $hash
* @return \Illuminate\Http\JsonResponse
*/
public function __invoke(Request $request, $hash)
{
$company = Company::where('unique_hash', $hash)->first();
@ -56,6 +57,7 @@ class CustomerSalesReportController extends Controller
$dateFormat = CompanySetting::getSetting('carbon_date_format', $company->id);
$from_date = Carbon::createFromFormat('Y-m-d', $request->from_date)->format($dateFormat);
$to_date = Carbon::createFromFormat('Y-m-d', $request->to_date)->format($dateFormat);
$currency = Currency::findOrFail(CompanySetting::getSetting('currency', $company->id));
$colors = [
'primary_text_color',
@ -80,6 +82,7 @@ class CustomerSalesReportController extends Controller
'company' => $company,
'from_date' => $from_date,
'to_date' => $to_date,
'currency' => $currency,
]);
$pdf = PDF::loadView('app.pdf.reports.sales-customers');

View File

@ -2,24 +2,25 @@
namespace Crater\Http\Controllers\V1\Admin\Report;
use Carbon\Carbon;
use Crater\Http\Controllers\Controller;
use Crater\Models\Company;
use Crater\Models\CompanySetting;
use Crater\Models\Expense;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use PDF;
use Carbon\Carbon;
use Crater\Models\Company;
use Crater\Models\Expense;
use Crater\Models\Currency;
use Illuminate\Http\Request;
use Crater\Models\CompanySetting;
use Illuminate\Support\Facades\App;
use Crater\Http\Controllers\Controller;
class ExpensesReportController extends Controller
{
/**
* Handle the incoming request.
*
* @param \Illuminate\Http\Request $request
* @param string $hash
* @return \Illuminate\Http\JsonResponse
*/
* Handle the incoming request.
*
* @param \Illuminate\Http\Request $request
* @param string $hash
* @return \Illuminate\Http\JsonResponse
*/
public function __invoke(Request $request, $hash)
{
$company = Company::where('unique_hash', $hash)->first();
@ -43,6 +44,7 @@ class ExpensesReportController extends Controller
$dateFormat = CompanySetting::getSetting('carbon_date_format', $company->id);
$from_date = Carbon::createFromFormat('Y-m-d', $request->from_date)->format($dateFormat);
$to_date = Carbon::createFromFormat('Y-m-d', $request->to_date)->format($dateFormat);
$currency = Currency::findOrFail(CompanySetting::getSetting('currency', $company->id));
$colors = [
'primary_text_color',
@ -66,6 +68,7 @@ class ExpensesReportController extends Controller
'company' => $company,
'from_date' => $from_date,
'to_date' => $to_date,
'currency' => $currency,
]);
$pdf = PDF::loadView('app.pdf.reports.expenses');

View File

@ -2,24 +2,25 @@
namespace Crater\Http\Controllers\V1\Admin\Report;
use Carbon\Carbon;
use Crater\Http\Controllers\Controller;
use Crater\Models\Company;
use Crater\Models\CompanySetting;
use Crater\Models\InvoiceItem;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use PDF;
use Carbon\Carbon;
use Crater\Models\Company;
use Crater\Models\Currency;
use Illuminate\Http\Request;
use Crater\Models\InvoiceItem;
use Crater\Models\CompanySetting;
use Illuminate\Support\Facades\App;
use Crater\Http\Controllers\Controller;
class ItemSalesReportController extends Controller
{
/**
* Handle the incoming request.
*
* @param \Illuminate\Http\Request $request
* @param string $hash
* @return \Illuminate\Http\JsonResponse
*/
* Handle the incoming request.
*
* @param \Illuminate\Http\Request $request
* @param string $hash
* @return \Illuminate\Http\JsonResponse
*/
public function __invoke(Request $request, $hash)
{
$company = Company::where('unique_hash', $hash)->first();
@ -43,6 +44,7 @@ class ItemSalesReportController extends Controller
$dateFormat = CompanySetting::getSetting('carbon_date_format', $company->id);
$from_date = Carbon::createFromFormat('Y-m-d', $request->from_date)->format($dateFormat);
$to_date = Carbon::createFromFormat('Y-m-d', $request->to_date)->format($dateFormat);
$currency = Currency::findOrFail(CompanySetting::getSetting('currency', $company->id));
$colors = [
'primary_text_color',
@ -66,6 +68,7 @@ class ItemSalesReportController extends Controller
'company' => $company,
'from_date' => $from_date,
'to_date' => $to_date,
'currency' => $currency,
]);
$pdf = PDF::loadView('app.pdf.reports.sales-items');

View File

@ -2,25 +2,26 @@
namespace Crater\Http\Controllers\V1\Admin\Report;
use PDF;
use Carbon\Carbon;
use Crater\Http\Controllers\Controller;
use Crater\Models\Company;
use Crater\Models\CompanySetting;
use Crater\Models\Expense;
use Crater\Models\Payment;
use Crater\Models\Currency;
use Illuminate\Http\Request;
use Crater\Models\CompanySetting;
use Illuminate\Support\Facades\App;
use PDF;
use Crater\Http\Controllers\Controller;
class ProfitLossReportController extends Controller
{
/**
* Handle the incoming request.
*
* @param \Illuminate\Http\Request $request
* @param string $hash
* @return \Illuminate\Http\JsonResponse
*/
* Handle the incoming request.
*
* @param \Illuminate\Http\Request $request
* @param string $hash
* @return \Illuminate\Http\JsonResponse
*/
public function __invoke(Request $request, $hash)
{
$company = Company::where('unique_hash', $hash)->first();
@ -49,6 +50,8 @@ class ProfitLossReportController extends Controller
$dateFormat = CompanySetting::getSetting('carbon_date_format', $company->id);
$from_date = Carbon::createFromFormat('Y-m-d', $request->from_date)->format($dateFormat);
$to_date = Carbon::createFromFormat('Y-m-d', $request->to_date)->format($dateFormat);
$currency = Currency::findOrFail(CompanySetting::getSetting('currency', $company->id));
$colors = [
'primary_text_color',
@ -74,6 +77,7 @@ class ProfitLossReportController extends Controller
'company' => $company,
'from_date' => $from_date,
'to_date' => $to_date,
'currency' => $currency,
]);
$pdf = PDF::loadView('app.pdf.reports.profit-loss');

View File

@ -2,24 +2,25 @@
namespace Crater\Http\Controllers\V1\Admin\Report;
use Carbon\Carbon;
use Crater\Http\Controllers\Controller;
use Crater\Models\Company;
use Crater\Models\CompanySetting;
use Crater\Models\Tax;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use PDF;
use Carbon\Carbon;
use Crater\Models\Tax;
use Crater\Models\Company;
use Crater\Models\Currency;
use Illuminate\Http\Request;
use Crater\Models\CompanySetting;
use Illuminate\Support\Facades\App;
use Crater\Http\Controllers\Controller;
class TaxSummaryReportController extends Controller
{
/**
* Handle the incoming request.
*
* @param \Illuminate\Http\Request $request
* @param string $hash
* @return \Illuminate\Http\JsonResponse
*/
* Handle the incoming request.
*
* @param \Illuminate\Http\Request $request
* @param string $hash
* @return \Illuminate\Http\JsonResponse
*/
public function __invoke(Request $request, $hash)
{
$company = Company::where('unique_hash', $hash)->first();
@ -44,6 +45,8 @@ class TaxSummaryReportController extends Controller
$dateFormat = CompanySetting::getSetting('carbon_date_format', $company->id);
$from_date = Carbon::createFromFormat('Y-m-d', $request->from_date)->format($dateFormat);
$to_date = Carbon::createFromFormat('Y-m-d', $request->to_date)->format($dateFormat);
$currency = Currency::findOrFail(CompanySetting::getSetting('currency', $company->id));
$colors = [
'primary_text_color',
@ -68,6 +71,7 @@ class TaxSummaryReportController extends Controller
'company' => $company,
'from_date' => $from_date,
'to_date' => $to_date,
'currency' => $currency,
]);
$pdf = PDF::loadView('app.pdf.reports.tax-summary');

View File

@ -71,6 +71,9 @@ class CompanyController extends Controller
$data = json_decode($request->company_logo);
if (isset($request->is_company_logo_removed) && (bool) $request->is_company_logo_removed) {
$company->clearMediaCollection('logo');
}
if ($data) {
$company = Company::find($request->header('company'));
@ -98,6 +101,9 @@ class CompanyController extends Controller
{
$user = auth()->user();
if (isset($request->is_admin_avatar_removed) && (bool) $request->is_admin_avatar_removed) {
$user->clearMediaCollection('admin_avatar');
}
if ($user && $request->hasFile('admin_avatar')) {
$user->clearMediaCollection('admin_avatar');

View File

@ -0,0 +1,27 @@
<?php
namespace Crater\Http\Controllers\V1\Admin\Settings;
use Crater\Http\Controllers\Controller;
use Crater\Models\Company;
use Illuminate\Http\Request;
class CompanyCurrencyCheckTransactionsController extends Controller
{
/**
* Handle the incoming request.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function __invoke(Request $request)
{
$company = Company::find($request->header('company'));
$this->authorize('manage company', $company);
return response()->json([
'has_transactions' => $company->hasTransactions(),
]);
}
}

View File

@ -3,80 +3,29 @@
namespace Crater\Http\Controllers\V1\Admin\Settings;
use Crater\Http\Controllers\Controller;
use Crater\Http\Requests\MailEnvironmentRequest;
use Crater\Http\Requests\TestMailDriverRequest;
use Crater\Mail\TestMail;
use Crater\Models\Setting;
use Crater\Space\EnvironmentManager;
use Illuminate\Http\JsonResponse;
use Crater\Models\MailSender;
use Illuminate\Http\Request;
use Mail;
class MailConfigurationController extends Controller
{
/**
* @var EnvironmentManager
*/
protected $environmentManager;
/**
* @param EnvironmentManager $environmentManager
*/
public function __construct(EnvironmentManager $environmentManager)
{
$this->environmentManager = $environmentManager;
}
/**
*
* @param MailEnvironmentRequest $request
* @return JsonResponse
*/
public function saveMailEnvironment(MailEnvironmentRequest $request)
public function TestMailDriver(TestMailDriverRequest $request)
{
$this->authorize('manage email config');
$setting = Setting::getSetting('profile_complete');
$results = $this->environmentManager->saveMailVariables($request);
MailSender::setMailConfiguration($request->mail_sender_id);
if ($setting !== 'COMPLETED') {
Setting::setSetting('profile_complete', 4);
}
Mail::to($request->to)->send(new TestMail($request->subject, $request->message));
return response()->json($results);
return response()->json([
'success' => true,
]);
}
public function getMailEnvironment()
public function getMailDrivers(Request $request)
{
$this->authorize('manage email config');
$MailData = [
'mail_driver' => config('mail.driver'),
'mail_host' => config('mail.host'),
'mail_port' => config('mail.port'),
'mail_username' => config('mail.username'),
'mail_password' => config('mail.password'),
'mail_encryption' => config('mail.encryption'),
'from_name' => config('mail.from.name'),
'from_mail' => config('mail.from.address'),
'mail_mailgun_endpoint' => config('services.mailgun.endpoint'),
'mail_mailgun_domain' => config('services.mailgun.domain'),
'mail_mailgun_secret' => config('services.mailgun.secret'),
'mail_ses_key' => config('services.ses.key'),
'mail_ses_secret' => config('services.ses.secret'),
];
return response()->json($MailData);
}
/**
*
* @return JsonResponse
*/
public function getMailDrivers()
{
$this->authorize('manage email config');
$drivers = [
'smtp',
'mail',
@ -87,21 +36,4 @@ class MailConfigurationController extends Controller
return response()->json($drivers);
}
public function testEmailConfig(Request $request)
{
$this->authorize('manage email config');
$this->validate($request, [
'to' => 'required|email',
'subject' => 'required',
'message' => 'required',
]);
Mail::to($request->to)->send(new TestMail($request->subject, $request->message));
return response()->json([
'success' => true,
]);
}
}

View File

@ -6,6 +6,7 @@ use Crater\Http\Controllers\Controller;
use Crater\Http\Requests\UpdateSettingsRequest;
use Crater\Models\Company;
use Crater\Models\CompanySetting;
use Illuminate\Support\Arr;
class UpdateCompanySettingsController extends Controller
{
@ -17,9 +18,23 @@ class UpdateCompanySettingsController extends Controller
*/
public function __invoke(UpdateSettingsRequest $request)
{
$this->authorize('manage company', Company::find($request->header('company')));
$company = Company::find($request->header('company'));
$this->authorize('manage company', $company);
CompanySetting::setSettings($request->settings, $request->header('company'));
$data = $request->settings;
if (
Arr::exists($data, 'currency') &&
(CompanySetting::getSetting('currency', $company->id) !== $data['currency']) &&
$company->hasTransactions()
) {
return response()->json([
'success' => false,
'message' => 'Cannot update company currency after transactions are created.'
]);
}
CompanySetting::setSettings($data, $request->header('company'));
return response()->json([
'success' => true,

View File

@ -3,7 +3,6 @@
namespace Crater\Http\Controllers\V1\Customer\Estimate;
use Crater\Http\Controllers\Controller;
use Crater\Http\Requests\CustomerEstimateStatusRequest;
use Crater\Http\Resources\Customer\EstimateResource;
use Crater\Models\Company;
use Crater\Models\Estimate;

View File

@ -9,6 +9,7 @@ use Crater\Models\CompanySetting;
use Crater\Models\Customer;
use Crater\Models\EmailLog;
use Crater\Models\Estimate;
use Crater\Models\MailSender;
use Illuminate\Http\Request;
class EstimatePdfController extends Controller
@ -27,14 +28,16 @@ class EstimatePdfController extends Controller
);
if ($notifyEstimateViewed == 'YES') {
$data['estimate'] = Estimate::findOrFail($estimate->id)->toArray();
$data['user'] = Customer::find($estimate->customer_id)->toArray();
$notificationEmail = CompanySetting::getSetting(
'notification_email',
$estimate->company_id
);
$notificationEmail = CompanySetting::getSetting('notification_email', $estimate->company_id);
$mailSender = MailSender::where('company_id', $estimate->company_id)->where('is_default', true)->first();
MailSender::setMailConfiguration($mailSender->id);
\Mail::to($notificationEmail)->send(new EstimateViewedMail($data));
$data['from_address'] = $mailSender->from_address;
$data['from_name'] = $mailSender->from_name;
$data['user'] = Customer::find($estimate->customer_id)->toArray();
$data['estimate'] = Estimate::findOrFail($estimate->id)->toArray();
send_mail(new EstimateViewedMail($data), $mailSender, $notificationEmail);
}
}

View File

@ -17,6 +17,9 @@ class ProfileController extends Controller
$customer->update($request->validated());
if (isset($request->is_customer_avatar_removed) && (bool) $request->is_customer_avatar_removed) {
$customer->clearMediaCollection('customer_avatar');
}
if ($customer && $request->hasFile('customer_avatar')) {
$customer->clearMediaCollection('customer_avatar');

View File

@ -9,6 +9,7 @@ use Crater\Models\CompanySetting;
use Crater\Models\Customer;
use Crater\Models\EmailLog;
use Crater\Models\Invoice;
use Crater\Models\MailSender;
use Illuminate\Http\Request;
class InvoicePdfController extends Controller
@ -28,14 +29,16 @@ class InvoicePdfController extends Controller
);
if ($notifyInvoiceViewed == 'YES') {
$notificationEmail = CompanySetting::getSetting('notification_email', $invoice->company_id);
$mailSender = MailSender::where('company_id', $invoice->company_id)->where('is_default', true)->first();
MailSender::setMailConfiguration($mailSender->id);
$data['from_address'] = $mailSender->from_address;
$data['from_name'] = $mailSender->from_name;
$data['invoice'] = Invoice::findOrFail($invoice->id)->toArray();
$data['user'] = Customer::find($invoice->customer_id)->toArray();
$notificationEmail = CompanySetting::getSetting(
'notification_email',
$invoice->company_id
);
\Mail::to($notificationEmail)->send(new InvoiceViewedMail($data));
send_mail(new InvoiceViewedMail($data), $mailSender, $notificationEmail);
}
}
@ -44,8 +47,8 @@ class InvoicePdfController extends Controller
}
return view('app')->with([
'customer_logo' => get_customer_logo($invoice->company_id),
'current_theme' => get_customer_portal_theme($invoice->company_id)
'customer_logo' => get_company_setting('customer_portal_logo', $invoice->company_id),
'current_theme' => get_company_setting('customer_portal_theme', $invoice->company_id)
]);
}

View File

@ -17,6 +17,8 @@ class DownloadReceiptController extends Controller
*/
public function __invoke(Expense $expense)
{
$this->authorize('view', $expense);
if ($expense) {
$media = $expense->getFirstMedia('receipts');
if ($media) {

View File

@ -4,6 +4,7 @@ namespace Crater\Http\Controllers\V1\PDF;
use Crater\Http\Controllers\Controller;
use Crater\Models\Estimate;
use Illuminate\Http\Request;
class EstimatePdfController extends Controller
{
@ -13,8 +14,13 @@ class EstimatePdfController extends Controller
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function __invoke(Estimate $estimate)
public function __invoke(Request $request, Estimate $estimate)
{
if ($request->has('preview')) {
return $estimate->getPDFData();
}
return $estimate->getGeneratedPDFOrStream('estimate');
}
}

View File

@ -4,6 +4,7 @@ namespace Crater\Http\Controllers\V1\PDF;
use Crater\Http\Controllers\Controller;
use Crater\Models\Invoice;
use Illuminate\Http\Request;
class InvoicePdfController extends Controller
{
@ -13,8 +14,12 @@ class InvoicePdfController extends Controller
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function __invoke(Invoice $invoice)
public function __invoke(Request $request, Invoice $invoice)
{
if ($request->has('preview')) {
return $invoice->getPDFData();
}
return $invoice->getGeneratedPDFOrStream('invoice');
}
}

View File

@ -4,6 +4,7 @@ namespace Crater\Http\Controllers\V1\PDF;
use Crater\Http\Controllers\Controller;
use Crater\Models\Payment;
use Illuminate\Http\Request;
class PaymentPdfController extends Controller
{
@ -13,8 +14,12 @@ class PaymentPdfController extends Controller
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function __invoke(Payment $payment)
public function __invoke(Request $request, Payment $payment)
{
if ($request->has('preview')) {
return view('app.pdf.payment.payment');
}
return $payment->getGeneratedPDFOrStream('payment');
}
}

View File

@ -3,8 +3,8 @@
namespace Crater\Http\Middleware;
use Closure;
use Crater\Models\CompanySetting;
use Crater\Models\FileDisk;
use Crater\Models\MailSender;
class ConfigMiddleware
{
@ -18,15 +18,6 @@ class ConfigMiddleware
public function handle($request, Closure $next)
{
if (\Storage::disk('local')->has('database_created')) {
$setting = CompanySetting::getSetting('time_zone', $request->header('company'));
$timezone = config('app.timezone');
if ($setting && $setting != null && $setting != $timezone) {
config(['app.timezone' => $setting]);
date_default_timezone_set($setting);
}
if ($request->has('file_disk_id')) {
$file_disk = FileDisk::find($request->file_disk_id);
} else {
@ -38,6 +29,12 @@ class ConfigMiddleware
}
}
$default_mail_sender = MailSender::where('company_id', $request->header('company'))->where('is_default', true)->first();
if ($default_mail_sender) {
$default_mail_sender->setMailConfiguration($default_mail_sender->id);
}
return $next($request);
}
}

View File

@ -17,7 +17,7 @@ class PdfMiddleware
*/
public function handle(Request $request, Closure $next)
{
if (Auth::guard('web')->check() || Auth::guard('api')->check() || Auth::guard('customer')->check()) {
if (Auth::guard('web')->check() || Auth::guard('sanctum')->check() || Auth::guard('customer')->check()) {
return $next($request);
}

View File

@ -92,6 +92,12 @@ class CustomerProfileRequest extends FormRequest
],
'shipping.fax' => [
'nullable',
],
'customer_avatar' => [
'nullable',
'file',
'mimes:gif,jpg,png',
'max:20000'
]
];
}

View File

@ -0,0 +1,85 @@
<?php
namespace Crater\Http\Requests;
use Illuminate\Validation\Rule;
use Illuminate\Foundation\Http\FormRequest;
class MailSenderRequest 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()
{
$rules = [
'name' => [
'required',
Rule::unique('mail_senders')
->where('company_id', $this->header('company'))
],
'driver' => [
'required',
],
'is_default' => [
'nullable'
],
'bcc' => [
'nullable'
],
'cc' => [
'nullable'
],
'from_address' => [
'nullable'
],
'from_name' => [
'nullable'
],
'settings' => [
'nullable'
],
'settings.*' => [
'nullable'
]
];
if ($this->isMethod('PUT')) {
$rules['name'] = [
'nullable',
Rule::unique('mail_senders')
->ignore($this->route('mail_sender')->id)
->where('company_id', $this->header('company'))
];
}
return $rules;
}
public function getMailSenderPayload()
{
$data = $this->validated();
if ($data['settings'] && $data['settings']['encryption'] == 'none') {
$data['settings']['encryption'] = '';
}
return collect($data)
->merge([
'company_id' => $this->header('company'),
])
->toArray();
}
}

View File

@ -30,7 +30,7 @@ class SendEstimatesRequest extends FormRequest
'body' => [
'required',
],
'from' => [
'mail_sender_id' => [
'required',
],
'to' => [

View File

@ -30,7 +30,7 @@ class SendInvoiceRequest extends FormRequest
'subject' => [
'required',
],
'from' => [
'mail_sender_id' => [
'required',
],
'to' => [

View File

@ -30,7 +30,7 @@ class SendPaymentRequest extends FormRequest
'body' => [
'required',
],
'from' => [
'mail_sender_id' => [
'required',
],
'to' => [

View File

@ -0,0 +1,39 @@
<?php
namespace Crater\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class TestMailDriverRequest 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 [
'to' => [
'required',
'email'
],
'subject' => [
'required'
],
'message' => [
'required'
],
];
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace Crater\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UnzipUpdateRequest 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 [
'path' => [
'required',
'regex:/^[\.\/\w\-]+$/'
],
'module' => [
'required',
'string'
]
];
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace Crater\Http\Requests;
use Crater\Rules\Base64Mime;
use Illuminate\Foundation\Http\FormRequest;
class UploadExpenseReceiptRequest 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 [
'attachment_receipt' => [
'nullable',
new Base64Mime(['gif', 'jpg', 'png'])
]
];
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace Crater\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UploadModuleRequest 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 [
'avatar' => [
'required',
'file',
'mimes:zip',
'max:20000'
],
'module' => [
'required',
'string',
'max:100'
]
];
}
}

View File

@ -24,7 +24,7 @@ class InvoiceResource extends JsonResource
'paid_status' => $this->paid_status,
'tax_per_item' => $this->tax_per_item,
'discount_per_item' => $this->discount_per_item,
'notes' => $this->notes,
'notes' => $this->getNotes(),
'discount_type' => $this->discount_type,
'discount' => $this->discount,
'discount_val' => $this->discount_val,
@ -46,10 +46,12 @@ class InvoiceResource extends JsonResource
'base_due_amount' => $this->base_due_amount,
'currency_id' => $this->currency_id,
'formatted_created_at' => $this->formattedCreatedAt,
'formatted_notes' => $this->formattedNotes,
'invoice_pdf_url' => $this->invoicePdfUrl,
'formatted_invoice_date' => $this->formattedInvoiceDate,
'formatted_due_date' => $this->formattedDueDate,
'payment_module_enabled' => $this->payment_module_enabled,
'overdue' => $this->overdue,
'items' => $this->when($this->items()->exists(), function () {
return InvoiceItemResource::collection($this->items);
}),

View File

@ -23,7 +23,7 @@ class EstimateResource extends JsonResource
'reference_number' => $this->reference_number,
'tax_per_item' => $this->tax_per_item,
'discount_per_item' => $this->discount_per_item,
'notes' => $this->notes,
'notes' => $this->getNotes(),
'discount' => $this->discount,
'discount_type' => $this->discount_type,
'discount_val' => $this->discount_val,

View File

@ -55,6 +55,7 @@ class InvoiceResource extends JsonResource
'payment_module_enabled' => $this->payment_module_enabled,
'sales_tax_type' => $this->sales_tax_type,
'sales_tax_address_type' => $this->sales_tax_address_type,
'overdue' => $this->overdue,
'items' => $this->when($this->items()->exists(), function () {
return InvoiceItemResource::collection($this->items);
}),

View File

@ -0,0 +1,30 @@
<?php
namespace Crater\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class MailSenderResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
*/
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'driver' => $this->driver,
'is_default' => $this->is_default,
'bcc' => $this->bcc,
'cc' => $this->cc,
'from_address' => $this->from_address,
'from_name' => $this->from_name,
'company_id' => $this->company_id,
'settings' => $this->settings
];
}
}

View File

@ -18,7 +18,7 @@ class PaymentResource extends JsonResource
'id' => $this->id,
'payment_number' => $this->payment_number,
'payment_date' => $this->payment_date,
'notes' => $this->notes,
'notes' => $this->getNotes(),
'amount' => $this->amount,
'unique_hash' => $this->unique_hash,
'invoice_id' => $this->invoice_id,

View File

@ -30,7 +30,7 @@ class EstimateViewedMail extends Mailable
*/
public function build()
{
return $this->from(config('mail.from.address'), config('mail.from.name'))
return $this->from($this->data['from_address'], $this->data['from_name'])
->markdown('emails.viewed.estimate', ['data', $this->data]);
}
}

View File

@ -30,7 +30,7 @@ class InvoiceViewedMail extends Mailable
*/
public function build()
{
return $this->from(config('mail.from.address'), config('mail.from.name'))
return $this->from($this->data['from_address'], $this->data['from_name'])
->markdown('emails.viewed.invoice', ['data', $this->data]);
}
}

View File

@ -34,7 +34,7 @@ class SendEstimateMail extends Mailable
public function build()
{
$log = EmailLog::create([
'from' => $this->data['from'],
'from' => $this->data['from_address'],
'to' => $this->data['to'],
'subject' => $this->data['subject'],
'body' => $this->data['body'],
@ -47,9 +47,10 @@ class SendEstimateMail extends Mailable
$this->data['url'] = route('estimate', ['email_log' => $log->token]);
$mailContent = $this->from($this->data['from'], config('mail.from.name'))
->subject($this->data['subject'])
->markdown('emails.send.estimate', ['data', $this->data]);
$mailContent = $this->from($this->data['from_address'], $this->data['from_name'])
->subject($this->data['subject'])
->markdown("emails.send.estimate", ['data', $this->data]);
if ($this->data['attach']['data']) {
$mailContent->attachData(

View File

@ -34,7 +34,7 @@ class SendInvoiceMail extends Mailable
public function build()
{
$log = EmailLog::create([
'from' => $this->data['from'],
'from' => $this->data['from_address'],
'to' => $this->data['to'],
'subject' => $this->data['subject'],
'body' => $this->data['body'],
@ -47,9 +47,9 @@ class SendInvoiceMail extends Mailable
$this->data['url'] = route('invoice', ['email_log' => $log->token]);
$mailContent = $this->from($this->data['from'], config('mail.from.name'))
$mailContent = $this->from($this->data['from_address'], $this->data['from_name'])
->subject($this->data['subject'])
->markdown('emails.send.invoice', ['data', $this->data]);
->markdown("emails.send.invoice", ['data', $this->data]);
if ($this->data['attach']['data']) {
$mailContent->attachData(

View File

@ -34,7 +34,7 @@ class SendPaymentMail extends Mailable
public function build()
{
$log = EmailLog::create([
'from' => $this->data['from'],
'from' => $this->data['from_address'],
'to' => $this->data['to'],
'subject' => $this->data['subject'],
'body' => $this->data['body'],
@ -47,9 +47,9 @@ class SendPaymentMail extends Mailable
$this->data['url'] = route('payment', ['email_log' => $log->token]);
$mailContent = $this->from($this->data['from'], config('mail.from.name'))
->subject($this->data['subject'])
->markdown('emails.send.payment', ['data', $this->data]);
$mailContent = $this->from($this->data['from_address'], $this->data['from_name'])
->subject($this->data['subject'])
->markdown("emails.send.payment", ['data', $this->data]);
if ($this->data['attach']['data']) {
$mailContent->attachData(

View File

@ -217,7 +217,7 @@ class Company extends Model implements HasMedia
'estimate_billing_address_format' => $billingAddressFormat,
'payment_company_address_format' => $companyAddressFormat,
'payment_from_customer_address_format' => $paymentFromCustomerAddress,
'currency' => request()->currency ?? 12,
'currency' => request()->currency ?? 13,
'time_zone' => 'Asia/Kolkata',
'language' => 'en',
'fiscal_year' => '1-12',
@ -300,6 +300,10 @@ class Company extends Model implements HasMedia
if ($this->invoices()->exists()) {
$this->invoices->map(function ($invoice) {
$this->checkModelData($invoice);
if ($invoice->transactions()->exists()) {
$invoice->transactions()->delete();
}
});
$this->invoices()->delete();
@ -376,4 +380,21 @@ class Company extends Model implements HasMedia
$model->taxes()->delete();
}
}
public function hasTransactions()
{
if (
$this->customers()->exists() ||
$this->items()->exists() ||
$this->invoices()->exists() ||
$this->estimates()->exists() ||
$this->expenses()->exists() ||
$this->payments()->exists() ||
$this->recurringInvoices()->exists()
) {
return true;
}
return false;
}
}

View File

@ -139,7 +139,12 @@ class Customer extends Authenticatable implements HasMedia
}
if ($customer->invoices()->exists()) {
$customer->invoices()->delete();
$customer->invoices->map(function ($invoice) {
if ($invoice->transactions()->exists()) {
$invoice->transactions()->delete();
}
$invoice->delete();
});
}
if ($customer->payments()->exists()) {

View File

@ -5,10 +5,10 @@ namespace Crater\Models;
use App;
use Barryvdh\DomPDF\Facade as PDF;
use Carbon\Carbon;
use Crater\Mail\SendEstimateMail;
use Crater\Services\SerialNumberFormatter;
use Crater\Traits\GeneratesPdfTrait;
use Crater\Traits\HasCustomFieldsTrait;
use Crater\Traits\MailTrait;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Storage;
@ -20,6 +20,7 @@ use Vinkla\Hashids\Facades\Hashids;
class Estimate extends Model implements HasMedia
{
use HasFactory;
use MailTrait;
use InteractsWithMedia;
use GeneratesPdfTrait;
use HasCustomFieldsTrait;
@ -363,7 +364,7 @@ class Estimate extends Model implements HasMedia
$this->save();
}
\Mail::to($data['to'])->send(new SendEstimateMail($data));
$this->setMail('estimate', $data);
return [
'success' => true,
@ -412,6 +413,10 @@ class Estimate extends Model implements HasMedia
'taxes' => $taxes,
]);
if (request()->has('preview')) {
return view('app.pdf.estimate.'.$estimateTemplate);
}
return PDF::loadView('app.pdf.estimate.'.$estimateTemplate);
}
@ -480,7 +485,6 @@ class Estimate extends Model implements HasMedia
'{ESTIMATE_EXPIRY_DATE}' => $this->formattedExpiryDate,
'{ESTIMATE_NUMBER}' => $this->estimate_number,
'{ESTIMATE_REF_NUMBER}' => $this->reference_number,
'{ESTIMATE_LINK}' => url('/customer/estimates/pdf/'.$this->unique_hash),
];
}

View File

@ -262,6 +262,9 @@ class Expense extends Model implements HasMedia
ExchangeRateLog::addExchangeRateLog($this);
}
if (isset($request->is_attachment_receipt_removed) && (bool) $request->is_attachment_receipt_removed) {
$this->clearMediaCollection('receipts');
}
if ($request->hasFile('attachment_receipt')) {
$this->clearMediaCollection('receipts');
$this->addMediaFromRequest('attachment_receipt')->toMediaCollection('receipts');

View File

@ -9,6 +9,7 @@ use Crater\Mail\SendInvoiceMail;
use Crater\Services\SerialNumberFormatter;
use Crater\Traits\GeneratesPdfTrait;
use Crater\Traits\HasCustomFieldsTrait;
use Crater\Traits\MailTrait;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Storage;
@ -21,6 +22,7 @@ use Vinkla\Hashids\Facades\Hashids;
class Invoice extends Model implements HasMedia
{
use HasFactory;
use MailTrait;
use InteractsWithMedia;
use GeneratesPdfTrait;
use HasCustomFieldsTrait;
@ -28,10 +30,8 @@ class Invoice extends Model implements HasMedia
public const STATUS_DRAFT = 'DRAFT';
public const STATUS_SENT = 'SENT';
public const STATUS_VIEWED = 'VIEWED';
public const STATUS_OVERDUE = 'OVERDUE';
public const STATUS_COMPLETED = 'COMPLETED';
public const STATUS_DUE = 'DUE';
public const STATUS_UNPAID = 'UNPAID';
public const STATUS_PARTIALLY_PAID = 'PARTIALLY_PAID';
public const STATUS_PAID = 'PAID';
@ -138,7 +138,6 @@ class Invoice extends Model implements HasMedia
self::STATUS_DRAFT,
self::STATUS_SENT,
self::STATUS_VIEWED,
self::STATUS_OVERDUE,
self::STATUS_COMPLETED,
];
@ -155,9 +154,7 @@ class Invoice extends Model implements HasMedia
public function getPreviousStatus()
{
if ($this->due_date < Carbon::now()) {
return self::STATUS_OVERDUE;
} elseif ($this->viewed) {
if ($this->viewed) {
return self::STATUS_VIEWED;
} elseif ($this->sent) {
return self::STATUS_SENT;
@ -166,6 +163,11 @@ class Invoice extends Model implements HasMedia
}
}
public function getFormattedNotesAttribute($value)
{
return $this->getNotes();
}
public function getFormattedCreatedAtAttribute($value)
{
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);
@ -249,7 +251,7 @@ class Invoice extends Model implements HasMedia
$filters->get('status') == self::STATUS_PAID
) {
$query->wherePaidStatus($filters->get('status'));
} elseif ($filters->get('status') == self::STATUS_DUE) {
} elseif ($filters->get('status') == 'DUE') {
$query->whereDueStatus($filters->get('status'));
} else {
$query->whereStatus($filters->get('status'));
@ -443,7 +445,8 @@ class Invoice extends Model implements HasMedia
$data['invoice'] = $this->toArray();
$data['customer'] = $this->customer->toArray();
$data['company'] = Company::find($this->company_id);
$data['body'] = $this->getEmailBody($data['body']);
$data['subject'] = $this->getEmailString($data['subject']);
$data['body'] = $this->getEmailString($data['body']);
$data['attach']['data'] = ($this->getEmailAttachmentSetting()) ? $this->getPDFData() : null;
return $data;
@ -463,7 +466,7 @@ class Invoice extends Model implements HasMedia
{
$data = $this->sendInvoiceData($data);
\Mail::to($data['to'])->send(new SendInvoiceMail($data));
$this->setMail('invoice', $data);
if ($this->status == Invoice::STATUS_DRAFT) {
$this->status = Invoice::STATUS_SENT;
@ -498,6 +501,10 @@ class Invoice extends Model implements HasMedia
if (array_key_exists('taxes', $invoiceItem) && $invoiceItem['taxes']) {
foreach ($invoiceItem['taxes'] as $tax) {
$tax['company_id'] = $invoice->company_id;
$tax['exchange_rate'] = $invoice->exchange_rate;
$tax['base_amount'] = $tax['amount'] * $exchange_rate;
$tax['currency_id'] = $invoice->currency_id;
if (gettype($tax['amount']) !== "NULL") {
if (array_key_exists('recurring_invoice_id', $invoiceItem)) {
unset($invoiceItem['recurring_invoice_id']);
@ -520,7 +527,7 @@ class Invoice extends Model implements HasMedia
foreach ($taxes as $tax) {
$tax['company_id'] = $invoice->company_id;
$tax['exchnage_rate'] = $invoice->exchange_rate;
$tax['exchange_rate'] = $invoice->exchange_rate;
$tax['base_amount'] = $tax['amount'] * $exchange_rate;
$tax['currency_id'] = $invoice->currency_id;
@ -575,6 +582,10 @@ class Invoice extends Model implements HasMedia
'taxes' => $taxes,
]);
if (request()->has('preview')) {
return view('app.pdf.invoice.'.$invoiceTemplate);
}
return PDF::loadView('app.pdf.invoice.'.$invoiceTemplate);
}
@ -627,7 +638,7 @@ class Invoice extends Model implements HasMedia
return $this->getFormattedString($this->notes);
}
public function getEmailBody($body)
public function getEmailString($body)
{
$values = array_merge($this->getFieldsArray(), $this->getExtraFields());
@ -643,7 +654,6 @@ class Invoice extends Model implements HasMedia
'{INVOICE_DUE_DATE}' => $this->formattedDueDate,
'{INVOICE_NUMBER}' => $this->invoice_number,
'{INVOICE_REF_NUMBER}' => $this->reference_number,
'{INVOICE_LINK}' => url('/customer/invoices/pdf/'.$this->unique_hash),
];
}
@ -688,6 +698,7 @@ class Invoice extends Model implements HasMedia
if ($amount == 0) {
$this->status = Invoice::STATUS_COMPLETED;
$this->paid_status = Invoice::STATUS_PAID;
$this->overdue = false;
} elseif ($amount == $this->total) {
$this->status = $this->getPreviousStatus();
$this->paid_status = Invoice::STATUS_UNPAID;
@ -698,4 +709,19 @@ class Invoice extends Model implements HasMedia
$this->save();
}
public static function deleteInvoices($ids)
{
foreach ($ids as $id) {
$invoice = self::find($id);
if ($invoice->transactions()->exists()) {
$invoice->transactions()->delete();
}
$invoice->delete();
}
return true;
}
}

111
app/Models/MailSender.php Normal file
View File

@ -0,0 +1,111 @@
<?php
namespace Crater\Models;
use Crater\Http\Requests\MailSenderRequest;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Config;
class MailSender extends Model
{
use HasFactory;
protected $guarded = [
'id'
];
protected $casts = [
'settings' => 'array',
'is_default' => 'boolean'
];
public function company()
{
return $this->belongsTo(Company::class);
}
public function scopeWhereOrder($query, $orderByField, $orderBy)
{
$query->orderBy($orderByField, $orderBy);
}
public function scopeApplyFilters($query, array $filters)
{
$filters = collect($filters);
if ($filters->get('orderByField') || $filters->get('orderBy')) {
$field = $filters->get('orderByField') ? $filters->get('orderByField') : 'name';
$orderBy = $filters->get('orderBy') ? $filters->get('orderBy') : 'desc';
$query->whereOrder($field, $orderBy);
}
}
public function scopePaginateData($query, $limit)
{
if ($limit == 'all') {
return $query->get();
}
return $query->paginate($limit);
}
public function scopeWhereCompany($query)
{
$query->where('mail_senders.company_id', request()->header('company'));
}
public static function createFromRequest(MailSenderRequest $request)
{
$senderMail = self::create($request->getMailSenderPayload());
if ($request->is_default) {
$senderMail->removeOtherDefaultMailSenders($request);
}
return $senderMail;
}
public function updateFromRequest(MailSenderRequest $request)
{
$data = $request->getMailSenderPayload();
$this->update($data);
if ($request->is_default) {
$this->removeOtherDefaultMailSenders($request);
}
return $this;
}
public static function setMailConfiguration($id, $check = null)
{
$mailSender = MailSender::find($id);
$settings = $mailSender->settings;
$settings['driver'] = $mailSender->driver;
$settings['from'] = [
'address' => $mailSender->from_address,
'name' => $mailSender->from_name
];
$settings['sendmail'] = config('mail.sendmail');
$settings['markdown'] = config('mail.markdown');
$settings['log_channel'] = config('mail.log_channel');
Config::set('mail', $settings);
if ($check) {
return $mailSender;
}
return true;
}
public function removeOtherDefaultMailSenders($request) {
MailSender::where('company_id', $request->header('company'))
->where('is_default', true)
->where('id', '<>', $this->id)
->update(['is_default' => false]);
}
}

View File

@ -5,10 +5,10 @@ namespace Crater\Models;
use Barryvdh\DomPDF\Facade as PDF;
use Carbon\Carbon;
use Crater\Jobs\GeneratePaymentPdfJob;
use Crater\Mail\SendPaymentMail;
use Crater\Services\SerialNumberFormatter;
use Crater\Traits\GeneratesPdfTrait;
use Crater\Traits\HasCustomFieldsTrait;
use Crater\Traits\MailTrait;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Spatie\MediaLibrary\HasMedia;
@ -18,6 +18,7 @@ use Vinkla\Hashids\Facades\Hashids;
class Payment extends Model implements HasMedia
{
use HasFactory;
use MailTrait;
use InteractsWithMedia;
use GeneratesPdfTrait;
use HasCustomFieldsTrait;
@ -135,7 +136,7 @@ class Payment extends Model implements HasMedia
{
$data = $this->sendPaymentData($data);
\Mail::to($data['to'])->send(new SendPaymentMail($data));
$this->setMail('payment', $data);
return [
'success' => true,
@ -375,6 +376,10 @@ class Payment extends Model implements HasMedia
'logo' => $logo ?? null,
]);
if (request()->has('preview')) {
return view('app.pdf.payment.payment');
}
return PDF::loadView('app.pdf.payment.payment');
}
@ -432,7 +437,6 @@ class Payment extends Model implements HasMedia
'{PAYMENT_MODE}' => $this->paymentMethod ? $this->paymentMethod->name : null,
'{PAYMENT_NUMBER}' => $this->payment_number,
'{PAYMENT_AMOUNT}' => $this->reference_number,
'{PAYMENT_LINK}' => url('/customer/payments/pdf/'.$this->unique_hash)
];
}
@ -442,7 +446,7 @@ class Payment extends Model implements HasMedia
$serial = (new SerialNumberFormatter())
->setModel(new Payment())
->setCompany(request()->header('company'))
->setCompany($invoice->company_id)
->setCustomer($invoice->customer_id)
->setNextNumbers();
@ -455,7 +459,7 @@ class Payment extends Model implements HasMedia
$data['exchange_rate'] = $invoice->exchange_rate;
$data['base_amount'] = $data['amount'] * $data['exchange_rate'];
$data['currency_id'] = $invoice->currency_id;
$data['company_id'] = request()->header('company');
$data['company_id'] = $invoice->company_id;
$data['transaction_id'] = $transaction->id;
$payment = Payment::create($data);

View File

@ -31,11 +31,21 @@ class PaymentMethod extends Model
return $this->hasMany(Payment::class);
}
public function expenses()
{
return $this->hasMany(Expense::class);
}
public function company()
{
return $this->belongsTo(Company::class);
}
public function scopeWhereCompanyId($query, $id)
{
$query->where('company_id', $id);
}
public function scopeWhereCompany($query)
{
$query->where('company_id', request()->header('company'));
@ -48,7 +58,7 @@ class PaymentMethod extends Model
public function scopeWhereSearch($query, $search)
{
$query->where('name', 'LIKE', '%' . $search . '%');
$query->where('name', 'LIKE', '%'.$search.'%');
}
public function scopeApplyFilters($query, array $filters)
@ -88,8 +98,7 @@ class PaymentMethod extends Model
public static function getSettings($id)
{
$settings = PaymentMethod::whereCompany()
->find($id)
$settings = PaymentMethod::find($id)
->settings;
return $settings;

View File

@ -305,9 +305,15 @@ class RecurringInvoice extends Model
->setCustomer($this->customer_id)
->setNextNumbers();
$days = CompanySetting::getSetting('invoice_due_date_days', $this->company_id);
if (! $days || $days == "null") {
$days = 7;
}
$newInvoice['creator_id'] = $this->creator_id;
$newInvoice['invoice_date'] = Carbon::today()->format('Y-m-d');
$newInvoice['due_date'] = Carbon::today()->addDays(7)->format('Y-m-d');
$newInvoice['due_date'] = Carbon::today()->addDays($days)->format('Y-m-d');
$newInvoice['status'] = Invoice::STATUS_DRAFT;
$newInvoice['company_id'] = $this->company_id;
$newInvoice['paid_status'] = Invoice::STATUS_UNPAID;

View File

@ -0,0 +1,123 @@
<?php
namespace Crater\Policies;
use Crater\Models\MailSender;
use Crater\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;
use Silber\Bouncer\BouncerFacade;
class MailSenderPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*
* @param \Crater\Models\User $user
* @return \Illuminate\Auth\Access\Response|bool
*/
public function viewAny(User $user)
{
if (BouncerFacade::can('view-mail-sender', MailSender::class)) {
return true;
}
return false;
}
/**
* Determine whether the user can view the model.
*
* @param \Crater\Models\User $user
* @param \Crater\Models\MailSender $mailSender
* @return \Illuminate\Auth\Access\Response|bool
*/
public function view(User $user, MailSender $mailSender)
{
if (BouncerFacade::can('view-mail-sender', $mailSender) && $user->hasCompany($mailSender->company_id)) {
return true;
}
return false;
}
/**
* Determine whether the user can create models.
*
* @param \Crater\Models\User $user
* @return \Illuminate\Auth\Access\Response|bool
*/
public function create(User $user)
{
if (BouncerFacade::can('create-mail-sender', MailSender::class)) {
return true;
}
return false;
}
/**
* Determine whether the user can update the model.
*
* @param \Crater\Models\User $user
* @param \Crater\Models\MailSender $mailSender
* @return \Illuminate\Auth\Access\Response|bool
*/
public function update(User $user, MailSender $mailSender)
{
if (BouncerFacade::can('edit-mail-sender', $mailSender) && $user->hasCompany($mailSender->company_id)) {
return true;
}
return false;
}
/**
* Determine whether the user can delete the model.
*
* @param \Crater\Models\User $user
* @param \Crater\Models\MailSender $mailSender
* @return \Illuminate\Auth\Access\Response|bool
*/
public function delete(User $user, MailSender $mailSender)
{
if (BouncerFacade::can('delete-mail-sender', $mailSender) && $user->hasCompany($mailSender->company_id)) {
return true;
}
return false;
}
/**
* Determine whether the user can restore the model.
*
* @param \Crater\Models\User $user
* @param \Crater\Models\MailSender $mailSender
* @return \Illuminate\Auth\Access\Response|bool
*/
public function restore(User $user, MailSender $mailSender)
{
if (BouncerFacade::can('delete-mail-sender', $mailSender) && $user->hasCompany($mailSender->company_id)) {
return true;
}
return false;
}
/**
* Determine whether the user can permanently delete the model.
*
* @param \Crater\Models\User $user
* @param \Crater\Models\MailSender $mailSender
* @return \Illuminate\Auth\Access\Response|bool
*/
public function forceDelete(User $user, MailSender $mailSender)
{
if (BouncerFacade::can('delete-mail-sender', $mailSender) && $user->hasCompany($mailSender->company_id)) {
return true;
}
return false;
}
}

View File

@ -39,6 +39,7 @@ class AuthServiceProvider extends ServiceProvider
\Crater\Models\CustomField::class => \Crater\Policies\CustomFieldPolicy::class,
\Crater\Models\User::class => \Crater\Policies\UserPolicy::class,
\Crater\Models\Item::class => \Crater\Policies\ItemPolicy::class,
\Crater\Models\MailSender::class => \Crater\Policies\MailSenderPolicy::class,
\Silber\Bouncer\Database\Role::class => \Crater\Policies\RolePolicy::class,
\Crater\Models\Unit::class => \Crater\Policies\UnitPolicy::class,
\Crater\Models\RecurringInvoice::class => \Crater\Policies\RecurringInvoicePolicy::class,

View File

@ -0,0 +1,36 @@
<?php
namespace Crater\Providers;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
use Schema;
class ViewServiceProvider extends ServiceProvider
{
/**
* Register services.
*
* @return void
*/
public function register()
{
//
}
/**
* Bootstrap services.
*
* @return void
*/
public function boot()
{
if (\Storage::disk('local')->has('database_created') && Schema::hasTable('settings')) {
View::share('login_page_logo', get_app_setting('login_page_logo'));
View::share('login_page_heading', get_app_setting('login_page_heading'));
View::share('login_page_description', get_app_setting('login_page_description'));
View::share('admin_page_title', get_app_setting('admin_page_title'));
View::share('copyright_text', get_app_setting('copyright_text'));
}
}
}

View File

@ -223,204 +223,6 @@ class EnvironmentManager
return false;
}
/**
* Save the mail content to the .env file.
*
* @param Request $request
* @return array
*/
public function saveMailVariables(MailEnvironmentRequest $request)
{
$mailData = $this->getMailData($request);
try {
file_put_contents($this->envPath, str_replace(
$mailData['old_mail_data'],
$mailData['new_mail_data'],
file_get_contents($this->envPath)
));
if ($mailData['extra_old_mail_data']) {
file_put_contents($this->envPath, str_replace(
$mailData['extra_old_mail_data'],
$mailData['extra_mail_data'],
file_get_contents($this->envPath)
));
} else {
file_put_contents(
$this->envPath,
"\n".$mailData['extra_mail_data'],
FILE_APPEND
);
}
} catch (Exception $e) {
return [
'error' => 'mail_variables_save_error',
];
}
return [
'success' => 'mail_variables_save_successfully',
];
}
private function getMailData($request)
{
$mailFromCredential = "";
$extraMailData = "";
$extraOldMailData = "";
$oldMailData = "";
$newMailData = "";
if (env('MAIL_FROM_ADDRESS') !== null && env('MAIL_FROM_NAME') !== null) {
$mailFromCredential =
'MAIL_FROM_ADDRESS='.config('mail.from.address')."\n".
'MAIL_FROM_NAME="'.config('mail.from.name')."\"\n\n";
}
switch ($request->mail_driver) {
case 'smtp':
$oldMailData =
'MAIL_DRIVER='.config('mail.driver')."\n".
'MAIL_HOST='.config('mail.host')."\n".
'MAIL_PORT='.config('mail.port')."\n".
'MAIL_USERNAME='.config('mail.username')."\n".
'MAIL_PASSWORD='.config('mail.password')."\n".
'MAIL_ENCRYPTION='.config('mail.encryption')."\n\n".
$mailFromCredential;
$newMailData =
'MAIL_DRIVER='.$request->mail_driver."\n".
'MAIL_HOST='.$request->mail_host."\n".
'MAIL_PORT='.$request->mail_port."\n".
'MAIL_USERNAME='.$request->mail_username."\n".
'MAIL_PASSWORD='.$request->mail_password."\n".
'MAIL_ENCRYPTION='.$request->mail_encryption."\n\n".
'MAIL_FROM_ADDRESS='.$request->from_mail."\n".
'MAIL_FROM_NAME="'.$request->from_name."\"\n\n";
break;
case 'mailgun':
$oldMailData =
'MAIL_DRIVER='.config('mail.driver')."\n".
'MAIL_HOST='.config('mail.host')."\n".
'MAIL_PORT='.config('mail.port')."\n".
'MAIL_USERNAME='.config('mail.username')."\n".
'MAIL_PASSWORD='.config('mail.password')."\n".
'MAIL_ENCRYPTION='.config('mail.encryption')."\n\n".
$mailFromCredential;
$newMailData =
'MAIL_DRIVER='.$request->mail_driver."\n".
'MAIL_HOST='.$request->mail_host."\n".
'MAIL_PORT='.$request->mail_port."\n".
'MAIL_USERNAME='.config('mail.username')."\n".
'MAIL_PASSWORD='.config('mail.password')."\n".
'MAIL_ENCRYPTION='.$request->mail_encryption."\n\n".
'MAIL_FROM_ADDRESS='.$request->from_mail."\n".
'MAIL_FROM_NAME="'.$request->from_name."\"\n\n";
$extraMailData =
'MAILGUN_DOMAIN='.$request->mail_mailgun_domain."\n".
'MAILGUN_SECRET='.$request->mail_mailgun_secret."\n".
'MAILGUN_ENDPOINT='.$request->mail_mailgun_endpoint."\n";
if (env('MAILGUN_DOMAIN') !== null && env('MAILGUN_SECRET') !== null && env('MAILGUN_ENDPOINT') !== null) {
$extraOldMailData =
'MAILGUN_DOMAIN='.config('services.mailgun.domain')."\n".
'MAILGUN_SECRET='.config('services.mailgun.secret')."\n".
'MAILGUN_ENDPOINT='.config('services.mailgun.endpoint')."\n";
}
break;
case 'ses':
$oldMailData =
'MAIL_DRIVER='.config('mail.driver')."\n".
'MAIL_HOST='.config('mail.host')."\n".
'MAIL_PORT='.config('mail.port')."\n".
'MAIL_USERNAME='.config('mail.username')."\n".
'MAIL_PASSWORD='.config('mail.password')."\n".
'MAIL_ENCRYPTION='.config('mail.encryption')."\n\n".
$mailFromCredential;
$newMailData =
'MAIL_DRIVER='.$request->mail_driver."\n".
'MAIL_HOST='.$request->mail_host."\n".
'MAIL_PORT='.$request->mail_port."\n".
'MAIL_USERNAME='.config('mail.username')."\n".
'MAIL_PASSWORD='.config('mail.password')."\n".
'MAIL_ENCRYPTION='.$request->mail_encryption."\n\n".
'MAIL_FROM_ADDRESS='.$request->from_mail."\n".
'MAIL_FROM_NAME="'.$request->from_name."\"\n\n";
$extraMailData =
'SES_KEY='.$request->mail_ses_key."\n".
'SES_SECRET='.$request->mail_ses_secret."\n";
if (env('SES_KEY') !== null && env('SES_SECRET') !== null) {
$extraOldMailData =
'SES_KEY='.config('services.ses.key')."\n".
'SES_SECRET='.config('services.ses.secret')."\n";
}
break;
case 'mail':
$oldMailData =
'MAIL_DRIVER='.config('mail.driver')."\n".
'MAIL_HOST='.config('mail.host')."\n".
'MAIL_PORT='.config('mail.port')."\n".
'MAIL_USERNAME='.config('mail.username')."\n".
'MAIL_PASSWORD='.config('mail.password')."\n".
'MAIL_ENCRYPTION='.config('mail.encryption')."\n\n".
$mailFromCredential;
$newMailData =
'MAIL_DRIVER='.$request->mail_driver."\n".
'MAIL_HOST='.config('mail.host')."\n".
'MAIL_PORT='.config('mail.port')."\n".
'MAIL_USERNAME='.config('mail.username')."\n".
'MAIL_PASSWORD='.config('mail.password')."\n".
'MAIL_ENCRYPTION='.config('mail.encryption')."\n\n".
'MAIL_FROM_ADDRESS='.$request->from_mail."\n".
'MAIL_FROM_NAME="'.$request->from_name."\"\n\n";
break;
case 'sendmail':
$oldMailData =
'MAIL_DRIVER='.config('mail.driver')."\n".
'MAIL_HOST='.config('mail.host')."\n".
'MAIL_PORT='.config('mail.port')."\n".
'MAIL_USERNAME='.config('mail.username')."\n".
'MAIL_PASSWORD='.config('mail.password')."\n".
'MAIL_ENCRYPTION='.config('mail.encryption')."\n\n".
$mailFromCredential;
$newMailData =
'MAIL_DRIVER='.$request->mail_driver."\n".
'MAIL_HOST='.config('mail.host')."\n".
'MAIL_PORT='.config('mail.port')."\n".
'MAIL_USERNAME='.config('mail.username')."\n".
'MAIL_PASSWORD='.config('mail.password')."\n".
'MAIL_ENCRYPTION='.config('mail.encryption')."\n\n".
'MAIL_FROM_ADDRESS='.$request->from_mail."\n".
'MAIL_FROM_NAME="'.$request->from_name."\"\n\n";
break;
}
return [
'old_mail_data' => $oldMailData,
'new_mail_data' => $newMailData,
'extra_mail_data' => $extraMailData,
'extra_old_mail_data' => $extraOldMailData,
];
}
/**
* Save the disk content to the .env file.
*

View File

@ -5,53 +5,58 @@ use Crater\Models\Currency;
use Crater\Models\CustomField;
use Crater\Models\Setting;
use Illuminate\Support\Str;
use Illuminate\Mail\Mailable;
/**
* Get current customer theme
* Get company setting
*
* @param $company_id
* @return string
*/
function get_customer_portal_theme($company_id)
function get_company_setting($key, $company_id)
{
if (\Storage::disk('local')->has('database_created')) {
return CompanySetting::getSetting('customer_portal_theme', $company_id);
return CompanySetting::getSetting($key, $company_id);
}
}
/**
* Get current customer logo
* Get app setting
*
* @param $company_id
* @return string
*/
function get_customer_logo($company_id)
function get_app_setting($key)
{
if (\Storage::disk('local')->has('database_created')) {
return CompanySetting::getSetting('customer_portal_logo', $company_id);
return Setting::getSetting($key);
}
}
/**
* Get current admin theme
* Get page title
*
* @param $company_id
* @return string
*/
function get_admin_portal_theme()
function get_page_title($company_id)
{
if (\Storage::disk('local')->has('database_created')) {
$setting = Setting::getSetting('admin_portal_theme');
$routeName = Route::currentRouteName();
if ($setting) {
return $setting;
$pageTitle = null;
$defaultPageTitle = 'Crater - Self Hosted Invoicing Platform';
if (\Storage::disk('local')->has('database_created')) {
if ($routeName === 'customer.dashboard') {
$pageTitle = CompanySetting::getSetting('customer_portal_page_title', $company_id);
return $pageTitle ? $pageTitle : $defaultPageTitle;
}
return 'crater';
}
$pageTitle = Setting::getSetting('admin_page_title');
return 'crater';
return $pageTitle ? $pageTitle : $defaultPageTitle;
}
}
/**
@ -66,6 +71,42 @@ function set_active($path, $active = 'active')
return call_user_func_array('Request::is', (array)$path) ? $active : '';
}
/**
* Send Mail
*
* @param Mailable $mailable
* @param object $mailSender
* @return string $to
*/
function send_mail(Mailable $mailable, object $mailSender = null, string $to)
{
if ($mailSender->bcc && $mailSender->cc) {
\Mail::to($to)
->bcc(explode(',', $mailSender->bcc))
->cc(explode(',', $mailSender->cc))
->send($mailable);
}
if ($mailSender->bcc && $mailSender->cc == null) {
\Mail::to($to)
->bcc(explode(',', $mailSender->bcc))
->send($mailable);
}
if ($mailSender->bcc == null && $mailSender->cc) {
\Mail::to($to)
->cc(explode(',', $mailSender->cc))
->send($mailable);
}
if ($mailSender->bcc == null && $mailSender->cc == null) {
\Mail::to($to)
->send($mailable);
}
return true;
}
/**
* @param $path
* @return mixed

View File

@ -16,7 +16,7 @@ trait GeneratesPdfTrait
if ($pdf && file_exists($pdf['path'])) {
return response()->make(file_get_contents($pdf['path']), 200, [
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'inline; filename="'.$pdf['file_name'].'.pdf"',
'Content-Disposition' => 'inline; filename="'.$pdf['file_name'].'"',
]);
}
@ -158,6 +158,10 @@ trait GeneratesPdfTrait
$fields['{'.$customField->customField->slug.'}'] = $customField->defaultAnswer;
}
foreach ($fields as $key => $field) {
$fields[$key] = htmlspecialchars($field, ENT_QUOTES, 'UTF-8');
}
return $fields;
}

40
app/Traits/MailTrait.php Normal file
View File

@ -0,0 +1,40 @@
<?php
namespace Crater\Traits;
use Crater\Mail\EstimateViewedMail;
use Crater\Mail\InvoiceViewedMail;
use Crater\Mail\SendEstimateMail;
use Crater\Mail\SendInvoiceMail;
use Crater\Mail\SendPaymentMail;
use Crater\Models\MailSender;
trait MailTrait
{
public function setMail($model, $data)
{
$mailSender = MailSender::setMailConfiguration($data['mail_sender_id'], true);
$data['from_address'] = $mailSender->from_address;
$data['from_name'] = $mailSender->from_name;
switch ($model) {
case 'invoice':
send_mail(new SendInvoiceMail($data), $mailSender, $data['to']);
break;
case 'estimate':
send_mail(new SendEstimateMail($data), $mailSender, $data['to']);
break;
case 'payment':
send_mail(new SendPaymentMail($data), $mailSender, $data['to']);
break;
}
return true;
}
}

View File

@ -10,8 +10,8 @@
"require": {
"php": "^7.4 || ^8.0",
"aws/aws-sdk-php": "^3.142",
"barryvdh/laravel-dompdf": "^0.9.0",
"crater-invoice/modules": "^1.0.0",
"barryvdh/laravel-dompdf": "^0.8.7",
"doctrine/dbal": "^2.10",
"dragonmantank/cron-expression": "^3.1",
"fideloper/proxy": "^4.0",
@ -38,15 +38,15 @@
"barryvdh/laravel-ide-helper": "^2.6",
"beyondcode/laravel-dump-server": "^1.0",
"facade/ignition": "^2.3.6",
"friendsofphp/php-cs-fixer": "^3.0",
"fzaninotto/faker": "^1.9.1",
"friendsofphp/php-cs-fixer": "^3.8",
"fakerphp/faker": "^1.9.1",
"mockery/mockery": "^1.3.1",
"nunomaduro/collision": "^5.0",
"pestphp/pest": "^1.0",
"pestphp/pest-plugin-faker": "^1.0",
"pestphp/pest-plugin-laravel": "^1.0",
"pestphp/pest-plugin-parallel": "^0.2.1",
"phpunit/phpunit": "^9.0"
"phpunit/phpunit": "^9.3"
},
"autoload": {
"psr-4": {
@ -81,7 +81,10 @@
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true
"sort-packages": true,
"allow-plugins": {
"pestphp/pest-plugin": true
}
},
"extra": {
"laravel": {

2384
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -7,6 +7,7 @@ use Crater\Models\ExchangeRateProvider;
use Crater\Models\Expense;
use Crater\Models\Invoice;
use Crater\Models\Item;
use Crater\Models\MailSender;
use Crater\Models\Note;
use Crater\Models\Payment;
use Crater\Models\RecurringInvoice;
@ -397,6 +398,41 @@ return [
]
],
// Mail Sender
[
"name" => "view mail sender",
"ability" => "view-mail-sender",
"model" => MailSender::class,
'owner_only' => false,
],
[
"name" => "create mail sender",
"ability" => "create-mail-sender",
"model" => MailSender::class,
'owner_only' => false,
"depends_on" => [
'view-mail-sender',
]
],
[
"name" => "edit mail sender",
"ability" => "edit-mail-sender",
"model" => MailSender::class,
'owner_only' => false,
"depends_on" => [
'view-mail-sender',
]
],
[
"name" => "delete mail sender",
"ability" => "delete-mail-sender",
"model" => MailSender::class,
'owner_only' => false,
"depends_on" => [
'view-mail-sender',
]
],
// Settings
[
"name" => "view company dashboard",

View File

@ -168,6 +168,7 @@ return [
Crater\Providers\EventServiceProvider::class,
Crater\Providers\RouteServiceProvider::class,
Crater\Providers\DropboxServiceProvider::class,
Crater\Providers\ViewServiceProvider::class,
Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class,
],

View File

@ -7,6 +7,7 @@ use Crater\Models\ExchangeRateProvider;
use Crater\Models\Expense;
use Crater\Models\Invoice;
use Crater\Models\Item;
use Crater\Models\MailSender;
use Crater\Models\Note;
use Crater\Models\Payment;
use Crater\Models\RecurringInvoice;
@ -68,6 +69,10 @@ return [
["code" => "sv", "name" => "Svenska"],
["code" => "sk", "name" => "Slovak"],
["code" => "vi", "name" => "Tiếng Việt"],
["code" => "cs", "name" => "Czech"],
["code" => "el", "name" => "Greek"],
["code" => "hr", "name" => "Crotian"],
["code" => "th", "name" => "ไทย"],
],
/*
@ -221,6 +226,17 @@ return [
'ability' => 'view-all-notes',
'model' => Note::class
],
[
'title' => 'settings.menu_title.mail_sender',
'group' => '',
'name' => 'Mail Sender',
'link' => '/admin/settings/mail-sender',
'icon' => 'MailIcon',
'owner_only' => false,
'ability' => 'view-mail-sender',
'model' => MailSender::class
],
[
'title' => 'settings.menu_title.expense_category',
'group' => '',
@ -231,16 +247,6 @@ return [
'ability' => 'view-expense',
'model' => Expense::class
],
[
'title' => 'settings.mail.mail_config',
'group' => '',
'name' => 'Mail Configuration',
'link' => '/admin/settings/mail-configuration',
'icon' => 'MailIcon',
'owner_only' => true,
'ability' => '',
'model' => ''
],
[
'title' => 'settings.menu_title.file_disk',
'group' => '',
@ -271,6 +277,7 @@ return [
'ability' => '',
'model' => ''
],
],
/*

View File

@ -27,6 +27,7 @@ return [
'tokenizer',
'JSON',
'cURL',
'zip',
],
'apache' => [
'mod_rewrite',

3
crowdin.yml Normal file
View File

@ -0,0 +1,3 @@
files:
- source: /resources/scripts/locales/en.json
translation: /resources/scripts/locales/%two_letters_code%.json

View File

@ -37,15 +37,6 @@ class InvoiceFactory extends Factory
});
}
public function overdue()
{
return $this->state(function (array $attributes) {
return [
'status' => Invoice::STATUS_OVERDUE,
];
});
}
public function completed()
{
return $this->state(function (array $attributes) {
@ -55,15 +46,6 @@ class InvoiceFactory extends Factory
});
}
public function due()
{
return $this->state(function (array $attributes) {
return [
'status' => Invoice::STATUS_DUE,
];
});
}
public function unpaid()
{
return $this->state(function (array $attributes) {

View File

@ -21,7 +21,7 @@ class ChangeEnablePortalFieldOfCustomersTable extends Migration
$customers = Customer::all();
if ($customers) {
$customers->map(function ($customer) {
$customers->map(function ($customer) {
$customer->enable_portal = false;
$customer->save();
});

View File

@ -0,0 +1,27 @@
<?php
use Crater\Models\Setting;
use Illuminate\Database\Migrations\Migration;
class UpdateCraterVersion601 extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Setting::setSetting('version', '6.0.1');
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}

View File

@ -0,0 +1,27 @@
<?php
use Crater\Models\Setting;
use Illuminate\Database\Migrations\Migration;
class UpdateCraterVersion602 extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Setting::setSetting('version', '6.0.2');
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}

View File

@ -0,0 +1,27 @@
<?php
use Crater\Models\Setting;
use Illuminate\Database\Migrations\Migration;
class UpdateCraterVersion603 extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Setting::setSetting('version', '6.0.3');
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}

View File

@ -0,0 +1,27 @@
<?php
use Crater\Models\Setting;
use Illuminate\Database\Migrations\Migration;
class UpdateCraterVersion604 extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Setting::setSetting('version', '6.0.4');
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}

View File

@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class UpdateValueColumnToNullableOnSettingsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('settings', function (Blueprint $table) {
$table->string('value')->nullable()->change();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddOverdueToInvoicesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('invoices', function (Blueprint $table) {
$table->boolean('overdue')->default(false);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('invoices', function (Blueprint $table) {
$table->dropForeign(['overdue']);
});
}
}

View File

@ -0,0 +1,27 @@
<?php
use Crater\Models\Setting;
use Illuminate\Database\Migrations\Migration;
class CraterVersion605 extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Setting::setSetting('version', '6.0.5');
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}

View File

@ -0,0 +1,35 @@
<?php
use Crater\Models\Invoice;
use Illuminate\Database\Migrations\Migration;
class ChangeOverDueStatusToSent extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$overdueInvoices = Invoice::where('status', 'OVERDUE')->get();
if ($overdueInvoices) {
$overdueInvoices->map(function ($overdueInvoice) {
$overdueInvoice->status = Invoice::STATUS_SENT;
$overdueInvoice->overdue = true;
$overdueInvoice->save();
});
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}

View File

@ -0,0 +1,38 @@
<?php
use Crater\Models\InvoiceItem;
use Crater\Models\Tax;
use Illuminate\Database\Migrations\Migration;
class CalculateBaseValuesForInvoiceItems extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$taxes = Tax::whereRelation('invoiceItem', 'base_amount', null)->get();
if ($taxes) {
$taxes->map(function ($tax) {
$invoiceItem = InvoiceItem::find($tax->invoice_item_id);
$exchange_rate = $invoiceItem->exchange_rate;
$tax->exchange_rate = $exchange_rate;
$tax->base_amount = $tax->amount * $exchange_rate;
$tax->save();
});
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}

View File

@ -0,0 +1,27 @@
<?php
use Crater\Models\Setting;
use Illuminate\Database\Migrations\Migration;
class UpdateCraterVersion606 extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Setting::setSetting('version', '6.0.6');
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}

View File

@ -0,0 +1,106 @@
<?php
use Crater\Models\Company;
use Crater\Models\MailSender;
use Crater\Models\User;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Silber\Bouncer\BouncerFacade;
class CreateMailSendersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('mail_senders', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('driver');
$table->boolean('is_default')->default(false);
$table->string('bcc')->nullable();
$table->string('cc')->nullable();
$table->string('from_address')->nullable();
$table->string('from_name')->nullable();
$table->json('settings')->nullable();
$table->integer('company_id')->unsigned()->nullable();
$table->foreign('company_id')->references('id')->on('companies');
$table->timestamps();
});
$users = User::where('role', 'super admin')->get();
foreach ($users as $user) {
BouncerFacade::allow($user)->toManage(MailSender::class);
}
$companies = Company::all();
$companies->map(function ($company) {
if (env('MAIL_DRIVER') == 'smtp') {
$settings = [
'MAIL_HOST' => env('MAIL_HOST'),
'MAIL_PORT' => env('MAIL_PORT'),
'MAIL_USERNAME' => env('MAIL_USERNAME'),
'MAIL_PASSWORD' => env('MAIL_PASSWORD'),
'MAIL_ENCRYPTION' => env('MAIL_ENCRYPTION')
];
$this->createSender($settings, $company->id);
}
if (env('MAIL_DRIVER') == 'mail' || env('MAIL_DRIVER') == 'sendmail') {
$this->createSender(null, $company->id);
}
if (env('MAIL_DRIVER') == 'mailgun') {
$settings = [
'MAILGUN_DOMAIN' => env('MAILGUN_DOMAIN'),
'MAILGUN_SECRET' => env('MAILGUN_SECRET'),
'MAILGUN_ENDPOINT' => env('MAILGUN_ENDPOINT'),
];
$this->createSender($settings, $company->id);
}
if (env('MAIL_DRIVER') == 'ses') {
$settings = [
'MAIL_HOST' => env('MAIL_HOST'),
'MAIL_PORT' => env('MAIL_PORT'),
'MAIL_ENCRYPTION' => env('MAIL_ENCRYPTION'),
'MAILGUN_DOMAIN' => env('MAILGUN_DOMAIN'),
'SES_KEY' => env('SES_KEY'),
'SES_SECRET' => env('SES_SECRET'),
];
$this->createSender($settings, $company->id);
}
});
}
public function createSender($settings, $company_id)
{
$data = [
'name' => env('MAIL_DRIVER'),
'driver' => env('MAIL_DRIVER'),
'is_default' => true,
'from_address' => env('MAIL_FROM_ADDRESS'),
'from_name' => env('MAIL_FROM_NAME'),
'settings' => $settings ?? null,
'company_id' => $company_id
];
MailSender::create($data);
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('mail_senders');
}
}

View File

@ -170,7 +170,7 @@ class CountriesTableSeeder extends Seeder
['id' => 152,'code' => 'NR','name' => "Nauru",'phonecode' => 674],
['id' => 153,'code' => 'NP','name' => "Nepal",'phonecode' => 977],
['id' => 154,'code' => 'AN','name' => "Netherlands Antilles",'phonecode' => 599],
['id' => 155,'code' => 'NL','name' => "Netherlands The",'phonecode' => 31],
['id' => 155,'code' => 'NL','name' => "Netherlands",'phonecode' => 31],
['id' => 156,'code' => 'NC','name' => "New Caledonia",'phonecode' => 687],
['id' => 157,'code' => 'NZ','name' => "New Zealand",'phonecode' => 64],
['id' => 158,'code' => 'NI','name' => "Nicaragua",'phonecode' => 505],

View File

@ -106,6 +106,14 @@ class CurrenciesTableSeeder extends Seeder
'thousand_separator' => ',',
'decimal_separator' => '.',
],
[
'name' => 'Nepali Rupee',
'code' => 'NPR',
'symbol' => 'रू',
'precision' => '2',
'thousand_separator' => ',',
'decimal_separator' => '.',
],
[
'name' => 'Indian Rupee',
'code' => 'INR',
@ -267,6 +275,14 @@ class CurrenciesTableSeeder extends Seeder
'thousand_separator' => '.',
'decimal_separator' => ',',
],
[
'name' => 'Central African Franc',
'code' => 'XAF',
'symbol' => 'CFA ',
'precision' => '2',
'thousand_separator' => ',',
'decimal_separator' => '.',
],
[
'name' => 'West African Franc',
'code' => 'XOF',
@ -575,6 +591,15 @@ class CurrenciesTableSeeder extends Seeder
'thousand_separator' => ',',
'decimal_separator' => '.',
],
[
'name' => 'Macedonian Denar',
'code' => 'MKD',
'symbol' => 'ден',
'precision' => '0',
'thousand_separator' => '.',
'decimal_separator' => ',',
'swap_currency_symbol' => true,
],
];

View File

@ -1,7 +1,7 @@
FROM php:7.4-fpm-alpine
FROM php:8.0-fpm-alpine
RUN apk add --no-cache \
php7-bcmath
php8-bcmath
RUN docker-php-ext-install pdo pdo_mysql bcmath

View File

@ -33,6 +33,7 @@
"@stripe/stripe-js": "^1.21.2",
"@tailwindcss/line-clamp": "^0.3.0",
"@tiptap/core": "^2.0.0-beta.85",
"@tiptap/extension-text-align": "^2.0.0-beta.29",
"@tiptap/starter-kit": "^2.0.0-beta.81",
"@tiptap/vue-3": "^2.0.0-beta.38",
"@vuelidate/components": "^1.1.12",

View File

@ -1 +0,0 @@
import{G as u,aN as d,k as m,r as n,o as h,e as p,h as s,t as o,f as c,w as f,i as _,u as x}from"./vendor.01d0adc5.js";const g={class:"w-full h-screen h-screen-ios"},w={class:"flex items-center justify-center w-full h-full"},y={class:"flex flex-col items-center justify-center"},b={class:"text-primary-500",style:{"font-size":"10rem"}},v={class:"mb-10 text-3xl text-primary-500"},$={setup(k){const e=u();d();const l=m(()=>{if(e.path.indexOf("customer")>-1&&e.params.company)return`/${e.params.company}/customer/dashboard`;if(e.params.catchAll){let a=e.params.catchAll.indexOf("/");return a>-1?`/${e.params.catchAll.substring(a,0)}/customer/dashboard`:"/"}else return"/admin/dashboard"});return(t,a)=>{const r=n("BaseIcon"),i=n("router-link");return h(),p("div",g,[s("div",w,[s("div",y,[s("h1",b,o(t.$t("general.four_zero_four")),1),s("h5",v,o(t.$t("general.you_got_lost")),1),c(i,{class:"flex items-center w-32 h-12 px-3 py-1 text-base font-medium leading-none text-center text-white rounded whitespace-nowrap bg-primary-500 btn-lg hover:text-white",to:x(l)},{default:f(()=>[c(r,{name:"ArrowLeftIcon",class:"mr-2 text-white icon"}),_(" "+o(t.$t("general.go_home")),1)]),_:1},8,["to"])])])])}}};export{$ as default};

View File

@ -0,0 +1 @@
import{G as u,aN as d,k as m,r as n,o as h,e as p,h as s,t as o,f as c,w as f,i as _,u as x}from"./vendor.d12b5734.js";const g={class:"w-full h-screen"},w={class:"flex items-center justify-center w-full h-full"},y={class:"flex flex-col items-center justify-center"},b={class:"text-primary-500",style:{"font-size":"10rem"}},v={class:"mb-10 text-3xl text-primary-500"},$={setup(k){const e=u();d();const l=m(()=>{if(e.path.indexOf("customer")>-1&&e.params.company)return`/${e.params.company}/customer/dashboard`;if(e.params.catchAll){let a=e.params.catchAll.indexOf("/");return a>-1?`/${e.params.catchAll.substring(a,0)}/customer/dashboard`:"/"}else return"/admin/dashboard"});return(t,a)=>{const r=n("BaseIcon"),i=n("router-link");return h(),p("div",g,[s("div",w,[s("div",y,[s("h1",b,o(t.$t("general.four_zero_four")),1),s("h5",v,o(t.$t("general.you_got_lost")),1),c(i,{class:"flex items-center w-32 h-12 px-3 py-1 text-base font-medium leading-none text-center text-white rounded whitespace-nowrap bg-primary-500 btn-lg hover:text-white",to:x(l)},{default:f(()=>[c(r,{name:"ArrowLeftIcon",class:"mr-2 text-white icon"}),_(" "+o(t.$t("general.go_home")),1)]),_:1},8,["to"])])])])}}};export{$ as default};

View File

@ -0,0 +1 @@
var L=Object.defineProperty,P=Object.defineProperties;var T=Object.getOwnPropertyDescriptors;var V=Object.getOwnPropertySymbols;var z=Object.prototype.hasOwnProperty,E=Object.prototype.propertyIsEnumerable;var U=(u,s,i)=>s in u?L(u,s,{enumerable:!0,configurable:!0,writable:!0,value:i}):u[s]=i,S=(u,s)=>{for(var i in s||(s={}))z.call(s,i)&&U(u,i,s[i]);if(V)for(var i of V(s))E.call(s,i)&&U(u,i,s[i]);return u},I=(u,s)=>P(u,T(s));import{J,B as b,k as y,L as _,M as C,Q,N as H,P as K,a0 as O,T as W,r as m,o as M,e as X,f as r,w as d,u as e,x as Y,l as Z,m as x,j as ee,i as ae,t as se,U as te,h as ne}from"./vendor.d12b5734.js";import{e as oe,d as re,b as le}from"./main.465728e1.js";const ie=["onSubmit"],ue=ne("span",null,null,-1),ce={setup(u){const s=oe(),i=re(),F=le(),{t:v}=J();let p=b(!1),c=b(null),f=b([]);const $=b(!1);s.currentUser.avatar&&f.value.push({image:s.currentUser.avatar});const q=y(()=>({name:{required:_.withMessage(v("validation.required"),C)},email:{required:_.withMessage(v("validation.required"),C),email:_.withMessage(v("validation.email_incorrect"),Q)},password:{minLength:_.withMessage(v("validation.password_length",{count:8}),H(8))},confirm_password:{sameAsPassword:_.withMessage(v("validation.password_incorrect"),K(t.password))}})),t=O({name:s.currentUser.name,email:s.currentUser.email,language:s.currentUserSettings.language||F.selectedCompanySettings.language,password:"",confirm_password:""}),o=W(q,y(()=>t));function k(l,a){c.value=a}function N(){c.value=null,$.value=!0}async function A(){if(o.value.$touch(),o.value.$invalid)return!0;p.value=!0;let l={name:t.name,email:t.email};try{if(t.password!=null&&t.password!==void 0&&t.password!==""&&(l=I(S({},l),{password:t.password})),s.currentUserSettings.language!==t.language&&await s.updateUserSettings({settings:{language:t.language}}),(await s.updateCurrentUser(l)).data.data){if(p.value=!1,c.value||$.value){let w=new FormData;c.value&&w.append("admin_avatar",c.value),w.append("is_admin_avatar_removed",$.value),await s.uploadAvatar(w),c.value=null,$.value=!1}t.password="",t.confirm_password=""}}catch{return p.value=!1,!0}}return(l,a)=>{const w=m("BaseFileUploader"),g=m("BaseInputGroup"),B=m("BaseInput"),G=m("BaseMultiselect"),D=m("BaseInputGrid"),R=m("BaseIcon"),h=m("BaseButton"),j=m("BaseSettingCard");return M(),X("form",{class:"relative",onSubmit:te(A,["prevent"])},[r(j,{title:l.$t("settings.account_settings.account_settings"),description:l.$t("settings.account_settings.section_description")},{default:d(()=>[r(D,null,{default:d(()=>[r(g,{label:l.$tc("settings.account_settings.profile_picture")},{default:d(()=>[r(w,{modelValue:e(f),"onUpdate:modelValue":a[0]||(a[0]=n=>Y(f)?f.value=n:f=n),avatar:!0,accept:"image/*",onChange:k,onRemove:N},null,8,["modelValue"])]),_:1},8,["label"]),ue,r(g,{label:l.$tc("settings.account_settings.name"),error:e(o).name.$error&&e(o).name.$errors[0].$message,required:""},{default:d(()=>[r(B,{modelValue:e(t).name,"onUpdate:modelValue":a[1]||(a[1]=n=>e(t).name=n),invalid:e(o).name.$error,onInput:a[2]||(a[2]=n=>e(o).name.$touch())},null,8,["modelValue","invalid"])]),_:1},8,["label","error"]),r(g,{label:l.$tc("settings.account_settings.email"),error:e(o).email.$error&&e(o).email.$errors[0].$message,required:""},{default:d(()=>[r(B,{modelValue:e(t).email,"onUpdate:modelValue":a[3]||(a[3]=n=>e(t).email=n),invalid:e(o).email.$error,onInput:a[4]||(a[4]=n=>e(o).email.$touch())},null,8,["modelValue","invalid"])]),_:1},8,["label","error"]),r(g,{error:e(o).password.$error&&e(o).password.$errors[0].$message,label:l.$tc("settings.account_settings.password")},{default:d(()=>[r(B,{modelValue:e(t).password,"onUpdate:modelValue":a[5]||(a[5]=n=>e(t).password=n),type:"password",onInput:a[6]||(a[6]=n=>e(o).password.$touch())},null,8,["modelValue"])]),_:1},8,["error","label"]),r(g,{label:l.$tc("settings.account_settings.confirm_password"),error:e(o).confirm_password.$error&&e(o).confirm_password.$errors[0].$message},{default:d(()=>[r(B,{modelValue:e(t).confirm_password,"onUpdate:modelValue":a[7]||(a[7]=n=>e(t).confirm_password=n),type:"password",onInput:a[8]||(a[8]=n=>e(o).confirm_password.$touch())},null,8,["modelValue"])]),_:1},8,["label","error"]),r(g,{label:l.$tc("settings.language")},{default:d(()=>[r(G,{modelValue:e(t).language,"onUpdate:modelValue":a[9]||(a[9]=n=>e(t).language=n),options:e(i).config.languages,label:"name","value-prop":"code","track-by":"name","open-direction":"top"},null,8,["modelValue","options"])]),_:1},8,["label"])]),_:1}),r(h,{loading:e(p),disabled:e(p),class:"mt-6"},{left:d(n=>[e(p)?ee("",!0):(M(),Z(R,{key:0,name:"SaveIcon",class:x(n.class)},null,8,["class"]))]),default:d(()=>[ae(" "+se(l.$tc("settings.company_info.save")),1)]),_:1},8,["loading","disabled"])]),_:1},8,["title","description"])],40,ie)}}};export{ce as default};

View File

@ -1 +0,0 @@
var L=Object.defineProperty,R=Object.defineProperties;var P=Object.getOwnPropertyDescriptors;var V=Object.getOwnPropertySymbols;var T=Object.prototype.hasOwnProperty,z=Object.prototype.propertyIsEnumerable;var b=(u,s,i)=>s in u?L(u,s,{enumerable:!0,configurable:!0,writable:!0,value:i}):u[s]=i,U=(u,s)=>{for(var i in s||(s={}))T.call(s,i)&&b(u,i,s[i]);if(V)for(var i of V(s))z.call(s,i)&&b(u,i,s[i]);return u},S=(u,s)=>R(u,P(s));import{J as E,B,k as I,L as v,M as y,Q as J,N as Q,P as H,a0 as K,T as O,r as m,o as C,e as W,f as r,w as d,u as e,x as X,l as Y,m as Z,j as x,i as ee,t as ae,U as se,h as te}from"./vendor.01d0adc5.js";import{e as ne,d as oe,b as re}from"./main.75722495.js";const le=["onSubmit"],ie=te("span",null,null,-1),pe={setup(u){const s=ne(),i=oe(),M=re(),{t:g}=E();let p=B(!1),w=B(null),f=B([]);s.currentUser.avatar&&f.value.push({image:s.currentUser.avatar});const F=I(()=>({name:{required:v.withMessage(g("validation.required"),y)},email:{required:v.withMessage(g("validation.required"),y),email:v.withMessage(g("validation.email_incorrect"),J)},password:{minLength:v.withMessage(g("validation.password_length",{count:8}),Q(8))},confirm_password:{sameAsPassword:v.withMessage(g("validation.password_incorrect"),H(t.password))}})),t=K({name:s.currentUser.name,email:s.currentUser.email,language:s.currentUserSettings.language||M.selectedCompanySettings.language,password:"",confirm_password:""}),o=O(F,I(()=>t));function q(l,a){w.value=a}function k(){w.value=null}async function N(){if(o.value.$touch(),o.value.$invalid)return!0;p.value=!0;let l={name:t.name,email:t.email};try{if(t.password!=null&&t.password!==void 0&&t.password!==""&&(l=S(U({},l),{password:t.password})),s.currentUserSettings.language!==t.language&&await s.updateUserSettings({settings:{language:t.language}}),(await s.updateCurrentUser(l)).data.data){if(p.value=!1,w.value){let $=new FormData;$.append("admin_avatar",w.value),await s.uploadAvatar($)}t.password="",t.confirm_password=""}}catch{return p.value=!1,!0}}return(l,a)=>{const $=m("BaseFileUploader"),c=m("BaseInputGroup"),_=m("BaseInput"),G=m("BaseMultiselect"),D=m("BaseInputGrid"),h=m("BaseIcon"),j=m("BaseButton"),A=m("BaseSettingCard");return C(),W("form",{class:"relative",onSubmit:se(N,["prevent"])},[r(A,{title:l.$t("settings.account_settings.account_settings"),description:l.$t("settings.account_settings.section_description")},{default:d(()=>[r(D,null,{default:d(()=>[r(c,{label:l.$tc("settings.account_settings.profile_picture")},{default:d(()=>[r($,{modelValue:e(f),"onUpdate:modelValue":a[0]||(a[0]=n=>X(f)?f.value=n:f=n),avatar:!0,accept:"image/*",onChange:q,onRemove:k},null,8,["modelValue"])]),_:1},8,["label"]),ie,r(c,{label:l.$tc("settings.account_settings.name"),error:e(o).name.$error&&e(o).name.$errors[0].$message,required:""},{default:d(()=>[r(_,{modelValue:e(t).name,"onUpdate:modelValue":a[1]||(a[1]=n=>e(t).name=n),invalid:e(o).name.$error,onInput:a[2]||(a[2]=n=>e(o).name.$touch())},null,8,["modelValue","invalid"])]),_:1},8,["label","error"]),r(c,{label:l.$tc("settings.account_settings.email"),error:e(o).email.$error&&e(o).email.$errors[0].$message,required:""},{default:d(()=>[r(_,{modelValue:e(t).email,"onUpdate:modelValue":a[3]||(a[3]=n=>e(t).email=n),invalid:e(o).email.$error,onInput:a[4]||(a[4]=n=>e(o).email.$touch())},null,8,["modelValue","invalid"])]),_:1},8,["label","error"]),r(c,{error:e(o).password.$error&&e(o).password.$errors[0].$message,label:l.$tc("settings.account_settings.password")},{default:d(()=>[r(_,{modelValue:e(t).password,"onUpdate:modelValue":a[5]||(a[5]=n=>e(t).password=n),type:"password",onInput:a[6]||(a[6]=n=>e(o).password.$touch())},null,8,["modelValue"])]),_:1},8,["error","label"]),r(c,{label:l.$tc("settings.account_settings.confirm_password"),error:e(o).confirm_password.$error&&e(o).confirm_password.$errors[0].$message},{default:d(()=>[r(_,{modelValue:e(t).confirm_password,"onUpdate:modelValue":a[7]||(a[7]=n=>e(t).confirm_password=n),type:"password",onInput:a[8]||(a[8]=n=>e(o).confirm_password.$touch())},null,8,["modelValue"])]),_:1},8,["label","error"]),r(c,{label:l.$tc("settings.language")},{default:d(()=>[r(G,{modelValue:e(t).language,"onUpdate:modelValue":a[9]||(a[9]=n=>e(t).language=n),options:e(i).config.languages,label:"name","value-prop":"code","track-by":"code","open-direction":"top"},null,8,["modelValue","options"])]),_:1},8,["label"])]),_:1}),r(j,{loading:e(p),disabled:e(p),class:"mt-6"},{left:d(n=>[e(p)?x("",!0):(C(),Y(h,{key:0,name:"SaveIcon",class:Z(n.class)},null,8,["class"]))]),default:d(()=>[ee(" "+ae(l.$tc("settings.company_info.save")),1)]),_:1},8,["loading","disabled"])]),_:1},8,["title","description"])],40,le)}}};export{pe as default};

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