Merge pull request #325 from akaunting/1.2-dev

1.2 dev
This commit is contained in:
Cüneyt Şentürk 2018-05-03 15:44:31 +03:00 committed by GitHub
commit 37ea9dcae2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
450 changed files with 10179 additions and 6209 deletions

View File

@ -0,0 +1,192 @@
<?php
namespace App\Console\Commands;
use App\Models\Company\Company;
use App\Models\Expense\BillHistory;
use App\Models\Income\InvoiceHistory;
use App\Notifications\Expense\Bill as BillNotification;
use App\Notifications\Income\Invoice as InvoiceNotification;
use App\Traits\Incomes;
use App\Utilities\Overrider;
use Date;
use Illuminate\Console\Command;
use Recurr\Rule;
use Recurr\Transformer\ArrayTransformer;
use Recurr\Transformer\ArrayTransformerConfig;
class RecurringCheck extends Command
{
use Incomes;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'recurring:check';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Check for recurring';
/**
* Create a new command instance.
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$this->today = Date::today();
// Get all companies
$companies = Company::all();
foreach ($companies as $company) {
// Set company id
session(['company_id' => $company->id]);
// Override settings and currencies
Overrider::load('settings');
Overrider::load('currencies');
$company->setSettings();
foreach ($company->recurring as $recur) {
if (!$current = $recur->current()) {
continue;
}
$current_date = Date::parse($current->format('Y-m-d'));
// Check if should recur today
if ($this->today->ne($current_date)) {
continue;
}
$model = $recur->recurable;
if (!$model) {
continue;
}
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();
$clone->parent_id = $model->id;
$clone->paid_at = $this->today->format('Y-m-d');
$clone->save();
break;
}
}
}
// Unset company_id
session()->forget('company_id');
}
protected function recurInvoice($company, $model)
{
$model->cloneable_relations = ['items', 'totals'];
// Create new record
$clone = $model->duplicate();
// Set original invoice id
$clone->parent_id = $model->id;
// Days between invoiced and due date
$diff_days = Date::parse($clone->due_at)->diffInDays(Date::parse($clone->invoiced_at));
// Update dates
$clone->invoiced_at = $this->today->format('Y-m-d');
$clone->due_at = $this->today->addDays($diff_days)->format('Y-m-d');
$clone->save();
// Add invoice history
InvoiceHistory::create([
'company_id' => session('company_id'),
'invoice_id' => $clone->id,
'status_code' => 'draft',
'notify' => 0,
'description' => trans('messages.success.added', ['type' => $clone->invoice_number]),
]);
// Notify the customer
if ($clone->customer && !empty($clone->customer_email)) {
$clone->customer->notify(new InvoiceNotification($clone));
}
// Notify all users assigned to this company
foreach ($company->users as $user) {
if (!$user->can('read-notifications')) {
continue;
}
$user->notify(new InvoiceNotification($clone));
}
// Update next invoice number
$this->increaseNextInvoiceNumber();
}
protected function recurBill($company, $model)
{
$model->cloneable_relations = ['items', 'totals'];
// Create new record
$clone = $model->duplicate();
// Set original bill id
$clone->parent_id = $model->id;
// Days between invoiced and due date
$diff_days = Date::parse($clone->due_at)->diffInDays(Date::parse($clone->invoiced_at));
// Update dates
$clone->billed_at = $this->today->format('Y-m-d');
$clone->due_at = $this->today->addDays($diff_days)->format('Y-m-d');
$clone->save();
// Add bill history
BillHistory::create([
'company_id' => session('company_id'),
'bill_id' => $clone->id,
'status_code' => 'draft',
'notify' => 0,
'description' => trans('messages.success.added', ['type' => $clone->bill_number]),
]);
// Notify all users assigned to this company
foreach ($company->users as $user) {
if (!$user->can('read-notifications')) {
continue;
}
$user->notify(new BillNotification($clone));
}
}
}

View File

@ -18,6 +18,7 @@ class Kernel extends ConsoleKernel
Commands\Install::class,
Commands\InvoiceReminder::class,
Commands\ModuleInstall::class,
Commands\RecurringCheck::class,
];
/**
@ -35,6 +36,7 @@ class Kernel extends ConsoleKernel
$schedule->command('reminder:invoice')->dailyAt(setting('general.schedule_time', '09:00'));
$schedule->command('reminder:bill')->dailyAt(setting('general.schedule_time', '09:00'));
$schedule->command('recurring:check')->dailyAt(setting('general.schedule_time', '09:00'));
}
/**

View File

@ -67,6 +67,7 @@ class Bills extends ApiController
$item_id = $item['item_id'];
$item['name'] = $item_object->name;
$item_sku = $item_object->sku;
// Increase stock (item bought)
@ -143,6 +144,7 @@ class Bills extends ApiController
$item_id = $item['item_id'];
$item['name'] = $item_object->name;
$item_sku = $item_object->sku;
} elseif (!empty($item['sku'])) {
$item_sku = $item['sku'];

View File

@ -36,11 +36,18 @@ class Invoices extends ApiController
/**
* Display the specified resource.
*
* @param Invoice $invoice
* @param $id
* @return \Dingo\Api\Http\Response
*/
public function show(Invoice $invoice)
public function show($id)
{
// Check if we're querying by id or number
if (is_numeric($id)) {
$invoice = Invoice::find($id);
} else {
$invoice = Invoice::where('invoice_number', $id)->first();
}
return $this->response->item($invoice, new Transformer());
}
@ -76,6 +83,7 @@ class Invoices extends ApiController
$item_id = $item['item_id'];
$item['name'] = $item_object->name;
$item_sku = $item_object->sku;
// Decrease stock (item sold)
@ -194,6 +202,7 @@ class Invoices extends ApiController
$item_id = $item['item_id'];
$item['name'] = $item_object->name;
$item_sku = $item_object->sku;
} elseif (!empty($item['sku'])) {
$item_sku = $item['sku'];

View File

@ -22,6 +22,16 @@ class Accounts extends Controller
return view('banking.accounts.index', compact('accounts'));
}
/**
* Show the form for viewing the specified resource.
*
* @return Response
*/
public function show()
{
return redirect('banking/accounts');
}
/**
* Show the form for creating a new resource.
*
@ -84,19 +94,34 @@ class Accounts extends Controller
*/
public function update(Account $account, Request $request)
{
$account->update($request->all());
// Set default account
if ($request['default_account']) {
setting()->set('general.default_account', $account->id);
setting()->save();
// Check if we can disable it
if (!$request['enabled']) {
if ($account->id == setting('general.default_account')) {
$relationships[] = strtolower(trans_choice('general.companies', 1));
}
}
$message = trans('messages.success.updated', ['type' => trans_choice('general.accounts', 1)]);
if (empty($relationships)) {
$account->update($request->all());
flash($message)->success();
// Set default account
if ($request['default_account']) {
setting()->set('general.default_account', $account->id);
setting()->save();
}
return redirect('banking/accounts');
$message = trans('messages.success.updated', ['type' => trans_choice('general.accounts', 1)]);
flash($message)->success();
return redirect('banking/accounts');
} else {
$message = trans('messages.warning.disabled', ['name' => $account->name, 'text' => implode(', ', $relationships)]);
flash($message)->warning();
return redirect('banking/accounts/' . $account->id . '/edit');
}
}
/**
@ -115,6 +140,10 @@ class Accounts extends Controller
'revenues' => 'revenues',
]);
if ($account->id == setting('general.default_account')) {
$relationships[] = strtolower(trans_choice('general.companies', 1));
}
if (empty($relationships)) {
$account->delete();

View File

@ -72,6 +72,16 @@ class Transfers extends Controller
return view('banking.transfers.index', compact('transfers', 'items', 'accounts'));
}
/**
* Show the form for viewing the specified resource.
*
* @return Response
*/
public function show()
{
return redirect('banking/transfers');
}
/**
* Show the form for creating a new resource.
*

View File

@ -28,6 +28,17 @@ class Companies extends Controller
return view('companies.companies.index', compact('companies'));
}
/**
* Show the form for viewing the specified resource.
*
* @return Response
*/
public function show()
{
return redirect('companies/companies');
}
/**
* Show the form for creating a new resource.
*

View File

@ -243,52 +243,63 @@ class Dashboard extends Controller
private function calculateAmounts()
{
$incomes_amount = $expenses_amount = 0;
$incomes_amount = $open_invoice = $overdue_invoice = 0;
$expenses_amount = $open_bill = $overdue_bill = 0;
// Invoices
$invoices = Invoice::with('payments')->accrued()->get();
list($invoice_paid_amount, $open_invoice, $overdue_invoice) = $this->calculateTotals($invoices, 'invoice');
$incomes_amount += $invoice_paid_amount;
// Add to Incomes By Category
$this->addToIncomeDonut('#00c0ef', $invoice_paid_amount, trans_choice('general.invoices', 2));
// Bills
$bills = Bill::with('payments')->accrued()->get();
list($bill_paid_amount, $open_bill, $overdue_bill) = $this->calculateTotals($bills, 'bill');
$expenses_amount += $bill_paid_amount;
// Add to Expenses By Category
$this->addToExpenseDonut('#dd4b39', $bill_paid_amount, trans_choice('general.bills', 2));
// Revenues & Payments
$categories = Category::orWhere('type', 'income')->orWhere('type', 'expense')->enabled()->get();
// Get categories
$categories = Category::with(['bills', 'invoices', 'payments', 'revenues'])->orWhere('type', 'income')->orWhere('type', 'expense')->enabled()->get();
foreach ($categories as $category) {
switch ($category->type) {
case 'income':
$amount = 0;
// Revenues
foreach ($category->revenues as $revenue) {
$amount += $revenue->getConvertedAmount();
}
$incomes_amount += $amount;
// Invoices
$invoices = $category->invoices()->accrued()->get();
foreach ($invoices as $invoice) {
list($paid, $open, $overdue) = $this->calculateInvoiceBillTotals($invoice, 'invoice');
$incomes_amount += $paid;
$open_invoice += $open;
$overdue_invoice += $overdue;
$amount += $paid;
}
$this->addToIncomeDonut($category->color, $amount, $category->name);
$incomes_amount += $amount;
break;
case 'expense':
$amount = 0;
// Payments
foreach ($category->payments as $payment) {
$amount += $payment->getConvertedAmount();
}
$expenses_amount += $amount;
// Bills
$bills = $category->bills()->accrued()->get();
foreach ($bills as $bill) {
list($paid, $open, $overdue) = $this->calculateInvoiceBillTotals($bill, 'bill');
$expenses_amount += $paid;
$open_bill += $open;
$overdue_bill += $overdue;
$amount += $paid;
}
$this->addToExpenseDonut($category->color, $amount, $category->name);
$expenses_amount += $amount;
break;
}
}
@ -359,6 +370,10 @@ class Dashboard extends Controller
$i = Date::parse($item->paid_at)->quarter;
}
if (!isset($totals[$i])) {
continue;
}
$totals[$i] += $item->getConvertedAmount();
}
}
@ -378,22 +393,19 @@ class Dashboard extends Controller
return $profit;
}
private function calculateTotals($items, $type)
private function calculateInvoiceBillTotals($item, $type)
{
$paid = $open = $overdue = 0;
$today = $this->today->toDateString();
foreach ($items as $item) {
$paid += $item->getConvertedAmount();
$paid += $item->getConvertedAmount();
$code_field = $type . '_status_code';
if ($item->$code_field == 'paid') {
continue;
}
$code_field = $type . '_status_code';
if ($item->$code_field != 'paid') {
$payments = 0;
if ($item->$code_field == 'partial') {
foreach ($item->payments as $payment) {
$payments += $payment->getConvertedAmount();

View File

@ -100,9 +100,11 @@ class Bills extends Controller
$items = Item::enabled()->pluck('name', 'id');
$taxes = Tax::enabled()->pluck('name', 'id');
$taxes = Tax::enabled()->get()->pluck('title', 'id');
return view('expenses.bills.create', compact('vendors', 'currencies', 'items', 'taxes'));
$categories = Category::enabled()->type('expense')->pluck('name', 'id');
return view('expenses.bills.create', compact('vendors', 'currencies', 'items', 'taxes', 'categories'));
}
/**
@ -146,6 +148,8 @@ class Bills extends Controller
$tax_total = 0;
$sub_total = 0;
$discount_total = 0;
$discount = $request['discount'];
$bill_item = [];
$bill_item['company_id'] = $request['company_id'];
@ -159,6 +163,7 @@ class Bills extends Controller
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)
@ -173,17 +178,22 @@ class Bills extends Controller
$tax_id = $item['tax_id'];
$tax = (($item['price'] * $item['quantity']) / 100) * $tax_object->rate;
$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'] = $item['quantity'];
$bill_item['price'] = $item['price'];
$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'] = $item['price'] * $item['quantity'];
$bill_item['total'] = (double) $item['price'] * (double) $item['quantity'];
BillItem::create($bill_item);
@ -207,12 +217,21 @@ class Bills extends Controller
}
}
$request['amount'] += $sub_total + $tax_total;
$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;
}
$request['amount'] = $s_total + $tax_total;
$bill->update($request->input());
// Add bill totals
$this->addTotals($bill, $request, $taxes, $sub_total, $tax_total);
$this->addTotals($bill, $request, $taxes, $sub_total, $discount_total, $tax_total);
// Add bill history
BillHistory::create([
@ -223,6 +242,9 @@ class Bills extends Controller
'description' => trans('messages.success.added', ['type' => $bill->bill_number]),
]);
// Recurring
$bill->createRecurring();
// Fire the event to make it extendible
event(new BillCreated($bill));
@ -300,9 +322,11 @@ class Bills extends Controller
$items = Item::enabled()->pluck('name', 'id');
$taxes = Tax::enabled()->pluck('name', 'id');
$taxes = Tax::enabled()->get()->pluck('title', 'id');
return view('expenses.bills.edit', compact('bill', 'vendors', 'currencies', 'items', 'taxes'));
$categories = Category::enabled()->type('expense')->pluck('name', 'id');
return view('expenses.bills.edit', compact('bill', 'vendors', 'currencies', 'items', 'taxes', 'categories'));
}
/**
@ -333,6 +357,8 @@ class Bills extends Controller
$taxes = [];
$tax_total = 0;
$sub_total = 0;
$discount_total = 0;
$discount = $request['discount'];
$bill_item = [];
$bill_item['company_id'] = $request['company_id'];
@ -348,6 +374,7 @@ class Bills extends Controller
if (!empty($item['item_id'])) {
$item_object = Item::find($item['item_id']);
$item['name'] = $item_object->name;
$item_sku = $item_object->sku;
}
@ -358,17 +385,22 @@ class Bills extends Controller
$tax_id = $item['tax_id'];
$tax = (($item['price'] * $item['quantity']) / 100) * $tax_object->rate;
$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'] = $item['quantity'];
$bill_item['price'] = $item['price'];
$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'] = $item['price'] * $item['quantity'];
$bill_item['total'] = (double) $item['price'] * (double) $item['quantity'];
if (isset($tax_object)) {
if (array_key_exists($tax_object->id, $taxes)) {
@ -388,7 +420,16 @@ class Bills extends Controller
}
}
$request['amount'] = $sub_total + $tax_total;
$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;
}
$request['amount'] = $s_total + $tax_total;
$bill->update($request->input());
@ -403,7 +444,11 @@ class Bills extends Controller
BillTotal::where('bill_id', $bill->id)->delete();
// Add bill totals
$this->addTotals($bill, $request, $taxes, $sub_total, $tax_total);
$bill->totals()->delete();
$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));
@ -424,6 +469,7 @@ class Bills extends Controller
*/
public function destroy(Bill $bill)
{
$bill->recurring()->delete();
$bill->delete();
/*
@ -472,9 +518,7 @@ class Bills extends Controller
{
$bill = $this->prepareBill($bill);
$logo = $this->getLogo($bill);
return view($bill->template_path, compact('bill', 'logo'));
return view($bill->template_path, compact('bill'));
}
/**
@ -488,9 +532,7 @@ class Bills extends Controller
{
$bill = $this->prepareBill($bill);
$logo = $this->getLogo($bill);
$html = view($bill->template_path, compact('bill', 'logo'))->render();
$html = view($bill->template_path, compact('bill'))->render();
$pdf = \App::make('dompdf.wrapper');
$pdf->loadHTML($html);
@ -536,7 +578,7 @@ class Bills extends Controller
}
if ($amount > $total_amount) {
$message = trans('messages.error.payment_add');
$message = trans('messages.error.over_payment');
return response()->json([
'success' => false,
@ -569,7 +611,7 @@ class Bills extends Controller
BillHistory::create($request->input());
$message = trans('messages.success.added', ['type' => trans_choice('general.revenues', 1)]);
$message = trans('messages.success.added', ['type' => trans_choice('general.payments', 1)]);
return response()->json([
'success' => true,
@ -589,25 +631,23 @@ class Bills extends Controller
{
$bill = Bill::find($payment->bill_id);
$status = 'received';
if ($bill->payments()->count() > 1) {
$bill->bill_status_code = 'partial';
} else {
$bill->bill_status_code = 'received';
}
$bill->bill_status_code = $status;
$bill->save();
$desc_amount = money((float) $payment->amount, (string) $payment->currency_code, true)->format();
$description = $desc_amount . ' ' . trans_choice('general.payments', 1);
// Add invoice history
// Add bill history
BillHistory::create([
'company_id' => $bill->company_id,
'invoice_id' => $bill->id,
'status_code' => $status,
'bill_id' => $bill->id,
'status_code' => $bill->bill_status_code,
'notify' => 0,
'description' => trans('messages.success.deleted', ['type' => $description]),
]);
@ -640,11 +680,11 @@ class Bills extends Controller
return $bill;
}
protected function addTotals($bill, $request, $taxes, $sub_total, $tax_total)
protected function addTotals($bill, $request, $taxes, $sub_total, $discount_total, $tax_total)
{
$sort_order = 1;
// Added bill total sub total
// Added bill sub total
BillTotal::create([
'company_id' => $request['company_id'],
'bill_id' => $bill->id,
@ -656,7 +696,24 @@ class Bills extends Controller
$sort_order++;
// Added bill total taxes
// 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([
@ -672,7 +729,7 @@ class Bills extends Controller
}
}
// Added bill total total
// Added bill total
BillTotal::create([
'company_id' => $request['company_id'],
'bill_id' => $bill->id,
@ -682,39 +739,4 @@ class Bills extends Controller
'sort_order' => $sort_order,
]);
}
protected function getLogo($bill)
{
$logo = '';
$media_id = setting('general.company_logo');
if (isset($bill->vendor->logo) && !empty($bill->vendor->logo->id)) {
$media_id = $bill->vendor->logo->id;
}
$media = Media::find($media_id);
if (!empty($media)) {
$path = Storage::path($media->getDiskPath());
if (!is_file($path)) {
return $logo;
}
} else {
$path = asset('public/img/company.png');
}
$image = Image::make($path)->encode()->getEncoded();
if (empty($image)) {
return $logo;
}
$extension = File::extension($path);
$logo = 'data:image/' . $extension . ';base64,' . base64_encode($image);
return $logo;
}
}

View File

@ -40,6 +40,16 @@ class Payments extends Controller
return view('expenses.payments.index', compact('payments', 'vendors', 'categories', 'accounts', 'transfer_cat_id'));
}
/**
* Show the form for viewing the specified resource.
*
* @return Response
*/
public function show()
{
return redirect('expenses/payments');
}
/**
* Show the form for creating a new resource.
*
@ -86,6 +96,9 @@ class Payments extends Controller
$payment->attachMedia($media, 'attachment');
}
// Recurring
$payment->createRecurring();
$message = trans('messages.success.added', ['type' => trans_choice('general.payments', 1)]);
flash($message)->success();
@ -185,6 +198,9 @@ class Payments extends Controller
$payment->attachMedia($media, 'attachment');
}
// Recurring
$payment->updateRecurring();
$message = trans('messages.success.updated', ['type' => trans_choice('general.payments', 1)]);
flash($message)->success();
@ -206,6 +222,7 @@ class Payments extends Controller
return redirect('expenses/payments');
}
$payment->recurring()->delete();
$payment->delete();
$message = trans('messages.success.deleted', ['type' => trans_choice('general.payments', 1)]);

View File

@ -4,10 +4,16 @@ namespace App\Http\Controllers\Expenses;
use App\Http\Controllers\Controller;
use App\Http\Requests\Expense\Vendor as Request;
use App\Models\Expense\Bill;
use App\Models\Expense\Payment;
use App\Models\Expense\Vendor;
use App\Models\Setting\Currency;
use App\Traits\Uploads;
use App\Utilities\ImportFile;
use Date;
use Illuminate\Pagination\Paginator;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
class Vendors extends Controller
{
@ -25,6 +31,78 @@ class Vendors extends Controller
return view('expenses.vendors.index', compact('vendors'));
}
/**
* Show the form for viewing the specified resource.
*
* @param Vendor $vendor
*
* @return Response
*/
public function show(Vendor $vendor)
{
$amounts = [
'paid' => 0,
'open' => 0,
'overdue' => 0,
];
$counts = [
'bills' => 0,
'payments' => 0,
];
// Handle bills
$bills = Bill::with(['status', 'payments'])->where('vendor_id', $vendor->id)->get();
$counts['bills'] = $bills->count();
$bill_payments = [];
$today = Date::today()->toDateString();
foreach ($bills as $item) {
$payments = 0;
foreach ($item->payments as $payment) {
$payment->category = $item->category;
$bill_payments[] = $payment;
$amount = $payment->getConvertedAmount();
$amounts['paid'] += $amount;
$payments += $amount;
}
if ($item->bill_status_code == 'paid') {
continue;
}
// Check if it's open or overdue invoice
if ($item->due_at > $today) {
$amounts['open'] += $item->getConvertedAmount() - $payments;
} else {
$amounts['overdue'] += $item->getConvertedAmount() - $payments;
}
}
// Handle payments
$payments = Payment::with(['account', 'category'])->where('vendor_id', $vendor->id)->get();
$counts['payments'] = $payments->count();
// Prepare data
$items = collect($payments)->each(function ($item) use (&$amounts) {
$amounts['paid'] += $item->getConvertedAmount();
});
$limit = request('limit', setting('general.list_limit', '25'));
$transactions = $this->paginate($items->merge($bill_payments)->sortByDesc('paid_at'), $limit);
return view('expenses.vendors.show', compact('vendor', 'counts', 'amounts', 'transactions'));
}
/**
* Show the form for creating a new resource.
*
@ -206,4 +284,23 @@ class Vendors extends Controller
return response()->json($vendor);
}
/**
* Generate a pagination collection.
*
* @param array|Collection $items
* @param int $perPage
* @param int $page
* @param array $options
*
* @return LengthAwarePaginator
*/
public function paginate($items, $perPage = 15, $page = null, $options = [])
{
$page = $page ?: (Paginator::resolveCurrentPage() ?: 1);
$items = $items instanceof Collection ? $items : Collection::make($items);
return new LengthAwarePaginator($items->forPage($page, $perPage), $items->count(), $perPage, $page, $options);
}
}

View File

@ -4,11 +4,17 @@ namespace App\Http\Controllers\Incomes;
use App\Http\Controllers\Controller;
use App\Http\Requests\Income\Customer as Request;
use Illuminate\Http\Request as FRequest;
use App\Models\Auth\User;
use App\Models\Income\Customer;
use App\Models\Income\Invoice;
use App\Models\Income\Revenue;
use App\Models\Setting\Currency;
use App\Utilities\ImportFile;
use Date;
use Illuminate\Http\Request as FRequest;
use Illuminate\Pagination\Paginator;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
class Customers extends Controller
{
@ -25,6 +31,78 @@ class Customers extends Controller
return view('incomes.customers.index', compact('customers', 'emails'));
}
/**
* Show the form for viewing the specified resource.
*
* @param Customer $customer
*
* @return Response
*/
public function show(Customer $customer)
{
$amounts = [
'paid' => 0,
'open' => 0,
'overdue' => 0,
];
$counts = [
'invoices' => 0,
'revenues' => 0,
];
// Handle invoices
$invoices = Invoice::with(['status', 'payments'])->where('customer_id', $customer->id)->get();
$counts['invoices'] = $invoices->count();
$invoice_payments = [];
$today = Date::today()->toDateString();
foreach ($invoices as $item) {
$payments = 0;
foreach ($item->payments as $payment) {
$payment->category = $item->category;
$invoice_payments[] = $payment;
$amount = $payment->getConvertedAmount();
$amounts['paid'] += $amount;
$payments += $amount;
}
if ($item->invoice_status_code == 'paid') {
continue;
}
// Check if it's open or overdue invoice
if ($item->due_at > $today) {
$amounts['open'] += $item->getConvertedAmount() - $payments;
} else {
$amounts['overdue'] += $item->getConvertedAmount() - $payments;
}
}
// Handle revenues
$revenues = Revenue::with(['account', 'category'])->where('customer_id', $customer->id)->get();
$counts['revenues'] = $revenues->count();
// Prepare data
$items = collect($revenues)->each(function ($item) use (&$amounts) {
$amounts['paid'] += $item->getConvertedAmount();
});
$limit = request('limit', setting('general.list_limit', '25'));
$transactions = $this->paginate($items->merge($invoice_payments)->sortByDesc('paid_at'), $limit);
return view('incomes.customers.show', compact('customer', 'counts', 'amounts', 'transactions'));
}
/**
* Show the form for creating a new resource.
*
@ -266,4 +344,23 @@ class Customers extends Controller
return response()->json($json);
}
/**
* Generate a pagination collection.
*
* @param array|Collection $items
* @param int $perPage
* @param int $page
* @param array $options
*
* @return LengthAwarePaginator
*/
public function paginate($items, $perPage = 15, $page = null, $options = [])
{
$page = $page ?: (Paginator::resolveCurrentPage() ?: 1);
$items = $items instanceof Collection ? $items : Collection::make($items);
return new LengthAwarePaginator($items->forPage($page, $perPage), $items->count(), $perPage, $page, $options);
}
}

View File

@ -103,11 +103,13 @@ class Invoices extends Controller
$items = Item::enabled()->pluck('name', 'id');
$taxes = Tax::enabled()->pluck('name', 'id');
$taxes = Tax::enabled()->get()->pluck('title', 'id');
$categories = Category::enabled()->type('income')->pluck('name', 'id');
$number = $this->getNextInvoiceNumber();
return view('incomes.invoices.create', compact('customers', 'currencies', 'items', 'taxes', 'number'));
return view('incomes.invoices.create', compact('customers', 'currencies', 'items', 'taxes', 'categories', 'number'));
}
/**
@ -151,6 +153,8 @@ class Invoices extends Controller
$tax_total = 0;
$sub_total = 0;
$discount_total = 0;
$discount = $request['discount'];
$invoice_item = [];
$invoice_item['company_id'] = $request['company_id'];
@ -163,6 +167,7 @@ class Invoices extends Controller
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)
@ -188,17 +193,22 @@ class Invoices extends Controller
$tax_id = $item['tax_id'];
$tax = (($item['price'] * $item['quantity']) / 100) * $tax_object->rate;
$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'] = $item['quantity'];
$invoice_item['price'] = $item['price'];
$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'] = $item['price'] * $item['quantity'];
$invoice_item['total'] = (double) $item['price'] * (double) $item['quantity'];
InvoiceItem::create($invoice_item);
@ -222,12 +232,21 @@ class Invoices extends Controller
}
}
$request['amount'] = $sub_total + $tax_total;
$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;
}
$request['amount'] = $s_total + $tax_total;
$invoice->update($request->input());
// Add invoice totals
$this->addTotals($invoice, $request, $taxes, $sub_total, $tax_total);
$this->addTotals($invoice, $request, $taxes, $sub_total, $discount_total, $tax_total);
// Add invoice history
InvoiceHistory::create([
@ -241,6 +260,9 @@ class Invoices extends Controller
// Update next invoice number
$this->increaseNextInvoiceNumber();
// Recurring
$invoice->createRecurring();
// Fire the event to make it extendible
event(new InvoiceCreated($invoice));
@ -321,9 +343,11 @@ class Invoices extends Controller
$items = Item::enabled()->pluck('name', 'id');
$taxes = Tax::enabled()->pluck('name', 'id');
$taxes = Tax::enabled()->get()->pluck('title', 'id');
return view('incomes.invoices.edit', compact('invoice', 'customers', 'currencies', 'items', 'taxes'));
$categories = Category::enabled()->type('income')->pluck('name', 'id');
return view('incomes.invoices.edit', compact('invoice', 'customers', 'currencies', 'items', 'taxes', 'categories'));
}
/**
@ -354,6 +378,8 @@ class Invoices extends Controller
$taxes = [];
$tax_total = 0;
$sub_total = 0;
$discount_total = 0;
$discount = $request['discount'];
$invoice_item = [];
$invoice_item['company_id'] = $request['company_id'];
@ -369,6 +395,7 @@ class Invoices extends Controller
if (!empty($item['item_id'])) {
$item_object = Item::find($item['item_id']);
$item['name'] = $item_object->name;
$item_sku = $item_object->sku;
}
@ -379,17 +406,22 @@ class Invoices extends Controller
$tax_id = $item['tax_id'];
$tax = (($item['price'] * $item['quantity']) / 100) * $tax_object->rate;
$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'] = $item['quantity'];
$invoice_item['price'] = $item['price'];
$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'] = $item['price'] * $item['quantity'];
$invoice_item['total'] = (double) $item['price'] * (double) $item['quantity'];
if (isset($tax_object)) {
if (array_key_exists($tax_object->id, $taxes)) {
@ -409,7 +441,16 @@ class Invoices extends Controller
}
}
$request['amount'] = $sub_total + $tax_total;
$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;
}
$request['amount'] = $s_total + $tax_total;
$invoice->update($request->input());
@ -424,7 +465,11 @@ class Invoices extends Controller
InvoiceTotal::where('invoice_id', $invoice->id)->delete();
// Add invoice totals
$this->addTotals($invoice, $request, $taxes, $sub_total, $tax_total);
$invoice->totals()->delete();
$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));
@ -445,6 +490,7 @@ class Invoices extends Controller
*/
public function destroy(Invoice $invoice)
{
$invoice->recurring()->delete();
$invoice->delete();
/*
@ -507,9 +553,7 @@ class Invoices extends Controller
$invoice = $this->prepareInvoice($invoice);
$logo = $this->getLogo();
$html = view($invoice->template_path, compact('invoice', 'logo'))->render();
$html = view($invoice->template_path, compact('invoice'))->render();
$pdf = \App::make('dompdf.wrapper');
$pdf->loadHTML($html);
@ -563,9 +607,7 @@ class Invoices extends Controller
{
$invoice = $this->prepareInvoice($invoice);
$logo = $this->getLogo();
return view($invoice->template_path, compact('invoice', 'logo'));
return view($invoice->template_path, compact('invoice'));
}
/**
@ -579,9 +621,7 @@ class Invoices extends Controller
{
$invoice = $this->prepareInvoice($invoice);
$logo = $this->getLogo();
$html = view($invoice->template_path, compact('invoice', 'logo'))->render();
$html = view($invoice->template_path, compact('invoice'))->render();
$pdf = \App::make('dompdf.wrapper');
$pdf->loadHTML($html);
@ -669,7 +709,7 @@ class Invoices extends Controller
}
if ($amount > $total_amount) {
$message = trans('messages.error.payment_add');
$message = trans('messages.error.over_payment');
return response()->json([
'success' => false,
@ -702,7 +742,7 @@ class Invoices extends Controller
InvoiceHistory::create($request->input());
$message = trans('messages.success.added', ['type' => trans_choice('general.revenues', 1)]);
$message = trans('messages.success.added', ['type' => trans_choice('general.payments', 1)]);
return response()->json([
'success' => true,
@ -722,14 +762,12 @@ class Invoices extends Controller
{
$invoice = Invoice::find($payment->invoice_id);
$status = 'sent';
if ($invoice->payments()->count() > 1) {
$status = 'partial';
$invoice->invoice_status_code = 'partial';
} else {
$invoice->invoice_status_code = 'sent';
}
$invoice->invoice_status_code = $status;
$invoice->save();
$desc_amount = money((float) $payment->amount, (string) $payment->currency_code, true)->format();
@ -740,7 +778,7 @@ class Invoices extends Controller
InvoiceHistory::create([
'company_id' => $invoice->company_id,
'invoice_id' => $invoice->id,
'status_code' => $status,
'status_code' => $invoice->invoice_status_code,
'notify' => 0,
'description' => trans('messages.success.deleted', ['type' => $description]),
]);
@ -773,11 +811,11 @@ class Invoices extends Controller
return $invoice;
}
protected function addTotals($invoice, $request, $taxes, $sub_total, $tax_total)
protected function addTotals($invoice, $request, $taxes, $sub_total, $discount_total, $tax_total)
{
$sort_order = 1;
// Added invoice total sub total
// Added invoice sub total
InvoiceTotal::create([
'company_id' => $request['company_id'],
'invoice_id' => $invoice->id,
@ -789,7 +827,24 @@ class Invoices extends Controller
$sort_order++;
// Added invoice total taxes
// 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([
@ -805,7 +860,7 @@ class Invoices extends Controller
}
}
// Added invoice total total
// Added invoice total
InvoiceTotal::create([
'company_id' => $request['company_id'],
'invoice_id' => $invoice->id,
@ -815,39 +870,4 @@ class Invoices extends Controller
'sort_order' => $sort_order,
]);
}
protected function getLogo()
{
$logo = '';
$media_id = setting('general.company_logo');
if (setting('general.invoice_logo')) {
$media_id = setting('general.invoice_logo');
}
$media = Media::find($media_id);
if (!empty($media)) {
$path = Storage::path($media->getDiskPath());
if (!is_file($path)) {
return $logo;
}
} else {
$path = asset('public/img/company.png');
}
$image = Image::make($path)->encode()->getEncoded();
if (empty($image)) {
return $logo;
}
$extension = File::extension($path);
$logo = 'data:image/' . $extension . ';base64,' . base64_encode($image);
return $logo;
}
}

View File

@ -42,6 +42,16 @@ class Revenues extends Controller
return view('incomes.revenues.index', compact('revenues', 'customers', 'categories', 'accounts', 'transfer_cat_id'));
}
/**
* Show the form for viewing the specified resource.
*
* @return Response
*/
public function show()
{
return redirect('incomes/revenues');
}
/**
* Show the form for creating a new resource.
*
@ -88,6 +98,9 @@ class Revenues extends Controller
$revenue->attachMedia($media, 'attachment');
}
// Recurring
$revenue->createRecurring();
$message = trans('messages.success.added', ['type' => trans_choice('general.revenues', 1)]);
flash($message)->success();
@ -187,6 +200,9 @@ class Revenues extends Controller
$revenue->attachMedia($media, 'attachment');
}
// Recurring
$revenue->updateRecurring();
$message = trans('messages.success.updated', ['type' => trans_choice('general.revenues', 1)]);
flash($message)->success();
@ -208,6 +224,7 @@ class Revenues extends Controller
return redirect('incomes/revenues');
}
$revenue->recurring()->delete();
$revenue->delete();
$message = trans('messages.success.deleted', ['type' => trans_choice('general.revenues', 1)]);

View File

@ -30,6 +30,16 @@ class Items extends Controller
return view('items.items.index', compact('items', 'categories'));
}
/**
* Show the form for viewing the specified resource.
*
* @return Response
*/
public function show()
{
return redirect('items/items');
}
/**
* Show the form for creating a new resource.
*
@ -39,7 +49,7 @@ class Items extends Controller
{
$categories = Category::enabled()->type('item')->pluck('name', 'id');
$taxes = Tax::enabled()->pluck('name', 'id');
$taxes = Tax::enabled()->get()->pluck('title', 'id');
return view('items.items.create', compact('categories', 'taxes'));
}
@ -123,7 +133,7 @@ class Items extends Controller
{
$categories = Category::enabled()->type('item')->pluck('name', 'id');
$taxes = Tax::enabled()->pluck('name', 'id');
$taxes = Tax::enabled()->get()->pluck('title', 'id');
return view('items.items.edit', compact('item', 'categories', 'taxes'));
}
@ -195,9 +205,10 @@ class Items extends Controller
$currency = Currency::where('code', $currency_code)->first();
$filter_data = array(
'name' => $query
);
$filter_data = [
'name' => $query,
'sku' => $query,
];
$items = Item::getItems($filter_data);
@ -235,6 +246,7 @@ class Items extends Controller
{
$input_items = request('item');
$currency_code = request('currency_code');
$discount = request('discount');
if (empty($currency_code)) {
$currency_code = setting('general.default_currency');
@ -250,7 +262,7 @@ class Items extends Controller
if ($input_items) {
foreach ($input_items as $key => $item) {
$price = (double) $item['price'];
$quantity = (int) $item['quantity'];
$quantity = (double) $item['quantity'];
$item_tax_total= 0;
$item_sub_total = ($price * $quantity);
@ -262,11 +274,15 @@ class Items extends Controller
}
$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;
$total = $item_sub_total + $item_tax_total;
$items[$key] = money($total, $currency_code, true)->format();
$items[$key] = money($item_sub_total, $currency_code, true)->format();
}
}
@ -274,8 +290,19 @@ class Items extends Controller
$json->sub_total = money($sub_total, $currency_code, true)->format();
$json->discount_text= trans('invoices.add_discount');
$json->discount_total = '';
$json->tax_total = money($tax_total, $currency_code, true)->format();
// Apply discount to total
if ($discount) {
$json->discount_text= trans('invoices.show_discount', ['discount' => $discount]);
$json->discount_total = money($sub_total * ($discount / 100), $currency_code, true)->format();
$sub_total = $sub_total - ($sub_total * ($discount / 100));
}
$grand_total = $sub_total + $tax_total;
$json->grand_total = money($grand_total, $currency_code, true)->format();

View File

@ -5,6 +5,7 @@ namespace App\Http\Controllers\Modules;
use App\Http\Controllers\Controller;
use App\Traits\Modules;
use Illuminate\Routing\Route;
use Illuminate\Http\Request;
class Tiles extends Controller
{
@ -73,4 +74,27 @@ class Tiles extends Controller
return view('modules.tiles.index', compact('title', 'modules'));
}
/**
* Show the form for viewing the specified resource.
*
* @return Response
*/
public function searchModules(Request $request)
{
$this->checkApiToken();
$keyword = $request['keyword'];
$data = [
'query' => [
'keyword' => $keyword,
]
];
$title = trans('modules.search');
$modules = $this->getSearchModules($data);
return view('modules.tiles.index', compact('title', 'modules', 'keyword'));
}
}

View File

@ -23,12 +23,7 @@ class ExpenseSummary extends Controller
$status = request('status');
//if ($filter != 'upcoming') {
$categories = Category::enabled()->type('expense')->pluck('name', 'id')->toArray();
//}
// Add Bill in Categories
$categories[0] = trans_choice('general.bills', 2);
$categories = Category::enabled()->type('expense')->pluck('name', 'id')->toArray();
// Get year
$year = request('year');
@ -49,15 +44,6 @@ class ExpenseSummary extends Controller
'currency_rate' => 1
);
// Bill
$expenses[0][$dates[$j]] = array(
'category_id' => 0,
'name' => trans_choice('general.bills', 1),
'amount' => 0,
'currency_code' => setting('general.default_currency'),
'currency_rate' => 1
);
foreach ($categories as $category_id => $category_name) {
$expenses[$category_id][$dates[$j]] = array(
'category_id' => $category_id,
@ -87,10 +73,19 @@ class ExpenseSummary extends Controller
// Payments
if ($status != 'upcoming') {
$payments = Payment::monthsOfYear('paid_at')->get();
$payments = Payment::monthsOfYear('paid_at')->isNotTransfer()->get();
$this->setAmount($expenses_graph, $totals, $expenses, $payments, 'payment', 'paid_at');
}
// Check if it's a print or normal request
if (request('print')) {
$chart_template = 'vendor.consoletvs.charts.chartjs.multi.line_print';
$view_template = 'reports.expense_summary.print';
} else {
$chart_template = 'vendor.consoletvs.charts.chartjs.multi.line';
$view_template = 'reports.expense_summary.index';
}
// Expenses chart
$chart = Charts::multi('line', 'chartjs')
->dimensions(0, 300)
@ -98,12 +93,9 @@ class ExpenseSummary extends Controller
->dataset(trans_choice('general.expenses', 1), $expenses_graph)
->labels($dates)
->credits(false)
->view('vendor.consoletvs.charts.chartjs.multi.line');
->view($chart_template);
// Expenses Graph
$expenses_graph = json_encode($expenses_graph);
return view('reports.expense_summary.index', compact('chart', 'dates', 'categories', 'expenses', 'totals'));
return view($view_template, compact('chart', 'dates', 'categories', 'expenses', 'totals'));
}
private function setAmount(&$graph, &$totals, &$expenses, $items, $type, $date_field)
@ -111,13 +103,7 @@ class ExpenseSummary extends Controller
foreach ($items as $item) {
$date = Date::parse($item->$date_field)->format('F');
if ($type == 'bill') {
$category_id = 0;
} else {
$category_id = $item->category_id;
}
if (!isset($expenses[$category_id])) {
if (!isset($expenses[$item->category_id])) {
continue;
}
@ -130,9 +116,9 @@ class ExpenseSummary extends Controller
}
}
$expenses[$category_id][$date]['amount'] += $amount;
$expenses[$category_id][$date]['currency_code'] = $item->currency_code;
$expenses[$category_id][$date]['currency_rate'] = $item->currency_rate;
$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;
$graph[Date::parse($item->$date_field)->format('F-Y')] += $amount;

View File

@ -26,19 +26,9 @@ class IncomeExpenseSummary extends Controller
$status = request('status');
//if ($filter != 'upcoming') {
$income_categories = Category::enabled()->type('income')->pluck('name', 'id')->toArray();
//}
$income_categories = Category::enabled()->type('income')->pluck('name', 'id')->toArray();
// Add Invoice in Categories
$income_categories[0] = trans_choice('general.invoices', 2);
//if ($filter != 'upcoming') {
$expense_categories = Category::enabled()->type('expense')->pluck('name', 'id')->toArray();
//}
// Add Bill in Categories
$expense_categories[0] = trans_choice('general.bills', 2);
$expense_categories = Category::enabled()->type('expense')->pluck('name', 'id')->toArray();
// Get year
$year = request('year');
@ -59,15 +49,6 @@ class IncomeExpenseSummary extends Controller
'currency_rate' => 1
);
// Compares
$compares['income'][0][$dates[$j]] = array(
'category_id' => 0,
'name' => trans_choice('general.invoices', 1),
'amount' => 0,
'currency_code' => setting('general.default_currency'),
'currency_rate' => 1
);
foreach ($income_categories as $category_id => $category_name) {
$compares['income'][$category_id][$dates[$j]] = array(
'category_id' => $category_id,
@ -78,14 +59,6 @@ class IncomeExpenseSummary extends Controller
);
}
$compares['expense'][0][$dates[$j]] = array(
'category_id' => 0,
'name' => trans_choice('general.invoices', 1),
'amount' => 0,
'currency_code' => setting('general.default_currency'),
'currency_rate' => 1
);
foreach ($expense_categories as $category_id => $category_name) {
$compares['expense'][$category_id][$dates[$j]] = array(
'category_id' => $category_id,
@ -115,7 +88,7 @@ class IncomeExpenseSummary extends Controller
// Revenues
if ($status != 'upcoming') {
$revenues = Revenue::monthsOfYear('paid_at')->get();
$revenues = Revenue::monthsOfYear('paid_at')->isNotTransfer()->get();
$this->setAmount($profit_graph, $totals, $compares, $revenues, 'revenue', 'paid_at');
}
@ -137,10 +110,19 @@ class IncomeExpenseSummary extends Controller
// Payments
if ($status != 'upcoming') {
$payments = Payment::monthsOfYear('paid_at')->get();
$payments = Payment::monthsOfYear('paid_at')->isNotTransfer()->get();
$this->setAmount($profit_graph, $totals, $compares, $payments, 'payment', 'paid_at');
}
// Check if it's a print or normal request
if (request('print')) {
$chart_template = 'vendor.consoletvs.charts.chartjs.multi.line_print';
$view_template = 'reports.income_expense_summary.print';
} else {
$chart_template = 'vendor.consoletvs.charts.chartjs.multi.line';
$view_template = 'reports.income_expense_summary.index';
}
// Profit chart
$chart = Charts::multi('line', 'chartjs')
->dimensions(0, 300)
@ -148,9 +130,9 @@ class IncomeExpenseSummary extends Controller
->dataset(trans_choice('general.profits', 1), $profit_graph)
->labels($dates)
->credits(false)
->view('vendor.consoletvs.charts.chartjs.multi.line');
->view($chart_template);
return view('reports.income_expense_summary.index', compact('chart', 'dates', 'income_categories', 'expense_categories', 'compares', 'totals'));
return view($view_template, compact('chart', 'dates', 'income_categories', 'expense_categories', 'compares', 'totals'));
}
private function setAmount(&$graph, &$totals, &$compares, $items, $type, $date_field)
@ -158,15 +140,9 @@ class IncomeExpenseSummary extends Controller
foreach ($items as $item) {
$date = Date::parse($item->$date_field)->format('F');
if (($type == 'invoice') || ($type == 'bill')) {
$category_id = 0;
} else {
$category_id = $item->category_id;
}
$group = (($type == 'invoice') || ($type == 'revenue')) ? 'income' : 'expense';
$group = (($type == 'invoice') || ($type == 'revenue')) ? 'income' : 'expense';
if (!isset($compares[$group][$category_id])) {
if (!isset($compares[$group][$item->category_id])) {
continue;
}
@ -179,9 +155,9 @@ class IncomeExpenseSummary extends Controller
}
}
$compares[$group][$category_id][$date]['amount'] += $amount;
$compares[$group][$category_id][$date]['currency_code'] = $item->currency_code;
$compares[$group][$category_id][$date]['currency_rate'] = $item->currency_rate;
$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;
if ($group == 'income') {
$graph[Date::parse($item->$date_field)->format('F-Y')] += $amount;

View File

@ -23,12 +23,7 @@ class IncomeSummary extends Controller
$status = request('status');
//if ($filter != 'upcoming') {
$categories = Category::enabled()->type('income')->pluck('name', 'id')->toArray();
//}
// Add Invoice in Categories
$categories[0] = trans_choice('general.invoices', 2);
$categories = Category::enabled()->type('income')->pluck('name', 'id')->toArray();
// Get year
$year = request('year');
@ -49,15 +44,6 @@ class IncomeSummary extends Controller
'currency_rate' => 1
);
// Invoice
$incomes[0][$dates[$j]] = array(
'category_id' => 0,
'name' => trans_choice('general.invoices', 1),
'amount' => 0,
'currency_code' => setting('general.default_currency'),
'currency_rate' => 1
);
foreach ($categories as $category_id => $category_name) {
$incomes[$category_id][$dates[$j]] = array(
'category_id' => $category_id,
@ -87,10 +73,19 @@ class IncomeSummary extends Controller
// Revenues
if ($status != 'upcoming') {
$revenues = Revenue::monthsOfYear('paid_at')->get();
$revenues = Revenue::monthsOfYear('paid_at')->isNotTransfer()->get();
$this->setAmount($incomes_graph, $totals, $incomes, $revenues, 'revenue', 'paid_at');
}
// Check if it's a print or normal request
if (request('print')) {
$chart_template = 'vendor.consoletvs.charts.chartjs.multi.line_print';
$view_template = 'reports.income_summary.print';
} else {
$chart_template = 'vendor.consoletvs.charts.chartjs.multi.line';
$view_template = 'reports.income_summary.index';
}
// Incomes chart
$chart = Charts::multi('line', 'chartjs')
->dimensions(0, 300)
@ -98,9 +93,9 @@ class IncomeSummary extends Controller
->dataset(trans_choice('general.incomes', 1), $incomes_graph)
->labels($dates)
->credits(false)
->view('vendor.consoletvs.charts.chartjs.multi.line');
->view($chart_template);
return view('reports.income_summary.index', compact('chart', 'dates', 'categories', 'incomes', 'totals'));
return view($view_template, compact('chart', 'dates', 'categories', 'incomes', 'totals'));
}
private function setAmount(&$graph, &$totals, &$incomes, $items, $type, $date_field)
@ -108,13 +103,7 @@ class IncomeSummary extends Controller
foreach ($items as $item) {
$date = Date::parse($item->$date_field)->format('F');
if ($type == 'invoice') {
$category_id = 0;
} else {
$category_id = $item->category_id;
}
if (!isset($incomes[$category_id])) {
if (!isset($incomes[$item->category_id])) {
continue;
}
@ -127,9 +116,9 @@ class IncomeSummary extends Controller
}
}
$incomes[$category_id][$date]['amount'] += $amount;
$incomes[$category_id][$date]['currency_code'] = $item->currency_code;
$incomes[$category_id][$date]['currency_rate'] = $item->currency_rate;
$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;
$graph[Date::parse($item->$date_field)->format('F-Y')] += $amount;

View File

@ -0,0 +1,189 @@
<?php
namespace App\Http\Controllers\Reports;
use App\Http\Controllers\Controller;
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\Setting\Category;
use Charts;
use Date;
class ProfitLoss extends Controller
{
/**
* Display a listing of the resource.
*
* @return Response
*/
public function index()
{
$dates = $totals = $compares = $categories = [];
$status = request('status');
$income_categories = Category::enabled()->type('income')->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;
}
// Dates
for ($j = 1; $j <= 12; $j++) {
$dates[$j] = Date::parse($year . '-' . $j)->quarter;
// Totals
$totals[$dates[$j]] = array(
'amount' => 0,
'currency_code' => setting('general.default_currency'),
'currency_rate' => 1
);
foreach ($income_categories as $category_id => $category_name) {
$compares['income'][$category_id][$dates[$j]] = [
'category_id' => $category_id,
'name' => $category_name,
'amount' => 0,
'currency_code' => setting('general.default_currency'),
'currency_rate' => 1
];
}
foreach ($expense_categories as $category_id => $category_name) {
$compares['expense'][$category_id][$dates[$j]] = [
'category_id' => $category_id,
'name' => $category_name,
'amount' => 0,
'currency_code' => setting('general.default_currency'),
'currency_rate' => 1
];
}
$j += 2;
}
$totals['total'] = [
'amount' => 0,
'currency_code' => setting('general.default_currency'),
'currency_rate' => 1
];
$gross['income'] = $gross['expense'] = [1 => 0, 2 => 0, 3 => 0, 4 => 0, 'total' => 0];
foreach ($income_categories as $category_id => $category_name) {
$compares['income'][$category_id]['total'] = [
'category_id' => $category_id,
'name' => trans_choice('general.totals', 1),
'amount' => 0,
'currency_code' => setting('general.default_currency'),
'currency_rate' => 1
];
}
foreach ($expense_categories as $category_id => $category_name) {
$compares['expense'][$category_id]['total'] = [
'category_id' => $category_id,
'name' => trans_choice('general.totals', 1),
'amount' => 0,
'currency_code' => setting('general.default_currency'),
'currency_rate' => 1
];
}
// Invoices
switch ($status) {
case 'paid':
$invoices = InvoicePayment::monthsOfYear('paid_at')->get();
$this->setAmount($totals, $compares, $invoices, 'invoice', 'paid_at');
break;
case 'upcoming':
$invoices = Invoice::accrued()->monthsOfYear('due_at')->get();
$this->setAmount($totals, $compares, $invoices, 'invoice', 'due_at');
break;
default:
$invoices = Invoice::accrued()->monthsOfYear('invoiced_at')->get();
$this->setAmount($totals, $compares, $invoices, 'invoice', 'invoiced_at');
break;
}
// Revenues
if ($status != 'upcoming') {
$revenues = Revenue::monthsOfYear('paid_at')->isNotTransfer()->get();
$this->setAmount($totals, $compares, $revenues, 'revenue', 'paid_at');
}
// Bills
switch ($status) {
case 'paid':
$bills = BillPayment::monthsOfYear('paid_at')->get();
$this->setAmount($totals, $compares, $bills, 'bill', 'paid_at');
break;
case 'upcoming':
$bills = Bill::accrued()->monthsOfYear('due_at')->get();
$this->setAmount($totals, $compares, $bills, 'bill', 'due_at');
break;
default:
$bills = Bill::accrued()->monthsOfYear('billed_at')->get();
$this->setAmount($totals, $compares, $bills, 'bill', 'billed_at');
break;
}
// Payments
if ($status != 'upcoming') {
$payments = Payment::monthsOfYear('paid_at')->isNotTransfer()->get();
$this->setAmount($totals, $compares, $payments, 'payment', 'paid_at');
}
// Check if it's a print or normal request
if (request('print')) {
$view_template = 'reports.profit_loss.print';
} else {
$view_template = 'reports.profit_loss.index';
}
return view($view_template, compact('dates', 'income_categories', 'expense_categories', 'compares', 'totals', 'gross'));
}
private function setAmount(&$totals, &$compares, $items, $type, $date_field)
{
foreach ($items as $item) {
$date = Date::parse($item->$date_field)->quarter;
$group = (($type == 'invoice') || ($type == 'revenue')) ? 'income' : 'expense';
if (!isset($compares[$group][$item->category_id])) {
continue;
}
$amount = $item->getConvertedAmount();
// Forecasting
if ((($type == 'invoice') || ($type == 'bill')) && ($date_field == 'due_at')) {
foreach ($item->payments as $payment) {
$amount -= $payment->getConvertedAmount();
}
}
$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]['total']['amount'] += $amount;
if ($group == 'income') {
$totals[$date]['amount'] += $amount;
$totals['total']['amount'] += $amount;
} else {
$totals[$date]['amount'] -= $amount;
$totals['total']['amount'] -= $amount;
}
}
}
}

View File

@ -0,0 +1,135 @@
<?php
namespace App\Http\Controllers\Reports;
use App\Http\Controllers\Controller;
use App\Models\Expense\Bill;
use App\Models\Expense\BillPayment;
use App\Models\Expense\BillTotal;
use App\Models\Income\Invoice;
use App\Models\Income\InvoicePayment;
use App\Models\Income\InvoiceTotal;
use App\Models\Setting\Tax;
use App\Traits\Currencies;
use Date;
class TaxSummary extends Controller
{
use Currencies;
/**
* Display a listing of the resource.
*
* @return Response
*/
public function index()
{
$dates = $incomes = $expenses = $totals = [];
$status = request('status');
$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');
foreach ($taxes as $tax_name) {
$incomes[$tax_name][$dates[$j]] = [
'amount' => 0,
'currency_code' => setting('general.default_currency'),
'currency_rate' => 1,
];
$expenses[$tax_name][$dates[$j]] = [
'amount' => 0,
'currency_code' => setting('general.default_currency'),
'currency_rate' => 1,
];
$totals[$tax_name][$dates[$j]] = [
'amount' => 0,
'currency_code' => setting('general.default_currency'),
'currency_rate' => 1,
];
}
}
switch ($status) {
case 'paid':
// Invoices
$invoices = InvoicePayment::with(['invoice', 'invoice.totals'])->monthsOfYear('paid_at')->get();
$this->setAmount($incomes, $totals, $invoices, 'invoice', 'paid_at');
// Bills
$bills = BillPayment::with(['bill', 'bill.totals'])->monthsOfYear('paid_at')->get();
$this->setAmount($expenses, $totals, $bills, 'bill', 'paid_at');
break;
case 'upcoming':
// Invoices
$invoices = Invoice::with(['totals'])->accrued()->monthsOfYear('due_at')->get();
$this->setAmount($incomes, $totals, $invoices, 'invoice', 'due_at');
// Bills
$bills = Bill::with(['totals'])->accrued()->monthsOfYear('due_at')->get();
$this->setAmount($expenses, $totals, $bills, 'bill', 'due_at');
break;
default:
// Invoices
$invoices = Invoice::with(['totals'])->accrued()->monthsOfYear('invoiced_at')->get();
$this->setAmount($incomes, $totals, $invoices, 'invoice', 'invoiced_at');
// Bills
$bills = Bill::with(['totals'])->accrued()->monthsOfYear('billed_at')->get();
$this->setAmount($expenses, $totals, $bills, 'bill', 'billed_at');
break;
}
// Check if it's a print or normal request
if (request('print')) {
$view_template = 'reports.tax_summary.print';
} else {
$view_template = 'reports.tax_summary.index';
}
return view($view_template, compact('dates', 'taxes', 'incomes', 'expenses', 'totals'));
}
private function setAmount(&$items, &$totals, $rows, $type, $date_field)
{
foreach ($rows as $row) {
$date = Date::parse($row->$date_field)->format('M');
if ($date_field == 'paid_at') {
$row_totals = $row->$type->totals;
} else {
$row_totals = $row->totals;
}
foreach ($row_totals as $row_total) {
if ($row_total->code != 'tax') {
continue;
}
if (!isset($items[$row_total->name])) {
continue;
}
$amount = $this->convert($row_total->amount, $row->currency_code, $row->currency_rate);
$items[$row_total->name][$date]['amount'] += $amount;
if ($type == 'invoice') {
$totals[$row_total->name][$date]['amount'] += $amount;
} else {
$totals[$row_total->name][$date]['amount'] -= $amount;
}
}
}
}
}

View File

@ -20,12 +20,26 @@ class Categories extends Controller
$transfer_id = Category::transfer();
$types = collect(['expense' => 'Expense', 'income' => 'Income', 'item' => 'Item', 'other' => 'Other'])
->prepend(trans('general.all_type', ['type' => trans_choice('general.types', 2)]), '');
$types = collect([
'expense' => trans_choice('general.expenses', 1),
'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'));
}
/**
* Show the form for viewing the specified resource.
*
* @return Response
*/
public function show()
{
return redirect('settings/categories');
}
/**
* Show the form for creating a new resource.
*
@ -33,7 +47,14 @@ class Categories extends Controller
*/
public function create()
{
return view('settings.categories.create');
$types = [
'expense' => trans_choice('general.expenses', 1),
'income' => trans_choice('general.incomes', 1),
'item' => trans_choice('general.items', 1),
'other' => trans_choice('general.others', 1),
];
return view('settings.categories.create', compact('types'));
}
/**
@ -63,7 +84,16 @@ class Categories extends Controller
*/
public function edit(Category $category)
{
return view('settings.categories.edit', compact('category'));
$types = [
'expense' => trans_choice('general.expenses', 1),
'income' => trans_choice('general.incomes', 1),
'item' => trans_choice('general.items', 1),
'other' => trans_choice('general.others', 1),
];
$type_disabled = (Category::where('type', $category->type)->count() == 1) ?: false;
return view('settings.categories.edit', compact('category', 'types', 'type_disabled'));
}
/**
@ -78,7 +108,9 @@ class Categories extends Controller
{
$relationships = $this->countRelationships($category, [
'items' => 'items',
'invoices' => 'invoices',
'revenues' => 'revenues',
'bills' => 'bills',
'payments' => 'payments',
]);
@ -108,17 +140,23 @@ class Categories extends Controller
*/
public function destroy(Category $category)
{
$relationships = $this->countRelationships($category, [
'items' => 'items',
'revenues' => 'revenues',
'payments' => 'payments',
]);
// Can not delete the last category by type
if (Category::where('type', $category->type)->count() == 1) {
$message = trans('messages.error.last_category', ['type' => strtolower(trans_choice('general.' . $category->type . 's', 1))]);
flash($message)->warning();
// Can't delete transfer category
if ($category->id == Category::transfer()) {
return redirect('settings/categories');
}
$relationships = $this->countRelationships($category, [
'items' => 'items',
'invoices' => 'invoices',
'revenues' => 'revenues',
'bills' => 'bills',
'payments' => 'payments',
]);
if (empty($relationships)) {
$category->delete();
@ -133,4 +171,11 @@ class Categories extends Controller
return redirect('settings/categories');
}
public function category(Request $request)
{
$category = Category::create($request->all());
return response()->json($category);
}
}

View File

@ -22,6 +22,16 @@ class Currencies extends Controller
return view('settings.currencies.index', compact('currencies'));
}
/**
* Show the form for viewing the specified resource.
*
* @return Response
*/
public function show()
{
return redirect('settings/currencies');
}
/**
* Show the form for creating a new resource.
*

View File

@ -47,7 +47,7 @@ class Settings extends Controller
$currencies = Currency::enabled()->pluck('name', 'code');
$taxes = Tax::enabled()->pluck('name', 'id');
$taxes = Tax::enabled()->get()->pluck('title', 'id');
$payment_methods = Modules::getPaymentMethods();
@ -56,7 +56,7 @@ class Settings extends Controller
'd F Y' => '31 December 2017',
'd m Y' => '31 12 2017',
'm d Y' => '12 31 2017',
'Y m d' => '2017 12 31'
'Y m d' => '2017 12 31',
];
$date_separators = [
@ -71,7 +71,12 @@ class Settings extends Controller
'mail' => trans('settings.email.php'),
'smtp' => trans('settings.email.smtp.name'),
'sendmail' => trans('settings.email.sendmail'),
'log' => trans('settings.email.log')
'log' => trans('settings.email.log'),
];
$percent_positions = [
'before' => trans('settings.localisation.percent.before'),
'after' => trans('settings.localisation.percent.after'),
];
return view('settings.settings.edit', compact(
@ -83,7 +88,8 @@ class Settings extends Controller
'payment_methods',
'date_formats',
'date_separators',
'email_protocols'
'email_protocols',
'percent_positions'
));
}

View File

@ -21,6 +21,16 @@ class Taxes extends Controller
return view('settings.taxes.index', compact('taxes', 'rates'));
}
/**
* Show the form for viewing the specified resource.
*
* @return Response
*/
public function show()
{
return redirect('settings/taxes');
}
/**
* Show the form for creating a new resource.
*

View File

@ -111,7 +111,13 @@ class AdminMenu
}
// Reports
if ($user->can(['read-reports-income-summary', 'read-reports-expense-summary', 'read-reports-income-expense-summary'])) {
if ($user->can([
'read-reports-income-summary',
'read-reports-expense-summary',
'read-reports-income-expense-summary',
'read-reports-tax-summary',
'read-reports-profit-loss',
])) {
$menu->dropdown(trans_choice('general.reports', 2), function ($sub) use($user, $attr) {
if ($user->can('read-reports-income-summary')) {
$sub->url('reports/income-summary', trans('reports.summary.income'), 1, $attr);
@ -124,6 +130,14 @@ class AdminMenu
if ($user->can('read-reports-income-expense-summary')) {
$sub->url('reports/income-expense-summary', trans('reports.summary.income_expense'), 3, $attr);
}
if ($user->can('read-reports-tax-summary')) {
$sub->url('reports/tax-summary', trans('reports.summary.tax'), 4, $attr);
}
if ($user->can('read-reports-profit-loss')) {
$sub->url('reports/profit-loss', trans('reports.profit_loss'), 5, $attr);
}
}, 6, [
'title' => trans_choice('general.reports', 2),
'icon' => 'fa fa-bar-chart',

View File

@ -39,6 +39,7 @@ class Bill extends Request
'billed_at' => 'required|date',
'due_at' => 'required|date',
'currency_code' => 'required|string',
'category_id' => 'required|integer',
'attachment' => 'mimes:' . setting('general.file_types') . '|between:0,' . setting('general.file_size') * 1024,
];
}

View File

@ -39,6 +39,7 @@ class Invoice extends Request
'invoiced_at' => 'required|date',
'due_at' => 'required|date',
'currency_code' => 'required|string',
'category_id' => 'required|integer',
'attachment' => 'mimes:' . setting('general.file_types') . '|between:0,' . setting('general.file_size') * 1024,
];
}

View File

@ -3,9 +3,26 @@
namespace App\Http\Requests\Module;
use App\Http\Requests\Request;
use App\Traits\Modules;
use Illuminate\Validation\Factory as ValidationFactory;
class Module extends Request
{
use Modules;
public function __construct(ValidationFactory $validation)
{
$validation->extend(
'check',
function ($attribute, $value, $parameters) {
return $this->checkToken($value);
},
trans('messages.error.invalid_token')
);
}
/**
* Determine if the user is authorized to make this request.
*
@ -24,7 +41,7 @@ class Module extends Request
public function rules()
{
return [
'api_token' => 'required|string',
'api_token' => 'required|string|check',
];
}
}

View File

@ -25,7 +25,7 @@ class Tax extends Request
{
return [
'name' => 'required|string',
'rate' => 'required',
'rate' => 'required|min:0|max:100',
];
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace App\Http\ViewComposers;
use App\Models\Common\Media;
use Illuminate\View\View;
use File;
use Image;
use Storage;
class Logo
{
/**
* Bind data to the view.
*
* @param View $view
* @return void
*/
public function compose(View $view)
{
$logo = '';
$media_id = setting('general.company_logo');
if (setting('general.invoice_logo')) {
$media_id = setting('general.invoice_logo');
}
$media = Media::find($media_id);
if (!empty($media)) {
$path = Storage::path($media->getDiskPath());
if (!is_file($path)) {
return $logo;
}
} else {
$path = asset('public/img/company.png');
}
$image = Image::make($path)->encode()->getEncoded();
if (empty($image)) {
return $logo;
}
$extension = File::extension($path);
$logo = 'data:image/' . $extension . ';base64,' . base64_encode($image);
$view->with(['logo' => $logo]);
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace App\Http\ViewComposers;
use Illuminate\View\View;
class Recurring
{
/**
* Bind data to the view.
*
* @param View $view
* @return void
*/
public function compose(View $view)
{
$recurring_frequencies = [
'no' => trans('general.no'),
'daily' => trans('recurring.daily'),
'weekly' => trans('recurring.weekly'),
'monthly' => trans('recurring.monthly'),
'yearly' => trans('recurring.yearly'),
'custom' => trans('recurring.custom'),
];
$recurring_custom_frequencies = [
'daily' => trans('recurring.days'),
'weekly' => trans('recurring.weeks'),
'monthly' => trans('recurring.months'),
'yearly' => trans('recurring.years'),
];
$view->with(['recurring_frequencies' => $recurring_frequencies, 'recurring_custom_frequencies' => $recurring_custom_frequencies]);
}
}

View File

@ -0,0 +1,137 @@
<?php
namespace App\Listeners\Updates;
use App\Events\UpdateFinished;
use App\Models\Auth\Role;
use App\Models\Auth\Permission;
use App\Models\Company\Company;
use App\Models\Expense\Bill;
use App\Models\Income\Invoice;
use App\Models\Setting\Category;
use DB;
use Schema;
use Artisan;
class Version120 extends Listener
{
const ALIAS = 'core';
const VERSION = '1.2.0';
/**
* 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]);
$this->updateInvoicesAndBills();
$this->changeQuantityColumn();
}
protected function updatePermissions()
{
$permissions = [];
// Create tax summary permission
$permissions[] = Permission::firstOrCreate([
'name' => 'read-reports-tax-summary',
'display_name' => 'Read Reports Tax Summary',
'description' => 'Read Reports Tax Summary',
]);
// Create profit loss permission
$permissions[] = Permission::firstOrCreate([
'name' => 'read-reports-profit-loss',
'display_name' => 'Read Reports Profit Loss',
'description' => 'Read Reports Profit Loss',
]);
// 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);
}
}
}
protected function updateInvoicesAndBills()
{
$companies = Company::all();
foreach ($companies as $company) {
// Invoices
$invoice_category = Category::create([
'company_id' => $company->id,
'name' => trans_choice('general.invoices', 2),
'type' => 'income',
'color' => '#00c0ef',
'enabled' => '1'
]);
foreach ($company->invoices as $invoice) {
$invoice->category_id = $invoice_category->id;
$invoice->save();
}
// Bills
$bill_category = Category::create([
'company_id' => $company->id,
'name' => trans_choice('general.bills', 2),
'type' => 'expense',
'color' => '#dd4b39',
'enabled' => '1'
]);
foreach ($company->bills as $bill) {
$bill->category_id = $bill_category->id;
$bill->save();
}
}
}
protected function changeQuantityColumn()
{
$connection = env('DB_CONNECTION');
if ($connection == 'mysql') {
$tables = [
env('DB_PREFIX') . 'invoice_items',
env('DB_PREFIX') . 'bill_items'
];
foreach ($tables as $table) {
DB::statement("ALTER TABLE `$table` MODIFY `quantity` DOUBLE(7,2) NOT NULL");
}
} else {
Schema::table('invoice_items', function ($table) {
$table->decimal('quantity', 7, 2)->change();
});
Schema::table('bill_items', function ($table) {
$table->decimal('quantity', 7, 2)->change();
});
}
}
}

View File

@ -65,6 +65,6 @@ class Permission extends LaratrustPermission
$input = $request->input();
$limit = $request->get('limit', setting('general.list_limit', '25'));
return $this->filter($input)->sortable($sort)->paginate($limit);
return $query->filter($input)->sortable($sort)->paginate($limit);
}
}

View File

@ -64,6 +64,6 @@ class Role extends LaratrustRole
$input = $request->input();
$limit = $request->get('limit', setting('general.list_limit', '25'));
return $this->filter($input)->sortable($sort)->paginate($limit);
return $query->filter($input)->sortable($sort)->paginate($limit);
}
}

View File

@ -177,7 +177,7 @@ class User extends Authenticatable
$input = $request->input();
$limit = $request->get('limit', setting('general.list_limit', '25'));
return $this->filter($input)->sortable($sort)->paginate($limit);
return $query->filter($input)->sortable($sort)->paginate($limit);
}
/**

View File

@ -0,0 +1,29 @@
<?php
namespace App\Models\Common;
use App\Models\Model;
use App\Traits\Recurring as RecurringTrait;
class Recurring extends Model
{
use RecurringTrait;
protected $table = 'recurring';
/**
* Attributes that should be mass-assignable.
*
* @var array
*/
protected $fillable = ['company_id', 'recurable_id', 'recurable_type', 'frequency', 'interval', 'started_at', 'count'];
/**
* Get all of the owning recurable models.
*/
public function recurable()
{
return $this->morphTo();
}
}

View File

@ -106,6 +106,11 @@ class Company extends Eloquent
return $this->hasMany('App\Models\Expense\Payment');
}
public function recurring()
{
return $this->hasMany('App\Models\Common\Recurring');
}
public function revenues()
{
return $this->hasMany('App\Models\Income\Revenue');

View File

@ -5,16 +5,24 @@ namespace App\Models\Expense;
use App\Models\Model;
use App\Traits\Currencies;
use App\Traits\DateTime;
use App\Traits\Media;
use App\Traits\Recurring;
use Bkwld\Cloner\Cloneable;
use Sofa\Eloquence\Eloquence;
use App\Traits\Media;
class Bill extends Model
{
use Cloneable, Currencies, DateTime, Eloquence, Media;
use Cloneable, Currencies, DateTime, Eloquence, Media, Recurring;
protected $table = 'bills';
/**
* The accessors to append to the model's array form.
*
* @var array
*/
protected $appends = ['attachment', 'discount'];
protected $dates = ['deleted_at', 'billed_at', 'due_at'];
/**
@ -22,7 +30,7 @@ class Bill extends Model
*
* @var array
*/
protected $fillable = ['company_id', 'bill_number', 'order_number', 'bill_status_code', 'billed_at', 'due_at', 'amount', 'currency_code', 'currency_rate', 'vendor_id', 'vendor_name', 'vendor_email', 'vendor_tax_number', 'vendor_phone', 'vendor_address', 'notes'];
protected $fillable = ['company_id', 'bill_number', 'order_number', 'bill_status_code', 'billed_at', 'due_at', 'amount', 'currency_code', 'currency_rate', 'vendor_id', 'vendor_name', 'vendor_email', 'vendor_tax_number', 'vendor_phone', 'vendor_address', 'notes', 'category_id', 'parent_id'];
/**
* Sortable columns.
@ -51,11 +59,11 @@ class Bill extends Model
*
* @var array
*/
protected $cloneable_relations = ['items', 'totals'];
public $cloneable_relations = ['items', 'recurring', 'totals'];
public function vendor()
public function category()
{
return $this->belongsTo('App\Models\Expense\Vendor');
return $this->belongsTo('App\Models\Setting\Category');
}
public function currency()
@ -63,9 +71,9 @@ class Bill extends Model
return $this->belongsTo('App\Models\Setting\Currency', 'currency_code', 'code');
}
public function status()
public function histories()
{
return $this->belongsTo('App\Models\Expense\BillStatus', 'bill_status_code', 'code');
return $this->hasMany('App\Models\Expense\BillHistory');
}
public function items()
@ -73,19 +81,29 @@ class Bill extends Model
return $this->hasMany('App\Models\Expense\BillItem');
}
public function totals()
{
return $this->hasMany('App\Models\Expense\BillTotal');
}
public function payments()
{
return $this->hasMany('App\Models\Expense\BillPayment');
}
public function histories()
public function recurring()
{
return $this->hasMany('App\Models\Expense\BillHistory');
return $this->morphOne('App\Models\Common\Recurring', 'recurable');
}
public function status()
{
return $this->belongsTo('App\Models\Expense\BillStatus', 'bill_status_code', 'code');
}
public function totals()
{
return $this->hasMany('App\Models\Expense\BillTotal');
}
public function vendor()
{
return $this->belongsTo('App\Models\Expense\Vendor');
}
public function scopeDue($query, $date)
@ -155,4 +173,24 @@ class Bill extends Model
return $this->getMedia('attachment')->last();
}
/**
* Get the discount percentage.
*
* @return string
*/
public function getDiscountAttribute()
{
$percent = 0;
$discount = $this->totals()->where('code', 'discount')->value('amount');
if ($discount) {
$sub_total = $this->totals()->where('code', 'sub_total')->value('amount');
$percent = number_format((($discount * 100) / $sub_total), 0);
}
return $percent;
}
}

View File

@ -3,6 +3,7 @@
namespace App\Models\Expense;
use App\Models\Model;
use App\Models\Setting\Tax;
use App\Traits\DateTime;
class BillTotal extends Model
@ -11,6 +12,13 @@ class BillTotal extends Model
protected $table = 'bill_totals';
/**
* The accessors to append to the model's array form.
*
* @var array
*/
protected $appends = ['title'];
/**
* Attributes that should be mass-assignable.
*
@ -33,4 +41,46 @@ class BillTotal extends Model
{
$this->attributes['amount'] = (double) $value;
}
/**
* Get the formatted name.
*
* @return string
*/
public function getTitleAttribute()
{
$title = $this->name;
$percent = 0;
switch ($this->code) {
case 'discount':
$title = trans($title);
$percent = $this->bill->discount;
break;
case 'tax':
$rate = Tax::where('name', $title)->value('rate');
if (!empty($rate)) {
$percent = $rate;
}
break;
}
if (!empty($percent)) {
$title .= ' (';
if (setting('general.percent_position', 'after') == 'after') {
$title .= $percent . '%';
} else {
$title .= '%' . $percent;
}
$title .= ')';
}
return $title;
}
}

View File

@ -6,13 +6,14 @@ use App\Models\Model;
use App\Models\Setting\Category;
use App\Traits\Currencies;
use App\Traits\DateTime;
use App\Traits\Media;
use App\Traits\Recurring;
use Bkwld\Cloner\Cloneable;
use Sofa\Eloquence\Eloquence;
use App\Traits\Media;
class Payment extends Model
{
use Cloneable, Currencies, DateTime, Eloquence, Media;
use Cloneable, Currencies, DateTime, Eloquence, Media, Recurring;
protected $table = 'payments';
@ -23,7 +24,7 @@ class Payment extends Model
*
* @var array
*/
protected $fillable = ['company_id', 'account_id', 'paid_at', 'amount', 'currency_code', 'currency_rate', 'vendor_id', 'description', 'category_id', 'payment_method', 'reference'];
protected $fillable = ['company_id', 'account_id', 'paid_at', 'amount', 'currency_code', 'currency_rate', 'vendor_id', 'description', 'category_id', 'payment_method', 'reference', 'parent_id'];
/**
* Sortable columns.
@ -44,24 +45,31 @@ class Payment extends Model
'description' ,
];
/**
* Clonable relationships.
*
* @var array
*/
public $cloneable_relations = ['recurring'];
public function account()
{
return $this->belongsTo('App\Models\Banking\Account');
}
public function currency()
{
return $this->belongsTo('App\Models\Setting\Currency', 'currency_code', 'code');
}
public function category()
{
return $this->belongsTo('App\Models\Setting\Category');
}
public function vendor()
public function currency()
{
return $this->belongsTo('App\Models\Expense\Vendor');
return $this->belongsTo('App\Models\Setting\Currency', 'currency_code', 'code');
}
public function recurring()
{
return $this->morphOne('App\Models\Common\Recurring', 'recurable');
}
public function transfers()
@ -69,6 +77,11 @@ class Payment extends Model
return $this->hasMany('App\Models\Banking\Transfer');
}
public function vendor()
{
return $this->belongsTo('App\Models\Expense\Vendor');
}
/**
* Get only transfers.
*

View File

@ -6,13 +6,14 @@ use App\Models\Model;
use App\Traits\Currencies;
use App\Traits\DateTime;
use App\Traits\Incomes;
use App\Traits\Media;
use App\Traits\Recurring;
use Bkwld\Cloner\Cloneable;
use Sofa\Eloquence\Eloquence;
use App\Traits\Media;
class Invoice extends Model
{
use Cloneable, Currencies, DateTime, Eloquence, Incomes, Media;
use Cloneable, Currencies, DateTime, Eloquence, Incomes, Media, Recurring;
protected $table = 'invoices';
@ -21,7 +22,7 @@ class Invoice extends Model
*
* @var array
*/
protected $appends = ['attachment'];
protected $appends = ['attachment', 'discount'];
protected $dates = ['deleted_at', 'invoiced_at', 'due_at'];
@ -30,7 +31,7 @@ class Invoice extends Model
*
* @var array
*/
protected $fillable = ['company_id', 'invoice_number', 'order_number', 'invoice_status_code', 'invoiced_at', 'due_at', 'amount', 'currency_code', 'currency_rate', 'customer_id', 'customer_name', 'customer_email', 'customer_tax_number', 'customer_phone', 'customer_address', 'notes'];
protected $fillable = ['company_id', 'invoice_number', 'order_number', 'invoice_status_code', 'invoiced_at', 'due_at', 'amount', 'currency_code', 'currency_rate', 'customer_id', 'customer_name', 'customer_email', 'customer_tax_number', 'customer_phone', 'customer_address', 'notes', 'category_id', 'parent_id'];
/**
* Sortable columns.
@ -59,11 +60,11 @@ class Invoice extends Model
*
* @var array
*/
protected $cloneable_relations = ['items', 'totals'];
public $cloneable_relations = ['items', 'recurring', 'totals'];
public function customer()
public function category()
{
return $this->belongsTo('App\Models\Income\Customer');
return $this->belongsTo('App\Models\Setting\Category');
}
public function currency()
@ -71,9 +72,9 @@ class Invoice extends Model
return $this->belongsTo('App\Models\Setting\Currency', 'currency_code', 'code');
}
public function status()
public function customer()
{
return $this->belongsTo('App\Models\Income\InvoiceStatus', 'invoice_status_code', 'code');
return $this->belongsTo('App\Models\Income\Customer');
}
public function items()
@ -81,9 +82,9 @@ class Invoice extends Model
return $this->hasMany('App\Models\Income\InvoiceItem');
}
public function totals()
public function histories()
{
return $this->hasMany('App\Models\Income\InvoiceTotal');
return $this->hasMany('App\Models\Income\InvoiceHistory');
}
public function payments()
@ -91,9 +92,19 @@ class Invoice extends Model
return $this->hasMany('App\Models\Income\InvoicePayment');
}
public function histories()
public function recurring()
{
return $this->hasMany('App\Models\Income\InvoiceHistory');
return $this->morphOne('App\Models\Common\Recurring', 'recurable');
}
public function status()
{
return $this->belongsTo('App\Models\Income\InvoiceStatus', 'invoice_status_code', 'code');
}
public function totals()
{
return $this->hasMany('App\Models\Income\InvoiceTotal');
}
public function scopeDue($query, $date)
@ -164,4 +175,24 @@ class Invoice extends Model
return $this->getMedia('attachment')->last();
}
/**
* Get the discount percentage.
*
* @return string
*/
public function getDiscountAttribute()
{
$percent = 0;
$discount = $this->totals()->where('code', 'discount')->value('amount');
if ($discount) {
$sub_total = $this->totals()->where('code', 'sub_total')->value('amount');
$percent = number_format((($discount * 100) / $sub_total), 0);
}
return $percent;
}
}

View File

@ -3,6 +3,7 @@
namespace App\Models\Income;
use App\Models\Model;
use App\Models\Setting\Tax;
use App\Traits\DateTime;
class InvoiceTotal extends Model
@ -11,6 +12,13 @@ class InvoiceTotal extends Model
protected $table = 'invoice_totals';
/**
* The accessors to append to the model's array form.
*
* @var array
*/
protected $appends = ['title'];
/**
* Attributes that should be mass-assignable.
*
@ -33,4 +41,46 @@ class InvoiceTotal extends Model
{
$this->attributes['amount'] = (double) $value;
}
/**
* Get the formatted name.
*
* @return string
*/
public function getTitleAttribute()
{
$title = $this->name;
$percent = 0;
switch ($this->code) {
case 'discount':
$title = trans($title);
$percent = $this->invoice->discount;
break;
case 'tax':
$rate = Tax::where('name', $title)->value('rate');
if (!empty($rate)) {
$percent = $rate;
}
break;
}
if (!empty($percent)) {
$title .= ' (';
if (setting('general.percent_position', 'after') == 'after') {
$title .= $percent . '%';
} else {
$title .= '%' . $percent;
}
$title .= ')';
}
return $title;
}
}

View File

@ -6,13 +6,14 @@ use App\Models\Model;
use App\Models\Setting\Category;
use App\Traits\Currencies;
use App\Traits\DateTime;
use App\Traits\Media;
use App\Traits\Recurring;
use Bkwld\Cloner\Cloneable;
use Sofa\Eloquence\Eloquence;
use App\Traits\Media;
class Revenue extends Model
{
use Cloneable, Currencies, DateTime, Eloquence, Media;
use Cloneable, Currencies, DateTime, Eloquence, Media, Recurring;
protected $table = 'revenues';
@ -23,7 +24,7 @@ class Revenue extends Model
*
* @var array
*/
protected $fillable = ['company_id', 'account_id', 'paid_at', 'amount', 'currency_code', 'currency_rate', 'customer_id', 'description', 'category_id', 'payment_method', 'reference'];
protected $fillable = ['company_id', 'account_id', 'paid_at', 'amount', 'currency_code', 'currency_rate', 'customer_id', 'description', 'category_id', 'payment_method', 'reference', 'parent_id'];
/**
* Sortable columns.
@ -45,6 +46,13 @@ class Revenue extends Model
'notes' => 2,
];
/**
* Clonable relationships.
*
* @var array
*/
public $cloneable_relations = ['recurring'];
public function user()
{
return $this->belongsTo('App\Models\Auth\User', 'customer_id', 'id');
@ -70,6 +78,11 @@ class Revenue extends Model
return $this->belongsTo('App\Models\Income\Customer');
}
public function recurring()
{
return $this->morphOne('App\Models\Common\Recurring', 'recurable');
}
public function transfers()
{
return $this->hasMany('App\Models\Banking\Transfer');

View File

@ -87,13 +87,15 @@ class Item extends Model
return Item::all();
}
$query = Item::select('id as item_id', 'name', 'sale_price', 'purchase_price', 'tax_id');
$query = Item::select('id as item_id', 'name', 'sku', 'sale_price', 'purchase_price', 'tax_id');
$query->where('quantity', '>', '0');
foreach ($filter_data as $key => $value) {
$query->where($key, 'LIKE', "%" . $value . "%");
}
$query->where(function ($query) use ($filter_data) {
foreach ($filter_data as $key => $value) {
$query->orWhere($key, 'LIKE', "%" . $value . "%");
}
});
return $query->get();
}

View File

@ -91,7 +91,7 @@ class Model extends Eloquent
$input = $request->input();
$limit = $request->get('limit', setting('general.list_limit', '25'));
return $this->filter($input)->sortable($sort)->paginate($limit);
return $query->filter($input)->sortable($sort)->paginate($limit);
}
/**

View File

@ -22,9 +22,19 @@ class Category extends Model
*/
public $sortable = ['name', 'type', 'enabled'];
public function revenues()
public function bills()
{
return $this->hasMany('App\Models\Income\Revenue');
return $this->hasMany('App\Models\Expense\Bill');
}
public function invoices()
{
return $this->hasMany('App\Models\Income\Invoice');
}
public function items()
{
return $this->hasMany('App\Models\Item\Item');
}
public function payments()
@ -32,9 +42,9 @@ class Category extends Model
return $this->hasMany('App\Models\Expense\Payment');
}
public function items()
public function revenues()
{
return $this->hasMany('App\Models\Item\Item');
return $this->hasMany('App\Models\Income\Revenue');
}
/**

View File

@ -9,6 +9,13 @@ class Tax extends Model
protected $table = 'taxes';
/**
* The accessors to append to the model's array form.
*
* @var array
*/
protected $appends = ['title'];
/**
* Attributes that should be mass-assignable.
*
@ -48,4 +55,24 @@ class Tax extends Model
{
$this->attributes['rate'] = (double) $value;
}
/**
* Get the name including rate.
*
* @return string
*/
public function getTitleAttribute()
{
$title = $this->name . ' (';
if (setting('general.percent_position', 'after') == 'after') {
$title .= $this->rate . '%';
} else {
$title .= '%' . $this->rate;
}
$title .= ')';
return $title;
}
}

View File

