Compare commits
242 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
9d6cfba94f | |
|
|
cabef574d7 | |
|
|
b81befc343 | |
|
|
729834a0ef | |
|
|
a776c7b5a9 | |
|
|
ce25e05b64 | |
|
|
31df25305d | |
|
|
09b4e1641e | |
|
|
3ad75c4d6b | |
|
|
5772eca363 | |
|
|
c82b4883a7 | |
|
|
aba76b12c8 | |
|
|
311054bad7 | |
|
|
9ddab69242 | |
|
|
38677e65c2 | |
|
|
91f1956dc9 | |
|
|
1789662440 | |
|
|
6f50a4e685 | |
|
|
e6c67ef504 | |
|
|
f9c57bd402 | |
|
|
3c033af0d6 | |
|
|
3a94319805 | |
|
|
09574e0335 | |
|
|
804cb8e568 | |
|
|
6df965c930 | |
|
|
2c4d52b15e | |
|
|
2c12e7524c | |
|
|
d85c1b51bb | |
|
|
952626f52a | |
|
|
939d2b726a | |
|
|
8359383ba9 | |
|
|
9aed0937ab | |
|
|
afb52a52f8 | |
|
|
9da40446e6 | |
|
|
a44d9d156b | |
|
|
2b098e5e51 | |
|
|
072e070d09 | |
|
|
752e670a06 | |
|
|
24f0dbb961 | |
|
|
b5d096c615 | |
|
|
12ec20f8ac | |
|
|
e44fd37bad | |
|
|
4829c7728d | |
|
|
fb9f7eaaa3 | |
|
|
7c130a4ece | |
|
|
7f1fc69330 | |
|
|
695f8cf6ae | |
|
|
4a0419774b | |
|
|
c5d61d8f4e | |
|
|
d4f2b5b4da | |
|
|
6c67e99a1b | |
|
|
ef56e7e91e | |
|
|
a7917f42b3 | |
|
|
24fc846ce3 | |
|
|
11459b5bb3 | |
|
|
1f8d3f3cbf | |
|
|
421cc1091a | |
|
|
aa393c58fc | |
|
|
b12cebd08a | |
|
|
4d59bdf754 | |
|
|
df83efb84e | |
|
|
e523979ad6 | |
|
|
d6c9083b80 | |
|
|
c78fb4ce34 | |
|
|
e8556ee306 | |
|
|
86b987eb9b | |
|
|
6cfd9410d6 | |
|
|
0c7fa0cf0d | |
|
|
7e5a64262c | |
|
|
c6716e7763 | |
|
|
b56a3bda56 | |
|
|
89b600dec9 | |
|
|
b5d8499ac7 | |
|
|
1f68dd4b4d | |
|
|
1a0bf56c63 | |
|
|
c7b70dd224 | |
|
|
e76c84e46c | |
|
|
dee95e412e | |
|
|
ac58f4d0ae | |
|
|
c1dd91238d | |
|
|
416f879353 | |
|
|
f720e22117 | |
|
|
8fb34dc8d2 | |
|
|
dc13c8fabb | |
|
|
1bd3c5a153 | |
|
|
70a5e5acdd | |
|
|
47dd774286 | |
|
|
355cee0cf2 | |
|
|
774f64a051 | |
|
|
48ae7a5a8a | |
|
|
6c485567ca | |
|
|
502c7b0d31 | |
|
|
51d2097cd6 | |
|
|
b7492d3ab0 | |
|
|
7317d1b512 | |
|
|
f9b3d9ebfa | |
|
|
2beba59bb1 | |
|
|
c1f73ae6dc | |
|
|
bbf83478ea | |
|
|
795baf9585 | |
|
|
65705deafb | |
|
|
a4f48d2342 | |
|
|
7b1eaf30fc | |
|
|
6dd89a1b0f | |
|
|
0d8c1eece4 | |
|
|
3a151bdc7f | |
|
|
71b5ebcd32 | |
|
|
9f66fed1ec | |
|
|
c059ad261c | |
|
|
41d0e4b3cc | |
|
|
139be97e11 | |
|
|
355c34920c | |
|
|
65407e04b2 | |
|
|
42ddac24d5 | |
|
|
180c8bc100 | |
|
|
ffe2442d18 | |
|
|
dfb165f379 | |
|
|
c1eb27034f | |
|
|
cd446685a7 | |
|
|
037cf7421c | |
|
|
21cc0b7c62 | |
|
|
8850f8ef84 | |
|
|
d252b8bfd3 | |
|
|
10a2198141 | |
|
|
1e3b2ffc91 | |
|
|
155e02d0fd | |
|
|
8dc32979ae | |
|
|
56ac86ad3c | |
|
|
79f7911e1a | |
|
|
bd0fe0e0be | |
|
|
62315c98b5 | |
|
|
45899f1992 | |
|
|
6f706fce1b | |
|
|
b07c5dc62d | |
|
|
dd4d09305f | |
|
|
635fee12ff | |
|
|
6f43604832 | |
|
|
0fceb96078 | |
|
|
4c25459dd7 | |
|
|
eaa1476c26 | |
|
|
b614a22b3e | |
|
|
84ee8b123a | |
|
|
09e32ad262 | |
|
|
07579e61bb | |
|
|
b9949d1140 | |
|
|
76b9e18c26 | |
|
|
6d22c72f69 | |
|
|
729488713a | |
|
|
9e8be67be8 | |
|
|
febb4ede9c | |
|
|
723dfa7f04 | |
|
|
5ab50131d3 | |
|
|
85352bc768 | |
|
|
6ccf4a7277 | |
|
|
16b8147733 | |
|
|
b9d2c61d2f | |
|
|
89ae0f4fdb | |
|
|
9f30bc7174 | |
|
|
19adb56f52 | |
|
|
2d29a83e1e | |
|
|
a1062f9c74 | |
|
|
5d0f76dfd1 | |
|
|
776e8c8599 | |
|
|
543d180060 | |
|
|
de4a32ec00 | |
|
|
e0ed546c56 | |
|
|
cab78dde3c | |
|
|
df374afbb5 | |
|
|
aede38ff1b | |
|
|
bf024c5ae2 | |
|
|
cfa2a64e1b | |
|
|
c466a1d153 | |
|
|
49fe71d671 | |
|
|
b93ef9bf13 | |
|
|
023db3e020 | |
|
|
5d1ab331d4 | |
|
|
3a3f3ce86f | |
|
|
49937eb721 | |
|
|
c6e41da70b | |
|
|
59ede8853d | |
|
|
9b54043cf1 | |
|
|
e06a26d62e | |
|
|
763bc57eac | |
|
|
cfee94711c | |
|
|
f6897049f6 | |
|
|
15f4da3d3f | |
|
|
1ba84a040a | |
|
|
7ba3313add | |
|
|
ec3659ef5e | |
|
|
811076afcf | |
|
|
805dbb8a71 | |
|
|
a8adeff326 | |
|
|
003453e9e0 | |
|
|
00989224b9 | |
|
|
e8920c7f9f | |
|
|
5b988adb91 | |
|
|
32038a1736 | |
|
|
8138f71727 | |
|
|
bd4d65dca7 | |
|
|
7503a7c291 | |
|
|
1b6a3b7366 | |
|
|
19042679c9 | |
|
|
d7dba36e4d | |
|
|
de7a8db650 | |
|
|
1f9ad0e648 | |
|
|
40ac0b05d7 | |
|
|
16d7e4c772 | |
|
|
c7a16c46a3 | |
|
|
559fcd261c | |
|
|
8ca80ba745 | |
|
|
fa83ee7bc3 | |
|
|
6e3b1e8055 | |
|
|
4e70a6783a | |
|
|
45129061db | |
|
|
608fe4fd92 | |
|
|
cdd09a3af5 | |
|
|
6595d23f0e | |
|
|
685c4be90c | |
|
|
85f32a228b | |
|
|
a626e90825 | |
|
|
0ed1ba4258 | |
|
|
528d67b7ef | |
|
|
fc8502da5e | |
|
|
f3aa88f8e0 | |
|
|
69209be54d | |
|
|
4733247c21 | |
|
|
82b17da9f7 | |
|
|
5609196d77 | |
|
|
2d212119fc | |
|
|
4d5ea3857a | |
|
|
1a5d5bcfed | |
|
|
9abd5f4ab4 | |
|
|
af67d9462c | |
|
|
1a6696b520 | |
|
|
21e99867c6 | |
|
|
aa5bb91e61 | |
|
|
dd60498804 | |
|
|
74346122ea | |
|
|
a079dc9157 | |
|
|
9e2af48132 | |
|
|
873c793311 | |
|
|
dffc1370f3 |
|
|
@ -20,10 +20,10 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Cache Composer
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.composer/cache/files
|
||||
key: php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ jobs:
|
|||
steps:
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Sync with Crowdin
|
||||
uses: crowdin/github-action@master
|
||||
|
|
|
|||
|
|
@ -24,6 +24,10 @@
|
|||
</IfModule>
|
||||
</FilesMatch>
|
||||
|
||||
# Prevent Direct Access to Protected Files (OpenLiteSpeed syntax)
|
||||
RewriteCond %{REQUEST_URI} (^|/)(\.env|\.log|artisan)$ [NC]
|
||||
RewriteRule .* - [F,L]
|
||||
|
||||
# Prevent Direct Access To Protected Folders
|
||||
RewriteRule ^(app|bootstrap|config|database|overrides|resources|routes|storage|tests)/(.*) / [L,R=301]
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ Online accounting software designed for small businesses and freelancers. Akaunt
|
|||
## Requirements
|
||||
|
||||
* PHP 8.1 or higher
|
||||
* Database (e.g.: MySQL, PostgreSQL, SQLite)
|
||||
* Database (e.g.: MariaDB, MySQL, PostgreSQL, SQLite)
|
||||
* Web Server (eg: Apache, Nginx, IIS)
|
||||
* [Other libraries](https://akaunting.com/hc/docs/on-premise/requirements/)
|
||||
|
||||
|
|
|
|||
|
|
@ -304,7 +304,7 @@ abstract class BulkAction
|
|||
|
||||
$batch[] = new CreateMediableForDownload(user(), $file_name, $translation);
|
||||
|
||||
Bus::chain($batch)->onQueue('default')->dispatch();
|
||||
Bus::chain($batch)->onQueue('jobs')->dispatch();
|
||||
|
||||
$message = trans('messages.success.download_queued', ['type' => $translation]);
|
||||
|
||||
|
|
@ -314,7 +314,7 @@ abstract class BulkAction
|
|||
} else {
|
||||
$this->dispatch(new CreateZipForDownload($selected, $class, $file_name));
|
||||
|
||||
$folder_path = 'app/temp/' . company_id() . '/bulk_actions/';
|
||||
$folder_path = 'app' . DIRECTORY_SEPARATOR . 'temp' . DIRECTORY_SEPARATOR . company_id() . DIRECTORY_SEPARATOR . 'bulk_actions' . DIRECTORY_SEPARATOR;
|
||||
|
||||
return response()->download(get_storage_path($folder_path . $file_name . '.zip'))->deleteFileAfterSend(true);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -109,11 +109,19 @@ abstract class Export implements FromCollection, HasLocalePreference, ShouldAuto
|
|||
|
||||
public function preferredLocale()
|
||||
{
|
||||
if (! $this->user) {
|
||||
return setting('default.locale');
|
||||
}
|
||||
|
||||
return $this->user->locale;
|
||||
}
|
||||
|
||||
public function failed(\Throwable $exception): void
|
||||
{
|
||||
if (! $this->user) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->user->notify(new ExportFailed($exception->getMessage()));
|
||||
}
|
||||
|
||||
|
|
@ -182,7 +190,7 @@ abstract class Export implements FromCollection, HasLocalePreference, ShouldAuto
|
|||
for ($i = 3; $i <= $this->row_count; $i++) {
|
||||
$event->sheet->getCell("{$drop_column}{$i}")->setDataValidation(clone $validation);
|
||||
}
|
||||
|
||||
|
||||
// set columns to autosize
|
||||
for ($i = 1; $i <= $this->column_count; $i++) {
|
||||
$column = Coordinate::stringFromColumnIndex($i);
|
||||
|
|
@ -215,14 +223,14 @@ abstract class Export implements FromCollection, HasLocalePreference, ShouldAuto
|
|||
|
||||
if (strpos($r, 'date_format') !== false) {
|
||||
$prompt = $prompt . trans('validation.date_format', [
|
||||
'attribute' => $value,
|
||||
'attribute' => $value,
|
||||
'format' => str_replace('date_format:', '', $r)
|
||||
]) . ' ';
|
||||
}
|
||||
|
||||
if (strpos($r, 'required_without') !== false) {
|
||||
$prompt = $prompt . trans('validation.required_without', [
|
||||
'attribute' => $value,
|
||||
'attribute' => $value,
|
||||
'values' => str_replace('required_without:', '', $r)
|
||||
]) . ' ';
|
||||
}
|
||||
|
|
@ -238,7 +246,7 @@ abstract class Export implements FromCollection, HasLocalePreference, ShouldAuto
|
|||
$validation->setShowInputMessage(true);
|
||||
$validation->setPromptTitle(trans('general.validation_warning'));
|
||||
$validation->setPrompt($prompt ?? null);
|
||||
|
||||
|
||||
for ($i = 3; $i <= $this->row_count; $i++) {
|
||||
$event->sheet->getCell("{$drop_column}{$i}")->setDataValidation(clone $validation);
|
||||
}
|
||||
|
|
@ -257,7 +265,7 @@ abstract class Export implements FromCollection, HasLocalePreference, ShouldAuto
|
|||
|
||||
$model::select($select)->each(function ($row) use (&$selects, &$totalLength, $limit, $select) {
|
||||
$nameLength = mb_strlen($row->$select);
|
||||
|
||||
|
||||
if ($totalLength + $nameLength <= $limit && $nameLength !== 0) {
|
||||
$selects[] = $row->$select;
|
||||
$totalLength += $nameLength;
|
||||
|
|
@ -277,7 +285,7 @@ abstract class Export implements FromCollection, HasLocalePreference, ShouldAuto
|
|||
|
||||
public function registerEvents(): array
|
||||
{
|
||||
return [
|
||||
return [
|
||||
AfterSheet::class => function(AfterSheet $event) {
|
||||
$this->afterSheet($event);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ use Illuminate\Pagination\Paginator;
|
|||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
|
||||
/**
|
||||
* @property string $type
|
||||
*/
|
||||
abstract class Controller extends BaseController
|
||||
{
|
||||
use AuthorizesRequests, Jobs, Permissions, Relationships, SearchString, ValidatesRequests;
|
||||
|
|
@ -114,6 +117,19 @@ abstract class Controller extends BaseController
|
|||
}
|
||||
}
|
||||
|
||||
if (! request()->has('list_records') && request()->has('search')) {
|
||||
$status = $this->getSearchStringValue('status');
|
||||
|
||||
if (empty($status)) {
|
||||
$tab_pins = setting('favorites.tab.' . user()->id, []);
|
||||
$tab_pins = ! empty($tab_pins) ? json_decode($tab_pins, true) : [];
|
||||
|
||||
if (! empty($tab_pins) && (($tab_pins[$this->type] ?? null) === 'all')) {
|
||||
request()->offsetSet('list_records', 'all');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (request()->get('list_records') == 'all') {
|
||||
return;
|
||||
}
|
||||
|
|
@ -152,5 +168,105 @@ abstract class Controller extends BaseController
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! request()->has('list_records') && request()->has('search')) {
|
||||
$type = $this->getSearchStringValue('type');
|
||||
|
||||
if (empty($type)) {
|
||||
$tab_pins = setting('favorites.tab.' . user()->id, []);
|
||||
$tab_pins = ! empty($tab_pins) ? json_decode($tab_pins, true) : [];
|
||||
|
||||
if (! empty($tab_pins) && (($tab_pins['transactions'] ?? null) === 'all')) {
|
||||
request()->offsetSet('list_records', 'all');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (request()->get('list_records') == 'all') {
|
||||
return;
|
||||
}
|
||||
|
||||
$type = $this->getSearchStringValue('type');
|
||||
|
||||
if (empty($type)) {
|
||||
$search = config('type.transaction.transactions.route.params.income.search');
|
||||
|
||||
request()->offsetSet('search', $search);
|
||||
request()->offsetSet('programmatic', '1');
|
||||
} else {
|
||||
$income = str_replace('type:', 'income', config('type.transaction.transactions.route.params.income.search'));
|
||||
$expense = str_replace('type:', 'expense', config('type.transaction.transactions.route.params.expense.search'));
|
||||
|
||||
if (($type == $income) || ($type == $expense)) {
|
||||
return;
|
||||
}
|
||||
|
||||
request()->offsetSet('list_records', 'all');
|
||||
}
|
||||
}
|
||||
|
||||
public function setActiveTabForCategories(): void
|
||||
{
|
||||
if (! request()->has('list_records') && ! request()->has('search')) {
|
||||
$tab_pins = setting('favorites.tab.' . user()->id, []);
|
||||
$tab_pins = ! empty($tab_pins) ? json_decode($tab_pins, true) : [];
|
||||
|
||||
if (! empty($tab_pins) && ! empty($tab_pins['categories'])) {
|
||||
$tab = $tab_pins['categories'];
|
||||
|
||||
if (! empty($tab)) {
|
||||
|
||||
request()->offsetSet('list_records', $tab);
|
||||
request()->offsetSet('programmatic', '1');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! request()->has('list_records') && request()->has('search')) {
|
||||
$type = $this->getSearchStringValue('type');
|
||||
|
||||
if (empty($type)) {
|
||||
$tab_pins = setting('favorites.tab.' . user()->id, []);
|
||||
$tab_pins = ! empty($tab_pins) ? json_decode($tab_pins, true) : [];
|
||||
|
||||
if (! empty($tab_pins) && (($tab_pins['categories'] ?? null) === 'all')) {
|
||||
request()->offsetSet('list_records', 'all');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (request()->get('list_records') == 'all') {
|
||||
return;
|
||||
}
|
||||
|
||||
$types = $this->getSearchStringValue('type');
|
||||
|
||||
if (!empty($types)) {
|
||||
$types = is_string($types) ? explode(',', $types) : $types;
|
||||
|
||||
$tab = config('type.category.' . $types[0] . '.group') ? config('type.category.' . $types[0] . '.group') : 'all';
|
||||
|
||||
if (!empty($types) && count($types) > 0) {
|
||||
request()->offsetSet('list_records', $tab);
|
||||
|
||||
$currentSearch = request('search', '');
|
||||
|
||||
$searchParts = array_filter(explode(' ', $currentSearch), function($part) {
|
||||
return !empty(trim($part)) && !str_starts_with(trim($part), 'type:');
|
||||
});
|
||||
|
||||
$searchParts[] = 'type:' . implode(',', $types);
|
||||
|
||||
request()->offsetSet('search', implode(' ', $searchParts));
|
||||
request()->offsetSet('programmatic', '1');
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($tab)) {
|
||||
request()->offsetSet('list_records', 'all');
|
||||
request()->offsetSet('programmatic', '1');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,8 +70,11 @@ abstract class Import implements HasLocalePreference, ShouldQueue, SkipsEmptyRow
|
|||
}
|
||||
|
||||
try {
|
||||
$row[$date_field] = Date::parse(ExcelDate::excelToDateTimeObject($row[$date_field]))
|
||||
->format('Y-m-d H:i:s');
|
||||
$row[$date_field] = is_numeric($row[$date_field])
|
||||
? Date::parse(ExcelDate::excelToDateTimeObject($row[$date_field]))
|
||||
->format('Y-m-d H:i:s')
|
||||
: Date::parse($row[$date_field])
|
||||
->format('Y-m-d H:i:s');
|
||||
} catch (InvalidFormatException | \Exception $e) {
|
||||
Log::info($e->getMessage());
|
||||
}
|
||||
|
|
@ -119,7 +122,18 @@ abstract class Import implements HasLocalePreference, ShouldQueue, SkipsEmptyRow
|
|||
} catch (ValidationException $e) {
|
||||
foreach ($e->validator->failed() as $attribute => $value) {
|
||||
foreach ($value as $rule => $params) {
|
||||
$validator->addFailure($row . '.' . $attribute, $rule, $params);
|
||||
if ($rule === 'In' && !empty($params)) {
|
||||
$actual_value = $data[$attribute] ?? 'null';
|
||||
$expected_values = implode(', ', $params);
|
||||
$custom_message = trans('validation.in_detailed', [
|
||||
'attribute' => $attribute,
|
||||
'value' => $actual_value,
|
||||
'values' => $expected_values
|
||||
]);
|
||||
$validator->errors()->add($row . '.' . $attribute, $custom_message);
|
||||
} else {
|
||||
$validator->addFailure($row . '.' . $attribute, $rule, $params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -43,6 +43,10 @@ abstract class Job
|
|||
return;
|
||||
}
|
||||
|
||||
if (empty($arguments[0])) {
|
||||
$arguments[0] = [];
|
||||
}
|
||||
|
||||
$request = $this->getRequestInstance($arguments[0]);
|
||||
if ($request instanceof Request) {
|
||||
$this->request = $request;
|
||||
|
|
@ -67,6 +71,10 @@ abstract class Job
|
|||
$this->model = $arguments[0];
|
||||
}
|
||||
|
||||
if (empty($arguments[1])) {
|
||||
$arguments[1] = [];
|
||||
}
|
||||
|
||||
$request = $this->getRequestInstance($arguments[1]);
|
||||
if ($request instanceof Request) {
|
||||
$this->request = $request;
|
||||
|
|
@ -91,7 +99,7 @@ abstract class Job
|
|||
|
||||
public function getRequestInstance($request)
|
||||
{
|
||||
if (!is_array($request)) {
|
||||
if (! is_array($request)) {
|
||||
return $request;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -47,6 +47,10 @@ abstract class JobShouldQueue implements ShouldQueue
|
|||
return;
|
||||
}
|
||||
|
||||
if (empty($arguments[0])) {
|
||||
$arguments[0] = [];
|
||||
}
|
||||
|
||||
$request = $this->getRequestInstance($arguments[0]);
|
||||
if ($request instanceof QueueCollection) {
|
||||
$this->request = $request;
|
||||
|
|
@ -71,6 +75,10 @@ abstract class JobShouldQueue implements ShouldQueue
|
|||
$this->model = $arguments[0];
|
||||
}
|
||||
|
||||
if (empty($arguments[1])) {
|
||||
$arguments[1] = [];
|
||||
}
|
||||
|
||||
$request = $this->getRequestInstance($arguments[1]);
|
||||
if ($request instanceof QueueCollection) {
|
||||
$this->request = $request;
|
||||
|
|
|
|||
|
|
@ -5,13 +5,14 @@ namespace App\Abstracts\Listeners;
|
|||
use App\Models\Banking\Account;
|
||||
use App\Models\Common\Contact;
|
||||
use App\Models\Setting\Category;
|
||||
use App\Traits\Categories;
|
||||
use App\Traits\Contacts;
|
||||
use App\Traits\DateTime;
|
||||
use App\Traits\SearchString;
|
||||
|
||||
abstract class Report
|
||||
{
|
||||
use Contacts, DateTime, SearchString;
|
||||
use Categories, Contacts, DateTime, SearchString;
|
||||
|
||||
protected $classes = [];
|
||||
|
||||
|
|
@ -90,22 +91,24 @@ abstract class Report
|
|||
|
||||
public function getItemCategories($limit = false)
|
||||
{
|
||||
return $this->getCategories('item', $limit);
|
||||
return $this->getCategories($this->getItemCategoryTypes(), $limit);
|
||||
}
|
||||
|
||||
public function getIncomeCategories($limit = false)
|
||||
{
|
||||
return $this->getCategories('income', $limit);
|
||||
return $this->getCategories($this->getIncomeCategoryTypes(), $limit);
|
||||
}
|
||||
|
||||
public function getExpenseCategories($limit = false)
|
||||
{
|
||||
return $this->getCategories('expense', $limit);
|
||||
return $this->getCategories($this->getExpenseCategoryTypes(), $limit);
|
||||
}
|
||||
|
||||
public function getIncomeExpenseCategories($limit = false)
|
||||
{
|
||||
return $this->getCategories(['income', 'expense'], $limit);
|
||||
$types = array_merge($this->getIncomeCategoryTypes(), $this->getExpenseCategoryTypes());
|
||||
|
||||
return $this->getCategories($types, $limit);
|
||||
}
|
||||
|
||||
public function getCategories($types, $limit = false)
|
||||
|
|
@ -158,11 +161,42 @@ abstract class Report
|
|||
];
|
||||
}
|
||||
|
||||
public function getDiscount()
|
||||
{
|
||||
return [
|
||||
'item' => trans('settings.localisation.discount_location.item'),
|
||||
'total' => trans('settings.localisation.discount_location.total'),
|
||||
'both' => trans('settings.localisation.discount_location.both'),
|
||||
];
|
||||
}
|
||||
|
||||
public function applyDateFilter($event)
|
||||
{
|
||||
$event->model->dateFilter($event->args['date_field']);
|
||||
}
|
||||
|
||||
public function applyDiscountFilter($event)
|
||||
{
|
||||
$input = request('search', '');
|
||||
$discount = $this->getSearchStringValue('discount', 'both', $input);
|
||||
|
||||
switch ($discount) {
|
||||
case 'item':
|
||||
$discount_types = ['item_discount'];
|
||||
break;
|
||||
case 'total':
|
||||
$discount_types = ['discount'];
|
||||
break;
|
||||
default:
|
||||
$discount_types = ['item_discount', 'discount'];
|
||||
break;
|
||||
}
|
||||
|
||||
$event->model->whereHas('totals', function ($query) use ($discount_types) {
|
||||
$query->whereIn('code', $discount_types);
|
||||
});
|
||||
}
|
||||
|
||||
public function applySearchStringFilter($event)
|
||||
{
|
||||
$input = request('search', '');
|
||||
|
|
|
|||
|
|
@ -246,6 +246,15 @@ abstract class Model extends Eloquent implements Ownable
|
|||
return $query->where($this->qualifyColumn('type'), 'not like', '%-recurring');
|
||||
}
|
||||
|
||||
public function scopeModuleEnabled(Builder $query, string $module): Builder
|
||||
{
|
||||
return $query->allCompanies()->whereHas('company', fn (Builder $q1) =>
|
||||
$q1->enabled()->whereHas('modules', fn (Builder $q2) =>
|
||||
$q2->allCompanies()->alias($module)->enabled(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function ownerKey($owner)
|
||||
{
|
||||
if ($this->isNotOwnable()) {
|
||||
|
|
|
|||
|
|
@ -40,6 +40,8 @@ abstract class Report
|
|||
|
||||
public $has_money = true;
|
||||
|
||||
public $group;
|
||||
|
||||
public $groups = [];
|
||||
|
||||
public $year;
|
||||
|
|
@ -251,17 +253,52 @@ abstract class Report
|
|||
|
||||
public function show()
|
||||
{
|
||||
return view($this->views['show'])->with('class', $this);
|
||||
return view($this->views['show'], ['print' => false])->with('class', $this);
|
||||
}
|
||||
|
||||
public function print()
|
||||
{
|
||||
return view($this->views['print'])->with('class', $this);
|
||||
return view($this->views['print'], ['print' => true])->with('class', $this);
|
||||
}
|
||||
|
||||
public function array(): array
|
||||
{
|
||||
$data = [];
|
||||
|
||||
$group = Str::plural($this->group ?? $this->getSetting('group'));
|
||||
|
||||
foreach ($this->tables as $table_key => $table_name) {
|
||||
if (! isset($this->row_values[$table_key])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($this->row_values[$table_key] as $key => $values) {
|
||||
if (empty($this->row_names[$table_key][$key])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->has_money) {
|
||||
$values = array_map(fn($value) => money($value)->format(), $values);
|
||||
}
|
||||
|
||||
$data[$table_key][$group][$this->row_names[$table_key][$key]] = $values;
|
||||
}
|
||||
|
||||
$footer_totals = $this->footer_totals[$table_key];
|
||||
|
||||
if ($this->has_money) {
|
||||
$footer_totals = array_map(fn($value) => money($value)->format(), $footer_totals);
|
||||
}
|
||||
|
||||
$data[$table_key]['totals'] = $footer_totals;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function pdf()
|
||||
{
|
||||
$view = view($this->views['print'])->with('class', $this)->render();
|
||||
$view = view($this->views['print'], ['print' => true])->with('class', $this)->render();
|
||||
|
||||
$html = mb_convert_encoding($view, 'HTML-ENTITIES', 'UTF-8');
|
||||
|
||||
|
|
@ -509,11 +546,19 @@ abstract class Report
|
|||
|
||||
public function divArithmeticAmount(&$current, $amount)
|
||||
{
|
||||
if ($amount == 0) {
|
||||
throw new \InvalidArgumentException('Division by zero is not allowed');
|
||||
}
|
||||
|
||||
$current = $current / $amount;
|
||||
}
|
||||
|
||||
public function modArithmeticAmount(&$current, $amount)
|
||||
{
|
||||
if ($amount == 0) {
|
||||
throw new \InvalidArgumentException('Modulo by zero is not allowed');
|
||||
}
|
||||
|
||||
$current = $current % $amount;
|
||||
}
|
||||
|
||||
|
|
@ -551,7 +596,6 @@ abstract class Report
|
|||
$url .= $parameters;
|
||||
}
|
||||
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
|
|
@ -570,6 +614,11 @@ abstract class Report
|
|||
return $this->getSearchStringValue('period', $this->getSetting('period'));
|
||||
}
|
||||
|
||||
public function getDiscount()
|
||||
{
|
||||
return $this->getSearchStringValue('discount');
|
||||
}
|
||||
|
||||
public function getFields()
|
||||
{
|
||||
return [
|
||||
|
|
|
|||
|
|
@ -30,6 +30,10 @@ abstract class Index extends Component
|
|||
public $page;
|
||||
|
||||
public $permissionCreate;
|
||||
|
||||
public $permissionUpdate;
|
||||
|
||||
public $permissionDelete;
|
||||
/* -- Main End -- */
|
||||
|
||||
/* -- Buttons Start -- */
|
||||
|
|
|
|||
|
|
@ -483,6 +483,7 @@ abstract class Index extends Component
|
|||
'text' => trans('general.title.new', ['type' => trans_choice($this->textPage ?? 'general.' . $prefix, 1)]),
|
||||
'description' => trans('general.empty.actions.new', ['type' => strtolower(trans_choice($this->textPage ?? 'general.' . $prefix, 1))]),
|
||||
'active_badge' => true,
|
||||
'stack' => 'create_button',
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -494,6 +495,7 @@ abstract class Index extends Component
|
|||
'url' => route($this->importRoute, $this->importRouteParameters),
|
||||
'text' => trans('import.title', ['type' => trans_choice($this->textPage ?? 'general.' . $prefix, 2)]),
|
||||
'description' => trans('general.empty.actions.import', ['type' => strtolower(trans_choice($this->textPage ?? 'general.' . $prefix, 2))]),
|
||||
'stack' => 'import_button',
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -93,6 +93,12 @@ abstract class Form extends Component
|
|||
/** @var string */
|
||||
public $inputGroupClass = '';
|
||||
|
||||
/** @var string */
|
||||
public $icon = '';
|
||||
|
||||
/** @var string */
|
||||
public $trailing = '';
|
||||
|
||||
/** @var array */
|
||||
public $custom_attributes = [];
|
||||
|
||||
|
|
@ -112,7 +118,7 @@ abstract class Form extends Component
|
|||
$options = [], $option = [], string $optionKey = 'id', string $optionValue = 'name', $fullOptions = [], $checked = null, $checkedKey = null, $selected = null, $selectedKey = null, $rows = '3',
|
||||
$remote = false, $multiple = false, $addNew = false, $group = false,
|
||||
bool $searchable = false, bool $disabled = false, bool $readonly = false, bool $required = true, bool $notRequired = false,
|
||||
string $formGroupClass = '', string $inputGroupClass = '',
|
||||
string $formGroupClass = '', string $inputGroupClass = '', $icon = '', $trailing = '',
|
||||
$dynamicAttributes = '',
|
||||
bool $hideCurrency = false
|
||||
) {
|
||||
|
|
@ -146,6 +152,9 @@ abstract class Form extends Component
|
|||
$this->formGroupClass = $this->getFromGroupClass($formGroupClass);
|
||||
$this->inputGroupClass = $this->getInputGroupClass($inputGroupClass);
|
||||
|
||||
$this->icon = $icon;
|
||||
$this->trailing = $trailing;
|
||||
|
||||
$this->custom_attributes = $this->getCustomAttributes();
|
||||
|
||||
$this->setDynamicAttributes($dynamicAttributes);
|
||||
|
|
|
|||
|
|
@ -304,6 +304,9 @@ abstract class Show extends Component
|
|||
/** @var bool */
|
||||
public $hideRecurringMessage;
|
||||
|
||||
/** @var bool */
|
||||
public $hideConnectMessage;
|
||||
|
||||
/** @var bool */
|
||||
public $hideCreated;
|
||||
|
||||
|
|
@ -335,7 +338,7 @@ abstract class Show extends Component
|
|||
string $routeDocumentShow = '', string $routeTransactionShow = '', string $textButtonAddNew = '',
|
||||
|
||||
bool $hideSchedule = false, bool $hideChildren = false, bool $hideConnect = false, bool $hideTransfer = false, bool $hideAttachment = false, $attachment = [],
|
||||
array $connectTranslations = [], string $textRecurringType = '', bool $hideRecurringMessage = false, bool $hideCreated = false
|
||||
array $connectTranslations = [], string $textRecurringType = '', bool $hideRecurringMessage = false, $hideConnectMessage = false, bool $hideCreated = false
|
||||
) {
|
||||
$this->type = $type;
|
||||
$this->transaction = $transaction;
|
||||
|
|
@ -472,6 +475,7 @@ abstract class Show extends Component
|
|||
|
||||
// Connect translations
|
||||
$this->connectTranslations = $this->getTranslationsForConnect($type);
|
||||
$this->hideConnectMessage = $hideConnectMessage;
|
||||
|
||||
$this->textRecurringType = $this->getTextRecurringType($type, $textRecurringType);
|
||||
$this->hideRecurringMessage = $hideRecurringMessage;
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ abstract class Widget
|
|||
'header' => 'components.widgets.header',
|
||||
];
|
||||
|
||||
public array $data = [];
|
||||
|
||||
public function __construct($model = null)
|
||||
{
|
||||
$this->model = $model;
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ class Categories extends BulkAction
|
|||
public function edit($request)
|
||||
{
|
||||
$selected = $this->getSelectedInput($request);
|
||||
|
||||
$types = $this->getCategoryTypes();
|
||||
|
||||
return $this->response('bulk-actions.settings.categories.edit', compact('selected', 'types'));
|
||||
|
|
|
|||
|
|
@ -218,12 +218,14 @@ class RecurringCheck extends Command
|
|||
$model->created_from = 'core::recurring';
|
||||
$model->save();
|
||||
|
||||
$this->updateRelationTypes($model, $template->cloneable_relations);
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
protected function getTransactionModel(Transaction $template, Date $schedule_date): Transaction
|
||||
{
|
||||
$template->cloneable_relations = [];
|
||||
$template->cloneable_relations = ['taxes'];
|
||||
|
||||
$model = $template->duplicate();
|
||||
|
||||
|
|
@ -233,6 +235,8 @@ class RecurringCheck extends Command
|
|||
$model->created_from = 'core::recurring';
|
||||
$model->save();
|
||||
|
||||
$this->updateRelationTypes($model, $template->cloneable_relations);
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
|
|
@ -266,4 +270,15 @@ class RecurringCheck extends Command
|
|||
{
|
||||
return Str::replace('-recurring', '', $recurring_type);
|
||||
}
|
||||
|
||||
public function updateRelationTypes($model, $relations)
|
||||
{
|
||||
foreach ($relations as $relation) {
|
||||
if (! method_exists($model, $relation)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$model->$relation()->update(['type' => $model->type]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -116,9 +116,9 @@ class Handler extends ExceptionHandler
|
|||
*/
|
||||
protected function unauthenticated($request, AuthenticationException $exception)
|
||||
{
|
||||
// Store the current url in the session
|
||||
if ($request->url() !== config('app.url')) {
|
||||
session(['url.intended' => $request->url()]);
|
||||
// Store the current url in the session (fullUrl includes query string)
|
||||
if ($request->fullUrl() !== config('app.url')) {
|
||||
session(['url.intended' => $request->fullUrl()]);
|
||||
}
|
||||
|
||||
return $request->expectsJson()
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ class Reports implements FromView, ShouldAutoSize, WithTitle
|
|||
|
||||
public function view(): View
|
||||
{
|
||||
return view($this->view, ['class' => $this->class]);
|
||||
return view($this->view, ['class' => $this->class, 'print' => true]);
|
||||
}
|
||||
|
||||
public function title(): string
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ class BillItemTaxes extends Export implements WithParentSheet
|
|||
|
||||
$model->bill_number = $document->document_number;
|
||||
$model->item_name = $model->item->name;
|
||||
$model->tax_name = $model->tax->name;
|
||||
$model->tax_rate = $model->tax->rate;
|
||||
|
||||
return parent::map($model);
|
||||
|
|
@ -33,6 +34,7 @@ class BillItemTaxes extends Export implements WithParentSheet
|
|||
return [
|
||||
'bill_number',
|
||||
'item_name',
|
||||
'tax_name',
|
||||
'tax_rate',
|
||||
'amount',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@ class BillItems extends Export implements WithParentSheet
|
|||
'item_description',
|
||||
'item_type',
|
||||
'quantity',
|
||||
'discount_type',
|
||||
'discount_rate',
|
||||
'price',
|
||||
'total',
|
||||
'tax',
|
||||
|
|
|
|||
|
|
@ -40,6 +40,8 @@ class Bills extends Export implements WithColumnFormatting
|
|||
'billed_at',
|
||||
'due_at',
|
||||
'amount',
|
||||
'discount_type',
|
||||
'discount_rate',
|
||||
'currency_code',
|
||||
'currency_rate',
|
||||
'category_name',
|
||||
|
|
@ -52,7 +54,11 @@ class Bills extends Export implements WithColumnFormatting
|
|||
'contact_state',
|
||||
'contact_zip_code',
|
||||
'contact_city',
|
||||
'title',
|
||||
'subheading',
|
||||
'notes',
|
||||
'template',
|
||||
'color',
|
||||
'parent_number'
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ class RecurringBillItemTaxes extends Export implements WithParentSheet
|
|||
|
||||
$model->bill_number = $document->document_number;
|
||||
$model->item_name = $model->item->name;
|
||||
$model->tax_name = $model->tax->name;
|
||||
$model->tax_rate = $model->tax->rate;
|
||||
|
||||
return parent::map($model);
|
||||
|
|
@ -33,6 +34,7 @@ class RecurringBillItemTaxes extends Export implements WithParentSheet
|
|||
return [
|
||||
'bill_number',
|
||||
'item_name',
|
||||
'tax_name',
|
||||
'tax_rate',
|
||||
'amount',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@ class RecurringBillItems extends Export implements WithParentSheet
|
|||
'item_description',
|
||||
'item_type',
|
||||
'quantity',
|
||||
'discount_type',
|
||||
'discount_rate',
|
||||
'price',
|
||||
'total',
|
||||
'tax',
|
||||
|
|
|
|||
|
|
@ -40,6 +40,8 @@ class RecurringBills extends Export implements WithColumnFormatting, WithParentS
|
|||
'billed_at',
|
||||
'due_at',
|
||||
'amount',
|
||||
'discount_type',
|
||||
'discount_rate',
|
||||
'currency_code',
|
||||
'currency_rate',
|
||||
'category_name',
|
||||
|
|
@ -52,7 +54,11 @@ class RecurringBills extends Export implements WithColumnFormatting, WithParentS
|
|||
'contact_state',
|
||||
'contact_zip_code',
|
||||
'contact_city',
|
||||
'title',
|
||||
'subheading',
|
||||
'notes',
|
||||
'template',
|
||||
'color',
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ class InvoiceItemTaxes extends Export implements WithParentSheet
|
|||
|
||||
$model->invoice_number = $document->document_number;
|
||||
$model->item_name = $model->item->name;
|
||||
$model->tax_name = $model->tax->name;
|
||||
$model->tax_rate = $model->tax->rate;
|
||||
|
||||
return parent::map($model);
|
||||
|
|
@ -33,6 +34,7 @@ class InvoiceItemTaxes extends Export implements WithParentSheet
|
|||
return [
|
||||
'invoice_number',
|
||||
'item_name',
|
||||
'tax_name',
|
||||
'tax_rate',
|
||||
'amount',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@ class InvoiceItems extends Export implements WithParentSheet
|
|||
'item_description',
|
||||
'item_type',
|
||||
'quantity',
|
||||
'discount_type',
|
||||
'discount_rate',
|
||||
'price',
|
||||
'total',
|
||||
'tax',
|
||||
|
|
|
|||
|
|
@ -40,6 +40,8 @@ class Invoices extends Export implements WithColumnFormatting
|
|||
'invoiced_at',
|
||||
'due_at',
|
||||
'amount',
|
||||
'discount_type',
|
||||
'discount_rate',
|
||||
'currency_code',
|
||||
'currency_rate',
|
||||
'category_name',
|
||||
|
|
@ -52,8 +54,12 @@ class Invoices extends Export implements WithColumnFormatting
|
|||
'contact_state',
|
||||
'contact_zip_code',
|
||||
'contact_city',
|
||||
'title',
|
||||
'subheading',
|
||||
'notes',
|
||||
'footer',
|
||||
'template',
|
||||
'color',
|
||||
'parent_number',
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ class RecurringInvoiceItemTaxes extends Export implements WithParentSheet
|
|||
|
||||
$model->invoice_number = $document->document_number;
|
||||
$model->item_name = $model->item->name;
|
||||
$model->tax_name = $model->tax->name;
|
||||
$model->tax_rate = $model->tax->rate;
|
||||
|
||||
return parent::map($model);
|
||||
|
|
@ -33,6 +34,7 @@ class RecurringInvoiceItemTaxes extends Export implements WithParentSheet
|
|||
return [
|
||||
'invoice_number',
|
||||
'item_name',
|
||||
'tax_name',
|
||||
'tax_rate',
|
||||
'amount',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@ class RecurringInvoiceItems extends Export implements WithParentSheet
|
|||
'item_description',
|
||||
'item_type',
|
||||
'quantity',
|
||||
'discount_type',
|
||||
'discount_rate',
|
||||
'price',
|
||||
'total',
|
||||
'tax',
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ class RecurringInvoices extends Export implements WithColumnFormatting, WithPare
|
|||
$model->invoiced_at = $model->issued_at;
|
||||
$model->contact_country = $country;
|
||||
|
||||
|
||||
return parent::map($model);
|
||||
}
|
||||
|
||||
|
|
@ -41,6 +40,8 @@ class RecurringInvoices extends Export implements WithColumnFormatting, WithPare
|
|||
'invoiced_at',
|
||||
'due_at',
|
||||
'amount',
|
||||
'discount_type',
|
||||
'discount_rate',
|
||||
'currency_code',
|
||||
'currency_rate',
|
||||
'category_name',
|
||||
|
|
@ -53,8 +54,12 @@ class RecurringInvoices extends Export implements WithColumnFormatting, WithPare
|
|||
'contact_state',
|
||||
'contact_zip_code',
|
||||
'contact_city',
|
||||
'title',
|
||||
'subheading',
|
||||
'notes',
|
||||
'footer',
|
||||
'template',
|
||||
'color',
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,9 +25,11 @@ class Categories extends Export
|
|||
public function fields(): array
|
||||
{
|
||||
return [
|
||||
'code',
|
||||
'name',
|
||||
'type',
|
||||
'color',
|
||||
'description',
|
||||
'parent_name',
|
||||
'enabled',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -43,6 +43,10 @@ class Transactions extends ApiController
|
|||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
if ($request->has('document_id')) {
|
||||
return $this->errorBadRequest(trans('transactions.messages.create_document_transaction_error'));
|
||||
}
|
||||
|
||||
$transaction = $this->dispatch(new CreateTransaction($request));
|
||||
|
||||
return $this->created(route('api.transactions.show', $transaction->id), new Resource($transaction));
|
||||
|
|
@ -57,6 +61,10 @@ class Transactions extends ApiController
|
|||
*/
|
||||
public function update(Transaction $transaction, Request $request)
|
||||
{
|
||||
if ($request->has('document_id')) {
|
||||
return $this->errorBadRequest(trans('transactions.messages.update_document_transaction_error'));
|
||||
}
|
||||
|
||||
$transaction = $this->dispatch(new UpdateTransaction($transaction, $request));
|
||||
|
||||
return new Resource($transaction->fresh());
|
||||
|
|
@ -70,6 +78,10 @@ class Transactions extends ApiController
|
|||
*/
|
||||
public function destroy(Transaction $transaction)
|
||||
{
|
||||
if ($transaction->document_id) {
|
||||
return $this->errorBadRequest(trans('transactions.messages.delete_document_transaction_error'));
|
||||
}
|
||||
|
||||
try {
|
||||
$this->dispatch(new DeleteTransaction($transaction));
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use App\Abstracts\Http\ApiController;
|
|||
use App\Http\Requests\Banking\Transaction as Request;
|
||||
use App\Http\Resources\Banking\Transaction as Resource;
|
||||
use App\Jobs\Banking\CreateBankingDocumentTransaction;
|
||||
use App\Jobs\Banking\UpdateBankingDocumentTransaction;
|
||||
use App\Jobs\Banking\DeleteTransaction;
|
||||
use App\Models\Banking\Transaction;
|
||||
use App\Models\Document\Document;
|
||||
|
|
@ -32,7 +33,7 @@ class DocumentTransactions extends ApiController
|
|||
*/
|
||||
public function index($document_id)
|
||||
{
|
||||
$transactions = Transaction::documentId($document_id)->get();
|
||||
$transactions = Transaction::with(['document', 'taxes'])->documentId($document_id)->get();
|
||||
|
||||
return Resource::collection($transactions);
|
||||
}
|
||||
|
|
@ -46,7 +47,7 @@ class DocumentTransactions extends ApiController
|
|||
*/
|
||||
public function show($document_id, $id)
|
||||
{
|
||||
$transaction = Transaction::documentId($document_id)->find($id);
|
||||
$transaction = Transaction::with(['document', 'taxes'])->documentId($document_id)->find($id);
|
||||
|
||||
if (! $transaction instanceof Transaction) {
|
||||
return $this->errorInternal('No query results for model [' . Transaction::class . '] ' . $id);
|
||||
|
|
@ -71,6 +72,25 @@ class DocumentTransactions extends ApiController
|
|||
return $this->created(route('api.documents.transactions.show', [$document_id, $transaction->id]), new Resource($transaction));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param $document_id
|
||||
* @param $id
|
||||
* @param $request
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function update($document_id, $id, Request $request)
|
||||
{
|
||||
$document = Document::find($document_id);
|
||||
|
||||
$transaction = Transaction::documentId($document_id)->find($id);
|
||||
|
||||
$transaction = $this->dispatch(new UpdateBankingDocumentTransaction($document, $transaction, $request));
|
||||
|
||||
return $this->created(route('api.documents.transactions.show', [$document_id, $transaction->id]), new Resource($transaction));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class Documents extends ApiController
|
|||
*/
|
||||
public function index()
|
||||
{
|
||||
$documents = Document::with('contact', 'histories', 'items', 'transactions')->collect(['issued_at'=> 'desc']);
|
||||
$documents = Document::with('contact', 'histories', 'items', 'item_taxes', 'totals', 'transactions')->collect(['issued_at'=> 'desc']);
|
||||
|
||||
return Resource::collection($documents);
|
||||
}
|
||||
|
|
@ -34,9 +34,33 @@ class Documents extends ApiController
|
|||
{
|
||||
// Check if we're querying by id or number
|
||||
if (is_numeric($id)) {
|
||||
$document = Document::find($id);
|
||||
$document = Document::with([
|
||||
'contact',
|
||||
'histories',
|
||||
'items',
|
||||
'items.taxes',
|
||||
'items.taxes.tax',
|
||||
'item_taxes',
|
||||
'totals',
|
||||
'transactions',
|
||||
'transactions.currency',
|
||||
'transactions.account',
|
||||
'transactions.category',
|
||||
])->find($id);
|
||||
} else {
|
||||
$document = Document::where('document_number', $id)->first();
|
||||
$document = Document::with([
|
||||
'contact',
|
||||
'histories',
|
||||
'items',
|
||||
'items.taxes',
|
||||
'items.taxes.tax',
|
||||
'item_taxes',
|
||||
'totals',
|
||||
'transactions',
|
||||
'transactions.currency',
|
||||
'transactions.account',
|
||||
'transactions.category',
|
||||
])->where('document_number', $id)->first();
|
||||
}
|
||||
|
||||
if (! $document instanceof Document) {
|
||||
|
|
|
|||
|
|
@ -13,9 +13,11 @@ use App\Models\Banking\Account;
|
|||
use App\Models\Banking\Transaction;
|
||||
use App\Models\Common\Recurring;
|
||||
use App\Models\Setting\Currency;
|
||||
use App\Models\Setting\Tax;
|
||||
use App\Traits\Currencies;
|
||||
use App\Traits\DateTime;
|
||||
use App\Traits\Transactions as TransactionsTrait;
|
||||
use App\Utilities\Date;
|
||||
|
||||
class RecurringTransactions extends Controller
|
||||
{
|
||||
|
|
@ -76,13 +78,16 @@ class RecurringTransactions extends Controller
|
|||
|
||||
$currency = Currency::where('code', $account_currency_code)->first();
|
||||
|
||||
$taxes = Tax::enabled()->orderBy('name')->get();
|
||||
|
||||
return view('banking.recurring_transactions.create', compact(
|
||||
'type',
|
||||
'real_type',
|
||||
'number',
|
||||
'contact_type',
|
||||
'account_currency_code',
|
||||
'currency'
|
||||
'currency',
|
||||
'taxes'
|
||||
));
|
||||
}
|
||||
|
||||
|
|
@ -95,7 +100,9 @@ class RecurringTransactions extends Controller
|
|||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$response = $this->ajaxDispatch(new CreateTransaction($request->merge(['paid_at' => $request->get('recurring_started_at')])));
|
||||
$request->merge(['paid_at' => $request->get('recurring_started_at')]);
|
||||
|
||||
$response = $this->ajaxDispatch(new CreateTransaction($request));
|
||||
|
||||
if ($response['success']) {
|
||||
$response['redirect'] = route('recurring-transactions.show', $response['data']->id);
|
||||
|
|
@ -173,6 +180,8 @@ class RecurringTransactions extends Controller
|
|||
|
||||
$currency = Currency::where('code', $recurring_transaction->currency_code)->first();
|
||||
|
||||
$taxes = Tax::enabled()->orderBy('name')->get();
|
||||
|
||||
$date_format = $this->getCompanyDateFormat();
|
||||
|
||||
return view('banking.recurring_transactions.edit', compact(
|
||||
|
|
@ -182,6 +191,7 @@ class RecurringTransactions extends Controller
|
|||
'contact_type',
|
||||
'recurring_transaction',
|
||||
'currency',
|
||||
'taxes',
|
||||
'date_format'
|
||||
));
|
||||
}
|
||||
|
|
@ -196,7 +206,9 @@ class RecurringTransactions extends Controller
|
|||
*/
|
||||
public function update(Transaction $recurring_transaction, Request $request)
|
||||
{
|
||||
$response = $this->ajaxDispatch(new UpdateTransaction($recurring_transaction, $request->merge(['paid_at' => $request->get('recurring_started_at')])));
|
||||
$request->merge(['paid_at' => $request->get('recurring_started_at')]);
|
||||
|
||||
$response = $this->ajaxDispatch(new UpdateTransaction($recurring_transaction, $request));
|
||||
|
||||
if ($response['success']) {
|
||||
$response['redirect'] = route('recurring-transactions.show', $recurring_transaction->id);
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ class Transactions extends Controller
|
|||
{
|
||||
$this->setActiveTabForTransactions();
|
||||
|
||||
$transactions = Transaction::with('account', 'category', 'contact')->collect(['paid_at'=> 'desc']);
|
||||
$transactions = Transaction::with('account', 'category', 'contact', 'taxes')->collect(['paid_at'=> 'desc']);
|
||||
|
||||
$total_transactions = Transaction::count();
|
||||
|
||||
|
|
@ -239,6 +239,19 @@ class Transactions extends Controller
|
|||
*/
|
||||
public function update(Transaction $transaction, Request $request)
|
||||
{
|
||||
if ($transaction->document_id) {
|
||||
$message = trans('transactions.messages.update_document_transaction');
|
||||
|
||||
flash($message)->error()->important();
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'error' => true,
|
||||
'message' => $message,
|
||||
'redirect' => route('transactions.edit', $transaction->id),
|
||||
]);
|
||||
}
|
||||
|
||||
$response = $this->ajaxDispatch(new UpdateTransaction($transaction, $request));
|
||||
|
||||
if ($response['success']) {
|
||||
|
|
@ -333,7 +346,9 @@ class Transactions extends Controller
|
|||
{
|
||||
event(new TransactionPrinting($transaction));
|
||||
|
||||
$view = view('banking.transactions.print_default', compact('transaction'));
|
||||
$real_type = $this->getRealTypeTransaction($transaction->type);
|
||||
|
||||
$view = view('banking.transactions.print_default', compact('transaction', 'real_type'));
|
||||
|
||||
return mb_convert_encoding($view, 'HTML-ENTITIES', 'UTF-8');
|
||||
}
|
||||
|
|
@ -351,7 +366,9 @@ class Transactions extends Controller
|
|||
|
||||
$currency_style = true;
|
||||
|
||||
$view = view('banking.transactions.print_default', compact('transaction', 'currency_style'))->render();
|
||||
$real_type = $this->getRealTypeTransaction($transaction->type);
|
||||
|
||||
$view = view('banking.transactions.print_default', compact('transaction', 'currency_style', 'real_type'))->render();
|
||||
$html = mb_convert_encoding($view, 'HTML-ENTITIES', 'UTF-8');
|
||||
|
||||
$pdf = app('dompdf.wrapper');
|
||||
|
|
@ -395,7 +412,7 @@ class Transactions extends Controller
|
|||
$translations = collect($this->getTranslationsForConnect($transaction->type));
|
||||
|
||||
$data = [
|
||||
'transaction' => $transaction->load(['account', 'category'])->toJson(),
|
||||
'transaction' => $transaction->load(['account', 'category', 'taxes'])->toJson(),
|
||||
'currency' => $transaction->currency->toJson(),
|
||||
'documents' => $documents,
|
||||
'translations' => $translations->toJson(),
|
||||
|
|
|
|||
|
|
@ -37,7 +37,8 @@ class Dashboards extends Controller
|
|||
*/
|
||||
public function index()
|
||||
{
|
||||
$dashboards = user()->dashboards()->collect();
|
||||
// Eager load users relationship to prevent N+1 queries in dashboard index view
|
||||
$dashboards = user()->dashboards()->with('users')->collect();
|
||||
|
||||
return $this->response('common.dashboards.index', compact('dashboards'));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -245,11 +245,12 @@ class Items extends Controller
|
|||
$currency_code = default_currency();
|
||||
}
|
||||
|
||||
$autocomplete = Item::autocomplete([
|
||||
$autocomplete = Item::with('taxes')->autocomplete([
|
||||
'name' => $query
|
||||
]);
|
||||
|
||||
$items = $autocomplete->get();
|
||||
// Eager load taxes and tax relationships to prevent N+1 queries
|
||||
$items = $autocomplete->with(['taxes.tax'])->get();
|
||||
|
||||
if ($items) {
|
||||
foreach ($items as $item) {
|
||||
|
|
@ -260,6 +261,7 @@ class Items extends Controller
|
|||
$inclusives = $compounds = [];
|
||||
|
||||
foreach($item->taxes as $item_tax) {
|
||||
// Tax relationship is now eager loaded, preventing N+1 query
|
||||
$tax = $item_tax->tax;
|
||||
|
||||
switch ($tax->type) {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ namespace App\Http\Controllers\Common;
|
|||
|
||||
use App\Abstracts\Http\Controller;
|
||||
use App\Http\Requests\Common\Report as Request;
|
||||
use App\Http\Requests\Common\ReportShow as ShowRequest;
|
||||
use App\Jobs\Common\CreateReport;
|
||||
use App\Jobs\Common\DeleteReport;
|
||||
use App\Jobs\Common\UpdateReport;
|
||||
|
|
@ -66,9 +67,10 @@ class Reports extends Controller
|
|||
* Show the form for viewing the specified resource.
|
||||
*
|
||||
* @param Report $report
|
||||
* @param ShowRequest $request
|
||||
* @return Response
|
||||
*/
|
||||
public function show(Report $report)
|
||||
public function show(Report $report, ShowRequest $request)
|
||||
{
|
||||
if (Utility::cannotShow($report->class)) {
|
||||
abort(403);
|
||||
|
|
|
|||
|
|
@ -6,10 +6,14 @@ use App\Abstracts\Http\Controller;
|
|||
use App\Http\Requests\Setting\Category as Request;
|
||||
use App\Jobs\Setting\CreateCategory;
|
||||
use App\Models\Setting\Category;
|
||||
use App\Traits\Categories as Helper;
|
||||
use App\Traits\Modules;
|
||||
use Illuminate\Http\Request as IRequest;
|
||||
|
||||
class Categories extends Controller
|
||||
{
|
||||
use Helper, Modules;
|
||||
|
||||
/**
|
||||
* Instantiate a new controller instance.
|
||||
*/
|
||||
|
|
@ -29,19 +33,29 @@ class Categories extends Controller
|
|||
*/
|
||||
public function create(IRequest $request)
|
||||
{
|
||||
$type = $request->get('type', 'item');
|
||||
$type = $request->get('type', Category::ITEM_TYPE);
|
||||
|
||||
$category_types = $this->getTypeCategoryTypes($type);
|
||||
$hide_code_types = $this->hideCodeCategoryTypes($category_types);
|
||||
|
||||
$categories = collect();
|
||||
|
||||
Category::type($type)->enabled()->orderBy('name')->get()->each(function ($category) use (&$categories) {
|
||||
$categories->push([
|
||||
'id' => $category->id,
|
||||
'title' => $category->name,
|
||||
'level' => $category->level,
|
||||
]);
|
||||
});
|
||||
Category::type($category_types)
|
||||
->enabled()
|
||||
->orderBy('name')
|
||||
->get()
|
||||
->each(function ($category) use (&$categories) {
|
||||
$categories->push([
|
||||
'id' => $category->id,
|
||||
'title' => $category->name,
|
||||
'level' => $category->level,
|
||||
]);
|
||||
});
|
||||
|
||||
$html = view('modals.categories.create', compact('type', 'categories'))->render();
|
||||
$type_group = count($category_types) > 1 ? true : false;
|
||||
$types = $this->getCategoryTypes(group: true, types: $category_types);
|
||||
|
||||
$html = view('modals.categories.create', compact('type', 'types', 'categories', 'type_group', 'hide_code_types'))->render();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
|
|
@ -61,7 +75,7 @@ class Categories extends Controller
|
|||
public function store(Request $request)
|
||||
{
|
||||
$request['enabled'] = 1;
|
||||
$request['type'] = $request->get('type', 'income');
|
||||
$request['type'] = $request->get('type', Category::ITEM_TYPE);
|
||||
$request['color'] = $request->get('color', '#' . dechex(rand(0x000000, 0xFFFFFF)));
|
||||
|
||||
$response = $this->ajaxDispatch(new CreateCategory($request));
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ class DocumentTransactions extends Controller
|
|||
*/
|
||||
public function create(Document $document)
|
||||
{
|
||||
$document->load(['totals', 'transactions']);
|
||||
|
||||
$currency = Currency::where('code', $document->currency_code)->first();
|
||||
|
||||
$paid = $document->paid;
|
||||
|
|
@ -149,6 +151,8 @@ class DocumentTransactions extends Controller
|
|||
*/
|
||||
public function edit(Document $document, Transaction $transaction)
|
||||
{
|
||||
$document->load(['totals', 'transactions']);
|
||||
|
||||
$currency = Currency::where('code', $document->currency_code)->first();
|
||||
|
||||
// if you edit transaction before remove transaction amount
|
||||
|
|
|
|||
|
|
@ -44,6 +44,20 @@ class Invoices extends Controller
|
|||
*/
|
||||
public function show(Document $invoice, Request $request)
|
||||
{
|
||||
$invoice->load([
|
||||
'items.taxes.tax',
|
||||
'items.item',
|
||||
'totals',
|
||||
'contact',
|
||||
'currency',
|
||||
'category',
|
||||
'histories',
|
||||
'media',
|
||||
'transactions',
|
||||
'recurring',
|
||||
'children',
|
||||
]);
|
||||
|
||||
$payment_methods = Modules::getPaymentMethods();
|
||||
|
||||
event(new \App\Events\Document\DocumentViewed($invoice));
|
||||
|
|
|
|||
|
|
@ -69,7 +69,10 @@ class Payments extends Controller
|
|||
event(new TransactionPrinting($payment));
|
||||
|
||||
$transaction = $payment;
|
||||
$view = view('banking.transactions.print_default', compact('transaction'));
|
||||
|
||||
$real_type = $this->getRealTypeTransaction($transaction->type);
|
||||
|
||||
$view = view('banking.transactions.print_default', compact('transaction', 'real_type'));
|
||||
|
||||
return mb_convert_encoding($view, 'HTML-ENTITIES', 'UTF-8');
|
||||
}
|
||||
|
|
@ -88,7 +91,10 @@ class Payments extends Controller
|
|||
$currency_style = true;
|
||||
|
||||
$transaction = $payment;
|
||||
$view = view('banking.transactions.print_default', compact('transaction', 'currency_style'))->render();
|
||||
|
||||
$real_type = $this->getRealTypeTransaction($transaction->type);
|
||||
|
||||
$view = view('banking.transactions.print_default', compact('transaction', 'currency_style', 'real_type'))->render();
|
||||
$html = mb_convert_encoding($view, 'HTML-ENTITIES', 'UTF-8');
|
||||
|
||||
$pdf = app('dompdf.wrapper');
|
||||
|
|
|
|||
|
|
@ -45,6 +45,20 @@ class Bills extends Controller
|
|||
*/
|
||||
public function show(Document $bill)
|
||||
{
|
||||
$bill->load([
|
||||
'items.taxes.tax',
|
||||
'items.item',
|
||||
'totals',
|
||||
'contact',
|
||||
'currency',
|
||||
'category',
|
||||
'histories',
|
||||
'media',
|
||||
'transactions',
|
||||
'recurring',
|
||||
'children',
|
||||
]);
|
||||
|
||||
return view('purchases.bills.show', compact('bill'));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ use App\Jobs\Document\UpdateDocument;
|
|||
use App\Models\Common\Recurring;
|
||||
use App\Models\Document\Document;
|
||||
use App\Traits\Documents;
|
||||
use App\Utilities\Date;
|
||||
|
||||
class RecurringBills extends Controller
|
||||
{
|
||||
|
|
@ -80,7 +81,11 @@ class RecurringBills extends Controller
|
|||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$response = $this->ajaxDispatch(new CreateDocument($request->merge(['issued_at' => $request->get('recurring_started_at')])));
|
||||
$issue_at = Date::parse($request->get('recurring_started_at'))->format('Y-m-d');
|
||||
|
||||
$request->merge(['issued_at' => $issue_at]);
|
||||
|
||||
$response = $this->ajaxDispatch(new CreateDocument($request));
|
||||
|
||||
if ($response['success']) {
|
||||
$response['redirect'] = route('recurring-bills.show', $response['data']->id);
|
||||
|
|
@ -163,7 +168,11 @@ class RecurringBills extends Controller
|
|||
*/
|
||||
public function update(Document $recurring_bill, Request $request)
|
||||
{
|
||||
$response = $this->ajaxDispatch(new UpdateDocument($recurring_bill, $request->merge(['issued_at' => $request->get('recurring_started_at')])));
|
||||
$issue_at = Date::parse($request->get('recurring_started_at'))->format('Y-m-d');
|
||||
|
||||
$request->merge(['issued_at' => $issue_at]);
|
||||
|
||||
$response = $this->ajaxDispatch(new UpdateDocument($recurring_bill, $request));
|
||||
|
||||
if ($response['success']) {
|
||||
$response['redirect'] = route('recurring-bills.show', $response['data']->id);
|
||||
|
|
|
|||
|
|
@ -30,7 +30,20 @@ class Vendors extends Controller
|
|||
*/
|
||||
public function index()
|
||||
{
|
||||
$vendors = Contact::with('media', 'bills.histories', 'bills.totals', 'bills.transactions', 'bills.media')->vendor()->collect();
|
||||
$vendors = Contact::with([
|
||||
'media',
|
||||
'bills.histories',
|
||||
'bills.totals',
|
||||
'bills.transactions',
|
||||
'bills.media'
|
||||
])
|
||||
->withCount([
|
||||
'contact_persons as contact_persons_with_email_count' => function ($query) {
|
||||
$query->whereNotNull('email');
|
||||
}
|
||||
])
|
||||
->vendor()
|
||||
->collect();
|
||||
|
||||
return $this->response('purchases.vendors.index', compact('vendors'));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,20 @@ class Customers extends Controller
|
|||
*/
|
||||
public function index()
|
||||
{
|
||||
$customers = Contact::customer()->with('media', 'invoices.histories', 'invoices.totals', 'invoices.transactions', 'invoices.media')->collect();
|
||||
$customers = Contact::customer()
|
||||
->with([
|
||||
'media',
|
||||
'invoices.histories',
|
||||
'invoices.totals',
|
||||
'invoices.transactions',
|
||||
'invoices.media'
|
||||
])
|
||||
->withCount([
|
||||
'contact_persons as contact_persons_with_email_count' => function ($query) {
|
||||
$query->whereNotNull('email');
|
||||
}
|
||||
])
|
||||
->collect();
|
||||
|
||||
return $this->response('sales.customers.index', compact('customers'));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ class Invoices extends Controller
|
|||
{
|
||||
$this->setActiveTabForDocuments();
|
||||
|
||||
$invoices = Document::invoice()->with('contact', 'items', 'item_taxes', 'last_history', 'transactions', 'totals', 'histories', 'media')->collect(['document_number'=> 'desc']);
|
||||
$invoices = Document::invoice()->with('contact', 'items', 'items.taxes', 'item_taxes', 'last_history', 'transactions', 'totals', 'histories', 'media')->collect(['document_number'=> 'desc']);
|
||||
|
||||
$total_invoices = Document::invoice()->count();
|
||||
|
||||
|
|
@ -47,6 +47,20 @@ class Invoices extends Controller
|
|||
*/
|
||||
public function show(Document $invoice)
|
||||
{
|
||||
$invoice->load([
|
||||
'items.taxes.tax',
|
||||
'items.item',
|
||||
'totals',
|
||||
'contact',
|
||||
'currency',
|
||||
'category',
|
||||
'histories',
|
||||
'media',
|
||||
'transactions',
|
||||
'recurring',
|
||||
'children',
|
||||
]);
|
||||
|
||||
return view('sales.invoices.show', compact('invoice'));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ use App\Jobs\Document\UpdateDocument;
|
|||
use App\Models\Common\Recurring;
|
||||
use App\Models\Document\Document;
|
||||
use App\Traits\Documents;
|
||||
use App\Utilities\Date;
|
||||
|
||||
class RecurringInvoices extends Controller
|
||||
{
|
||||
|
|
@ -80,7 +81,11 @@ class RecurringInvoices extends Controller
|
|||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$response = $this->ajaxDispatch(new CreateDocument($request->merge(['issued_at' => $request->get('recurring_started_at')])));
|
||||
$issue_at = Date::parse($request->get('recurring_started_at'))->format('Y-m-d');
|
||||
|
||||
$request->merge(['issued_at' => $issue_at]);
|
||||
|
||||
$response = $this->ajaxDispatch(new CreateDocument($request));
|
||||
|
||||
if ($response['success']) {
|
||||
$response['redirect'] = route('recurring-invoices.show', $response['data']->id);
|
||||
|
|
@ -163,7 +168,11 @@ class RecurringInvoices extends Controller
|
|||
*/
|
||||
public function update(Document $recurring_invoice, Request $request)
|
||||
{
|
||||
$response = $this->ajaxDispatch(new UpdateDocument($recurring_invoice, $request->merge(['issued_at' => $request->get('recurring_started_at')])));
|
||||
$issue_at = Date::parse($request->get('recurring_started_at'))->format('Y-m-d');
|
||||
|
||||
$request->merge(['issued_at' => $issue_at]);
|
||||
|
||||
$response = $this->ajaxDispatch(new UpdateDocument($recurring_invoice, $request));
|
||||
|
||||
if ($response['success']) {
|
||||
$response['redirect'] = route('recurring-invoices.show', $response['data']->id);
|
||||
|
|
|
|||
|
|
@ -24,17 +24,44 @@ class Categories extends Controller
|
|||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->setActiveTabForCategories();
|
||||
|
||||
$query = Category::with('sub_categories');
|
||||
|
||||
if (request()->has('search')) {
|
||||
$query->withSubcategory();
|
||||
if (search_string_value('searchable')) {
|
||||
$query->withSubCategory();
|
||||
}
|
||||
|
||||
$types = $this->getCategoryTypes();
|
||||
|
||||
$categories = $query->type(array_keys($types))->collect();
|
||||
if (request()->get('list_records') == 'all') {
|
||||
$query->type(array_keys($types));
|
||||
}
|
||||
|
||||
return $this->response('settings.categories.index', compact('categories', 'types'));
|
||||
$categories = $query->collect();
|
||||
|
||||
$tabs = $this->getCategoryTabs();
|
||||
|
||||
$tab = request()->get('list_records');
|
||||
$tab_active = ! empty($tab) ? 'categories-' . $tab : 'categories-all';
|
||||
|
||||
$hide_code_column = true;
|
||||
|
||||
$search_string_type = search_string_value('type');
|
||||
$selected_types = ! empty($search_string_type) ? explode(',', $search_string_type) : array_keys($types);
|
||||
|
||||
foreach (config('type.category', []) as $type => $config) {
|
||||
if (! in_array($type, $selected_types)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (empty($config['hide']) || !in_array('code', $config['hide'])) {
|
||||
$hide_code_column = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->response('settings.categories.index', compact('categories', 'types', 'tabs', 'tab_active', 'hide_code_column'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -54,14 +81,17 @@ class Categories extends Controller
|
|||
*/
|
||||
public function create()
|
||||
{
|
||||
$types = $this->getCategoryTypes();
|
||||
|
||||
$categories = [];
|
||||
|
||||
foreach (config('type.category') as $type => $config) {
|
||||
$categories[$type] = [];
|
||||
}
|
||||
|
||||
$type_group = $this->isGroupCategoryType();
|
||||
$hide_code_types = $this->hideCodeCategoryTypes(array_keys($categories));
|
||||
|
||||
$types = $this->getCategoryTypes(group: $type_group);
|
||||
|
||||
Category::enabled()->orderBy('name')->get()->each(function ($category) use (&$categories) {
|
||||
$categories[$category->type][] = [
|
||||
'id' => $category->id,
|
||||
|
|
@ -70,7 +100,7 @@ class Categories extends Controller
|
|||
];
|
||||
});
|
||||
|
||||
return view('settings.categories.create', compact('types', 'categories'));
|
||||
return view('settings.categories.create', compact('types', 'categories', 'type_group', 'hide_code_types'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -134,8 +164,6 @@ class Categories extends Controller
|
|||
*/
|
||||
public function edit(Category $category)
|
||||
{
|
||||
$types = $this->getCategoryTypes();
|
||||
|
||||
$type_disabled = (Category::where('type', $category->type)->count() == 1) ?: false;
|
||||
|
||||
$edited_category_id = $category->id;
|
||||
|
|
@ -146,6 +174,11 @@ class Categories extends Controller
|
|||
$categories[$type] = [];
|
||||
}
|
||||
|
||||
$type_group = $this->isGroupCategoryType();
|
||||
$hide_code_types = $this->hideCodeCategoryTypes(array_keys($categories));
|
||||
|
||||
$types = $this->getCategoryTypes(group: $type_group);
|
||||
|
||||
$skip_categories = [];
|
||||
$skip_categories[] = $edited_category_id;
|
||||
|
||||
|
|
@ -175,7 +208,7 @@ class Categories extends Controller
|
|||
|
||||
$parent_categories = $categories[$category->type] ?? [];
|
||||
|
||||
return view('settings.categories.edit', compact('category', 'types', 'type_disabled', 'categories', 'parent_categories'));
|
||||
return view('settings.categories.edit', compact('category', 'types', 'type_disabled', 'categories', 'parent_categories', 'type_group', 'hide_code_types'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -6,9 +6,12 @@ use App\Abstracts\Http\SettingController;
|
|||
use App\Models\Banking\Account;
|
||||
use App\Models\Setting\Category;
|
||||
use App\Models\Setting\Tax;
|
||||
use App\Traits\Categories;
|
||||
|
||||
class Defaults extends SettingController
|
||||
{
|
||||
use Categories;
|
||||
|
||||
public function edit()
|
||||
{
|
||||
$accounts = Account::enabled()->orderBy('name')->get()->pluck('title', 'id');
|
||||
|
|
@ -39,11 +42,16 @@ class Defaults extends SettingController
|
|||
|
||||
$taxes = Tax::enabled()->orderBy('name')->get()->pluck('title', 'id');
|
||||
|
||||
$income_category_types = $this->getIncomeCategoryTypes('string');
|
||||
$expense_category_types = $this->getExpenseCategoryTypes('string');
|
||||
|
||||
return view('settings.default.edit', compact(
|
||||
'accounts',
|
||||
'sales_categories',
|
||||
'purchases_categories',
|
||||
'taxes',
|
||||
'income_category_types',
|
||||
'expense_category_types',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ class Kernel extends HttpKernel
|
|||
'menu.admin',
|
||||
'permission:read-admin-panel',
|
||||
'plan.limits',
|
||||
'module.subscription',
|
||||
],
|
||||
|
||||
'wizard' => [
|
||||
|
|
@ -175,6 +176,7 @@ class Kernel extends HttpKernel
|
|||
'dropzone' => \App\Http\Middleware\Dropzone::class,
|
||||
'header.x' => \App\Http\Middleware\AddXHeader::class,
|
||||
'plan.limits' => \App\Http\Middleware\RedirectIfHitPlanLimits::class,
|
||||
'module.subscription' => \App\Http\Middleware\RedirectIfHitModuleSubscription::class,
|
||||
'menu.admin' => \App\Http\Middleware\AdminMenu::class,
|
||||
'menu.portal' => \App\Http\Middleware\PortalMenu::class,
|
||||
'date.format' => \App\Http\Middleware\DateFormat::class,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Traits\Modules;
|
||||
use App\Utilities\Versions;
|
||||
use Closure;
|
||||
|
||||
class RedirectIfHitModuleSubscription
|
||||
{
|
||||
use Modules;
|
||||
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
if (! $request->isMethod(strtolower('GET'))) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
if ($request->ajax()) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
if ($request->is(company_id() . '/apps/*')) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
if (! $this->getModulesLimitOfSubscription()->action_status) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Common;
|
||||
|
||||
use App\Abstracts\Http\FormRequest;
|
||||
use Illuminate\Contracts\Validation\Validator;
|
||||
|
||||
class ReportShow extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'start_date' => 'nullable|date',
|
||||
'end_date' => 'nullable|date',
|
||||
];
|
||||
}
|
||||
|
||||
public function failedValidation(Validator $validator)
|
||||
{
|
||||
// "If start_date and end_date is invalid, clear the values
|
||||
if ($validator->errors()->has('start_date') && $validator->errors()->has('end_date')) {
|
||||
request()->query->remove('start_date');
|
||||
request()->query->remove('end_date');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// If start_date is invalid, set it to be equal to end_date.
|
||||
if ($validator->errors()->has('start_date')) {
|
||||
request()->merge([
|
||||
'start_date' => request('end_date'),
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// If end_date is invalid, set it to be equal to start_date.
|
||||
if ($validator->errors()->has('end_date')) {
|
||||
request()->merge([
|
||||
'end_date' => request('start_date'),
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -93,6 +93,8 @@ class Document extends FormRequest
|
|||
foreach ($items as $key => $item) {
|
||||
$size = 10;
|
||||
|
||||
$items[$key]['quantity'] = calculation_to_quantity($item['quantity']);
|
||||
|
||||
if (Str::contains($item['quantity'], ['.', ','])) {
|
||||
$size = 12;
|
||||
}
|
||||
|
|
@ -101,6 +103,8 @@ class Document extends FormRequest
|
|||
|
||||
$this->items_quantity_size[$key] = $size;
|
||||
}
|
||||
|
||||
$this->request->set('items', $items);
|
||||
}
|
||||
|
||||
return $rules;
|
||||
|
|
|
|||
|
|
@ -15,8 +15,14 @@ class Category extends FormRequest
|
|||
{
|
||||
$types = collect(config('type.category'))->keys();
|
||||
|
||||
$type = $this->request->get('type');
|
||||
$config = config('type.category.' . $type, []);
|
||||
$code_hidden = !empty($config['hide']) && in_array('code', $config['hide']);
|
||||
$code = $code_hidden ? 'nullable|string' : 'required|string';
|
||||
|
||||
return [
|
||||
'name' => 'required|string',
|
||||
'code' => $code,
|
||||
'type' => 'required|string|in:' . $types->implode(','),
|
||||
'color' => 'required|string|colour',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -29,8 +29,9 @@ class Currency extends FormRequest
|
|||
'rate' => 'required|gt:0',
|
||||
'enabled' => 'integer|boolean',
|
||||
'default_currency' => 'nullable|boolean',
|
||||
'decimal_mark' => 'nullable|string|different:thousands_separator|regex:/^[A-Za-z.,_\s-]+$/',
|
||||
'symbol_first' => 'nullable|boolean',
|
||||
'thousands_separator' => 'different:decimal_mark',
|
||||
'thousands_separator' => 'nullable|different:decimal_mark|regex:/^[A-Za-z.,_\s-]+$/',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,9 +17,11 @@ class Category extends JsonResource
|
|||
return [
|
||||
'id' => $this->id,
|
||||
'company_id' => $this->company_id,
|
||||
'code' => $this->code,
|
||||
'name' => $this->name,
|
||||
'type' => $this->type,
|
||||
'color' => $this->color,
|
||||
'description' => $this->description,
|
||||
'enabled' => $this->enabled,
|
||||
'parent_id' => $this->parent_id,
|
||||
'created_from' => $this->created_from,
|
||||
|
|
|
|||
|
|
@ -32,6 +32,10 @@ class Transactions extends Import
|
|||
{
|
||||
$row = parent::map($row);
|
||||
|
||||
if (!isset($row['type'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$real_type = $this->getRealTypeTransaction($row['type']);
|
||||
$contact_type = config('type.transaction.' . $real_type . '.contact_type', $real_type == 'income' ? 'customer' : 'vendor');
|
||||
|
||||
|
|
@ -49,6 +53,7 @@ class Transactions extends Import
|
|||
public function prepareRules($rules): array
|
||||
{
|
||||
$rules['number'] = 'required|string';
|
||||
$rules['type'] = 'required|string';
|
||||
//$rules['currency_rate'] = 'required|gt:0';
|
||||
|
||||
return $rules;
|
||||
|
|
|
|||
|
|
@ -44,6 +44,10 @@ class BillItemTaxes extends Import
|
|||
|
||||
$document = Document::with('items')->bill()->number($row['bill_number'])->first();
|
||||
|
||||
if (! $document) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$row['document_id'] = (int) $document->id;
|
||||
|
||||
if (empty($row['document_item_id']) && !empty($row['item_name'])) {
|
||||
|
|
@ -51,7 +55,11 @@ class BillItemTaxes extends Import
|
|||
|
||||
$item_id = Item::name($row['item_name'])->whereIn('id', $document_items_ids)->pluck('id')->first();
|
||||
|
||||
$row['document_item_id'] = DocumentItem::bill()->where('item_id', $item_id)->pluck('id')->first();
|
||||
$row['document_item_id'] = DocumentItem::bill()
|
||||
->where('document_id', $row['document_id'])
|
||||
->where('item_id', $item_id)
|
||||
->pluck('id')
|
||||
->first();
|
||||
}
|
||||
|
||||
$row['tax_id'] = $this->getTaxId($row);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ namespace App\Imports\Purchases\Bills\Sheets;
|
|||
use App\Abstracts\Import;
|
||||
use App\Http\Requests\Banking\Transaction as Request;
|
||||
use App\Models\Banking\Transaction as Model;
|
||||
use App\Models\Setting\Category;
|
||||
|
||||
class BillTransactions extends Import
|
||||
{
|
||||
|
|
@ -36,9 +37,9 @@ class BillTransactions extends Import
|
|||
|
||||
$row = parent::map($row);
|
||||
|
||||
$row['type'] = 'expense';
|
||||
$row['type'] = Model::EXPENSE_TYPE;
|
||||
$row['account_id'] = $this->getAccountId($row);
|
||||
$row['category_id'] = $this->getCategoryId($row, 'expense');
|
||||
$row['category_id'] = $this->getCategoryId($row, Category::EXPENSE_TYPE);
|
||||
$row['contact_id'] = $this->getContactId($row, 'vendor');
|
||||
$row['currency_code'] = $this->getCurrencyCode($row);
|
||||
$row['document_id'] = $this->getDocumentId($row);
|
||||
|
|
|
|||
|
|
@ -5,10 +5,13 @@ namespace App\Imports\Purchases\Bills\Sheets;
|
|||
use App\Abstracts\Import;
|
||||
use App\Http\Requests\Document\Document as Request;
|
||||
use App\Models\Document\Document as Model;
|
||||
use App\Traits\Documents;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class Bills extends Import
|
||||
{
|
||||
Use Documents;
|
||||
|
||||
public $request_class = Request::class;
|
||||
|
||||
public $model = Model::class;
|
||||
|
|
@ -46,6 +49,9 @@ class Bills extends Import
|
|||
$row['currency_code'] = $this->getCurrencyCode($row);
|
||||
$row['type'] = Model::BILL_TYPE;
|
||||
$row['contact_country'] = !empty($country) ? $country : null;
|
||||
$row['title'] = $row['title'] ?? Model::BILL_TYPE;
|
||||
$row['template'] = $row['template'] ?? setting($this->getDocumentSettingKey(Model::BILL_TYPE, 'template'), 'default');
|
||||
$row['color'] = $row['color'] ?? setting($this->getDocumentSettingKey(Model::BILL_TYPE, 'color'), '#55588b');
|
||||
$row['parent_id'] = $this->getParentId($row) ?? 0;
|
||||
|
||||
return $row;
|
||||
|
|
|
|||
|
|
@ -42,15 +42,19 @@ class RecurringBillItemTaxes extends Import
|
|||
|
||||
$row = parent::map($row);
|
||||
|
||||
$row['document_id'] = (int) Document::where('type', '=', Document::BILL_RECURRING_TYPE)
|
||||
->number($row['bill_number'])
|
||||
->pluck('id')
|
||||
->first();
|
||||
$document = Document::with('items')->billRecurring()->number($row['bill_number'])->first();
|
||||
|
||||
if (! $document) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$row['document_id'] = (int) $document->id;
|
||||
|
||||
if (empty($row['document_item_id']) && !empty($row['item_name'])) {
|
||||
$item_id = Item::name($row['item_name'])->pluck('id')->first();
|
||||
|
||||
$row['document_item_id'] = DocumentItem::where('type', '=', Document::BILL_RECURRING_TYPE)
|
||||
$row['document_item_id'] = DocumentItem::billRecurring()
|
||||
->where('document_id', $row['document_id'])
|
||||
->where('item_id', $item_id)
|
||||
->pluck('id')
|
||||
->first();
|
||||
|
|
|
|||
|
|
@ -5,10 +5,13 @@ namespace App\Imports\Purchases\RecurringBills\Sheets;
|
|||
use App\Abstracts\Import;
|
||||
use App\Http\Requests\Document\Document as Request;
|
||||
use App\Models\Document\Document as Model;
|
||||
use App\Traits\Documents;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class RecurringBills extends Import
|
||||
{
|
||||
Use Documents;
|
||||
|
||||
public $request_class = Request::class;
|
||||
|
||||
public $model = Model::class;
|
||||
|
|
@ -46,6 +49,9 @@ class RecurringBills extends Import
|
|||
$row['currency_code'] = $this->getCurrencyCode($row);
|
||||
$row['type'] = Model::BILL_RECURRING_TYPE;
|
||||
$row['contact_country'] = !empty($country) ? $country : null;
|
||||
$row['title'] = $row['title'] ?? Model::BILL_RECURRING_TYPE;
|
||||
$row['template'] = $row['template'] ?? setting($this->getDocumentSettingKey(Model::BILL_RECURRING_TYPE, 'template'), 'default');
|
||||
$row['color'] = $row['color'] ?? setting($this->getDocumentSettingKey(Model::BILL_RECURRING_TYPE, 'color'), '#55588b');
|
||||
|
||||
return $row;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,6 +44,10 @@ class InvoiceItemTaxes extends Import
|
|||
|
||||
$document = Document::with('items')->invoice()->number($row['invoice_number'])->first();
|
||||
|
||||
if (! $document) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$row['document_id'] = (int) $document->id;
|
||||
|
||||
if (empty($row['document_item_id']) && !empty($row['item_name'])) {
|
||||
|
|
@ -51,7 +55,11 @@ class InvoiceItemTaxes extends Import
|
|||
|
||||
$item_id = Item::name($row['item_name'])->whereIn('id', $document_items_ids)->pluck('id')->first();
|
||||
|
||||
$row['document_item_id'] = DocumentItem::invoice()->where('item_id', $item_id)->pluck('id')->first();
|
||||
$row['document_item_id'] = DocumentItem::invoice()
|
||||
->where('document_id', $row['document_id'])
|
||||
->where('item_id', $item_id)
|
||||
->pluck('id')
|
||||
->first();
|
||||
}
|
||||
|
||||
$row['tax_id'] = $this->getTaxId($row);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ namespace App\Imports\Sales\Invoices\Sheets;
|
|||
use App\Abstracts\Import;
|
||||
use App\Http\Requests\Banking\Transaction as Request;
|
||||
use App\Models\Banking\Transaction as Model;
|
||||
use App\Models\Setting\Category;
|
||||
|
||||
class InvoiceTransactions extends Import
|
||||
{
|
||||
|
|
@ -37,10 +38,10 @@ class InvoiceTransactions extends Import
|
|||
|
||||
$row = parent::map($row);
|
||||
|
||||
$row['type'] = 'income';
|
||||
$row['type'] = Model::INCOME_TYPE;
|
||||
$row['currency_code'] = $this->getCurrencyCode($row);
|
||||
$row['account_id'] = $this->getAccountId($row);
|
||||
$row['category_id'] = $this->getCategoryId($row, 'income');
|
||||
$row['category_id'] = $this->getCategoryId($row, Category::INCOME_TYPE);
|
||||
$row['contact_id'] = $this->getContactId($row, 'customer');
|
||||
$row['document_id'] = $this->getDocumentId($row);
|
||||
$row['number'] = $row['transaction_number'];
|
||||
|
|
|
|||
|
|
@ -5,10 +5,13 @@ namespace App\Imports\Sales\Invoices\Sheets;
|
|||
use App\Abstracts\Import;
|
||||
use App\Http\Requests\Document\Document as Request;
|
||||
use App\Models\Document\Document as Model;
|
||||
use App\Traits\Documents;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class Invoices extends Import
|
||||
{
|
||||
Use Documents;
|
||||
|
||||
public $request_class = Request::class;
|
||||
|
||||
public $model = Model::class;
|
||||
|
|
@ -46,6 +49,9 @@ class Invoices extends Import
|
|||
$row['currency_code'] = $this->getCurrencyCode($row);
|
||||
$row['type'] = Model::INVOICE_TYPE;
|
||||
$row['contact_country'] = !empty($country) ? $country : null;
|
||||
$row['title'] = $row['title'] ?? Model::INVOICE_TYPE;
|
||||
$row['template'] = $row['template'] ?? setting($this->getDocumentSettingKey(Model::INVOICE_TYPE, 'template'), 'default');
|
||||
$row['color'] = $row['color'] ?? setting($this->getDocumentSettingKey(Model::INVOICE_TYPE, 'color'), '#55588b');
|
||||
$row['parent_id'] = $this->getParentId($row) ?? 0;
|
||||
|
||||
return $row;
|
||||
|
|
|
|||
|
|
@ -42,15 +42,21 @@ class RecurringInvoiceItemTaxes extends Import
|
|||
|
||||
$row = parent::map($row);
|
||||
|
||||
$row['document_id'] = (int) Document::where('type', '=', Document::INVOICE_RECURRING_TYPE)
|
||||
->number($row['invoice_number'])
|
||||
->pluck('id')
|
||||
->first();
|
||||
$document = Document::with('items')->invoiceRecurring()->number($row['invoice_number'])->first();
|
||||
|
||||
if (! $document) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$row['document_id'] = (int) $document->id;
|
||||
|
||||
if (empty($row['document_item_id']) && !empty($row['item_name'])) {
|
||||
$item_id = Item::name($row['item_name'])->pluck('id')->first();
|
||||
$document_items_ids = $document->items->pluck('item_id')->toArray();
|
||||
|
||||
$row['document_item_id'] = DocumentItem::where('type', '=', Document::INVOICE_RECURRING_TYPE)
|
||||
$item_id = Item::name($row['item_name'])->whereIn('id', $document_items_ids)->pluck('id')->first();
|
||||
|
||||
$row['document_item_id'] = DocumentItem::invoiceRecurring()
|
||||
->where('document_id', $row['document_id'])
|
||||
->where('item_id', $item_id)
|
||||
->pluck('id')
|
||||
->first();
|
||||
|
|
|
|||
|
|
@ -5,10 +5,13 @@ namespace App\Imports\Sales\RecurringInvoices\Sheets;
|
|||
use App\Abstracts\Import;
|
||||
use App\Http\Requests\Document\Document as Request;
|
||||
use App\Models\Document\Document as Model;
|
||||
use App\Traits\Documents;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class RecurringInvoices extends Import
|
||||
{
|
||||
Use Documents;
|
||||
|
||||
public $request_class = Request::class;
|
||||
|
||||
public $model = Model::class;
|
||||
|
|
@ -45,6 +48,9 @@ class RecurringInvoices extends Import
|
|||
$row['contact_id'] = $this->getContactId($row, 'customer');
|
||||
$row['currency_code'] = $this->getCurrencyCode($row);
|
||||
$row['type'] = Model::INVOICE_RECURRING_TYPE;
|
||||
$row['title'] = $row['title'] ?? Model::INVOICE_RECURRING_TYPE;
|
||||
$row['template'] = $row['template'] ?? setting($this->getDocumentSettingKey(Model::INVOICE_RECURRING_TYPE, 'template'), 'default');
|
||||
$row['color'] = $row['color'] ?? setting($this->getDocumentSettingKey(Model::INVOICE_RECURRING_TYPE, 'color'), '#55588b');
|
||||
$row['contact_country'] = !empty($country) ? $country : null;
|
||||
|
||||
return $row;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ class Categories extends Import
|
|||
public $columns = [
|
||||
'name',
|
||||
'type',
|
||||
'code',
|
||||
'description',
|
||||
];
|
||||
|
||||
public function model(array $row)
|
||||
|
|
|
|||
|
|
@ -34,7 +34,9 @@ class UpdateUser extends Job implements ShouldUpdate
|
|||
$media = $this->getMedia($this->request->file('picture'), 'users');
|
||||
|
||||
$this->model->attachMedia($media, 'picture');
|
||||
} elseif (! $this->request->file('picture') && $this->model->picture) {
|
||||
} elseif ($this->request->isNotApi() && ! $this->request->file('picture') && $this->model->picture) {
|
||||
$this->deleteMediaModel($this->model, 'picture', $this->request);
|
||||
} elseif ($this->request->isApi() && $this->request->has('remove_picture') && $this->model->picture) {
|
||||
$this->deleteMediaModel($this->model, 'picture', $this->request);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,13 +38,6 @@ class CreateBankingDocumentTransaction extends Job implements ShouldCreate
|
|||
\DB::transaction(function () {
|
||||
$this->transaction = $this->dispatch(new CreateTransaction($this->request));
|
||||
|
||||
// Upload attachment
|
||||
if ($this->request->file('attachment')) {
|
||||
$media = $this->getMedia($this->request->file('attachment'), 'transactions');
|
||||
|
||||
$this->transaction->attachMedia($media, 'attachment');
|
||||
}
|
||||
|
||||
$this->model->save();
|
||||
|
||||
$this->createHistory();
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ class UpdateBankingDocumentTransaction extends Job implements ShouldUpdate
|
|||
{
|
||||
use Currencies;
|
||||
|
||||
protected Transaction $transaction;
|
||||
|
||||
public function __construct(Document $model, Transaction $transaction, $request)
|
||||
{
|
||||
$this->model = $model;
|
||||
|
|
@ -37,13 +39,6 @@ class UpdateBankingDocumentTransaction extends Job implements ShouldUpdate
|
|||
\DB::transaction(function () {
|
||||
$this->transaction = $this->dispatch(new UpdateTransaction($this->transaction, $this->request));
|
||||
|
||||
// Upload attachment
|
||||
if ($this->request->file('attachment')) {
|
||||
$media = $this->getMedia($this->request->file('attachment'), 'transactions');
|
||||
|
||||
$this->transaction->attachMedia($media, 'attachment');
|
||||
}
|
||||
|
||||
$this->model->save();
|
||||
|
||||
$this->createHistory();
|
||||
|
|
|
|||
|
|
@ -35,7 +35,9 @@ class UpdateTransaction extends Job implements ShouldUpdate
|
|||
|
||||
$this->model->attachMedia($media, 'attachment');
|
||||
}
|
||||
} elseif (! $this->request->file('attachment') && $this->model->attachment) {
|
||||
} elseif ($this->request->isNotApi() && ! $this->request->file('attachment') && $this->model->attachment) {
|
||||
$this->deleteMediaModel($this->model, 'attachment', $this->request);
|
||||
} elseif ($this->request->isApi() && $this->request->has('remove_attachment') && $this->model->attachment) {
|
||||
$this->deleteMediaModel($this->model, 'attachment', $this->request);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,9 @@ class UpdateTransfer extends Job implements ShouldUpdate
|
|||
|
||||
$this->model->attachMedia($media, 'attachment');
|
||||
}
|
||||
} elseif (! $this->request->file('attachment') && $this->model->attachment) {
|
||||
} elseif ($this->request->isNotApi() && ! $this->request->file('attachment') && $this->model->attachment) {
|
||||
$this->deleteMediaModel($this->model, 'attachment', $this->request);
|
||||
} elseif ($this->request->isApi() && $this->request->has('remove_attachment') && $this->model->attachment) {
|
||||
$this->deleteMediaModel($this->model, 'attachment', $this->request);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ class CreateMediableForDownload extends JobShouldQueue
|
|||
|
||||
public function getQueuedMedia()
|
||||
{
|
||||
return config('excel.temporary_files.remote_disk') !== null
|
||||
return config('dompdf.disk') !== null
|
||||
? $this->getRemoteQueuedMedia()
|
||||
: $this->getLocalQueuedMedia();
|
||||
}
|
||||
|
|
@ -88,12 +88,15 @@ class CreateMediableForDownload extends JobShouldQueue
|
|||
|
||||
public function getRemoteQueuedMedia()
|
||||
{
|
||||
$disk = config('excel.temporary_files.remote_disk');
|
||||
$prefix = config('excel.temporary_files.remote_prefix');
|
||||
$disk = config('dompdf.disk');
|
||||
|
||||
$content = Storage::disk($disk)->get($this->file_name);
|
||||
$folder_path = 'app/temp/' . company_id() . '/bulk_actions/';
|
||||
|
||||
$file_name = str_replace([$prefix, '.xlsx', '.xls'], '', $this->file_name);
|
||||
$source = get_storage_path($folder_path . $this->file_name . '.zip');
|
||||
|
||||
$content = Storage::disk($disk)->get($source);
|
||||
|
||||
$file_name = str_replace(['.pdf', '.zip'], '', $this->file_name);
|
||||
|
||||
$destination = $this->getMediaFolder('bulk_actions');
|
||||
|
||||
|
|
@ -106,7 +109,7 @@ class CreateMediableForDownload extends JobShouldQueue
|
|||
->toDirectory($destination)
|
||||
->upload();
|
||||
|
||||
Storage::disk($disk)->delete($this->file_name);
|
||||
Storage::disk($disk)->delete($source);
|
||||
|
||||
return $media;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ namespace App\Jobs\Common;
|
|||
|
||||
use App\Abstracts\JobShouldQueue;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use ZipArchive;
|
||||
|
||||
class CreateZipForDownload extends JobShouldQueue
|
||||
|
|
@ -34,27 +35,76 @@ class CreateZipForDownload extends JobShouldQueue
|
|||
{
|
||||
$zip_archive = new ZipArchive();
|
||||
|
||||
$folder_path = 'app/temp/' . company_id() . '/bulk_actions/';
|
||||
$folder_path = 'app' . DIRECTORY_SEPARATOR . 'temp' . DIRECTORY_SEPARATOR . company_id() . DIRECTORY_SEPARATOR . 'bulk_actions' . DIRECTORY_SEPARATOR;
|
||||
|
||||
File::ensureDirectoryExists(storage_path($folder_path));
|
||||
File::ensureDirectoryExists(get_storage_path($folder_path));
|
||||
|
||||
$zip_path = get_storage_path($folder_path . $this->file_name . '.zip');
|
||||
$zip_path = storage_path($folder_path . $this->file_name . '.zip');
|
||||
|
||||
$zip_archive->open($zip_path, ZipArchive::CREATE | ZipArchive::OVERWRITE);
|
||||
|
||||
$total = count($this->selected);
|
||||
$current = 0;
|
||||
|
||||
foreach ($this->selected as $selected) {
|
||||
$current++;
|
||||
|
||||
if ($current === $total) {
|
||||
$this->dispatch(new $this->class($selected, $folder_path, $zip_archive, true));
|
||||
} else {
|
||||
$this->dispatch(new $this->class($selected, $folder_path, $zip_archive));
|
||||
}
|
||||
$pdf_path = $this->dispatch(new $this->class($selected, $folder_path));
|
||||
|
||||
$fileContent = $this->getQueuedFile($pdf_path);
|
||||
|
||||
$zip_archive->addFromString(basename($pdf_path), $fileContent);
|
||||
|
||||
/*
|
||||
Storage::disk('local')->put($folder_path . basename($pdf_path), $fileContent);
|
||||
|
||||
$zip->addFile(storage_path($folder_path . basename($pdf_path)), basename($pdf_path));
|
||||
*/
|
||||
}
|
||||
|
||||
$zip_archive->close();
|
||||
|
||||
$this->copyQueuedFile($folder_path, $zip_path);
|
||||
|
||||
return $zip_path;
|
||||
}
|
||||
|
||||
public function getQueuedFile($pdf_path)
|
||||
{
|
||||
return config('dompdf.disk') !== null
|
||||
? $this->getRemoteQueuedMedia($pdf_path)
|
||||
: $this->getLocalQueuedMedia($pdf_path);
|
||||
}
|
||||
|
||||
public function getLocalQueuedMedia($pdf_path)
|
||||
{
|
||||
$content = File::get($pdf_path);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
public function getRemoteQueuedMedia($pdf_path)
|
||||
{
|
||||
$disk = config('dompdf.disk');
|
||||
|
||||
$content = Storage::disk($disk)->get($pdf_path);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
public function copyQueuedFile($folder_path, $zip_path)
|
||||
{
|
||||
return config('dompdf.disk') !== null
|
||||
? $this->copyRemoteQueuedMedia($folder_path, $zip_path)
|
||||
: true;
|
||||
}
|
||||
|
||||
public function copyRemoteQueuedMedia($folder_path, $zip_path)
|
||||
{
|
||||
$disk = config('dompdf.disk');
|
||||
|
||||
$file_path = get_storage_path($folder_path . basename($zip_path));
|
||||
|
||||
$content = Storage::disk($disk)->put($file_path, fopen($zip_path, 'r+'));
|
||||
|
||||
report($file_path);
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,10 +28,14 @@ class UpdateContact extends Job implements ShouldUpdate
|
|||
|
||||
// Upload logo
|
||||
if ($this->request->file('logo')) {
|
||||
$this->deleteMediaModel($this->model, 'logo', $this->request);
|
||||
|
||||
$media = $this->getMedia($this->request->file('logo'), Str::plural($this->model->type));
|
||||
|
||||
$this->model->attachMedia($media, 'logo');
|
||||
} elseif (! $this->request->file('logo') && $this->model->logo) {
|
||||
} elseif ($this->request->isNotApi() && ! $this->request->file('logo') && $this->model->logo) {
|
||||
$this->deleteMediaModel($this->model, 'logo', $this->request);
|
||||
} elseif ($this->request->isApi() && $this->request->has('remove_logo') && $this->model->logo) {
|
||||
$this->deleteMediaModel($this->model, 'logo', $this->request);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,9 +20,15 @@ class UpdateItem extends Job implements ShouldUpdate
|
|||
|
||||
// Upload picture
|
||||
if ($this->request->file('picture')) {
|
||||
$this->deleteMediaModel($this->model, 'picture', $this->request);
|
||||
|
||||
$media = $this->getMedia($this->request->file('picture'), 'items');
|
||||
|
||||
$this->model->attachMedia($media, 'picture');
|
||||
} elseif ($this->request->isNotApi() && ! $this->request->file('picture') && $this->model->picture) {
|
||||
$this->deleteMediaModel($this->model, 'picture', $this->request);
|
||||
} elseif ($this->request->isApi() && $this->request->has('remove_picture') && $this->model->picture) {
|
||||
$this->deleteMediaModel($this->model, 'picture', $this->request);
|
||||
}
|
||||
|
||||
$this->deleteRelationships($this->model, ['taxes']);
|
||||
|
|
|
|||
|
|
@ -25,6 +25,11 @@ class CreateDocument extends Job implements HasOwner, HasSource, ShouldCreate
|
|||
$this->request['amount'] = 0;
|
||||
}
|
||||
|
||||
// Disable this lines for global discount issue fixed ( https://github.com/akaunting/akaunting/issues/2797 )
|
||||
if (! empty($this->request['discount'])) {
|
||||
$this->request['discount_rate'] = $this->request['discount'];
|
||||
}
|
||||
|
||||
event(new DocumentCreating($this->request));
|
||||
|
||||
\DB::transaction(function () {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ class CreateDocumentItem extends Job implements HasOwner, HasSource, ShouldCreat
|
|||
$item_id = ! empty($this->request['item_id']) ? $this->request['item_id'] : 0;
|
||||
$precision = currency($this->document->currency_code)->getPrecision();
|
||||
|
||||
$item_amount = (double) $this->request['price'] * (double) $this->request['quantity'];
|
||||
$item_amount = (double) $this->request['price'] * (double) calculation_to_quantity($this->request['quantity']);
|
||||
|
||||
$item_discounted_amount = $item_amount;
|
||||
|
||||
|
|
@ -47,10 +47,12 @@ class CreateDocumentItem extends Job implements HasOwner, HasSource, ShouldCreat
|
|||
// Apply total discount to amount
|
||||
if (! empty($this->request['global_discount'])) {
|
||||
if ($this->request['global_discount_type'] === 'percentage') {
|
||||
$item_discounted_amount -= $item_discounted_amount * ($this->request['global_discount'] / 100);
|
||||
$global_discount = $item_discounted_amount * ($this->request['global_discount'] / 100);
|
||||
} else {
|
||||
$item_discounted_amount -= $this->request['global_discount'];
|
||||
$global_discount = $this->request['global_discount'];
|
||||
}
|
||||
|
||||
$item_discounted_amount -= $global_discount;
|
||||
}
|
||||
|
||||
$tax_amount = 0;
|
||||
|
|
@ -153,13 +155,19 @@ class CreateDocumentItem extends Job implements HasOwner, HasSource, ShouldCreat
|
|||
}
|
||||
}
|
||||
|
||||
if (! empty($global_discount)) {
|
||||
$actual_price_item += $global_discount;
|
||||
$item_amount += $global_discount;
|
||||
$item_discounted_amount += $global_discount;
|
||||
}
|
||||
|
||||
$this->request['company_id'] = $this->document->company_id;
|
||||
$this->request['type'] = $this->document->type;
|
||||
$this->request['document_id'] = $this->document->id;
|
||||
$this->request['item_id'] = $item_id;
|
||||
$this->request['name'] = Str::limit($this->request['name'], 180, '');
|
||||
$this->request['description'] = ! empty($this->request['description']) ? $this->request['description'] : '';
|
||||
$this->request['quantity'] = (double) $this->request['quantity'];
|
||||
$this->request['quantity'] = (double) calculation_to_quantity($this->request['quantity']);
|
||||
$this->request['price'] = round($this->request['price'], $precision);
|
||||
$this->request['tax'] = round($item_tax_total, $precision);
|
||||
$this->request['discount_type'] = ! empty($this->request['discount_type']) ? $this->request['discount_type'] : 'percent';
|
||||
|
|
|
|||
|
|
@ -70,7 +70,8 @@ class CreateDocumentItemsAndTotals extends Job implements HasOwner, HasSource, S
|
|||
|
||||
if (! empty($this->request['discount'])) {
|
||||
if ($this->request['discount_type'] === 'percentage') {
|
||||
$discount_total = ($sub_total - $discount_amount_total) * ($this->request['discount'] / 100);
|
||||
//$discount_total = ($sub_total - $discount_amount_total) * ($this->request['discount'] / 100);
|
||||
$discount_total = $sub_total * ($this->request['discount'] / 100);
|
||||
} else {
|
||||
$discount_total = $this->request['discount'];
|
||||
}
|
||||
|
|
@ -173,10 +174,11 @@ class CreateDocumentItemsAndTotals extends Job implements HasOwner, HasSource, S
|
|||
foreach ((array) $this->request['items'] as $key => $item) {
|
||||
$item['global_discount'] = 0;
|
||||
|
||||
// Disable this lines for global discount issue fixed ( https://github.com/akaunting/akaunting/issues/2797 )
|
||||
if (! empty($this->request['discount'])) {
|
||||
if (isset($for_fixed_discount)) {
|
||||
$item['global_discount'] = ($for_fixed_discount[$key] / ($for_fixed_discount['total'] / 100)) * ($this->request['discount'] / 100);
|
||||
$item['global_discount_type'] = '';
|
||||
$item['global_discount_type'] = $this->request['discount_type'];
|
||||
} else {
|
||||
$item['global_discount'] = $this->request['discount'];
|
||||
$item['global_discount_type'] = $this->request['discount_type'];
|
||||
|
|
@ -209,7 +211,9 @@ class CreateDocumentItemsAndTotals extends Job implements HasOwner, HasSource, S
|
|||
|
||||
$document_item = $this->dispatch(new CreateDocumentItem($this->document, $item));
|
||||
|
||||
$item_amount = (double) $item['price'] * (double) $item['quantity'];
|
||||
# This line changed for discount calcualter issue
|
||||
//$item_amount = (double) $item['price'] * (double) $item['quantity'];
|
||||
$item_amount = $document_item->total;
|
||||
|
||||
$discount_amount = 0;
|
||||
|
||||
|
|
@ -234,16 +238,25 @@ class CreateDocumentItemsAndTotals extends Job implements HasOwner, HasSource, S
|
|||
// Set taxes
|
||||
foreach ((array) $document_item->item_taxes as $item_tax) {
|
||||
if (array_key_exists($item_tax['tax_id'], $taxes)) {
|
||||
$taxes[$item_tax['tax_id']]['amount'] += $item_tax['amount'];
|
||||
$taxes[$item_tax['tax_id']]['amount'] += round((float) $item_tax['amount'], $this->document->currency->precision);
|
||||
} else {
|
||||
$taxes[$item_tax['tax_id']] = [
|
||||
'name' => $item_tax['name'],
|
||||
'amount' => $item_tax['amount'],
|
||||
'amount' => round((float) $item_tax['amount'], $this->document->currency->precision),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Disable this lines for global discount issue fixed ( https://github.com/akaunting/akaunting/issues/2797 )
|
||||
if (! empty($this->request['discount'])) {
|
||||
if ($this->request['discount_type'] === 'percentage') {
|
||||
$actual_total -= ($sub_total * ($this->request['discount'] / 100));
|
||||
} else {
|
||||
$actual_total -= $this->request['discount'];
|
||||
}
|
||||
}
|
||||
|
||||
return [$sub_total, $actual_total, $discount_amount_total, $taxes];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -49,28 +49,20 @@ class DownloadDocument extends Job
|
|||
|
||||
switch ($this->method) {
|
||||
case 'download':
|
||||
return $pdf->download($file_name);
|
||||
$response = $pdf->download($file_name);
|
||||
|
||||
break;
|
||||
default:
|
||||
if (empty($this->zip_archive)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$pdf_path = get_storage_path($this->folder_path . $file_name);
|
||||
|
||||
// Save the PDF file into temp folder
|
||||
$pdf->save($pdf_path);
|
||||
|
||||
$this->zip_archive->addFile($pdf_path, $file_name);
|
||||
|
||||
if ($this->close_zip) {
|
||||
$this->zip_archive->close();
|
||||
}
|
||||
|
||||
return;
|
||||
$response = $pdf_path;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
|
@ -18,13 +18,21 @@ class UpdateDocument extends Job implements ShouldUpdate
|
|||
|
||||
public function handle(): Document
|
||||
{
|
||||
$this->authorize();
|
||||
|
||||
if (empty($this->request['amount'])) {
|
||||
$this->request['amount'] = 0;
|
||||
}
|
||||
|
||||
// Disable this lines for global discount issue fixed ( https://github.com/akaunting/akaunting/issues/2797 )
|
||||
$this->request['discount_rate'] = $this->request['discount'] ?? null;
|
||||
|
||||
event(new DocumentUpdating($this->model, $this->request));
|
||||
|
||||
\DB::transaction(function () {
|
||||
// Track original contact_id to sync transactions if it changes
|
||||
$originalContactId = $this->model->contact_id;
|
||||
|
||||
\DB::transaction(function () use ($originalContactId) {
|
||||
// Upload attachment
|
||||
if ($this->request->file('attachment')) {
|
||||
$this->deleteMediaModel($this->model, 'attachment', $this->request);
|
||||
|
|
@ -34,7 +42,9 @@ class UpdateDocument extends Job implements ShouldUpdate
|
|||
|
||||
$this->model->attachMedia($media, 'attachment');
|
||||
}
|
||||
} elseif (! $this->request->file('attachment') && $this->model->attachment) {
|
||||
} elseif ($this->request->isNotApi() && ! $this->request->file('attachment') && $this->model->attachment) {
|
||||
$this->deleteMediaModel($this->model, 'attachment', $this->request);
|
||||
} elseif ($this->request->isApi() && $this->request->has('remove_attachment') && $this->model->attachment) {
|
||||
$this->deleteMediaModel($this->model, 'attachment', $this->request);
|
||||
}
|
||||
|
||||
|
|
@ -61,6 +71,13 @@ class UpdateDocument extends Job implements ShouldUpdate
|
|||
|
||||
$this->model->update($this->request->all());
|
||||
|
||||
// Sync transaction contact_id if document contact changed
|
||||
if (isset($this->request['contact_id']) && $originalContactId != $this->request['contact_id']) {
|
||||
$this->model->transactions()->update([
|
||||
'contact_id' => $this->request['contact_id'],
|
||||
]);
|
||||
}
|
||||
|
||||
$this->model->updateRecurring($this->request->all());
|
||||
});
|
||||
|
||||
|
|
@ -68,4 +85,23 @@ class UpdateDocument extends Job implements ShouldUpdate
|
|||
|
||||
return $this->model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this action is applicable.
|
||||
*/
|
||||
public function authorize(): void
|
||||
{
|
||||
$lockedStatuses = ['sent', 'received', 'viewed', 'partial', 'paid', 'overdue', 'unpaid', 'cancelled'];
|
||||
|
||||
if (
|
||||
isset($this->request['contact_id']) &&
|
||||
(int) $this->request['contact_id'] !== (int) $this->model->contact_id &&
|
||||
in_array($this->model->status, $lockedStatuses)
|
||||
) {
|
||||
$type = Str::plural($this->model->type);
|
||||
$message = trans('messages.warning.contact_change', ['type' => trans_choice("general.$type", 1)]);
|
||||
|
||||
throw new \Exception($message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ class AddCustomers extends Listener
|
|||
protected $classes = [
|
||||
'App\Reports\IncomeSummary',
|
||||
'App\Reports\IncomeExpenseSummary',
|
||||
'App\Reports\DiscountSummary',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ class AddDate extends Listener
|
|||
'App\Reports\IncomeExpenseSummary',
|
||||
'App\Reports\ProfitLoss',
|
||||
'App\Reports\TaxSummary',
|
||||
'App\Reports\DiscountSummary',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
namespace App\Listeners\Report;
|
||||
|
||||
use App\Abstracts\Listeners\Report as Listener;
|
||||
use App\Events\Report\FilterApplying;
|
||||
use App\Events\Report\FilterShowing;
|
||||
|
||||
class AddDiscount extends Listener
|
||||
{
|
||||
protected $classes = [
|
||||
'App\Reports\DiscountSummary',
|
||||
];
|
||||
|
||||
/**
|
||||
* Handle filter showing event.
|
||||
*
|
||||
* @param $event
|
||||
* @return void
|
||||
*/
|
||||
public function handleFilterShowing(FilterShowing $event)
|
||||
{
|
||||
if ($this->skipThisClass($event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event->class->filters['discounts'] = $this->getDiscount();
|
||||
$event->class->filters['keys']['discounts'] = 'discount';
|
||||
$event->class->filters['defaults']['discounts'] = 'both';
|
||||
$event->class->filters['operators']['discounts'] = [
|
||||
'equal' => true,
|
||||
'not_equal' => false,
|
||||
'range' => false,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle filter applying event.
|
||||
*
|
||||
* @param $event
|
||||
* @return void
|
||||
*/
|
||||
public function handleFilterApplying(FilterApplying $event)
|
||||
{
|
||||
if ($this->skipThisClass($event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Apply discount filter
|
||||
$this->applyDiscountFilter($event);
|
||||
}
|
||||
}
|
||||
|
|
@ -26,8 +26,8 @@ class AddExpenseCategories extends Listener
|
|||
}
|
||||
|
||||
// send true for add limit on search and filter..
|
||||
$event->class->filters['categories'] = $this->getExpenseCategories(true);
|
||||
$event->class->filters['routes']['categories'] = ['categories.index', 'search=type:expense enabled:1'];
|
||||
$event->class->filters['categories'] = $this->getExpenseCategories();
|
||||
$event->class->filters['routes']['categories'] = ['categories.index', 'search=type:' . $this->getExpenseCategoryTypes('string') . ' enabled:1'];
|
||||
$event->class->filters['multiple']['categories'] = true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,8 +26,8 @@ class AddIncomeCategories extends Listener
|
|||
}
|
||||
|
||||
// send true for add limit on search and filter..
|
||||
$event->class->filters['categories'] = $this->getIncomeCategories(true);
|
||||
$event->class->filters['routes']['categories'] = ['categories.index', 'search=type:income enabled:1'];
|
||||
$event->class->filters['categories'] = $this->getIncomeCategories();
|
||||
$event->class->filters['routes']['categories'] = ['categories.index', 'search=type:' . $this->getIncomeCategoryTypes('string') . ' enabled:1'];
|
||||
$event->class->filters['multiple']['categories'] = true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,14 +20,17 @@ class AddIncomeExpenseCategories extends Listener
|
|||
{
|
||||
$classes = [
|
||||
'App\Reports\IncomeExpenseSummary',
|
||||
'App\Reports\DiscountSummary',
|
||||
];
|
||||
|
||||
if (empty($event->class) || !in_array(get_class($event->class), $classes)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event->class->filters['categories'] = $this->getIncomeExpenseCategories(true);
|
||||
$event->class->filters['routes']['categories'] = ['categories.index', 'search=type:income,expense enabled:1'];
|
||||
$types = array_merge($this->getIncomeCategoryTypes(), $this->getExpenseCategoryTypes());
|
||||
|
||||
$event->class->filters['categories'] = $this->getIncomeExpenseCategories();
|
||||
$event->class->filters['routes']['categories'] = ['categories.index', 'search=type:' . implode(',', $types) . ' enabled:1'];
|
||||
$event->class->filters['multiple']['categories'] = true;
|
||||
}
|
||||
|
||||
|
|
@ -42,6 +45,7 @@ class AddIncomeExpenseCategories extends Listener
|
|||
$classes = [
|
||||
'App\Reports\IncomeExpenseSummary',
|
||||
'App\Reports\ProfitLoss',
|
||||
'App\Reports\DiscountSummary',
|
||||
];
|
||||
|
||||
if (empty($event->class) || !in_array(get_class($event->class), $classes)) {
|
||||
|
|
@ -67,7 +71,7 @@ class AddIncomeExpenseCategories extends Listener
|
|||
return;
|
||||
}
|
||||
|
||||
$categories = Category::type(['income', 'expense'])->orderBy('name')->get();
|
||||
$categories = Category::type(array_merge($this->getIncomeCategoryTypes(), $this->getExpenseCategoryTypes()))->orderBy('name')->get();
|
||||
$rows = $categories->pluck('name', 'id')->toArray();
|
||||
|
||||
$this->setRowNamesAndValuesForCategories($event, $rows, $categories);
|
||||
|
|
@ -81,10 +85,12 @@ class AddIncomeExpenseCategories extends Listener
|
|||
{
|
||||
foreach ($event->class->dates as $date) {
|
||||
foreach ($event->class->tables as $table_key => $table_name) {
|
||||
$table_keys = $table_key == Category::INCOME_TYPE ? $this->getIncomeCategoryTypes() : $this->getExpenseCategoryTypes();
|
||||
|
||||
foreach ($rows as $id => $name) {
|
||||
$category = $categories->where('id', $id)->first();
|
||||
|
||||
if ($category->type != $table_key) {
|
||||
if (!in_array($category->type, $table_keys)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -98,10 +104,12 @@ class AddIncomeExpenseCategories extends Listener
|
|||
public function setTreeNodesForCategories($event, $nodes, $categories)
|
||||
{
|
||||
foreach ($event->class->tables as $table_key => $table_name) {
|
||||
$table_keys = $table_key == Category::INCOME_TYPE ? $this->getIncomeCategoryTypes() : $this->getExpenseCategoryTypes();
|
||||
|
||||
foreach ($nodes as $id => $node) {
|
||||
$category = $categories->where('id', $id)->first();
|
||||
|
||||
if ($category->type != $table_key) {
|
||||
if (!in_array($category->type, $table_keys)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ class AddPeriod extends Listener
|
|||
'App\Reports\IncomeExpenseSummary',
|
||||
'App\Reports\ProfitLoss',
|
||||
'App\Reports\TaxSummary',
|
||||
'App\Reports\DiscountSummary',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ class AddSearchString extends Listener
|
|||
'App\Reports\IncomeExpenseSummary',
|
||||
'App\Reports\ProfitLoss',
|
||||
'App\Reports\TaxSummary',
|
||||
'App\Reports\DiscountSummary',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -27,8 +28,8 @@ class AddSearchString extends Listener
|
|||
return;
|
||||
}
|
||||
|
||||
$old = old();
|
||||
$request = request()->all();
|
||||
$old = old() ?? [];
|
||||
$request = request()->all() ?? [];
|
||||
|
||||
if ($old || $request) {
|
||||
$input = request('search');
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ class AddVendors extends Listener
|
|||
protected $classes = [
|
||||
'App\Reports\ExpenseSummary',
|
||||
'App\Reports\IncomeExpenseSummary',
|
||||
'App\Reports\DiscountSummary',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace App\Listeners\Update\V31;
|
||||
|
||||
use App\Abstracts\Listeners\Update as Listener;
|
||||
use App\Events\Install\UpdateFinished as Event;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class Version3115 extends Listener
|
||||
{
|
||||
const ALIAS = 'core';
|
||||
|
||||
const VERSION = '3.1.15';
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param $event
|
||||
* @return void
|
||||
*/
|
||||
public function handle(Event $event)
|
||||
{
|
||||
if ($this->skipThisUpdate($event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log::channel('stdout')->info('Updating to 3.1.15 version...');
|
||||
|
||||
$this->updateDatabase();
|
||||
|
||||
Log::channel('stdout')->info('Done!');
|
||||
}
|
||||
|
||||
public function updateDatabase(): void
|
||||
{
|
||||
Log::channel('stdout')->info('Updating database...');
|
||||
|
||||
Artisan::call('migrate', ['--force' => true]);
|
||||
|
||||
Log::channel('stdout')->info('Database updated.');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
namespace App\Listeners\Update\V31;
|
||||
|
||||
use App\Abstracts\Listeners\Update as Listener;
|
||||
use App\Events\Install\UpdateFinished as Event;
|
||||
use App\Traits\Permissions;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class Version3119 extends Listener
|
||||
{
|
||||
use Permissions;
|
||||
|
||||
const ALIAS = 'core';
|
||||
|
||||
const VERSION = '3.1.19';
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param $event
|
||||
* @return void
|
||||
*/
|
||||
public function handle(Event $event)
|
||||
{
|
||||
if ($this->skipThisUpdate($event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log::channel('stdout')->info('Updating to 3.1.19 version...');
|
||||
|
||||
$this->updatePermissions();
|
||||
|
||||
Log::channel('stdout')->info('Done!');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update permissions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function updatePermissions()
|
||||
{
|
||||
$rows = [
|
||||
'admin' => [
|
||||
'reports-discount-summary' => 'r'
|
||||
],
|
||||
'manager' => [
|
||||
'reports-discount-summary' => 'r'
|
||||
],
|
||||
'accountant' => [
|
||||
'reports-discount-summary' => 'r'
|
||||
],
|
||||
];
|
||||
|
||||
$this->attachPermissionsByRoleNames($rows);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,12 +5,13 @@ namespace App\Models\Banking;
|
|||
use App\Abstracts\Model;
|
||||
use App\Models\Banking\Transaction;
|
||||
use App\Traits\Currencies;
|
||||
use App\Traits\Transactions;
|
||||
use Bkwld\Cloner\Cloneable;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class TransactionTax extends Model
|
||||
{
|
||||
use Cloneable, Currencies;
|
||||
use Cloneable, Currencies, Transactions;
|
||||
|
||||
protected $table = 'transaction_taxes';
|
||||
|
||||
|
|
@ -31,34 +32,92 @@ class TransactionTax extends Model
|
|||
return $this->belongsTo('App\Models\Banking\Transaction')->withDefault(['name' => trans('general.na')]);
|
||||
}
|
||||
|
||||
public function scopeType(Builder $query, string $type)
|
||||
public function scopeType(Builder $query, $types): Builder
|
||||
{
|
||||
return $query->where($this->qualifyColumn('type'), '=', $type);
|
||||
if (empty($types)) {
|
||||
return $query;
|
||||
}
|
||||
|
||||
return $query->whereIn($this->qualifyColumn('type'), (array) $types);
|
||||
}
|
||||
|
||||
public function scopeIncome(Builder $query)
|
||||
{
|
||||
return $query->where($this->qualifyColumn('type'), '=', Transaction::INCOME_TYPE);
|
||||
return $query->whereIn($this->qualifyColumn('type'), (array) $this->getIncomeTypes());
|
||||
}
|
||||
|
||||
public function scopeIncomeTransfer(Builder $query): Builder
|
||||
{
|
||||
return $query->where($this->qualifyColumn('type'), '=', Transaction::INCOME_TRANSFER_TYPE);
|
||||
}
|
||||
|
||||
public function scopeIncomeRecurring(Builder $query): Builder
|
||||
{
|
||||
return $query->where($this->qualifyColumn('type'), '=', Transaction::INCOME_RECURRING_TYPE)
|
||||
->whereHas('document.recurring', function (Builder $query) {
|
||||
$query->whereNull('deleted_at');
|
||||
});
|
||||
->whereHas('transaction.recurring', function (Builder $query) {
|
||||
$query->whereNull('deleted_at');
|
||||
});
|
||||
}
|
||||
|
||||
public function scopeExpense(Builder $query)
|
||||
{
|
||||
return $query->where($this->qualifyColumn('type'), '=', Transaction::EXPENSE_TYPE);
|
||||
return $query->whereIn($this->qualifyColumn('type'), (array) $this->getExpenseTypes());
|
||||
}
|
||||
|
||||
public function scopeExpenseTransfer(Builder $query): Builder
|
||||
{
|
||||
return $query->where($this->qualifyColumn('type'), '=', Transaction::EXPENSE_TRANSFER_TYPE);
|
||||
}
|
||||
|
||||
public function scopeExpenseRecurring(Builder $query): Builder
|
||||
{
|
||||
return $query->where($this->qualifyColumn('type'), '=', Transaction::EXPENSE_RECURRING_TYPE)
|
||||
->whereHas('document.recurring', function (Builder $query) {
|
||||
$query->whereNull('deleted_at');
|
||||
});
|
||||
->whereHas('transaction.recurring', function (Builder $query) {
|
||||
$query->whereNull('deleted_at');
|
||||
});
|
||||
}
|
||||
|
||||
public function scopeIsTransfer(Builder $query): Builder
|
||||
{
|
||||
return $query->where($this->qualifyColumn('type'), 'like', '%-transfer');
|
||||
}
|
||||
|
||||
public function scopeIsNotTransfer(Builder $query): Builder
|
||||
{
|
||||
return $query->where($this->qualifyColumn('type'), 'not like', '%-transfer');
|
||||
}
|
||||
|
||||
public function scopeIsRecurring(Builder $query): Builder
|
||||
{
|
||||
return $query->where($this->qualifyColumn('type'), 'like', '%-recurring');
|
||||
}
|
||||
|
||||
public function scopeIsNotRecurring(Builder $query): Builder
|
||||
{
|
||||
return $query->where($this->qualifyColumn('type'), 'not like', '%-recurring');
|
||||
}
|
||||
|
||||
public function scopeIsSplit(Builder $query): Builder
|
||||
{
|
||||
return $query->where($this->qualifyColumn('type'), 'like', '%-split');
|
||||
}
|
||||
|
||||
public function scopeIsNotSplit(Builder $query): Builder
|
||||
{
|
||||
return $query->where($this->qualifyColumn('type'), 'not like', '%-split');
|
||||
}
|
||||
|
||||
public function scopeIsDocument(Builder $query): Builder
|
||||
{
|
||||
return $query->whereHas('transaction', function ($q) {
|
||||
$q->whereNotNull('document_id');
|
||||
});
|
||||
}
|
||||
|
||||
public function scopeIsNotDocument(Builder $query): Builder
|
||||
{
|
||||
return $query->whereHas('transaction', function ($q) {
|
||||
$q->whereNull('document_id');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue