added category new fields(code, description)
This commit is contained in:
parent
2c12e7524c
commit
2c4d52b15e
|
|
@ -6,10 +6,14 @@ use App\Abstracts\Http\Controller;
|
|||
use App\Http\Requests\Setting\Category as Request;
|
||||
use App\Jobs\Setting\CreateCategory;
|
||||
use App\Models\Setting\Category;
|
||||
use App\Traits\Categories as Helper;
|
||||
use App\Traits\Modules;
|
||||
use Illuminate\Http\Request as IRequest;
|
||||
|
||||
class Categories extends Controller
|
||||
{
|
||||
use Helper, Modules;
|
||||
|
||||
/**
|
||||
* Instantiate a new controller instance.
|
||||
*/
|
||||
|
|
@ -29,19 +33,42 @@ class Categories extends Controller
|
|||
*/
|
||||
public function create(IRequest $request)
|
||||
{
|
||||
$type = $request->get('type', 'item');
|
||||
$type = $request->get('type', Category::ITEM_TYPE);
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
$categories = collect();
|
||||
|
||||
Category::type($type)->enabled()->orderBy('name')->get()->each(function ($category) use (&$categories) {
|
||||
$categories->push([
|
||||
'id' => $category->id,
|
||||
'title' => $category->name,
|
||||
'level' => $category->level,
|
||||
]);
|
||||
});
|
||||
Category::type($types)
|
||||
->enabled()
|
||||
->orderBy('name')
|
||||
->get()
|
||||
->each(function ($category) use (&$categories) {
|
||||
$categories->push([
|
||||
'id' => $category->id,
|
||||
'title' => $category->name,
|
||||
'level' => $category->level,
|
||||
]);
|
||||
});
|
||||
|
||||
$html = view('modals.categories.create', compact('type', 'categories'))->render();
|
||||
$has_code = $this->moduleIsEnabled('double-entry');
|
||||
|
||||
$html = view('modals.categories.create', compact('type', 'types', 'categories', 'has_code'))->render();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
|
|
@ -61,7 +88,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));
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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' => [
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -2,11 +2,22 @@
|
|||
<div class="grid sm:grid-cols-6 gap-x-8 gap-y-6 my-3.5">
|
||||
<x-form.group.text name="name" label="{{ trans('general.name') }}" form-group-class="col-span-6" />
|
||||
|
||||
@if ($has_code)
|
||||
<x-form.group.text name="code" label="{{ trans('general.code') }}" form-group-class="col-span-6" />
|
||||
@endif
|
||||
|
||||
<x-form.group.color name="color" label="{{ trans('general.color') }}" form-group-class="col-span-6" />
|
||||
|
||||
<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" />
|
||||
|
||||
<x-form.input.hidden name="type" value="{{ $type }}" />
|
||||
@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" />
|
||||
@else
|
||||
<x-form.input.hidden name="type" value="{{ $type }}" />
|
||||
@endif
|
||||
|
||||
<x-form.group.textarea name="description" label="{{ trans('general.description') }}" not-required />
|
||||
|
||||
<x-form.input.hidden name="enabled" value="1" />
|
||||
</div>
|
||||
</x-form>
|
||||
|
|
|
|||
|
|
@ -20,12 +20,18 @@
|
|||
<x-slot name="body">
|
||||
<x-form.group.text name="name" label="{{ trans('general.name') }}" />
|
||||
|
||||
@if ($has_code)
|
||||
<x-form.group.text name="code" label="{{ trans('general.code') }}" />
|
||||
@endif
|
||||
|
||||
<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="updateParentCategories" group />
|
||||
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -14,20 +14,26 @@
|
|||
<x-slot name="body">
|
||||
<x-form.group.text name="name" label="{{ trans('general.name') }}" />
|
||||
|
||||
@if ($has_code)
|
||||
<x-form.group.text name="code" label="{{ trans('general.code') }}" />
|
||||
@endif
|
||||
|
||||
<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="updateParentCategories" group />
|
||||
|
||||
<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>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue