Merge pull request #3347 from CihanSenturk/add-category-tabs
Added Category Tabs
This commit is contained in:
commit
9ddab69242
|
|
@ -153,4 +153,54 @@ abstract class Controller extends BaseController
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function setActiveTabForCategories(): void
|
||||
{
|
||||
if (! request()->has('list_records') && ! request()->has('search')) {
|
||||
$tab_pins = setting('favorites.tab.' . user()->id, []);
|
||||
$tab_pins = ! empty($tab_pins) ? json_decode($tab_pins, true) : [];
|
||||
|
||||
if (! empty($tab_pins) && ! empty($tab_pins['categories'])) {
|
||||
$tab = $tab_pins['categories'];
|
||||
|
||||
if (! empty($tab)) {
|
||||
|
||||
request()->offsetSet('list_records', $tab);
|
||||
request()->offsetSet('programmatic', '1');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (request()->get('list_records') == 'all') {
|
||||
return;
|
||||
}
|
||||
|
||||
$types = $this->getSearchStringValue('type');
|
||||
|
||||
if (!empty($types)) {
|
||||
$types = is_string($types) ? explode(',', $types) : $types;
|
||||
|
||||
$tab = config('type.category.' . $types[0] . '.group') ? config('type.category.' . $types[0] . '.group') : 'all';
|
||||
|
||||
if (!empty($types) && count($types) > 0) {
|
||||
request()->offsetSet('list_records', $tab);
|
||||
|
||||
$currentSearch = request('search', '');
|
||||
$searchParts = array_filter(explode(' ', $currentSearch), function($part) {
|
||||
return !empty(trim($part)) && !str_starts_with(trim($part), 'type:');
|
||||
});
|
||||
|
||||
$searchParts[] = 'type:' . implode(',', $types);
|
||||
|
||||
request()->offsetSet('search', implode(' ', $searchParts));
|
||||
request()->offsetSet('programmatic', '1');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($tab)) {
|
||||
request()->offsetSet('list_records', 'all');
|
||||
request()->offsetSet('programmatic', '1');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,13 +5,14 @@ namespace App\Abstracts\Listeners;
|
|||
use App\Models\Banking\Account;
|
||||
use App\Models\Common\Contact;
|
||||
use App\Models\Setting\Category;
|
||||
use App\Traits\Categories;
|
||||
use App\Traits\Contacts;
|
||||
use App\Traits\DateTime;
|
||||
use App\Traits\SearchString;
|
||||
|
||||
abstract class Report
|
||||
{
|
||||
use Contacts, DateTime, SearchString;
|
||||
use Categories, Contacts, DateTime, SearchString;
|
||||
|
||||
protected $classes = [];
|
||||
|
||||
|
|
@ -90,22 +91,24 @@ abstract class Report
|
|||
|
||||
public function getItemCategories($limit = false)
|
||||
{
|
||||
return $this->getCategories('item', $limit);
|
||||
return $this->getCategories($this->getItemCategoryTypes(), $limit);
|
||||
}
|
||||
|
||||
public function getIncomeCategories($limit = false)
|
||||
{
|
||||
return $this->getCategories('income', $limit);
|
||||
return $this->getCategories($this->getIncomeCategoryTypes(), $limit);
|
||||
}
|
||||
|
||||
public function getExpenseCategories($limit = false)
|
||||
{
|
||||
return $this->getCategories('expense', $limit);
|
||||
return $this->getCategories($this->getExpenseCategoryTypes(), $limit);
|
||||
}
|
||||
|
||||
public function getIncomeExpenseCategories($limit = false)
|
||||
{
|
||||
return $this->getCategories(['income', 'expense'], $limit);
|
||||
$types = array_merge($this->getIncomeCategoryTypes(), $this->getExpenseCategoryTypes());
|
||||
|
||||
return $this->getCategories($types, $limit);
|
||||
}
|
||||
|
||||
public function getCategories($types, $limit = false)
|
||||
|
|
|
|||
|
|
@ -25,9 +25,11 @@ class Categories extends Export
|
|||
public function fields(): array
|
||||
{
|
||||
return [
|
||||
'code',
|
||||
'name',
|
||||
'type',
|
||||
'color',
|
||||
'description',
|
||||
'parent_name',
|
||||
'enabled',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -6,10 +6,14 @@ use App\Abstracts\Http\Controller;
|
|||
use App\Http\Requests\Setting\Category as Request;
|
||||
use App\Jobs\Setting\CreateCategory;
|
||||
use App\Models\Setting\Category;
|
||||
use App\Traits\Categories as Helper;
|
||||
use App\Traits\Modules;
|
||||
use Illuminate\Http\Request as IRequest;
|
||||
|
||||
class Categories extends Controller
|
||||
{
|
||||
use Helper, Modules;
|
||||
|
||||
/**
|
||||
* Instantiate a new controller instance.
|
||||
*/
|
||||
|
|
@ -29,11 +33,42 @@ class Categories extends Controller
|
|||
*/
|
||||
public function create(IRequest $request)
|
||||
{
|
||||
$type = $request->get('type', 'item');
|
||||
$type = $request->get('type', Category::ITEM_TYPE);
|
||||
|
||||
$type_codes = [];
|
||||
|
||||
switch ($type) {
|
||||
case Category::INCOME_TYPE:
|
||||
$types = $this->getIncomeCategoryTypes();
|
||||
break;
|
||||
case Category::EXPENSE_TYPE:
|
||||
$types = $this->getExpenseCategoryTypes();
|
||||
break;
|
||||
case Category::ITEM_TYPE:
|
||||
$types = $this->getItemCategoryTypes();
|
||||
break;
|
||||
case Category::OTHER_TYPE:
|
||||
$types = $this->getOtherCategoryTypes();
|
||||
break;
|
||||
default:
|
||||
$types = [$type];
|
||||
}
|
||||
|
||||
foreach ($types as $type) {
|
||||
$config_type = config('type.category.' . $type, []);
|
||||
$type_codes[$type] = empty($config_type['hide']) || ! in_array('code', $config_type['hide']);
|
||||
}
|
||||
|
||||
$config_type = config('type.category.' . $type, []);
|
||||
$show_code_field = ! empty($config_type['hide']) && in_array('code', $config_type['hide']) ? false : true;
|
||||
|
||||
$categories = collect();
|
||||
|
||||
Category::type($type)->enabled()->orderBy('name')->get()->each(function ($category) use (&$categories) {
|
||||
Category::type($types)
|
||||
->enabled()
|
||||
->orderBy('name')
|
||||
->get()
|
||||
->each(function ($category) use (&$categories) {
|
||||
$categories->push([
|
||||
'id' => $category->id,
|
||||
'title' => $category->name,
|
||||
|
|
@ -41,7 +76,7 @@ class Categories extends Controller
|
|||
]);
|
||||
});
|
||||
|
||||
$html = view('modals.categories.create', compact('type', 'categories'))->render();
|
||||
$html = view('modals.categories.create', compact('type', 'types', 'categories', 'show_code_field', 'type_codes'))->render();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
|
|
@ -61,7 +96,7 @@ class Categories extends Controller
|
|||
public function store(Request $request)
|
||||
{
|
||||
$request['enabled'] = 1;
|
||||
$request['type'] = $request->get('type', 'income');
|
||||
$request['type'] = $request->get('type', Category::ITEM_TYPE);
|
||||
$request['color'] = $request->get('color', '#' . dechex(rand(0x000000, 0xFFFFFF)));
|
||||
|
||||
$response = $this->ajaxDispatch(new CreateCategory($request));
|
||||
|
|
|
|||
|
|
@ -24,17 +24,44 @@ class Categories extends Controller
|
|||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->setActiveTabForCategories();
|
||||
|
||||
$query = Category::with('sub_categories');
|
||||
|
||||
if (request()->has('search')) {
|
||||
$query->withSubcategory();
|
||||
if (search_string_value('searchable')) {
|
||||
$query->withSubCategory();
|
||||
}
|
||||
|
||||
$types = $this->getCategoryTypes();
|
||||
|
||||
$categories = $query->type(array_keys($types))->collect();
|
||||
if (request()->get('list_records') == 'all') {
|
||||
$query->type(array_keys($types));
|
||||
}
|
||||
|
||||
return $this->response('settings.categories.index', compact('categories', 'types'));
|
||||
$categories = $query->collect();
|
||||
|
||||
$tabs = $this->getCategoryTabs();
|
||||
|
||||
$tab = request()->get('list_records');
|
||||
$tab_active = ! empty($tab) ? 'categories-' . $tab : 'categories-all';
|
||||
|
||||
$hide_code_column = true;
|
||||
|
||||
$search_string_type = search_string_value('type');
|
||||
$selected_types = ! empty($search_string_type) ? explode(',', $search_string_type) : array_keys($types);
|
||||
|
||||
foreach (config('type.category', []) as $type => $config) {
|
||||
if (! in_array($type, $selected_types)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (empty($config['hide']) || !in_array('code', $config['hide'])) {
|
||||
$hide_code_column = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->response('settings.categories.index', compact('categories', 'types', 'tabs', 'tab_active', 'hide_code_column'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -54,11 +81,13 @@ class Categories extends Controller
|
|||
*/
|
||||
public function create()
|
||||
{
|
||||
$types = $this->getCategoryTypes();
|
||||
$types = $this->getCategoryTypes(true, true);
|
||||
|
||||
$categories = [];
|
||||
$type_codes = [];
|
||||
|
||||
foreach (config('type.category') as $type => $config) {
|
||||
$type_codes[$type] = empty($config['hide']) || ! in_array('code', $config['hide']);
|
||||
$categories[$type] = [];
|
||||
}
|
||||
|
||||
|
|
@ -70,7 +99,7 @@ class Categories extends Controller
|
|||
];
|
||||
});
|
||||
|
||||
return view('settings.categories.create', compact('types', 'categories'));
|
||||
return view('settings.categories.create', compact('types', 'categories', 'type_codes'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -134,15 +163,17 @@ class Categories extends Controller
|
|||
*/
|
||||
public function edit(Category $category)
|
||||
{
|
||||
$types = $this->getCategoryTypes();
|
||||
$types = $this->getCategoryTypes(true, true);
|
||||
|
||||
$type_disabled = (Category::where('type', $category->type)->count() == 1) ?: false;
|
||||
|
||||
$edited_category_id = $category->id;
|
||||
|
||||
$categories = [];
|
||||
$type_codes = [];
|
||||
|
||||
foreach (config('type.category') as $type => $config) {
|
||||
$type_codes[$type] = empty($config['hide']) || ! in_array('code', $config['hide']);
|
||||
$categories[$type] = [];
|
||||
}
|
||||
|
||||
|
|
@ -175,7 +206,7 @@ class Categories extends Controller
|
|||
|
||||
$parent_categories = $categories[$category->type] ?? [];
|
||||
|
||||
return view('settings.categories.edit', compact('category', 'types', 'type_disabled', 'categories', 'parent_categories'));
|
||||
return view('settings.categories.edit', compact('category', 'types', 'type_disabled', 'categories', 'parent_categories', 'type_codes'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -6,9 +6,12 @@ use App\Abstracts\Http\SettingController;
|
|||
use App\Models\Banking\Account;
|
||||
use App\Models\Setting\Category;
|
||||
use App\Models\Setting\Tax;
|
||||
use App\Traits\Categories;
|
||||
|
||||
class Defaults extends SettingController
|
||||
{
|
||||
use Categories;
|
||||
|
||||
public function edit()
|
||||
{
|
||||
$accounts = Account::enabled()->orderBy('name')->get()->pluck('title', 'id');
|
||||
|
|
@ -39,11 +42,16 @@ class Defaults extends SettingController
|
|||
|
||||
$taxes = Tax::enabled()->orderBy('name')->get()->pluck('title', 'id');
|
||||
|
||||
$income_category_types = $this->getIncomeCategoryTypes('string');
|
||||
$expense_category_types = $this->getExpenseCategoryTypes('string');
|
||||
|
||||
return view('settings.default.edit', compact(
|
||||
'accounts',
|
||||
'sales_categories',
|
||||
'purchases_categories',
|
||||
'taxes',
|
||||
'income_category_types',
|
||||
'expense_category_types',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,8 +15,14 @@ class Category extends FormRequest
|
|||
{
|
||||
$types = collect(config('type.category'))->keys();
|
||||
|
||||
$type = $this->request->get('type');
|
||||
$config = config('type.category.' . $type, []);
|
||||
$code_hidden = !empty($config['hide']) && in_array('code', $config['hide']);
|
||||
$code = $code_hidden ? 'nullable|string' : 'required|string';
|
||||
|
||||
return [
|
||||
'name' => 'required|string',
|
||||
'code' => $code,
|
||||
'type' => 'required|string|in:' . $types->implode(','),
|
||||
'color' => 'required|string|colour',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -17,9 +17,11 @@ class Category extends JsonResource
|
|||
return [
|
||||
'id' => $this->id,
|
||||
'company_id' => $this->company_id,
|
||||
'code' => $this->code,
|
||||
'name' => $this->name,
|
||||
'type' => $this->type,
|
||||
'color' => $this->color,
|
||||
'description' => $this->description,
|
||||
'enabled' => $this->enabled,
|
||||
'parent_id' => $this->parent_id,
|
||||
'created_from' => $this->created_from,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ namespace App\Imports\Purchases\Bills\Sheets;
|
|||
use App\Abstracts\Import;
|
||||
use App\Http\Requests\Banking\Transaction as Request;
|
||||
use App\Models\Banking\Transaction as Model;
|
||||
use App\Models\Setting\Category;
|
||||
|
||||
class BillTransactions extends Import
|
||||
{
|
||||
|
|
@ -36,9 +37,9 @@ class BillTransactions extends Import
|
|||
|
||||
$row = parent::map($row);
|
||||
|
||||
$row['type'] = 'expense';
|
||||
$row['type'] = Model::EXPENSE_TYPE;
|
||||
$row['account_id'] = $this->getAccountId($row);
|
||||
$row['category_id'] = $this->getCategoryId($row, 'expense');
|
||||
$row['category_id'] = $this->getCategoryId($row, Category::EXPENSE_TYPE);
|
||||
$row['contact_id'] = $this->getContactId($row, 'vendor');
|
||||
$row['currency_code'] = $this->getCurrencyCode($row);
|
||||
$row['document_id'] = $this->getDocumentId($row);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ namespace App\Imports\Sales\Invoices\Sheets;
|
|||
use App\Abstracts\Import;
|
||||
use App\Http\Requests\Banking\Transaction as Request;
|
||||
use App\Models\Banking\Transaction as Model;
|
||||
use App\Models\Setting\Category;
|
||||
|
||||
class InvoiceTransactions extends Import
|
||||
{
|
||||
|
|
@ -37,10 +38,10 @@ class InvoiceTransactions extends Import
|
|||
|
||||
$row = parent::map($row);
|
||||
|
||||
$row['type'] = 'income';
|
||||
$row['type'] = Model::INCOME_TYPE;
|
||||
$row['currency_code'] = $this->getCurrencyCode($row);
|
||||
$row['account_id'] = $this->getAccountId($row);
|
||||
$row['category_id'] = $this->getCategoryId($row, 'income');
|
||||
$row['category_id'] = $this->getCategoryId($row, Category::INCOME_TYPE);
|
||||
$row['contact_id'] = $this->getContactId($row, 'customer');
|
||||
$row['document_id'] = $this->getDocumentId($row);
|
||||
$row['number'] = $row['transaction_number'];
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ class Categories extends Import
|
|||
public $columns = [
|
||||
'name',
|
||||
'type',
|
||||
'code',
|
||||
'description',
|
||||
];
|
||||
|
||||
public function model(array $row)
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ class AddExpenseCategories extends Listener
|
|||
|
||||
// send true for add limit on search and filter..
|
||||
$event->class->filters['categories'] = $this->getExpenseCategories(true);
|
||||
$event->class->filters['routes']['categories'] = ['categories.index', 'search=type:expense enabled:1'];
|
||||
$event->class->filters['routes']['categories'] = ['categories.index', 'search=type:' . $this->getExpenseCategoryTypes('string') . ' enabled:1'];
|
||||
$event->class->filters['multiple']['categories'] = true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ class AddIncomeCategories extends Listener
|
|||
|
||||
// send true for add limit on search and filter..
|
||||
$event->class->filters['categories'] = $this->getIncomeCategories(true);
|
||||
$event->class->filters['routes']['categories'] = ['categories.index', 'search=type:income enabled:1'];
|
||||
$event->class->filters['routes']['categories'] = ['categories.index', 'search=type:' . $this->getIncomeCategoryTypes('string') . ' enabled:1'];
|
||||
$event->class->filters['multiple']['categories'] = true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ class AddIncomeExpenseCategories extends Listener
|
|||
}
|
||||
|
||||
$event->class->filters['categories'] = $this->getIncomeExpenseCategories(true);
|
||||
$event->class->filters['routes']['categories'] = ['categories.index', 'search=type:income,expense enabled:1'];
|
||||
$event->class->filters['routes']['categories'] = ['categories.index', 'search=type:' . implode(',', array_merge($this->getIncomeCategoryTypes(), $this->getExpenseCategoryTypes())) . ' enabled:1'];
|
||||
$event->class->filters['multiple']['categories'] = true;
|
||||
}
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ class AddIncomeExpenseCategories extends Listener
|
|||
return;
|
||||
}
|
||||
|
||||
$categories = Category::type(['income', 'expense'])->orderBy('name')->get();
|
||||
$categories = Category::type(array_merge($this->getIncomeCategoryTypes(), $this->getExpenseCategoryTypes()))->orderBy('name')->get();
|
||||
$rows = $categories->pluck('name', 'id')->toArray();
|
||||
|
||||
$this->setRowNamesAndValuesForCategories($event, $rows, $categories);
|
||||
|
|
@ -83,10 +83,12 @@ class AddIncomeExpenseCategories extends Listener
|
|||
{
|
||||
foreach ($event->class->dates as $date) {
|
||||
foreach ($event->class->tables as $table_key => $table_name) {
|
||||
$table_keys = $table_key == Category::INCOME_TYPE ? $this->getIncomeCategoryTypes() : $this->getExpenseCategoryTypes();
|
||||
|
||||
foreach ($rows as $id => $name) {
|
||||
$category = $categories->where('id', $id)->first();
|
||||
|
||||
if ($category->type != $table_key) {
|
||||
if (!in_array($category->type, $table_keys)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -100,10 +102,12 @@ class AddIncomeExpenseCategories extends Listener
|
|||
public function setTreeNodesForCategories($event, $nodes, $categories)
|
||||
{
|
||||
foreach ($event->class->tables as $table_key => $table_name) {
|
||||
$table_keys = $table_key == Category::INCOME_TYPE ? $this->getIncomeCategoryTypes() : $this->getExpenseCategoryTypes();
|
||||
|
||||
foreach ($nodes as $id => $node) {
|
||||
$category = $categories->where('id', $id)->first();
|
||||
|
||||
if ($category->type != $table_key) {
|
||||
if (!in_array($category->type, $table_keys)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -616,9 +616,10 @@ class Company extends Eloquent implements Ownable
|
|||
setting()->forgetAll();
|
||||
setting()->load(true);
|
||||
|
||||
// Override settings and currencies
|
||||
// Override settings, currencies, and category types
|
||||
Overrider::load('settings');
|
||||
Overrider::load('currencies');
|
||||
Overrider::load('categoryTypes');
|
||||
|
||||
event(new CompanyMadeCurrent($this));
|
||||
|
||||
|
|
|
|||
|
|
@ -4,11 +4,13 @@ namespace App\Models\Setting;
|
|||
|
||||
use App\Abstracts\Model;
|
||||
use App\Builders\Category as Builder;
|
||||
use App\Models\Banking\Transaction;
|
||||
use App\Models\Document\Document;
|
||||
use App\Interfaces\Export\WithParentSheet;
|
||||
use App\Relations\HasMany\Category as HasMany;
|
||||
use App\Scopes\Category as Scope;
|
||||
use App\Traits\Categories;
|
||||
use App\Traits\DateTime;
|
||||
use App\Traits\Tailwind;
|
||||
use App\Traits\Transactions;
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
|
|
@ -17,7 +19,7 @@ use Illuminate\Database\Eloquent\Model as EloquentModel;
|
|||
|
||||
class Category extends Model
|
||||
{
|
||||
use Categories, HasFactory, Tailwind, Transactions;
|
||||
use Categories, HasFactory, Tailwind, Transactions, DateTime;
|
||||
|
||||
public const INCOME_TYPE = 'income';
|
||||
public const EXPENSE_TYPE = 'expense';
|
||||
|
|
@ -33,14 +35,14 @@ class Category extends Model
|
|||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = ['company_id', 'name', 'type', 'color', 'enabled', 'created_from', 'created_by', 'parent_id'];
|
||||
protected $fillable = ['company_id', 'code', 'name', 'type', 'color', 'description', 'enabled', 'created_from', 'created_by', 'parent_id'];
|
||||
|
||||
/**
|
||||
* Sortable columns.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $sortable = ['name', 'type', 'enabled'];
|
||||
public $sortable = ['code', 'name', 'type', 'enabled'];
|
||||
|
||||
/**
|
||||
* The "booted" method of the model.
|
||||
|
|
@ -137,6 +139,18 @@ class Category extends Model
|
|||
return $this->hasMany('App\Models\Banking\Transaction');
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope code.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param $code
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function scopeCode($query, $code)
|
||||
{
|
||||
return $query->where('code', $code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope to only include categories of a given type.
|
||||
*
|
||||
|
|
@ -155,46 +169,50 @@ class Category extends Model
|
|||
|
||||
/**
|
||||
* Scope to include only income.
|
||||
* Uses Categories trait to support multiple income types (e.g. from modules).
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function scopeIncome($query)
|
||||
{
|
||||
return $query->where($this->qualifyColumn('type'), '=', 'income');
|
||||
return $query->whereIn($this->qualifyColumn('type'), $this->getIncomeCategoryTypes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope to include only expense.
|
||||
* Uses Categories trait to support multiple expense types (e.g. from modules).
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function scopeExpense($query)
|
||||
{
|
||||
return $query->where($this->qualifyColumn('type'), '=', 'expense');
|
||||
return $query->whereIn($this->qualifyColumn('type'), $this->getExpenseCategoryTypes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope to include only item.
|
||||
* Uses Categories trait to support multiple item types (e.g. from modules).
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function scopeItem($query)
|
||||
{
|
||||
return $query->where($this->qualifyColumn('type'), '=', 'item');
|
||||
return $query->whereIn($this->qualifyColumn('type'), $this->getItemCategoryTypes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope to include only other.
|
||||
* Uses Categories trait to support multiple other types (e.g. from modules).
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function scopeOther($query)
|
||||
{
|
||||
return $query->where($this->qualifyColumn('type'), '=', 'other');
|
||||
return $query->whereIn($this->qualifyColumn('type'), $this->getOtherCategoryTypes());
|
||||
}
|
||||
|
||||
public function scopeName($query, $name)
|
||||
|
|
@ -213,6 +231,17 @@ class Category extends Model
|
|||
return $query->withoutGlobalScope(new Scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope gets only parent categories.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function scopeIsNotSubCategory($query)
|
||||
{
|
||||
return $query->whereNull('parent_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope to export the rows of the current page filtered and sorted.
|
||||
*
|
||||
|
|
@ -233,7 +262,7 @@ class Category extends Model
|
|||
|
||||
$search = $request->get('search');
|
||||
|
||||
$query->withSubcategory();
|
||||
$query->withSubCategory();
|
||||
|
||||
$query->usingSearchString($search)->sortable($sort);
|
||||
|
||||
|
|
@ -261,9 +290,92 @@ class Category extends Model
|
|||
/**
|
||||
* Get the display name of the category.
|
||||
*/
|
||||
public function getDisplayNameAttribute()
|
||||
public function getDisplayNameAttribute(): string
|
||||
{
|
||||
return $this->name . ' (' . ucfirst($this->type) . ')';
|
||||
$typeConfig = config('type.category.' . $this->type, []);
|
||||
$hideCode = isset($typeConfig['hide']) && in_array('code', $typeConfig['hide']);
|
||||
|
||||
$typeNames = $this->getCategoryTypes();
|
||||
$typeName = $typeNames[$this->type] ?? ucfirst($this->type);
|
||||
|
||||
$prefix = (!$hideCode && $this->code) ? $this->code . ' - ' : '';
|
||||
|
||||
return $prefix . $this->name . ' (' . $typeName . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the balance of a category.
|
||||
*
|
||||
* @return double
|
||||
*/
|
||||
public function getBalanceAttribute()
|
||||
{
|
||||
// If view composer has set the balance, return it directly
|
||||
if (isset($this->de_balance)) {
|
||||
return $this->de_balance;
|
||||
}
|
||||
|
||||
$financial_year = $this->getFinancialYear();
|
||||
|
||||
$start_date = $financial_year->getStartDate();
|
||||
$end_date = $financial_year->getEndDate();
|
||||
|
||||
$this->transactions->whereBetween('paid_at', [$start_date, $end_date])
|
||||
->each(function ($transaction) use (&$incomes, &$expenses) {
|
||||
if (($transaction->isNotIncome() && $transaction->isNotExpense()) || $transaction->isTransferTransaction()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($transaction->isIncome()) {
|
||||
$incomes += $transaction->getAmountConvertedToDefault();
|
||||
} else {
|
||||
$expenses += $transaction->getAmountConvertedToDefault();
|
||||
}
|
||||
});
|
||||
|
||||
$balance = $incomes - $expenses;
|
||||
|
||||
$this->sub_categories()
|
||||
->each(function ($sub_category) use (&$balance) {
|
||||
$balance += $sub_category->balance;
|
||||
});
|
||||
|
||||
return $balance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the balance of a category without considering sub categories.
|
||||
*
|
||||
* @return double
|
||||
*/
|
||||
public function getBalanceWithoutSubcategoriesAttribute()
|
||||
{
|
||||
// If view composer has set the balance, return it directly
|
||||
if (isset($this->without_subcategory_de_balance)) {
|
||||
return $this->without_subcategory_de_balance;
|
||||
}
|
||||
|
||||
$financial_year = $this->getFinancialYear();
|
||||
|
||||
$start_date = $financial_year->getStartDate();
|
||||
$end_date = $financial_year->getEndDate();
|
||||
|
||||
$this->transactions->whereBetween('paid_at', [$start_date, $end_date])
|
||||
->each(function ($transaction) use (&$incomes, &$expenses) {
|
||||
if (($transaction->isNotIncome() && $transaction->isNotExpense()) || $transaction->isTransferTransaction()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($transaction->isIncome()) {
|
||||
$incomes += $transaction->getAmountConvertedToDefault();
|
||||
} else {
|
||||
$expenses += $transaction->getAmountConvertedToDefault();
|
||||
}
|
||||
});
|
||||
|
||||
$balance = $incomes - $expenses;
|
||||
|
||||
return $balance;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -303,6 +415,19 @@ class Category extends Model
|
|||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* A no-op callback that gets fired when a model is cloning but before it gets
|
||||
* committed to the database
|
||||
*
|
||||
* @param Illuminate\Database\Eloquent\Model $src
|
||||
* @param boolean $child
|
||||
* @return void
|
||||
*/
|
||||
public function onCloning($src, $child = null)
|
||||
{
|
||||
$this->code = $this->getNextCategoryCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new factory instance for the model.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -8,7 +8,97 @@ use Illuminate\Support\Str;
|
|||
|
||||
trait Categories
|
||||
{
|
||||
public function getCategoryTypes(bool $translate = true): array
|
||||
public function isIncomeCategory(): bool
|
||||
{
|
||||
$type = $this->type ?? $this->category->type ?? $this->model->type ?? Category::INCOME_TYPE;
|
||||
|
||||
return in_array($type, $this->getIncomeCategoryTypes());
|
||||
}
|
||||
|
||||
public function isExpenseCategory(): bool
|
||||
{
|
||||
$type = $this->type ?? $this->category->type ?? $this->model->type ?? Category::EXPENSE_TYPE;
|
||||
|
||||
return in_array($type, $this->getExpenseCategoryTypes());
|
||||
}
|
||||
|
||||
public function isItemCategory(): bool
|
||||
{
|
||||
$type = $this->type ?? $this->category->type ?? $this->model->type ?? Category::ITEM_TYPE;
|
||||
|
||||
return in_array($type, $this->getItemCategoryTypes());
|
||||
}
|
||||
|
||||
public function isOtherCategory(): bool
|
||||
{
|
||||
$type = $this->type ?? $this->category->type ?? $this->model->type ?? Category::OTHER_TYPE;
|
||||
|
||||
return in_array($type, $this->getOtherCategoryTypes());
|
||||
}
|
||||
|
||||
public function getIncomeCategoryTypes(string $return = 'array'): string|array
|
||||
{
|
||||
return $this->getCategoryTypesByIndex(Category::INCOME_TYPE, $return);
|
||||
}
|
||||
|
||||
public function getExpenseCategoryTypes(string $return = 'array'): string|array
|
||||
{
|
||||
return $this->getCategoryTypesByIndex(Category::EXPENSE_TYPE, $return);
|
||||
}
|
||||
|
||||
public function getItemCategoryTypes(string $return = 'array'): string|array
|
||||
{
|
||||
return $this->getCategoryTypesByIndex(Category::ITEM_TYPE, $return);
|
||||
}
|
||||
|
||||
public function getOtherCategoryTypes(string $return = 'array'): string|array
|
||||
{
|
||||
return $this->getCategoryTypesByIndex(Category::OTHER_TYPE, $return);
|
||||
}
|
||||
|
||||
public function getCategoryTypesByIndex(string $index, string $return = 'array'): string|array
|
||||
{
|
||||
$types = (string) setting('category.type.' . $index);
|
||||
|
||||
return ($return == 'array') ? explode(',', $types) : $types;
|
||||
}
|
||||
|
||||
public function addIncomeCategoryType(string $new_type): void
|
||||
{
|
||||
$this->addCategoryType($new_type, Category::INCOME_TYPE);
|
||||
}
|
||||
|
||||
public function addExpenseCategoryType(string $new_type): void
|
||||
{
|
||||
$this->addCategoryType($new_type, Category::EXPENSE_TYPE);
|
||||
}
|
||||
|
||||
public function addItemCategoryType(string $new_type): void
|
||||
{
|
||||
$this->addCategoryType($new_type, Category::ITEM_TYPE);
|
||||
}
|
||||
|
||||
public function addOtherCategoryType(string $new_type): void
|
||||
{
|
||||
$this->addCategoryType($new_type, Category::OTHER_TYPE);
|
||||
}
|
||||
|
||||
public function addCategoryType(string $new_type, string $index): void
|
||||
{
|
||||
$types = !empty(setting('category.type.' . $index)) ? explode(',', setting('category.type.' . $index)) : [];
|
||||
|
||||
if (in_array($new_type, $types)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$types[] = $new_type;
|
||||
|
||||
setting([
|
||||
'category.type.' . $index => implode(',', $types),
|
||||
])->save();
|
||||
}
|
||||
|
||||
public function getCategoryTypes(bool $translate = true, bool $group = false): array
|
||||
{
|
||||
$types = [];
|
||||
$configs = config('type.category');
|
||||
|
|
@ -22,12 +112,48 @@ trait Categories
|
|||
$name = $attr['alias'] . '::' . $name;
|
||||
}
|
||||
|
||||
if ($group) {
|
||||
$group_key = $attr['group'] ?? $type;
|
||||
$types[$group_key][$type] = $translate ? trans_choice($name, 1) : $name;
|
||||
} else {
|
||||
$types[$type] = $translate ? trans_choice($name, 1) : $name;
|
||||
}
|
||||
}
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
public function getCategoryTabs(): array
|
||||
{
|
||||
$tabs = [];
|
||||
$configs = config('type.category');
|
||||
|
||||
foreach ($configs as $type => $attr) {
|
||||
$tab_key = 'categories-' . ($attr['group'] ?? $type);
|
||||
|
||||
if (isset($tabs[$tab_key])) {
|
||||
$tabs[$tab_key]['key'] .= ',' . $type;
|
||||
continue;
|
||||
}
|
||||
|
||||
$plural_type = Str::plural($attr['group'] ?? $type);
|
||||
|
||||
$name = $attr['translation']['prefix'] . '.' . $plural_type;
|
||||
|
||||
if (!empty($attr['alias'])) {
|
||||
$name = $attr['alias'] . '::' . $name;
|
||||
}
|
||||
|
||||
$tabs[$tab_key] = [
|
||||
'key' => $type,
|
||||
'name' => trans_choice($name, 2),
|
||||
'show_code' => $attr['show_code'] ?? false,
|
||||
];
|
||||
}
|
||||
|
||||
return $tabs;
|
||||
}
|
||||
|
||||
public function getCategoryWithoutChildren(int $id): mixed
|
||||
{
|
||||
return Category::getWithoutChildren()->find($id);
|
||||
|
|
@ -36,7 +162,7 @@ trait Categories
|
|||
public function getTransferCategoryId(): mixed
|
||||
{
|
||||
// 1 hour set cache for same query
|
||||
return Cache::remember('transferCategoryId', 60, function () {
|
||||
return Cache::remember('transferCategoryId.' . company_id(), 60, function () {
|
||||
return Category::other()->pluck('id')->first();
|
||||
});
|
||||
}
|
||||
|
|
@ -62,4 +188,16 @@ trait Categories
|
|||
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds existing maximum code and increase it
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getNextCategoryCode()
|
||||
{
|
||||
return Category::isNotSubCategory()->get(['code'])->reject(function ($category) {
|
||||
return !preg_match('/^[0-9]*$/', $category->code);
|
||||
})->max('code') + 1;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ trait Import
|
|||
{
|
||||
$id = isset($row['category_id']) ? $row['category_id'] : null;
|
||||
|
||||
$type = !empty($type) ? $type : (!empty($row['type']) ? $row['type'] : 'income');
|
||||
$type = !empty($type) ? $type : (!empty($row['type']) ? $row['type'] : Category::INCOME_TYPE);
|
||||
|
||||
if (empty($id) && !empty($row['category_name'])) {
|
||||
$id = $this->getCategoryIdFromName($row, $type);
|
||||
|
|
@ -96,14 +96,14 @@ trait Import
|
|||
|
||||
public function getCategoryType($type)
|
||||
{
|
||||
return array_key_exists($type, config('type.category')) ? $type : 'other';
|
||||
return array_key_exists($type, config('type.category')) ? $type : Category::OTHER_TYPE;
|
||||
}
|
||||
|
||||
public function getContactId($row, $type = null)
|
||||
{
|
||||
$id = isset($row['contact_id']) ? $row['contact_id'] : null;
|
||||
|
||||
$type = !empty($type) ? $type : (!empty($row['type']) ? (($row['type'] == 'income') ? 'customer' : 'vendor') : 'customer');
|
||||
$type = !empty($type) ? $type : (!empty($row['type']) ? (($row['type'] == Transaction::INCOME_TYPE) ? 'customer' : 'vendor') : 'customer');
|
||||
|
||||
if (empty($row['contact_id']) && !empty($row['contact_email'])) {
|
||||
$id = $this->getContactIdFromEmail($row, $type);
|
||||
|
|
@ -180,7 +180,7 @@ trait Import
|
|||
}
|
||||
|
||||
if (empty($id) && !empty($row['invoice_bill_number'])) {
|
||||
if ($row['type'] == 'income') {
|
||||
if ($row['type'] == Transaction::INCOME_TYPE) {
|
||||
$id = Document::invoice()->number($row['invoice_bill_number'])->pluck('id')->first();
|
||||
} else {
|
||||
$id = Document::bill()->number($row['invoice_bill_number'])->pluck('id')->first();
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@ trait SearchString
|
|||
foreach ($columns as $column) {
|
||||
$variable = preg_split('/:|>?<?=/', $column);
|
||||
|
||||
if ($name == 'searchable' && count($variable) == 1 && preg_match('/^".*"$/', $variable[0])) {
|
||||
return trim($variable[0], '"');
|
||||
}
|
||||
|
||||
if (empty($variable[0]) || ($variable[0] != $name) || empty($variable[1])) {
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ namespace App\Traits;
|
|||
|
||||
use Akaunting\Module\Module;
|
||||
use App\Events\Common\BulkActionsAdding;
|
||||
use App\Models\Setting\Category;
|
||||
use App\Traits\Modules;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
|
@ -188,19 +189,19 @@ trait ViewComponents
|
|||
case 'bill':
|
||||
case 'expense':
|
||||
case 'purchase':
|
||||
$category_type = 'expense';
|
||||
$category_type = Category::EXPENSE_TYPE;
|
||||
break;
|
||||
case 'item':
|
||||
$category_type = 'item';
|
||||
$category_type = Category::ITEM_TYPE;
|
||||
break;
|
||||
case 'other':
|
||||
$category_type = 'other';
|
||||
$category_type = Category::OTHER_TYPE;
|
||||
break;
|
||||
case 'transfer':
|
||||
$category_type = 'transfer';
|
||||
break;
|
||||
default:
|
||||
$category_type = 'income';
|
||||
$category_type = Category::INCOME_TYPE;
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@
|
|||
namespace App\Utilities;
|
||||
|
||||
use Akaunting\Money\Money;
|
||||
use App\Models\Setting\Category;
|
||||
use App\Models\Setting\Currency;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class Overrider
|
||||
{
|
||||
|
|
@ -71,6 +73,45 @@ class Overrider
|
|||
}
|
||||
}
|
||||
|
||||
protected static function loadCategoryTypes()
|
||||
{
|
||||
$category = new Category;
|
||||
|
||||
$income_types = $category->getIncomeCategoryTypes('string');
|
||||
$expense_types = $category->getExpenseCategoryTypes('string');
|
||||
$item_types = $category->getItemCategoryTypes('string');
|
||||
$other_types = $category->getOtherCategoryTypes('string');
|
||||
|
||||
$search_string = config('search-string');
|
||||
|
||||
foreach ($search_string as $model => &$model_config) {
|
||||
$route = $model_config['columns']['category_id']['route'] ?? null;
|
||||
|
||||
// Only update category_id routes that point to categories.index
|
||||
if (!is_array($route) || ($route[0] ?? '') !== 'categories.index' || !isset($route[1])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Longest match first (income,expense must come before income)
|
||||
$replacements = [
|
||||
'type:' . Category::INCOME_TYPE . ',' . Category::EXPENSE_TYPE => 'type:' . $income_types . ',' . $expense_types,
|
||||
'type:' . Category::INCOME_TYPE => 'type:' . $income_types,
|
||||
'type:' . Category::EXPENSE_TYPE => 'type:' . $expense_types,
|
||||
'type:' . Category::ITEM_TYPE => 'type:' . $item_types,
|
||||
'type:' . Category::OTHER_TYPE => 'type:' . $other_types,
|
||||
];
|
||||
|
||||
foreach ($replacements as $search => $replace) {
|
||||
if (Str::contains($route[1], $search)) {
|
||||
$model_config['columns']['category_id']['route'][1] = Str::replace($search, $replace, $route[1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config(['search-string' => $search_string]);
|
||||
}
|
||||
|
||||
protected static function loadCurrencies()
|
||||
{
|
||||
$currencies = Currency::all();
|
||||
|
|
|
|||
|
|
@ -4,10 +4,15 @@ namespace App\View\Components\Form\Group;
|
|||
|
||||
use App\Abstracts\View\Components\Form;
|
||||
use App\Models\Setting\Category as Model;
|
||||
use App\Traits\Categories;
|
||||
use App\Traits\Modules;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class Category extends Form
|
||||
{
|
||||
public $type = 'income';
|
||||
use Categories, Modules;
|
||||
|
||||
public $type = Model::INCOME_TYPE;
|
||||
|
||||
public $path;
|
||||
|
||||
|
|
@ -15,6 +20,14 @@ class Category extends Form
|
|||
|
||||
public $categories;
|
||||
|
||||
/** @var bool */
|
||||
public $group;
|
||||
|
||||
public $option_field = [
|
||||
'key' => 'id',
|
||||
'value' => 'name',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the view / contents that represent the component.
|
||||
*
|
||||
|
|
@ -26,26 +39,133 @@ class Category extends Form
|
|||
$this->name = 'category_id';
|
||||
}
|
||||
|
||||
$this->path = route('modals.categories.create', ['type' => $this->type]);
|
||||
$this->remoteAction = route('categories.index', ['search' => 'type:' . $this->type . ' enabled:1']);
|
||||
switch ($this->type) {
|
||||
case Model::INCOME_TYPE:
|
||||
$types = $this->getIncomeCategoryTypes();
|
||||
break;
|
||||
case Model::EXPENSE_TYPE:
|
||||
$types = $this->getExpenseCategoryTypes();
|
||||
break;
|
||||
case Model::ITEM_TYPE:
|
||||
$types = $this->getItemCategoryTypes();
|
||||
break;
|
||||
case Model::OTHER_TYPE:
|
||||
$types = $this->getOtherCategoryTypes();
|
||||
break;
|
||||
default:
|
||||
$types = [$this->type];
|
||||
}
|
||||
|
||||
$this->categories = Model::type($this->type)->enabled()->orderBy('name')->take(setting('default.select_limit'))->get();
|
||||
$this->path = route('modals.categories.create', ['type' => $this->type]);
|
||||
$this->remoteAction = route('categories.index', ['search' => 'type:' . implode(',', $types) . ' enabled:1']);
|
||||
|
||||
$typeLabels = collect($this->getCategoryTypes())->only($types)->all();
|
||||
|
||||
$is_code = false;
|
||||
|
||||
foreach (config('type.category', []) as $type => $config) {
|
||||
if (! in_array($type, $types)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (empty($config['hide']) || ! in_array('code', $config['hide'])) {
|
||||
$is_code = true;
|
||||
$this->group = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$order_by = $is_code ? 'code' : 'name';
|
||||
|
||||
if ($this->group) {
|
||||
$this->option_field = [
|
||||
'key' => 'id',
|
||||
'value' => 'title',
|
||||
];
|
||||
}
|
||||
|
||||
$query = Model::type($types);
|
||||
|
||||
$query->enabled()
|
||||
->orderBy($order_by);
|
||||
|
||||
if (! $this->group) {
|
||||
$query->take(setting('default.select_limit'));
|
||||
}
|
||||
|
||||
$this->categories = $query->get();
|
||||
|
||||
if ($this->group) {
|
||||
$groups = [];
|
||||
|
||||
foreach ($this->categories as $category) {
|
||||
$group = $typeLabels[$category->type] ?? trans_choice('general.others', 1);
|
||||
|
||||
$category->title = ($category->code ? $category->code . ' - ' : '') . $category->name;
|
||||
$category->group = $group;
|
||||
|
||||
$groups[$group][$category->id] = $category;
|
||||
}
|
||||
|
||||
ksort($groups);
|
||||
|
||||
$this->categories = $groups;
|
||||
}
|
||||
|
||||
$model = $this->getParentData('model');
|
||||
$selected_category = null;
|
||||
|
||||
$categoryExists = function ($categoryId): bool {
|
||||
if (! $this->group) {
|
||||
return $this->categories->contains(function ($category) use ($categoryId) {
|
||||
return (int) $category->id === (int) $categoryId;
|
||||
});
|
||||
}
|
||||
|
||||
foreach ($this->categories as $group_categories) {
|
||||
foreach ($group_categories as $category) {
|
||||
if ((int) $category->id === (int) $categoryId) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
$appendCategory = function ($category) use ($typeLabels): void {
|
||||
if (empty($category)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$category->title = ($category->code ? $category->code . ' - ' : '') . $category->name;
|
||||
|
||||
if (! $this->group) {
|
||||
$this->categories->push($category);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$group = $typeLabels[$category->type] ?? trans_choice('general.others', 1);
|
||||
|
||||
if (! isset($this->categories[$group])) {
|
||||
$this->categories[$group] = [];
|
||||
}
|
||||
|
||||
$this->categories[$group][$category->id] = $category;
|
||||
|
||||
ksort($this->categories);
|
||||
};
|
||||
|
||||
$category_id = old('category.id', old('category_id', null));
|
||||
|
||||
if (! empty($category_id)) {
|
||||
$this->selected = $category_id;
|
||||
|
||||
$has_category = $this->categories->search(function ($category, int $key) use ($category_id) {
|
||||
return $category->id === $category_id;
|
||||
});
|
||||
|
||||
if ($has_category === false) {
|
||||
if (! $categoryExists($category_id)) {
|
||||
$category = Model::find($category_id);
|
||||
|
||||
$this->categories->push($category);
|
||||
$appendCategory($category);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -61,15 +181,15 @@ class Category extends Form
|
|||
$selected_category = Model::find($this->selected);
|
||||
}
|
||||
|
||||
if (empty($selected_category) && ! empty($this->selected)) {
|
||||
$selected_category = Model::find($this->selected);
|
||||
}
|
||||
|
||||
if (! empty($selected_category)) {
|
||||
$selected_category_id = $selected_category->id;
|
||||
|
||||
$has_selected_category = $this->categories->search(function ($category, int $key) use ($selected_category_id) {
|
||||
return $category->id === $selected_category_id;
|
||||
});
|
||||
|
||||
if ($has_selected_category === false) {
|
||||
$this->categories->push($selected_category);
|
||||
if (! $categoryExists($selected_category_id)) {
|
||||
$appendCategory($selected_category);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
namespace App\View\Components\Index;
|
||||
|
||||
use App\Abstracts\View\Component;
|
||||
|
||||
class Balance extends Component
|
||||
{
|
||||
/**
|
||||
* The balance amount.
|
||||
*
|
||||
* @var float
|
||||
*/
|
||||
public $amount;
|
||||
|
||||
/**
|
||||
* The text color class.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $textColor;
|
||||
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($amount = 0) {
|
||||
$this->amount = $this->getAmount($amount);
|
||||
$this->textColor = $this->getTextColor($amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the view / contents that represent the component.
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\View|string
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return view('components.index.balance');
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the amount according to the location context.
|
||||
*
|
||||
* @param float $amount
|
||||
* @return string
|
||||
*/
|
||||
protected function getAmount($amount)
|
||||
{
|
||||
return money($amount, setting('default.currency'), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the class of color considering given amount.
|
||||
*
|
||||
* @param float $amount
|
||||
* @return string|null
|
||||
*/
|
||||
protected function getTextColor($amount)
|
||||
{
|
||||
switch ($amount) {
|
||||
case $amount > 0:
|
||||
return 'text-green';
|
||||
case $amount < 0:
|
||||
return 'text-red';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Setting\Category;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|
|
@ -184,7 +186,7 @@ return [
|
|||
'payment_method',
|
||||
'reference',
|
||||
'category_id' => [
|
||||
'route' => ['categories.index', 'search=type:income,expense enabled:1'],
|
||||
'route' => ['categories.index', 'search=type:' . Category::INCOME_TYPE . ',' . Category::EXPENSE_TYPE . ' enabled:1'],
|
||||
'fields' => [
|
||||
'key' => 'id',
|
||||
'value' => 'display_name',
|
||||
|
|
@ -246,7 +248,7 @@ return [
|
|||
'description' => ['searchable' => true],
|
||||
'enabled' => ['boolean' => true],
|
||||
'category_id' => [
|
||||
'route' => ['categories.index', 'search=type:item enabled:1'],
|
||||
'route' => ['categories.index', 'search=type:' . Category::ITEM_TYPE . ' enabled:1'],
|
||||
'fields' => [
|
||||
'key' => 'id',
|
||||
'value' => 'name',
|
||||
|
|
@ -352,7 +354,7 @@ return [
|
|||
'contact_phone' => ['searchable' => true],
|
||||
'contact_address' => ['searchable' => true],
|
||||
'category_id' => [
|
||||
'route' => ['categories.index', 'search=type:income,expense enabled:1'],
|
||||
'route' => ['categories.index', 'search=type:' . Category::INCOME_TYPE . ',' . Category::EXPENSE_TYPE . ' enabled:1'],
|
||||
'multiple' => true,
|
||||
],
|
||||
'parent_id',
|
||||
|
|
@ -403,7 +405,7 @@ return [
|
|||
'contact_phone' => ['searchable' => true],
|
||||
'contact_address' => ['searchable' => true],
|
||||
'category_id' => [
|
||||
'route' => ['categories.index', 'search=type:expense enabled:1'],
|
||||
'route' => ['categories.index', 'search=type:' . Category::EXPENSE_TYPE . ' enabled:1'],
|
||||
'fields' => [
|
||||
'key' => 'id',
|
||||
'value' => 'name',
|
||||
|
|
@ -459,7 +461,7 @@ return [
|
|||
'contact_phone' => ['searchable' => true],
|
||||
'contact_address' => ['searchable' => true],
|
||||
'category_id' => [
|
||||
'route' => ['categories.index', 'search=type:income enabled:1'],
|
||||
'route' => ['categories.index', 'search=type:' . Category::INCOME_TYPE . ' enabled:1'],
|
||||
'fields' => [
|
||||
'key' => 'id',
|
||||
'value' => 'name',
|
||||
|
|
@ -480,6 +482,8 @@ return [
|
|||
App\Models\Setting\Category::class => [
|
||||
'columns' => [
|
||||
'id',
|
||||
'code' => ['searchable' => true],
|
||||
'description' => ['searchable' => true],
|
||||
'name' => ['searchable' => true],
|
||||
'enabled' => ['boolean' => true],
|
||||
'type' => [
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use App\Models\Common\Contact;
|
||||
use App\Models\Banking\Transaction;
|
||||
use App\Models\Setting\Category;
|
||||
|
||||
return [
|
||||
|
||||
|
|
@ -168,6 +169,14 @@ return [
|
|||
'bill_days' => env('SETTING_FALLBACK_SCHEDULE_BILL_DAYS', '10,5,3,1'),
|
||||
'time' => env('SETTING_FALLBACK_SCHEDULE_TIME', '09:00'),
|
||||
],
|
||||
'category' => [
|
||||
'type' => [
|
||||
'income' => env('SETTING_FALLBACK_CATEGORY_TYPE_INCOME', Category::INCOME_TYPE),
|
||||
'expense' => env('SETTING_FALLBACK_CATEGORY_TYPE_EXPENSE', Category::EXPENSE_TYPE),
|
||||
'item' => env('SETTING_FALLBACK_CATEGORY_TYPE_ITEM', Category::ITEM_TYPE),
|
||||
'other' => env('SETTING_FALLBACK_CATEGORY_TYPE_OTHER', Category::OTHER_TYPE),
|
||||
],
|
||||
],
|
||||
'contact' => [
|
||||
'type' => [
|
||||
'customer' => env('SETTING_FALLBACK_CONTACT_TYPE_CUSTOMER', Contact::CUSTOMER_TYPE),
|
||||
|
|
|
|||
|
|
@ -11,30 +11,38 @@ return [
|
|||
'category' => [
|
||||
Category::INCOME_TYPE => [
|
||||
'alias' => '',
|
||||
'group' => Category::INCOME_TYPE,
|
||||
'translation' => [
|
||||
'prefix' => 'general',
|
||||
],
|
||||
'hide' => ['code'],
|
||||
],
|
||||
|
||||
Category::EXPENSE_TYPE => [
|
||||
'alias' => '',
|
||||
'group' => Category::EXPENSE_TYPE,
|
||||
'translation' => [
|
||||
'prefix' => 'general',
|
||||
],
|
||||
'hide' => ['code'],
|
||||
],
|
||||
|
||||
Category::ITEM_TYPE => [
|
||||
'alias' => '',
|
||||
'group' => Category::ITEM_TYPE,
|
||||
'translation' => [
|
||||
'prefix' => 'general',
|
||||
],
|
||||
'hide' => ['code'],
|
||||
],
|
||||
|
||||
Category::OTHER_TYPE => [
|
||||
'alias' => '',
|
||||
'group' => Category::OTHER_TYPE,
|
||||
'translation' => [
|
||||
'prefix' => 'general',
|
||||
],
|
||||
'hide' => ['code'],
|
||||
],
|
||||
],
|
||||
|
||||
|
|
@ -58,9 +66,9 @@ return [
|
|||
'section_billing_description' => 'customers.form_description.billing',
|
||||
'section_address_description' => 'customers.form_description.address',
|
||||
],
|
||||
'category_type' => 'income',
|
||||
'document_type' => 'invoice',
|
||||
'transaction_type' => 'income',
|
||||
'category_type' => Category::INCOME_TYPE,
|
||||
'document_type' => Document::INVOICE_TYPE,
|
||||
'transaction_type' => Transaction::INCOME_TYPE,
|
||||
'hide' => [],
|
||||
'class' => [],
|
||||
'script' => [
|
||||
|
|
@ -87,9 +95,9 @@ return [
|
|||
'section_billing_description' => 'vendors.form_description.billing',
|
||||
'section_address_description' => 'vendors.form_description.address',
|
||||
],
|
||||
'category_type' => 'expense',
|
||||
'document_type' => 'bill',
|
||||
'transaction_type' => 'expense',
|
||||
'category_type' => Category::EXPENSE_TYPE,
|
||||
'document_type' => Document::BILL_TYPE,
|
||||
'transaction_type' => Transaction::EXPENSE_TYPE,
|
||||
'hide' => [],
|
||||
'class' => [],
|
||||
'script' => [
|
||||
|
|
@ -130,9 +138,9 @@ return [
|
|||
'setting' => [
|
||||
'prefix' => 'invoice',
|
||||
],
|
||||
'category_type' => 'income',
|
||||
'transaction_type' => 'income',
|
||||
'contact_type' => 'customer', // use contact type
|
||||
'category_type' => Category::INCOME_TYPE,
|
||||
'transaction_type' => Transaction::INCOME_TYPE,
|
||||
'contact_type' => Contact::CUSTOMER_TYPE, // use contact type
|
||||
'inventory_stock_action' => 'decrease', // decrease stock in stock tracking
|
||||
'transaction' => [
|
||||
'email_template' => 'invoice_payment_customer', // use email template
|
||||
|
|
@ -185,9 +193,9 @@ return [
|
|||
'setting' => [
|
||||
'prefix' => 'invoice',
|
||||
],
|
||||
'category_type' => 'income',
|
||||
'transaction_type' => 'income',
|
||||
'contact_type' => 'customer', // use contact type
|
||||
'category_type' => Category::INCOME_TYPE,
|
||||
'transaction_type' => Transaction::INCOME_TYPE,
|
||||
'contact_type' => Contact::CUSTOMER_TYPE, // use contact type
|
||||
'inventory_stock_action' => 'decrease', // decrease stock in stock tracking
|
||||
'hide' => [], // for document items
|
||||
'class' => [],
|
||||
|
|
@ -235,9 +243,9 @@ return [
|
|||
'setting' => [
|
||||
'prefix' => 'bill',
|
||||
],
|
||||
'category_type' => 'expense',
|
||||
'transaction_type' => 'expense',
|
||||
'contact_type' => 'vendor',
|
||||
'category_type' => Category::EXPENSE_TYPE,
|
||||
'transaction_type' => Transaction::EXPENSE_TYPE,
|
||||
'contact_type' => Contact::VENDOR_TYPE,
|
||||
'inventory_stock_action' => 'increase', // increases stock in stock tracking
|
||||
'transaction' => [
|
||||
'email_template' => 'invoice_payment_customer', // use email template
|
||||
|
|
@ -288,9 +296,9 @@ return [
|
|||
'setting' => [
|
||||
'prefix' => 'bill',
|
||||
],
|
||||
'category_type' => 'expense',
|
||||
'transaction_type' => 'expense',
|
||||
'contact_type' => 'vendor',
|
||||
'category_type' => Category::EXPENSE_TYPE,
|
||||
'transaction_type' => Transaction::EXPENSE_TYPE,
|
||||
'contact_type' => Contact::VENDOR_TYPE,
|
||||
'inventory_stock_action' => 'increase', // increases stock in stock tracking
|
||||
'hide' => [],
|
||||
'class' => [],
|
||||
|
|
@ -334,8 +342,9 @@ return [
|
|||
'related_document_amount' => 'invoices.invoice_amount',
|
||||
'transactions' => 'general.incomes',
|
||||
],
|
||||
'contact_type' => 'customer',
|
||||
'document_type' => 'invoice',
|
||||
'category_type' => Category::INCOME_TYPE,
|
||||
'contact_type' => Contact::CUSTOMER_TYPE,
|
||||
'document_type' => Document::INVOICE_TYPE,
|
||||
'split_type' => Transaction::INCOME_SPLIT_TYPE,
|
||||
'email_template' => 'payment_received_customer',
|
||||
'script' => [
|
||||
|
|
@ -365,8 +374,9 @@ return [
|
|||
'related_document_amount' => 'invoices.invoice_amount',
|
||||
'transactions' => 'general.incomes',
|
||||
],
|
||||
'contact_type' => 'customer',
|
||||
'document_type' => 'invoice',
|
||||
'category_type' => Category::INCOME_TYPE,
|
||||
'contact_type' => Contact::CUSTOMER_TYPE,
|
||||
'document_type' => Document::INVOICE_TYPE,
|
||||
'split_type' => Transaction::INCOME_SPLIT_TYPE,
|
||||
'email_template' => 'payment_received_customer',
|
||||
'script' => [
|
||||
|
|
@ -396,8 +406,9 @@ return [
|
|||
'related_document_amount' => 'invoices.invoice_amount',
|
||||
'transactions' => 'general.incomes',
|
||||
],
|
||||
'contact_type' => 'customer',
|
||||
'document_type' => 'invoice',
|
||||
'category_type' => Category::INCOME_TYPE,
|
||||
'contact_type' => Contact::CUSTOMER_TYPE,
|
||||
'document_type' => Document::INVOICE_TYPE,
|
||||
'split_type' => Transaction::INCOME_SPLIT_TYPE,
|
||||
'email_template' => 'payment_received_customer',
|
||||
'script' => [
|
||||
|
|
@ -427,8 +438,9 @@ return [
|
|||
'related_document_amount' => 'invoices.invoice_amount',
|
||||
'transactions' => 'general.incomes',
|
||||
],
|
||||
'contact_type' => 'customer',
|
||||
'document_type' => 'invoice',
|
||||
'category_type' => Category::INCOME_TYPE,
|
||||
'contact_type' => Contact::CUSTOMER_TYPE,
|
||||
'document_type' => Document::INVOICE_TYPE,
|
||||
'email_template' => 'payment_received_customer',
|
||||
'script' => [
|
||||
'folder' => 'banking',
|
||||
|
|
@ -489,8 +501,9 @@ return [
|
|||
'prefix' => 'transactions', // this translation file name.
|
||||
'related_document_amount' => 'bills.bill_amount',
|
||||
],
|
||||
'contact_type' => 'vendor',
|
||||
'document_type' => 'bill',
|
||||
'category_type' => Category::EXPENSE_TYPE,
|
||||
'contact_type' => Contact::VENDOR_TYPE,
|
||||
'document_type' => Document::BILL_TYPE,
|
||||
'split_type' => Transaction::EXPENSE_SPLIT_TYPE,
|
||||
'email_template' => 'payment_made_vendor',
|
||||
'script' => [
|
||||
|
|
@ -519,8 +532,9 @@ return [
|
|||
'prefix' => 'transactions', // this translation file name.
|
||||
'related_document_amount' => 'bills.bill_amount',
|
||||
],
|
||||
'contact_type' => 'vendor',
|
||||
'document_type' => 'bill',
|
||||
'category_type' => Category::EXPENSE_TYPE,
|
||||
'contact_type' => Contact::VENDOR_TYPE,
|
||||
'document_type' => Document::BILL_TYPE,
|
||||
'split_type' => Transaction::EXPENSE_SPLIT_TYPE,
|
||||
'email_template' => 'payment_made_vendor',
|
||||
'script' => [
|
||||
|
|
@ -549,8 +563,9 @@ return [
|
|||
'prefix' => 'transactions', // this translation file name.
|
||||
'related_document_amount' => 'bills.bill_amount',
|
||||
],
|
||||
'contact_type' => 'vendor',
|
||||
'document_type' => 'bill',
|
||||
'category_type' => Category::EXPENSE_TYPE,
|
||||
'contact_type' => Contact::VENDOR_TYPE,
|
||||
'document_type' => Document::BILL_TYPE,
|
||||
'email_template' => 'payment_made_vendor',
|
||||
'script' => [
|
||||
'folder' => 'banking',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('categories', function (Blueprint $table) {
|
||||
$table->string('code')->nullable()->after('company_id');
|
||||
$table->text('description')->nullable()->after('color');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('categories', function (Blueprint $table) {
|
||||
$table->dropColumn('code');
|
||||
$table->dropColumn('description');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -20,6 +20,7 @@
|
|||
:collapse-tags="collapse"
|
||||
:remote-method="remoteMethod"
|
||||
:loading="loading"
|
||||
:class="[{ 'with-color-prefix': selectedOptionColor, 'with-icon-prefix': icon }]"
|
||||
>
|
||||
<div
|
||||
v-if="loading"
|
||||
|
|
@ -56,7 +57,6 @@
|
|||
<div v-if="!loading && addNew.status && options.length == 0">
|
||||
<el-option class="text-center" disabled :label="noDataText" value="value"></el-option>
|
||||
|
||||
<ul class="el-scrollbar__view el-select-dropdown__list">
|
||||
<li class="el-select-dropdown__item el-select__footer bg-purple sticky bottom-0">
|
||||
<div class="w-full flex items-center" @click="onAddItem">
|
||||
<span class="material-icons text-xl text-purple">add</span>
|
||||
|
|
@ -65,12 +65,16 @@
|
|||
</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<template slot="prefix">
|
||||
<span class="el-input__suffix-inner el-select-icon">
|
||||
<i :class="'select-icon-position el-input__icon fa fa-' + icon"></i>
|
||||
<span class="aka-select-prefix">
|
||||
<span
|
||||
v-if="!isDropdownVisible && selectedOptionColor"
|
||||
class="w-4 h-4 rounded-full mt-1 ml-2"
|
||||
:style="{ backgroundColor: selectedOptionColor }"
|
||||
></span>
|
||||
<i v-if="icon" :class="'select-icon-position el-input__icon fa fa-' + icon"></i>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
|
|
@ -141,12 +145,19 @@
|
|||
|
||||
<component v-bind:is="add_new_html" @submit="onSubmit" @cancel="onCancel"></component>
|
||||
|
||||
<span slot="infoBlock" class="absolute right-8 top-3 bg-green text-white px-2 py-1 rounded-md text-xs" v-if="new_options[selected] || (sorted_options.length && sorted_options[sorted_options.length - 1].mark_new && sorted_options[sorted_options.length - 1].key == selected)">{{ addNew.new_text }}</span>
|
||||
<span slot="infoBlock" class="absolute right-8 top-3 bg-green text-white px-2 py-1 rounded-md text-xs" v-if="!isDropdownVisible && (new_options[selected] || (sorted_options.length && sorted_options[sorted_options.length - 1].mark_new && sorted_options[sorted_options.length - 1].key == selected))">{{ addNew.new_text }}</span>
|
||||
|
||||
<span
|
||||
slot="infoBlock"
|
||||
class="absolute right-8 top-4 rounded-md bg-gray-50 px-2 py-0.5 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10"
|
||||
v-if="!isDropdownVisible && group && selectedGroupLabel"
|
||||
>
|
||||
{{ selectedGroupLabel }}
|
||||
</span>
|
||||
|
||||
<select :name="name" :id="name + '-' + _uid" class="hidden">
|
||||
<option v-for="option in sortedOptions" :key="option.key" :value="option.key">{{ option.value }}</option>
|
||||
</select>
|
||||
|
||||
</base-input>
|
||||
|
||||
<span v-else>
|
||||
|
|
@ -159,6 +170,7 @@
|
|||
:collapse-tags="collapse"
|
||||
:remote-method="remoteMethod"
|
||||
:loading="loading"
|
||||
:class="[{ 'with-color-prefix': selectedOptionColor, 'with-icon-prefix': icon }]"
|
||||
>
|
||||
<div v-if="loading" class="el-select-dropdown__wrap" slot="empty">
|
||||
<p class="el-select-dropdown__empty pt-2 pb-0 loading">
|
||||
|
|
@ -185,7 +197,7 @@
|
|||
|
||||
<div v-if="!loading && addNew.status && options.length == 0">
|
||||
<el-option class="text-center" disabled :label="noDataText" value="value"></el-option>
|
||||
<ul class="el-scrollbar__view el-select-dropdown__list">
|
||||
|
||||
<li class="el-select-dropdown__item el-select__footer bg-purple sticky bottom-0">
|
||||
<div class="w-full flex items-center" @click="onAddItem">
|
||||
<span class="material-icons text-xl text-purple">add</span>
|
||||
|
|
@ -194,12 +206,16 @@
|
|||
</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<template slot="prefix">
|
||||
<span class="el-input__suffix-inner el-select-icon">
|
||||
<i :class="'select-icon-position el-input__icon fa fa-' + icon"></i>
|
||||
<span class="aka-select-prefix">
|
||||
<span
|
||||
v-if="selectedOptionColor"
|
||||
class="aka-select-prefix-dot"
|
||||
:style="{ backgroundColor: selectedOptionColor }"
|
||||
></span>
|
||||
<i v-if="icon" :class="'select-icon-position el-input__icon fa fa-' + icon"></i>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
|
|
@ -265,12 +281,19 @@
|
|||
</span>
|
||||
</div>
|
||||
</el-option>
|
||||
|
||||
</el-select>
|
||||
|
||||
<component v-bind:is="add_new_html" @submit="onSubmit" @cancel="onCancel"></component>
|
||||
|
||||
<span slot="infoBlock" class="absolute right-8 top-3 bg-green text-white px-2 py-1 rounded-md text-xs" v-if="new_options[selected] || (sorted_options.length && sorted_options[sorted_options.length - 1].mark_new && sorted_options[sorted_options.length - 1].key == selected)">{{ addNew.new_text }}</span>
|
||||
<span slot="infoBlock" class="absolute right-8 top-3 bg-green text-white px-2 py-1 rounded-md text-xs" v-if="!isDropdownVisible && (new_options[selected] || (sorted_options.length && sorted_options[sorted_options.length - 1].mark_new && sorted_options[sorted_options.length - 1].key == selected))">{{ addNew.new_text }}</span>
|
||||
|
||||
<span
|
||||
slot="infoBlock"
|
||||
class="absolute right-8 top-4 rounded-md bg-gray-50 px-2 py-0.5 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10"
|
||||
v-if="!isDropdownVisible && group && selectedGroupLabel"
|
||||
>
|
||||
{{ selectedGroupLabel }}
|
||||
</span>
|
||||
|
||||
<select :name="name" :id="name + '-' + _uid" v-model="selected" class="d-none">
|
||||
<option v-for="option in sortedOptions" :key="option.key" :value="option.key">{{ option.value }}</option>
|
||||
|
|
@ -520,6 +543,7 @@ export default {
|
|||
full_options:[],
|
||||
new_options: {},
|
||||
loading: false,
|
||||
isDropdownVisible: false,
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -549,6 +573,48 @@ export default {
|
|||
|
||||
return this.sorted_options;
|
||||
},
|
||||
|
||||
selectedGroupLabel() {
|
||||
if (!this.group || !Array.isArray(this.sorted_options) || !this.sorted_options.length) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (this.multiple) {
|
||||
if (!Array.isArray(this.selected) || !this.selected.length) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const labels = this.selected
|
||||
.map(value => this.findGroupLabelByOptionKey(value))
|
||||
.filter(Boolean);
|
||||
|
||||
return [...new Set(labels)].join(', ');
|
||||
}
|
||||
|
||||
return this.findGroupLabelByOptionKey(this.selected);
|
||||
},
|
||||
|
||||
selectedOptionColor() {
|
||||
const selectedOption = this.getSelectedOptionData();
|
||||
|
||||
if (!selectedOption || typeof selectedOption !== 'object') {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (selectedOption.color_hex_code) {
|
||||
return selectedOption.color_hex_code;
|
||||
}
|
||||
|
||||
if (selectedOption.color_hex) {
|
||||
return selectedOption.color_hex;
|
||||
}
|
||||
|
||||
if (selectedOption.color && selectedOption.color.toString().startsWith('#')) {
|
||||
return selectedOption.color;
|
||||
}
|
||||
|
||||
return '';
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
|
|
@ -579,6 +645,78 @@ export default {
|
|||
},
|
||||
|
||||
methods: {
|
||||
getSelectedOptionData() {
|
||||
const selectedKey = this.multiple
|
||||
? (Array.isArray(this.selected) && this.selected.length ? this.selected[0] : null)
|
||||
: this.selected;
|
||||
|
||||
if (selectedKey === null || selectedKey === undefined || selectedKey === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const foundOption = this.findOptionByKey(selectedKey);
|
||||
|
||||
if (!foundOption) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return foundOption.option ? foundOption.option : foundOption;
|
||||
},
|
||||
|
||||
findOptionByKey(optionKey) {
|
||||
const normalizedKey = optionKey.toString();
|
||||
|
||||
if (this.group) {
|
||||
for (const groupOption of this.sorted_options) {
|
||||
if (!Array.isArray(groupOption.value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const found = groupOption.value.find(option => option.key == normalizedKey);
|
||||
|
||||
if (found) {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const found = this.sorted_options.find(option => option.key == normalizedKey);
|
||||
|
||||
if (found) {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
|
||||
const foundInFullOptions = this.full_options.find(option => option.key == normalizedKey);
|
||||
|
||||
if (foundInFullOptions) {
|
||||
return foundInFullOptions;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
findGroupLabelByOptionKey(optionKey) {
|
||||
if (optionKey === null || optionKey === undefined || optionKey === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
const normalizedKey = optionKey.toString();
|
||||
|
||||
const foundGroup = this.sorted_options.find(groupOption => {
|
||||
if (!Array.isArray(groupOption.value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return groupOption.value.some(option => option.key == normalizedKey);
|
||||
});
|
||||
|
||||
if (!foundGroup) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return foundGroup.key ? foundGroup.key.toString() : '';
|
||||
},
|
||||
|
||||
sortBy(option) {
|
||||
return (firstEl, secondEl) => {
|
||||
let first_element = firstEl[option].toUpperCase(); // ignore upper and lowercase
|
||||
|
|
@ -910,6 +1048,8 @@ export default {
|
|||
visibleChange(event) {
|
||||
this.$emit('visible-change', event);
|
||||
|
||||
this.isDropdownVisible = event;
|
||||
|
||||
this.dynamicPlaceholder = this.placeholder;
|
||||
|
||||
if (event && this.searchText) {
|
||||
|
|
@ -1492,11 +1632,25 @@ export default {
|
|||
</script>
|
||||
|
||||
<style>
|
||||
.aka-select-prefix {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.with-color-prefix .el-input__inner {
|
||||
padding-left: 2.25rem !important;
|
||||
}
|
||||
|
||||
.with-color-prefix.with-icon-prefix .el-input__inner {
|
||||
padding-left: 2.8rem !important;
|
||||
}
|
||||
|
||||
.el-select-dropdown__item.el-select__footer.bg-purple.sticky.bottom-0 {
|
||||
background-color: #fff !important;
|
||||
}
|
||||
|
||||
.el-select-dropdown__item.el-select__footer.bg-purple.sticky.bottom-0:hover {
|
||||
background-color: 55588b !important;
|
||||
background-color: #55588b !important;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -30,16 +30,32 @@ const app = new Vue({
|
|||
form: new Form('category'),
|
||||
bulk_action: new BulkAction('categories'),
|
||||
categoriesBasedTypes: null,
|
||||
selected_type: true
|
||||
selected_type: true,
|
||||
type_codes: {},
|
||||
show_code_field: false
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (typeof type_codes !== 'undefined') {
|
||||
this.type_codes = type_codes;
|
||||
|
||||
if (this.form.type) {
|
||||
this.show_code_field = this.type_codes[this.form.type] || false;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
updateParentCategories(event) {
|
||||
changeCategories(event) {
|
||||
if (event === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.type_codes[event] !== undefined) {
|
||||
this.show_code_field = this.type_codes[event];
|
||||
}
|
||||
|
||||
if (typeof JSON.parse(this.form.categories)[event] === 'undefined') {
|
||||
this.categoriesBasedTypes = [];
|
||||
|
||||
|
|
|
|||
|
|
@ -291,6 +291,10 @@
|
|||
color:#595959 !important;
|
||||
}
|
||||
|
||||
.el-select-group__title {
|
||||
@apply text-purple-500 font-bold;
|
||||
}
|
||||
|
||||
.el-select-dropdown .popper__arrow::after {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
@if ((! $attributes->has('withoutRemote') && ! $attributes->has('without-remote')) && (! $attributes->has('withoutAddNew') && ! $attributes->has('without-add-new')))
|
||||
@if (
|
||||
(! $attributes->has('withoutRemote') && ! $attributes->has('without-remote'))
|
||||
&& (! $attributes->has('withoutAddNew') && ! $attributes->has('without-add-new'))
|
||||
)
|
||||
<x-form.group.select
|
||||
remote
|
||||
remote_action="{{ $remoteAction }}"
|
||||
|
|
@ -11,10 +14,7 @@
|
|||
:options="$categories"
|
||||
:selected="$selected"
|
||||
sort-options="false"
|
||||
:option_field="[
|
||||
'key' => 'id',
|
||||
'value' => 'title'
|
||||
]"
|
||||
:option_field="$option_field"
|
||||
|
||||
:multiple="$multiple"
|
||||
:group="$group"
|
||||
|
|
@ -28,11 +28,19 @@
|
|||
<template #option="{option}">
|
||||
<div class="flex items-center">
|
||||
<span class="w-5 h-4 rounded-full" :style="{backgroundColor: option.option.color_hex_code}"></span>
|
||||
|
||||
@if ($option_field['value'] == 'title')
|
||||
<span>@{{ option.option.title }}</span>
|
||||
@else
|
||||
<span>@{{ option.option.name }}</span>
|
||||
@endif
|
||||
</div>
|
||||
</template>
|
||||
</x-form.group.select>
|
||||
@elseif (($attributes->has('withoutRemote') || $attributes->has('without-remote')) && (! $attributes->has('withoutAddNew') && ! $attributes->has('without-add-new')))
|
||||
@elseif (
|
||||
($attributes->has('withoutRemote') || $attributes->has('without-remote'))
|
||||
&& (! $attributes->has('withoutAddNew') && ! $attributes->has('without-add-new'))
|
||||
)
|
||||
<x-form.group.select
|
||||
add-new
|
||||
path="{{ $path }}"
|
||||
|
|
@ -42,10 +50,7 @@
|
|||
:options="$categories"
|
||||
:selected="$selected"
|
||||
sort-options="false"
|
||||
:option_field="[
|
||||
'key' => 'id',
|
||||
'value' => 'title'
|
||||
]"
|
||||
:option_field="$option_field"
|
||||
|
||||
:multiple="$multiple"
|
||||
:group="$group"
|
||||
|
|
@ -59,11 +64,19 @@
|
|||
<template #option="{option}">
|
||||
<div class="flex items-center">
|
||||
<span class="w-5 h-4 rounded-full" :style="{backgroundColor: option.option.color_hex_code}"></span>
|
||||
|
||||
@if ($option_field['value'] == 'title')
|
||||
<span>@{{ option.option.title }}</span>
|
||||
@else
|
||||
<span>@{{ option.option.name }}</span>
|
||||
@endif
|
||||
</div>
|
||||
</template>
|
||||
</x-form.group.select>
|
||||
@elseif ((! $attributes->has('withoutRemote') && ! $attributes->has('without-remote')) && ($attributes->has('withoutAddNew') || $attributes->has('without-add-new')))
|
||||
@elseif (
|
||||
(! $attributes->has('withoutRemote') && ! $attributes->has('without-remote'))
|
||||
&& ($attributes->has('withoutAddNew') || $attributes->has('without-add-new'))
|
||||
)
|
||||
<x-form.group.select
|
||||
remote
|
||||
remote_action="{{ $remoteAction }}"
|
||||
|
|
@ -73,10 +86,7 @@
|
|||
:options="$categories"
|
||||
:selected="$selected"
|
||||
sort-options="false"
|
||||
:option_field="[
|
||||
'key' => 'id',
|
||||
'value' => 'title'
|
||||
]"
|
||||
:option_field="$option_field"
|
||||
|
||||
:multiple="$multiple"
|
||||
:group="$group"
|
||||
|
|
@ -90,7 +100,12 @@
|
|||
<template #option="{option}">
|
||||
<div class="flex items-center">
|
||||
<span class="w-5 h-4 rounded-full" :style="{backgroundColor: option.option.color_hex_code}"></span>
|
||||
|
||||
@if ($option_field['value'] == 'title')
|
||||
<span>@{{ option.option.title }}</span>
|
||||
@else
|
||||
<span>@{{ option.option.name }}</span>
|
||||
@endif
|
||||
</div>
|
||||
</template>
|
||||
</x-form.group.select>
|
||||
|
|
@ -101,10 +116,7 @@
|
|||
:options="$categories"
|
||||
:selected="$selected"
|
||||
sort-options="false"
|
||||
:option_field="[
|
||||
'key' => 'id',
|
||||
'value' => 'title'
|
||||
]"
|
||||
:option_field="$option_field"
|
||||
|
||||
:multiple="$multiple"
|
||||
:group="$group"
|
||||
|
|
@ -117,8 +129,13 @@
|
|||
>
|
||||
<template #option="{option}">
|
||||
<div class="flex items-center">
|
||||
<span class="w-5 h-4 rounded-full":style="{backgroundColor: option.option.color_hex_code}"></span>
|
||||
<span class="w-5 h-4 rounded-full" :style="{backgroundColor: option.option.color_hex_code}"></span>
|
||||
|
||||
@if ($option_field['value'] == 'title')
|
||||
<span>@{{ option.option.title }}</span>
|
||||
@else
|
||||
<span>@{{ option.option.name }}</span>
|
||||
@endif
|
||||
</div>
|
||||
</template>
|
||||
</x-form.group.select>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
<span @class(['font-medium', $textColor])>
|
||||
{{ $amount }}
|
||||
</span>
|
||||
|
|
@ -6,7 +6,27 @@
|
|||
|
||||
<x-form.group.select name="parent_id" label="{{ trans('general.parent') . ' ' . trans_choice('general.categories', 1) }}" :options="$categories" not-required sort-options="false" searchable form-group-class="col-span-6" />
|
||||
|
||||
@if (!empty($types) && count($types) > 1)
|
||||
<x-form.group.select name="type" label="{{ trans_choice('general.types', 1) }}" :options="$types" value="{{ $type }}" form-group-class="col-span-6" change="changeCategories" />
|
||||
|
||||
<x-form.group.text name="code" label="{{ trans('general.code') }}" form-group-class="col-span-6" v-show="show_code_field" />
|
||||
@else
|
||||
<x-form.input.hidden name="type" value="{{ $type }}" />
|
||||
|
||||
@if ($show_code_field)
|
||||
<x-form.group.text name="code" label="{{ trans('general.code') }}" form-group-class="col-span-6" />
|
||||
@endif
|
||||
@endif
|
||||
|
||||
<x-form.group.textarea name="description" label="{{ trans('general.description') }}" not-required />
|
||||
|
||||
<x-form.input.hidden name="enabled" value="1" />
|
||||
</div>
|
||||
</x-form>
|
||||
|
||||
<script type="text/javascript">
|
||||
if (typeof type_codes === 'undefined') {
|
||||
var type_codes = {!! json_encode($type_codes) !!};
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -22,10 +22,14 @@
|
|||
|
||||
<x-form.group.color name="color" label="{{ trans('general.color') }}" />
|
||||
|
||||
<x-form.group.select name="type" label="{{ trans_choice('general.types', 1) }}" :options="$types" :selected="config('general.types')" change="updateParentCategories" />
|
||||
<x-form.group.select name="type" label="{{ trans_choice('general.types', 1) }}" :options="$types" :selected="config('general.types')" change="changeCategories" group />
|
||||
|
||||
<x-form.group.text name="code" label="{{ trans('general.code') }}" v-show="show_code_field" />
|
||||
|
||||
<x-form.group.select name="parent_id" label="{{ trans('general.parent') . ' ' . trans_choice('general.categories', 1) }}" :options="[]" not-required dynamicOptions="categoriesBasedTypes" sort-options="false" v-disabled="selected_type" />
|
||||
|
||||
<x-form.group.textarea name="description" label="{{ trans('general.description') }}" not-required />
|
||||
|
||||
<x-form.input.hidden name="categories" value="{{ json_encode($categories) }}" />
|
||||
</x-slot>
|
||||
</x-form.section>
|
||||
|
|
@ -39,5 +43,11 @@
|
|||
</x-form.container>
|
||||
</x-slot>
|
||||
|
||||
@push('scripts_start')
|
||||
<script type="text/javascript">
|
||||
var type_codes = {!! json_encode($type_codes) !!};
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
<x-script folder="settings" file="categories" />
|
||||
</x-layouts.admin>
|
||||
|
|
|
|||
|
|
@ -14,20 +14,25 @@
|
|||
<x-slot name="body">
|
||||
<x-form.group.text name="name" label="{{ trans('general.name') }}" />
|
||||
|
||||
|
||||
<x-form.group.color name="color" label="{{ trans('general.color') }}" />
|
||||
|
||||
@if ($type_disabled)
|
||||
<x-form.group.select name="type" label="{{ trans_choice('general.types', 1) }}" :options="$types" v-disabled="true" />
|
||||
<x-form.group.select name="type" label="{{ trans_choice('general.types', 1) }}" :options="$types" v-disabled="true" group />
|
||||
|
||||
<input type="hidden" name="type" value="{{ $category->type }}" />
|
||||
@else
|
||||
<x-form.group.select name="type" label="{{ trans_choice('general.types', 1) }}" :options="$types" change="updateParentCategories" />
|
||||
<x-form.group.select name="type" label="{{ trans_choice('general.types', 1) }}" :options="$types" change="changeCategories" group />
|
||||
|
||||
<x-form.group.text name="code" label="{{ trans('general.code') }}" v-show="show_code_field" />
|
||||
|
||||
<x-form.group.select name="parent_id" label="{{ trans('general.parent') . ' ' . trans_choice('general.categories', 1) }}" :options="$parent_categories" not-required dynamicOptions="categoriesBasedTypes" sort-options="false" />
|
||||
|
||||
<x-form.input.hidden name="parent_category_id" value="{{ $category->parent_id }}" />
|
||||
<x-form.input.hidden name="categories" value="{{ json_encode($categories) }}" />
|
||||
@endif
|
||||
|
||||
<x-form.group.textarea name="description" label="{{ trans('general.description') }}" not-required />
|
||||
</x-slot>
|
||||
</x-form.section>
|
||||
|
||||
|
|
@ -46,5 +51,11 @@
|
|||
</x-form.container>
|
||||
</x-slot>
|
||||
|
||||
@push('scripts_start')
|
||||
<script type="text/javascript">
|
||||
var type_codes = {!! json_encode($type_codes) !!};
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
<x-script folder="settings" file="categories" />
|
||||
</x-layouts.admin>
|
||||
|
|
|
|||
|
|
@ -37,6 +37,51 @@
|
|||
|
||||
<x-slot name="content">
|
||||
<x-index.container>
|
||||
<x-tabs active="{{ $tab_active }}">
|
||||
<x-slot name="navs">
|
||||
@foreach($tabs as $tab => $data)
|
||||
@if ($tab_active == $tab)
|
||||
<x-tabs.nav-pin
|
||||
id="{{ $tab }}"
|
||||
name="{{ $data['name'] }}"
|
||||
type="categories"
|
||||
tab="{{ $tab }}"
|
||||
/>
|
||||
@else
|
||||
<x-tabs.nav-pin
|
||||
id="{{ $tab }}"
|
||||
href="{{ route('categories.index', ['search' => 'type:' . $data['key']]) }}"
|
||||
name="{{ $data['name'] }}"
|
||||
type="categories"
|
||||
tab="{{ $tab }}"
|
||||
/>
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
@if ($tab_active == 'categories-all')
|
||||
<x-tabs.nav-pin
|
||||
id="categories-all"
|
||||
name="{{ trans('general.all_type', ['type' => trans_choice('general.categories', 2)]) }}"
|
||||
type="categories"
|
||||
tab="all"
|
||||
/>
|
||||
@else
|
||||
<x-tabs.nav-pin
|
||||
id="categories-all"
|
||||
href="{{ route('categories.index', ['list_records' => 'all']) }}"
|
||||
name="{{ trans('general.all_type', ['type' => trans_choice('general.categories', 2)]) }}"
|
||||
type="categories"
|
||||
tab="all"
|
||||
/>
|
||||
@endif
|
||||
</x-slot>
|
||||
|
||||
<x-slot name="content">
|
||||
@php
|
||||
$name_class = $hide_code_column ? 'w-5/12' : 'w-4/12';
|
||||
@endphp
|
||||
|
||||
<x-tabs.tab id="{{ $tab_active }}">
|
||||
<x-index.search
|
||||
search-string="App\Models\Setting\Category"
|
||||
bulk-action="App\BulkActions\Settings\Categories"
|
||||
|
|
@ -49,13 +94,23 @@
|
|||
<x-index.bulkaction.all />
|
||||
</x-table.th>
|
||||
|
||||
<x-table.th class="w-6/12">
|
||||
@if (!$hide_code_column)
|
||||
<x-table.th class="w-1/12">
|
||||
<x-sortablelink column="code" title="{{ trans('general.code') }}" />
|
||||
</x-table.th>
|
||||
@endif
|
||||
|
||||
<x-table.th class="{{ $name_class }}">
|
||||
<x-sortablelink column="name" title="{{ trans('general.name') }}" />
|
||||
</x-table.th>
|
||||
|
||||
<x-table.th class="w-6/12">
|
||||
<x-table.th class="w-3/12">
|
||||
<x-sortablelink column="type" title="{{ trans_choice('general.types', 1) }}" />
|
||||
</x-table.th>
|
||||
|
||||
<x-table.th class="w-2/12 ltr:text-right rtl:text-left">
|
||||
{{ trans('general.balance') }}
|
||||
</x-table.th>
|
||||
</x-table.tr>
|
||||
</x-table.thead>
|
||||
|
||||
|
|
@ -70,7 +125,17 @@
|
|||
/>
|
||||
</x-table.td>
|
||||
|
||||
<x-table.td class="w-6/12">
|
||||
@if (!$hide_code_column)
|
||||
<x-table.td class="w-1/12">
|
||||
@if(!empty($item->code))
|
||||
{{ $item->code }}
|
||||
@else
|
||||
<x-empty-data />
|
||||
@endif
|
||||
</x-table.td>
|
||||
@endif
|
||||
|
||||
<x-table.td class="{{ $name_class }}">
|
||||
<div class="flex items-center">
|
||||
@if ($item->sub_categories->count())
|
||||
<x-tooltip id="tooltip-category-{{ $item->id }}" placement="bottom" message="{{ trans('categories.collapse') }}">
|
||||
|
|
@ -103,7 +168,7 @@
|
|||
@endif
|
||||
</x-table.td>
|
||||
|
||||
<x-table.td class="w-6/12">
|
||||
<x-table.td class="w-3/12">
|
||||
@if (! empty($types[$item->type]))
|
||||
{{ $types[$item->type] }}
|
||||
@else
|
||||
|
|
@ -111,19 +176,26 @@
|
|||
@endif
|
||||
</x-table.td>
|
||||
|
||||
<x-table.td class="w-2/12 ltr:text-right rtl:text-left">
|
||||
<x-index.balance :amount="$item->balance" />
|
||||
</x-table.td>
|
||||
|
||||
<x-table.td kind="action">
|
||||
<x-table.actions :model="$item" />
|
||||
</x-table.td>
|
||||
</x-table.tr>
|
||||
|
||||
@foreach($item->sub_categories as $sub_category)
|
||||
@include('settings.categories.sub_category', ['parent_category' => $item, 'sub_category' => $sub_category, 'tree_level' => 1])
|
||||
@include('settings.categories.sub_category', ['parent_category' => $item, 'sub_category' => $sub_category, 'tree_level' => 1, 'hide_code_column' => $hide_code_column, 'name_class' => $name_class])
|
||||
@endforeach
|
||||
@endforeach
|
||||
</x-table.tbody>
|
||||
</x-table>
|
||||
|
||||
<x-pagination :items="$categories" />
|
||||
</x-tabs.tab>
|
||||
</x-slot>
|
||||
</x-tabs>
|
||||
</x-index.container>
|
||||
</x-slot>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,64 @@
|
|||
@if ($sub_category->sub_categories)
|
||||
@if ($loop->first)
|
||||
<x-table.tr data-collapse="child-{{ $parent_category->id }}" data-animation class="relative flex items-center hover:bg-gray-100 px-1 group border-b transition-all collapse-sub" href="{{ route('categories.edit', $sub_category->id) }}">
|
||||
<x-table.td kind="bulkaction">
|
||||
<x-index.bulkaction.single id="{{ $parent_category->id }}" name="{{ $parent_category->name }}" disabled />
|
||||
</x-table.td>
|
||||
|
||||
@if (!$hide_code_column && (empty(config('type.category.' . $parent_category->type . '.hide', [])) || ! in_array('code', config('type.category.' . $sub_category->type . '.hide'))))
|
||||
<x-table.td class="w-1/12 py-4 ltr:text-left rtl:text-right whitespace-nowrap text-sm font-medium text-black truncate">
|
||||
@if(!empty($parent_category->code))
|
||||
{{ $parent_category->code }}
|
||||
@else
|
||||
<x-empty-data />
|
||||
@endif
|
||||
</x-table.td>
|
||||
@endif
|
||||
|
||||
<x-table.td class="relative {{ $name_class }} py-4 ltr:text-left rtl:text-right whitespace-nowrap text-sm font-medium text-black truncate" style="padding-left: {{ $tree_level * 30 }}px;">
|
||||
<div class="flex items-center ml-2">
|
||||
<span class="material-icons text-3xl text-{{ $parent_category->color }}" style="color:{{ $sub_category->color }};">circle</span>
|
||||
|
||||
<div class="flex items-center font-bold table-submenu ltr:ml-2 rtl:mr-2">
|
||||
{{ $parent_category->name }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (! $parent_category->enabled)
|
||||
<x-index.disable text="{{ trans_choice('general.categories', 1) }}" />
|
||||
@endif
|
||||
</x-table.td>
|
||||
|
||||
<x-table.td class="w-3/12 py-4 ltr:text-left rtl:text-right whitespace-nowrap text-sm font-normal text-black cursor-pointer truncate">
|
||||
@if (! empty($types[$item->type]))
|
||||
{{ $types[$item->type] }}
|
||||
@else
|
||||
<x-empty-data />
|
||||
@endif
|
||||
</x-table.td>
|
||||
|
||||
<x-table.td class="w-2/12 py-4 ltr:text-right rtl:text-left whitespace-nowrap text-sm font-normal text-black cursor-pointer truncate">
|
||||
<x-index.balance :amount="$parent_category->balance_without_subcategories" />
|
||||
</x-table.td>
|
||||
</x-table.tr>
|
||||
@endif
|
||||
|
||||
<x-table.tr data-collapse="child-{{ $parent_category->id }}" data-animation class="relative flex items-center hover:bg-gray-100 px-1 group border-b transition-all collapse-sub" href="{{ route('categories.edit', $sub_category->id) }}">
|
||||
<x-table.td kind="bulkaction">
|
||||
<x-index.bulkaction.single id="{{ $sub_category->id }}" name="{{ $sub_category->name }}" />
|
||||
</x-table.td>
|
||||
|
||||
<x-table.td class="relative w-6/12 py-4 ltr:text-left rtl:text-right whitespace-nowrap text-sm font-medium text-black truncate" style="padding-left: {{ $tree_level * 30 }}px;">
|
||||
@if (!$hide_code_column && (empty(config('type.category.' . $sub_category->type . '.hide', [])) || ! in_array('code', config('type.category.' . $sub_category->type . '.hide'))))
|
||||
<x-table.td class="w-1/12 py-4 ltr:text-left rtl:text-right whitespace-nowrap text-sm font-medium text-black truncate">
|
||||
@if(!empty($sub_category->code))
|
||||
{{ $sub_category->code }}
|
||||
@else
|
||||
<x-empty-data />
|
||||
@endif
|
||||
</x-table.td>
|
||||
@endif
|
||||
|
||||
<x-table.td class="relative {{ $name_class }} py-4 ltr:text-left rtl:text-right whitespace-nowrap text-sm font-medium text-black truncate" style="padding-left: {{ $tree_level * 30 }}px;">
|
||||
<div class="flex items-center ml-2">
|
||||
@if ($sub_category->sub_categories->count())
|
||||
<x-tooltip id="tooltip-category-{{ $parent_category->id }}" placement="bottom" message="{{ trans('categories.collapse') }}">
|
||||
|
|
@ -20,23 +74,21 @@
|
|||
<div class="flex items-center font-bold table-submenu">
|
||||
{{ $sub_category->name }}
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<div class="flex items-center ml-2">
|
||||
<span class="material-icons text-3xl text-{{ $sub_category->color }}" style="color:{{ $sub_category->color }};">circle</span>
|
||||
|
||||
<div class="flex items-center font-bold table-submenu ltr:ml-2 rtl:mr-2">
|
||||
{{ $sub_category->name }}
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@if (! $sub_category->enabled)
|
||||
<x-index.disable text="{{ trans_choice('general.categories', 1) }}" />
|
||||
@endif
|
||||
</x-table.td>
|
||||
|
||||
<x-table.td class="w-6/12 py-4 ltr:text-left rtl:text-right whitespace-nowrap text-sm font-normal text-black cursor-pointer truncate">
|
||||
<x-table.td class="w-3/12 py-4 ltr:text-left rtl:text-right whitespace-nowrap text-sm font-normal text-black cursor-pointer truncate">
|
||||
@if (! empty($types[$item->type]))
|
||||
{{ $types[$item->type] }}
|
||||
@else
|
||||
|
|
@ -44,6 +96,10 @@
|
|||
@endif
|
||||
</x-table.td>
|
||||
|
||||
<x-table.td class="w-2/12 py-4 ltr:text-right rtl:text-left whitespace-nowrap text-sm font-normal text-black cursor-pointer truncate">
|
||||
<x-index.balance :amount="$sub_category->balance" />
|
||||
</x-table.td>
|
||||
|
||||
<x-table.td kind="action">
|
||||
<x-table.actions :model="$sub_category" />
|
||||
</x-table.td>
|
||||
|
|
@ -55,6 +111,9 @@
|
|||
@endphp
|
||||
|
||||
@foreach($sub_category->sub_categories as $sub_category)
|
||||
@include('settings.categories.sub_category', ['parent_category' => $parent_category, 'sub_category' => $sub_category, 'tree_level' => $tree_level])
|
||||
@php
|
||||
$sub_category->load(['sub_categories']);
|
||||
@endphp
|
||||
@include('settings.categories.sub_category', ['parent_category' => $parent_category, 'sub_category' => $sub_category, 'tree_level' => $tree_level, 'hide_code_column' => $hide_code_column, 'name_class' => $name_class])
|
||||
@endforeach
|
||||
@endif
|
||||
|
|
@ -31,9 +31,9 @@
|
|||
</x-slot>
|
||||
|
||||
<x-slot name="body">
|
||||
<x-form.group.select remote name="income_category" label="{{ trans('settings.default.income_category') }}" :options="$sales_categories" :clearable="'false'" :selected="setting('default.income_category')" remote_action="{{ route('categories.index'). '?search=type:income enabled:1' }}" sort-options="false" />
|
||||
<x-form.group.select remote name="income_category" label="{{ trans('settings.default.income_category') }}" :options="$sales_categories" :clearable="'false'" :selected="setting('default.income_category')" remote_action="{{ route('categories.index'). '?search=type:' . $income_category_types . ' enabled:1' }}" sort-options="false" />
|
||||
|
||||
<x-form.group.select remote name="expense_category" label="{{ trans('settings.default.expense_category') }}" :options="$purchases_categories" :clearable="'false'" :selected="setting('default.expense_category')" remote_action="{{ route('categories.index'). '?search=type:expense enabled:1' }}" sort-options="false" />
|
||||
<x-form.group.select remote name="expense_category" label="{{ trans('settings.default.expense_category') }}" :options="$purchases_categories" :clearable="'false'" :selected="setting('default.expense_category')" remote_action="{{ route('categories.index'). '?search=type:' . $expense_category_types . ' enabled:1' }}" sort-options="false" />
|
||||
</x-slot>
|
||||
</x-form.section>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue