Merge branch 'master' of github.com:akaunting/akaunting

Conflicts:
	app/Http/Controllers/Auth/Login.php
This commit is contained in:
cuneytsenturk 2018-12-03 13:49:31 +03:00
commit 2570dad2b7
853 changed files with 22158 additions and 3528 deletions

View File

@ -26,3 +26,5 @@ MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_NAME=null
MAIL_FROM_ADDRESS=null

26
.travis.yml Normal file
View File

@ -0,0 +1,26 @@
language: php
php:
- '5.6'
- '7.0'
- '7.1'
- '7.2'
env:
- DB=sqlite
sqlite:
adapter: sqlite3
database: ':memory:'
timeout: 500
before_install:
# turn off XDebug
- phpenv config-rm xdebug.ini || return
before_script:
- cp .env.testing .env
- composer install --no-interaction
script:
- vendor/bin/phpunit

View File

@ -1,6 +1,6 @@
# Akaunting™
# Akaunting™
![Latest Stable Version](https://img.shields.io/github/release/akaunting/akaunting.svg) ![Total Downloads](https://img.shields.io/github/downloads/akaunting/akaunting/total.svg) [![Crowdin](https://d322cqt584bo4o.cloudfront.net/akaunting/localized.svg)](https://crowdin.com/project/akaunting) [![Backers on Open Collective](https://opencollective.com/akaunting/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/akaunting/sponsors/badge.svg)](#sponsors)
![Latest Stable Version](https://img.shields.io/github/release/akaunting/akaunting.svg) ![Total Downloads](https://img.shields.io/github/downloads/akaunting/akaunting/total.svg) [![Crowdin](https://d322cqt584bo4o.cloudfront.net/akaunting/localized.svg)](https://crowdin.com/project/akaunting) ![Build Status](https://travis-ci.com/akaunting/akaunting.svg) [![Backers on Open Collective](https://opencollective.com/akaunting/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/akaunting/sponsors/badge.svg)](#sponsors)
Akaunting is a free, open source and online accounting software designed for small businesses and freelancers. It is built with modern technologies such as Laravel, Bootstrap, jQuery, RESTful API etc. Thanks to its modular structure, Akaunting provides an awesome App Store for users and developers.

View File

@ -0,0 +1,78 @@
<?php
namespace App\Console\Commands;
use App\Models\Module\Module;
use App\Models\Module\ModuleHistory;
use Artisan;
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputArgument;
class ModuleDelete extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'module:delete {alias} {company_id}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Delete the specified module.';
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$alias = $this->argument('alias');
$company_id = $this->argument('company_id');
$model = Module::alias($alias)->companyId($company_id)->first();
if (!$model) {
$this->info("Module [{$alias}] not found.");
return;
}
$module = $this->laravel['modules']->findByAlias($alias);
$module->delete();
$model->status = 0;
$model->save();
// Add history
$data = [
'company_id' => $company_id,
'module_id' => $model->id,
'category' => $module->get('category'),
'version' => $module->get('version'),
'description' => trans('modules.deleted', ['module' => $module->get('name')]),
];
ModuleHistory::create($data);
Artisan::call('cache:clear');
$this->info("Module [{$alias}] deleted.");
}
/**
* Get the console command arguments.
*
* @return array
*/
protected function getArguments()
{
return array(
array('alias', InputArgument::REQUIRED, 'Module alias.'),
array('company_id', InputArgument::REQUIRED, 'Company ID.'),
);
}
}

View File

@ -54,6 +54,9 @@ class ModuleInstall extends Command
ModuleHistory::create($data);
// Clear cache
$this->call('cache:clear');
// Update database
$this->call('migrate', ['--force' => true]);

View File

@ -63,43 +63,41 @@ class RecurringCheck extends Command
$company->setSettings();
foreach ($company->recurring as $recur) {
if (!$current = $recur->current()) {
continue;
}
foreach ($company->recurring as $recurring) {
foreach ($recurring->schedule() as $recur) {
$recur_date = Date::parse($recur->getStart()->format('Y-m-d'));
$current_date = Date::parse($current->format('Y-m-d'));
// Check if should recur today
if ($this->today->ne($recur_date)) {
continue;
}
// Check if should recur today
if ($this->today->ne($current_date)) {
continue;
}
$model = $recurring->recurable;
$model = $recur->recurable;
if (!$model) {
continue;
}
if (!$model) {
continue;
}
switch ($recurring->recurable_type) {
case 'App\Models\Expense\Bill':
$this->recurBill($company, $model);
break;
case 'App\Models\Income\Invoice':
$this->recurInvoice($company, $model);
break;
case 'App\Models\Expense\Payment':
case 'App\Models\Income\Revenue':
$model->cloneable_relations = [];
switch ($recur->recurable_type) {
case 'App\Models\Expense\Bill':
$this->recurBill($company, $model);
break;
case 'App\Models\Income\Invoice':
$this->recurInvoice($company, $model);
break;
case 'App\Models\Expense\Payment':
case 'App\Models\Income\Revenue':
$model->cloneable_relations = [];
// Create new record
$clone = $model->duplicate();
// Create new record
$clone = $model->duplicate();
$clone->parent_id = $model->id;
$clone->paid_at = $this->today->format('Y-m-d');
$clone->save();
$clone->parent_id = $model->id;
$clone->paid_at = $this->today->format('Y-m-d');
$clone->save();
break;
break;
}
}
}
}

View File

@ -13,10 +13,11 @@ class Kernel extends ConsoleKernel
* @var array
*/
protected $commands = [
Commands\CompanySeed::class,
Commands\BillReminder::class,
Commands\CompanySeed::class,
Commands\Install::class,
Commands\InvoiceReminder::class,
Commands\ModuleDelete::class,
Commands\ModuleDisable::class,
Commands\ModuleEnable::class,
Commands\ModuleInstall::class,

View File

@ -15,4 +15,4 @@ class AdminMenuCreated
{
$this->menu = $menu;
}
}
}

View File

@ -15,4 +15,4 @@ class BillCreated
{
$this->bill = $bill;
}
}
}

View File

@ -15,4 +15,4 @@ class BillUpdated
{
$this->bill = $bill;
}
}
}

View File

@ -15,4 +15,4 @@ class CompanySwitched
{
$this->company = $company;
}
}
}

View File

@ -15,4 +15,4 @@ class CustomerMenuCreated
{
$this->menu = $menu;
}
}
}

View File

@ -15,4 +15,4 @@ class InvoiceCreated
{
$this->invoice = $invoice;
}
}
}

View File

@ -15,4 +15,4 @@ class InvoicePrinting
{
$this->invoice = $invoice;
}
}
}

View File

@ -15,4 +15,4 @@ class InvoiceUpdated
{
$this->invoice = $invoice;
}
}
}

View File

@ -19,4 +19,4 @@ class ModuleInstalled
$this->alias = $alias;
$this->company_id = $company_id;
}
}
}

View File

@ -23,4 +23,4 @@ class UpdateFinished
$this->old = $old;
$this->new = $new;
}
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Filters\Banking;
use EloquentFilter\ModelFilter;
class Reconciliations extends ModelFilter
{
/**
* Related Models that have ModelFilters as well as the method on the ModelFilter
* As [relatedModel => [input_key1, input_key2]].
*
* @var array
*/
public $relations = [];
public function accounts($accounts)
{
return $this->whereIn('account_id', (array) $accounts);
}
}

View File

@ -14,18 +14,27 @@ class Transactions extends ModelFilter
*/
public $relations = [];
public function account($account_id)
public function accounts($accounts)
{
return $this->where('account_id', $account_id);
return $this->whereIn('account_id', (array) $accounts);
}
public function category($category_id)
public function categories($categories)
{
// No category for bills/invoices
if (in_array($this->getModel()->getTable(), ['bill_payments', 'invoice_payments'])) {
return $this;
}
return $this->where('category_id', $category_id);
return $this->whereIn('category_id', (array) $categories);
}
public function date($date)
{
$dates = explode('_', $date);
$dates[0] .= ' 00:00:00';
$dates[1] .= ' 23:59:59';
return $this->whereBetween('paid_at', $dates);
}
}

View File

@ -23,4 +23,13 @@ class Transfers extends ModelFilter
{
return $this->related('revenue', 'revenues.account_id', '=', $account_id);
}
public function date($date)
{
$dates = explode('_', $date);
$dates[0] .= ' 00:00:00';
$dates[1] .= ' 23:59:59';
return $this->whereBetween('paid_at', $dates);
}
}

View File

@ -19,8 +19,8 @@ class Items extends ModelFilter
return $this->whereLike('name', $query);
}
public function category($id)
public function categories($ids)
{
return $this->where('category_id', $id);
return $this->whereIn('category_id', (array) $ids);
}
}

View File

@ -19,13 +19,27 @@ class Bills extends ModelFilter
return $this->whereLike('vendor_name', $query);
}
public function vendor($vendor)
public function vendors($vendors)
{
return $this->where('vendor_id', $vendor);
return $this->whereIn('vendor_id', (array) $vendors);
}
public function status($status)
public function categories($categories)
{
return $this->where('bill_status_code', $status);
return $this->whereIn('category_id', (array) $categories);
}
public function statuses($statuses)
{
return $this->whereIn('bill_status_code', (array) $statuses);
}
public function billDate($date)
{
$dates = explode('_', $date);
$dates[0] .= ' 00:00:00';
$dates[1] .= ' 23:59:59';
return $this->whereBetween('billed_at', $dates);
}
}

View File

@ -19,18 +19,27 @@ class Payments extends ModelFilter
return $this->whereLike('description', $query);
}
public function vendor($vendor)
public function vendors($vendors)
{
return $this->where('vendor_id', $vendor);
return $this->whereIn('vendor_id', (array) $vendors);
}
public function category($category)
public function categories($categories)
{
return $this->where('category_id', $category);
return $this->whereIn('category_id', (array) $categories);
}
public function account($account)
public function accounts($accounts)
{
return $this->where('account_id', $account);
return $this->whereIn('account_id', (array) $accounts);
}
public function date($date)
{
$dates = explode('_', $date);
$dates[0] .= ' 00:00:00';
$dates[1] .= ' 23:59:59';
return $this->whereBetween('paid_at', $dates);
}
}

View File

@ -19,13 +19,27 @@ class Invoices extends ModelFilter
return $this->whereLike('customer_name', $query);
}
public function customer($customer)
public function customers($customers)
{
return $this->where('customer_id', $customer);
return $this->whereIn('customer_id', (array) $customers);
}
public function status($status)
public function categories($categories)
{
return $this->where('invoice_status_code', $status);
return $this->whereIn('category_id', (array) $categories);
}
}
public function statuses($statuses)
{
return $this->whereIn('invoice_status_code', (array) $statuses);
}
public function invoiceDate($date)
{
$dates = explode('_', $date);
$dates[0] .= ' 00:00:00';
$dates[1] .= ' 23:59:59';
return $this->whereBetween('invoiced_at', $dates);
}
}

View File

@ -19,18 +19,27 @@ class Revenues extends ModelFilter
return $this->whereLike('description', $query);
}
public function customer($customer)
public function customers($customers)
{
return $this->where('customer_id', $customer);
return $this->whereIn('customer_id', (array) $customers);
}
public function category($category)
public function categories($categories)
{
return $this->where('category_id', $category);
return $this->whereIn('category_id', (array) $categories);
}
public function account($account)
public function accounts($accounts)
{
return $this->where('account_id', $account);
return $this->whereIn('account_id', (array) $accounts);
}
public function date($date)
{
$dates = explode('_', $date);
$dates[0] .= ' 00:00:00';
$dates[1] .= ' 23:59:59';
return $this->whereBetween('paid_at', $dates);
}
}

View File

@ -19,8 +19,8 @@ class Categories extends ModelFilter
return $this->whereLike('name', $query);
}
public function type($type)
public function types($types)
{
return $this->where('type', $type);
return $this->whereIn('type', (array) $types);
}
}

View File

@ -2,10 +2,10 @@
namespace App\Http\Controllers\Api\Incomes;
use App\Events\InvoiceCreated;
use App\Events\InvoiceUpdated;
use App\Http\Controllers\ApiController;
use App\Http\Requests\Income\Invoice as Request;
use App\Jobs\Income\CreateInvoice;
use App\Models\Income\Invoice;
use App\Models\Income\InvoiceHistory;
use App\Models\Income\InvoiceItem;
@ -13,13 +13,13 @@ use App\Models\Income\InvoicePayment;
use App\Models\Income\InvoiceTotal;
use App\Models\Common\Item;
use App\Models\Setting\Tax;
use App\Notifications\Common\Item as ItemNotification;
use App\Traits\Incomes;
use App\Transformers\Income\Invoice as Transformer;
use Dingo\Api\Routing\Helpers;
class Invoices extends ApiController
{
use Helpers;
use Helpers, Incomes;
/**
* Display a listing of the resource.
@ -63,114 +63,9 @@ class Invoices extends ApiController
$request['amount'] = 0;
}
$invoice = Invoice::create($request->all());
$invoice = dispatch(new CreateInvoice($request));
$taxes = [];
$tax_total = 0;
$sub_total = 0;
$invoice_item = array();
$invoice_item['company_id'] = $request['company_id'];
$invoice_item['invoice_id'] = $invoice->id;
if ($request['item']) {
foreach ($request['item'] as $item) {
$item_id = 0;
$item_sku = '';
if (!empty($item['item_id'])) {
$item_object = Item::find($item['item_id']);
$item_id = $item['item_id'];
$item['name'] = $item_object->name;
$item_sku = $item_object->sku;
// Decrease stock (item sold)
$item_object->quantity -= $item['quantity'];
$item_object->save();
// Notify users if out of stock
if ($item_object->quantity == 0) {
foreach ($item_object->company->users as $user) {
if (!$user->can('read-notifications')) {
continue;
}
$user->notify(new ItemNotification($item_object));
}
}
} elseif (!empty($item['sku'])) {
$item_sku = $item['sku'];
}
$tax = $tax_id = 0;
if (!empty($item['tax_id'])) {
$tax_object = Tax::find($item['tax_id']);
$tax_id = $item['tax_id'];
$tax = (($item['price'] * $item['quantity']) / 100) * $tax_object->rate;
} elseif (!empty($item['tax'])) {
$tax = $item['tax'];
}
$invoice_item['item_id'] = $item_id;
$invoice_item['name'] = str_limit($item['name'], 180, '');
$invoice_item['sku'] = $item_sku;
$invoice_item['quantity'] = $item['quantity'];
$invoice_item['price'] = $item['price'];
$invoice_item['tax'] = $tax;
$invoice_item['tax_id'] = $tax_id;
$invoice_item['total'] = $item['price'] * $item['quantity'];
InvoiceItem::create($invoice_item);
if (isset($tax_object)) {
if (array_key_exists($tax_object->id, $taxes)) {
$taxes[$tax_object->id]['amount'] += $tax;
} else {
$taxes[$tax_object->id] = [
'name' => $tax_object->name,
'amount' => $tax
];
}
}
$tax_total += $tax;
$sub_total += $invoice_item['total'];
unset($item_object);
unset($tax_object);
}
}
if (empty($request['amount'])) {
$request['amount'] = $sub_total + $tax_total;
}
$invoice->update($request->input());
// Add invoice totals
$this->addTotals($invoice, $request, $taxes, $sub_total, $tax_total);
$request['invoice_id'] = $invoice->id;
$request['status_code'] = $request['invoice_status_code'];
$request['notify'] = 0;
$request['description'] = trans('messages.success.added', ['type' => $request['invoice_number']]);
InvoiceHistory::create($request->input());
// Update next invoice number
$next = setting('general.invoice_number_next', 1) + 1;
setting(['general.invoice_number_next' => $next]);
setting()->save();
// Fire the event to make it extendible
event(new InvoiceCreated($invoice));
return $this->response->created(url('api/invoices/'.$invoice->id));
return $this->response->created(url('api/invoices/' . $invoice->id));
}
/**
@ -277,12 +172,9 @@ class Invoices extends ApiController
*/
public function destroy(Invoice $invoice)
{
$this->deleteRelationships($invoice, ['items', 'histories', 'payments', 'recurring', 'totals']);
$invoice->delete();
InvoiceItem::where('invoice_id', $invoice->id)->delete();
InvoicePayment::where('invoice_id', $invoice->id)->delete();
InvoiceHistory::where('invoice_id', $invoice->id)->delete();
return $this->response->noContent();
}

View File

@ -49,7 +49,7 @@ class Login extends Controller
public function store()
{
// Attempt to login
if (!auth()->attempt(request(['email', 'password']))) {
if (!auth()->attempt(request(['email', 'password']), request('remember', false))) {
flash(trans('auth.failed'))->error();
return back();
@ -79,6 +79,11 @@ class Login extends Controller
return redirect($path);
}
// Check wizard
if (!setting('general.wizard', false)) {
return redirect('wizard');
}
return redirect()->intended('/');
}

View File

@ -29,9 +29,21 @@ class Roles extends Controller
*/
public function create()
{
$permissions = Permission::all();
$names = $permissions = [];
$allPermissions = Permission::all();
return view('auth.roles.create', compact('permissions'));
foreach ($allPermissions as $permission) {
// permission code explode - and get permission type
$n = explode('-', $permission->name);
if (!in_array($n[0], $names)) {
$names[] = $n[0];
}
$permissions[$n[0]][] = $permission;
}
return view('auth.roles.create', compact('names', 'permissions'));
}
/**
@ -66,11 +78,23 @@ class Roles extends Controller
public function edit(Role $role)
{
//$permissions = Permission::all()->sortBy('display_name');
$permissions = Permission::all();
$names = $permissions = [];
$allPermissions = Permission::all();
$rolePermissions = $role->permissions->pluck('id', 'id')->toArray();
return view('auth.roles.edit', compact('role', 'permissions', 'rolePermissions'));
foreach ($allPermissions as $permission) {
// permission code explode - and get permission type
$n = explode('-', $permission->name);
if (!in_array($n[0], $names)) {
$names[] = $n[0];
}
$permissions[$n[0]][] = $permission;
}
return view('auth.roles.edit', compact('role', 'names', 'permissions', 'rolePermissions'));
}
/**

View File

@ -98,8 +98,15 @@ class Accounts extends Controller
*/
public function update(Account $account, Request $request)
{
// Check if we can disable it
if (!$request['enabled']) {
// Check if we can disable or change the code
if (!$request['enabled'] || ($account->currency_code != $request['currency_code'])) {
$relationships = $this->countRelationships($account, [
'invoice_payments' => 'invoices',
'revenues' => 'revenues',
'bill_payments' => 'bills',
'payments' => 'payments',
]);
if ($account->id == setting('general.default_account')) {
$relationships[] = strtolower(trans_choice('general.companies', 1));
}
@ -120,7 +127,7 @@ class Accounts extends Controller
return redirect('banking/accounts');
} else {
$message = trans('messages.warning.disabled', ['name' => $account->name, 'text' => implode(', ', $relationships)]);
$message = trans('messages.warning.disable_code', ['name' => $account->name, 'text' => implode(', ', $relationships)]);
flash($message)->warning();

View File

@ -0,0 +1,315 @@
<?php
namespace App\Http\Controllers\Banking;
use App\Http\Controllers\Controller;
use App\Http\Requests\Banking\Reconciliation as Request;
use App\Models\Banking\Account;
use App\Models\Banking\Reconciliation;
use App\Models\Setting\Currency;
use Date;
class Reconciliations extends Controller
{
/**
* Display a listing of the resource.
*
* @return Response
*/
public function index()
{
$reconciliations = Reconciliation::collect();
$accounts = collect(Account::enabled()->orderBy('name')->pluck('name', 'id'));
return view('banking.reconciliations.index', compact('reconciliations', 'accounts'));
}
/**
* Show the form for viewing the specified resource.
*
* @return Response
*/
public function show()
{
return redirect()->route('reconciliations.index');
}
/**
* Show the form for creating a new resource.
*
* @return Response
*/
public function create()
{
$accounts = Account::enabled()->pluck('name', 'id');
$account_id = request('account_id', setting('general.default_account'));
$started_at = request('started_at', '0000-00-00');
$ended_at = request('ended_at', '0000-00-00');
$account = Account::find($account_id);
$currency = $account->currency;
$transactions = $this->getTransactions($account, $started_at, $ended_at);
$opening_balance = $this->getOpeningBalance($account, $started_at, $ended_at);
return view('banking.reconciliations.create', compact('accounts', 'account', 'currency', 'opening_balance', 'transactions'));
}
/**
* Store a newly created resource in storage.
*
* @param Request $request
*
* @return Response
*/
public function store(Request $request)
{
$reconcile = $request->get('reconcile');
$transactions = $request->get('transactions');
Reconciliation::create([
'company_id' => session('company_id'),
'account_id' => $request->get('account_id'),
'started_at' => $request->get('started_at'),
'ended_at' => $request->get('ended_at'),
'closing_balance' => $request->get('closing_balance'),
'reconciled' => $reconcile ? 1 : 0,
]);
if ($transactions) {
foreach ($transactions as $key => $value) {
$t = explode('_', $key);
$m = '\\' . $t['1'];
$transaction = $m::find($t[0]);
$transaction->reconciled = 1;
$transaction->save();
}
}
$message = trans('messages.success.added', ['type' => trans_choice('general.reconciliations', 1)]);
flash($message)->success();
return redirect()->route('reconciliations.index');
}
/**
* Show the form for editing the specified resource.
*
* @param Reconciliation $reconciliation
*
* @return Response
*/
public function edit(Reconciliation $reconciliation)
{
$account = $reconciliation->account;
$currency = $account->currency;
$transactions = $this->getTransactions($account, $reconciliation->started_at, $reconciliation->ended_at);
$opening_balance = $this->getOpeningBalance($account, $reconciliation->started_at, $reconciliation->ended_at);
return view('banking.reconciliations.edit', compact('reconciliation', 'account', 'currency', 'opening_balance', 'transactions'));
}
/**
* Update the specified resource in storage.
*
* @param Reconciliation $reconciliation
* @param Request $request
*
* @return Response
*/
public function update(Reconciliation $reconciliation, Request $request)
{
$reconcile = $request->get('reconcile');
$transactions = $request->get('transactions');
$reconciliation->reconciled = $reconcile ? 1 : 0;
$reconciliation->save();
if ($transactions) {
foreach ($transactions as $key => $value) {
$t = explode('_', $key);
$m = '\\' . $t['1'];
$transaction = $m::find($t[0]);
$transaction->reconciled = 1;
$transaction->save();
}
}
$message = trans('messages.success.updated', ['type' => trans_choice('general.reconciliations', 1)]);
flash($message)->success();
return redirect()->route('reconciliations.index');
}
/**
* Remove the specified resource from storage.
*
* @param Reconciliation $reconciliation
*
* @return Response
*/
public function destroy(Reconciliation $reconciliation)
{
$reconciliation->delete();
$models = [
'App\Models\Expense\Payment',
'App\Models\Expense\BillPayment',
'App\Models\Income\Revenue',
'App\Models\Income\InvoicePayment',
];
foreach ($models as $model) {
$m = '\\' . $model;
$m::where('account_id', $reconciliation->account_id)
->reconciled()
->whereBetween('paid_at', [$reconciliation->started_at, $reconciliation->ended_at])->each(function ($item) {
$item->reconciled = 0;
$item->save();
});
}
$message = trans('messages.success.deleted', ['type' => trans_choice('general.reconciliations', 1)]);
flash($message)->success();
return redirect()->route('reconciliations.index');
}
/**
* Add transactions array.
*
* @param $account_id
* @param $started_at
* @param $ended_at
*
* @return array
*/
protected function getTransactions($account, $started_at, $ended_at)
{
$started = explode(' ', $started_at);
$ended = explode(' ', $ended_at);
$models = [
'App\Models\Expense\Payment',
'App\Models\Expense\BillPayment',
'App\Models\Income\Revenue',
'App\Models\Income\InvoicePayment',
];
$transactions = [];
foreach ($models as $model) {
$m = '\\' . $model;
$m::where('account_id', $account->id)->whereBetween('paid_at', [$started[0], $ended[0]])->each(function($item) use(&$transactions, $model) {
$item->model = $model;
if (($model == 'App\Models\Income\Invoice') || ($model == 'App\Models\Income\Revenue')) {
if ($item->invoice) {
$item->contact = $item->invoice->customer;
} else {
$item->contact = $item->customer;
}
} else {
if ($item->bill) {
$item->contact = $item->bill->vendor;
} else {
$item->contact = $item->vendor;
}
}
$transactions[] = $item;
});
}
return collect($transactions)->sortByDesc('paid_at');
}
/**
* Get the opening balance
*
* @param $account
* @param $started_at
*
* @return string
*/
public function getOpeningBalance($account, $started_at)
{
// Opening Balance
$total = $account->opening_balance;
// Sum invoices
$invoice_payments = $account->invoice_payments()->whereDate('paid_at', '<', $started_at)->get();
foreach ($invoice_payments as $item) {
$total += $item->amount;
}
// Sum revenues
$revenues = $account->revenues()->whereDate('paid_at', '<', $started_at)->get();
foreach ($revenues as $item) {
$total += $item->amount;
}
// Subtract bills
$bill_payments = $account->bill_payments()->whereDate('paid_at', '<', $started_at)->get();
foreach ($bill_payments as $item) {
$total -= $item->amount;
}
// Subtract payments
$payments = $account->payments()->whereDate('paid_at', '<', $started_at)->get();
foreach ($payments as $item) {
$total -= $item->amount;
}
return $total;
}
public function calculate()
{
$currency_code = request('currency_code');
$closing_balance = request('closing_balance');
$json = new \stdClass();
$cleared_amount = $difference = $income_total = $expense_total = 0;
if ($transactions = request('transactions')) {
$opening_balance = request('opening_balance');
foreach ($transactions as $key => $value) {
$model = explode('_', $key);
if (($model[1] == 'App\Models\Income\Invoice') || ($model[1] == 'App\Models\Income\Revenue')) {
$income_total += $value;
} else {
$expense_total += $value;
}
}
$cleared_amount = $opening_balance + ($income_total - $expense_total);
}
$difference = $closing_balance - $cleared_amount;
$json->closing_balance = money($closing_balance, $currency_code, true)->format();
$json->cleared_amount = money($cleared_amount, $currency_code, true)->format();
$json->difference = money($difference, $currency_code, true)->format();
$json->difference_raw = (int) $difference;
return response()->json($json);
}
}

View File

@ -25,25 +25,24 @@ class Transactions extends Controller
{
$request = request();
$accounts = collect(Account::enabled()->pluck('name', 'id'))
->prepend(trans('general.all_type', ['type' => trans_choice('general.accounts', 2)]), '');
$accounts = collect(Account::enabled()->pluck('name', 'id'));
$types = collect(['expense' => 'Expense', 'income' => 'Income'])
->prepend(trans('general.all_type', ['type' => trans_choice('general.types', 2)]), '');
$categories = collect(Category::enabled()->type('income')->pluck('name', 'id'))
->prepend(trans('general.all_type', ['type' => trans_choice('general.categories', 2)]), '');
$type = $request->get('type');
$type_cats = empty($type) ? ['income', 'expense'] : $type;
$categories = collect(Category::enabled()->type($type_cats)->pluck('name', 'id'));
if ($type != 'income') {
$this->addTransactions(Payment::collect(['paid_at'=> 'desc']), trans_choice('general.expenses', 1));
$this->addTransactions(BillPayment::collect(['paid_at'=> 'desc']), trans_choice('general.expenses', 1), trans_choice('general.bills', 1));
$this->addTransactions(BillPayment::collect(['paid_at'=> 'desc']), trans_choice('general.expenses', 1));
}
if ($type != 'expense') {
$this->addTransactions(Revenue::collect(['paid_at'=> 'desc']), trans_choice('general.incomes', 1));
$this->addTransactions(InvoicePayment::collect(['paid_at'=> 'desc']), trans_choice('general.incomes', 1), trans_choice('general.invoices', 1));
$this->addTransactions(InvoicePayment::collect(['paid_at'=> 'desc']), trans_choice('general.incomes', 1));
}
$transactions = $this->getTransactions($request);
@ -56,27 +55,29 @@ class Transactions extends Controller
*
* @param $items
* @param $type
* @param $category
*/
protected function addTransactions($items, $type, $category = null)
protected function addTransactions($items, $type)
{
foreach ($items as $item) {
$data = [
if (!empty($item->category)) {
$category_name = ($item->category) ? $item->category->name : trans('general.na');
} else {
if ($type == trans_choice('general.incomes', 1)) {
$category_name = ($item->invoice->category) ? $item->invoice->category->name : trans('general.na');
} else {
$category_name = ($item->bill->category) ? $item->bill->category->name : trans('general.na');
}
}
$this->transactions[] = (object) [
'paid_at' => $item->paid_at,
'account_name' => $item->account->name,
'type' => $type,
'description' => $item->description,
'amount' => $item->amount,
'currency_code' => $item->currency_code,
'category_name' => $category_name,
];
if (!is_null($category)) {
$data['category_name'] = $category;
} else {
$data['category_name'] = $item->category->name;
}
$this->transactions[] = (object) $data;
}
}

View File

@ -264,6 +264,11 @@ class Companies extends Controller
event(new CompanySwitched($company));
}
// Check wizard
if (!setting('general.wizard', false)) {
return redirect('wizard');
}
return redirect('/');
}

View File

@ -126,6 +126,7 @@ class Dashboard extends Controller
$start = Date::parse(request('start', $this->today->startOfYear()->format('Y-m-d')));
$end = Date::parse(request('end', $this->today->endOfYear()->format('Y-m-d')));
$period = request('period', 'month');
$range = request('range', 'custom');
$start_month = $start->month;
$end_month = $end->month;
@ -135,6 +136,14 @@ class Dashboard extends Controller
$s = clone $start;
if ($range == 'last_12_months') {
$end_month = 12;
$start_month = 0;
} elseif ($range == 'custom') {
$end_month = $end->diffInMonths($start);
$start_month = 0;
}
for ($j = $end_month; $j >= $start_month; $j--) {
$labels[$end_month - $j] = $s->format('M Y');

View File

@ -26,8 +26,7 @@ class Items extends Controller
{
$items = Item::with('category')->collect();
$categories = Category::enabled()->orderBy('name')->type('item')->pluck('name', 'id')
->prepend(trans('general.all_type', ['type' => trans_choice('general.categories', 2)]), '');
$categories = Category::enabled()->orderBy('name')->type('item')->pluck('name', 'id');
return view('common.items.index', compact('items', 'categories'));
}
@ -51,7 +50,7 @@ class Items extends Controller
{
$categories = Category::enabled()->orderBy('name')->type('item')->pluck('name', 'id');
$taxes = Tax::enabled()->orderBy('rate')->get()->pluck('title', 'id');
$taxes = Tax::enabled()->orderBy('name')->get()->pluck('title', 'id');
$currency = Currency::where('code', '=', setting('general.default_currency', 'USD'))->first();
@ -132,7 +131,7 @@ class Items extends Controller
{
$categories = Category::enabled()->orderBy('name')->type('item')->pluck('name', 'id');
$taxes = Tax::enabled()->orderBy('rate')->get()->pluck('title', 'id');
$taxes = Tax::enabled()->orderBy('name')->get()->pluck('title', 'id');
$currency = Currency::where('code', '=', setting('general.default_currency', 'USD'))->first();
@ -323,22 +322,83 @@ class Items extends Controller
$price = (double) $item['price'];
$quantity = (double) $item['quantity'];
$item_tax_total= 0;
$item_tax_total = 0;
$item_tax_amount = 0;
$item_sub_total = ($price * $quantity);
$item_discount_total = $item_sub_total;
// Apply discount to item
if ($discount) {
$item_discount_total = $item_sub_total - ($item_sub_total * ($discount / 100));
}
if (!empty($item['tax_id'])) {
$tax = Tax::find($item['tax_id']);
$inclusives = $compounds = $taxes = [];
$item_tax_total = (($price * $quantity) / 100) * $tax->rate;
foreach ($item['tax_id'] as $tax_id) {
$tax = Tax::find($tax_id);
switch ($tax->type) {
case 'inclusive':
$inclusives[] = $tax;
break;
case 'compound':
$compounds[] = $tax;
break;
case 'normal':
default:
$taxes[] = $tax;
$item_tax_amount = ($item_discount_total / 100) * $tax->rate;
$item_tax_total += $item_tax_amount;
break;
}
}
if ($inclusives) {
if ($discount) {
$item_tax_total = 0;
if ($taxes) {
foreach ($taxes as $tax) {
$item_tax_amount = ($item_sub_total / 100) * $tax->rate;
$item_tax_total += $item_tax_amount;
}
}
foreach ($inclusives as $inclusive) {
$item_sub_and_tax_total = $item_sub_total + $item_tax_total;
$item_tax_total = $item_sub_and_tax_total - (($item_sub_and_tax_total * (100 - $inclusive->rate)) / 100);
$item_sub_total = $item_sub_and_tax_total - $item_tax_total;
$item_discount_total = $item_sub_total - ($item_sub_total * ($discount / 100));
}
} else {
foreach ($inclusives as $inclusive) {
$item_sub_and_tax_total = $item_discount_total + $item_tax_total;
$item_tax_total = $item_sub_and_tax_total - (($item_sub_and_tax_total * (100 - $inclusive->rate)) / 100);
$item_sub_total = $item_sub_and_tax_total - $item_tax_total;
$item_discount_total = $item_sub_total - ($item_sub_total * ($discount / 100));
}
}
}
if ($compounds) {
foreach ($compounds as $compound) {
$item_tax_total += (($item_discount_total + $item_tax_total) / 100) * $compound->rate;
}
}
}
$sub_total += $item_sub_total;
// Apply discount to tax
if ($discount) {
$item_tax_total = $item_tax_total - ($item_tax_total * ($discount / 100));
}
$tax_total += $item_tax_total;
$items[$key] = money($item_sub_total, $currency_code, true)->format();

View File

@ -0,0 +1,70 @@
<?php
namespace App\Http\Controllers\Common;
use Date;
use App\Http\Controllers\Controller;
use App\Traits\Modules as RemoteModules;
use App\Http\Requests\Common\Notification as Request;
class Notifications extends Controller
{
use RemoteModules;
/**
* Display a listing of the resource.
*
* @return Response
*/
public function index()
{
$notifications = setting('notifications');
return view('common.notifications.index', compact('notifications'));
}
/**
* Show the form for viewing the specified resource.
*
* @return Response
*/
public function show($path, $id)
{
$notification = setting('notifications.' . $path . '.' . $id);
return view('common.notifications.show', compact('notification'));
}
/**
* Disable the specified resource.
*
* @param Company $company
*
* @return Response
*/
public function disable(Request $request)
{
$id = $request['id'];
$path = str_replace('#', '/', $request['path']);
$notifications = $this->getNotifications($path);
foreach ($notifications as $notification) {
if ($notification->id == $id) {
setting()->set('notifications.'. $path . '.' . $id . '.name', $notification->name);
setting()->set('notifications.'. $path . '.' . $id . '.message', $notification->message);
setting()->set('notifications.'. $path . '.' . $id . '.date', Date::now());
setting()->set('notifications.'. $path . '.' . $id . '.status', '0');
setting()->save();
break;
}
}
return response()->json([
'success' => true,
'error' => false,
'data' => null,
]);
}
}

View File

@ -91,6 +91,10 @@ class Uploads extends Controller
*/
protected function getPath($media)
{
if (!is_object($media)) {
return false;
}
$path = $media->basename;
if (!empty($media->directory)) {

View File

@ -6,6 +6,7 @@ use App\Http\Controllers\Controller;
use App\Events\InvoicePrinting;
use App\Models\Banking\Account;
use App\Models\Income\Customer;
use App\Models\Income\Revenue;
use App\Models\Income\Invoice;
use App\Models\Income\InvoiceStatus;
use App\Models\Setting\Category;
@ -16,8 +17,10 @@ use App\Traits\DateTime;
use App\Traits\Uploads;
use App\Utilities\Modules;
use File;
use Illuminate\Http\Request;
use Image;
use Storage;
use SignedUrl;
class Invoices extends Controller
{
@ -178,4 +181,52 @@ class Invoices extends Controller
return $logo;
}
public function link(Invoice $invoice, Request $request)
{
session(['company_id' => $invoice->company_id]);
$paid = 0;
foreach ($invoice->payments as $item) {
$amount = $item->amount;
if ($invoice->currency_code != $item->currency_code) {
$item->default_currency_code = $invoice->currency_code;
$amount = $item->getDynamicConvertedAmount();
}
$paid += $amount;
}
$invoice->paid = $paid;
$accounts = Account::enabled()->pluck('name', 'id');
$currencies = Currency::enabled()->pluck('name', 'code')->toArray();
$account_currency_code = Account::where('id', setting('general.default_account'))->pluck('currency_code')->first();
$customers = Customer::enabled()->pluck('name', 'id');
$categories = Category::enabled()->type('income')->pluck('name', 'id');
$payment_methods = Modules::getPaymentMethods();
$payment_actions = [];
foreach ($payment_methods as $payment_method_key => $payment_method_value) {
$codes = explode('.', $payment_method_key);
if (!isset($payment_actions[$codes[0]])) {
$payment_actions[$codes[0]] = SignedUrl::sign(url('links/invoices/' . $invoice->id . '/' . $codes[0]), 1);
}
}
$print_action = SignedUrl::sign(url('links/invoices/' . $invoice->id . '/print'), 1);
$pdf_action = SignedUrl::sign(url('links/invoices/' . $invoice->id . '/pdf'), 1);
return view('customers.invoices.link', compact('invoice', 'accounts', 'currencies', 'account_currency_code', 'customers', 'categories', 'payment_methods', 'payment_actions', 'print_action', 'pdf_action'));
}
}

View File

@ -8,12 +8,16 @@ use App\Events\BillUpdated;
use App\Http\Controllers\Controller;
use App\Http\Requests\Expense\Bill as Request;
use App\Http\Requests\Expense\BillPayment as PaymentRequest;
use App\Jobs\Expense\CreateBill;
use App\Jobs\Expense\UpdateBill;
use App\Jobs\Expense\CreateBillPayment;
use App\Models\Banking\Account;
use App\Models\Common\Media;
use App\Models\Expense\BillStatus;
use App\Models\Expense\Vendor;
use App\Models\Expense\Bill;
use App\Models\Expense\BillItem;
use App\Models\Expense\BillItemTax;
use App\Models\Expense\BillTotal;
use App\Models\Expense\BillHistory;
use App\Models\Expense\BillPayment;
@ -46,13 +50,13 @@ class Bills extends Controller
{
$bills = Bill::with(['vendor', 'status', 'items', 'payments', 'histories'])->collect(['billed_at'=> 'desc']);
$vendors = collect(Vendor::enabled()->orderBy('name')->pluck('name', 'id'))
->prepend(trans('general.all_type', ['type' => trans_choice('general.vendors', 2)]), '');
$vendors = collect(Vendor::enabled()->orderBy('name')->pluck('name', 'id'));
$statuses = collect(BillStatus::all()->pluck('name', 'code'))
->prepend(trans('general.all_type', ['type' => trans_choice('general.statuses', 2)]), '');
$categories = collect(Category::enabled()->type('expense')->orderBy('name')->pluck('name', 'id'));
return view('expenses.bills.index', compact('bills', 'vendors', 'statuses'));
$statuses = collect(BillStatus::all()->pluck('name', 'code'));
return view('expenses.bills.index', compact('bills', 'vendors', 'categories', 'statuses'));
}
/**
@ -64,43 +68,6 @@ class Bills extends Controller
*/
public function show(Bill $bill)
{
$paid = 0;
// Get Bill Payments
if ($bill->payments->count()) {
$_currencies = Currency::enabled()->pluck('rate', 'code')->toArray();
foreach ($bill->payments as $item) {
$default_amount = (double) $item->amount;
if ($bill->currency_code == $item->currency_code) {
$amount = $default_amount;
} else {
$default_amount_model = new BillPayment();
$default_amount_model->default_currency_code = $bill->currency_code;
$default_amount_model->amount = $default_amount;
$default_amount_model->currency_code = $item->currency_code;
$default_amount_model->currency_rate = $_currencies[$item->currency_code];
$default_amount = (double) $default_amount_model->getDivideConvertedAmount();
$convert_amount = new BillPayment();
$convert_amount->default_currency_code = $item->currency_code;
$convert_amount->amount = $default_amount;
$convert_amount->currency_code = $bill->currency_code;
$convert_amount->currency_rate = $_currencies[$bill->currency_code];
$amount = (double) $convert_amount->getDynamicConvertedAmount();
}
$paid += $amount;
}
}
$bill->paid = $paid;
$accounts = Account::enabled()->orderBy('name')->pluck('name', 'id');
$currencies = Currency::enabled()->orderBy('name')->pluck('name', 'code')->toArray();
@ -131,7 +98,7 @@ class Bills extends Controller
$items = Item::enabled()->orderBy('name')->pluck('name', 'id');
$taxes = Tax::enabled()->orderBy('rate')->get()->pluck('title', 'id');
$taxes = Tax::enabled()->orderBy('name')->get()->pluck('title', 'id');
$categories = Category::enabled()->type('expense')->orderBy('name')->pluck('name', 'id');
@ -147,120 +114,7 @@ class Bills extends Controller
*/
public function store(Request $request)
{
$bill = Bill::create($request->input());
// Upload attachment
if ($request->file('attachment')) {
$media = $this->getMedia($request->file('attachment'), 'bills');
$bill->attachMedia($media, 'attachment');
}
$taxes = [];
$tax_total = 0;
$sub_total = 0;
$discount_total = 0;
$discount = $request['discount'];
$bill_item = [];
$bill_item['company_id'] = $request['company_id'];
$bill_item['bill_id'] = $bill->id;
if ($request['item']) {
foreach ($request['item'] as $item) {
unset($tax_object);
$item_sku = '';
if (!empty($item['item_id'])) {
$item_object = Item::find($item['item_id']);
$item['name'] = $item_object->name;
$item_sku = $item_object->sku;
// Increase stock (item bought)
$item_object->quantity += $item['quantity'];
$item_object->save();
}
$tax = $tax_id = 0;
if (!empty($item['tax_id'])) {
$tax_object = Tax::find($item['tax_id']);
$tax_id = $item['tax_id'];
$tax = (((double) $item['price'] * (double) $item['quantity']) / 100) * $tax_object->rate;
// Apply discount to tax
if ($discount) {
$tax = $tax - ($tax * ($discount / 100));
}
}
$bill_item['item_id'] = $item['item_id'];
$bill_item['name'] = str_limit($item['name'], 180, '');
$bill_item['sku'] = $item_sku;
$bill_item['quantity'] = (double) $item['quantity'];
$bill_item['price'] = (double) $item['price'];
$bill_item['tax'] = $tax;
$bill_item['tax_id'] = $tax_id;
$bill_item['total'] = (double) $item['price'] * (double) $item['quantity'];
BillItem::create($bill_item);
// Set taxes
if (isset($tax_object)) {
if (array_key_exists($tax_object->id, $taxes)) {
$taxes[$tax_object->id]['amount'] += $tax;
} else {
$taxes[$tax_object->id] = [
'name' => $tax_object->name,
'amount' => $tax
];
}
}
// Calculate totals
$tax_total += $tax;
$sub_total += $bill_item['total'];
unset($tax_object);
}
}
$s_total = $sub_total;
// Apply discount to total
if ($discount) {
$s_discount = $s_total * ($discount / 100);
$discount_total += $s_discount;
$s_total = $s_total - $s_discount;
}
$amount = $s_total + $tax_total;
$request['amount'] = money($amount, $request['currency_code'])->getAmount();
$bill->update($request->input());
// Add bill totals
$this->addTotals($bill, $request, $taxes, $sub_total, $discount_total, $tax_total);
// Add bill history
BillHistory::create([
'company_id' => session('company_id'),
'bill_id' => $bill->id,
'status_code' => 'draft',
'notify' => 0,
'description' => trans('messages.success.added', ['type' => $bill->bill_number]),
]);
// Recurring
$bill->createRecurring();
// Fire the event to make it extendible
event(new BillCreated($bill));
$bill = dispatch(new CreateBill($request));
$message = trans('messages.success.added', ['type' => trans_choice('general.bills', 1)]);
@ -373,105 +227,7 @@ class Bills extends Controller
*/
public function update(Bill $bill, Request $request)
{
$taxes = [];
$tax_total = 0;
$sub_total = 0;
$discount_total = 0;
$discount = $request['discount'];
$bill_item = [];
$bill_item['company_id'] = $request['company_id'];
$bill_item['bill_id'] = $bill->id;
if ($request['item']) {
$this->deleteRelationships($bill, 'items');
foreach ($request['item'] as $item) {
unset($tax_object);
$item_sku = '';
if (!empty($item['item_id'])) {
$item_object = Item::find($item['item_id']);
$item['name'] = $item_object->name;
$item_sku = $item_object->sku;
}
$tax = $tax_id = 0;
if (!empty($item['tax_id'])) {
$tax_object = Tax::find($item['tax_id']);
$tax_id = $item['tax_id'];
$tax = (((double) $item['price'] * (double) $item['quantity']) / 100) * $tax_object->rate;
// Apply discount to tax
if ($discount) {
$tax = $tax - ($tax * ($discount / 100));
}
}
$bill_item['item_id'] = $item['item_id'];
$bill_item['name'] = str_limit($item['name'], 180, '');
$bill_item['sku'] = $item_sku;
$bill_item['quantity'] = (double) $item['quantity'];
$bill_item['price'] = (double) $item['price'];
$bill_item['tax'] = $tax;
$bill_item['tax_id'] = $tax_id;
$bill_item['total'] = (double) $item['price'] * (double) $item['quantity'];
if (isset($tax_object)) {
if (array_key_exists($tax_object->id, $taxes)) {
$taxes[$tax_object->id]['amount'] += $tax;
} else {
$taxes[$tax_object->id] = [
'name' => $tax_object->name,
'amount' => $tax
];
}
}
$tax_total += $tax;
$sub_total += $bill_item['total'];
BillItem::create($bill_item);
}
}
$s_total = $sub_total;
// Apply discount to total
if ($discount) {
$s_discount = $s_total * ($discount / 100);
$discount_total += $s_discount;
$s_total = $s_total - $s_discount;
}
$amount = $s_total + $tax_total;
$request['amount'] = money($amount, $request['currency_code'])->getAmount();
$bill->update($request->input());
// Upload attachment
if ($request->file('attachment')) {
$media = $this->getMedia($request->file('attachment'), 'bills');
$bill->attachMedia($media, 'attachment');
}
// Delete previous bill totals
$this->deleteRelationships($bill, 'totals');
// Add bill totals
$this->addTotals($bill, $request, $taxes, $sub_total, $discount_total, $tax_total);
// Recurring
$bill->updateRecurring();
// Fire the event to make it extendible
event(new BillUpdated($bill));
$bill = dispatch(new UpdateBill($bill, $request));
$message = trans('messages.success.updated', ['type' => trans_choice('general.bills', 1)]);
@ -509,9 +265,9 @@ class Bills extends Controller
\Excel::create('bills', function ($excel) {
$bills = Bill::with(['items', 'histories', 'payments', 'totals'])->filter(request()->input())->get();
$excel->sheet('invoices', function ($sheet) use ($bills) {
$excel->sheet('bills', function ($sheet) use ($bills) {
$sheet->fromModel($bills->makeHidden([
'company_id', 'parent_id', 'created_at', 'updated_at', 'deleted_at', 'attachment', 'discount', 'items', 'histories', 'payments', 'totals', 'media'
'company_id', 'parent_id', 'created_at', 'updated_at', 'deleted_at', 'attachment', 'discount', 'items', 'histories', 'payments', 'totals', 'media', 'paid'
]));
});
@ -581,7 +337,9 @@ class Bills extends Controller
{
$bill = $this->prepareBill($bill);
$html = view($bill->template_path, compact('bill'))->render();
$currency_style = true;
$html = view($bill->template_path, compact('bill', 'currency_style'))->render();
$pdf = \App::make('dompdf.wrapper');
$pdf->loadHTML($html);
@ -652,20 +410,7 @@ class Bills extends Controller
$bill->save();
$bill_payment_request = [
'company_id' => $request['company_id'],
'bill_id' => $request['bill_id'],
'account_id' => $request['account_id'],
'paid_at' => $request['paid_at'],
'amount' => $request['amount'],
'currency_code' => $request['currency_code'],
'currency_rate' => $request['currency_rate'],
'description' => $request['description'],
'payment_method' => $request['payment_method'],
'reference' => $request['reference']
];
$bill_payment = BillPayment::create($bill_payment_request);
$bill_payment = dispatch(new CreateBillPayment($request, $bill));
// Upload attachment
if ($request->file('attachment')) {
@ -674,15 +419,6 @@ class Bills extends Controller
$bill_payment->attachMedia($media, 'attachment');
}
$request['status_code'] = $bill->bill_status_code;
$request['notify'] = 0;
$desc_amount = money((float) $request['amount'], (string) $request['currency_code'], true)->format();
$request['description'] = $desc_amount . ' ' . trans_choice('general.payments', 1);
BillHistory::create($request->input());
$message = trans('messages.success.added', ['type' => trans_choice('general.payments', 1)]);
return response()->json([
@ -791,64 +527,4 @@ class Bills extends Controller
return $bill;
}
protected function addTotals($bill, $request, $taxes, $sub_total, $discount_total, $tax_total)
{
$sort_order = 1;
// Added bill sub total
BillTotal::create([
'company_id' => $request['company_id'],
'bill_id' => $bill->id,
'code' => 'sub_total',
'name' => 'bills.sub_total',
'amount' => $sub_total,
'sort_order' => $sort_order,
]);
$sort_order++;
// Added bill discount
if ($discount_total) {
BillTotal::create([
'company_id' => $request['company_id'],
'bill_id' => $bill->id,
'code' => 'discount',
'name' => 'bills.discount',
'amount' => $discount_total,
'sort_order' => $sort_order,
]);
// This is for total
$sub_total = $sub_total - $discount_total;
}
$sort_order++;
// Added bill taxes
if ($taxes) {
foreach ($taxes as $tax) {
BillTotal::create([
'company_id' => $request['company_id'],
'bill_id' => $bill->id,
'code' => 'tax',
'name' => $tax['name'],
'amount' => $tax['amount'],
'sort_order' => $sort_order,
]);
$sort_order++;
}
}
// Added bill total
BillTotal::create([
'company_id' => $request['company_id'],
'bill_id' => $bill->id,
'code' => 'total',
'name' => 'bills.total',
'amount' => $sub_total + $tax_total,
'sort_order' => $sort_order,
]);
}
}

View File

@ -27,14 +27,11 @@ class Payments extends Controller
{
$payments = Payment::with(['vendor', 'account', 'category'])->isNotTransfer()->collect(['paid_at'=> 'desc']);
$vendors = collect(Vendor::enabled()->orderBy('name')->pluck('name', 'id'))
->prepend(trans('general.all_type', ['type' => trans_choice('general.vendors', 2)]), '');
$vendors = collect(Vendor::enabled()->orderBy('name')->pluck('name', 'id'));
$categories = collect(Category::enabled()->type('expense')->orderBy('name')->pluck('name', 'id'))
->prepend(trans('general.all_type', ['type' => trans_choice('general.categories', 2)]), '');
$categories = collect(Category::enabled()->type('expense')->orderBy('name')->pluck('name', 'id'));
$accounts = collect(Account::enabled()->orderBy('name')->pluck('name', 'id'))
->prepend(trans('general.all_type', ['type' => trans_choice('general.accounts', 2)]), '');
$accounts = collect(Account::enabled()->orderBy('name')->pluck('name', 'id'));
$transfer_cat_id = Category::transfer();
@ -64,7 +61,7 @@ class Payments extends Controller
$account_currency_code = Account::where('id', setting('general.default_account'))->pluck('currency_code')->first();
$currency = Currency::where('code', '=', $account_currency_code)->first();
$currency = Currency::where('code', $account_currency_code)->first();
$vendors = Vendor::enabled()->orderBy('name')->pluck('name', 'id');
@ -154,9 +151,7 @@ class Payments extends Controller
$currencies = Currency::enabled()->orderBy('name')->pluck('name', 'code')->toArray();
$account_currency_code = Account::where('id', $payment->account_id)->pluck('currency_code')->first();
$currency = Currency::where('code', '=', $account_currency_code)->first();
$currency = Currency::where('code', $payment->currency_code)->first();
$vendors = Vendor::enabled()->orderBy('name')->pluck('name', 'id');
@ -164,7 +159,7 @@ class Payments extends Controller
$payment_methods = Modules::getPaymentMethods();
return view('expenses.payments.edit', compact('payment', 'accounts', 'currencies', 'account_currency_code', 'currency', 'vendors', 'categories', 'payment_methods'));
return view('expenses.payments.edit', compact('payment', 'accounts', 'currencies', 'currency', 'vendors', 'categories', 'payment_methods'));
}
/**

View File

@ -100,8 +100,10 @@ class Vendors extends Controller
$limit = request('limit', setting('general.list_limit', '25'));
$transactions = $this->paginate($items->merge($bill_payments)->sortByDesc('paid_at'), $limit);
$bills = $this->paginate($bills->sortByDesc('paid_at'), $limit);
$payments = $this->paginate($payments->sortByDesc('paid_at'), $limit);
return view('expenses.vendors.show', compact('vendor', 'counts', 'amounts', 'transactions'));
return view('expenses.vendors.show', compact('vendor', 'counts', 'amounts', 'transactions', 'bills', 'payments'));
}
/**
@ -309,7 +311,7 @@ class Vendors extends Controller
if (empty($vendor_id)) {
return response()->json([]);
}
$vendor = Vendor::find($vendor_id);
if (empty($vendor)) {
@ -332,6 +334,12 @@ class Vendors extends Controller
$vendor->currency_code = $currency_code;
$vendor->currency_rate = $currency->rate;
$vendor->thousands_separator = $currency->thousands_separator;
$vendor->decimal_mark = $currency->decimal_mark;
$vendor->precision = (int) $currency->precision;
$vendor->symbol_first = $currency->symbol_first;
$vendor->symbol = $currency->symbol;
return response()->json($vendor);
}

View File

@ -100,8 +100,10 @@ class Customers extends Controller
$limit = request('limit', setting('general.list_limit', '25'));
$transactions = $this->paginate($items->merge($invoice_payments)->sortByDesc('paid_at'), $limit);
$invoices = $this->paginate($invoices->sortByDesc('paid_at'), $limit);
$revenues = $this->paginate($revenues->sortByDesc('paid_at'), $limit);
return view('incomes.customers.show', compact('customer', 'counts', 'amounts', 'transactions'));
return view('incomes.customers.show', compact('customer', 'counts', 'amounts', 'transactions', 'invoices', 'revenues'));
}
/**

View File

@ -8,21 +8,26 @@ use App\Events\InvoiceUpdated;
use App\Http\Controllers\Controller;
use App\Http\Requests\Income\Invoice as Request;
use App\Http\Requests\Income\InvoicePayment as PaymentRequest;
use App\Jobs\Income\CreateInvoice;
use App\Jobs\Income\UpdateInvoice;
use App\Jobs\Income\CreateInvoicePayment;
use App\Models\Banking\Account;
use App\Models\Common\Item;
use App\Models\Common\Media;
use App\Models\Income\Customer;
use App\Models\Income\Invoice;
use App\Models\Income\InvoiceHistory;
use App\Models\Income\InvoiceItem;
use App\Models\Income\InvoiceItemTax;
use App\Models\Income\InvoiceTotal;
use App\Models\Income\InvoicePayment;
use App\Models\Income\InvoiceStatus;
use App\Models\Common\Item;
use App\Models\Setting\Category;
use App\Models\Setting\Currency;
use App\Models\Setting\Tax;
use App\Notifications\Income\Invoice as Notification;
use App\Notifications\Common\Item as ItemNotification;
use App\Notifications\Common\ItemReminder as ItemReminderNotification;
use App\Traits\Currencies;
use App\Traits\DateTime;
use App\Traits\Incomes;
@ -35,6 +40,7 @@ use File;
use Illuminate\Http\Request as ItemRequest;
use Image;
use Storage;
use SignedUrl;
class Invoices extends Controller
{
@ -49,13 +55,13 @@ class Invoices extends Controller
{
$invoices = Invoice::with(['customer', 'status', 'items', 'payments', 'histories'])->collect(['invoice_number'=> 'desc']);
$customers = collect(Customer::enabled()->orderBy('name')->pluck('name', 'id'))
->prepend(trans('general.all_type', ['type' => trans_choice('general.customers', 2)]), '');
$customers = collect(Customer::enabled()->orderBy('name')->pluck('name', 'id'));
$status = collect(InvoiceStatus::all()->pluck('name', 'code'))
->prepend(trans('general.all_type', ['type' => trans_choice('general.statuses', 2)]), '');
$categories = collect(Category::enabled()->type('income')->orderBy('name')->pluck('name', 'id'));
return view('incomes.invoices.index', compact('invoices', 'customers', 'status'));
$statuses = collect(InvoiceStatus::all()->pluck('name', 'code'));
return view('incomes.invoices.index', compact('invoices', 'customers', 'categories', 'statuses'));
}
/**
@ -67,43 +73,6 @@ class Invoices extends Controller
*/
public function show(Invoice $invoice)
{
$paid = 0;
// Get Invoice Payments
if ($invoice->payments->count()) {
$_currencies = Currency::enabled()->pluck('rate', 'code')->toArray();
foreach ($invoice->payments as $item) {
$default_amount = $item->amount;
if ($invoice->currency_code == $item->currency_code) {
$amount = (double)$default_amount;
} else {
$default_amount_model = new InvoicePayment();
$default_amount_model->default_currency_code = $invoice->currency_code;
$default_amount_model->amount = $default_amount;
$default_amount_model->currency_code = $item->currency_code;
$default_amount_model->currency_rate = $_currencies[$item->currency_code];
$default_amount = (double) $default_amount_model->getDivideConvertedAmount();
$convert_amount = new InvoicePayment();
$convert_amount->default_currency_code = $item->currency_code;
$convert_amount->amount = $default_amount;
$convert_amount->currency_code = $invoice->currency_code;
$convert_amount->currency_rate = $_currencies[$invoice->currency_code];
$amount = (double) $convert_amount->getDynamicConvertedAmount();
}
$paid += $amount;
}
}
$invoice->paid = $paid;
$accounts = Account::enabled()->orderBy('name')->pluck('name', 'id');
$currencies = Currency::enabled()->orderBy('name')->pluck('name', 'code')->toArray();
@ -116,7 +85,9 @@ class Invoices extends Controller
$payment_methods = Modules::getPaymentMethods();
return view('incomes.invoices.show', compact('invoice', 'accounts', 'currencies', 'account_currency_code', 'customers', 'categories', 'payment_methods'));
$customer_share = SignedUrl::sign(url('links/invoices/' . $invoice->id));
return view('incomes.invoices.show', compact('invoice', 'accounts', 'currencies', 'account_currency_code', 'customers', 'categories', 'payment_methods', 'customer_share'));
}
/**
@ -134,7 +105,7 @@ class Invoices extends Controller
$items = Item::enabled()->orderBy('name')->pluck('name', 'id');
$taxes = Tax::enabled()->orderBy('rate')->get()->pluck('title', 'id');
$taxes = Tax::enabled()->orderBy('name')->get()->pluck('title', 'id');
$categories = Category::enabled()->type('income')->orderBy('name')->pluck('name', 'id');
@ -152,133 +123,7 @@ class Invoices extends Controller
*/
public function store(Request $request)
{
$invoice = Invoice::create($request->input());
// Upload attachment
if ($request->file('attachment')) {
$media = $this->getMedia($request->file('attachment'), 'invoices');
$invoice->attachMedia($media, 'attachment');
}
$taxes = [];
$tax_total = 0;
$sub_total = 0;
$discount_total = 0;
$discount = $request['discount'];
$invoice_item = [];
$invoice_item['company_id'] = $request['company_id'];
$invoice_item['invoice_id'] = $invoice->id;
if ($request['item']) {
foreach ($request['item'] as $item) {
$item_sku = '';
if (!empty($item['item_id'])) {
$item_object = Item::find($item['item_id']);
$item['name'] = $item_object->name;
$item_sku = $item_object->sku;
// Decrease stock (item sold)
$item_object->quantity -= $item['quantity'];
$item_object->save();
// Notify users if out of stock
if ($item_object->quantity == 0) {
foreach ($item_object->company->users as $user) {
if (!$user->can('read-notifications')) {
continue;
}
$user->notify(new ItemNotification($item_object));
}
}
}
$tax = $tax_id = 0;
if (!empty($item['tax_id'])) {
$tax_object = Tax::find($item['tax_id']);
$tax_id = $item['tax_id'];
$tax = (((double) $item['price'] * (double) $item['quantity']) / 100) * $tax_object->rate;
// Apply discount to tax
if ($discount) {
$tax = $tax - ($tax * ($discount / 100));
}
}
$invoice_item['item_id'] = $item['item_id'];
$invoice_item['name'] = str_limit($item['name'], 180, '');
$invoice_item['sku'] = $item_sku;
$invoice_item['quantity'] = (double) $item['quantity'];
$invoice_item['price'] = (double) $item['price'];
$invoice_item['tax'] = $tax;
$invoice_item['tax_id'] = $tax_id;
$invoice_item['total'] = (double) $item['price'] * (double) $item['quantity'];
InvoiceItem::create($invoice_item);
// Set taxes
if (isset($tax_object)) {
if (array_key_exists($tax_object->id, $taxes)) {
$taxes[$tax_object->id]['amount'] += $tax;
} else {
$taxes[$tax_object->id] = [
'name' => $tax_object->name,
'amount' => $tax
];
}
}
// Calculate totals
$tax_total += $tax;
$sub_total += $invoice_item['total'];
unset($tax_object);
}
}
$s_total = $sub_total;
// Apply discount to total
if ($discount) {
$s_discount = $s_total * ($discount / 100);
$discount_total += $s_discount;
$s_total = $s_total - $s_discount;
}
$amount = $s_total + $tax_total;
$request['amount'] = money($amount, $request['currency_code'])->getAmount();
$invoice->update($request->input());
// Add invoice totals
$this->addTotals($invoice, $request, $taxes, $sub_total, $discount_total, $tax_total);
// Add invoice history
InvoiceHistory::create([
'company_id' => session('company_id'),
'invoice_id' => $invoice->id,
'status_code' => 'draft',
'notify' => 0,
'description' => trans('messages.success.added', ['type' => $invoice->invoice_number]),
]);
// Update next invoice number
$this->increaseNextInvoiceNumber();
// Recurring
$invoice->createRecurring();
// Fire the event to make it extendible
event(new InvoiceCreated($invoice));
$invoice = dispatch(new CreateInvoice($request));
$message = trans('messages.success.added', ['type' => trans_choice('general.invoices', 1)]);
@ -377,7 +222,7 @@ class Invoices extends Controller
$items = Item::enabled()->orderBy('name')->pluck('name', 'id');
$taxes = Tax::enabled()->orderBy('rate')->get()->pluck('title', 'id');
$taxes = Tax::enabled()->orderBy('name')->get()->pluck('title', 'id');
$categories = Category::enabled()->type('income')->orderBy('name')->pluck('name', 'id');
@ -394,105 +239,7 @@ class Invoices extends Controller
*/
public function update(Invoice $invoice, Request $request)
{
$taxes = [];
$tax_total = 0;
$sub_total = 0;
$discount_total = 0;
$discount = $request['discount'];
$invoice_item = [];
$invoice_item['company_id'] = $request['company_id'];
$invoice_item['invoice_id'] = $invoice->id;
if ($request['item']) {
$this->deleteRelationships($invoice, 'items');
foreach ($request['item'] as $item) {
unset($tax_object);
$item_sku = '';
if (!empty($item['item_id'])) {
$item_object = Item::find($item['item_id']);
$item['name'] = $item_object->name;
$item_sku = $item_object->sku;
}
$tax = $tax_id = 0;
if (!empty($item['tax_id'])) {
$tax_object = Tax::find($item['tax_id']);
$tax_id = $item['tax_id'];
$tax = (((double) $item['price'] * (double) $item['quantity']) / 100) * $tax_object->rate;
// Apply discount to tax
if ($discount) {
$tax = $tax - ($tax * ($discount / 100));
}
}
$invoice_item['item_id'] = $item['item_id'];
$invoice_item['name'] = str_limit($item['name'], 180, '');
$invoice_item['sku'] = $item_sku;
$invoice_item['quantity'] = (double) $item['quantity'];
$invoice_item['price'] = (double) $item['price'];
$invoice_item['tax'] = $tax;
$invoice_item['tax_id'] = $tax_id;
$invoice_item['total'] = (double) $item['price'] * (double) $item['quantity'];
if (isset($tax_object)) {
if (array_key_exists($tax_object->id, $taxes)) {
$taxes[$tax_object->id]['amount'] += $tax;
} else {
$taxes[$tax_object->id] = [
'name' => $tax_object->name,
'amount' => $tax
];
}
}
$tax_total += $tax;
$sub_total += $invoice_item['total'];
InvoiceItem::create($invoice_item);
}
}
$s_total = $sub_total;
// Apply discount to total
if ($discount) {
$s_discount = $s_total * ($discount / 100);
$discount_total += $s_discount;
$s_total = $s_total - $s_discount;
}
$amount = $s_total + $tax_total;
$request['amount'] = money($amount, $request['currency_code'])->getAmount();
$invoice->update($request->input());
// Upload attachment
if ($request->file('attachment')) {
$media = $this->getMedia($request->file('attachment'), 'invoices');
$invoice->attachMedia($media, 'attachment');
}
// Delete previous invoice totals
$this->deleteRelationships($invoice, 'totals');
// Add invoice totals
$this->addTotals($invoice, $request, $taxes, $sub_total, $discount_total, $tax_total);
// Recurring
$invoice->updateRecurring();
// Fire the event to make it extendible
event(new InvoiceUpdated($invoice));
$invoice = dispatch(new UpdateInvoice($invoice, $request));
$message = trans('messages.success.updated', ['type' => trans_choice('general.invoices', 1)]);
@ -532,7 +279,7 @@ class Invoices extends Controller
$excel->sheet('invoices', function ($sheet) use ($invoices) {
$sheet->fromModel($invoices->makeHidden([
'company_id', 'parent_id', 'created_at', 'updated_at', 'deleted_at', 'attachment', 'discount', 'items', 'histories', 'payments', 'totals', 'media'
'company_id', 'parent_id', 'created_at', 'updated_at', 'deleted_at', 'attachment', 'discount', 'items', 'histories', 'payments', 'totals', 'media', 'paid'
]));
});
@ -623,6 +370,7 @@ class Invoices extends Controller
unset($invoice->paid);
unset($invoice->template_path);
unset($invoice->pdf_path);
unset($invoice->reconciled);
// Mark invoice as sent
if ($invoice->invoice_status_code != 'partial') {
@ -670,7 +418,9 @@ class Invoices extends Controller
{
$invoice = $this->prepareInvoice($invoice);
$html = view($invoice->template_path, compact('invoice'))->render();
$currency_style = true;
$html = view($invoice->template_path, compact('invoice', 'currency_style'))->render();
$pdf = app('dompdf.wrapper');
$pdf->loadHTML($html);
@ -789,20 +539,7 @@ class Invoices extends Controller
$invoice->save();
$invoice_payment_request = [
'company_id' => $request['company_id'],
'invoice_id' => $request['invoice_id'],
'account_id' => $request['account_id'],
'paid_at' => $request['paid_at'],
'amount' => $request['amount'],
'currency_code' => $request['currency_code'],
'currency_rate' => $request['currency_rate'],
'description' => $request['description'],
'payment_method' => $request['payment_method'],
'reference' => $request['reference']
];
$invoice_payment = InvoicePayment::create($invoice_payment_request);
$invoice_payment = dispatch(new CreateInvoicePayment($request, $invoice));
// Upload attachment
if ($request->file('attachment')) {
@ -811,15 +548,6 @@ class Invoices extends Controller
$invoice_payment->attachMedia($media, 'attachment');
}
$request['status_code'] = $invoice->invoice_status_code;
$request['notify'] = 0;
$desc_amount = money((float) $request['amount'], (string) $request['currency_code'], true)->format();
$request['description'] = $desc_amount . ' ' . trans_choice('general.payments', 1);
InvoiceHistory::create($request->input());
$message = trans('messages.success.added', ['type' => trans_choice('general.payments', 1)]);
return response()->json([
@ -928,64 +656,4 @@ class Invoices extends Controller
return $invoice;
}
protected function addTotals($invoice, $request, $taxes, $sub_total, $discount_total, $tax_total)
{
$sort_order = 1;
// Added invoice sub total
InvoiceTotal::create([
'company_id' => $request['company_id'],
'invoice_id' => $invoice->id,
'code' => 'sub_total',
'name' => 'invoices.sub_total',
'amount' => $sub_total,
'sort_order' => $sort_order,
]);
$sort_order++;
// Added invoice discount
if ($discount_total) {
InvoiceTotal::create([
'company_id' => $request['company_id'],
'invoice_id' => $invoice->id,
'code' => 'discount',
'name' => 'invoices.discount',
'amount' => $discount_total,
'sort_order' => $sort_order,
]);
// This is for total
$sub_total = $sub_total - $discount_total;
}
$sort_order++;
// Added invoice taxes
if ($taxes) {
foreach ($taxes as $tax) {
InvoiceTotal::create([
'company_id' => $request['company_id'],
'invoice_id' => $invoice->id,
'code' => 'tax',
'name' => $tax['name'],
'amount' => $tax['amount'],
'sort_order' => $sort_order,
]);
$sort_order++;
}
}
// Added invoice total
InvoiceTotal::create([
'company_id' => $request['company_id'],
'invoice_id' => $invoice->id,
'code' => 'total',
'name' => 'invoices.total',
'amount' => $sub_total + $tax_total,
'sort_order' => $sort_order,
]);
}
}

View File

@ -29,14 +29,11 @@ class Revenues extends Controller
{
$revenues = Revenue::with(['account', 'category', 'customer'])->isNotTransfer()->collect(['paid_at'=> 'desc']);
$customers = collect(Customer::enabled()->orderBy('name')->pluck('name', 'id'))
->prepend(trans('general.all_type', ['type' => trans_choice('general.customers', 2)]), '');
$customers = collect(Customer::enabled()->orderBy('name')->pluck('name', 'id'));
$categories = collect(Category::enabled()->type('income')->orderBy('name')->pluck('name', 'id'))
->prepend(trans('general.all_type', ['type' => trans_choice('general.categories', 2)]), '');
$categories = collect(Category::enabled()->type('income')->orderBy('name')->pluck('name', 'id'));
$accounts = collect(Account::enabled()->orderBy('name')->pluck('name', 'id'))
->prepend(trans('general.all_type', ['type' => trans_choice('general.accounts', 2)]), '');
$accounts = collect(Account::enabled()->orderBy('name')->pluck('name', 'id'));
$transfer_cat_id = Category::transfer();
@ -66,7 +63,7 @@ class Revenues extends Controller
$account_currency_code = Account::where('id', setting('general.default_account'))->pluck('currency_code')->first();
$currency = Currency::where('code', '=', $account_currency_code)->first();
$currency = Currency::where('code', $account_currency_code)->first();
$customers = Customer::enabled()->orderBy('name')->pluck('name', 'id');
@ -156,9 +153,7 @@ class Revenues extends Controller
$currencies = Currency::enabled()->orderBy('name')->pluck('name', 'code')->toArray();
$account_currency_code = Account::where('id', $revenue->account_id)->pluck('currency_code')->first();
$currency = Currency::where('code', '=', $account_currency_code)->first();
$currency = Currency::where('code', $revenue->currency_code)->first();
$customers = Customer::enabled()->orderBy('name')->pluck('name', 'id');
@ -166,7 +161,7 @@ class Revenues extends Controller
$payment_methods = Modules::getPaymentMethods();
return view('incomes.revenues.edit', compact('revenue', 'accounts', 'currencies', 'account_currency_code', 'currency', 'customers', 'categories', 'payment_methods'));
return view('incomes.revenues.edit', compact('revenue', 'accounts', 'currencies', 'currency', 'customers', 'categories', 'payment_methods'));
}
/**

View File

@ -6,8 +6,10 @@ use App\Http\Controllers\Controller;
use App\Events\UpdateFinished;
use App\Utilities\Updater;
use App\Utilities\Versions;
use Illuminate\Http\Request;
use Artisan;
use Module;
use File;
class Updates extends Controller
{
@ -80,15 +82,20 @@ class Updates extends Controller
*/
public function update($alias, $version)
{
set_time_limit(600); // 10 minutes
if ($alias == 'core') {
$name = 'Akaunting v' . $version;
if (Updater::update($alias, $version)) {
return redirect('install/updates/post/' . $alias . '/' . version('short') . '/' . $version);
$installed = version('short');
} else {
// Get module instance
$module = Module::findByAlias($alias);
$name = $module->get('name');
$installed = $module->get('version');
}
flash(trans('updates.error'))->error()->important();
return redirect()->back();
return view('install.updates.edit', compact('alias', 'name', 'installed', 'version'));
}
/**
@ -117,4 +124,229 @@ class Updates extends Controller
return redirect('install/updates');
}
/**
* Show the form for viewing the specified resource.
*
* @param $request
*
* @return Response
*/
public function steps(Request $request)
{
$json = [];
$json['step'] = [];
$name = $request['name'];
$version = $request['version'];
// Download
$json['step'][] = [
'text' => trans('modules.installation.download', ['module' => $name]),
'url' => url('install/updates/download')
];
// Unzip
$json['step'][] = [
'text' => trans('modules.installation.unzip', ['module' => $name]),
'url' => url('install/updates/unzip')
];
// File Copy
$json['step'][] = [
'text' => trans('modules.installation.file_copy', ['module' => $name]),
'url' => url('install/updates/file-copy')
];
// Migrate DB and trigger event UpdateFinish event
$json['step'][] = [
'text' => trans('modules.installation.migrate', ['module' => $name]),
'url' => url('install/updates/migrate')
];
// redirect update page
$json['step'][] = [
'text' => trans('modules.installation.finish'),
'url' => url('install/updates/finish')
];
return response()->json($json);
}
/**
* Show the form for viewing the specified resource.
*
* @param $request
*
* @return Response
*/
public function download(Request $request)
{
set_time_limit(600); // 10 minutes
$status = true;
if ($request['alias'] != 'core') {
$this->checkApiToken();
}
// Download file
if (!$data = Updater::download($request['alias'], $request['version'])) {
$status = false;
$message = trans('modules.errors.download', ['module' => $request['name']]);
}
// Create temp directory
$path = 'temp-' . md5(mt_rand());
$temp_path = storage_path('app/temp') . '/' . $path;
if (!File::isDirectory($temp_path)) {
File::makeDirectory($temp_path);
}
$file = $temp_path . '/upload.zip';
// Add content to the Zip file
$uploaded = is_int(file_put_contents($file, $data)) ? true : false;
if (!$uploaded) {
$status = false;
$message = trans('modules.errors.upload', ['module' => $request['name']]);
}
$json = [
'success' => ($status) ? true : false,
'errors' => (!$status) ? $message : false,
'data' => [
'path' => $path
]
];
return response()->json($json);
}
/**
* Show the form for viewing the specified resource.
*
* @param $request
*
* @return Response
*/
public function unzip(Request $request)
{
set_time_limit(600); // 10 minutes
if ($request['alias'] != 'core') {
$this->checkApiToken();
}
$path = storage_path('app/temp') . '/' . $request['path'];
$file = $path . '/upload.zip';
$result = Updater::unzip($file, $path);
$json = [
'success' => ($result) ? true : false,
'errors' => (!$result) ? trans('modules.errors.unzip', ['module' => $request['name']]) : false,
'data' => [
'path' => $request['path']
]
];
return response()->json($json);
}
/**
* Show the form for viewing the specified resource.
*
* @param $request
*
* @return Response
*/
public function fileCopy(Request $request)
{
set_time_limit(600); // 10 minutes
if ($request['alias'] != 'core') {
$this->checkApiToken();
}
$path = storage_path('app/temp') . '/' . $request['path'];
$result = Updater::fileCopy($request['alias'], $path, $request['version']);
$json = [
'success' => ($result) ? true : false,
'errors' => (!$result) ? trans('modules.errors.file_copy', ['module' => $request['name']]) : false,
'data' => [
'path' => $request['path']
]
];
return response()->json($json);
}
/**
* Show the form for viewing the specified resource.
*
* @param $request
*
* @return Response
*/
public function migrate(Request $request)
{
// Check if the file mirror was successful
if (($request['alias'] == 'core') && (version('short') != $request['version'])) {
$json = [
'success' => false,
'errors' => trans('modules.errors.migrate core', ['module' => $request['name']]),
'data' => []
];
return response()->json($json);
}
// Clear cache after update
Artisan::call('cache:clear');
try {
event(new UpdateFinished($request['alias'], $request['installed'], $request['version']));
$json = [
'success' => true,
'errors' => false,
'data' => []
];
} catch (\Exception $e) {
$json = [
'success' => false,
'errors' => trans('modules.errors.migrate', ['module' => $request['name']]),
'data' => []
];
}
return response()->json($json);
}
/**
* Show the form for viewing the specified resource.
*
* @param $request
*
* @return Response
*/
public function finish(Request $request)
{
$json = [
'success' => true,
'errors' => false,
'redirect' => url("install/updates"),
'data' => [],
];
return response()->json($json);
}
}

View File

@ -39,10 +39,10 @@ class BillPayments extends Controller
$currencies = Currency::enabled()->orderBy('name')->pluck('name', 'code')->toArray();
$currency = Currency::where('code', setting('general.default_currency'))->first();
$account_currency_code = Account::where('id', setting('general.default_account'))->pluck('currency_code')->first();
$currency = Currency::where('code', $account_currency_code)->first();
$payment_methods = Modules::getPaymentMethods();
$bill->paid = $this->getPaid($bill);
@ -52,7 +52,9 @@ class BillPayments extends Controller
$bill->{$bill_total->code} = $bill_total->amount;
}
$bill->grand_total = $bill->total;
$total = money($bill->total, $currency->code, true)->format();
$bill->grand_total = money($total, $currency->code)->getAmount();
if (!empty($paid)) {
$bill->grand_total = $bill->total - $paid;

View File

@ -52,7 +52,9 @@ class InvoicePayments extends Controller
$invoice->{$invoice_total->code} = $invoice_total->amount;
}
$invoice->grand_total = $invoice->total;
$total = money($invoice->total, $currency->code, true)->format();
$invoice->grand_total = money($total, $currency->code)->getAmount();
if (!empty($paid)) {
$invoice->grand_total = $invoice->total - $paid;
@ -147,7 +149,7 @@ class InvoicePayments extends Controller
$error_amount = (double) $convert_amount->getDynamicConvertedAmount();
}
$message = trans('messages.error.over_payment', ['amount' => money($error_amount, $request['currency_code'],true)]);
$message = trans('messages.error.over_payment', ['amount' => money($error_amount, $request['currency_code'], true)]);
return response()->json([
'success' => false,

View File

@ -0,0 +1,69 @@
<?php
namespace App\Http\Controllers\Modals;
use App\Http\Controllers\Controller;
use App\Http\Requests\Setting\Tax as Request;
use App\Models\Setting\Tax;
class Taxes extends Controller
{
/**
* Instantiate a new controller instance.
*/
public function __construct()
{
// Add CRUD permission check
$this->middleware('permission:create-settings-taxes')->only(['create', 'store']);
$this->middleware('permission:read-settings-taxes')->only(['index', 'edit']);
$this->middleware('permission:update-settings-taxes')->only(['update', 'enable', 'disable']);
$this->middleware('permission:delete-settings-taxes')->only('destroy');
}
/**
* Show the form for creating a new resource.
*
* @return Response
*/
public function create()
{
$types = [
'normal' => trans('taxes.normal'),
'inclusive' => trans('taxes.inclusive'),
'compound' => trans('taxes.compound'),
];
$html = view('modals.taxes.create', compact('types'))->render();
return response()->json([
'success' => true,
'error' => false,
'message' => 'null',
'html' => $html,
]);
}
/**
* Store a newly created resource in storage.
*
* @param Request $request
*
* @return Response
*/
public function store(Request $request)
{
$request['enabled'] = 1;
$tax = Tax::create($request->all());
$message = trans('messages.success.added', ['type' => trans_choice('general.taxes', 1)]);
return response()->json([
'success' => true,
'error' => false,
'data' => $tax,
'message' => $message,
'html' => 'null',
]);
}
}

View File

@ -67,7 +67,7 @@ class Item extends Controller
$module->action_url .= $character . http_build_query($parameters);
}
return view('modules.item.show', compact('module', 'about', 'installed', 'enable'));
return view('modules.item.show', compact('module', 'installed', 'enable'));
}
/**
@ -302,4 +302,42 @@ class Item extends Controller
return redirect('apps/' . $alias);
}
public function reviews($alias, Request $request)
{
$page = $request['page'];
$data = [
'query' => [
'page' => ($page) ? $page : 1,
]
];
$reviews = $this->getModuleReviews($alias, $data);
$html = view('partials.modules.reviews', compact('reviews'))->render();
return response()->json([
'success' => true,
'error' => false,
'data' => null,
'message' => null,
'html' => $html,
]);
}
public function documentation($alias)
{
$this->checkApiToken();
$documentation = $this->getDocumentation($alias);
if (empty($documentation)) {
return redirect('apps/' . $alias)->send();
}
$back = 'apps/' . $alias;
return view('modules.item.documentation', compact('documentation', 'back'));
}
}

View File

@ -23,7 +23,15 @@ class Tiles extends Controller
{
$this->checkApiToken();
$data = $this->getModulesByCategory($alias);
$page = request('page', 1);
$request = [
'query' => [
'page' => $page,
]
];
$data = $this->getModulesByCategory($alias, $request);
$title = $data->category->name;
$modules = $data->modules;
@ -32,6 +40,34 @@ class Tiles extends Controller
return view('modules.tiles.index', compact('title', 'modules', 'installed'));
}
/**
* Show the form for viewing the specified resource.
*
* @param $alias
*
* @return Response
*/
public function vendorModules($alias)
{
$this->checkApiToken();
$page = request('page', 1);
$request = [
'query' => [
'page' => $page,
]
];
$data = $this->getModulesByVendor($alias, $request);
$title = $data->vendor->name;
$modules = $data->modules;
$installed = Module::all()->pluck('status', 'alias')->toArray();
return view('modules.tiles.index', compact('title', 'modules', 'installed'));
}
/**
* Show the form for viewing the specified resource.
*
@ -41,8 +77,16 @@ class Tiles extends Controller
{
$this->checkApiToken();
$page = request('page', 1);
$data = [
'query' => [
'page' => $page,
]
];
$title = trans('modules.top_paid');
$modules = $this->getPaidModules();
$modules = $this->getPaidModules($data);
$installed = Module::all()->pluck('status', 'alias')->toArray();
return view('modules.tiles.index', compact('title', 'modules', 'installed'));
@ -57,8 +101,16 @@ class Tiles extends Controller
{
$this->checkApiToken();
$page = request('page', 1);
$data = [
'query' => [
'page' => $page,
]
];
$title = trans('modules.new');
$modules = $this->getNewModules();
$modules = $this->getNewModules($data);
$installed = Module::all()->pluck('status', 'alias')->toArray();
return view('modules.tiles.index', compact('title', 'modules', 'installed'));
@ -73,8 +125,16 @@ class Tiles extends Controller
{
$this->checkApiToken();
$page = request('page', 1);
$data = [
'query' => [
'page' => $page,
]
];
$title = trans('modules.top_free');
$modules = $this->getFreeModules();
$modules = $this->getFreeModules($data);
$installed = Module::all()->pluck('status', 'alias')->toArray();
return view('modules.tiles.index', compact('title', 'modules', 'installed'));
@ -90,10 +150,12 @@ class Tiles extends Controller
$this->checkApiToken();
$keyword = $request['keyword'];
$page = request('page', 1);
$data = [
'query' => [
'keyword' => $keyword,
'page' => $page,
]
];

View File

@ -3,10 +3,13 @@
namespace App\Http\Controllers\Reports;
use App\Http\Controllers\Controller;
use App\Models\Banking\Account;
use App\Models\Expense\Bill;
use App\Models\Expense\BillPayment;
use App\Models\Expense\Payment;
use App\Models\Expense\Vendor;
use App\Models\Setting\Category;
use App\Utilities\Recurring;
use Charts;
use Date;
@ -22,13 +25,16 @@ class ExpenseSummary extends Controller
$dates = $totals = $expenses = $expenses_graph = $categories = [];
$status = request('status');
$year = request('year', Date::now()->year);
$categories = Category::enabled()->type('expense')->pluck('name', 'id')->toArray();
$categories = Category::enabled()->type('expense')->orderBy('name')->pluck('name', 'id')->toArray();
// Get year
$year = request('year');
if (empty($year)) {
$year = Date::now()->year;
if ($categories_filter = request('categories')) {
$cats = collect($categories)->filter(function ($value, $key) use ($categories_filter) {
return in_array($key, $categories_filter);
});
} else {
$cats = $categories;
}
// Dates
@ -44,7 +50,7 @@ class ExpenseSummary extends Controller
'currency_rate' => 1
);
foreach ($categories as $category_id => $category_name) {
foreach ($cats as $category_id => $category_name) {
$expenses[$category_id][$dates[$j]] = array(
'category_id' => $category_id,
'name' => $category_name,
@ -55,27 +61,47 @@ class ExpenseSummary extends Controller
}
}
// Bills
$payments = Payment::monthsOfYear('paid_at')->account(request('accounts'))->vendor(request('vendors'))->isNotTransfer()->get();
switch ($status) {
case 'paid':
$bills = BillPayment::monthsOfYear('paid_at')->get();
// Bills
$bills = BillPayment::monthsOfYear('paid_at')->account(request('accounts'))->get();
$this->setAmount($expenses_graph, $totals, $expenses, $bills, 'bill', 'paid_at');
// Payments
$this->setAmount($expenses_graph, $totals, $expenses, $payments, 'payment', 'paid_at');
break;
case 'upcoming':
$bills = Bill::accrued()->monthsOfYear('due_at')->get();
// Bills
$bills = Bill::accrued()->monthsOfYear('due_at')->vendor(request('vendors'))->get();
Recurring::reflect($bills, 'bill', 'billed_at', $status);
$this->setAmount($expenses_graph, $totals, $expenses, $bills, 'bill', 'due_at');
// Payments
Recurring::reflect($payments, 'payment', 'paid_at', $status);
$this->setAmount($expenses_graph, $totals, $expenses, $payments, 'payment', 'paid_at');
break;
default:
$bills = Bill::accrued()->monthsOfYear('billed_at')->get();
// Bills
$bills = Bill::accrued()->monthsOfYear('billed_at')->vendor(request('vendors'))->get();
Recurring::reflect($bills, 'bill', 'billed_at', $status);
$this->setAmount($expenses_graph, $totals, $expenses, $bills, 'bill', 'billed_at');
// Payments
Recurring::reflect($payments, 'payment', 'paid_at', $status);
$this->setAmount($expenses_graph, $totals, $expenses, $payments, 'payment', 'paid_at');
break;
}
// Payments
if ($status != 'upcoming') {
$payments = Payment::monthsOfYear('paid_at')->isNotTransfer()->get();
$this->setAmount($expenses_graph, $totals, $expenses, $payments, 'payment', 'paid_at');
}
$statuses = collect([
'all' => trans('general.all'),
'paid' => trans('invoices.paid'),
'upcoming' => trans('dashboard.payables'),
]);
$accounts = Account::enabled()->pluck('name', 'id')->toArray();
$vendors = Vendor::enabled()->pluck('name', 'id')->toArray();
// Check if it's a print or normal request
if (request('print')) {
@ -86,6 +112,8 @@ class ExpenseSummary extends Controller
$view_template = 'reports.expense_summary.index';
}
$print_url = $this->getPrintUrl($year);
// Expenses chart
$chart = Charts::multi('line', 'chartjs')
->dimensions(0, 300)
@ -95,21 +123,49 @@ class ExpenseSummary extends Controller
->credits(false)
->view($chart_template);
return view($view_template, compact('chart', 'dates', 'categories', 'expenses', 'totals'));
return view($view_template, compact(
'chart',
'dates',
'categories',
'statuses',
'accounts',
'vendors',
'expenses',
'totals',
'print_url'
));
}
private function setAmount(&$graph, &$totals, &$expenses, $items, $type, $date_field)
{
foreach ($items as $item) {
if ($item['table'] == 'bill_payments') {
$bill = $item->bill;
switch ($item->getTable()) {
case 'bill_payments':
$bill = $item->bill;
$item->category_id = $bill->category_id;
if ($vendors = request('vendors')) {
if (!in_array($bill->vendor_id, $vendors)) {
continue;
}
}
$item->category_id = $bill->category_id;
break;
case 'bills':
if ($accounts = request('accounts')) {
foreach ($item->payments as $payment) {
if (!in_array($payment->account_id, $accounts)) {
continue 2;
}
}
}
break;
}
$date = Date::parse($item->$date_field)->format('F');
$month = Date::parse($item->$date_field)->format('F');
$month_year = Date::parse($item->$date_field)->format('F-Y');
if (!isset($expenses[$item->category_id])) {
if (!isset($expenses[$item->category_id]) || !isset($expenses[$item->category_id][$month]) || !isset($graph[$month_year])) {
continue;
}
@ -122,13 +178,34 @@ class ExpenseSummary extends Controller
}
}
$expenses[$item->category_id][$date]['amount'] += $amount;
$expenses[$item->category_id][$date]['currency_code'] = $item->currency_code;
$expenses[$item->category_id][$date]['currency_rate'] = $item->currency_rate;
$expenses[$item->category_id][$month]['amount'] += $amount;
$expenses[$item->category_id][$month]['currency_code'] = $item->currency_code;
$expenses[$item->category_id][$month]['currency_rate'] = $item->currency_rate;
$graph[Date::parse($item->$date_field)->format('F-Y')] += $amount;
$graph[$month_year] += $amount;
$totals[$date]['amount'] += $amount;
$totals[$month]['amount'] += $amount;
}
}
private function getPrintUrl($year)
{
$print_url = 'reports/expense-summary?print=1'
. '&status=' . request('status')
. '&year='. request('year', $year);
collect(request('accounts'))->each(function($item) use(&$print_url) {
$print_url .= '&accounts[]=' . $item;
});
collect(request('vendors'))->each(function($item) use(&$print_url) {
$print_url .= '&vendors[]=' . $item;
});
collect(request('categories'))->each(function($item) use(&$print_url) {
$print_url .= '&categories[]=' . $item;
});
return $print_url;
}
}

View File

@ -3,13 +3,17 @@
namespace App\Http\Controllers\Reports;
use App\Http\Controllers\Controller;
use App\Models\Banking\Account;
use App\Models\Income\Customer;
use App\Models\Income\Invoice;
use App\Models\Income\InvoicePayment;
use App\Models\Income\Revenue;
use App\Models\Expense\Bill;
use App\Models\Expense\BillPayment;
use App\Models\Expense\Payment;
use App\Models\Expense\Vendor;
use App\Models\Setting\Category;
use App\Utilities\Recurring;
use Charts;
use Date;
@ -25,16 +29,16 @@ class IncomeExpenseSummary extends Controller
$dates = $totals = $compares = $profit_graph = $categories = [];
$status = request('status');
$year = request('year', Date::now()->year);
$categories_filter = request('categories');
$income_categories = Category::enabled()->type('income')->pluck('name', 'id')->toArray();
$income_categories = Category::enabled()->type('income')->when($categories_filter, function ($query) use ($categories_filter) {
return $query->whereIn('id', $categories_filter);
})->orderBy('name')->pluck('name', 'id')->toArray();
$expense_categories = Category::enabled()->type('expense')->pluck('name', 'id')->toArray();
// Get year
$year = request('year');
if (empty($year)) {
$year = Date::now()->year;
}
$expense_categories = Category::enabled()->type('expense')->when($categories_filter, function ($query) use ($categories_filter) {
return $query->whereIn('id', $categories_filter);
})->orderBy('name')->pluck('name', 'id')->toArray();
// Dates
for ($j = 1; $j <= 12; $j++) {
@ -70,49 +74,75 @@ class IncomeExpenseSummary extends Controller
}
}
// Invoices
$revenues = Revenue::monthsOfYear('paid_at')->account(request('accounts'))->customer(request('customers'))->isNotTransfer()->get();
$payments = Payment::monthsOfYear('paid_at')->account(request('accounts'))->vendor(request('vendors'))->isNotTransfer()->get();
switch ($status) {
case 'paid':
$invoices = InvoicePayment::monthsOfYear('paid_at')->get();
// Invoices
$invoices = InvoicePayment::monthsOfYear('paid_at')->account(request('accounts'))->get();
$this->setAmount($profit_graph, $totals, $compares, $invoices, 'invoice', 'paid_at');
break;
case 'upcoming':
$invoices = Invoice::accrued()->monthsOfYear('due_at')->get();
$this->setAmount($profit_graph, $totals, $compares, $invoices, 'invoice', 'due_at');
break;
default:
$invoices = Invoice::accrued()->monthsOfYear('invoiced_at')->get();
$this->setAmount($profit_graph, $totals, $compares, $invoices, 'invoice', 'invoiced_at');
break;
}
// Revenues
if ($status != 'upcoming') {
$revenues = Revenue::monthsOfYear('paid_at')->isNotTransfer()->get();
$this->setAmount($profit_graph, $totals, $compares, $revenues, 'revenue', 'paid_at');
}
// Revenues
$this->setAmount($profit_graph, $totals, $compares, $revenues, 'revenue', 'paid_at');
// Bills
switch ($status) {
case 'paid':
$bills = BillPayment::monthsOfYear('paid_at')->get();
// Bills
$bills = BillPayment::monthsOfYear('paid_at')->account(request('accounts'))->get();
$this->setAmount($profit_graph, $totals, $compares, $bills, 'bill', 'paid_at');
// Payments
$this->setAmount($profit_graph, $totals, $compares, $payments, 'payment', 'paid_at');
break;
case 'upcoming':
$bills = Bill::accrued()->monthsOfYear('due_at')->get();
// Invoices
$invoices = Invoice::accrued()->monthsOfYear('due_at')->customer(request('customers'))->get();
Recurring::reflect($invoices, 'invoice', 'due_at', $status);
$this->setAmount($profit_graph, $totals, $compares, $invoices, 'invoice', 'due_at');
// Revenues
Recurring::reflect($revenues, 'revenue', 'paid_at', $status);
$this->setAmount($profit_graph, $totals, $compares, $revenues, 'revenue', 'paid_at');
// Bills
$bills = Bill::accrued()->monthsOfYear('due_at')->vendor(request('vendors'))->get();
Recurring::reflect($bills, 'bill', 'billed_at', $status);
$this->setAmount($profit_graph, $totals, $compares, $bills, 'bill', 'due_at');
// Payments
Recurring::reflect($payments, 'payment', 'paid_at', $status);
$this->setAmount($profit_graph, $totals, $compares, $payments, 'payment', 'paid_at');
break;
default:
$bills = Bill::accrued()->monthsOfYear('billed_at')->get();
// Invoices
$invoices = Invoice::accrued()->monthsOfYear('invoiced_at')->customer(request('customers'))->get();
Recurring::reflect($invoices, 'invoice', 'invoiced_at', $status);
$this->setAmount($profit_graph, $totals, $compares, $invoices, 'invoice', 'invoiced_at');
// Revenues
Recurring::reflect($revenues, 'revenue', 'paid_at', $status);
$this->setAmount($profit_graph, $totals, $compares, $revenues, 'revenue', 'paid_at');
// Bills
$bills = Bill::accrued()->monthsOfYear('billed_at')->vendor(request('vendors'))->get();
Recurring::reflect($bills, 'bill', 'billed_at', $status);
$this->setAmount($profit_graph, $totals, $compares, $bills, 'bill', 'billed_at');
// Payments
Recurring::reflect($payments, 'payment', 'paid_at', $status);
$this->setAmount($profit_graph, $totals, $compares, $payments, 'payment', 'paid_at');
break;
}
// Payments
if ($status != 'upcoming') {
$payments = Payment::monthsOfYear('paid_at')->isNotTransfer()->get();
$this->setAmount($profit_graph, $totals, $compares, $payments, 'payment', 'paid_at');
}
$statuses = collect([
'all' => trans('general.all'),
'paid' => trans('invoices.paid'),
'upcoming' => trans('general.upcoming'),
]);
$accounts = Account::enabled()->pluck('name', 'id')->toArray();
$customers = Customer::enabled()->pluck('name', 'id')->toArray();
$vendors = Vendor::enabled()->pluck('name', 'id')->toArray();
$categories = Category::enabled()->type(['income', 'expense'])->pluck('name', 'id')->toArray();
// Check if it's a print or normal request
if (request('print')) {
@ -123,6 +153,8 @@ class IncomeExpenseSummary extends Controller
$view_template = 'reports.income_expense_summary.index';
}
$print_url = $this->getPrintUrl($year);
// Profit chart
$chart = Charts::multi('line', 'chartjs')
->dimensions(0, 300)
@ -132,23 +164,72 @@ class IncomeExpenseSummary extends Controller
->credits(false)
->view($chart_template);
return view($view_template, compact('chart', 'dates', 'income_categories', 'expense_categories', 'compares', 'totals'));
return view($view_template, compact(
'chart',
'dates',
'income_categories',
'expense_categories',
'categories',
'statuses',
'accounts',
'customers',
'vendors',
'compares',
'totals',
'print_url'
));
}
private function setAmount(&$graph, &$totals, &$compares, $items, $type, $date_field)
{
foreach ($items as $item) {
if ($item['table'] == 'bill_payments' || $item['table'] == 'invoice_payments') {
if ($item->getTable() == 'bill_payments' || $item->getTable() == 'invoice_payments') {
$type_item = $item->$type;
$item->category_id = $type_item->category_id;
}
$date = Date::parse($item->$date_field)->format('F');
switch ($item->getTable()) {
case 'invoice_payments':
$invoice = $item->invoice;
if ($customers = request('customers')) {
if (!in_array($invoice->customer_id, $customers)) {
continue;
}
}
$item->category_id = $invoice->category_id;
break;
case 'bill_payments':
$bill = $item->bill;
if ($vendors = request('vendors')) {
if (!in_array($bill->vendor_id, $vendors)) {
continue;
}
}
$item->category_id = $bill->category_id;
break;
case 'invoices':
case 'bills':
if ($accounts = request('accounts')) {
foreach ($item->payments as $payment) {
if (!in_array($payment->account_id, $accounts)) {
continue 2;
}
}
}
break;
}
$month = Date::parse($item->$date_field)->format('F');
$month_year = Date::parse($item->$date_field)->format('F-Y');
$group = (($type == 'invoice') || ($type == 'revenue')) ? 'income' : 'expense';
if (!isset($compares[$group][$item->category_id])) {
if (!isset($compares[$group][$item->category_id]) || !isset($compares[$group][$item->category_id][$month]) || !isset($graph[$month_year])) {
continue;
}
@ -161,19 +242,44 @@ class IncomeExpenseSummary extends Controller
}
}
$compares[$group][$item->category_id][$date]['amount'] += $amount;
$compares[$group][$item->category_id][$date]['currency_code'] = $item->currency_code;
$compares[$group][$item->category_id][$date]['currency_rate'] = $item->currency_rate;
$compares[$group][$item->category_id][$month]['amount'] += $amount;
$compares[$group][$item->category_id][$month]['currency_code'] = $item->currency_code;
$compares[$group][$item->category_id][$month]['currency_rate'] = $item->currency_rate;
if ($group == 'income') {
$graph[Date::parse($item->$date_field)->format('F-Y')] += $amount;
$graph[$month_year] += $amount;
$totals[$date]['amount'] += $amount;
$totals[$month]['amount'] += $amount;
} else {
$graph[Date::parse($item->$date_field)->format('F-Y')] -= $amount;
$graph[$month_year] -= $amount;
$totals[$date]['amount'] -= $amount;
$totals[$month]['amount'] -= $amount;
}
}
}
private function getPrintUrl($year)
{
$print_url = 'reports/income-expense-summary?print=1'
. '&status=' . request('status')
. '&year='. request('year', $year);
collect(request('accounts'))->each(function($item) use(&$print_url) {
$print_url .= '&accounts[]=' . $item;
});
collect(request('customers'))->each(function($item) use(&$print_url) {
$print_url .= '&customers[]=' . $item;
});
collect(request('vendors'))->each(function($item) use(&$print_url) {
$print_url .= '&vendors[]=' . $item;
});
collect(request('categories'))->each(function($item) use(&$print_url) {
$print_url .= '&categories[]=' . $item;
});
return $print_url;
}
}

View File

@ -3,10 +3,13 @@
namespace App\Http\Controllers\Reports;
use App\Http\Controllers\Controller;
use App\Models\Banking\Account;
use App\Models\Income\Customer;
use App\Models\Income\Invoice;
use App\Models\Income\InvoicePayment;
use App\Models\Income\Revenue;
use App\Models\Setting\Category;
use App\Utilities\Recurring;
use Charts;
use Date;
@ -22,13 +25,16 @@ class IncomeSummary extends Controller
$dates = $totals = $incomes = $incomes_graph = $categories = [];
$status = request('status');
$year = request('year', Date::now()->year);
$categories = Category::enabled()->type('income')->pluck('name', 'id')->toArray();
$categories = Category::enabled()->type('income')->orderBy('name')->pluck('name', 'id')->toArray();
// Get year
$year = request('year');
if (empty($year)) {
$year = Date::now()->year;
if ($categories_filter = request('categories')) {
$cats = collect($categories)->filter(function ($value, $key) use ($categories_filter) {
return in_array($key, $categories_filter);
});
} else {
$cats = $categories;
}
// Dates
@ -44,38 +50,58 @@ class IncomeSummary extends Controller
'currency_rate' => 1
);
foreach ($categories as $category_id => $category_name) {
$incomes[$category_id][$dates[$j]] = array(
foreach ($cats as $category_id => $category_name) {
$incomes[$category_id][$dates[$j]] = [
'category_id' => $category_id,
'name' => $category_name,
'amount' => 0,
'currency_code' => setting('general.default_currency'),
'currency_rate' => 1
);
];
}
}
// Invoices
$revenues = Revenue::monthsOfYear('paid_at')->account(request('accounts'))->customer(request('customers'))->isNotTransfer()->get();
switch ($status) {
case 'paid':
$invoices = InvoicePayment::monthsOfYear('paid_at')->get();
// Invoices
$invoices = InvoicePayment::monthsOfYear('paid_at')->account(request('accounts'))->get();
$this->setAmount($incomes_graph, $totals, $incomes, $invoices, 'invoice', 'paid_at');
// Revenues
$this->setAmount($incomes_graph, $totals, $incomes, $revenues, 'revenue', 'paid_at');
break;
case 'upcoming':
$invoices = Invoice::accrued()->monthsOfYear('due_at')->get();
// Invoices
$invoices = Invoice::accrued()->monthsOfYear('due_at')->customer(request('customers'))->get();
Recurring::reflect($invoices, 'invoice', 'invoiced_at', $status);
$this->setAmount($incomes_graph, $totals, $incomes, $invoices, 'invoice', 'due_at');
// Revenues
Recurring::reflect($revenues, 'revenue', 'paid_at', $status);
$this->setAmount($incomes_graph, $totals, $incomes, $revenues, 'revenue', 'paid_at');
break;
default:
$invoices = Invoice::accrued()->monthsOfYear('invoiced_at')->get();
// Invoices
$invoices = Invoice::accrued()->monthsOfYear('invoiced_at')->customer(request('customers'))->get();
Recurring::reflect($invoices, 'invoice', 'invoiced_at', $status);
$this->setAmount($incomes_graph, $totals, $incomes, $invoices, 'invoice', 'invoiced_at');
// Revenues
Recurring::reflect($revenues, 'revenue', 'paid_at', $status);
$this->setAmount($incomes_graph, $totals, $incomes, $revenues, 'revenue', 'paid_at');
break;
}
// Revenues
if ($status != 'upcoming') {
$revenues = Revenue::monthsOfYear('paid_at')->isNotTransfer()->get();
$this->setAmount($incomes_graph, $totals, $incomes, $revenues, 'revenue', 'paid_at');
}
$statuses = collect([
'all' => trans('general.all'),
'paid' => trans('invoices.paid'),
'upcoming' => trans('dashboard.receivables'),
]);
$accounts = Account::enabled()->pluck('name', 'id')->toArray();
$customers = Customer::enabled()->pluck('name', 'id')->toArray();
// Check if it's a print or normal request
if (request('print')) {
@ -86,6 +112,8 @@ class IncomeSummary extends Controller
$view_template = 'reports.income_summary.index';
}
$print_url = $this->getPrintUrl($year);
// Incomes chart
$chart = Charts::multi('line', 'chartjs')
->dimensions(0, 300)
@ -95,21 +123,49 @@ class IncomeSummary extends Controller
->credits(false)
->view($chart_template);
return view($view_template, compact('chart', 'dates', 'categories', 'incomes', 'totals'));
return view($view_template, compact(
'chart',
'dates',
'categories',
'statuses',
'accounts',
'customers',
'incomes',
'totals',
'print_url'
));
}
private function setAmount(&$graph, &$totals, &$incomes, $items, $type, $date_field)
{
foreach ($items as $item) {
if ($item['table'] == 'invoice_payments') {
$invoice = $item->invoice;
switch ($item->getTable()) {
case 'invoice_payments':
$invoice = $item->invoice;
$item->category_id = $invoice->category_id;
if ($customers = request('customers')) {
if (!in_array($invoice->customer_id, $customers)) {
continue;
}
}
$item->category_id = $invoice->category_id;
break;
case 'invoices':
if ($accounts = request('accounts')) {
foreach ($item->payments as $payment) {
if (!in_array($payment->account_id, $accounts)) {
continue 2;
}
}
}
break;
}
$date = Date::parse($item->$date_field)->format('F');
$month = Date::parse($item->$date_field)->format('F');
$month_year = Date::parse($item->$date_field)->format('F-Y');
if (!isset($incomes[$item->category_id])) {
if (!isset($incomes[$item->category_id]) || !isset($incomes[$item->category_id][$month]) || !isset($graph[$month_year])) {
continue;
}
@ -122,13 +178,34 @@ class IncomeSummary extends Controller
}
}
$incomes[$item->category_id][$date]['amount'] += $amount;
$incomes[$item->category_id][$date]['currency_code'] = $item->currency_code;
$incomes[$item->category_id][$date]['currency_rate'] = $item->currency_rate;
$incomes[$item->category_id][$month]['amount'] += $amount;
$incomes[$item->category_id][$month]['currency_code'] = $item->currency_code;
$incomes[$item->category_id][$month]['currency_rate'] = $item->currency_rate;
$graph[Date::parse($item->$date_field)->format('F-Y')] += $amount;
$graph[$month_year] += $amount;
$totals[$date]['amount'] += $amount;
$totals[$month]['amount'] += $amount;
}
}
private function getPrintUrl($year)
{
$print_url = 'reports/income-summary?print=1'
. '&status=' . request('status')
. '&year='. request('year', $year);
collect(request('accounts'))->each(function($item) use(&$print_url) {
$print_url .= '&accounts[]=' . $item;
});
collect(request('customers'))->each(function($item) use(&$print_url) {
$print_url .= '&customers[]=' . $item;
});
collect(request('categories'))->each(function($item) use(&$print_url) {
$print_url .= '&categories[]=' . $item;
});
return $print_url;
}
}

View File

@ -25,16 +25,11 @@ class ProfitLoss extends Controller
$dates = $totals = $compares = $categories = [];
$status = request('status');
$year = request('year', Date::now()->year);
$income_categories = Category::enabled()->type('income')->pluck('name', 'id')->toArray();
$income_categories = Category::enabled()->type('income')->orderBy('name')->pluck('name', 'id')->toArray();
$expense_categories = Category::enabled()->type('expense')->pluck('name', 'id')->toArray();
// Get year
$year = request('year');
if (empty($year)) {
$year = Date::now()->year;
}
$expense_categories = Category::enabled()->type('expense')->orderBy('name')->pluck('name', 'id')->toArray();
// Dates
for ($j = 1; $j <= 12; $j++) {
@ -142,6 +137,12 @@ class ProfitLoss extends Controller
$this->setAmount($totals, $compares, $payments, 'payment', 'paid_at');
}
$statuses = collect([
'all' => trans('general.all'),
'paid' => trans('invoices.paid'),
'upcoming' => trans('general.upcoming'),
]);
// Check if it's a print or normal request
if (request('print')) {
$view_template = 'reports.profit_loss.print';
@ -149,7 +150,7 @@ class ProfitLoss extends Controller
$view_template = 'reports.profit_loss.index';
}
return view($view_template, compact('dates', 'income_categories', 'expense_categories', 'compares', 'totals', 'gross'));
return view($view_template, compact('dates', 'income_categories', 'expense_categories', 'compares', 'totals', 'gross', 'statuses'));
}
private function setAmount(&$totals, &$compares, $items, $type, $date_field)

View File

@ -27,17 +27,12 @@ class TaxSummary extends Controller
$dates = $incomes = $expenses = $totals = [];
$status = request('status');
$year = request('year', Date::now()->year);
$t = Tax::enabled()->where('rate', '<>', '0')->pluck('name')->toArray();
$taxes = array_combine($t, $t);
// Get year
$year = request('year');
if (empty($year)) {
$year = Date::now()->year;
}
// Dates
for ($j = 1; $j <= 12; $j++) {
$dates[$j] = Date::parse($year . '-' . $j)->format('M');
@ -90,6 +85,12 @@ class TaxSummary extends Controller
break;
}
$statuses = collect([
'all' => trans('general.all'),
'paid' => trans('invoices.paid'),
'upcoming' => trans('general.upcoming'),
]);
// Check if it's a print or normal request
if (request('print')) {
$view_template = 'reports.tax_summary.print';
@ -97,13 +98,13 @@ class TaxSummary extends Controller
$view_template = 'reports.tax_summary.index';
}
return view($view_template, compact('dates', 'taxes', 'incomes', 'expenses', 'totals'));
return view($view_template, compact('dates', 'taxes', 'incomes', 'expenses', 'totals', 'statuses'));
}
private function setAmount(&$items, &$totals, $rows, $type, $date_field)
{
foreach ($rows as $row) {
if ($row['table'] == 'bill_payments' || $row['table'] == 'invoice_payments') {
if ($row->getTable() == 'bill_payments' || $row->getTable() == 'invoice_payments') {
$type_row = $row->$type;
$row->category_id = $type_row->category_id;
@ -126,7 +127,14 @@ class TaxSummary extends Controller
continue;
}
$amount = $this->convert($row_total->amount, $row->currency_code, $row->currency_rate);
if ($date_field == 'paid_at') {
$rate = ($row->amount * 100) / $type_row->amount;
$row_amount = ($row_total->amount / 100) * $rate;
} else {
$row_amount = $row_total->amount;
}
$amount = $this->convert($row_amount, $row->currency_code, $row->currency_rate);
$items[$row_total->name][$date]['amount'] += $amount;

View File

@ -25,7 +25,7 @@ class Categories extends Controller
'income' => trans_choice('general.incomes', 1),
'item' => trans_choice('general.items', 1),
'other' => trans_choice('general.others', 1),
])->prepend(trans('general.all_type', ['type' => trans_choice('general.types', 2)]), '');
]);
return view('settings.categories.index', compact('categories', 'types', 'transfer_id'));
}

View File

@ -162,7 +162,7 @@ class Currencies extends Controller
return redirect('settings/currencies');
} else {
$message = trans('messages.warning.disabled', ['name' => $currency->name, 'text' => implode(', ', $relationships)]);
$message = trans('messages.warning.disable_code', ['name' => $currency->name, 'text' => implode(', ', $relationships)]);
flash($message)->warning();

View File

@ -47,7 +47,7 @@ class Settings extends Controller
$currencies = Currency::enabled()->orderBy('name')->pluck('name', 'code');
$taxes = Tax::enabled()->orderBy('rate')->get()->pluck('title', 'id');
$taxes = Tax::enabled()->orderBy('name')->get()->pluck('title', 'id');
$payment_methods = Modules::getPaymentMethods();
@ -67,6 +67,24 @@ class Settings extends Controller
'space' => trans('settings.localisation.date.space'),
];
$item_names = [
'settings.invoice.item' => trans('settings.invoice.item'),
'settings.invoice.product' => trans('settings.invoice.product'),
'settings.invoice.service' => trans('settings.invoice.service'),
'custom' => trans('settings.invoice.custom'),
];
$price_names = [
'settings.invoice.price' => trans('settings.invoice.price'),
'settings.invoice.rate' => trans('settings.invoice.rate'),
'custom' => trans('settings.invoice.custom'),
];
$quantity_names = [
'settings.invoice.quantity' => trans('settings.invoice.quantity'),
'custom' => trans('settings.invoice.custom'),
];
$email_protocols = [
'mail' => trans('settings.email.php'),
'smtp' => trans('settings.email.smtp.name'),
@ -88,6 +106,9 @@ class Settings extends Controller
'payment_methods',
'date_formats',
'date_separators',
'item_names',
'price_names',
'quantity_names',
'email_protocols',
'percent_positions'
));
@ -160,17 +181,17 @@ class Settings extends Controller
protected function oneCompany($key, $value)
{
switch ($key) {
case 'company_name':
Installer::updateEnv(['MAIL_FROM_NAME' => '"' . $value . '"']);
break;
case 'company_email':
Installer::updateEnv(['MAIL_FROM_ADDRESS' => $value]);
break;
case 'default_locale':
// Change default locale
Installer::updateEnv([
'APP_LOCALE' => $value
]);
Installer::updateEnv(['APP_LOCALE' => $value]);
break;
case 'session_handler':
// Change session handler
Installer::updateEnv([
'SESSION_DRIVER' => $value
]);
Installer::updateEnv(['SESSION_DRIVER' => $value]);
break;
}
}

View File

@ -18,7 +18,13 @@ class Taxes extends Controller
{
$taxes = Tax::collect();
return view('settings.taxes.index', compact('taxes', 'rates'));
$types = [
'normal' => trans('taxes.normal'),
'inclusive' => trans('taxes.inclusive'),
'compound' => trans('taxes.compound'),
];
return view('settings.taxes.index', compact('taxes', 'types'));
}
/**
@ -38,7 +44,13 @@ class Taxes extends Controller
*/
public function create()
{
return view('settings.taxes.create');
$types = [
'normal' => trans('taxes.normal'),
'inclusive' => trans('taxes.inclusive'),
'compound' => trans('taxes.compound'),
];
return view('settings.taxes.create', compact('types'));
}
/**
@ -68,7 +80,13 @@ class Taxes extends Controller
*/
public function edit(Tax $tax)
{
return view('settings.taxes.edit', compact('tax'));
$types = [
'normal' => trans('taxes.normal'),
'inclusive' => trans('taxes.inclusive'),
'compound' => trans('taxes.compound'),
];
return view('settings.taxes.edit', compact('tax', 'types'));
}
/**

View File

@ -0,0 +1,81 @@
<?php
namespace App\Http\Controllers\Wizard;
use App\Http\Controllers\Controller;
use App\Http\Requests\Wizard\Company as Request;
use App\Models\Common\Company;
use App\Traits\Uploads;
class Companies extends Controller
{
use Uploads;
/**
* Show the form for creating a new resource.
*
* @return Response
*/
public function edit()
{
if (setting('general.wizard', false)) {
return redirect('/');
}
$company = Company::find(session('company_id'));
$company->setSettings();
return view('wizard.companies.edit', compact('company'));
}
/**
* Update the specified resource in storage.
*
* @param Company $company
* @param Request $request
*
* @return Response
*/
public function update(Request $request)
{
// Company
$company = Company::find(session('company_id'));
$fields = $request->all();
$skip_keys = ['company_id', '_method', '_token'];
$file_keys = ['company_logo', 'invoice_logo'];
foreach ($fields as $key => $value) {
// Don't process unwanted keys
if (in_array($key, $skip_keys)) {
continue;
}
// Process file uploads
if (in_array($key, $file_keys)) {
// Upload attachment
if ($request->file($key)) {
$media = $this->getMedia($request->file($key), 'settings');
$company->attachMedia($media, $key);
$value = $media->id;
}
// Prevent reset
if (empty($value)) {
continue;
}
}
setting()->set('general.' . $key, $value);
}
// Save all settings
setting()->save();
return redirect('wizard/currencies');
}
}

View File

@ -0,0 +1,311 @@
<?php
namespace App\Http\Controllers\Wizard;
use Akaunting\Money\Currency as MoneyCurrency;
use App\Http\Controllers\Controller;
use App\Http\Requests\Setting\Currency as Request;
use App\Models\Banking\Account;
use App\Models\Setting\Currency;
class Currencies extends Controller
{
/**
* Show the form for editing the specified resource.
*
* @param Currency $currency
*
* @return Response
*/
public function index()
{
if (setting('general.wizard', false)) {
return redirect('/');
}
$currencies = Currency::all();
return view('wizard.currencies.index', compact('currencies'));
}
/**
* Show the form for creating a new resource.
*
* @return Response
*/
public function create()
{
if (setting(setting('general.wizard', false))) {
return redirect('/');
}
// Get current currencies
$current = Currency::pluck('code')->toArray();
// Prepare codes
$codes = array();
$currencies = MoneyCurrency::getCurrencies();
foreach ($currencies as $key => $item) {
// Don't show if already available
if (in_array($key, $current)) {
continue;
}
$codes[$key] = $key;
}
$html = view('wizard.currencies.create', compact('codes'))->render();
return response()->json([
'success' => true,
'error' => false,
'message' => 'null',
'html' => $html,
]);
}
/**
* Store a newly created resource in storage.
*
* @param Request $request
*
* @return Response
*/
public function store(Request $request)
{
// Force the rate to be 1 for default currency
if ($request['default_currency']) {
$request['rate'] = '1';
}
$currency = Currency::create($request->all());
// Update default currency setting
if ($request['default_currency']) {
setting()->set('general.default_currency', $request['code']);
setting()->save();
}
$message = trans('messages.success.added', ['type' => trans_choice('general.currencies', 1)]);
return response()->json([
'success' => true,
'error' => false,
'message' => $message,
'data' => $currency,
]);
}
/**
* Show the form for editing the specified resource.
*
* @param Currency $currency
*
* @return Response
*/
public function edit(Currency $currency)
{
if (setting('general.wizard', false)) {
return redirect('/');
}
// Get current currencies
$current = Currency::pluck('code')->toArray();
// Prepare codes
$codes = array();
$currencies = MoneyCurrency::getCurrencies();
foreach ($currencies as $key => $item) {
// Don't show if already available
if (($key != $currency->code) && in_array($key, $current)) {
continue;
}
$codes[$key] = $key;
}
$item = $currency;
$html = view('wizard.currencies.edit', compact('item', 'codes'))->render();
return response()->json([
'success' => true,
'error' => false,
'message' => 'null',
'html' => $html,
]);
}
/**
* Update the specified resource in storage.
*
* @param Currency $currency
* @param Request $request
*
* @return Response
*/
public function update(Currency $currency, Request $request)
{
// Check if we can disable or change the code
if (!$request['enabled'] || ($currency->code != $request['code'])) {
$relationships = $this->countRelationships($currency, [
'accounts' => 'accounts',
'customers' => 'customers',
'invoices' => 'invoices',
'revenues' => 'revenues',
'bills' => 'bills',
'payments' => 'payments',
]);
if ($currency->code == setting('general.default_currency')) {
$relationships[] = strtolower(trans_choice('general.companies', 1));
}
}
if (empty($relationships)) {
// Force the rate to be 1 for default currency
if ($request['default_currency']) {
$request['rate'] = '1';
}
$currency->update($request->all());
// Update default currency setting
if ($request['default_currency']) {
setting()->set('general.default_currency', $request['code']);
setting()->save();
}
$message = trans('messages.success.updated', ['type' => trans_choice('general.currencies', 1)]);
return response()->json([
'success' => true,
'error' => false,
'message' => $message,
'data' => $currency,
]);
} else {
$message = trans('messages.warning.disabled', ['name' => $currency->name, 'text' => implode(', ', $relationships)]);
return response()->json([
'success' => true,
'error' => false,
'message' => $message,
'data' => $currency,
]);
}
}
/**
* Enable the specified resource.
*
* @param Currency $currency
*
* @return Response
*/
public function enable(Currency $currency)
{
$currency->enabled = 1;
$currency->save();
$message = trans('messages.success.enabled', ['type' => trans_choice('general.currencies', 1)]);
return response()->json([
'success' => true,
'error' => false,
'message' => $message,
'data' => $currency,
]);
}
/**
* Disable the specified resource.
*
* @param Currency $currency
*
* @return Response
*/
public function disable(Currency $currency)
{
$relationships = $this->countRelationships($currency, [
'accounts' => 'accounts',
'customers' => 'customers',
'invoices' => 'invoices',
'revenues' => 'revenues',
'bills' => 'bills',
'payments' => 'payments',
]);
if ($currency->code == setting('general.default_currency')) {
$relationships[] = strtolower(trans_choice('general.companies', 1));
}
if (empty($relationships)) {
$currency->enabled = 0;
$currency->save();
$message = trans('messages.success.disabled', ['type' => trans_choice('general.currencies', 1)]);
return response()->json([
'success' => true,
'error' => false,
'message' => $message,
'data' => $currency,
]);
} else {
$message = trans('messages.warning.disabled', ['name' => $currency->name, 'text' => implode(', ', $relationships)]);
return response()->json([
'success' => false,
'error' => true,
'message' => $message,
'data' => $currency,
]);
}
}
/**
* Remove the specified resource from storage.
*
* @param Currency $currency
*
* @return Response
*/
public function destroy(Currency $currency)
{
$relationships = $this->countRelationships($currency, [
'accounts' => 'accounts',
'customers' => 'customers',
'invoices' => 'invoices',
'revenues' => 'revenues',
'bills' => 'bills',
'payments' => 'payments',
]);
if ($currency->code == setting('general.default_currency')) {
$relationships[] = strtolower(trans_choice('general.companies', 1));
}
if (empty($relationships)) {
$currency->delete();
$message = trans('messages.success.deleted', ['type' => trans_choice('general.currencies', 1)]);
return response()->json([
'success' => true,
'error' => false,
'message' => $message,
'data' => $currency,
]);
} else {
$message = trans('messages.warning.deleted', ['name' => $currency->name, 'text' => implode(', ', $relationships)]);
return response()->json([
'success' => false,
'error' => true,
'message' => $message,
'data' => $currency,
]);
}
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace App\Http\Controllers\Wizard;
use Illuminate\Routing\Controller;
use App\Traits\Modules;
use App\Models\Module\Module;
class Finish extends Controller
{
use Modules;
/**
* Show the form for creating a new resource.
*
* @return Response
*/
public function index()
{
if (setting(setting('general.wizard', false))) {
return redirect('/');
}
setting()->set('general.wizard', true);
// Save all settings
setting()->save();
$data = [
'query' => [
'limit' => 4
]
];
$modules = $this->getFeaturedModules($data);
return view('wizard.finish.index', compact('modules'));
}
}

View File

@ -0,0 +1,231 @@
<?php
namespace App\Http\Controllers\Wizard;
use App\Http\Controllers\Controller;
use App\Http\Requests\Setting\Tax as Request;
use App\Models\Setting\Tax;
class Taxes extends Controller
{
/**
* Show the form for editing the specified resource.
*
* @return Response
*/
public function index()
{
if (setting(setting('general.wizard', false))) {
return redirect('/');
}
$taxes = Tax::all();
return view('wizard.taxes.index', compact('taxes'));
}
/**
* Show the form for creating a new resource.
*
* @return Response
*/
public function create()
{
if (setting(setting('general.wizard', false))) {
return redirect('/');
}
$html = view('wizard.taxes.create', compact('codes'))->render();
return response()->json([
'success' => true,
'error' => false,
'message' => 'null',
'html' => $html,
]);
}
/**
* Store a newly created resource in storage.
*
* @param Request $request
*
* @return Response
*/
public function store(Request $request)
{
$tax = Tax::create($request->all());
$message = trans('messages.success.added', ['type' => trans_choice('general.tax_rates', 1)]);
return response()->json([
'success' => true,
'error' => false,
'message' => $message,
'data' => $tax,
]);
}
/**
* Show the form for editing the specified resource.
*
* @param Tax $tax
*
* @return Response
*/
public function edit(Tax $tax)
{
if (setting(setting('general.wizard', false))) {
return redirect('/');
}
$item = $tax;
$html = view('wizard.taxes.edit', compact('item'))->render();
return response()->json([
'success' => true,
'error' => false,
'message' => 'null',
'html' => $html,
]);
}
/**
* Update the specified resource in storage.
*
* @param Tax $tax
* @param Request $request
*
* @return Response
*/
public function update(Tax $tax, Request $request)
{
$relationships = $this->countRelationships($tax, [
'items' => 'items',
'invoice_items' => 'invoices',
'bill_items' => 'bills',
]);
if (empty($relationships) || $request['enabled']) {
$tax->update($request->all());
$message = trans('messages.success.updated', ['type' => trans_choice('general.tax_rates', 1)]);
return response()->json([
'success' => true,
'error' => false,
'message' => $message,
'data' => $tax,
]);
} else {
$message = trans('messages.warning.disabled', ['name' => $tax->name, 'text' => implode(', ', $relationships)]);
return response()->json([
'success' => true,
'error' => false,
'message' => $message,
'data' => $tax,
]);
}
}
/**
* Enable the specified resource.
*
* @param Tax $tax
*
* @return Response
*/
public function enable(Tax $tax)
{
$tax->enabled = 1;
$tax->save();
$message = trans('messages.success.enabled', ['type' => trans_choice('general.tax_rates', 1)]);
return response()->json([
'success' => true,
'error' => false,
'message' => $message,
'data' => $tax,
]);
}
/**
* Disable the specified resource.
*
* @param Tax $tax
*
* @return Response
*/
public function disable(Tax $tax)
{
$relationships = $this->countRelationships($tax, [
'items' => 'items',
'invoice_items' => 'invoices',
'bill_items' => 'bills',
]);
if (empty($relationships)) {
$tax->enabled = 0;
$tax->save();
$message = trans('messages.success.disabled', ['type' => trans_choice('general.tax_rates', 1)]);
return response()->json([
'success' => true,
'error' => false,
'message' => $message,
'data' => $tax,
]);
} else {
$message = trans('messages.warning.disabled', ['name' => $tax->name, 'text' => implode(', ', $relationships)]);
return response()->json([
'success' => false,
'error' => true,
'message' => $message,
'data' => $tax,
]);
}
}
/**
* Remove the specified resource from storage.
*
* @param Tax $tax
*
* @return Response
*/
public function destroy(Tax $tax)
{
$relationships = $this->countRelationships($tax, [
'items' => 'items',
'invoice_items' => 'invoices',
'bill_items' => 'bills',
]);
if (empty($relationships)) {
$tax->delete();
$message = trans('messages.success.deleted', ['type' => trans_choice('general.taxes', 1)]);
return response()->json([
'success' => true,
'error' => false,
'message' => $message,
'data' => $tax,
]);
} else {
$message = trans('messages.warning.deleted', ['name' => $tax->name, 'text' => implode(', ', $relationships)]);
return response()->json([
'success' => false,
'error' => true,
'message' => $message,
'data' => $tax,
]);
}
}
}

View File

@ -41,6 +41,13 @@ class Kernel extends HttpKernel
'company.currencies',
],
'wizard' => [
'web',
'language',
'auth',
'permission:read-admin-panel',
],
'admin' => [
'web',
'language',

View File

@ -70,7 +70,7 @@ class AdminMenu
]);
}
// Expences
// Expenses
if ($user->can(['read-expenses-bills', 'read-expenses-payments', 'read-expenses-vendors'])) {
$menu->dropdown(trans_choice('general.expenses', 2), function ($sub) use($user, $attr) {
if ($user->can('read-expenses-bills')) {
@ -91,7 +91,7 @@ class AdminMenu
}
// Banking
if ($user->can(['read-banking-accounts', 'read-banking-transfers', 'read-banking-transactions'])) {
if ($user->can(['read-banking-accounts', 'read-banking-transfers', 'read-banking-transactions', 'read-banking-reconciliations'])) {
$menu->dropdown(trans('general.banking'), function ($sub) use($user, $attr) {
if ($user->can('read-banking-accounts')) {
$sub->url('banking/accounts', trans_choice('general.accounts', 2), 1, $attr);
@ -104,6 +104,10 @@ class AdminMenu
if ($user->can('read-banking-transactions')) {
$sub->url('banking/transactions', trans_choice('general.transactions', 2), 3, $attr);
}
if ($user->can('read-banking-reconciliations')) {
$sub->url('banking/reconciliations', trans_choice('general.reconciliations', 2), 4, $attr);
}
}, 5, [
'title' => trans('general.banking'),
'icon' => 'fa fa-university',
@ -204,4 +208,4 @@ class AdminMenu
return $next($request);
}
}
}

View File

@ -17,7 +17,7 @@ class DateFormat
public function handle($request, Closure $next)
{
if (($request->method() == 'POST') || ($request->method() == 'PATCH')) {
$fields = ['paid_at', 'due_at', 'billed_at', 'invoiced_at'];
$fields = ['paid_at', 'due_at', 'billed_at', 'invoiced_at', 'started_at', 'ended_at'];
foreach ($fields as $field) {
$date = $request->get($field);

View File

@ -25,7 +25,7 @@ class Permission extends FormRequest
{
// Check if store or update
if ($this->getMethod() == 'PATCH') {
$id = $this->role->getAttribute('id');
$id = $this->permission->getAttribute('id');
} else {
$id = null;
}

View File

@ -0,0 +1,33 @@
<?php
namespace App\Http\Requests\Banking;
use App\Http\Requests\Request;
class Reconciliation extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer',
'started_at' => 'required|date_format:Y-m-d H:i:s',
'ended_at' => 'required|date_format:Y-m-d H:i:s',
'closing_balance' => 'required',
];
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Http\Requests\Common;
use App\Http\Requests\Request;
class Notification extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'path' => 'required|string',
'id' => 'required|integer',
];
}
}

View File

@ -26,6 +26,8 @@ class Tax extends Request
return [
'name' => 'required|string',
'rate' => 'required|min:0|max:100',
'type' => 'required|string',
'enabled' => 'integer|boolean',
];
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Http\Requests\Wizard;
use Illuminate\Foundation\Http\FormRequest;
class Company extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'company_logo' => 'mimes:' . setting('general.file_types') . '|between:0,' . setting('general.file_size') * 1024,
];
}
}

View File

@ -18,9 +18,16 @@ class All
public function compose(View $view)
{
// Make sure it's installed
if (env('APP_INSTALLED')) {
// Share date format
$view->with(['date_format' => $this->getCompanyDateFormat()]);
if (!env('APP_INSTALLED') && (env('APP_ENV') !== 'testing')) {
return;
}
// Share user logged in
$auth_user = auth()->user();
// Share date format
$date_format = $this->getCompanyDateFormat();
$view->with(['auth_user' => $auth_user, 'date_format' => $date_format]);
}
}

View File

@ -24,6 +24,7 @@ class Header
$bills = [];
$invoices = [];
$items = [];
$items_reminder = [];
$notifications = 0;
$company = null;
@ -55,6 +56,10 @@ class Header
$items[$data['item_id']] = $data['name'];
$notifications++;
break;
case 'App\Notifications\Common\ItemReminder':
$items_reminder[$data['item_id']] = $data['name'];
$notifications++;
break;
}
}
@ -68,6 +73,7 @@ class Header
'bills' => $bills,
'invoices' => $invoices,
'items' => $items,
'items_reminder' => $items_reminder,
'company' => $company,
'updates' => $updates,
]);

View File

@ -0,0 +1,45 @@
<?php
namespace App\Http\ViewComposers;
use Illuminate\View\View;
class InvoiceText
{
/**
* Bind data to the view.
*
* @param View $view
* @return void
*/
public function compose(View $view)
{
$text_override = [];
$text_items = setting('general.invoice_item', trans_choice('general.items', 2));
if ($text_items == 'custom') {
$text_items = setting('general.invoice_item_input');
}
$text_quantity = setting('general.invoice_quantity', trans('invoices.quantity'));
if ($text_quantity == 'custom') {
$text_quantity = setting('general.invoice_quantity_input');
}
$text_price = setting('general.invoice_price', trans('invoices.price'));
if ($text_price == 'custom') {
$text_price = setting('general.invoice_price_input');
}
$text_override['items'] = $text_items;
$text_override['quantity'] = $text_quantity;
$text_override['price'] = $text_price;
$view->with(['text_override' => $text_override]);
}
}

View File

@ -3,7 +3,6 @@
namespace App\Http\ViewComposers;
use Illuminate\View\View;
use anlutro\LaravelSettings\Facade as Settingg;
class Menu
{

View File

@ -0,0 +1,52 @@
<?php
namespace App\Http\ViewComposers;
use Route;
use Illuminate\View\View;
use App\Traits\Modules as RemoteModules;
class Notifications
{
use RemoteModules;
/**
* Bind data to the view.
*
* @param View $view
* @return void
*/
public function compose(View $view)
{
// No need to add suggestions in console
if (app()->runningInConsole() || !env('APP_INSTALLED')) {
return;
}
$path = Route::current()->uri();
if (empty($path)) {
return;
}
$notifications = $this->getNotifications($path);
if (empty($notifications)) {
return;
}
// Push to a stack
foreach ($notifications as $notification) {
$setting = 'notifications.'. $notification->path . '.' . $notification->id . '.status';
$path = str_replace('/', '#', $notification->path);
$message = str_replace('#path#', $path, $notification->message);
$message = str_replace('#token#', csrf_token(), $message);
if (setting($setting, 1)) {
$view->getFactory()->startPush('content_content_start', $message);
}
}
}
}

View File

@ -0,0 +1,191 @@
<?php
namespace App\Jobs\Expense;
use App\Events\BillCreated;
use App\Models\Expense\Bill;
use App\Models\Expense\BillHistory;
use App\Models\Expense\BillTotal;
use App\Traits\Currencies;
use App\Traits\DateTime;
use App\Traits\Uploads;
use Illuminate\Foundation\Bus\Dispatchable;
class CreateBill
{
use Currencies, DateTime, Dispatchable, Uploads;
protected $request;
/**
* Create a new job instance.
*
* @param $request
*/
public function __construct($request)
{
$this->request = $request;
}
/**
* Execute the job.
*
* @return Invoice
*/
public function handle()
{
$bill = Bill::create($this->request->input());
// Upload attachment
if ($this->request->file('attachment')) {
$media = $this->getMedia($this->request->file('attachment'), 'bills');
$bill->attachMedia($media, 'attachment');
}
$taxes = [];
$tax_total = 0;
$sub_total = 0;
$discount_total = 0;
$discount = $this->request['discount'];
if ($this->request['item']) {
foreach ($this->request['item'] as $item) {
$bill_item = dispatch(new CreateBillItem($item, $bill, $discount));
// Calculate totals
$tax_total += $bill_item->tax;
$sub_total += $bill_item->total;
// Set taxes
if ($bill_item->item_taxes) {
foreach ($bill_item->item_taxes as $item_tax) {
if (isset($taxes) && array_key_exists($item_tax['tax_id'], $taxes)) {
$taxes[$item_tax['tax_id']]['amount'] += $item_tax['amount'];
} else {
$taxes[$item_tax['tax_id']] = [
'name' => $item_tax['name'],
'amount' => $item_tax['amount']
];
}
}
}
}
}
$s_total = $sub_total;
// Apply discount to total
if ($discount) {
$s_discount = $s_total * ($discount / 100);
$discount_total += $s_discount;
$s_total = $s_total - $s_discount;
}
$amount = $s_total + $tax_total;
$this->request['amount'] = money($amount, $this->request['currency_code'])->getAmount();
$bill->update($this->request->input());
// Add bill totals
$this->addTotals($bill, $this->request, $taxes, $sub_total, $discount_total, $tax_total);
// Add bill history
BillHistory::create([
'company_id' => session('company_id'),
'bill_id' => $bill->id,
'status_code' => 'draft',
'notify' => 0,
'description' => trans('messages.success.added', ['type' => $bill->bill_number]),
]);
// Recurring
$bill->createRecurring();
// Fire the event to make it extendible
event(new BillCreated($bill));
return $bill;
}
protected function addTotals($bill, $request, $taxes, $sub_total, $discount_total, $tax_total)
{
// Check if totals are in request, i.e. api
if (!empty($request['totals'])) {
$sort_order = 1;
foreach ($request['totals'] as $total) {
$total['bill_id'] = $bill->id;
if (empty($total['sort_order'])) {
$total['sort_order'] = $sort_order;
}
BillTotal::create($total);
$sort_order++;
}
return;
}
$sort_order = 1;
// Added bill sub total
BillTotal::create([
'company_id' => $request['company_id'],
'bill_id' => $bill->id,
'code' => 'sub_total',
'name' => 'bills.sub_total',
'amount' => $sub_total,
'sort_order' => $sort_order,
]);
$sort_order++;
// Added bill discount
if ($discount_total) {
BillTotal::create([
'company_id' => $request['company_id'],
'bill_id' => $bill->id,
'code' => 'discount',
'name' => 'bills.discount',
'amount' => $discount_total,
'sort_order' => $sort_order,
]);
// This is for total
$sub_total = $sub_total - $discount_total;
$sort_order++;
}
// Added bill taxes
if (isset($taxes)) {
foreach ($taxes as $tax) {
BillTotal::create([
'company_id' => $request['company_id'],
'bill_id' => $bill->id,
'code' => 'tax',
'name' => $tax['name'],
'amount' => $tax['amount'],
'sort_order' => $sort_order,
]);
$sort_order++;
}
}
// Added bill total
BillTotal::create([
'company_id' => $request['company_id'],
'bill_id' => $bill->id,
'code' => 'total',
'name' => 'bills.total',
'amount' => $sub_total + $tax_total,
'sort_order' => $sort_order,
]);
}
}

View File

@ -0,0 +1,214 @@
<?php
namespace App\Jobs\Expense;
use App\Models\Common\Item;
use App\Models\Expense\BillItem;
use App\Models\Expense\BillItemTax;
use App\Models\Setting\Tax;
use App\Notifications\Common\Item as ItemNotification;
use App\Notifications\Common\ItemReminder as ItemReminderNotification;
use Illuminate\Foundation\Bus\Dispatchable;
class CreateBillItem
{
use Dispatchable;
protected $data;
protected $bill;
protected $discount;
/**
* Create a new job instance.
*
* @param $data
* @param $bill
* @param $discount
*/
public function __construct($data, $bill, $discount = null)
{
$this->data = $data;
$this->bill = $bill;
$this->discount = $discount;
}
/**
* Execute the job.
*
* @return BillItem
*/
public function handle()
{
$item_sku = '';
$item_id = !empty($this->data['item_id']) ? $this->data['item_id'] : 0;
$item_amount = (double) $this->data['price'] * (double) $this->data['quantity'];
$item_discount_amount = $item_amount;
// Apply discount to tax
if ($this->discount) {
$item_discount_amount = $item_amount - ($item_amount * ($this->discount / 100));
}
if (!empty($item_id)) {
$item_object = Item::find($item_id);
$this->data['name'] = $item_object->name;
$item_sku = $item_object->sku;
// Increase stock (item bought)
$item_object->quantity += (double) $this->data['quantity'];
$item_object->save();
} elseif (!empty($this->data['sku'])) {
$item_sku = $this->data['sku'];
}
$tax_amount = 0;
$item_taxes = [];
$item_tax_total = 0;
if (!empty($this->data['tax_id'])) {
$inclusives = $compounds = $taxes = [];
foreach ((array) $this->data['tax_id'] as $tax_id) {
$tax = Tax::find($tax_id);
switch ($tax->type) {
case 'included':
$inclusives[] = $tax;
break;
case 'compound':
$compounds[] = $tax;
break;
case 'normal':
default:
$taxes[] = $tax;
$tax_amount = ($item_discount_amount / 100) * $tax->rate;
$item_taxes[] = [
'company_id' => $this->bill->company_id,
'bill_id' => $this->bill->id,
'tax_id' => $tax_id,
'name' => $tax->name,
'amount' => $tax_amount,
];
$item_tax_total += $tax_amount;
break;
}
}
if ($inclusives) {
if ($this->discount) {
$item_tax_total = 0;
if ($taxes) {
foreach ($taxes as $tax) {
$item_tax_amount = ($item_amount / 100) * $tax->rate;
$item_tax_total += $item_tax_amount;
}
}
foreach ($inclusives as $inclusive) {
$item_sub_and_tax_total = $item_amount + $item_tax_total;
$item_tax_total = $item_sub_and_tax_total - (($item_sub_and_tax_total * (100 - $inclusive->rate)) / 100);
$item_sub_total = $item_sub_and_tax_total - $item_tax_total;
$item_taxes[] = [
'company_id' => $this->bill->company_id,
'bill_id' => $this->bill->id,
'tax_id' => $inclusive->id,
'name' => $inclusive->name,
'amount' => $tax_amount,
];
$item_discount_amount = $item_sub_total - ($item_sub_total * ($this->discount / 100));
}
} else {
foreach ($inclusives as $inclusive) {
$item_sub_and_tax_total = $item_discount_amount + $item_tax_total;
$item_tax_total = $tax_amount = $item_sub_and_tax_total - ($item_sub_and_tax_total / (1 + ($inclusive->rate / 100)));
$item_taxes[] = [
'company_id' => $this->bill->company_id,
'bill_id' => $this->bill->id,
'tax_id' => $inclusive->id,
'name' => $inclusive->name,
'amount' => $tax_amount,
];
$item_amount = $item_sub_and_tax_total - $item_tax_total;
}
}
}
if ($compounds) {
foreach ($compounds as $compound) {
$tax_amount = (($item_discount_amount + $item_tax_total) / 100) * $compound->rate;
$item_tax_total += $tax_amount;
$item_taxes[] = [
'company_id' => $this->bill->company_id,
'bill_id' => $this->bill->id,
'tax_id' => $compound->id,
'name' => $compound->name,
'amount' => $tax_amount,
];
}
}
}
$bill_item = BillItem::create([
'company_id' => $this->bill->company_id,
'bill_id' => $this->bill->id,
'item_id' => $item_id,
'name' => str_limit($this->data['name'], 180, ''),
'sku' => $item_sku,
'quantity' => (double) $this->data['quantity'],
'price' => (double) $this->data['price'],
'tax' => $item_tax_total,
'tax_id' => 0,
'total' => $item_amount,
]);
$bill_item->item_taxes = false;
$bill_item->inclusives = false;
$bill_item->compounds = false;
// set item_taxes for
if (!empty($this->data['tax_id'])) {
$bill_item->item_taxes = $item_taxes;
$bill_item->inclusives = $inclusives;
$bill_item->compounds = $compounds;
}
if ($item_taxes) {
foreach ($item_taxes as $item_tax) {
$item_tax['bill_item_id'] = $bill_item->id;
BillItemTax::create($item_tax);
// Set taxes
if (isset($taxes) && array_key_exists($item_tax['tax_id'], $taxes)) {
$taxes[$item_tax['tax_id']]['amount'] += $item_tax['amount'];
} else {
$taxes[$item_tax['tax_id']] = [
'name' => $item_tax['name'],
'amount' => $item_tax['amount']
];
}
}
}
return $bill_item;
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace App\Jobs\Expense;
use App\Models\Expense\BillHistory;
use App\Models\Expense\BillPayment;
use Illuminate\Foundation\Bus\Dispatchable;
class CreateBillPayment
{
use Dispatchable;
protected $request;
protected $bill;
/**
* Create a new job instance.
*
* @param $request
* @param $bill
*/
public function __construct($request, $bill)
{
$this->request = $request;
$this->bill = $bill;
}
/**
* Execute the job.
*
* @return BillPayment
*/
public function handle()
{
$bill_payment = BillPayment::create($this->request->input());
$desc_amount = money((float) $bill_payment->amount, (string) $bill_payment->currency_code, true)->format();
$history_data = [
'company_id' => $bill_payment->company_id,
'bill_id' => $bill_payment->bill_id,
'status_code' => $this->bill->bill_status_code,
'notify' => '0',
'description' => $desc_amount . ' ' . trans_choice('general.payments', 1),
];
BillHistory::create($history_data);
return $bill_payment;
}
}

View File

@ -0,0 +1,232 @@
<?php
namespace App\Jobs\Expense;
use App\Events\BillUpdated;
use App\Models\Common\Item;
use App\Models\Expense\Bill;
use App\Models\Expense\BillTotal;
use App\Traits\Currencies;
use App\Traits\DateTime;
use App\Traits\Uploads;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Foundation\Bus\Dispatchable;
class UpdateBill
{
use Currencies, DateTime, Dispatchable, Uploads;
protected $bill;
protected $request;
/**
* Create a new job instance.
*
* @param $request
*/
public function __construct($bill, $request)
{
$this->bill = $bill;
$this->request = $request;
}
/**
* Execute the job.
*
* @return Bill
*/
public function handle()
{
// Upload attachment
if ($this->request->file('attachment')) {
$media = $this->getMedia($this->request->file('attachment'), 'bills');
$this->bill->attachMedia($media, 'attachment');
}
$taxes = [];
$tax_total = 0;
$sub_total = 0;
$discount_total = 0;
$discount = $this->request['discount'];
if ($this->request['item']) {
$items = $this->bill->items;
if ($items) {
foreach ($items as $item) {
if (empty($item->item_id)) {
continue;
}
$item_object = Item::find($item->item_id);
// Decrease stock
$item_object->quantity -= (double) $item->quantity;
$item_object->save();
}
}
$this->deleteRelationships($this->bill, 'items');
foreach ($this->request['item'] as $item) {
$bill_item = dispatch(new CreateBillItem($item, $this->bill, $discount));
// Calculate totals
$tax_total += $bill_item->tax;
$sub_total += $bill_item->total;
// Set taxes
if ($bill_item->item_taxes) {
foreach ($bill_item->item_taxes as $item_tax) {
if (isset($taxes) && array_key_exists($item_tax['tax_id'], $taxes)) {
$taxes[$item_tax['tax_id']]['amount'] += $item_tax['amount'];
} else {
$taxes[$item_tax['tax_id']] = [
'name' => $item_tax['name'],
'amount' => $item_tax['amount']
];
}
}
}
}
}
$s_total = $sub_total;
// Apply discount to total
if ($discount) {
$s_discount = $s_total * ($discount / 100);
$discount_total += $s_discount;
$s_total = $s_total - $s_discount;
}
$amount = $s_total + $tax_total;
$this->request['amount'] = money($amount, $this->request['currency_code'])->getAmount();
$this->bill->update($this->request->input());
// Delete previous bill totals
$this->deleteRelationships($this->bill, 'totals');
// Add bill totals
$this->addTotals($this->bill, $this->request, $taxes, $sub_total, $discount_total, $tax_total);
// Recurring
$this->bill->updateRecurring();
// Fire the event to make it extensible
event(new BillUpdated($this->bill));
return $this->bill;
}
protected function addTotals($bill, $request, $taxes, $sub_total, $discount_total, $tax_total)
{
// Check if totals are in request, i.e. api
if (!empty($request['totals'])) {
$sort_order = 1;
foreach ($request['totals'] as $total) {
$total['bill_id'] = $bill->id;
if (empty($total['sort_order'])) {
$total['sort_order'] = $sort_order;
}
BillTotal::create($total);
$sort_order++;
}
return;
}
$sort_order = 1;
// Added bill sub total
BillTotal::create([
'company_id' => $request['company_id'],
'bill_id' => $bill->id,
'code' => 'sub_total',
'name' => 'bills.sub_total',
'amount' => $sub_total,
'sort_order' => $sort_order,
]);
$sort_order++;
// Added bill discount
if ($discount_total) {
BillTotal::create([
'company_id' => $request['company_id'],
'bill_id' => $bill->id,
'code' => 'discount',
'name' => 'bills.discount',
'amount' => $discount_total,
'sort_order' => $sort_order,
]);
// This is for total
$sub_total = $sub_total - $discount_total;
$sort_order++;
}
// Added bill taxes
if (isset($taxes)) {
foreach ($taxes as $tax) {
BillTotal::create([
'company_id' => $request['company_id'],
'bill_id' => $bill->id,
'code' => 'tax',
'name' => $tax['name'],
'amount' => $tax['amount'],
'sort_order' => $sort_order,
]);
$sort_order++;
}
}
// Added bill total
BillTotal::create([
'company_id' => $request['company_id'],
'bill_id' => $bill->id,
'code' => 'total',
'name' => 'bills.total',
'amount' => $sub_total + $tax_total,
'sort_order' => $sort_order,
]);
}
/**
* Mass delete relationships with events being fired.
*
* @param $model
* @param $relationships
*
* @return void
*/
public function deleteRelationships($model, $relationships)
{
foreach ((array) $relationships as $relationship) {
if (empty($model->$relationship)) {
continue;
}
$items = $model->$relationship->all();
if ($items instanceof Collection) {
$items = $items->all();
}
foreach ((array) $items as $item) {
$item->delete();
}
}
}
}

View File

@ -0,0 +1,195 @@
<?php
namespace App\Jobs\Income;
use App\Events\InvoiceCreated;
use App\Models\Income\Invoice;
use App\Models\Income\InvoiceHistory;
use App\Models\Income\InvoiceTotal;
use App\Traits\Currencies;
use App\Traits\DateTime;
use App\Traits\Incomes;
use App\Traits\Uploads;
use Illuminate\Foundation\Bus\Dispatchable;
class CreateInvoice
{
use Currencies, DateTime, Dispatchable, Incomes, Uploads;
protected $request;
/**
* Create a new job instance.
*
* @param $request
*/
public function __construct($request)
{
$this->request = $request;
}
/**
* Execute the job.
*
* @return Invoice
*/
public function handle()
{
$invoice = Invoice::create($this->request->input());
// Upload attachment
if ($this->request->file('attachment')) {
$media = $this->getMedia($this->request->file('attachment'), 'invoices');
$invoice->attachMedia($media, 'attachment');
}
$taxes = [];
$tax_total = 0;
$sub_total = 0;
$discount_total = 0;
$discount = $this->request['discount'];
if ($this->request['item']) {
foreach ($this->request['item'] as $item) {
$invoice_item = dispatch(new CreateInvoiceItem($item, $invoice, $discount));
// Calculate totals
$tax_total += $invoice_item->tax;
$sub_total += $invoice_item->total;
// Set taxes
if ($invoice_item->item_taxes) {
foreach ($invoice_item->item_taxes as $item_tax) {
if (isset($taxes) && array_key_exists($item_tax['tax_id'], $taxes)) {
$taxes[$item_tax['tax_id']]['amount'] += $item_tax['amount'];
} else {
$taxes[$item_tax['tax_id']] = [
'name' => $item_tax['name'],
'amount' => $item_tax['amount']
];
}
}
}
}
}
$s_total = $sub_total;
// Apply discount to total
if ($discount) {
$s_discount = $s_total * ($discount / 100);
$discount_total += $s_discount;
$s_total = $s_total - $s_discount;
}
$amount = $s_total + $tax_total;
$this->request['amount'] = money($amount, $this->request['currency_code'])->getAmount();
$invoice->update($this->request->input());
// Add invoice totals
$this->addTotals($invoice, $this->request, $taxes, $sub_total, $discount_total, $tax_total);
// Add invoice history
InvoiceHistory::create([
'company_id' => session('company_id'),
'invoice_id' => $invoice->id,
'status_code' => 'draft',
'notify' => 0,
'description' => trans('messages.success.added', ['type' => $invoice->invoice_number]),
]);
// Update next invoice number
$this->increaseNextInvoiceNumber();
// Recurring
$invoice->createRecurring();
// Fire the event to make it extensible
event(new InvoiceCreated($invoice));
return $invoice;
}
protected function addTotals($invoice, $request, $taxes, $sub_total, $discount_total, $tax_total)
{
// Check if totals are in request, i.e. api
if (!empty($request['totals'])) {
$sort_order = 1;
foreach ($request['totals'] as $total) {
$total['invoice_id'] = $invoice->id;
if (empty($total['sort_order'])) {
$total['sort_order'] = $sort_order;
}
InvoiceTotal::create($total);
$sort_order++;
}
return;
}
$sort_order = 1;
// Added invoice sub total
InvoiceTotal::create([
'company_id' => $request['company_id'],
'invoice_id' => $invoice->id,
'code' => 'sub_total',
'name' => 'invoices.sub_total',
'amount' => $sub_total,
'sort_order' => $sort_order,
]);
$sort_order++;
// Added invoice discount
if ($discount_total) {
InvoiceTotal::create([
'company_id' => $request['company_id'],
'invoice_id' => $invoice->id,
'code' => 'discount',
'name' => 'invoices.discount',
'amount' => $discount_total,
'sort_order' => $sort_order,
]);
// This is for total
$sub_total = $sub_total - $discount_total;
$sort_order++;
}
// Added invoice taxes
if (isset($taxes)) {
foreach ($taxes as $tax) {
InvoiceTotal::create([
'company_id' => $request['company_id'],
'invoice_id' => $invoice->id,
'code' => 'tax',
'name' => $tax['name'],
'amount' => $tax['amount'],
'sort_order' => $sort_order,
]);
$sort_order++;
}
}
// Added invoice total
InvoiceTotal::create([
'company_id' => $request['company_id'],
'invoice_id' => $invoice->id,
'code' => 'total',
'name' => 'invoices.total',
'amount' => $sub_total + $tax_total,
'sort_order' => $sort_order,
]);
}
}

View File

@ -0,0 +1,241 @@
<?php
namespace App\Jobs\Income;
use App\Models\Common\Item;
use App\Models\Income\InvoiceItem;
use App\Models\Income\InvoiceItemTax;
use App\Models\Setting\Tax;
use App\Notifications\Common\Item as ItemNotification;
use App\Notifications\Common\ItemReminder as ItemReminderNotification;
use Illuminate\Foundation\Bus\Dispatchable;
class CreateInvoiceItem
{
use Dispatchable;
protected $data;
protected $invoice;
protected $discount;
/**
* Create a new job instance.
*
* @param $data
* @param $invoice
* @param $discount
*/
public function __construct($data, $invoice, $discount = null)
{
$this->data = $data;
$this->invoice = $invoice;
$this->discount = $discount;
}
/**
* Execute the job.
*
* @return InvoiceItem
*/
public function handle()
{
$item_sku = '';
$item_id = !empty($this->data['item_id']) ? $this->data['item_id'] : 0;
$item_amount = (double) $this->data['price'] * (double) $this->data['quantity'];
$item_discount_amount = $item_amount;
// Apply discount to tax
if ($this->discount) {
$item_discount_amount = $item_amount - ($item_amount * ($this->discount / 100));
}
if (!empty($item_id)) {
$item_object = Item::find($item_id);
$this->data['name'] = $item_object->name;
$item_sku = $item_object->sku;
// Decrease stock (item sold)
$item_object->quantity -= (double) $this->data['quantity'];
$item_object->save();
if (setting('general.send_item_reminder')) {
$item_stocks = explode(',', setting('general.schedule_item_stocks'));
foreach ($item_stocks as $item_stock) {
if ($item_object->quantity == $item_stock) {
foreach ($item_object->company->users as $user) {
if (!$user->can('read-notifications')) {
continue;
}
$user->notify(new ItemReminderNotification($item_object));
}
}
}
}
// Notify users if out of stock
if ($item_object->quantity == 0) {
foreach ($item_object->company->users as $user) {
if (!$user->can('read-notifications')) {
continue;
}
$user->notify(new ItemNotification($item_object));
}
}
} elseif (!empty($this->data['sku'])) {
$item_sku = $this->data['sku'];
}
$tax_amount = 0;
$item_taxes = [];
$item_tax_total = 0;
if (!empty($this->data['tax_id'])) {
$inclusives = $compounds = $taxes = [];
foreach ((array) $this->data['tax_id'] as $tax_id) {
$tax = Tax::find($tax_id);
switch ($tax->type) {
case 'inclusive':
$inclusives[] = $tax;
break;
case 'compound':
$compounds[] = $tax;
break;
case 'normal':
default:
$taxes[] = $tax;
$tax_amount = ($item_discount_amount / 100) * $tax->rate;
$item_taxes[] = [
'company_id' => $this->invoice->company_id,
'invoice_id' => $this->invoice->id,
'tax_id' => $tax_id,
'name' => $tax->name,
'amount' => $tax_amount,
];
$item_tax_total += $tax_amount;
break;
}
}
if ($inclusives) {
if ($this->discount) {
$item_tax_total = 0;
if ($taxes) {
foreach ($taxes as $tax) {
$item_tax_amount = ($item_amount / 100) * $tax->rate;
$item_tax_total += $item_tax_amount;
}
}
foreach ($inclusives as $inclusive) {
$item_sub_and_tax_total = $item_amount + $item_tax_total;
$item_tax_total = $item_sub_and_tax_total - (($item_sub_and_tax_total * (100 - $inclusive->rate)) / 100);
$item_sub_total = $item_sub_and_tax_total - $item_tax_total;
$item_taxes[] = [
'company_id' => $this->invoice->company_id,
'invoice_id' => $this->invoice->id,
'tax_id' => $inclusive->id,
'name' => $inclusive->name,
'amount' => $tax_amount,
];
$item_discount_amount = $item_sub_total - ($item_sub_total * ($this->discount / 100));
}
} else {
foreach ($inclusives as $inclusive) {
$item_sub_and_tax_total = $item_discount_amount + $item_tax_total;
$item_tax_total = $tax_amount = $item_sub_and_tax_total - ($item_sub_and_tax_total / (1 + ($inclusive->rate / 100)));
$item_taxes[] = [
'company_id' => $this->invoice->company_id,
'invoice_id' => $this->invoice->id,
'tax_id' => $inclusive->id,
'name' => $inclusive->name,
'amount' => $tax_amount,
];
$item_amount = $item_sub_and_tax_total - $item_tax_total;
}
}
}
if ($compounds) {
foreach ($compounds as $compound) {
$tax_amount = (($item_discount_amount + $item_tax_total) / 100) * $compound->rate;
$item_tax_total += $tax_amount;
$item_taxes[] = [
'company_id' => $this->invoice->company_id,
'invoice_id' => $this->invoice->id,
'tax_id' => $compound->id,
'name' => $compound->name,
'amount' => $tax_amount,
];
}
}
}
$invoice_item = InvoiceItem::create([
'company_id' => $this->invoice->company_id,
'invoice_id' => $this->invoice->id,
'item_id' => $item_id,
'name' => str_limit($this->data['name'], 180, ''),
'sku' => $item_sku,
'quantity' => (double) $this->data['quantity'],
'price' => (double) $this->data['price'],
'tax' => $item_tax_total,
'tax_id' => 0,
'total' => $item_amount,
]);
$invoice_item->item_taxes = false;
$invoice_item->inclusives = false;
$invoice_item->compounds = false;
// set item_taxes for
if (!empty($this->data['tax_id'])) {
$invoice_item->item_taxes = $item_taxes;
$invoice_item->inclusives = $inclusives;
$invoice_item->compounds = $compounds;
}
if ($item_taxes) {
foreach ($item_taxes as $item_tax) {
$item_tax['invoice_item_id'] = $invoice_item->id;
InvoiceItemTax::create($item_tax);
// Set taxes
if (isset($taxes) && array_key_exists($item_tax['tax_id'], $taxes)) {
$taxes[$item_tax['tax_id']]['amount'] += $item_tax['amount'];
} else {
$taxes[$item_tax['tax_id']] = [
'name' => $item_tax['name'],
'amount' => $item_tax['amount']
];
}
}
}
return $invoice_item;
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace App\Jobs\Income;
use App\Models\Income\InvoiceHistory;
use App\Models\Income\InvoicePayment;
use Illuminate\Foundation\Bus\Dispatchable;
class CreateInvoicePayment
{
use Dispatchable;
protected $request;
protected $invoice;
/**
* Create a new job instance.
*
* @param $request
* @param $invoice
*/
public function __construct($request, $invoice)
{
$this->request = $request;
$this->invoice = $invoice;
}
/**
* Execute the job.
*
* @return InvoicePayment
*/
public function handle()
{
$invoice_payment = InvoicePayment::create($this->request->input());
$desc_amount = money((float) $invoice_payment->amount, (string) $invoice_payment->currency_code, true)->format();
$history_data = [
'company_id' => $invoice_payment->company_id,
'invoice_id' => $invoice_payment->invoice_id,
'status_code' => $this->invoice->invoice_status_code,
'notify' => '0',
'description' => $desc_amount . ' ' . trans_choice('general.payments', 1),
];
InvoiceHistory::create($history_data);
return $invoice_payment;
}
}

View File

@ -0,0 +1,233 @@
<?php
namespace App\Jobs\Income;
use App\Events\InvoiceUpdated;
use App\Models\Common\Item;
use App\Models\Income\Invoice;
use App\Models\Income\InvoiceTotal;
use App\Traits\Currencies;
use App\Traits\DateTime;
use App\Traits\Incomes;
use App\Traits\Uploads;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Foundation\Bus\Dispatchable;
class UpdateInvoice
{
use Currencies, DateTime, Dispatchable, Incomes, Uploads;
protected $invoice;
protected $request;
/**
* Create a new job instance.
*
* @param $request
*/
public function __construct($invoice, $request)
{
$this->invoice = $invoice;
$this->request = $request;
}
/**
* Execute the job.
*
* @return Invoice
*/
public function handle()
{
// Upload attachment
if ($this->request->file('attachment')) {
$media = $this->getMedia($this->request->file('attachment'), 'invoices');
$this->invoice->attachMedia($media, 'attachment');
}
$taxes = [];
$tax_total = 0;
$sub_total = 0;
$discount_total = 0;
$discount = $this->request['discount'];
if ($this->request['item']) {
$items = $this->invoice->items;
if ($items) {
foreach ($items as $item) {
if (empty($item->item_id)) {
continue;
}
$item_object = Item::find($item->item_id);
// Increase stock
$item_object->quantity += (double) $item->quantity;
$item_object->save();
}
}
$this->deleteRelationships($this->invoice, 'items');
foreach ($this->request['item'] as $item) {
$invoice_item = dispatch(new CreateInvoiceItem($item, $this->invoice, $discount));
// Calculate totals
$tax_total += $invoice_item->tax;
$sub_total += $invoice_item->total;
// Set taxes
if ($invoice_item->item_taxes) {
foreach ($invoice_item->item_taxes as $item_tax) {
if (isset($taxes) && array_key_exists($item_tax['tax_id'], $taxes)) {
$taxes[$item_tax['tax_id']]['amount'] += $item_tax['amount'];
} else {
$taxes[$item_tax['tax_id']] = [
'name' => $item_tax['name'],
'amount' => $item_tax['amount']
];
}
}
}
}
}
$s_total = $sub_total;
// Apply discount to total
if ($discount) {
$s_discount = $s_total * ($discount / 100);
$discount_total += $s_discount;
$s_total = $s_total - $s_discount;
}
$amount = $s_total + $tax_total;
$this->request['amount'] = money($amount, $this->request['currency_code'])->getAmount();
$this->invoice->update($this->request->input());
// Delete previous invoice totals
$this->deleteRelationships($this->invoice, 'totals');
// Add invoice totals
$this->addTotals($this->invoice, $this->request, $taxes, $sub_total, $discount_total, $tax_total);
// Recurring
$this->invoice->updateRecurring();
// Fire the event to make it extensible
event(new InvoiceUpdated($this->invoice));
return $this->invoice;
}
protected function addTotals($invoice, $request, $taxes, $sub_total, $discount_total, $tax_total)
{
// Check if totals are in request, i.e. api
if (!empty($request['totals'])) {
$sort_order = 1;
foreach ($request['totals'] as $total) {
$total['invoice_id'] = $invoice->id;
if (empty($total['sort_order'])) {
$total['sort_order'] = $sort_order;
}
InvoiceTotal::create($total);
$sort_order++;
}
return;
}
$sort_order = 1;
// Added invoice sub total
InvoiceTotal::create([
'company_id' => $request['company_id'],
'invoice_id' => $invoice->id,
'code' => 'sub_total',
'name' => 'invoices.sub_total',
'amount' => $sub_total,
'sort_order' => $sort_order,
]);
$sort_order++;
// Added invoice discount
if ($discount_total) {
InvoiceTotal::create([
'company_id' => $request['company_id'],
'invoice_id' => $invoice->id,
'code' => 'discount',
'name' => 'invoices.discount',
'amount' => $discount_total,
'sort_order' => $sort_order,
]);
// This is for total
$sub_total = $sub_total - $discount_total;
$sort_order++;
}
// Added invoice taxes
if (isset($taxes)) {
foreach ($taxes as $tax) {
InvoiceTotal::create([
'company_id' => $request['company_id'],
'invoice_id' => $invoice->id,
'code' => 'tax',
'name' => $tax['name'],
'amount' => $tax['amount'],
'sort_order' => $sort_order,
]);
$sort_order++;
}
}
// Added invoice total
InvoiceTotal::create([
'company_id' => $request['company_id'],
'invoice_id' => $invoice->id,
'code' => 'total',
'name' => 'invoices.total',
'amount' => $sub_total + $tax_total,
'sort_order' => $sort_order,
]);
}
/**
* Mass delete relationships with events being fired.
*
* @param $model
* @param $relationships
*
* @return void
*/
public function deleteRelationships($model, $relationships)
{
foreach ((array) $relationships as $relationship) {
if (empty($model->$relationship)) {
continue;
}
$items = $model->$relationship->all();
if ($items instanceof Collection) {
$items = $items->all();
}
foreach ((array) $items as $item) {
$item->delete();
}
}
}
}

View File

@ -3,11 +3,9 @@
namespace App\Listeners\Incomes\Invoice;
use App\Events\InvoicePaid;
use App\Models\Income\Invoice;
use App\Models\Income\InvoicePayment;
use App\Models\Income\InvoiceHistory;
use App\Http\Requests\Income\InvoicePayment as PaymentRequest;
use App\Jobs\Income\CreateInvoicePayment;
use App\Notifications\Customer\Invoice as Notification;
use App\Traits\DateTime;
use Date;
@ -19,24 +17,14 @@ class Paid
* Handle the event.
*
* @param $event
* @return void
* @return array
*/
public function handle(InvoicePaid $event)
{
$invoice = $event->invoice;
$request = $event->request;
$request['invoice_id'] = $invoice->id;
$request['account_id'] = setting('general.default_account');
if (!isset($request['amount'])) {
$request['amount'] = $invoice->amount;
}
$request['currency_code'] = $invoice->currency_code;
$request['currency_rate'] = $invoice->currency_rate;
$request['paid_at'] = Date::parse('now')->format('Y-m-d');
$invoice_payment = $this->createPayment($invoice, $request);
if ($request['amount'] > $invoice->amount) {
$message = trans('messages.error.added', ['type' => trans_choice('general.payment', 1)]);
@ -53,23 +41,41 @@ class Paid
$invoice->save();
InvoicePayment::create($request->input());
// Customer add payment on invoice send user notification
foreach ($invoice->company->users as $user) {
if (!$user->can('read-notifications')) {
continue;
}
$request['status_code'] = $invoice->invoice_status_code;
$request['notify'] = 0;
$desc_date = Date::parse($request['paid_at'])->format($this->getCompanyDateFormat());
$desc_amount = money((float) $request['amount'], $request['currency_code'], true)->format();
$request['description'] = $desc_date . ' ' . $desc_amount;
InvoiceHistory::create($request->input());
$user->notify(new Notification($invoice, $invoice_payment));
}
return [
'success' => true,
'error' => false,
];
}
protected function createPayment($invoice, $request)
{
if (!is_array($request)) {
$request = $request->input();
}
$request['invoice_id'] = $invoice->id;
$request['paid_at'] = Date::parse('now')->format('Y-m-d');
$request['company_id'] = isset($request['company_id']) ? $request['company_id'] : session('company_id');
$request['account_id'] = isset($request['account_id']) ? $request['account_id'] : setting('general.default_account');
$request['payment_method'] = isset($request['payment_method']) ? $request['payment_method'] : setting('general.default_payment_method');
$request['currency_code'] = isset($request['currency_code']) ? $request['currency_code'] : $invoice->currency_code;
$request['currency_rate'] = isset($request['currency_rate']) ? $request['currency_rate'] : $invoice->currency_rate;
$request['notify'] = isset($request['notify']) ? $request['notify'] : 0;
$payment_request = new PaymentRequest();
$payment_request->merge($request);
$invoice_payment = dispatch(new CreateInvoicePayment($payment_request, $invoice));
return $invoice_payment;
}
}

View File

@ -0,0 +1,164 @@
<?php
namespace App\Listeners\Updates;
use App\Events\UpdateFinished;
use App\Models\Auth\Role;
use App\Models\Auth\Permission;
use Artisan;
class Version130 extends Listener
{
const ALIAS = 'core';
const VERSION = '1.3.0';
/**
* Handle the event.
*
* @param $event
* @return void
*/
public function handle(UpdateFinished $event)
{
// Check if should listen
if (!$this->check($event)) {
return;
}
// Set new Item Reminder settings
setting(['general.send_item_reminder' => '0']);
setting(['general.schedule_item_stocks' => '3,5,7']);
setting(['general.wizard' => '1']);
setting(['general.invoice_item' => 'settings.invoice.item']);
setting(['general.invoice_price' => 'settings.invoice.price']);
setting(['general.invoice_quantity' => 'settings.invoice.quantity']);
setting()->save();
$this->updatePermissions();
// Update database
Artisan::call('migrate', ['--force' => true]);
}
protected function updatePermissions()
{
$permissions = [];
// Banking Reconciliations
$permissions[] = Permission::firstOrCreate([
'name' => 'read-banking-reconciliations',
'display_name' => 'Read Banking Reconciliations',
'description' => 'Read Banking Reconciliations',
]);
$permissions[] = Permission::firstOrCreate([
'name' => 'create-banking-reconciliations',
'display_name' => 'Create Banking Reconciliations',
'description' => 'Create Banking Reconciliations',
]);
$permissions[] = Permission::firstOrCreate([
'name' => 'update-banking-reconciliations',
'display_name' => 'Update Banking Reconciliations',
'description' => 'Update Banking Reconciliations',
]);
$permissions[] = Permission::firstOrCreate([
'name' => 'delete-banking-reconciliations',
'display_name' => 'Delete Banking Reconciliations',
'description' => 'Delete Banking Reconciliations',
]);
// Create Wizard Permissions
$permissions[] = Permission::firstOrCreate([
'name' => 'create-wizard-companies',
'display_name' => 'Create Wizard Compaines',
'description' => 'Create Wizard Compaines',
]);
$permissions[] = Permission::firstOrCreate([
'name' => 'create-wizard-currencies',
'display_name' => 'Create Wizard Currencies',
'description' => 'Create Wizard Currencies',
]);
$permissions[] = Permission::firstOrCreate([
'name' => 'create-wizard-taxes',
'display_name' => 'Create Wizard Taxes',
'description' => 'Create Wizard Taxes',
]);
$permissions[] = Permission::firstOrCreate([
'name' => 'create-wizard-finish',
'display_name' => 'Create Wizard Finish',
'description' => 'Create Wizard Finish',
]);
// Read Wizard Permissions
$permissions[] = Permission::firstOrCreate([
'name' => 'read-wizard-companies',
'display_name' => 'Read Wizard Compaines',
'description' => 'Read Wizard Compaines',
]);
$permissions[] = Permission::firstOrCreate([
'name' => 'read-wizard-currencies',
'display_name' => 'Read Wizard Currencies',
'description' => 'Read Wizard Currencies',
]);
$permissions[] = Permission::firstOrCreate([
'name' => 'read-wizard-taxes',
'display_name' => 'Read Wizard Taxes',
'description' => 'Read Wizard Taxes',
]);
$permissions[] = Permission::firstOrCreate([
'name' => 'read-wizard-finish',
'display_name' => 'Read Wizard Finish',
'description' => 'Read Wizard Finish',
]);
// Update Wizard Permissions
$permissions[] = Permission::firstOrCreate([
'name' => 'update-wizard-companies',
'display_name' => 'Update Wizard Compaines',
'description' => 'Update Wizard Compaines',
]);
$permissions[] = Permission::firstOrCreate([
'name' => 'update-wizard-currencies',
'display_name' => 'Update Wizard Currencies',
'description' => 'Update Wizard Currencies',
]);
$permissions[] = Permission::firstOrCreate([
'name' => 'update-wizard-taxes',
'display_name' => 'Update Wizard Taxes',
'description' => 'Update Wizard Taxes',
]);
$permissions[] = Permission::firstOrCreate([
'name' => 'update-wizard-finish',
'display_name' => 'Update Wizard Finish',
'description' => 'Update Wizard Finish',
]);
// Attach permission to roles
$roles = Role::all();
foreach ($roles as $role) {
$allowed = ['admin', 'manager'];
if (!in_array($role->name, $allowed)) {
continue;
}
foreach ($permissions as $permission) {
$role->attachPermission($permission);
}
}
}
}

View File

@ -0,0 +1,79 @@
<?php
namespace App\Listeners\Updates;
use App\Events\UpdateFinished;
use App\Models\Auth\Role;
use App\Models\Auth\Permission;
use Artisan;
class Version132 extends Listener
{
const ALIAS = 'core';
const VERSION = '1.3.2';
/**
* Handle the event.
*
* @param $event
* @return void
*/
public function handle(UpdateFinished $event)
{
// Check if should listen
if (!$this->check($event)) {
return;
}
$this->updatePermissions();
// Update database
Artisan::call('migrate', ['--force' => true]);
}
protected function updatePermissions()
{
$permissions = [];
// Banking Reconciliations
$permissions[] = Permission::firstOrCreate([
'name' => 'read-common-notifications',
'display_name' => 'Read Common Notifications',
'description' => 'Read Common Notifications',
]);
$permissions[] = Permission::firstOrCreate([
'name' => 'create-common-notifications',
'display_name' => 'Create Common Notifications',
'description' => 'Create Common Notifications',
]);
$permissions[] = Permission::firstOrCreate([
'name' => 'update-common-notifications',
'display_name' => 'Update Common Notifications',
'description' => 'Update Common Notifications',
]);
$permissions[] = Permission::firstOrCreate([
'name' => 'delete-common-notifications',
'display_name' => 'Delete Common Notifications',
'description' => 'Delete Common Notifications',
]);
// Attach permission to roles
$roles = Role::all();
foreach ($roles as $role) {
$allowed = ['admin', 'manager'];
if (!in_array($role->name, $allowed)) {
continue;
}
foreach ($permissions as $permission) {
$role->attachPermission($permission);
}
}
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace App\Models\Banking;
use App\Models\Model;
use Sofa\Eloquence\Eloquence;
class Reconciliation extends Model
{
use Eloquence;
protected $table = 'reconciliations';
protected $dates = ['deleted_at', 'started_at', 'ended_at'];
/**
* Attributes that should be mass-assignable.
*
* @var array
*/
protected $fillable = ['company_id', 'account_id', 'started_at', 'ended_at', 'closing_balance', 'reconciled'];
/**
* Sortable columns.
*
* @var array
*/
public $sortable = ['created_at', 'account_id', 'started_at', 'ended_at', 'closing_balance', 'reconciled'];
public function account()
{
return $this->belongsTo('App\Models\Banking\Account');
}
/**
* Convert closing balance to double.
*
* @param string $value
* @return void
*/
public function setClosingBalanceAttribute($value)
{
$this->attributes['closing_balance'] = (double) $value;
}
}

View File

@ -3,6 +3,7 @@
namespace App\Models\Expense;
use App\Models\Model;
use App\Models\Setting\Currency;
use App\Traits\Currencies;
use App\Traits\DateTime;
use App\Traits\Media;
@ -22,7 +23,7 @@ class Bill extends Model
*
* @var array
*/
protected $appends = ['attachment', 'discount'];
protected $appends = ['attachment', 'discount', 'paid'];
protected $dates = ['deleted_at', 'billed_at', 'due_at'];
@ -82,6 +83,11 @@ class Bill extends Model
return $this->hasMany('App\Models\Expense\BillItem');
}
public function itemTaxes()
{
return $this->hasMany('App\Models\Expense\BillItemTax');
}
public function payments()
{
return $this->hasMany('App\Models\Expense\BillPayment');
@ -194,4 +200,59 @@ class Bill extends Model
return $percent;
}
/**
* Get the paid amount.
*
* @return string
*/
public function getPaidAttribute()
{
if (empty($this->amount)) {
return false;
}
$paid = 0;
$reconciled = $reconciled_amount = 0;
if ($this->payments->count()) {
$currencies = Currency::enabled()->pluck('rate', 'code')->toArray();
foreach ($this->payments as $item) {
if ($this->currency_code == $item->currency_code) {
$amount = (double) $item->amount;
} else {
$default_model = new BillPayment();
$default_model->default_currency_code = $this->currency_code;
$default_model->amount = $item->amount;
$default_model->currency_code = $item->currency_code;
$default_model->currency_rate = $currencies[$item->currency_code];
$default_amount = (double) $default_model->getDivideConvertedAmount();
$convert_model = new BillPayment();
$convert_model->default_currency_code = $item->currency_code;
$convert_model->amount = $default_amount;
$convert_model->currency_code = $this->currency_code;
$convert_model->currency_rate = $currencies[$this->currency_code];
$amount = (double) $convert_model->getDynamicConvertedAmount();
}
$paid += $amount;
if ($item->reconciled) {
$reconciled_amount = +$amount;
}
}
}
if ($this->amount == $reconciled_amount) {
$reconciled = 1;
}
$this->setAttribute('reconciled', $reconciled);
return $paid;
}
}

View File

@ -29,6 +29,11 @@ class BillItem extends Model
return $this->belongsTo('App\Models\Common\Item');
}
public function itemTaxes()
{
return $this->hasMany('App\Models\Expense\BillItemTax', 'bill_item_id', 'id');
}
public function tax()
{
return $this->belongsTo('App\Models\Setting\Tax');
@ -66,4 +71,23 @@ class BillItem extends Model
{
$this->attributes['tax'] = (double) $value;
}
/**
* Convert tax to double.
*
* @param string $value
* @return void
*/
public function getTaxIdAttribute($value)
{
$tax_ids = [];
if (!empty($value)) {
$tax_ids[] = $value;
return $tax_ids;
}
return $this->itemTaxes->pluck('tax_id');
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace App\Models\Expense;
use App\Models\Model;
use App\Traits\Currencies;
class BillItemTax extends Model
{
use Currencies;
protected $table = 'bill_item_taxes';
/**
* Attributes that should be mass-assignable.
*
* @var array
*/
protected $fillable = ['company_id', 'bill_id', 'bill_item_id', 'tax_id', 'name', 'amount'];
public function bill()
{
return $this->belongsTo('App\Models\Expense\Bill');
}
public function item()
{
return $this->belongsTo('App\Models\Common\Item');
}
public function tax()
{
return $this->belongsTo('App\Models\Setting\Tax');
}
/**
* Convert amount to double.
*
* @param string $value
* @return void
*/
public function setAmountAttribute($value)
{
$this->attributes['amount'] = (double) $value;
}
}

View File

@ -4,12 +4,13 @@ namespace App\Models\Expense;
use App\Models\Model;
use Bkwld\Cloner\Cloneable;
use App\Traits\Currencies;
use Sofa\Eloquence\Eloquence;
use App\Traits\Media;
class Vendor extends Model
{
use Cloneable, Eloquence, Media;
use Cloneable, Currencies, Eloquence, Media;
protected $table = 'vendors';
@ -18,7 +19,7 @@ class Vendor extends Model
*
* @var array
*/
protected $fillable = ['company_id', 'name', 'email', 'tax_number', 'phone', 'address', 'website', 'currency_code', 'enabled'];
protected $fillable = ['company_id', 'name', 'email', 'tax_number', 'phone', 'address', 'website', 'currency_code', 'reference', 'enabled'];
/**
* Sortable columns.
@ -70,4 +71,19 @@ class Vendor extends Model
return $this->getMedia('logo')->last();
}
public function getUnpaidAttribute()
{
$amount = 0;
$bills = $this->bills()->accrued()->notPaid()->get();
foreach ($bills as $bill) {
$bill_amount = $bill->amount - $bill->paid;
$amount += $this->dynamicConvert(setting('general.default_currency'), $bill_amount, $bill->currency_code, $bill->currency_rate, false);
}
return $amount;
}
}

View File

@ -4,12 +4,13 @@ namespace App\Models\Income;
use App\Models\Model;
use Bkwld\Cloner\Cloneable;
use App\Traits\Currencies;
use Illuminate\Notifications\Notifiable;
use Sofa\Eloquence\Eloquence;
class Customer extends Model
{
use Cloneable, Eloquence, Notifiable;
use Cloneable, Currencies, Eloquence, Notifiable;
protected $table = 'customers';
@ -18,7 +19,7 @@ class Customer extends Model
*
* @var array
*/
protected $fillable = ['company_id', 'user_id', 'name', 'email', 'tax_number', 'phone', 'address', 'website', 'currency_code', 'enabled'];
protected $fillable = ['company_id', 'user_id', 'name', 'email', 'tax_number', 'phone', 'address', 'website', 'currency_code', 'reference', 'enabled'];
/**
* Sortable columns.
@ -64,4 +65,19 @@ class Customer extends Model
{
$this->user_id = null;
}
public function getUnpaidAttribute()
{
$amount = 0;
$invoices = $this->invoices()->accrued()->notPaid()->get();
foreach ($invoices as $invoice) {
$invoice_amount = $invoice->amount - $invoice->paid;
$amount += $this->dynamicConvert(setting('general.default_currency'), $invoice_amount, $invoice->currency_code, $invoice->currency_rate, false);
}
return $amount;
}
}

View File

@ -3,6 +3,7 @@
namespace App\Models\Income;
use App\Models\Model;
use App\Models\Setting\Currency;
use App\Traits\Currencies;
use App\Traits\DateTime;
use App\Traits\Incomes;
@ -23,7 +24,7 @@ class Invoice extends Model
*
* @var array
*/
protected $appends = ['attachment', 'discount'];
protected $appends = ['attachment', 'discount', 'paid'];
protected $dates = ['deleted_at', 'invoiced_at', 'due_at'];
@ -56,6 +57,8 @@ class Invoice extends Model
'notes' => 2,
];
protected $reconciled_amount = [];
/**
* Clonable relationships.
*
@ -83,6 +86,11 @@ class Invoice extends Model
return $this->hasMany('App\Models\Income\InvoiceItem');
}
public function itemTaxes()
{
return $this->hasMany('App\Models\Income\InvoiceItemTax');
}
public function histories()
{
return $this->hasMany('App\Models\Income\InvoiceHistory');
@ -196,4 +204,59 @@ class Invoice extends Model
return $percent;
}
/**
* Get the paid amount.
*
* @return string
*/
public function getPaidAttribute()
{
if (empty($this->amount)) {
return false;
}
$paid = 0;
$reconciled = $reconciled_amount = 0;
if ($this->payments->count()) {
$currencies = Currency::enabled()->pluck('rate', 'code')->toArray();
foreach ($this->payments as $item) {
if ($this->currency_code == $item->currency_code) {
$amount = (double) $item->amount;
} else {
$default_model = new InvoicePayment();
$default_model->default_currency_code = $this->currency_code;
$default_model->amount = $item->amount;
$default_model->currency_code = $item->currency_code;
$default_model->currency_rate = $currencies[$item->currency_code];
$default_amount = (double) $default_model->getDivideConvertedAmount();
$convert_model = new InvoicePayment();
$convert_model->default_currency_code = $item->currency_code;
$convert_model->amount = $default_amount;
$convert_model->currency_code = $this->currency_code;
$convert_model->currency_rate = $currencies[$this->currency_code];
$amount = (double) $convert_model->getDynamicConvertedAmount();
}
$paid += $amount;
if ($item->reconciled) {
$reconciled_amount = +$amount;
}
}
}
if ($this->amount == $reconciled_amount) {
$reconciled = 1;
}
$this->setAttribute('reconciled', $reconciled);
return $paid;
}
}

View File

@ -29,6 +29,11 @@ class InvoiceItem extends Model
return $this->belongsTo('App\Models\Common\Item');
}
public function itemTaxes()
{
return $this->hasMany('App\Models\Income\InvoiceItemTax', 'invoice_item_id', 'id');
}
public function tax()
{
return $this->belongsTo('App\Models\Setting\Tax');
@ -66,4 +71,23 @@ class InvoiceItem extends Model
{
$this->attributes['tax'] = (double) $value;
}
/**
* Convert tax to double.
*
* @param string $value
* @return void
*/
public function getTaxIdAttribute($value)
{
$tax_ids = [];
if (!empty($value)) {
$tax_ids[] = $value;
return $tax_ids;
}
return $this->itemTaxes->pluck('tax_id');
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace App\Models\Income;
use App\Models\Model;
use App\Traits\Currencies;
class InvoiceItemTax extends Model
{
use Currencies;
protected $table = 'invoice_item_taxes';
/**
* Attributes that should be mass-assignable.
*
* @var array
*/
protected $fillable = ['company_id', 'invoice_id', 'invoice_item_id', 'tax_id', 'name', 'amount'];
public function invoice()
{
return $this->belongsTo('App\Models\Income\Invoice');
}
public function item()
{
return $this->belongsTo('App\Models\Common\Item');
}
public function tax()
{
return $this->belongsTo('App\Models\Setting\Tax');
}
/**
* Convert amount to double.
*
* @param string $value
* @return void
*/
public function setAmountAttribute($value)
{
$this->attributes['amount'] = (double) $value;
}
}

View File

@ -115,4 +115,43 @@ class Model extends Eloquent
{
return $query->where('enabled', 0);
}
/**
* Scope to only include reconciled models.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param $value
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeReconciled($query, $value = 1)
{
return $query->where('reconciled', $value);
}
public function scopeAccount($query, $accounts)
{
if (empty($accounts)) {
return;
}
return $query->whereIn('account_id', (array) $accounts);
}
public function scopeCustomer($query, $customers)
{
if (empty($customers)) {
return;
}
return $query->whereIn('customer_id', (array) $customers);
}
public function scopeVendor($query, $vendors)
{
if (empty($vendors)) {
return;
}
return $query->whereIn('vendor_id', (array) $vendors);
}
}

View File

@ -56,7 +56,7 @@ class Category extends Model
*/
public function scopeType($query, $type)
{
return $query->where('type', $type);
return $query->whereIn('type', (array) $type);
}
/**

View File

@ -81,7 +81,7 @@ class Currency extends Model
*/
public function getPrecisionAttribute($value)
{
if (empty($value)) {
if (is_null($value)) {
return config('money.' . $this->code . '.precision');
}
@ -95,7 +95,7 @@ class Currency extends Model
*/
public function getSymbolAttribute($value)
{
if (empty($value)) {
if (is_null($value)) {
return config('money.' . $this->code . '.symbol');
}
@ -109,7 +109,7 @@ class Currency extends Model
*/
public function getSymbolFirstAttribute($value)
{
if (empty($value)) {
if (is_null($value)) {
return config('money.' . $this->code . '.symbol_first');
}
@ -123,7 +123,7 @@ class Currency extends Model
*/
public function getDecimalMarkAttribute($value)
{
if (empty($value)) {
if (is_null($value)) {
return config('money.' . $this->code . '.decimal_mark');
}
@ -137,7 +137,7 @@ class Currency extends Model
*/
public function getThousandsSeparatorAttribute($value)
{
if (empty($value)) {
if (is_null($value)) {
return config('money.' . $this->code . '.thousands_separator');
}

View File

@ -21,7 +21,7 @@ class Tax extends Model
*
* @var array
*/
protected $fillable = ['company_id', 'name', 'rate', 'enabled'];
protected $fillable = ['company_id', 'name', 'rate', 'type', 'enabled'];
/**
* Sortable columns.

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