Merge branch 'master' into title-subheading

This commit is contained in:
Cüneyt Şentürk 2023-07-19 14:43:19 +03:00 committed by GitHub
commit 797ee71f6b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
628 changed files with 65799 additions and 80258 deletions

View File

@ -1,10 +1,11 @@
name: Tests
on:
push:
push:
pull_request:
schedule:
- cron: '0 0 * * *'
workflow_dispatch:
jobs:
tests:
@ -15,11 +16,11 @@ jobs:
strategy:
matrix:
php: ['8.0', '8.1']
php: ['8.1', '8.2']
steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Cache Composer
uses: actions/cache@v1

View File

@ -3,24 +3,24 @@
[![Release](https://img.shields.io/github/v/release/akaunting/akaunting?label=release)](https://github.com/akaunting/akaunting/releases)
![Downloads](https://img.shields.io/github/downloads/akaunting/akaunting/total?label=downloads)
[![Translations](https://badges.crowdin.net/akaunting/localized.svg)](https://crowdin.com/project/akaunting)
[![Tests](https://img.shields.io/github/workflow/status/akaunting/akaunting/Tests?label=tests)](https://github.com/akaunting/akaunting/actions)
[![Tests](https://img.shields.io/github/actions/workflow/status/akaunting/akaunting/tests.yml?label=tests)](https://github.com/akaunting/akaunting/actions)
[![License](https://img.shields.io/github/license/akaunting/akaunting?label=license)](LICENSE.txt)
Akaunting is a free, open source and online accounting software designed for small businesses and freelancers. It is built with modern technologies such as Laravel, VueJS, Tailwind, RESTful API etc. Thanks to its modular structure, Akaunting provides an awesome App Store for users and developers.
* [Home](https://akaunting.com) - The house of Akaunting
* [Forum](https://akaunting.com/forum) - Ask for support
* [Documentation](https://akaunting.com/docs) - Learn how to use
* [Documentation](https://akaunting.com/hc/docs) - Learn how to use
* [Developer Portal](https://developer.akaunting.com) - Generate passive income
* [App Store](https://akaunting.com/apps) - Extend your Akaunting
* [Translations](https://crowdin.com/project/akaunting) - Help us translate Akaunting
## Requirements
* PHP 8.0 or higher
* PHP 8.1 or higher
* Database (eg: MySQL, PostgreSQL, SQLite)
* Web Server (eg: Apache, Nginx, IIS)
* [Other libraries](https://akaunting.com/docs/requirements)
* [Other libraries](https://akaunting.com/hc/docs/on-premise/requirements/)
## Framework

View File

@ -15,9 +15,10 @@ use Maatwebsite\Excel\Concerns\ShouldAutoSize;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
use Maatwebsite\Excel\Concerns\WithTitle;
use Maatwebsite\Excel\Concerns\WithStrictNullComparison;
use PhpOffice\PhpSpreadsheet\Shared\Date as ExcelDate;
abstract class Export implements FromCollection, HasLocalePreference, ShouldAutoSize, ShouldQueue, WithHeadings, WithMapping, WithTitle
abstract class Export implements FromCollection, HasLocalePreference, ShouldAutoSize, ShouldQueue, WithHeadings, WithMapping, WithTitle, WithStrictNullComparison
{
use Exportable;
@ -48,13 +49,18 @@ abstract class Export implements FromCollection, HasLocalePreference, ShouldAuto
{
$map = [];
$date_fields = ['paid_at', 'invoiced_at', 'billed_at', 'due_at', 'issued_at', 'created_at', 'transferred_at'];
$date_fields = ['paid_at', 'invoiced_at', 'billed_at', 'due_at', 'issued_at', 'transferred_at'];
$evil_chars = ['=', '+', '-', '@'];
foreach ($this->fields as $field) {
$value = $model->$field;
// created_by is equal to the owner id. Therefore, the value in export is owner email.
if ($field == 'created_by') {
$value = $model->owner->email ?? null;
}
if (in_array($field, $date_fields)) {
$value = ExcelDate::PHPToExcel(Date::parse($value)->format('Y-m-d'));
}

View File

@ -2,6 +2,7 @@
namespace App\Abstracts;
use App\Abstracts\Http\FormRequest;
use App\Traits\Import as ImportHelper;
use App\Traits\Sources;
use App\Utilities\Date;
@ -11,6 +12,7 @@ use Illuminate\Contracts\Translation\HasLocalePreference;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use Maatwebsite\Excel\Concerns\Importable;
use Maatwebsite\Excel\Concerns\SkipsEmptyRows;
use Maatwebsite\Excel\Concerns\ToModel;
@ -27,6 +29,8 @@ abstract class Import implements HasLocalePreference, ShouldQueue, SkipsEmptyRow
public $user;
public $request_class = null;
public function __construct()
{
$this->user = user();
@ -35,7 +39,12 @@ abstract class Import implements HasLocalePreference, ShouldQueue, SkipsEmptyRow
public function map($row): array
{
$row['company_id'] = company_id();
$row['created_by'] = $this->user->id;
// created_by is equal to the owner id. Therefore, the value in export is owner email.
if (isset($row['created_by'])) {
$row['created_by'] = $this->getCreatedById($row);
}
$row['created_from'] = $this->getSourcePrefix() . 'import';
// Make enabled field integer
@ -48,7 +57,7 @@ abstract class Import implements HasLocalePreference, ShouldQueue, SkipsEmptyRow
$row['reconciled'] = (int) $row['reconciled'];
}
$date_fields = ['paid_at', 'invoiced_at', 'billed_at', 'due_at', 'issued_at', 'created_at', 'transferred_at'];
$date_fields = ['paid_at', 'invoiced_at', 'billed_at', 'due_at', 'issued_at', 'transferred_at'];
foreach ($date_fields as $date_field) {
if (!isset($row[$date_field])) {
continue;
@ -70,6 +79,49 @@ abstract class Import implements HasLocalePreference, ShouldQueue, SkipsEmptyRow
return [];
}
/**
* You can override this method to add custom rules for each row.
*/
public function prepareRules(array $rules): array
{
return $rules;
}
/**
* Validate each row data.
*
* @param \Illuminate\Validation\Validator $validator
* @throws ValidationException
*/
public function withValidator($validator)
{
$condition = class_exists($this->request_class)
? ! ($request = new $this->request_class) instanceof FormRequest
: true;
if ($condition) {
return;
}
foreach ($validator->getData() as $row => $data) {
$request->initialize(request: $data);
$rules = $this->prepareRules($request->rules());
try {
Validator::make($data, $rules)->validate();
} catch (ValidationException $e) {
foreach ($e->validator->failed() as $attribute => $value) {
foreach ($value as $rule => $params) {
$validator->addFailure($row . '.' . $attribute, $rule, $params);
}
}
throw new ValidationException($validator);
}
}
}
public function chunkSize(): int
{
return config('excel.imports.chunk_size');

View File

@ -20,6 +20,6 @@ abstract class ImportMultipleSheets implements ShouldQueue, WithChunkReading, Wi
public function chunkSize(): int
{
return 100;
return config('excel.imports.chunk_size');
}
}

View File

@ -22,10 +22,10 @@ abstract class Model extends Eloquent implements Ownable
protected $tenantable = true;
protected $dates = ['deleted_at'];
protected $casts = [
'enabled' => 'boolean',
'amount' => 'double',
'enabled' => 'boolean',
'deleted_at' => 'datetime',
];
public $allAttributes = [];
@ -105,7 +105,7 @@ abstract class Model extends Eloquent implements Ownable
/**
* Modules that use the sort parameter in CRUD operations cause an error,
* so this sort parameter set back to old value after the query is executed.
*
*
* for Custom Fields module
*/
$request_sort = $request->get('sort');
@ -117,7 +117,8 @@ abstract class Model extends Eloquent implements Ownable
}
$request->merge(['sort' => $request_sort]);
$request->offsetUnset('direction');
// This line disabled because broken sortable issue.
//$request->offsetUnset('direction');
$limit = (int) $request->get('limit', setting('default.list_limit', '25'));
return $query->paginate($limit);

View File

@ -222,7 +222,9 @@ abstract class Report
foreach ($tmp_values as $id => $value) {
$labels[$id] = $this->row_names[$table_key][$id];
$colors[$id] = ($group == 'category') ? Category::withSubCategory()->find($id)?->colorHexCode : '#' . dechex(rand(0x000000, 0xFFFFFF));
$colors[$id] = ($group == 'category')
? Category::withSubCategory()->find($id)?->colorHexCode
: $this->randHexColor();
$values[$id] = round(($value * 100 / $total), 0);
}
@ -642,4 +644,14 @@ abstract class Report
],
];
}
public function randHexColorPart(): string
{
return str_pad( dechex( mt_rand( 0, 255 ) ), 2, '0', STR_PAD_LEFT);
}
public function randHexColor(): string
{
return '#' . $this->randHexColorPart() . $this->randHexColorPart() . $this->randHexColorPart();
}
}

View File

@ -283,8 +283,8 @@ abstract class Index extends Component
$items[] = [
'title' => ($key == 'overdue') ? trans('general.overdue') : trans('documents.statuses.' . $key),
//'href' => route($route, ['search' => 'status:' . $key]),
'amount' => money($total, default_currency(), true)->formatForHumans(),
'tooltip' => money($total, default_currency(), true)->format(),
'amount' => money($total)->formatForHumans(),
'tooltip' => money($total)->format(),
];
}

View File

@ -3,6 +3,7 @@
namespace App\Abstracts\View\Components\Documents;
use App\Abstracts\View\Component;
use App\Interfaces\Utility\DocumentNumber;
use App\Models\Common\Contact;
use App\Models\Document\Document;
use App\Models\Setting\Currency;
@ -163,30 +164,45 @@ abstract class Form extends Component
/** @var bool */
public $hideItemName;
/** @var bool */
public $hideSettingItemName;
/** @var string */
public $textItemName;
/** @var bool */
public $hideItemDescription;
/** @var bool */
public $hideSettingItemDescription;
/** @var string */
public $textItemDescription;
/** @var bool */
public $hideItemQuantity;
/** @var bool */
public $hideSettingItemQuantity;
/** @var string */
public $textItemQuantity;
/** @var bool */
public $hideItemPrice;
/** @var bool */
public $hideSettingItemPrice;
/** @var string */
public $textItemPrice;
/** @var bool */
public $hideItemAmount;
/** @var bool */
public $hideSettingItemAmount;
/** @var string */
public $textItemAmount;
@ -276,8 +292,8 @@ abstract class Form extends Component
bool $hideDocumentTitle = false, bool $hideDocumentSubheading = false, string $title = '', string $subheading = '',
bool $hideIssuedAt = false, string $textIssuedAt = '', string $issuedAt = '', bool $hideDueAt = false, string $textDueAt = '', string $dueAt = '', $periodDueAt = '',
bool $hideDocumentNumber = false, string $textDocumentNumber = '', string $documentNumber = '', bool $hideOrderNumber = false, string $textOrderNumber = '', string $orderNumber = '',
bool $hideEditItemColumns = false, bool $hideItems = false, bool $hideItemName = false, string $textItemName = '', bool $hideItemDescription = false, string $textItemDescription = '',
bool $hideItemQuantity = false, string $textItemQuantity = '', bool $hideItemPrice = false, string $textItemPrice = '', bool $hideItemAmount = false, string $textItemAmount = '',
bool $hideEditItemColumns = false, bool $hideItems = false, bool $hideItemName = false, bool $hideSettingItemName = false, string $textItemName = '', bool $hideItemDescription = false, bool $hideSettingItemDescription = false, string $textItemDescription = '',
bool $hideItemQuantity = false, bool $hideSettingItemQuantity = false, string $textItemQuantity = '', bool $hideItemPrice = false, bool $hideSettingItemPrice = false, string $textItemPrice = '', bool $hideItemAmount = false, bool $hideSettingItemAmount = false, string $textItemAmount = '',
bool $hideDiscount = false, bool $isSalePrice = false, bool $isPurchasePrice = false, int $searchCharLimit = 0, string $notes = '',
bool $showRecurring = false,
bool $hideAdvanced = false, string $textSectionAdvancedTitle = '', string $textSectionAdvancedDescription = '',
@ -290,9 +306,11 @@ abstract class Form extends Component
$this->model = ! empty($model) ? $model : $document;
$this->document = $this->model;
$this->currencies = $this->getCurrencies($currencies);
$this->currency = $this->getCurrency($document, $currency, $currency_code);
$this->currency_code = ! empty($this->currency) ? $this->currency->code : default_currency();
$this->currency = $this->getCurrency($document, $currency, $currency_code);
$this->currencies = $this->getCurrencies($currencies);
$this->taxes = Tax::enabled()->orderBy('name')->get()->pluck('title', 'id');
/* -- Content Start -- */
@ -357,18 +375,23 @@ abstract class Form extends Component
$this->hideItems = $this->getHideItems($type, $hideItems, $hideItemName, $hideItemDescription);
$this->hideItemName = $this->getHideItemName($type, $hideItemName);
$this->hideSettingItemName = $this->getHideSettingItemName($type, $hideSettingItemName);
$this->textItemName = $this->getTextItemName($type, $textItemName);
$this->hideItemDescription = $this->getHideItemDescription($type, $hideItemDescription);
$this->hideSettingItemDescription = $this->getHideSettingItemDescription($type, $hideSettingItemDescription);
$this->textItemDescription = $this->getTextItemDescription($type, $textItemDescription);
$this->hideItemQuantity = $this->getHideItemQuantity($type, $hideItemQuantity);
$this->hideSettingItemQuantity = $this->getHideSettingItemQuantity($type, $hideSettingItemQuantity);
$this->textItemQuantity = $this->getTextItemQuantity($type, $textItemQuantity);
$this->hideItemPrice = $this->getHideItemPrice($type, $hideItemPrice);
$this->hideSettingItemPrice = $this->getHideSettingItemPrice($type, $hideSettingItemPrice);
$this->textItemPrice = $this->getTextItemPrice($type, $textItemPrice);
$this->hideItemAmount = $this->getHideItemAmount($type, $hideItemAmount);
$this->hideSettingItemAmount = $this->getHideSettingItemAmount($type, $hideSettingItemAmount);
$this->textItemAmount = $this->getTextItemAmount($type, $textItemAmount);
$this->hideDiscount = $this->getHideDiscount($type, $hideDiscount);
@ -763,7 +786,7 @@ abstract class Form extends Component
$issuedAt = Date::now()->toDateString();
}
$addDays = setting($this->getSettingKey($type, 'payment_terms'), 0) ?: 0;
$addDays = setting($this->getDocumentSettingKey($type, 'payment_terms'), 0) ?: 0;
$dueAt = Date::parse($issuedAt)->addDays($addDays)->toDateString();
@ -815,10 +838,14 @@ abstract class Form extends Component
return $document->document_number;
}
$document_number = $this->getNextDocumentNumber($type);
$contact = ($this->contact instanceof Contact) ? $this->contact : null;
$utility = app(DocumentNumber::class);
$document_number = $utility->getNextNumber($type, $contact);
if (empty($document_number)) {
$document_number = $this->getNextDocumentNumber(Document::INVOICE_TYPE);
$document_number = $utility->getNextNumber(Document::INVOICE_TYPE, $contact);
}
return $document_number;
@ -869,25 +896,35 @@ abstract class Form extends Component
return $hideItems;
}
protected function getHideItemName($type, $hideItemName)
protected function getHideItemName($type, $hideItemName): bool
{
if (! empty($hideItemName)) {
return $hideItemName;
}
// if you use settting translation
if ($hideItemName = setting($this->getSettingKey($type, 'item_name'), false) && $hideItemName == 'hide') {
return $hideItemName;
}
$hide = $this->getHideFromConfig($type, 'name');
if ($hide) {
return $hide;
}
// @todo what return value invoice or always false??
return setting('invoice.item_name', $hideItemName) == 'hide' ? true : false;
return false;
}
protected function getHideSettingItemName($type, $hideSettingItemName): bool
{
if (! empty($hideSettingItemName)) {
return $hideSettingItemName;
}
$hideItemName = setting($this->getDocumentSettingKey($type, 'item_name'), false);
// if you use settting translation
if ($hideItemName === 'hide') {
return true;
}
return false;
}
protected function getTextItemName($type, $textItemName)
@ -897,18 +934,18 @@ abstract class Form extends Component
}
// if you use settting translation
if (setting($this->getSettingKey($type, 'item_name'), 'items') === 'custom') {
if (empty($textItemName = setting($this->getSettingKey($type, 'item_name_input')))) {
if (setting($this->getDocumentSettingKey($type, 'item_name'), 'items') === 'custom') {
if (empty($textItemName = setting($this->getDocumentSettingKey($type, 'item_name_input')))) {
$textItemName = 'general.items';
}
return $textItemName;
}
if (setting($this->getSettingKey($type, 'item_name')) !== null
&& (trans(setting($this->getSettingKey($type, 'item_name'))) != setting($this->getSettingKey($type, 'item_name')))
if (setting($this->getDocumentSettingKey($type, 'item_name')) !== null
&& (trans(setting($this->getDocumentSettingKey($type, 'item_name'))) != setting($this->getDocumentSettingKey($type, 'item_name')))
) {
return setting($this->getSettingKey($type, 'item_name'));
return setting($this->getDocumentSettingKey($type, 'item_name'));
}
$translation = $this->getTextFromConfig($type, 'items');
@ -920,25 +957,33 @@ abstract class Form extends Component
return 'general.items';
}
protected function getHideItemDescription($type, $hideItemDescription)
protected function getHideItemDescription($type, $hideItemDescription): bool
{
if (! empty($hideItemDescription)) {
return $hideItemDescription;
}
// if you use settting translation
if ($hideItemDescription = setting($this->getSettingKey($type, 'hide_item_description'), false) && $hideItemDescription == 'hide') {
return $hideItemDescription;
}
$hide = $this->getHideFromConfig($type, 'description');
if ($hide) {
return $hide;
}
// @todo what return value invoice or always false??
return setting('invoice.hide_item_description', $hideItemDescription);
return false;
}
protected function getHideSettingItemDescription($type, $hideSettingItemDescription): bool
{
if (! empty($hideSettingItemDescription)) {
return $hideSettingItemDescription;
}
// if you use settting translation
if (setting($this->getDocumentSettingKey($type, 'hide_item_description'), false)) {
return true;
}
return false;
}
protected function getTextItemDescription($type, $textItemDescription)
@ -956,25 +1001,35 @@ abstract class Form extends Component
return 'general.description';
}
protected function getHideItemQuantity($type, $hideItemQuantity)
protected function getHideItemQuantity($type, $hideItemQuantity): bool
{
if (! empty($hideItemQuantity)) {
return $hideItemQuantity;
}
// if you use settting translation
if ($hideItemQuantity = setting($this->getSettingKey($type, 'hide_quantity'), false) && $hideItemQuantity == 'hide') {
return $hideItemQuantity;
}
$hide = $this->getHideFromConfig($type, 'quantity');
if ($hide) {
return $hide;
}
// @todo what return value invoice or always false??
return setting('invoice.quantity_name', $hideItemQuantity) == 'hide' ? true : false;
return false;
}
protected function getHideSettingItemQuantity($type, $hideSettingItemQuantity): bool
{
if (! empty($hideSettingItemQuantity)) {
return $hideSettingItemQuantity;
}
$hideItemQuantity = setting($this->getDocumentSettingKey($type, 'quantity_name'), false);
// if you use settting translation
if ($hideItemQuantity === 'hide') {
return true;
}
return false;
}
protected function getTextItemQuantity($type, $textItemQuantity)
@ -984,18 +1039,18 @@ abstract class Form extends Component
}
// if you use settting translation
if (setting($this->getSettingKey($type, 'quantity_name'), 'quantity') === 'custom') {
if (empty($textItemQuantity = setting($this->getSettingKey($type, 'quantity_name_input')))) {
if (setting($this->getDocumentSettingKey($type, 'quantity_name'), 'quantity') === 'custom') {
if (empty($textItemQuantity = setting($this->getDocumentSettingKey($type, 'quantity_name_input')))) {
$textItemQuantity = 'invoices.quantity';
}
return $textItemQuantity;
}
if (setting($this->getSettingKey($type, 'quantity_name')) !== null
&& (trans(setting($this->getSettingKey($type, 'quantity_name'))) != setting($this->getSettingKey($type, 'quantity_name')))
if (setting($this->getDocumentSettingKey($type, 'quantity_name')) !== null
&& (trans(setting($this->getDocumentSettingKey($type, 'quantity_name'))) != setting($this->getDocumentSettingKey($type, 'quantity_name')))
) {
return setting($this->getSettingKey($type, 'quantity_name'));
return setting($this->getDocumentSettingKey($type, 'quantity_name'));
}
$translation = $this->getTextFromConfig($type, 'quantity');
@ -1007,25 +1062,35 @@ abstract class Form extends Component
return 'invoices.quantity';
}
protected function getHideItemPrice($type, $hideItemPrice)
protected function getHideItemPrice($type, $hideItemPrice): bool
{
if (! empty($hideItemPrice)) {
return $hideItemPrice;
}
// if you use settting translation
if ($hideItemPrice = setting($this->getSettingKey($type, 'hide_price'), false) && $hideItemPrice == 'hide') {
return $hideItemPrice;
}
$hide = $this->getHideFromConfig($type, 'price');
if ($hide) {
return $hide;
}
// @todo what return value invoice or always false??
return setting('invoice.price_name', $hideItemPrice) == 'hide' ? true : false;
return false;
}
protected function getHideSettingItemPrice($type, $hideSettingItemPrice): bool
{
if (! empty($hideSettingItemPrice)) {
return $hideSettingItemPrice;
}
$hideItemPrice = setting($this->getDocumentSettingKey($type, 'price_name'), false);
// if you use settting translation
if ($hideItemPrice === 'hide') {
return true;
}
return false;
}
protected function getTextItemPrice($type, $textItemPrice)
@ -1035,18 +1100,18 @@ abstract class Form extends Component
}
// if you use settting translation
if (setting($this->getSettingKey($type, 'price_name'), 'price') === 'custom') {
if (empty($textItemPrice = setting($this->getSettingKey($type, 'price_name_input')))) {
if (setting($this->getDocumentSettingKey($type, 'price_name'), 'price') === 'custom') {
if (empty($textItemPrice = setting($this->getDocumentSettingKey($type, 'price_name_input')))) {
$textItemPrice = 'invoices.price';
}
return $textItemPrice;
}
if (setting($this->getSettingKey($type, 'price_name')) !== null
&& (trans(setting($this->getSettingKey($type, 'price_name'))) != setting($this->getSettingKey($type, 'price_name')))
if (setting($this->getDocumentSettingKey($type, 'price_name')) !== null
&& (trans(setting($this->getDocumentSettingKey($type, 'price_name'))) != setting($this->getDocumentSettingKey($type, 'price_name')))
) {
return setting($this->getSettingKey($type, 'price_name'));
return setting($this->getDocumentSettingKey($type, 'price_name'));
}
$translation = $this->getTextFromConfig($type, 'price');
@ -1058,25 +1123,33 @@ abstract class Form extends Component
return 'invoices.price';
}
protected function getHideItemAmount($type, $hideItemAmount)
protected function getHideItemAmount($type, $hideItemAmount): bool
{
if (! empty($hideItemAmount)) {
return $hideItemAmount;
}
// if you use settting translation
if ($hideAmount = setting($this->getSettingKey($type, 'hide_amount'), false)) {
return $hideAmount;
}
$hide = $this->getHideFromConfig($type, 'amount');
if ($hide) {
return $hide;
}
// @todo what return value invoice or always false??
return setting('invoice.hide_amount', $hideAmount);
return false;
}
protected function getHideSettingItemAmount($type, $hideSettingItemAmount): bool
{
if (! empty($hideSettingItemAmount)) {
return $hideSettingItemAmount;
}
// if you use settting translation
if (setting($this->getDocumentSettingKey($type, 'hide_amount'), false)) {
return true;
}
return false;
}
protected function getTextItemAmount($type, $textItemAmount)
@ -1101,7 +1174,7 @@ abstract class Form extends Component
}
// if you use settting translation
if ($hideDiscount = setting($this->getSettingKey($type, 'hide_discount'), false)) {
if ($hideDiscount = setting($this->getDocumentSettingKey($type, 'hide_discount'), false)) {
return $hideDiscount;
}
@ -1122,7 +1195,7 @@ abstract class Form extends Component
}
// if you use settting translation
if ($settingCharLimit = setting($this->getSettingKey($type, 'item_search_chart_limit'), false)) {
if ($settingCharLimit = setting($this->getDocumentSettingKey($type, 'item_search_chart_limit'), false)) {
return $settingCharLimit;
}
@ -1146,7 +1219,7 @@ abstract class Form extends Component
return $this->document->notes;
}
return setting($this->getSettingKey($this->type, 'notes'));
return setting($this->getDocumentSettingKey($this->type, 'notes'));
}
protected function getTextSectionAdvancedTitle($type, $textSectionAdvancedTitle)
@ -1177,7 +1250,7 @@ abstract class Form extends Component
return $this->document->title;
}
return setting($this->getSettingKey($type, 'title'));
return setting($this->getDocumentSettingKey($type, 'title'));
}
protected function getSubheadingValue($type, $subheading)
@ -1190,7 +1263,7 @@ abstract class Form extends Component
return $this->document->subheading;
}
return setting($this->getSettingKey($type, 'subheading'));
return setting($this->getDocumentSettingKey($type, 'subheading'));
}
protected function getFooterValue($footer)
@ -1203,7 +1276,7 @@ abstract class Form extends Component
return $this->document->footer;
}
return setting($this->getSettingKey($this->type, 'footer'));
return setting($this->getDocumentSettingKey($this->type, 'footer'));
}
protected function getTypeCategory($type, $typeCategory)

View File

@ -392,8 +392,8 @@ abstract class Index extends Component
foreach ($totals as $key => $total) {
$title = ($key == 'overdue') ? trans('general.overdue') : trans('documents.statuses.' . $key);
$href = route($route, ['search' => 'status:' . $key]);
$amount = money($total, default_currency(), true)->formatForHumans();
$tooltip = money($total, default_currency(), true)->format();
$amount = money($total)->formatForHumans();
$tooltip = money($total)->format();
$items[] = [
'title' => $title,

View File

@ -11,7 +11,6 @@ use App\Abstracts\View\Component;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Intervention\Image\Exception\NotReadableException;
use Image;
@ -742,9 +741,7 @@ abstract class Show extends Component
return $textRecurringType;
}
$default_key = config('type.' . static::OBJECT_TYPE . '.' . $type . '.translation.prefix');
$translation = $this->getTextFromConfig($type, 'recurring_tye', $default_key);
$translation = config('type.' . static::OBJECT_TYPE . '.' . $type . '.translation.tab_document');
if (! empty($translation)) {
return $translation;
@ -910,7 +907,7 @@ abstract class Show extends Component
return $template;
}
$documentTemplate = setting($this->getSettingKey($type, 'template'), 'default');
$documentTemplate = setting($this->getDocumentSettingKey($type, 'template'), 'default');
return $documentTemplate;
}
@ -928,7 +925,7 @@ abstract class Show extends Component
if (! empty($media)) {
$path = $media->getDiskPath();
if (Storage::missing($path)) {
if (! $media->fileExists()) {
return $logo;
}
} else {
@ -941,7 +938,7 @@ abstract class Show extends Component
$height = setting('invoice.logo_size_height');
if ($media) {
$image->make(Storage::get($path))->resize($width, $height)->encode();
$image->make($media->contents())->resize($width, $height)->encode();
} else {
$image->make($path)->resize($width, $height)->encode();
}
@ -975,21 +972,28 @@ abstract class Show extends Component
return $backgroundColor;
}
if ($background_color = config('type.' . static::OBJECT_TYPE . '.' . $type . '.color', false)) {
return $background_color;
// checking setting color
$key = $this->getDocumentSettingKey($type, 'color');
if (! empty(setting($key))) {
$backgroundColor = setting($key);
}
if (! empty($alias = config('type.' . static::OBJECT_TYPE . '.' . $type . '.alias'))) {
$type = $alias . '.' . str_replace('-', '_', $type);
// checking config color
if (empty($backgroundColor) && $background_color = config('type.document.' . $type . '.color', false)) {
$backgroundColor = $background_color;
}
$backgroundColor = setting($this->getSettingKey($type, 'color'), '#55588b');
// set default color
if (empty($backgroundColor)) {
$backgroundColor = '#55588b';
}
return $this->getHexCodeOfTailwindClass($backgroundColor);
}
protected function getTextDocumentTitle($type, $textDocumentTitle)
{
{
if (! empty($textDocumentTitle)) {
return $textDocumentTitle;
}
@ -998,7 +1002,7 @@ abstract class Show extends Component
return $this->document->title;
}
$key = $this->getSettingKey($type, 'title');
$key = $this->getDocumentSettingKey($type, 'title');
if (! empty(setting($key))) {
return setting($key);
@ -1023,7 +1027,7 @@ abstract class Show extends Component
return $this->document->subheading;
}
$key = $this->getSettingKey($type, 'subheading');
$key = $this->getDocumentSettingKey($type, 'subheading');
if (!empty(setting($key))) {
return setting($key);
@ -1153,8 +1157,8 @@ abstract class Show extends Component
}
// if you use settting translation
if (setting($this->getSettingKey($type, 'item_name'), 'items') == 'custom') {
if (empty($textItems = setting($this->getSettingKey($type, 'item_name_input')))) {
if (setting($this->getDocumentSettingKey($type, 'item_name'), 'items') == 'custom') {
if (empty($textItems = setting($this->getDocumentSettingKey($type, 'item_name_input')))) {
$textItems = 'general.items';
}
@ -1177,8 +1181,8 @@ abstract class Show extends Component
}
// if you use settting translation
if (setting($this->getSettingKey($type, 'quantity_name'), 'quantity') === 'custom') {
if (empty($textQuantity = setting($this->getSettingKey($type, 'quantity_name_input')))) {
if (setting($this->getDocumentSettingKey($type, 'quantity_name'), 'quantity') === 'custom') {
if (empty($textQuantity = setting($this->getDocumentSettingKey($type, 'quantity_name_input')))) {
$textQuantity = 'invoices.quantity';
}
@ -1201,8 +1205,8 @@ abstract class Show extends Component
}
// if you use settting translation
if (setting($this->getSettingKey($type, 'price_name'), 'price') === 'custom') {
if (empty($textPrice = setting($this->getSettingKey($type, 'price_name_input')))) {
if (setting($this->getDocumentSettingKey($type, 'price_name'), 'price') === 'custom') {
if (empty($textPrice = setting($this->getDocumentSettingKey($type, 'price_name_input')))) {
$textPrice = 'invoices.price';
}
@ -1256,9 +1260,11 @@ abstract class Show extends Component
return $hideName;
}
$hideName = setting($this->getDocumentSettingKey($type, 'item_name'), false);
// if you use settting translation
if ($hideName = setting($this->getSettingKey($type, 'item_name'), false) && $hideName == 'hide') {
return $hideName;
if ($hideName === 'hide') {
return true;
}
$hide = $this->getHideFromConfig($type, 'name');
@ -1267,8 +1273,7 @@ abstract class Show extends Component
return $hide;
}
// @todo what return value invoice or always false??
return setting('invoice.item_name', $hideName) == 'hide' ? true : false;
return false;
}
protected function getHideDescription($type, $hideDescription)
@ -1278,8 +1283,8 @@ abstract class Show extends Component
}
// if you use settting translation
if ($hideDescription = setting($this->getSettingKey($type, 'hide_item_description'), false)) {
return $hideDescription;
if (setting($this->getDocumentSettingKey($type, 'hide_item_description'), false)) {
return true;
}
$hide = $this->getHideFromConfig($type, 'description');
@ -1288,8 +1293,7 @@ abstract class Show extends Component
return $hide;
}
// @todo what return value invoice or always false??
return setting('invoice.hide_item_description', $hideDescription);
return false;
}
protected function getHideQuantity($type, $hideQuantity)
@ -1298,9 +1302,11 @@ abstract class Show extends Component
return $hideQuantity;
}
$hideQuantity = setting($this->getDocumentSettingKey($type, 'quantity_name'), false);
// if you use settting translation
if ($hideQuantity = setting($this->getSettingKey($type, 'hide_quantity'), false) && $hideQuantity == 'hide') {
return $hideQuantity;
if ($hideQuantity === 'hide') {
return true;
}
$hide = $this->getHideFromConfig($type, 'quantity');
@ -1309,8 +1315,7 @@ abstract class Show extends Component
return $hide;
}
// @todo what return value invoice or always false??
return setting('invoice.quantity_name', $hideQuantity) == 'hide' ? true : false;
return false;
}
protected function getHidePrice($type, $hidePrice)
@ -1319,9 +1324,11 @@ abstract class Show extends Component
return $hidePrice;
}
$hidePrice = setting($this->getDocumentSettingKey($type, 'price_name'), false);
// if you use settting translation
if ($hidePrice = setting($this->getSettingKey($type, 'hide_price'), false) && $hidePrice == 'hide') {
return $hidePrice;
if ($hidePrice === 'hide') {
return true;
}
$hide = $this->getHideFromConfig($type, 'price');
@ -1330,8 +1337,7 @@ abstract class Show extends Component
return $hide;
}
// @todo what return value invoice or always false??
return setting('invoice.price_name', $hidePrice) == 'hide' ? true : false;
return false;
}
protected function getHideDiscount($type, $hideDiscount)
@ -1341,7 +1347,7 @@ abstract class Show extends Component
}
// if you use settting translation
if ($hideDiscount = setting($this->getSettingKey($type, 'hide_discount'), false)) {
if ($hideDiscount = setting($this->getDocumentSettingKey($type, 'hide_discount'), false)) {
return $hideDiscount;
}
@ -1362,8 +1368,8 @@ abstract class Show extends Component
}
// if you use settting translation
if ($hideAmount = setting($this->getSettingKey($type, 'hide_amount'), false)) {
return $hideAmount;
if (setting($this->getDocumentSettingKey($type, 'hide_amount'), false)) {
return true;
}
$hide = $this->getHideFromConfig($type, 'amount');
@ -1372,7 +1378,6 @@ abstract class Show extends Component
return $hide;
}
// @todo what return value invoice or always false??
return setting('invoice.hide_amount', $hideAmount);
return false;
}
}

View File

@ -10,7 +10,6 @@ use App\Traits\Tailwind;
use App\Traits\ViewComponents;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Intervention\Image\Exception\NotReadableException;
use Image;
@ -210,7 +209,7 @@ abstract class Template extends Component
return $template;
}
$documentTemplate = setting($this->getSettingKey($type, 'template'), 'default');
$documentTemplate = setting($this->getDocumentSettingKey($type, 'template'), 'default');
return $documentTemplate;
}
@ -228,7 +227,7 @@ abstract class Template extends Component
if (! empty($media)) {
$path = $media->getDiskPath();
if (Storage::missing($path)) {
if (! $media->fileExists()) {
return $logo;
}
} else {
@ -241,7 +240,7 @@ abstract class Template extends Component
$height = setting('invoice.logo_size_height');
if ($media) {
$image->make(Storage::get($path))->resize($width, $height)->encode();
$image->make($media->contents())->resize($width, $height)->encode();
} else {
$image->make($path)->resize($width, $height)->encode();
}
@ -275,16 +274,22 @@ abstract class Template extends Component
return $backgroundColor;
}
if ($background_color = config('type.document.' . $type . '.color', false)) {
return $background_color;
// checking setting color
$key = $this->getDocumentSettingKey($type, 'color');
if (! empty(setting($key))) {
$backgroundColor = setting($key);
}
if (! empty($alias = config('type.document.' . $type . '.alias'))) {
$type = $alias . '.' . str_replace('-', '_', $type);
// checking config color
if (empty($backgroundColor) && $background_color = config('type.document.' . $type . '.color', false)) {
$backgroundColor = $background_color;
}
$backgroundColor = setting($this->getSettingKey($type, 'color'), '#55588b');
// set default color
if (empty($backgroundColor)) {
$backgroundColor = '#55588b';
}
return $this->getHexCodeOfTailwindClass($backgroundColor);
}
@ -299,7 +304,7 @@ abstract class Template extends Component
return $this->document->title;
}
$key = $this->getSettingKey($type, 'title');
$key = $this->getDocumentSettingKey($type, 'title');
if (! empty(setting($key))) {
return setting($key);
@ -324,7 +329,7 @@ abstract class Template extends Component
return $this->document->subheading;
}
$key = $this->getSettingKey($type, 'subheading');
$key = $this->getDocumentSettingKey($type, 'subheading');
if (! empty(setting($key))) {
return setting($key);
@ -454,18 +459,18 @@ abstract class Template extends Component
}
// if you use settting translation
if (setting($this->getSettingKey($type, 'item_name'), 'items') === 'custom') {
if (empty($textItems = setting($this->getSettingKey($type, 'item_name_input')))) {
if (setting($this->getDocumentSettingKey($type, 'item_name'), 'items') === 'custom') {
if (empty($textItems = setting($this->getDocumentSettingKey($type, 'item_name_input')))) {
$textItems = 'general.items';
}
return $textItems;
}
if (setting($this->getSettingKey($type, 'item_name')) !== null
&& (trans(setting($this->getSettingKey($type, 'item_name'))) != setting($this->getSettingKey($type, 'item_name')))
if (setting($this->getDocumentSettingKey($type, 'item_name')) !== null
&& (trans(setting($this->getDocumentSettingKey($type, 'item_name'))) != setting($this->getDocumentSettingKey($type, 'item_name')))
) {
return setting($this->getSettingKey($type, 'item_name'));
return setting($this->getDocumentSettingKey($type, 'item_name'));
}
$translation = $this->getTextFromConfig($type, 'items');
@ -484,18 +489,18 @@ abstract class Template extends Component
}
// if you use settting translation
if (setting($this->getSettingKey($type, 'quantity_name'), 'quantity') === 'custom') {
if (empty($textQuantity = setting($this->getSettingKey($type, 'quantity_name_input')))) {
if (setting($this->getDocumentSettingKey($type, 'quantity_name'), 'quantity') === 'custom') {
if (empty($textQuantity = setting($this->getDocumentSettingKey($type, 'quantity_name_input')))) {
$textQuantity = 'invoices.quantity';
}
return $textQuantity;
}
if (setting($this->getSettingKey($type, 'quantity_name')) !== null
&& (trans(setting($this->getSettingKey($type, 'quantity_name'))) != setting($this->getSettingKey($type, 'quantity_name')))
if (setting($this->getDocumentSettingKey($type, 'quantity_name')) !== null
&& (trans(setting($this->getDocumentSettingKey($type, 'quantity_name'))) != setting($this->getDocumentSettingKey($type, 'quantity_name')))
) {
return setting($this->getSettingKey($type, 'quantity_name'));
return setting($this->getDocumentSettingKey($type, 'quantity_name'));
}
$translation = $this->getTextFromConfig($type, 'quantity');
@ -514,18 +519,18 @@ abstract class Template extends Component
}
// if you use settting translation
if (setting($this->getSettingKey($type, 'price_name'), 'price') === 'custom') {
if (empty($textPrice = setting($this->getSettingKey($type, 'price_name_input')))) {
if (setting($this->getDocumentSettingKey($type, 'price_name'), 'price') === 'custom') {
if (empty($textPrice = setting($this->getDocumentSettingKey($type, 'price_name_input')))) {
$textPrice = 'invoices.price';
}
return $textPrice;
}
if (setting($this->getSettingKey($type, 'price_name')) !== null
&& (trans(setting($this->getSettingKey($type, 'price_name'))) != setting($this->getSettingKey($type, 'price_name')))
if (setting($this->getDocumentSettingKey($type, 'price_name')) !== null
&& (trans(setting($this->getDocumentSettingKey($type, 'price_name'))) != setting($this->getDocumentSettingKey($type, 'price_name')))
) {
return setting($this->getSettingKey($type, 'price_name'));
return setting($this->getDocumentSettingKey($type, 'price_name'));
}
$translation = $this->getTextFromConfig($type, 'price');
@ -592,9 +597,11 @@ abstract class Template extends Component
return $hideName;
}
$hideName = setting($this->getDocumentSettingKey($type, 'item_name'), false);
// if you use settting translation
if ($hideName = setting($this->getSettingKey($type, 'item_name'), false) && $hideName == 'hide') {
return $hideName;
if ($hideName === 'hide') {
return true;
}
$hide = $this->getHideFromConfig($type, 'name');
@ -603,8 +610,7 @@ abstract class Template extends Component
return $hide;
}
// @todo what return value invoice or always false??
return setting('invoice.item_name', $hideName) == 'hide' ? true : false;
return false;
}
protected function getHideDescription($type, $hideDescription)
@ -614,8 +620,8 @@ abstract class Template extends Component
}
// if you use settting translation
if ($hideDescription = setting($this->getSettingKey($type, 'hide_item_description'), false)) {
return $hideDescription;
if (setting($this->getDocumentSettingKey($type, 'hide_item_description'), false)) {
return true;
}
$hide = $this->getHideFromConfig($type, 'description');
@ -624,8 +630,7 @@ abstract class Template extends Component
return $hide;
}
// @todo what return value invoice or always false??
return setting('invoice.hide_item_description', $hideDescription);
return false;
}
protected function getHideQuantity($type, $hideQuantity)
@ -634,9 +639,11 @@ abstract class Template extends Component
return $hideQuantity;
}
$hideQuantity = setting($this->getDocumentSettingKey($type, 'quantity_name'), false);
// if you use settting translation
if ($hideQuantity = setting($this->getSettingKey($type, 'hide_quantity'), false) && $hideQuantity == 'hide') {
return $hideQuantity;
if ($hideQuantity === 'hide') {
return true;
}
$hide = $this->getHideFromConfig($type, 'quantity');
@ -645,8 +652,7 @@ abstract class Template extends Component
return $hide;
}
// @todo what return value invoice or always false??
return setting('invoice.quantity_name', $hideQuantity) == 'hide' ? true : false;
return false;
}
protected function getHidePrice($type, $hidePrice)
@ -655,9 +661,11 @@ abstract class Template extends Component
return $hidePrice;
}
$hidePrice = setting($this->getDocumentSettingKey($type, 'price_name'), false);
// if you use settting translation
if ($hidePrice = setting($this->getSettingKey($type, 'hide_price'), false) && $hidePrice == 'hide') {
return $hidePrice;
if ($hidePrice === 'hide') {
return true;
}
$hide = $this->getHideFromConfig($type, 'price');
@ -666,8 +674,7 @@ abstract class Template extends Component
return $hide;
}
// @todo what return value invoice or always false??
return setting('invoice.price_name', $hidePrice) == 'hide' ? true : false;
return false;
}
protected function getHideDiscount($type, $hideDiscount)
@ -677,7 +684,7 @@ abstract class Template extends Component
}
// if you use settting translation
if ($hideDiscount = setting($this->getSettingKey($type, 'hide_discount'), false)) {
if ($hideDiscount = setting($this->getDocumentSettingKey($type, 'hide_discount'), false)) {
return $hideDiscount;
}
@ -698,8 +705,8 @@ abstract class Template extends Component
}
// if you use settting translation
if ($hideAmount = setting($this->getSettingKey($type, 'hide_amount'), false)) {
return $hideAmount;
if (setting($this->getDocumentSettingKey($type, 'hide_amount'), false)) {
return true;
}
$hide = $this->getHideFromConfig($type, 'amount');
@ -708,8 +715,7 @@ abstract class Template extends Component
return $hide;
}
// @todo what return value invoice or always false??
return setting('invoice.hide_amount', $hideAmount);
return false;
}
protected function getPrint($print)

View File

@ -9,7 +9,6 @@ use App\Traits\Transactions;
use App\Utilities\Modules;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Str;
use Intervention\Image\Facades\Image;
@ -483,7 +482,7 @@ abstract class Show extends Component
return $template;
}
$transactionTemplate = setting($this->getSettingKey($type, 'template')) ?: 'default';
$transactionTemplate = setting($this->getTransactionSettingKey($type, 'template')) ?: 'default';
return $transactionTemplate;
}
@ -501,7 +500,7 @@ abstract class Show extends Component
if (! empty($media)) {
$path = $media->getDiskPath();
if (Storage::missing($path)) {
if (! $media->fileExists()) {
return $logo;
}
} else {
@ -514,7 +513,7 @@ abstract class Show extends Component
$height = setting('invoice.logo_size_height');
if ($media) {
$image->make(Storage::get($path))->resize($width, $height)->encode();
$image->make($media->contents())->resize($width, $height)->encode();
} else {
$image->make($path)->resize($width, $height)->encode();
}
@ -1114,9 +1113,7 @@ abstract class Show extends Component
return $textRecurringType;
}
$default_key = config('type.transaction.' . $type . '.translation.transactions');
$translation = $this->getTextFromConfig($type, 'recurring_type', $default_key);
$translation = config('type.transaction.' . $type . '.translation.transactions');
if (! empty($translation)) {
return $translation;

View File

@ -9,7 +9,6 @@ use App\Traits\Transactions;
use App\Utilities\Modules;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Intervention\Image\Facades\Image;
use Intervention\Image\Exception\NotReadableException;
@ -277,7 +276,7 @@ abstract class Template extends Component
if (!empty($media)) {
$path = $media->getDiskPath();
if (Storage::missing($path)) {
if (! $media->fileExists()) {
return $logo;
}
} else {
@ -290,7 +289,7 @@ abstract class Template extends Component
$height = setting('invoice.logo_size_height');
if ($media) {
$image->make(Storage::get($path))->resize($width, $height)->encode();
$image->make($media->contents())->resize($width, $height)->encode();
} else {
$image->make($path)->resize($width, $height)->encode();
}

View File

@ -31,7 +31,7 @@ class Bills extends BulkAction
],
'cancelled' => [
'icon' => 'cancel',
'name' => 'general.cancel',
'name' => 'documents.actions.cancel',
'message' => 'bulk_actions.message.cancelled',
'permission' => 'update-purchases-bills',
],
@ -67,7 +67,7 @@ class Bills extends BulkAction
$bills = $this->getSelectedRecords($request);
foreach ($bills as $bill) {
if ($bill->status == 'cancelled') {
if (in_array($bill->status, ['cancelled', 'draft'])) {
continue;
}

View File

@ -31,7 +31,7 @@ class Invoices extends BulkAction
],
'cancelled' => [
'icon' => 'cancel',
'name' => 'general.cancel',
'name' => 'documents.actions.cancel',
'message' => 'bulk_actions.message.cancelled',
'permission' => 'update-sales-invoices',
],
@ -67,7 +67,7 @@ class Invoices extends BulkAction
$invoices = $this->getSelectedRecords($request);
foreach ($invoices as $invoice) {
if ($invoice->status == 'cancelled') {
if (in_array($invoice->status, ['cancelled', 'draft'])) {
continue;
}

View File

@ -1,29 +0,0 @@
<?php
namespace App\Classifiers;
use Wnx\LaravelStats\ReflectionClass;
use Wnx\LaravelStats\Contracts\Classifier;
class Transformer implements Classifier
{
public function name(): string
{
return 'Transformers';
}
public function satisfies(ReflectionClass $class): bool
{
return $class->isSubclassOf(\League\Fractal\TransformerAbstract::class);
}
public function countsTowardsApplicationCode(): bool
{
return true;
}
public function countsTowardsTests(): bool
{
return false;
}
}

View File

@ -125,6 +125,6 @@ class DownloadModule extends Command
$version = Versions::getLatestVersion($url, $current);
}
return $version;
return $version?->latest;
}
}

View File

@ -71,12 +71,18 @@ class RecurringCheck extends Command
$company_name = !empty($recur->company->name) ? $recur->company->name : 'Missing Company Name : ' . $recur->company->id;
$template = $recur->recurable()->where('company_id', $recur->company_id)->first();
// Check if company is disabled
if (! $recur->company->enabled) {
$this->info($company_name . ' company is disabled. Skipping...');
if (Date::parse($recur->company->updated_at)->format('Y-m-d') > Date::now()->subMonth(3)->format('Y-m-d')) {
$recur->delete();
if ($template) {
$template->delete();
}
}
continue;
@ -98,12 +104,16 @@ class RecurringCheck extends Command
$recur->delete();
if ($template) {
$template->delete();
}
continue;
}
company($recur->company_id)->makeCurrent();
if (! $template = $recur->recurable) {
if (! $template) {
$this->info('Missing model.');
$recur->delete();

View File

@ -68,6 +68,14 @@ class Update extends Command
$this->old = $this->getOldVersion();
if (version_compare($this->old, $this->new, '>=')) {
$message = 'The current version for the ' . $this->alias . ' is the latest version!';
$this->info($message);
return self::CMD_SUCCESS;
}
company($this->company)->makeCurrent();
if (!$path = $this->download()) {
@ -91,7 +99,7 @@ class Update extends Command
public function getNewVersion()
{
return ($this->argument('new') == 'latest') ? Versions::latest($this->alias) : $this->argument('new');
return ($this->argument('new') == 'latest') ? Versions::latest($this->alias)?->latest : $this->argument('new');
}
public function getOldVersion()

View File

@ -0,0 +1,23 @@
<?php
namespace App\Events\Common;
use App\Abstracts\Event;
class DatesFormating extends Event
{
public $columns;
public $request;
/**
* Create a new event instance.
*
* @param $request
*/
public function __construct($columns, $request)
{
$this->columns = $columns;
$this->request = $request;
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace App\Events\Document;
use App\Abstracts\Event;
class DocumentDeleted extends Event
{
public $document;
/**
* Create a new event instance.
*
* @param $document
*/
public function __construct($document)
{
$this->document = $document;
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace App\Events\Document;
use App\Abstracts\Event;
class DocumentDeleting extends Event
{
public $document;
/**
* Create a new event instance.
*
* @param $document
*/
public function __construct($document)
{
$this->document = $document;
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace App\Events\Document;
use App\Abstracts\Event;
class DocumentSending extends Event
{
public $document;
/**
* Create a new event instance.
*
* @param $document
*/
public function __construct($document)
{
$this->document = $document;
}
}

View File

@ -0,0 +1,51 @@
<?php
namespace App\Events\Email;
use App\Abstracts\Event;
use App\Models\Auth\User;
use App\Models\Common\Contact;
class InvalidEmailDetected extends Event
{
public $email;
public $error;
public $contact = null;
public $user = null;
public function __construct(string $email, string $error)
{
$this->email = $email;
$this->error = $error;
$this->setContact();
$this->setUser();
}
public function setContact()
{
$contact = Contact::email($this->email)->enabled()->first();
if (empty($contact)) {
return;
}
$this->contact = $contact;
}
public function setUser()
{
$user = User::email($this->email)->enabled()->first();
if (empty($user)) {
return;
}
$this->user = $user;
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace App\Events\Email;
use App\Abstracts\Event;
class TooManyEmailsSent extends Event
{
public $user_id;
public function __construct(int $user_id)
{
$this->user_id = $user_id;
}
}

View File

@ -3,6 +3,7 @@
namespace App\Exceptions;
use Akaunting\Money\Exceptions\UnexpectedAmountException;
use App\Events\Email\InvalidEmailDetected;
use App\Exceptions\Http\Resource as ResourceException;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
@ -15,6 +16,7 @@ use Illuminate\View\ViewException;
use Symfony\Component\Debug\Exception\FatalThrowableError;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Mailer\Exception\HttpTransportException as MailerHttpTransportException;
use Throwable;
class Handler extends ExceptionHandler
@ -72,6 +74,14 @@ class Handler extends ExceptionHandler
*/
public function report(Throwable $exception)
{
if ($exception instanceof MailerHttpTransportException) {
$email = $this->handleMailerExceptions($exception);
if (! empty($email)) {
return;
}
}
parent::report($exception);
}
@ -194,6 +204,24 @@ class Handler extends ExceptionHandler
}
}
if ($exception instanceof MailerHttpTransportException) {
$email = $this->handleMailerExceptions($exception);
if (! empty($email)) {
$message = trans('notifications.menu.invalid_email.description', ['email' => $email]);
if ($request->ajax()) {
return response()->json([
'error' => $message,
], $exception->getCode());
}
return response()->view('errors.403', [
'message' => $message,
], $exception->getCode());
}
}
return parent::render($request, $exception);
}
@ -214,6 +242,28 @@ class Handler extends ExceptionHandler
return new Response($response, $this->getStatusCode($exception), $this->getHeaders($exception));
}
protected function handleMailerExceptions(MailerHttpTransportException $exception): string
{
/**
* Couldn't access the SentMessage object to get the email address
* https://symfony.com/doc/current/mailer.html#debugging-emails
*
* https://codespeedy.com/extract-email-addresses-from-a-string-in-php
* https://phpliveregex.com/p/IMG
*/
preg_match("/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}/", $exception->getMessage(), $matches);
if (empty($matches[0])) {
return '';
}
$email = $matches[0];
event(new InvalidEmailDetected($email, $exception->getMessage()));
return $email;
}
/**
* Prepare the replacements array by gathering the keys and values.
*

View File

@ -22,6 +22,8 @@ class BillItems extends Export
$model->bill_number = $document->document_number;
$model->item_name = $model->item->name;
$model->item_description = $model->item->description;
$model->item_type = $model->item->type;
return parent::map($model);
}
@ -31,6 +33,8 @@ class BillItems extends Export
return [
'bill_number',
'item_name',
'item_description',
'item_type',
'quantity',
'price',
'total',

View File

@ -22,6 +22,8 @@ class Customers extends Export
$model->country = $country;
$model->can_login = $model->user_id ? true : false;
return parent::map($model);
}
@ -41,6 +43,7 @@ class Customers extends Export
'currency_code',
'reference',
'enabled',
'can_login',
];
}
}

View File

@ -22,6 +22,8 @@ class InvoiceItems extends Export
$model->invoice_number = $document->document_number;
$model->item_name = $model->item->name;
$model->item_description = $model->item->description;
$model->item_type = $model->item->type;
return parent::map($model);
}
@ -31,6 +33,8 @@ class InvoiceItems extends Export
return [
'invoice_number',
'item_name',
'item_description',
'item_type',
'quantity',
'price',
'total',

View File

@ -5,7 +5,7 @@ namespace App\Http\Controllers\Auth;
use App\Abstracts\Http\Controller;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Http\Request;
use App\Http\Requests\Auth\Forgot as Request;
use Illuminate\Support\Facades\Password;
class Forgot extends Controller
@ -42,13 +42,11 @@ class Forgot extends Controller
/**
* Send a reset link to the given user.
*
* @param \Illuminate\Http\Request $request
* @param \App\Http\Requests\Auth\Forgot $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(Request $request)
{
$this->validateEmail($request);
// We will send the password reset link to this user. Once we have attempted
// to send the link, we will examine the response then see the message we
// need to show to the user. Finally, we'll send out a proper response.
@ -96,7 +94,7 @@ class Forgot extends Controller
'error' => true,
'message' => trans('passwords.user'),
'data' => null,
'redirect' => null,
'redirect' => route('forgot'),
];
return response()->json($response);

View File

@ -4,7 +4,8 @@ namespace App\Http\Controllers\Auth;
use App\Abstracts\Http\Controller;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Http\Request;
use Illuminate\Http\Request as BaseRequest;
use App\Http\Requests\Auth\Reset as Request;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Str;
@ -29,17 +30,22 @@ class Reset extends Controller
$this->middleware('guest');
}
public function create(Request $request, $token = null)
public function create(BaseRequest $request, $token = null)
{
return view('auth.reset.create')->with(
['token' => $token, 'email' => $request->email]
);
}
/**
* Send a reset link to the given user.
*
* @param \App\Http\Requests\Auth\Reset $request
*
* @return \Illuminate\Http\JsonResponse
*/
public function store(Request $request)
{
$this->validate($request, $this->rules(), $this->validationErrorMessages());
// Here we will attempt to reset the user's password. If it is successful we
// will update the password on an actual user model and persist it to the
// database. Otherwise we will parse the error and return the response.

View File

@ -47,9 +47,19 @@ class Users extends Controller
*
* @return Response
*/
public function show()
public function show(User $user)
{
return redirect()->route('users.index');
$u = new \stdClass();
$u->role = $user->roles()->first();
$u->landing_pages = [];
event(new LandingPageShowing($u));
$landing_pages = $u->landing_pages;
$companies = $user->companies()->collect();
return view('auth.users.show', compact('user', 'landing_pages', 'companies'));
}
/**
@ -67,7 +77,13 @@ class Users extends Controller
$landing_pages = $u->landing_pages;
$roles = Role::all()->reject(function ($r) {
return $r->hasPermission('read-client-portal');
$status = $r->hasPermission('read-client-portal');
if ($r->name == 'employee') {
$status = true;
}
return $status;
})->pluck('display_name', 'id');
$companies = user()->companies()->take(setting('default.select_limit'))->get()->sortBy('name')->pluck('name', 'id');
@ -89,7 +105,7 @@ class Users extends Controller
$response = $this->ajaxDispatch(new CreateUser($request));
if ($response['success']) {
$response['redirect'] = route('users.index');
$response['redirect'] = route('users.show', $response['data']->id);
$message = trans('messages.success.invited', ['type' => trans_choice('general.users', 1)]);
@ -129,12 +145,21 @@ class Users extends Controller
if ($user->isCustomer()) {
// Show only roles with customer permission
$roles = Role::all()->reject(function ($r) {
return !$r->hasPermission('read-client-portal');
return ! $r->hasPermission('read-client-portal');
})->pluck('display_name', 'id');
} else if ($user->isEmployee()) {
// Show only roles with employee permission
$roles = Role::where('name', 'employee')->get()->pluck('display_name', 'id');
} else {
// Don't show roles with customer permission
$roles = Role::all()->reject(function ($r) {
return $r->hasPermission('read-client-portal');
$status = $r->hasPermission('read-client-portal');
if ($r->name == 'employee') {
$status = true;
}
return $status;
})->pluck('display_name', 'id');
}
@ -176,7 +201,7 @@ class Users extends Controller
$response = $this->ajaxDispatch(new UpdateUser($user, $request));
if ($response['success']) {
$response['redirect'] = user()->can('read-auth-users') ? route('users.index') : route('users.edit', $user->id);
$response['redirect'] = user()->can('read-auth-users') ? route('users.show', $user->id) : route('users.edit', $user->id);
$message = trans('messages.success.updated', ['type' => $user->name]);
@ -341,8 +366,6 @@ class Users extends Controller
{
$response = $this->ajaxDispatch(new CreateInvitation($user, company()));
$response['redirect'] = route('users.index');
if ($response['success']) {
$message = trans('messages.success.invited', ['type' => trans_choice('general.users', 1)]);
@ -353,7 +376,7 @@ class Users extends Controller
flash($message)->error()->important();
}
return response()->json($response);
return redirect()->route('users.index');
}
/**

View File

@ -35,16 +35,16 @@ class Accounts extends Controller
*/
public function show(Account $account)
{
$transactions = Transaction::with('category', 'contact', 'document')->where('account_id', $account->id)->collect(['paid_at'=> 'desc']);
$transactions = Transaction::with('category', 'contact', 'contact.media', 'document', 'document.totals', 'document.media', 'recurring', 'media')->where('account_id', $account->id)->collect(['paid_at'=> 'desc']);
$transfers = Transfer::with('expense_transaction', 'expense_transaction.account', 'income_transaction', 'income_transaction.account')
->whereHas('expense_transaction', fn ($query) => $query->where('account_id', $account->id))
->orWhereHas('income_transaction', fn ($query) => $query->where('account_id', $account->id))
->collect(['expense_transaction.paid_at' => 'desc']);
$incoming_amount = money($account->income_balance, $account->currency_code, true);
$outgoing_amount = money($account->expense_balance, $account->currency_code, true);
$current_amount = money($account->balance, $account->currency_code, true);
$incoming_amount = money($account->income_balance, $account->currency_code);
$outgoing_amount = money($account->expense_balance, $account->currency_code);
$current_amount = money($account->balance, $account->currency_code);
$summary_amounts = [
'incoming_exact' => $incoming_amount->format(),

View File

@ -24,8 +24,8 @@ class Reconciliations extends Controller
{
$reconciliations = Reconciliation::with('account')->collect();
$reconciled_amount = money($reconciliations->where('reconciled', 1)->sum('closing_balance'), default_currency(), true);
$in_progress_amount = money($reconciliations->where('reconciled', 0)->sum('closing_balance'), default_currency(), true);
$reconciled_amount = money($reconciliations->where('reconciled', 1)->sum('closing_balance'));
$in_progress_amount = money($reconciliations->where('reconciled', 0)->sum('closing_balance'));
$summary_amounts = [
'amount_exact' => $reconciled_amount->format(),
@ -246,9 +246,9 @@ class Reconciliations extends Controller
$difference = $closing_balance - $cleared_amount;
$json->closing_balance = money($closing_balance, $currency_code, true)->format();
$json->cleared_amount = money($cleared_amount, $currency_code, true)->format();
$json->difference = money($difference, $currency_code, true)->format();
$json->closing_balance = money($closing_balance, $currency_code)->format();
$json->cleared_amount = money($cleared_amount, $currency_code)->format();
$json->difference = money($difference, $currency_code)->format();
$json->difference_raw = (int) $difference;
return response()->json($json);

View File

@ -63,9 +63,9 @@ class RecurringTransactions extends Controller
*/
public function create()
{
$type = request()->get('type', 'income-recurring');
$real_type = request()->get('real_type', $this->getRealTypeOfRecurringTransaction($type));
$contact_type = config('type.transaction.' . $real_type . '.contact_type');
$type = $this->getTypeRecurringTransaction(request()->get('type', 'income-recurring'));
$real_type = $this->getTypeTransaction(request()->get('real_type', $this->getRealTypeOfRecurringTransaction($type)));
$contact_type = config('type.transaction.' . $real_type . '.contact_type', 'customer');
$number = $this->getNextTransactionNumber('-recurring');
@ -139,8 +139,8 @@ class RecurringTransactions extends Controller
public function edit(Transaction $recurring_transaction)
{
$type = $recurring_transaction->type;
$real_type = request()->get('real_type', $this->getRealTypeOfRecurringTransaction($type));
$contact_type = config('type.transaction.' . $real_type . '.contact_type');
$real_type = $this->getTypeTransaction(request()->get('real_type', $this->getRealTypeOfRecurringTransaction($type)));
$contact_type = config('type.transaction.' . $real_type . '.contact_type', 'customer');
$number = $this->getNextTransactionNumber('-recurring');

View File

@ -56,9 +56,9 @@ class Transactions extends Controller
$totals['profit'] = $totals['income'] - $totals['expense'];
$incoming_amount = money($totals['income'], default_currency(), true);
$expense_amount = money($totals['expense'], default_currency(), true);
$profit_amount = money($totals['profit'], default_currency(), true);
$incoming_amount = money($totals['income']);
$expense_amount = money($totals['expense']);
$profit_amount = money($totals['profit']);
$summary_amounts = [
'incoming_exact' => $incoming_amount->format(),
@ -98,10 +98,10 @@ class Transactions extends Controller
*/
public function create()
{
$type = request()->get('type', 'income');
$type = $this->getTypeTransaction(request()->get('type', 'income'));
$real_type = $this->getRealTypeTransaction($type);
$number = $this->getNextTransactionNumber();
$number = $this->getNextTransactionNumber($type);
$contact_type = config('type.transaction.' . $type . '.contact_type');

View File

@ -61,19 +61,28 @@ class BulkActions extends Controller
$result = $bulk_actions->{$handle}($request);
$message = trans($bulk_actions->messages['general'], ['type' => $handle, 'count' => count($request->get('selected'))]);
$count = count($request->get('selected'));
$not_passed = 0;
if (array_key_exists($handle, $bulk_actions->messages)) {
flash()->messages->each(function ($message) use (&$not_passed) {
if (in_array($message->level, ['danger', 'warning'])) {
$not_passed++;
}
});
$message = trans($bulk_actions->messages['general'], ['type' => $handle, 'count' => $count - $not_passed]);
if (array_key_exists($handle, $bulk_actions->messages) && $not_passed === 0) {
$message = trans($bulk_actions->messages[$handle], ['type' => $page]);
}
if (! empty($result) && ($result instanceof \Symfony\Component\HttpFoundation\BinaryFileResponse)) {
flash($message)->success();
$level = $not_passed > 0 ? 'info' : 'success';
flash($message)->{$level}();
if (! empty($result) && ($result instanceof \Symfony\Component\HttpFoundation\BinaryFileResponse)) {
return $result;
} elseif (! empty($result) && ($result instanceof RedirectResponse)) {
flash($message)->success();
return response()->json([
'success' => true,
'redirect' => $result->getTargetUrl(),
@ -82,8 +91,6 @@ class BulkActions extends Controller
'message' => ''
]);
} else {
flash($message)->success();
return response()->json([
'success' => true,
'redirect' => true,

View File

@ -16,6 +16,15 @@ class Companies extends Controller
{
use Uploads, Users;
public function __construct()
{
// Add CRUD permission checks to all methods only remove index method for all companies list.
$this->middleware('permission:create-common-companies')->only('create', 'store', 'duplicate', 'import');
$this->middleware('permission:read-common-companies')->only('show', 'edit', 'export');
$this->middleware('permission:update-common-companies')->only('update', 'enable', 'disable');
$this->middleware('permission:delete-common-companies')->only('destroy');
}
/**
* Display a listing of the resource.
*

View File

@ -34,6 +34,22 @@ class Uploads extends Controller
return $this->streamMedia($media);
}
public function inline($id)
{
try {
$media = Media::find($id);
} catch (\Exception $e) {
return response(null, 204);
}
// Get file path
if (!$this->getMediaPathOnStorage($media)) {
return response(null, 204);
}
return $this->streamMedia($media, 'inline');
}
/**
* Get the specified resource.
*

View File

@ -15,7 +15,12 @@ class Database extends Controller
*/
public function create()
{
return view('install.database.create');
return view('install.database.create', [
'host' => env('DB_HOST' , 'localhost'),
'username' => env('DB_USERNAME', ''),
'password' => env('DB_PASSWORD', ''),
'database' => env('DB_DATABASE', ''),
]);
}
/**
@ -34,9 +39,10 @@ class Database extends Controller
$database = $request['database'];
$username = $request['username'];
$password = $request['password'];
$prefix = config("database.connections.$connection.prefix", null);
// Check database connection
if (!Installer::createDbTables($host, $port, $database, $username, $password)) {
if (!Installer::createDbTables($host, $port, $database, $username, $password, $prefix)) {
$response = [
'status' => null,
'success' => false,

View File

@ -14,7 +14,15 @@ class Language extends Controller
*/
public function create()
{
return view('install.language.create');
$locale = config('app.locale');
$lang_allowed = language()->allowed();
if (! $locale || ! array_key_exists($locale, $lang_allowed)) {
$locale = 'en-GB';
}
return view('install.language.create', compact('locale', 'lang_allowed'));
}
/**

View File

@ -29,11 +29,13 @@ class Settings extends Controller
public function store(Request $request)
{
DB::transaction(function () use ($request) {
$locale = session('locale') ?? config('app.locale');
// Create company
Installer::createCompany($request->get('company_name'), $request->get('company_email'), session('locale'));
Installer::createCompany($request->get('company_name'), $request->get('company_email'), $locale);
// Create user
Installer::createUser($request->get('user_email'), $request->get('user_password'), session('locale'));
Installer::createUser($request->get('user_email'), $request->get('user_password'), $locale);
});
// Make the final touches

View File

@ -48,7 +48,9 @@ class Updates extends Controller
$m->name = $row->getName();
$m->alias = $row->get('alias');
$m->installed = $row->get('version');
$m->latest = $updates[$alias];
$m->latest = $updates[$alias]->latest;
$m->errors = $updates[$alias]->errors;
$m->message = $updates[$alias]->message;
$modules[] = $m;
}

View File

@ -64,7 +64,7 @@ class Currencies extends Controller
*/
public function store(Request $request)
{
$currency = config('money.' . $request->get('code'));
$currency = config('money.currencies.' . $request->get('code'));
$request['precision'] = (int) $currency['precision'];
$request['symbol'] = $currency['symbol'];

View File

@ -32,22 +32,22 @@ class DocumentItemColumns extends Controller
$item_names = [
'hide' => trans('settings.invoice.hide.item_name'),
'settings.invoice.item' => trans('settings.' . $type . '.item'),
'settings.invoice.product' => trans('settings.' . $type . '.product'),
'settings.invoice.service' => trans('settings.' . $type . '.service'),
'settings.invoice.item' => trans('settings.invoice.item'),
'settings.invoice.product' => trans('settings.invoice.product'),
'settings.invoice.service' => trans('settings.invoice.service'),
'custom' => trans('settings.invoice.custom'),
];
$price_names = [
'hide' => trans('settings.invoice.hide.price'),
'settings.invoice.price' => trans('settings.' . $type . '.price'),
'settings.invoice.rate' => trans('settings.' . $type . '.rate'),
'settings.invoice.price' => trans('settings.invoice.price'),
'settings.invoice.rate' => trans('settings.invoice.rate'),
'custom' => trans('settings.invoice.custom'),
];
$quantity_names = [
'hide' => trans('settings.invoice.hide.quantity'),
'settings.invoice.quantity' => trans('settings.' . $type . '.quantity'),
'settings.invoice.quantity' => trans('settings.invoice.quantity'),
'custom' => trans('settings.invoice.custom'),
];
@ -60,17 +60,17 @@ class DocumentItemColumns extends Controller
'90' => trans('settings.invoice.due_days', ['days' => 90]),
];
$item_name = setting($this->getSettingKey($type, 'item_name'));
$item_name_input = setting($this->getSettingKey($type, 'item_name_input'));
$price_name = setting($this->getSettingKey($type, 'price_name'));
$price_name_input = setting($this->getSettingKey($type, 'price_name_input'));
$quantity_name = setting($this->getSettingKey($type, 'quantity_name'));
$quantity_name_input = setting($this->getSettingKey($type, 'quantity_name_input'));
$hide_item_name = setting($this->getSettingKey($type, 'hide_item_name'));
$hide_item_description = setting($this->getSettingKey($type, 'hide_item_description'));
$hide_quantity = setting($this->getSettingKey($type, 'hide_quantity'));
$hide_price = setting($this->getSettingKey($type, 'hide_price'));
$hide_amount = setting($this->getSettingKey($type, 'hide_amount'));
$item_name = setting($this->getDocumentSettingKey($type, 'item_name'));
$item_name_input = setting($this->getDocumentSettingKey($type, 'item_name_input'));
$price_name = setting($this->getDocumentSettingKey($type, 'price_name'));
$price_name_input = setting($this->getDocumentSettingKey($type, 'price_name_input'));
$quantity_name = setting($this->getDocumentSettingKey($type, 'quantity_name'));
$quantity_name_input = setting($this->getDocumentSettingKey($type, 'quantity_name_input'));
$hide_item_name = setting($this->getDocumentSettingKey($type, 'hide_item_name'));
$hide_item_description = setting($this->getDocumentSettingKey($type, 'hide_item_description'));
$hide_quantity = setting($this->getDocumentSettingKey($type, 'hide_quantity'));
$hide_price = setting($this->getDocumentSettingKey($type, 'hide_price'));
$hide_amount = setting($this->getDocumentSettingKey($type, 'hide_amount'));
$html = view('modals.documents.item_columns', compact(
'type',
@ -117,7 +117,7 @@ class DocumentItemColumns extends Controller
}
foreach ($fields as $key => $value) {
$real_key = $this->getSettingKey($type, $key);
$real_key = $this->getDocumentSettingKey($type, $key);
// Don't process unwanted keys
if (in_array($key, $this->skip_keys)) {

View File

@ -51,9 +51,9 @@ class DocumentTransactions extends Controller
$document->{$document_total->code} = $document_total->amount;
}
$total = money($document->total, $currency->code, true)->format();
$total = money($document->total, $currency->code)->format();
$document->grand_total = money($total, $currency->code)->getAmount();
$document->grand_total = money($total, $currency->code, false)->getAmount();
if (! empty($paid)) {
$document->grand_total = round($document->total - $paid, $currency->precision);
@ -140,7 +140,7 @@ class DocumentTransactions extends Controller
$number = $transaction->number;
$document->grand_total = money($transaction->amount, $currency->code)->getAmount();
$document->grand_total = money($transaction->amount, $currency->code, false)->getAmount();
$document->paid_at = $transaction->paid_at;

View File

@ -242,16 +242,30 @@ class Item extends Controller
$this->dispatch(new InstallModule($request['alias'], company_id()));
$name = module($request['alias'])->getName();
$module_routes = module_attribute($request['alias'], 'routes', []);
$message = trans('modules.installed', ['module' => $name]);
flash($message)->success();
$redirect = route('apps.app.show', $request['alias']);
// Get module.json redirect route
if (! empty($module_routes['redirect_after_install'])) {
if (is_array($module_routes['redirect_after_install'])) {
$route = array_shift($module_routes['redirect_after_install']);
$redirect = route($route, $module_routes['redirect_after_install']);
} else {
$redirect = route($module_routes['redirect_after_install']);
}
}
$json = [
'success' => true,
'error' => false,
'message' => null,
'redirect' => route('apps.app.show', $request['alias']),
'redirect' => $redirect,
'data' => [
'name' => $name,
'alias' => $request['alias'],

View File

@ -194,20 +194,19 @@ class Tiles extends Controller
case 'paid':
$response = $this->getPaidModules($data);
$last_page = $response->last_page;
$last_page = ! empty($response) ? $response->last_page : 1;
$modules = $this->prepareModules($response);
break;
case 'new':
$response = $this->getNewModules($data);
$last_page = $response->last_page;
$last_page = ! empty($response) ? $response->last_page : 1;
$modules = $this->prepareModules($response);
break;
case 'free':
$response = $this->getFreeModules($data);
$last_page = $response->last_page;
$last_page = ! empty($response) ? $response->last_page : 1;
$modules = $this->prepareModules($response);
break;
case 'search':
@ -215,7 +214,7 @@ class Tiles extends Controller
$response = $this->getSearchModules($data);
$last_page = $response->last_page;
$last_page = ! empty($response) ? $response->last_page : 1;
$modules = $this->prepareModules($response);
break;
}

View File

@ -7,7 +7,6 @@ use App\Exports\Purchases\Bills as Export;
use App\Http\Requests\Common\Import as ImportRequest;
use App\Http\Requests\Document\Document as Request;
use App\Imports\Purchases\Bills as Import;
use App\Jobs\Banking\CreateBankingDocumentTransaction;
use App\Jobs\Document\CreateDocument;
use App\Jobs\Document\DeleteDocument;
use App\Jobs\Document\DuplicateDocument;
@ -31,7 +30,7 @@ class Bills extends Controller
*/
public function index()
{
$bills = Document::bill()->with('contact', 'transactions')->collect(['issued_at' => 'desc']);
$bills = Document::bill()->with('contact', 'items', 'item_taxes', 'last_history', 'transactions', 'totals', 'histories', 'media')->collect(['issued_at' => 'desc']);
return $this->response('purchases.bills.index', compact('bills'));
}

View File

@ -30,7 +30,7 @@ class Vendors extends Controller
*/
public function index()
{
$vendors = Contact::with('bills.transactions')->vendor()->collect();
$vendors = Contact::with('bills.histories', 'bills.totals', 'bills.transactions', 'media')->vendor()->collect();
return $this->response('purchases.vendors.index', compact('vendors'));
}
@ -152,7 +152,7 @@ class Vendors extends Controller
$response = $this->ajaxDispatch(new UpdateContact($vendor, $request));
if ($response['success']) {
$response['redirect'] = route('vendors.index');
$response['redirect'] = route('vendors.show', $response['data']->id);
$message = trans('messages.success.updated', ['type' => $vendor->name]);

View File

@ -30,7 +30,7 @@ class Customers extends Controller
*/
public function index()
{
$customers = Contact::customer()->with('invoices.transactions')->collect();
$customers = Contact::customer()->with('invoices.histories', 'invoices.totals', 'invoices.transactions', 'media')->collect();
return $this->response('sales.customers.index', compact('customers'));
}
@ -152,7 +152,7 @@ class Customers extends Controller
$response = $this->ajaxDispatch(new UpdateContact($customer, $request));
if ($response['success']) {
$response['redirect'] = route('customers.index');
$response['redirect'] = route('customers.show', $response['data']->id);
$message = trans('messages.success.updated', ['type' => $customer->name]);

View File

@ -10,9 +10,9 @@ use App\Imports\Sales\Invoices as Import;
use App\Jobs\Document\CreateDocument;
use App\Jobs\Document\DeleteDocument;
use App\Jobs\Document\DuplicateDocument;
use App\Jobs\Document\SendDocument;
use App\Jobs\Document\UpdateDocument;
use App\Models\Document\Document;
use App\Notifications\Sale\Invoice as Notification;
use App\Traits\Documents;
class Invoices extends Controller
@ -31,7 +31,7 @@ class Invoices extends Controller
*/
public function index()
{
$invoices = Document::invoice()->with('contact', 'transactions')->collect(['document_number'=> 'desc']);
$invoices = Document::invoice()->with('contact', 'items', 'item_taxes', 'last_history', 'transactions', 'totals', 'histories', 'media')->collect(['document_number'=> 'desc']);
return $this->response('sales.invoices.index', compact('invoices'));
}
@ -260,12 +260,17 @@ class Invoices extends Controller
return redirect()->back();
}
// Notify the customer
$invoice->contact->notify(new Notification($invoice, 'invoice_new_customer', true));
$response = $this->ajaxDispatch(new SendDocument($invoice));
event(new \App\Events\Document\DocumentSent($invoice));
if ($response['success']) {
$message = trans('documents.messages.email_sent', ['type' => trans_choice('general.invoices', 1)]);
flash(trans('documents.messages.email_sent', ['type' => trans_choice('general.invoices', 1)]))->success();
flash($message)->success();
} else {
$message = $response['message'];
flash($message)->error()->important();
}
return redirect()->back();
}

View File

@ -30,10 +30,10 @@ class Categories extends Controller
$query->withSubcategory();
}
$categories = $query->collect();
$types = $this->getCategoryTypes();
$categories = $query->type(array_keys($types))->collect();
return $this->response('settings.categories.index', compact('categories', 'types'));
}
@ -173,7 +173,9 @@ class Categories extends Controller
];
});
return view('settings.categories.edit', compact('category', 'types', 'type_disabled', 'categories'));
$parent_categories = $categories[$category->type] ?? [];
return view('settings.categories.edit', compact('category', 'types', 'type_disabled', 'categories', 'parent_categories'));
}
/**

View File

@ -232,13 +232,13 @@ class Currencies extends Controller
$code = request('code');
$currencies = Currency::all()->pluck('rate', 'code');
if ($code) {
$currency = config('money.' . $code);
$currencies = Currency::all()->pluck('rate', 'code');
$currency = config('money.currencies.' . $code);
$currency['rate'] = isset($currencies[$code]) ? $currencies[$code] : null;
$currency['symbol_first'] = $currency['symbol_first'] ? 1 : 0;
$currency['symbol_first'] = ! empty($currency['symbol_first']) ? 1 : 0;
$json = (object) $currency;
}

View File

@ -3,6 +3,7 @@
namespace App\Http\Controllers\Settings;
use App\Abstracts\Http\SettingController;
use App\Models\Banking\Account;
use App\Models\Setting\Category;
use App\Models\Setting\Tax;
@ -10,6 +11,8 @@ class Defaults extends SettingController
{
public function edit()
{
$accounts = Account::enabled()->orderBy('name')->get()->pluck('title', 'id');
$sales_categories = Category::income()->enabled()->orderBy('name')->take(setting('default.select_limit'))->get();
$sale_category_id = setting('default.income_category');
@ -37,6 +40,7 @@ class Defaults extends SettingController
$taxes = Tax::enabled()->orderBy('name')->get()->pluck('title', 'id');
return view('settings.default.edit', compact(
'accounts',
'sales_categories',
'purchases_categories',
'taxes',

View File

@ -16,7 +16,7 @@ class Kernel extends HttpKernel
protected $middleware = [
\App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\TrustProxies::class,
\Fruitcake\Cors\HandleCors::class,
\Illuminate\Http\Middleware\HandleCors::class,
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
@ -141,13 +141,13 @@ class Kernel extends HttpKernel
];
/**
* The application's route middleware.
* The application's middleware aliases.
*
* These middleware may be assigned to groups or used individually.
* Aliases may be used to conveniently assign middleware to routes and groups.
*
* @var array
* @var array<string, class-string|string>
*/
protected $routeMiddleware = [
protected $middlewareAliases = [
// Laravel
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,

View File

@ -26,11 +26,11 @@ class Browser extends Component
$this->status = false;
}
} elseif (Str::contains($user_agent, 'Edg')) {
// $view = 'livewire.notification.browser.chrome';
} elseif (Str::contains($user_agent, 'Safari')) {
// $view = 'livewire.notification.browser.edge';
} elseif (Str::contains($user_agent, 'Chrome')) {
} elseif (Str::contains($user_agent, 'Safari')) {
// $view = 'livewire.notification.browser.safari';
} elseif (Str::contains($user_agent, 'Chrome')) {
// $view = 'livewire.notification.browser.chrome';
} elseif (Str::contains($user_agent, 'Opera')) {
// $view = 'livewire.notification.browser.opera';
}

View File

@ -2,6 +2,7 @@
namespace App\Http\Middleware;
use App\Events\Common\DatesFormating;
use Closure;
use Date;
@ -10,14 +11,29 @@ class DateFormat
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param \Illuminate\Http\Request $request
* @param \Closure $next
*
* @return mixed
*/
public function handle($request, Closure $next)
{
if (($request->method() == 'POST') || ($request->method() == 'PATCH')) {
$fields = ['paid_at', 'due_at', 'issued_at', 'started_at', 'ended_at', 'expire_at'];
$columns = new \stdClass();
$columns->fields = [
'paid_at',
'due_at',
'issued_at',
'started_at',
'ended_at',
'expire_at',
'recurring_started_at',
'recurring_limit_date',
];
event(new DatesFormating($columns, $request));
$fields = $columns->fields;
foreach ($fields as $field) {
$date = $request->get($field);
@ -27,7 +43,7 @@ class DateFormat
}
if (Date::parse($date)->format('H:i:s') == '00:00:00') {
$new_date = Date::parse($date)->format('Y-m-d') . ' ' . Date::now()->format('H:i:s');
$new_date = Date::parse($date)->format('Y-m-d') . ' ' . Date::now()->format('H:i:s');
} else {
$new_date = Date::parse($date)->toDateTimeString();
}

View File

@ -84,7 +84,7 @@ class Money
$amount = $item['price'];
if (strpos($item['price'], config('money.' . $currency_code . '.symbol')) !== false) {
if (strpos($item['price'], config('money.currencies.' . $currency_code . '.symbol')) !== false) {
$amount = $this->getAmount($item['price'], $currency_code);
}
@ -101,11 +101,11 @@ class Money
protected function getAmount($money_format, $currency_code)
{
try {
if (config('money.' . $currency_code . '.decimal_mark') !== '.') {
$money_format = Str::replaceFirst('.', config('money.' . $currency_code . '.decimal_mark'), $money_format);
if (config('money.currencies.' . $currency_code . '.decimal_mark') !== '.') {
$money_format = Str::replaceFirst('.', config('money.currencies.' . $currency_code . '.decimal_mark'), $money_format);
}
$amount = money($money_format, $currency_code)->getAmount();
$amount = money($money_format, $currency_code, false)->getAmount();
} catch (InvalidArgumentException | OutOfBoundsException | UnexpectedValueException $e) {
report($e);

View File

@ -0,0 +1,30 @@
<?php
namespace App\Http\Requests\Auth;
use Illuminate\Foundation\Http\FormRequest;
class Forgot extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'email' => 'required|email',
];
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Http\Requests\Auth;
use Illuminate\Foundation\Http\FormRequest;
class Reset extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'token' => 'required',
'email' => 'required|email',
'password' => 'required|confirmed|min:6',
];
}
}

View File

@ -29,7 +29,7 @@ class Transaction extends FormRequest
}
// Get company id
$company_id = (int) $this->request->get('company_id');
$company_id = (int) $this->request->get('company_id', company_id());
$attachment = 'nullable';
@ -37,7 +37,7 @@ class Transaction extends FormRequest
$attachment = 'mimes:' . config('filesystems.mimes') . '|between:0,' . config('filesystems.max_size') * 1024;
}
return [
$rules = [
'type' => 'required|string',
'number' => 'required|string|unique:transactions,NULL,' . $id . ',id,company_id,' . $company_id . ',deleted_at,NULL',
'account_id' => 'required|integer',
@ -53,6 +53,29 @@ class Transaction extends FormRequest
'recurring_count' => 'gte:0',
'recurring_interval' => 'exclude_unless:recurring_frequency,custom|gt:0',
];
// Is Recurring
if ($this->request->has('recurring_frequency')) {
// first line of the recurring rule
if ($this->request->get('recurring_frequency') == 'custom') {
$rules['recurring_interval'] = 'required|gte:1';
$rules['recurring_custom_frequency'] = 'required|string|in:daily,weekly,monthly,yearly';
}
// second line of the recurring rule
$rules['recurring_started_at'] = 'required|date_format:Y-m-d H:i:s';
switch($this->request->get('recurring_limit')) {
case 'date':
$rules['recurring_limit_date'] = 'required|date_format:Y-m-d H:i:s|after_or_equal:recurring_started_at';
break;
case 'count':
$rules['recurring_limit_count'] = 'required|gte:0';
break;
}
}
return $rules;
}
public function withValidator($validator)
@ -61,6 +84,18 @@ class Transaction extends FormRequest
$paid_at = Date::parse($this->request->get('paid_at'))->format('Y-m-d');
$this->request->set('paid_at', $paid_at);
if ($this->request->get('recurring_started_at')) {
$recurring_started_at = Date::parse($this->request->get('recurring_started_at'))->format('Y-m-d');
$this->request->set('recurring_started_at', $recurring_started_at);
}
if ($this->request->get('recurring_limit_date')) {
$recurring_limit_date = Date::parse($this->request->get('recurring_limit_date'))->format('Y-m-d');
$this->request->set('recurring_limit_date', $recurring_limit_date);
}
}
}
}

View File

@ -14,9 +14,10 @@ class CustomMail extends FormRequest
public function rules()
{
return [
'to' => 'required|email',
'subject' => 'required|string',
'body' => 'required|string',
'to' => 'required|email',
'subject' => 'required|string',
'body' => 'required|string',
'attachments.*' => 'nullable|boolean',
];
}
}

View File

@ -31,7 +31,7 @@ class Item extends FormRequest
}
return [
'type' => 'required|string',
'type' => 'required|string|in:product,service',
'name' => 'required|string',
'sale_price' => $sale_price . '|regex:/^(?=.*?[0-9])[0-9.,]+$/',
'purchase_price' => $purchase_price . '|regex:/^(?=.*?[0-9])[0-9.,]+$/',

View File

@ -48,6 +48,7 @@ class Document extends FormRequest
$rules = [
'type' => 'required|string',
'document_number' => 'required|string|unique:documents,NULL,' . $id . ',id,type,' . $type . ',company_id,' . $company_id . ',deleted_at,NULL',
//'status' => 'required|string|in:draft,paid,partial,sent,received,viewed,cancelled',
'status' => 'required|string',
'issued_at' => 'required|date_format:Y-m-d H:i:s|before_or_equal:due_at',
'due_at' => 'required|date_format:Y-m-d H:i:s|after_or_equal:issued_at',
@ -65,6 +66,27 @@ class Document extends FormRequest
'recurring_interval' => 'exclude_unless:recurring_frequency,custom|gt:0',
];
// Is Recurring
if ($this->request->has('recurring_frequency')) {
// first line of the recurring rule
if ($this->request->get('recurring_frequency') == 'custom') {
$rules['recurring_interval'] = 'required|gte:1';
$rules['recurring_custom_frequency'] = 'required|string|in:daily,weekly,monthly,yearly';
}
// second line of the recurring rule
$rules['recurring_started_at'] = 'required|date_format:Y-m-d H:i:s';
switch($this->request->get('recurring_limit')) {
case 'date':
$rules['recurring_limit_date'] = 'required|date_format:Y-m-d H:i:s|after_or_equal:recurring_started_at';
break;
case 'count':
$rules['recurring_limit_count'] = 'required|gte:0';
break;
}
}
$items = $this->request->all('items');
if ($items) {
@ -93,6 +115,18 @@ class Document extends FormRequest
$this->request->set('issued_at', $issued_at);
$this->request->set('due_at', $due_at);
if ($this->request->has('recurring_started_at')) {
$recurring_started_at = Date::parse($this->request->get('recurring_started_at'))->format('Y-m-d');
$this->request->set('recurring_started_at', $recurring_started_at);
}
if ($this->request->has('recurring_limit_date')) {
$recurring_limit_date = Date::parse($this->request->get('recurring_limit_date'))->format('Y-m-d');
$this->request->set('recurring_limit_date', $recurring_limit_date);
}
}
}

View File

@ -13,9 +13,11 @@ class Category extends FormRequest
*/
public function rules()
{
$types = collect(config('type.category'))->keys();
return [
'name' => 'required|string',
'type' => 'required|string',
'type' => 'required|string|in:' . $types->implode(','),
'color' => 'required|string',
];
}

View File

@ -45,6 +45,10 @@ class Setting extends FormRequest
$rules['number_digit'] = 'required|integer|min:1|max:20';
}
if ($this->request->has('number_next')) {
$rules['number_next'] = 'required|integer';
}
return $rules;
}

View File

@ -22,9 +22,9 @@ class Tax extends FormRequest
$enabled = 'nullable';
}
$company_id = (int) $this->request->get('company_id');
$company_id = (int) $this->request->get('company_id', company_id());
$type = 'required|string';
$type = 'required|string|in:fixed,normal,inclusive,withholding,compound';
if (!empty($this->request->get('type')) && $this->request->get('type') == 'compound') {
$type .= '|unique:taxes,NULL,' . $id . ',id,company_id,' . $company_id . ',type,compound,deleted_at,NULL';
@ -32,7 +32,7 @@ class Tax extends FormRequest
return [
'name' => 'required|string',
'rate' => 'required|min:0|max:100',
'rate' => 'required|numeric|min:0|max:100',
'type' => $type,
'enabled' => $enabled,
];

View File

@ -22,9 +22,9 @@ class Account extends JsonResource
'number' => $this->number,
'currency_code' => $this->currency_code,
'opening_balance' => $this->opening_balance,
'opening_balance_formatted' => money($this->opening_balance, $this->currency_code, true)->format(),
'opening_balance_formatted' => money($this->opening_balance, $this->currency_code)->format(),
'current_balance' => $this->balance,
'current_balance_formatted' => money($this->balance, $this->currency_code, true)->format(),
'current_balance_formatted' => money($this->balance, $this->currency_code)->format(),
'bank_name' => $this->bank_name,
'bank_phone' => $this->bank_phone,
'bank_address' => $this->bank_address,

View File

@ -22,7 +22,7 @@ class Reconciliation extends JsonResource
'started_at' => $this->started_at->toIso8601String(),
'ended_at' => $this->ended_at->toIso8601String(),
'closing_balance' => $this->closing_balance,
'closing_balance_formatted' => money($this->closing_balance, default_currency(), true)->format(),
'closing_balance_formatted' => money($this->closing_balance)->format(),
'reconciled' => $this->reconciled,
'created_from' => $this->created_from,
'created_by' => $this->created_by,

View File

@ -25,7 +25,7 @@ class Transaction extends JsonResource
'account_id' => $this->account_id,
'paid_at' => $this->paid_at->toIso8601String(),
'amount' => $this->amount,
'amount_formatted' => money($this->amount, $this->currency_code, true)->format(),
'amount_formatted' => money($this->amount, $this->currency_code)->format(),
'currency_code' => $this->currency_code,
'currency_rate' => $this->currency_rate,
'document_id' => $this->document_id,

View File

@ -25,7 +25,7 @@ class Transfer extends JsonResource
'to_account' => $income_transaction->account->name,
'to_account_id' => $income_transaction->account->id,
'amount' => $expense_transaction->amount,
'amount_formatted' => money($expense_transaction->amount, $expense_transaction->currency_code, true)->format(),
'amount_formatted' => money($expense_transaction->amount, $expense_transaction->currency_code)->format(),
'currency_code' => $expense_transaction->currency_code,
'paid_at' => $expense_transaction->paid_at ? $expense_transaction->paid_at->toIso8601String() : '',
'created_from' => $this->created_from,

View File

@ -23,9 +23,9 @@ class Item extends JsonResource
'name' => $this->name,
'description' => $this->description,
'sale_price' => $this->sale_price,
'sale_price_formatted' => money($this->sale_price, default_currency(), true)->format(),
'sale_price_formatted' => money((double) $this->sale_price)->format(),
'purchase_price' => $this->purchase_price,
'purchase_price_formatted' => money($this->purchase_price, default_currency(), true)->format(),
'purchase_price_formatted' => money((double) $this->purchase_price)->format(),
'category_id' => $this->category_id,
'picture' => $this->picture,
'enabled' => $this->enabled,

View File

@ -8,6 +8,7 @@ use App\Http\Resources\Document\DocumentHistory;
use App\Http\Resources\Document\DocumentItem;
use App\Http\Resources\Document\DocumentItemTax;
use App\Http\Resources\Document\DocumentTotal;
use App\Http\Resources\Setting\Category;
use App\Http\Resources\Setting\Currency;
use Illuminate\Http\Resources\Json\JsonResource;
@ -31,7 +32,8 @@ class Document extends JsonResource
'issued_at' => $this->issued_at ? $this->issued_at->toIso8601String() : '',
'due_at' => $this->due_at ? $this->due_at->toIso8601String() : '',
'amount' => $this->amount,
'amount_formatted' => money($this->amount, $this->currency_code, true)->format(),
'amount_formatted' => money($this->amount, $this->currency_code)->format(),
'category_id' => $this->category_id,
'currency_code' => $this->currency_code,
'currency_rate' => $this->currency_rate,
'contact_id' => $this->contact_id,
@ -50,6 +52,7 @@ class Document extends JsonResource
'created_by' => $this->created_by,
'created_at' => $this->created_at ? $this->created_at->toIso8601String() : '',
'updated_at' => $this->updated_at ? $this->updated_at->toIso8601String() : '',
'category' => new Category($this->category),
'currency' => new Currency($this->currency),
'contact' => new Contact($this->contact),
'histories' => [static::$wrap => DocumentHistory::collection($this->histories)],

View File

@ -22,10 +22,11 @@ class DocumentItem extends JsonResource
'document_id' => $this->document_id,
'item_id' => $this->item_id,
'name' => $this->name,
'description' => $this->description,
'price' => $this->price,
'price_formatted' => money($this->price, $this->document->currency_code, true)->format(),
'price_formatted' => money($this->price, $this->document->currency_code)->format(),
'total' => $this->total,
'total_formatted' => money($this->total, $this->document->currency_code, true)->format(),
'total_formatted' => money($this->total, $this->document->currency_code)->format(),
'created_from' => $this->created_from,
'created_by' => $this->created_by,
'created_at' => $this->created_at ? $this->created_at->toIso8601String() : '',

View File

@ -24,7 +24,7 @@ class DocumentItemTax extends JsonResource
'tax_id' => $this->tax_id,
'name' => $this->name,
'amount' => $this->amount,
'amount_formatted' => money($this->amount, $this->document->currency_code, true)->format(),
'amount_formatted' => money($this->amount, $this->document->currency_code)->format(),
'created_from' => $this->created_from,
'created_by' => $this->created_by,
'created_at' => $this->created_at ? $this->created_at->toIso8601String() : '',

View File

@ -22,7 +22,7 @@ class DocumentTotal extends JsonResource
'code' => $this->code,
'name' => $this->name,
'amount' => $this->amount,
'amount_formatted' => money($this->amount, $this->document->currency_code, true)->format(),
'amount_formatted' => money($this->amount, $this->document->currency_code)->format(),
'sort_order' => $this->sort_order,
'created_from' => $this->created_from,
'created_by' => $this->created_by,

View File

@ -8,6 +8,8 @@ use App\Models\Banking\Transaction as Model;
class Transactions extends Import
{
public $request_class = Request::class;
public function model(array $row)
{
return new Model($row);
@ -17,17 +19,12 @@ class Transactions extends Import
{
$row = parent::map($row);
$row['currency_code'] = $this->getCurrencyCode($row);
$row['account_id'] = $this->getAccountId($row);
$row['category_id'] = $this->getCategoryId($row);
$row['contact_id'] = $this->getContactId($row);
$row['currency_code'] = $this->getCurrencyCode($row);
$row['document_id'] = $this->getDocumentId($row);
return $row;
}
public function rules(): array
{
return (new Request())->rules();
}
}

View File

@ -26,10 +26,10 @@ class Transfers extends Import
$row = parent::map($row);
$row['transferred_at'] = Date::parse($row['transferred_at'])->format('Y-m-d');
$row['from_account_id'] = $this->getFromAccountId($row);
$row['to_account_id'] = $this->getToAccountId($row);
$row['from_currency_code'] = $this->getFromCurrencyCode($row);
$row['to_currency_code'] = $this->getToCurrencyCode($row);
$row['from_account_id'] = $this->getFromAccountId($row);
$row['to_account_id'] = $this->getToAccountId($row);
$row['expense_transaction_id'] = $this->getExpenseTransactionId($row);
$row['income_transaction_id'] = $this->getIncomeTransactionId($row);
@ -41,10 +41,10 @@ class Transfers extends Import
return [
'from_account_id' => 'required|integer',
'from_currency_code' => 'required|string|currency',
'from_currency_rate' => 'required',
'from_currency_rate' => 'required|gt:0',
'to_account_id' => 'required|integer',
'to_currency_code' => 'required|string|currency',
'to_currency_rate' => 'required',
'to_currency_rate' => 'required|gt:0',
'amount' => 'required|amount',
'transferred_at' => 'required|date_format:Y-m-d',
'payment_method' => 'required|string',

View File

@ -4,11 +4,12 @@ namespace App\Imports\Common\Sheets;
use App\Abstracts\Import;
use App\Http\Requests\Common\ItemTax as Request;
use App\Models\Common\Item;
use App\Models\Common\ItemTax as Model;
class ItemTaxes extends Import
{
public $request_class = Request::class;
public function model(array $row)
{
return new Model($row);
@ -16,21 +17,16 @@ class ItemTaxes extends Import
public function map($row): array
{
$row = parent::map($row);
$row['item_id'] = (int) Item::where('name', $row['item_name'])->value('id');
if ($this->isEmpty($row, 'item_id')) {
if ($this->isEmpty($row, 'item_name')) {
return [];
}
$row = parent::map($row);
$row['item_id'] = $this->getItemId($row);
$row['tax_id'] = $this->getTaxId($row);
return $row;
}
public function rules(): array
{
return (new Request())->rules();
}
}

View File

@ -8,6 +8,8 @@ use App\Models\Common\Item as Model;
class Items extends Import
{
public $request_class = Request::class;
public function model(array $row)
{
return new Model($row);
@ -25,9 +27,4 @@ class Items extends Import
return $row;
}
public function rules(): array
{
return (new Request())->rules();
}
}

View File

@ -9,6 +9,8 @@ use App\Models\Document\DocumentHistory as Model;
class BillHistories extends Import
{
public $request_class = Request::class;
public function model(array $row)
{
return new Model($row);
@ -20,6 +22,8 @@ class BillHistories extends Import
return [];
}
$row['bill_number'] = (string) $row['bill_number'];
$row = parent::map($row);
$row['document_id'] = (int) Document::bill()->number($row['bill_number'])->pluck('id')->first();
@ -31,10 +35,8 @@ class BillHistories extends Import
return $row;
}
public function rules(): array
public function prepareRules(array $rules): array
{
$rules = (new Request())->rules();
$rules['bill_number'] = 'required|string';
unset($rules['bill_id']);

View File

@ -11,6 +11,8 @@ use App\Models\Document\DocumentItemTax as Model;
class BillItemTaxes extends Import
{
public $request_class = Request::class;
public function model(array $row)
{
return new Model($row);
@ -22,6 +24,8 @@ class BillItemTaxes extends Import
return [];
}
$row['bill_number'] = (string) $row['bill_number'];
$row = parent::map($row);
$row['document_id'] = (int) Document::bill()->number($row['bill_number'])->pluck('id')->first();
@ -44,10 +48,8 @@ class BillItemTaxes extends Import
return $row;
}
public function rules(): array
public function prepareRules(array $rules): array
{
$rules = (new Request())->rules();
$rules['bill_number'] = 'required|string';
unset($rules['bill_id']);

View File

@ -9,6 +9,8 @@ use App\Models\Document\DocumentItem as Model;
class BillItems extends Import
{
public $request_class = Request::class;
public function model(array $row)
{
return new Model($row);
@ -20,6 +22,8 @@ class BillItems extends Import
return [];
}
$row['bill_number'] = (string) $row['bill_number'];
$row = parent::map($row);
$row['document_id'] = (int) Document::bill()->number($row['bill_number'])->pluck('id')->first();
@ -30,6 +34,8 @@ class BillItems extends Import
$row['name'] = $row['item_name'];
}
$row['description'] = !empty($row['item_description']) ? $row['item_description'] : '';
$row['tax'] = (double) $row['tax'];
$row['tax_id'] = 0;
$row['type'] = Document::BILL_TYPE;
@ -37,10 +43,8 @@ class BillItems extends Import
return $row;
}
public function rules(): array
public function prepareRules(array $rules): array
{
$rules = (new Request())->rules();
$rules['bill_number'] = 'required|string';
unset($rules['bill_id']);

View File

@ -9,6 +9,8 @@ use App\Models\Document\DocumentTotal as Model;
class BillTotals extends Import
{
public $request_class = Request::class;
public function model(array $row)
{
return new Model($row);
@ -20,6 +22,8 @@ class BillTotals extends Import
return [];
}
$row['bill_number'] = (string) $row['bill_number'];
$row = parent::map($row);
$row['document_id'] = (int) Document::bill()->number($row['bill_number'])->pluck('id')->first();
@ -28,10 +32,8 @@ class BillTotals extends Import
return $row;
}
public function rules(): array
public function prepareRules(array $rules): array
{
$rules = (new Request())->rules();
$rules['bill_number'] = 'required|string';
unset($rules['bill_id']);

View File

@ -8,6 +8,8 @@ use App\Models\Banking\Transaction as Model;
class BillTransactions extends Import
{
public $request_class = Request::class;
public function model(array $row)
{
return new Model($row);
@ -19,6 +21,8 @@ class BillTransactions extends Import
return [];
}
$row['bill_number'] = (string) $row['bill_number'];
$row = parent::map($row);
$row['type'] = 'expense';
@ -32,10 +36,8 @@ class BillTransactions extends Import
return $row;
}
public function rules(): array
public function prepareRules(array $rules): array
{
$rules = (new Request())->rules();
$rules['bill_number'] = 'required|string';
return $rules;

View File

@ -9,6 +9,8 @@ use Illuminate\Support\Str;
class Bills extends Import
{
public $request_class = Request::class;
public function model(array $row)
{
return new Model($row);
@ -20,6 +22,8 @@ class Bills extends Import
return [];
}
$row['bill_number'] = (string) $row['bill_number'];
$row = parent::map($row);
$country = array_search($row['contact_country'], trans('countries'));
@ -35,16 +39,14 @@ class Bills extends Import
return $row;
}
public function rules(): array
public function prepareRules(array $rules): array
{
$rules = (new Request())->rules();
$rules['bill_number'] = Str::replaceFirst('unique:documents,NULL', 'unique:documents,document_number', $rules['document_number']);
$rules['billed_at'] = $rules['issued_at'];
$rules['currency_rate'] = 'required';
$rules['currency_rate'] = 'required|gt:0';
unset($rules['document_number'], $rules['issued_at'], $rules['type']);
return $this->replaceForBatchRules($rules);
return $rules;
}
}

View File

@ -8,6 +8,8 @@ use App\Models\Common\Contact as Model;
class Vendors extends Import
{
public $request_class = Request::class;
public function model(array $row)
{
return new Model($row);
@ -26,9 +28,4 @@ class Vendors extends Import
return $row;
}
public function rules(): array
{
return (new Request())->rules();
}
}

View File

@ -3,11 +3,14 @@
namespace App\Imports\Sales;
use App\Abstracts\Import;
use App\Models\Auth\User;
use App\Http\Requests\Common\Contact as Request;
use App\Models\Common\Contact as Model;
class Customers extends Import
{
public $request_class = Request::class;
public function model(array $row)
{
return new Model($row);
@ -24,11 +27,10 @@ class Customers extends Import
$row['currency_code'] = $this->getCurrencyCode($row);
$row['user_id'] = null;
if (isset($row['can_login']) && isset($row['email'])) {
$row['user_id'] = User::where('email', $row['email'])->first()?->id ?? null;
}
return $row;
}
public function rules(): array
{
return (new Request())->rules();
}
}

View File

@ -9,6 +9,8 @@ use App\Models\Document\DocumentHistory as Model;
class InvoiceHistories extends Import
{
public $request_class = Request::class;
public function model(array $row)
{
return new Model($row);
@ -20,6 +22,8 @@ class InvoiceHistories extends Import
return [];
}
$row['invoice_number'] = (string) $row['invoice_number'];
$row = parent::map($row);
$row['document_id'] = (int) Document::invoice()->number($row['invoice_number'])->pluck('id')->first();
@ -31,10 +35,8 @@ class InvoiceHistories extends Import
return $row;
}
public function rules(): array
public function prepareRules(array $rules): array
{
$rules = (new Request())->rules();
$rules['invoice_number'] = 'required|string';
unset($rules['invoice_id']);

View File

@ -11,6 +11,8 @@ use App\Models\Document\DocumentItemTax as Model;
class InvoiceItemTaxes extends Import
{
public $request_class = Request::class;
public function model(array $row)
{
return new Model($row);
@ -22,6 +24,8 @@ class InvoiceItemTaxes extends Import
return [];
}
$row['invoice_number'] = (string) $row['invoice_number'];
$row = parent::map($row);
$row['document_id'] = (int) Document::invoice()->number($row['invoice_number'])->pluck('id')->first();
@ -44,10 +48,8 @@ class InvoiceItemTaxes extends Import
return $row;
}
public function rules(): array
public function prepareRules(array $rules): array
{
$rules = (new Request())->rules();
$rules['invoice_number'] = 'required|string';
unset($rules['invoice_id']);

View File

@ -9,6 +9,8 @@ use App\Models\Document\DocumentItem as Model;
class InvoiceItems extends Import
{
public $request_class = Request::class;
public function model(array $row)
{
return new Model($row);
@ -20,16 +22,20 @@ class InvoiceItems extends Import
return [];
}
$row['invoice_number'] = (string) $row['invoice_number'];
$row = parent::map($row);
$row['document_id'] = (int) Document::invoice()->number($row['invoice_number'])->pluck('id')->first();
if (empty($row['item_id']) && !empty($row['item_name'])) {
if (empty($row['item_id']) && ! empty($row['item_name'])) {
$row['item_id'] = $this->getItemIdFromName($row);
$row['name'] = $row['item_name'];
}
$row['description'] = !empty($row['item_description']) ? $row['item_description'] : '';
$row['tax'] = (double) $row['tax'];
$row['tax_id'] = 0;
$row['type'] = Document::INVOICE_TYPE;
@ -37,10 +43,8 @@ class InvoiceItems extends Import
return $row;
}
public function rules(): array
public function prepareRules(array $rules): array
{
$rules = (new Request())->rules();
$rules['invoice_number'] = 'required|string';
unset($rules['invoice_id']);

View File

@ -9,6 +9,8 @@ use App\Models\Document\DocumentTotal as Model;
class InvoiceTotals extends Import
{
public $request_class = Request::class;
public function model(array $row)
{
return new Model($row);
@ -20,6 +22,8 @@ class InvoiceTotals extends Import
return [];
}
$row['invoice_number'] = (string) $row['invoice_number'];
$row = parent::map($row);
$row['document_id'] = (int) Document::invoice()->number($row['invoice_number'])->pluck('id')->first();
@ -28,10 +32,8 @@ class InvoiceTotals extends Import
return $row;
}
public function rules(): array
public function prepareRules(array $rules): array
{
$rules = (new Request())->rules();
$rules['invoice_number'] = 'required|string';
unset($rules['invoice_id']);

View File

@ -8,6 +8,8 @@ use App\Models\Banking\Transaction as Model;
class InvoiceTransactions extends Import
{
public $request_class = Request::class;
public function model(array $row)
{
return new Model($row);
@ -19,23 +21,23 @@ class InvoiceTransactions extends Import
return [];
}
$row['invoice_number'] = (string) $row['invoice_number'];
$row = parent::map($row);
$row['type'] = 'income';
$row['currency_code'] = $this->getCurrencyCode($row);
$row['account_id'] = $this->getAccountId($row);
$row['category_id'] = $this->getCategoryId($row, 'income');
$row['contact_id'] = $this->getContactId($row, 'customer');
$row['currency_code'] = $this->getCurrencyCode($row);
$row['document_id'] = $this->getDocumentId($row);
$row['number'] = $row['transaction_number'];
return $row;
}
public function rules(): array
public function prepareRules(array $rules): array
{
$rules = (new Request())->rules();
$rules['invoice_number'] = 'required|string';
return $rules;

View File

@ -9,6 +9,8 @@ use Illuminate\Support\Str;
class Invoices extends Import
{
public $request_class = Request::class;
public function model(array $row)
{
return new Model($row);
@ -20,6 +22,8 @@ class Invoices extends Import
return [];
}
$row['invoice_number'] = (string) $row['invoice_number'];
$row = parent::map($row);
$country = array_search($row['contact_country'], trans('countries'));
@ -35,16 +39,14 @@ class Invoices extends Import
return $row;
}
public function rules(): array
public function prepareRules(array $rules): array
{
$rules = (new Request())->rules();
$rules['invoice_number'] = Str::replaceFirst('unique:documents,NULL', 'unique:documents,document_number', $rules['document_number']);
$rules['invoiced_at'] = $rules['issued_at'];
$rules['currency_rate'] = 'required';
$rules['currency_rate'] = 'required|gt:0';
unset($rules['document_number'], $rules['issued_at'], $rules['type']);
return $this->replaceForBatchRules($rules);
return $rules;
}
}

View File

@ -8,6 +8,8 @@ use App\Models\Setting\Category as Model;
class Categories extends Import
{
public $request_class = Request::class;
public function model(array $row)
{
return new Model($row);
@ -21,9 +23,4 @@ class Categories extends Import
return $row;
}
public function rules(): array
{
return (new Request())->rules();
}
}

View File

@ -8,13 +8,10 @@ use App\Models\Setting\Tax as Model;
class Taxes extends Import
{
public $request_class = Request::class;
public function model(array $row)
{
return new Model($row);
}
public function rules(): array
{
return (new Request())->rules();
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace App\Interfaces\Utility;
use App\Models\Common\Contact;
interface DocumentNumber
{
public function getNextNumber(string $type, ?Contact $contact): string;
public function increaseNextNumber(string $type, ?Contact $contact): void;
}

View File

@ -0,0 +1,12 @@
<?php
namespace App\Interfaces\Utility;
use App\Models\Common\Contact;
interface TransactionNumber
{
public function getNextNumber(string $type, string $suffix, ?Contact $contact): string;
public function increaseNextNumber(string $type, string $suffix, ?Contact $contact): void;
}

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