@ -116,6 +116,7 @@ class MessageSelector
case 'bo':
case 'dz':
case 'id':
case 'id-ID':
case 'ja':
case 'jv':
case 'ka':
@ -126,14 +127,18 @@ class MessageSelector
case 'th':
case 'tr':
case 'vi':
case 'vi-VN':
case 'zh':
case 'zh-TW':
return 0;
break;
case 'af':
case 'bn':
case 'bg':
case 'bg-BG':
case 'ca':
case 'da':
case 'da-DK':
case 'de':
case 'de-DE':
case 'el':
@ -145,6 +150,7 @@ class MessageSelector
case 'eo':
case 'es':
case 'es-ES':
case 'es-MX':
case 'et':
case 'eu':
case 'fa':
@ -167,8 +173,10 @@ class MessageSelector
case 'mr':
case 'nah':
case 'nb':
case 'nb-NO':
case 'ne':
case 'nl':
case 'nl-NL':
case 'nn':
case 'no':
case 'om':
@ -179,7 +187,9 @@ class MessageSelector
case 'pt':
case 'so':
case 'sq':
case 'sq-AL':
case 'sv':
case 'sv-SE':
case 'sw':
case 'ta':
case 'te':
@ -207,11 +217,14 @@ class MessageSelector
case 'be':
case 'bs':
case 'hr':
case 'hr-HR':
case 'ru':
case 'ru-RU':
case 'sr':
case 'uk':
return (($number % 10 == 1) && ($number % 100 != 11)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2);
case 'cs':
case 'cs-CZ':
case 'sk':
return ($number == 1) ? 0 : ((($number >= 2) && ($number <= 4)) ? 1 : 2);
case 'ga':

View File

@ -22,6 +22,7 @@ class EventServiceProvider extends ServiceProvider
'App\Listeners\Updates\Version112',
'App\Listeners\Updates\Version113',
'App\Listeners\Updates\Version119',
'App\Listeners\Updates\Version120',
],
'Illuminate\Auth\Events\Login' => [
'App\Listeners\Auth\Login',

View File

@ -27,6 +27,10 @@ class FormServiceProvider extends ServiceProvider
'name', 'text', 'icon', 'attributes' => ['required' => 'required'], 'value' => null, 'col' => 'col-md-6',
]);
Form::component('numberGroup', 'partials.form.number_group', [
'name', 'text', 'icon', 'attributes' => ['required' => 'required'], 'value' => null, 'col' => 'col-md-6',
]);
Form::component('selectGroup', 'partials.form.select_group', [
'name', 'text', 'icon', 'values', 'selected' => null, 'attributes' => ['required' => 'required'], 'col' => 'col-md-6',
]);
@ -58,6 +62,10 @@ class FormServiceProvider extends ServiceProvider
Form::component('saveButtons', 'partials.form.save_buttons', [
'cancel', 'col' => 'col-md-12',
]);
Form::component('recurring', 'partials.form.recurring', [
'page', 'model' => null,
]);
}
/**

View File

@ -38,6 +38,16 @@ class ViewComposerServiceProvider extends ServiceProvider
View::composer(
'modules.*', 'App\Http\ViewComposers\Modules'
);
// Add recurring
View::composer(
['partials.form.recurring',], 'App\Http\ViewComposers\Recurring'
);
// Add logo
View::composer(
['incomes.invoices.invoice', 'expenses.bills.bill'], 'App\Http\ViewComposers\Logo'
);
}
/**

View File

@ -12,6 +12,25 @@ use ZipArchive;
trait Modules
{
public function checkToken($token)
{
$data = [
'form_params' => [
'token' => $token,
]
];
$response = $this->getRemote('token/check', 'POST', $data);
if ($response->getStatusCode() == 200) {
$result = json_decode($response->getBody());
return ($result->success) ? true : false;
}
return false;
}
public function getModules()
{
$response = $this->getRemote('apps/items');
@ -89,6 +108,17 @@ trait Modules
return [];
}
public function getSearchModules($data = [])
{
$response = $this->getRemote('apps/search', 'GET', $data);
if ($response->getStatusCode() == 200) {
return json_decode($response->getBody())->data;
}
return [];
}
public function getCoreVersion()
{
$data['query'] = Info::all();
@ -290,6 +320,7 @@ trait Modules
'Authorization' => 'Bearer ' . setting('general.api_token'),
'Accept' => 'application/json',
'Referer' => env('APP_URL'),
'Akaunting' => version('short'),
];
$data['http_errors'] = false;

152
app/Traits/Recurring.php Normal file
View File

@ -0,0 +1,152 @@
<?php
namespace App\Traits;
use DateTime;
use DateTimeZone;
use Recurr\Rule;
use Recurr\Transformer\ArrayTransformer;
use Recurr\Transformer\ArrayTransformerConfig;
trait Recurring
{
public function createRecurring()
{
$request = request();
if ($request->get('recurring_frequency') == 'no') {
return;
}
$frequency = ($request['recurring_frequency'] != 'custom') ? $request['recurring_frequency'] : $request['recurring_custom_frequency'];
$interval = ($request['recurring_frequency'] != 'custom') ? 1 : (int) $request['recurring_interval'];
$started_at = $request->get('paid_at') ?: ($request->get('invoiced_at') ?: $request->get('billed_at'));
$this->recurring()->create([
'company_id' => session('company_id'),
'frequency' => $frequency,
'interval' => $interval,
'started_at' => $started_at,
'count' => (int) $request['recurring_count'],
]);
}
public function updateRecurring()
{
$request = request();
if ($request->get('recurring_frequency') == 'no') {
$this->recurring()->delete();
return;
}
$frequency = ($request['recurring_frequency'] != 'custom') ? $request['recurring_frequency'] : $request['recurring_custom_frequency'];
$interval = ($request['recurring_frequency'] != 'custom') ? 1 : (int) $request['recurring_interval'];
$started_at = $request->get('paid_at') ?: ($request->get('invoiced_at') ?: $request->get('billed_at'));
$recurring = $this->recurring();
if ($recurring->count()) {
$function = 'update';
} else {
$function = 'create';
}
$recurring->$function([
'company_id' => session('company_id'),
'frequency' => $frequency,
'interval' => $interval,
'started_at' => $started_at,
'count' => (int) $request['recurring_count'],
]);
}
public function current()
{
if (!$schedule = $this->schedule()) {
return false;
}
return $schedule->current()->getStart();
}
public function next()
{
if (!$schedule = $this->schedule()) {
return false;
}
if (!$next = $schedule->next()) {
return false;
}
return $next->getStart();
}
public function first()
{
if (!$schedule = $this->schedule()) {
return false;
}
return $schedule->first()->getStart();
}
public function last()
{
if (!$schedule = $this->schedule()) {
return false;
}
return $schedule->last()->getStart();
}
public function schedule()
{
$config = new ArrayTransformerConfig();
$config->enableLastDayOfMonthFix();
$transformer = new ArrayTransformer();
$transformer->setConfig($config);
return $transformer->transform($this->getRule());
}
public function getRule()
{
$rule = (new Rule())
->setStartDate($this->getRuleStartDate())
->setTimezone($this->getRuleTimeZone())
->setFreq($this->getRuleFrequency())
->setInterval($this->interval);
// 0 means infinite
if ($this->count != 0) {
$rule->setCount($this->getRuleCount());
}
return $rule;
}
public function getRuleStartDate()
{
return new DateTime($this->started_at, new DateTimeZone($this->getRuleTimeZone()));
}
public function getRuleTimeZone()
{
return setting('general.timezone');
}
public function getRuleCount()
{
// Fix for humans
return $this->count + 1;
}
public function getRuleFrequency()
{
return strtoupper($this->frequency);
}
}

View File

@ -18,10 +18,11 @@
"bkwld/cloner": "3.2.*",
"consoletvs/charts": "4.6.*",
"dingo/api": "1.0.0-beta8",
"doctrine/dbal": "2.5.*",
"fideloper/proxy": "3.3.*",
"guzzlehttp/guzzle": "6.3.*",
"intervention/image": "2.3.*",
"jenssegers/date": "3.2.*",
"jenssegers/date": "3.3.*",
"kyslik/column-sortable": "5.4.*",
"laracasts/flash": "3.0.*",
"laravel/framework": "5.4.*",
@ -32,6 +33,7 @@
"nwidart/laravel-modules": "1.*",
"plank/laravel-mediable": "2.5.*",
"santigarcor/laratrust": "4.0.*",
"simshaun/recurr": "3.0.*",
"sofa/eloquence": "5.4.*",
"tucker-eric/eloquentfilter": "1.1.*"
},

4923
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -115,7 +115,7 @@ return [
|
*/
'allowed' => ['en-GB', 'ar-SA', 'cs-CZ', 'de-DE', 'es-ES', 'es-MX', 'fa-IR', 'fr-FR', 'hr-HR', 'id-ID', 'it-IT', 'nb-NO', 'nl-NL', 'pt-BR', 'ru-RU', 'sq-AL', 'sv-SE', 'tr-TR', 'vi-VN', 'zh-TW'],
'allowed' => ['en-GB', 'ar-SA', 'bg-BG', 'cs-CZ', 'da-DK', 'de-DE', 'el-GR', 'es-ES', 'es-MX', 'fa-IR', 'fr-FR', 'he-IL', 'hr-HR', 'id-ID', 'it-IT', 'nb-NO', 'nl-NL', 'pt-BR', 'ru-RU', 'sq-AL', 'sv-SE', 'tr-TR', 'uk-UA', 'vi-VN', 'zh-TW'],
/*
|--------------------------------------------------------------------------

View File

@ -4,21 +4,21 @@ return [
'name' => 'Akaunting',
'code' => 'Import',
'code' => 'Recurring',
'major' => '1',
'minor' => '1',
'minor' => '2',
'patch' => '15',
'patch' => '0',
'build' => '',
'status' => 'Stable',
'status' => 'RC',
'date' => '13-March-2018',
'date' => '28-April-2018',
'time' => '16:00',
'time' => '19:00',
'zone' => 'GMT +3',

View File

@ -45,7 +45,7 @@ class CreateBillsTable extends Migration
$table->integer('item_id')->nullable();
$table->string('name');
$table->string('sku')->nullable();
$table->integer('quantity');
$table->double('quantity', 7, 2);
$table->double('price', 15, 4);
$table->double('total', 15, 4);
$table->float('tax', 15, 4)->default('0.0000');

View File

@ -45,7 +45,7 @@ class CreateInvoicesTable extends Migration
$table->integer('item_id')->nullable();
$table->string('name');
$table->string('sku')->nullable();
$table->integer('quantity');
$table->double('quantity', 7, 2);
$table->double('price', 15, 4);
$table->double('total', 15, 4);
$table->double('tax', 15, 4)->default('0.0000');

View File

@ -0,0 +1,38 @@
<?php
use Illuminate\Database\Migrations\Migration;
class AddCategoryColumnInvoicesBills extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('invoices', function ($table) {
$table->integer('category_id');
});
Schema::table('bills', function ($table) {
$table->integer('category_id');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('invoices', function ($table) {
$table->dropColumn('category_id');
});
Schema::table('bills', function ($table) {
$table->dropColumn('category_id');
});
}
}

View File

@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
class CreateRecurringTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('recurring', function (Blueprint $table) {
$table->increments('id');
$table->integer('company_id');
$table->morphs('recurable');
$table->string('frequency');
$table->integer('interval')->default(1);
$table->date('started_at');
$table->integer('count')->default(0);
$table->timestamps();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('recurring');
}
}

View File

@ -0,0 +1,54 @@
<?php
use Illuminate\Database\Migrations\Migration;
class AddParentColumn extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('invoices', function ($table) {
$table->integer('parent_id')->default(0);
});
Schema::table('revenues', function ($table) {
$table->integer('parent_id')->default(0);
});
Schema::table('bills', function ($table) {
$table->integer('parent_id')->default(0);
});
Schema::table('payments', function ($table) {
$table->integer('parent_id')->default(0);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('invoices', function ($table) {
$table->dropColumn('parent_id');
});
Schema::table('revenues', function ($table) {
$table->dropColumn('parent_id');
});
Schema::table('bills', function ($table) {
$table->dropColumn('parent_id');
});
Schema::table('payments', function ($table) {
$table->dropColumn('parent_id');
});
}
}

View File

@ -61,6 +61,8 @@ class Roles extends Seeder
'reports-income-summary' => 'r',
'reports-expense-summary' => 'r',
'reports-income-expense-summary' => 'r',
'reports-profit-loss' => 'r',
'reports-tax-summary' => 'r',
],
'manager' => [
'admin-panel' => 'r',
@ -87,6 +89,8 @@ class Roles extends Seeder
'reports-income-summary' => 'r',
'reports-expense-summary' => 'r',
'reports-income-expense-summary' => 'r',
'reports-profit-loss' => 'r',
'reports-tax-summary' => 'r',
],
'customer' => [
'customer-panel' => 'r',

View File

@ -30,6 +30,7 @@ class Settings extends Seeder
'general.date_format' => 'd M Y',
'general.date_separator' => 'space',
'general.timezone' => 'Europe/London',
'general.percent_position' => 'after',
'general.invoice_number_prefix' => 'INV-',
'general.invoice_number_digit' => '5',
'general.invoice_number_next' => '1',

53
public/css/app.css vendored
View File

@ -505,10 +505,20 @@ ul.add-new.nav.navbar-nav.pull-left {
}
}
@media only screen and (min-width : 768px) {
.amount-space {
padding-right: 30px !important;
}
}
.text-disabled {
opacity: 0.4;
}
.print-width {
max-width: 1500px;
}
/*
.tooltip > .tooltip-inner {
background-color: #6da252;
@ -552,4 +562,45 @@ span.picture, span.attachment {
.form-group.col-md-6 input.fake-input.form-control{
min-width: 150px;
}
}
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type="number"] {
-moz-appearance: textfield;
}
.btn-icon {
height: 34px;
}
.form-small #account_id, .form-small .select2.select2-container {
width: 90% !important;
float: left;
}
.form-small #currency {
width: 10%;
}
.input-group-recurring {
padding-left: 0 !important;
padding-right: 0 !important;
}
.popover-content, .discount.box-body, .discount.box-footer {
padding-left: 0 !important;
padding-right: 0 !important;
}
.discount-description {
margin-top: 6px;
margin-left: -20px;
}
.user.user-menu, .user.user-menu a.dropdown-toggle {
min-height: 50px;
}

193
public/css/bootstrap3-print-fix.css vendored Normal file
View File

@ -0,0 +1,193 @@
@media print {
.col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 {
float: left;
}
.col-sm-12 {
width: 100%;
}
.col-sm-11 {
width: 91.66666667%;
}
.col-sm-10 {
width: 83.33333333%;
}
.col-sm-9 {
width: 75%;
}
.col-sm-8 {
width: 66.66666667%;
}
.col-sm-7 {
width: 58.33333333%;
}
.col-sm-6 {
width: 50%;
}
.col-sm-5 {
width: 41.66666667%;
}
.col-sm-4 {
width: 33.33333333%;
}
.col-sm-3 {
width: 25%;
}
.col-sm-2 {
width: 16.66666667%;
}
.col-sm-1 {
width: 8.33333333%;
}
.col-sm-pull-12 {
right: 100%;
}
.col-sm-pull-11 {
right: 91.66666667%;
}
.col-sm-pull-10 {
right: 83.33333333%;
}
.col-sm-pull-9 {
right: 75%;
}
.col-sm-pull-8 {
right: 66.66666667%;
}
.col-sm-pull-7 {
right: 58.33333333%;
}
.col-sm-pull-6 {
right: 50%;
}
.col-sm-pull-5 {
right: 41.66666667%;
}
.col-sm-pull-4 {
right: 33.33333333%;
}
.col-sm-pull-3 {
right: 25%;
}
.col-sm-pull-2 {
right: 16.66666667%;
}
.col-sm-pull-1 {
right: 8.33333333%;
}
.col-sm-pull-0 {
right: auto;
}
.col-sm-push-12 {
left: 100%;
}
.col-sm-push-11 {
left: 91.66666667%;
}
.col-sm-push-10 {
left: 83.33333333%;
}
.col-sm-push-9 {
left: 75%;
}
.col-sm-push-8 {
left: 66.66666667%;
}
.col-sm-push-7 {
left: 58.33333333%;
}
.col-sm-push-6 {
left: 50%;
}
.col-sm-push-5 {
left: 41.66666667%;
}
.col-sm-push-4 {
left: 33.33333333%;
}
.col-sm-push-3 {
left: 25%;
}
.col-sm-push-2 {
left: 16.66666667%;
}
.col-sm-push-1 {
left: 8.33333333%;
}
.col-sm-push-0 {
left: auto;
}
.col-sm-offset-12 {
margin-left: 100%;
}
.col-sm-offset-11 {
margin-left: 91.66666667%;
}
.col-sm-offset-10 {
margin-left: 83.33333333%;
}
.col-sm-offset-9 {
margin-left: 75%;
}
.col-sm-offset-8 {
margin-left: 66.66666667%;
}
.col-sm-offset-7 {
margin-left: 58.33333333%;
}
.col-sm-offset-6 {
margin-left: 50%;
}
.col-sm-offset-5 {
margin-left: 41.66666667%;
}
.col-sm-offset-4 {
margin-left: 33.33333333%;
}
.col-sm-offset-3 {
margin-left: 25%;
}
.col-sm-offset-2 {
margin-left: 16.66666667%;
}
.col-sm-offset-1 {
margin-left: 8.33333333%;
}
.col-sm-offset-0 {
margin-left: 0%;
}
.visible-xs {
display: none !important;
}
.hidden-xs {
display: block !important;
}
table.hidden-xs {
display: table;
}
tr.hidden-xs {
display: table-row !important;
}
th.hidden-xs,
td.hidden-xs {
display: table-cell !important;
}
.hidden-xs.hidden-print {
display: none !important;
}
.hidden-sm {
display: none !important;
}
.visible-sm {
display: block !important;
}
table.visible-sm {
display: table;
}
tr.visible-sm {
display: table-row !important;
}
th.visible-sm,
td.visible-sm {
display: table-cell !important;
}
}

36
public/js/app.js vendored
View File

@ -186,6 +186,11 @@ $(document).ready(function () {
disable_input.trigger('change');
}
});
if (document.getElementById('recurring_frequency')) {
$(".input-group-recurring #recurring_frequency").select2();
$('.input-group-recurring #recurring_frequency').trigger('change');
}
});
function confirmDelete(form_id, title, message, button_cancel, button_delete) {
@ -249,3 +254,34 @@ $(document).on('click', '.popup', function(e) {
}
});
});
$(document).on('change', '.input-group-recurring #recurring_frequency', function (e) {
var value = $(this).val();
var recurring_frequency = $('#recurring_frequency').parent().parent();
var recurring_interval = $('#recurring_interval').parent();
var recurring_custom_frequency = $('#recurring_custom_frequency').parent();
var recurring_count = $('#recurring_count').parent();
if (value == 'custom') {
recurring_frequency.removeClass('col-md-12').removeClass('col-md-12').addClass('col-md-4');
recurring_interval.removeClass('hidden');
recurring_custom_frequency.removeClass('hidden');
recurring_count.removeClass('hidden');
$("#recurring_custom_frequency").select2();
} else if (value == 'no' || value == '') {
recurring_frequency.removeClass('col-md-10').removeClass('col-md-4').addClass('col-md-12');
recurring_interval.addClass('hidden');
recurring_custom_frequency.addClass('hidden');
recurring_count.addClass('hidden');
} else {
recurring_frequency.removeClass('col-md-12').removeClass('col-md-4').addClass('col-md-10');
recurring_interval.addClass('hidden');
recurring_custom_frequency.addClass('hidden');
recurring_count.removeClass('hidden');
}
});

View File

@ -12,11 +12,16 @@ return [
'quantity' => 'الكمية',
'price' => 'السعر',
'sub_total' => 'المبلغ الاجمالى',
'discount' => 'Discount',
'tax_total' => 'اجمالى الضريبة',
'total' => 'اجمالى',
'item_name' => 'اسم الصنف | اسماء الصنف',
'show_discount' => ':discount% Discount',
'add_discount' => 'Add Discount',
'discount_desc' => 'of subtotal',
'payment_due' => 'استحقاق الدفع',
'amount_due' => 'استحقاق المبلغ',
'paid' => 'مدفوع',

View File

@ -2,47 +2,49 @@
return [
'items' => 'الصنف| أصناف',
'incomes' => 'دخل | دخل',
'items' => 'الصنف|أصناف',
'incomes' => 'إيراد|إيرادات',
'invoices' => 'فاتورة|فواتير',
'revenues' => 'الايراد | الايرادات',
'customers' => 'العميل | العملاء',
'expenses' => 'المصروف | المصروفات',
'bills' => 'فاتورة شراء| فواتير شراء',
'payments' => 'دفع | مدفوعات',
'vendors' => 'المورد | الموردين',
'accounts' => 'حساب | حسابات',
'transfers' => 'تحويل | تحويلات',
'transactions' => 'عملية | عمليات',
'reports' => 'تقرير | تقارير',
'settings' => 'اعداد | اعدادات',
'categories' => 'الفئة | الفئات',
'currencies' => 'عملة | عملات',
'tax_rates' => 'معدل الضريبة | معدلات الضرائب',
'users' => 'مستخدم | مستخدمين',
'roles' => 'الوظيفة | الوظائف',
'permissions' => 'تصريح | تصريحات',
'modules' => 'تطبيق | تطبيقات',
'companies' => 'شركة | شركات',
'profits' => 'ربح | أرباح',
'taxes' => 'ضريبة | ضرائب',
'pictures' => 'صورة | صور',
'types' => 'نوع | أنواع',
'payment_methods' => 'طريقة الدفع | طرق الدفع',
'compares' => 'الايراد مقابل المصروف | الايرادات مقابل المصروفات',
'notes' => 'ملحوظة | ملاحظات',
'totals' => 'اجمالى | اجماليات',
'languages' => 'لغة | لغات',
'updates' => 'تحديث | تحديثات',
'numbers' => 'رقم | أرقام',
'statuses' => 'حالة | حالات',
'revenues' => 'الدخل|الدخل',
'customers' => 'العميل|العملاء',
'expenses' => 'المصروف|المصروفات',
'bills' => 'سند | سندات الإيصال',
'payments' => 'دفع|مدفوعات',
'vendors' => 'المورد|الموردين',
'accounts' => 'حساب|حسابات',
'transfers' => 'تحويل|تحويلات',
'transactions' => 'عملية|عمليات',
'reports' => 'تقرير|تقارير',
'settings' => 'اعداد|اعدادات',
'categories' => 'الفئة|الفئات',
'currencies' => 'عملة|عملات',
'tax_rates' => 'معدل الضريبة|معدلات الضرائب',
'users' => 'مستخدم|مستخدمين',
'roles' => 'الوظيفة|الوظائف',
'permissions' => 'تصريح|تصريحات',
'modules' => 'تطبيق|تطبيقات',
'companies' => 'شركة|شركات',
'profits' => 'ربح|أرباح',
'taxes' => 'ضريبة|ضرائب',
'logos' => 'الشعار',
'pictures' => 'صورة|صور',
'types' => 'نوع|أنواع',
'payment_methods' => 'طريقة الدفع|طرق الدفع',
'compares' => 'الإيراد مقابل المصروف|الإيرادات مقابل المصروفات',
'notes' => 'ملحوظة|ملاحظات',
'totals' => 'المجموع|الإجمالي',
'languages' => 'لغة|لغات',
'updates' => 'تحديث|تحديثات',
'numbers' => 'رقم|أرقام',
'statuses' => 'حالة|حالات',
'others' => 'Other|Others',
'dashboard' => 'لوحة التحكم',
'banking' => 'الخدمات المصرفية',
'general' => 'عام',
'no_records' => 'لا توجد سجلات.',
'date' => 'تاريخ',
'amount' => 'قيمة',
'amount' => 'المبلغ',
'enabled' => 'مفعل',
'disabled' => 'غير مفعل',
'yes' => 'نعم',
@ -52,64 +54,64 @@ return [
'monthly' => 'شهرى',
'quarterly' => 'ربع سنوي',
'yearly' => 'سنوى',
'add' => 'اضافة',
'add_new' => 'اضافة جديد',
'add' => 'إضافة',
'add_new' => 'إضافة جديد',
'show' => 'عرض',
'edit' => 'تعديل',
'delete' => 'حذف',
'send' => 'ارسال',
'download' => حميل',
'delete_confirm' => 'تأكيد الحذف : الأسم : النوع؟',
'send' => 'إرسال',
'download' => نزيل',
'delete_confirm' => 'تأكيد الحذف :الاسم :type؟',
'name' => 'الاسم',
'email' => 'البريد الالكتروني',
'tax_number' => 'رقم الضريبة',
'phone' => 'هاتف',
'address' => 'عنوان',
'address' => 'العنوان',
'website' => 'الموقع الالكتروني',
'actions' => 'الاجراءات',
'actions' => 'الإجراءات',
'description' => 'الوصف',
'manage' => 'ادارة',
'code' => 'كود',
'manage' => 'إدارة',
'code' => 'الرمز',
'alias' => 'اسم مستعار',
'balance' => 'رصيد',
'balance' => 'الرصيد',
'reference' => 'مرجع',
'attachment' => 'مرفق',
'change' => 'تغيير',
'switch' => 'تبديل',
'color' => 'لون',
'color' => 'اللون',
'save' => 'حفظ',
'cancel' => 'الغاء',
'cancel' => 'إلغاء',
'from' => 'من',
'to' => 'الى',
'to' => 'إلى',
'print' => 'طباعة',
'search' => 'بحث',
'search_placeholder' => 'اكتب للبحث..',
'filter' => 'تصفية البحث',
'help' => 'مساعدة',
'all' => 'الكل',
'all_type' => 'الكل : نوع',
'upcoming' => 'المقبل',
'created' => 'تم انشاؤه',
'id' => 'الرقم المعرفى',
'all_type' => 'الكل :type',
'upcoming' => 'القادمة',
'created' => 'تم إنشاؤه',
'id' => 'رقم الهوية',
'more_actions' => 'المزيد من الاجراءات',
'duplicate' => 'تكرار',
'unpaid' => 'Unpaid',
'paid' => 'Paid',
'overdue' => 'Overdue',
'partially' => 'Partially',
'partially_paid' => 'Partially Paid',
'unpaid' => 'غير مدفوع',
'paid' => 'مدفوع',
'overdue' => 'مبلغ متأخر',
'partially' => 'جزئي',
'partially_paid' => 'مدفوع جزئياً',
'title' => [
'new' => 'جديد : نوع',
'edit' => 'تعديل : نوع',
'new' => 'جديد :type',
'edit' => 'تعديل :type',
],
'form' => [
'enter' => 'ادخال : التخصص',
'enter' => 'إدخال :field',
'select' => [
'field' => '-اختر : التخصص-',
'file' => 'اختر ملف',
'field' => '-اختر :field-',
'file' => 'اختر ملفاً',
],
'no_file_selected' => 'لم تختر أي ملف...',
'no_file_selected' => 'لم يتم اختيار أي ملف...',
],
];

View File

@ -3,12 +3,12 @@
return [
'change_language' => 'تغيير اللغة',
'last_login' => 'أخر تسجيل دخول : الوقت',
'last_login' => 'آخر تسجيل دخول :time',
'notifications' => [
'counter' => '{0} ليس لديك تنبيهات|{1} لديك :عدد تنبيهات|[2,*] لديك :عدد تنبيهات',
'overdue_invoices' => '{1} :عدد الفواتير المتأخرة|[2,*] :عدد الفواتير المتأخرة',
'upcoming_bills' => '{1} :عدد فواتير البيع الغير مدفوعة|[2,*] :عدد فواتير المشتريات الغير مدفوعة',
'items_stock' => '{1} :عدد الأصناف الغير متوفرة بالمخزن|[2,*] :عدد الأصناف الغير متوفرة بالمخزن',
'counter' => '{0} ليس لديك تنبيهات|{1} لديك :count تنبيهات|[2,*] لديك :count تنبيهات',
'overdue_invoices' => '{1} :count فاتورة متأخرة|[2,*] :count فواتير متأخرة',
'upcoming_bills' => '{1} :count فاتورة بيع قادمة|[2,*] :count فواتير بيع قادمة',
'items_stock' => '{1} :count منتج غير متوفر|[2,*] :count منتجات غير متوفرة',
'view_all' => 'عرض الكل'
],

View File

@ -12,11 +12,16 @@ return [
'quantity' => 'الكمية',
'price' => 'السعر',
'sub_total' => 'المبلغ الاجمالى',
'discount' => 'Discount',
'tax_total' => 'اجمالى الضريبة',
'total' => 'الاجمالى',
'item_name' => 'اسم الصنف | اسماء الاصناف',
'show_discount' => ':discount% Discount',
'add_discount' => 'Add Discount',
'discount_desc' => 'of subtotal',
'payment_due' => 'استحقاق الدفع',
'paid' => 'مدفوع',
'histories' => 'سجلات',

View File

@ -10,10 +10,12 @@ return [
'imported' => ':نوع تم الاستيراد!',
],
'error' => [
'payment_add' => 'خطأ: لا يمكنك إضافة الدفع! يجب عليك التحقق من إضافة المبلغ.',
'over_payment' => 'Error: Payment not added! Amount passes the total.',
'not_user_company' => 'خطأ: غير مسموح لك بادرة هذة الشركة!',
'customer' => 'خطأ:غير مسموح لك باضافة مستخدم! :اسم استخدام هذا البريد الالكترونية.',
'customer' => 'Error: User not created! :name already uses this email address.',
'no_file' => 'خطأ: لم يتم تحديد ملف!',
'last_category' => 'Error: Can not delete the last :type category!',
'invalid_token' => 'Error: The token entered is invalid!',
],
'warning' => [
'deleted' => 'تحذير: غير مسموح لك بحذف <b>:اسم</b> لأن هذا : مرتبط ب.',

View File

@ -8,6 +8,7 @@ return [
'new' => 'جديد',
'top_free' => 'المجانيات الأعلى',
'free' => 'مجانى',
'search' => 'Search',
'install' => 'تثبيت',
'buy_now' => 'اشترى الأن',
'token_link' => '<a href="https://akaunting.com/tokens" target="_blank">اضغط هنا</a> للحصول على رمز الوصول الخاص بك API.',

View File

@ -0,0 +1,20 @@
<?php
return [
'recurring' => 'Recurring',
'every' => 'Every',
'period' => 'Period',
'times' => 'Times',
'daily' => 'Daily',
'weekly' => 'Weekly',
'monthly' => 'Monthly',
'yearly' => 'Yearly',
'custom' => 'Custom',
'days' => 'Day(s)',
'weeks' => 'Week(s)',
'months' => 'Month(s)',
'years' => 'Year(s)',
'message' => 'This is a recurring :type and the next :type will be automatically generated at :date',
];

View File

@ -7,11 +7,24 @@ return [
'this_quarter' => 'هذا الربع',
'previous_quarter' => 'الربع السابق',
'last_12_months' => 'آخر 12 شهر',
'profit_loss' => 'Profit & Loss',
'gross_profit' => 'Gross Profit',
'net_profit' => 'Net Profit',
'total_expenses' => 'Total Expenses',
'net' => 'NET',
'summary' => [
'income' => 'ملخص الايرادات',
'expense' => 'مخلص المصروفات',
'income_expense' => 'الإيرادات مقابل المصروفات',
'tax' => 'Tax Summary',
],
'quarter' => [
'1' => 'Jan-Mar',
'2' => 'Apr-Jun',
'3' => 'Jul-Sep',
'4' => 'Oct-Dec',
],
];

View File

@ -21,6 +21,11 @@ return [
'space' => 'مسافة ( )',
],
'timezone' => 'التوقيت',
'percent' => [
'title' => 'Percent (%) Position',
'before' => 'Before Number',
'after' => 'After Number',
],
],
'invoice' => [
'tab' => 'فاتورة الشراء',

View File

@ -0,0 +1,14 @@
<?php
return [
'account_name' => 'Oрганизация',
'number' => 'Номер',
'opening_balance' => 'Начално салдо',
'current_balance' => 'Текущо салдо',
'bank_name' => 'Име на банка',
'bank_phone' => 'Телефон на банка',
'bank_address' => 'Адрес на банката',
'default_account' => 'Акаунт по подразбиране',
];

View File

@ -0,0 +1,30 @@
<?php
return [
'profile' => 'Профил',
'logout' => 'Изход',
'login' => 'Вход',
'login_to' => 'Влезте, за да стартирате сесия',
'remember_me' => 'Запомни ме',
'forgot_password' => 'Забравих си паролата',
'reset_password' => 'Възстановяване на парола',
'enter_email' => 'Въведете е-мейл адрес',
'current_email' => 'Текущ имейл адрес',
'reset' => 'Възстановяване',
'never' => 'никога',
'password' => [
'current' => 'Парола',
'current_confirm' => 'Потвърждение на паролата',
'new' => 'Нова парола',
'new_confirm' => 'Потвърждение на паролата',
],
'error' => [
'self_delete' => 'Грешка: Не може да изтриете себе си!'
],
'failed' => 'Неуспешно удостоверяване на потребител.',
'disabled' => 'Този акаунт е забранен. Моля обърнете се към системния администратор.',
'throttle' => 'Твърде много опити за логин. Моля, опитайте отново след :seconds секунди.',
];

View File

@ -0,0 +1,46 @@
<?php
return [
'bill_number' => 'Фактура Номер',
'bill_date' => 'Дата фактура',
'total_price' => 'Обща цена',
'due_date' => 'Падежна дата',
'order_number' => 'Номер на поръчка',
'bill_from' => 'Фактура от',
'quantity' => 'Количество',
'price' => 'Цена',
'sub_total' => 'Междинна сума',
'discount' => 'Discount',
'tax_total' => 'Общо данък',
'total' => 'Общо',
'item_name' => 'Име на артикул | Имена на артикули',
'show_discount' => ':discount% Discount',
'add_discount' => 'Add Discount',
'discount_desc' => 'of subtotal',
'payment_due' => 'Дължимото плащане',
'amount_due' => 'Дължимата сума',
'paid' => 'Платени',
'histories' => 'История',
'payments' => 'Плащания',
'add_payment' => 'Добавяне на плащане',
'mark_received' => 'Отбелязване като получено',
'download_pdf' => 'Изтегляне на PDF',
'send_mail' => 'Изпращане на имейл',
'status' => [
'draft' => 'Чернова',
'received' => 'Получено',
'partial' => 'Частичен',
'paid' => 'Платен',
],
'messages' => [
'received' => 'Фактура, отбелязана като платена!',
],
];

View File

@ -0,0 +1,13 @@
<?php
return [
'domain' => 'Домейн',
'logo' => 'Лого',
'manage' => 'Управление на фирми',
'all' => 'Всички фирми',
'error' => [
'delete_active' => 'Грешка: Невъзможно изтриване на активна компания, моля, първо я променете!',
],
];

View File

@ -0,0 +1,18 @@
<?php
return [
'code' => 'Код',
'rate' => 'Курс',
'default' => 'Валута по подразбиране',
'decimal_mark' => 'Десетичен знак',
'thousands_separator' => 'Разделител за хилядни',
'precision' => 'Точност',
'symbol' => [
'symbol' => 'Символ',
'position' => 'Символ позиция',
'before' => 'Преди сума',
'after' => 'След сума',
]
];

View File

@ -0,0 +1,11 @@
<?php
return [
'allow_login' => 'Разрешавате ли достъп?',
'user_created' => 'Създаден потребител',
'error' => [
'email' => 'Този имейл вече е бил регистриран.'
]
];

View File

@ -0,0 +1,24 @@
<?php
return [
'total_incomes' => 'Общо приходи',
'receivables' => 'Вземания',
'open_invoices' => 'Отворени фактури',
'overdue_invoices' => 'Просрочени фактури',
'total_expenses' => 'Общо разходи',
'payables' => 'Задължения',
'open_bills' => 'Отворени задължения',
'overdue_bills' => 'Просрочени задължения',
'total_profit' => 'Обща печалба',
'open_profit' => 'Отвори печалба',
'overdue_profit' => 'Закъсняла печалба',
'cash_flow' => 'Паричен поток',
'no_profit_loss' => 'Няма печалба загуба',
'incomes_by_category' => 'Приходи по категория',
'expenses_by_category' => 'Разходи по категория',
'account_balance' => 'Салдо',
'latest_incomes' => 'Последни приходи',
'latest_expenses' => 'Последни разходи',
];

View File

@ -0,0 +1,17 @@
<?php
return [
'accounts_cash' => 'В брой',
'categories_uncat' => 'Некатегоризирани',
'categories_deposit' => 'Депозит',
'categories_sales' => 'Продажби',
'currencies_usd' => 'Американски долар',
'currencies_eur' => 'Евро',
'currencies_gbp' => 'Британска лира',
'currencies_try' => 'Турска лира',
'taxes_exempt' => 'Освободени от данъци',
'taxes_normal' => 'Нормален данък',
'taxes_sales' => 'Данък продажби',
];

View File

@ -0,0 +1,9 @@
<?php
return [
'version' => 'Версия',
'powered' => 'С подкрепата на Akaunting',
'software' => 'Безплатен счетоводен софтуер',
];

View File

@ -0,0 +1,117 @@
<?php
return [
'items' => 'Стока | Стоки',
'incomes' => 'Приход | Приходи',
'invoices' => 'Фактура | Фактури',
'revenues' => 'Приходи | Приходи',
'customers' => 'Клиент | Клиенти',
'expenses' => 'Разход| Разходи',
'bills' => 'Сметка| Сметки',
'payments' => 'Плащане | Плащания',
'vendors' => 'Доставчик | Доставчици',
'accounts' => 'Сметка | Сметки',
'transfers' => 'Трансфер | Трансфери',
'transactions' => 'Транзакция | Транзакции',
'reports' => 'Доклад | Доклади',
'settings' => 'Настройка | Настройки',
'categories' => 'Категория | Категории',
'currencies' => 'Валута | Валути',
'tax_rates' => 'Данък | Данъци',
'users' => 'Потребител | Потребители',
'roles' => 'Роля | Роли',
'permissions' => 'Позволение | Позволения',
'modules' => 'Добавка | Добавки',
'companies' => 'Компания | Компании',
'profits' => 'Печалба | Печалби',
'taxes' => 'Данък | Данъци',
'logos' => 'Лого | Лога',
'pictures' => 'Снимка | Снимки',
'types' => 'Тип | Видове',
'payment_methods' => 'Метод на плащане | Начини на плащане',
'compares' => 'Приход срещу разход | Приходи срещу разходи',
'notes' => 'Бележка | Бележки',
'totals' => 'Общо | Общи суми',
'languages' => 'Език | Езици',
'updates' => 'Актуализация | Актуализации',
'numbers' => 'Номер | Числа',
'statuses' => 'Статус | Статуси',
'others' => 'Other|Others',
'dashboard' => 'Табло',
'banking' => 'Банкиране',
'general' => 'Общи',
'no_records' => 'Няма записи.',
'date' => 'Дата',
'amount' => 'Сума',
'enabled' => 'Включен',
'disabled' => 'Изключен',
'yes' => 'Да',
'no' => 'Не',
'na' => 'Не е достъпно',
'daily' => 'Дневно',
'monthly' => 'Месечно',
'quarterly' => 'На тримесечие',
'yearly' => 'Годишно',
'add' => 'Добави',
'add_new' => 'Добави нов',
'show' => 'Покажи',
'edit' => 'Редактиране',
'delete' => 'Изтрий',
'send' => 'Изпрати',
'download' => 'Изтегли',
'delete_confirm' => 'Потвърждаване на изтриване :name :type?',
'name' => 'Име',
'email' => 'Имейл',
'tax_number' => 'Данъчен номер',
'phone' => 'Телефон',
'address' => 'Адрес',
'website' => 'Сайт',
'actions' => 'Действия',
'description' => 'Описание',
'manage' => 'Управление',
'code' => 'Код',
'alias' => 'Псевдоним',
'balance' => 'Баланс',
'reference' => 'Референция',
'attachment' => 'Прикачен файл',
'change' => 'Промени',
'switch' => 'Превключи',
'color' => 'Цвят',
'save' => 'Запиши',
'cancel' => 'Отмени',
'from' => 'От',
'to' => 'До',
'print' => 'Печат',
'search' => 'Търси',
'search_placeholder' => 'Пиши да търсиш..',
'filter' => 'Филтър',
'help' => 'Помощ',
'all' => 'Всички',
'all_type' => 'Всички :type',
'upcoming' => 'Приближаващ',
'created' => 'Създаден',
'id' => 'ID',
'more_actions' => 'Още действия',
'duplicate' => 'Дублиране',
'unpaid' => 'Неплатени',
'paid' => 'Платени',
'overdue' => 'Прослочени',
'partially' => 'Частичен',
'partially_paid' => 'Частично платено',
'title' => [
'new' => 'Нов :type',
'edit' => 'Редактирай :type',
],
'form' => [
'enter' => 'Въведи :field',
'select' => [
'field' => '- Избери :field -',
'file' => 'Изберете файл',
],
'no_file_selected' => 'Не е избран файл...',
],
];

View File

@ -0,0 +1,15 @@
<?php
return [
'change_language' => 'Промяна на езика',
'last_login' => 'Последно влизане :time',
'notifications' => [
'counter' => '{0} Нямате известия|{1} Имате :count ново известие|[2,*] Имате :count нови известия',
'overdue_invoices' => '{1} :count просрочено вземане|[2,*] :count просрочени вземания',
'upcoming_bills' => '{1} :count наближаваща фактура|[2,*] :count наближаващи фактури',
'items_stock' => '{1} :count стока без наличност|[2,*] :count стоки без наличност',
'view_all' => 'Вижте всички'
],
];

View File

@ -0,0 +1,9 @@
<?php
return [
'import' => 'Импортиране',
'title' => 'Импортиране :type',
'message' => 'Разрешени формати: CSV, XLS. Моля, <a target="_blank" href=":link"><strong>изтегли</strong></a> примерен файл.',
];

View File

@ -0,0 +1,44 @@
<?php
return [
'next' => 'Следващ',
'refresh' => 'Обновяване',
'steps' => [
'requirements' => 'Моля, изпълнете следните изисквания!',
'language' => 'Стъпка 1/3: Избор на език',
'database' => 'Стъпка 2/3: Избор на база данни',
'settings' => 'Стъпка 3/3: Детайли на компанията и администратора',
],
'language' => [
'select' => 'Изберете език',
],
'requirements' => [
'enabled' => ':feature трябва да бъде активирана!',
'disabled' => ':feature трябва да бъде дезактивирана!',
'extension' => ':extension разширението трябва да бъде заредено!',
'directory' => ':directory директорията трябва да е с разрешение за промяна!',
],
'database' => [
'hostname' => 'Име на хост',
'username' => 'Потребителско име',
'password' => 'Парола',
'name' => 'База данни',
],
'settings' => [
'company_name' => 'Име на компанията',
'company_email' => 'Имейл на компанията',
'admin_email' => 'Администраторски имейл',
'admin_password' => 'Администраторска парола',
],
'error' => [
'connection' => 'Грешка: Не можа да се свърже с базата данни! Моля, уверете се, че данните са правилни.',
],
];

View File

@ -0,0 +1,55 @@
<?php
return [
'invoice_number' => 'Номер на фактура',
'invoice_date' => 'Дата на фактура',
'total_price' => 'Обща цена',
'due_date' => 'Падежна дата',
'order_number' => 'Номер на поръчка',
'bill_to' => 'Издадена на',
'quantity' => 'Количество',
'price' => 'Цена',
'sub_total' => 'Междинна сума',
'discount' => 'Discount',
'tax_total' => 'Общо данък',
'total' => 'Общо',
'item_name' => 'Име на артикул | Имена на артикули',
'show_discount' => ':discount% Discount',
'add_discount' => 'Add Discount',
'discount_desc' => 'of subtotal',
'payment_due' => 'Дължимото плащане',
'paid' => 'Платен',
'histories' => 'История',
'payments' => 'Плащания',
'add_payment' => 'Добавяне на плащане',
'mark_paid' => 'Отбележи като платено',
'mark_sent' => 'Маркирай като изпратено',
'download_pdf' => 'Изтегляне на PDF',
'send_mail' => 'Изпращане на имейл',
'status' => [
'draft' => 'Чернова',
'sent' => 'Изпратено',
'viewed' => 'Разгледани',
'approved' => 'Одобрени',
'partial' => 'Частичен',
'paid' => 'Платен',
],
'messages' => [
'email_sent' => 'И-мейла беше изпратен успешно!',
'marked_sent' => 'Фактурата беше изпратена успешно!',
'email_required' => 'Няма имейл адрес за този клиент!',
],
'notification' => [
'message' => 'Вие получавате този имейл, защото имате предстояща фактура за плащане на стойност :amount на :customer.',
'button' => 'Плати сега',
],
];

View File

@ -0,0 +1,15 @@
<?php
return [
'quantities' => 'Количество | Количества',
'sales_price' => 'Продажна цена',
'purchase_price' => 'Покупна цена',
'sku' => 'SKU',
'notification' => [
'message' => 'Вие получавате този имейл, защото :name е на изчерпване.',
'button' => 'Покажи сега',
],
];

View File

@ -0,0 +1,25 @@
<?php
return [
'success' => [
'added' => ':type добавен!',
'updated' => ':type променен!',
'deleted' => ':type изтрит!',
'duplicated' => ':type дублиран!',
'imported' => ':type импортиран!',
],
'error' => [
'over_payment' => 'Грешка: Плащането не е добавено! Сумата преминава общата сума.',
'not_user_company' => 'Грешка: Не ви е позволено да управлявате тази компания!',
'customer' => 'Грешка: Потребителят не е създаден! :name вече използва този имейл адрес.',
'no_file' => 'Грешка: Няма избран файл!',
'last_category' => 'Error: Can not delete the last :type category!',
'invalid_token' => 'Error: The token entered is invalid!',
],
'warning' => [
'deleted' => 'Предупреждение: Не ви е позволено да изтриете <b>:name</b>, защото има :text свързан.',
'disabled' => 'Предупреждение: Не ви е позволено да деактивирате <b>:name</b>, защото има :text свързан.',
],
];

View File

@ -0,0 +1,48 @@
<?php
return [
'title' => 'API Token',
'api_token' => 'Token',
'top_paid' => 'Топ платени',
'new' => 'Нов',
'top_free' => 'Топ безплатни',
'free' => 'БЕЗПЛАТНО',
'search' => 'Search',
'install' => 'Инсталирай',
'buy_now' => 'Купете сега',
'token_link' => '<a href="https://akaunting.com/tokens" target="_blank">Натиснете тук</a> за да получите Вашия API token.',
'no_apps' => 'Все още няма приложения в тази категория.',
'developer' => 'Вие сте разработчик? <a href="https://akaunting.com/blog/akaunting-app-store" target="_blank">Тук</a> можете да научите как да създадете приложение и да започнете да продавате днес!',
'about' => 'Относно',
'added' => 'Добавено',
'updated' => 'Обновено',
'compatibility' => 'Съвместимост',
'installed' => ':module инсталиран',
'uninstalled' => ':module деинсталиран',
//'updated' => ':module updated',
'enabled' => ':module включен',
'disabled' => ':module изключен',
'tab' => [
'installation' => 'Инсталация',
'faq' => 'ЧЗВ',
'changelog' => 'Списък на промените',
],
'installation' => [
'header' => 'Инсталиране на приложение',
'download' => 'Изтегляне :module файл.',
'unzip' => 'Извличане :module файлове.',
'install' => 'Инсталиране :module файлове.',
],
'button' => [
'uninstall' => 'Деинсталирай',
'disable' => 'Изключи',
'enable' => 'Активирай',
],
];

View File

@ -0,0 +1,9 @@
<?php
return [
'previous' => '&laquo; Предишна',
'next' => 'Следваща &raquo;',
'showing' => 'Показване на :first до :last от общо :total :type',
];

View File

@ -0,0 +1,22 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Password Reset Language Lines
|--------------------------------------------------------------------------
|
| The following language lines are the default lines which match reasons
| that are given by the password broker for a password update attempt
| has failed, such as for an invalid token or invalid new password.
|
*/
'password' => 'Паролата трябва да бъде поне шест знака и да съвпада.',
'reset' => 'Паролата е ресетната!',
'sent' => 'Изпратено е напомняне за вашата парола!',
'token' => 'Този токен за ресет на парола е невалиден.',
'user' => "Потребител с такъв e-mail адрес не може да бъде открит.",
];

View File

@ -0,0 +1,20 @@
<?php
return [
'recurring' => 'Recurring',
'every' => 'Every',
'period' => 'Period',
'times' => 'Times',
'daily' => 'Daily',
'weekly' => 'Weekly',
'monthly' => 'Monthly',
'yearly' => 'Yearly',
'custom' => 'Custom',
'days' => 'Day(s)',
'weeks' => 'Week(s)',
'months' => 'Month(s)',
'years' => 'Year(s)',
'message' => 'This is a recurring :type and the next :type will be automatically generated at :date',
];

View File

@ -0,0 +1,30 @@
<?php
return [
'this_year' => 'Текущата година',
'previous_year' => 'Миналата година',
'this_quarter' => 'Текущото тримесечие',
'previous_quarter' => 'Предходното тримесечие',
'last_12_months' => 'Последните 12 месеца',
'profit_loss' => 'Profit & Loss',
'gross_profit' => 'Gross Profit',
'net_profit' => 'Net Profit',
'total_expenses' => 'Total Expenses',
'net' => 'NET',
'summary' => [
'income' => 'Приходи',
'expense' => 'Разходи',
'income_expense' => 'Приходи срещу разходи',
'tax' => 'Tax Summary',
],
'quarter' => [
'1' => 'Jan-Mar',
'2' => 'Apr-Jun',
'3' => 'Jul-Sep',
'4' => 'Oct-Dec',
],
];

View File

@ -0,0 +1,90 @@
<?php
return [
'company' => [
'name' => 'Име',
'email' => 'Имейл',
'phone' => 'Телефон',
'address' => 'Адрес',
'logo' => 'Лого',
],
'localisation' => [
'tab' => 'Локализиране',
'date' => [
'format' => 'Формат на датата',
'separator' => 'Разделител за дата',
'dash' => 'Тире (-)',
'dot' => 'Точка (.)',
'comma' => 'Запетая (,)',
'slash' => 'Наклонена черта (/)',
'space' => 'Празно място ( )',
],
'timezone' => 'Часова зона',
'percent' => [
'title' => 'Percent (%) Position',
'before' => 'Before Number',
'after' => 'After Number',
],
],
'invoice' => [
'tab' => 'Фактура',
'prefix' => 'Префикс',
'digit' => 'Брой цифри',
'next' => 'Следващия номер',
'logo' => 'Лого',
],
'default' => [
'tab' => 'По подразбиране',
'account' => 'Акаунт по подразбиране',
'currency' => 'Валута по подразбиране',
'tax' => 'Данъчна ставка по подразбиране',
'payment' => 'Начин на плащане по подразбиране',
'language' => 'Език по подразбиране',
],
'email' => [
'protocol' => 'Протокол',
'php' => 'PHP Mail',
'smtp' => [
'name' => 'SMTP',
'host' => 'SMTP хост',
'port' => 'SMTP порт',
'username' => 'SMTP потребител',
'password' => 'SMTP парола',
'encryption' => 'SMTP сигурност',
'none' => 'Няма',
],
'sendmail' => 'Sendmail',
'sendmail_path' => 'Път към Sendmail',
'log' => 'Лог имейли',
],
'scheduling' => [
'tab' => 'График на дейностите',
'send_invoice' => 'Изпрати напомняне за фактура',
'invoice_days' => 'Изпрати след забавени дни',
'send_bill' => 'Изпрати напомняне за фактура',
'bill_days' => 'Изпрати преди забавени дни',
'cron_command' => 'Грешна команда',
'schedule_time' => 'Час за стартиране',
],
'appearance' => [
'tab' => 'Външен вид',
'theme' => 'Тема',
'light' => 'Светла',
'dark' => 'Тъмна',
'list_limit' => 'Резултати на страница',
'use_gravatar' => 'Използвай Gravatar',
],
'system' => [
'tab' => 'Система',
'session' => [
'lifetime' => 'Сесия живот (минути)',
'handler' => 'Управление на сесиите',
'file' => 'Файл',
'database' => 'База данни',
],
'file_size' => 'Макс размер на файл (МБ)',
'file_types' => 'Разрешени типове файлове',
],
];

View File

@ -0,0 +1,8 @@
<?php
return [
'rate' => 'Данък',
'rate_percent' => 'Ставка (%)',
];

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