mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-08 19:24:40 -04:00
Merge remote-tracking branch 'remotes/ninja/master'
This commit is contained in:
commit
ba7cf8b3a9
10
.env.example
10
.env.example
@ -3,9 +3,9 @@ APP_DEBUG=false
|
||||
APP_URL=http://ninja.dev
|
||||
APP_CIPHER=rijndael-128
|
||||
APP_KEY=SomeRandomString
|
||||
APP_TIMEZONE
|
||||
|
||||
DB_TYPE=mysql
|
||||
DB_STRICT=false
|
||||
DB_HOST=localhost
|
||||
DB_DATABASE=ninja
|
||||
DB_USERNAME
|
||||
@ -19,3 +19,11 @@ MAIL_USERNAME
|
||||
MAIL_FROM_ADDRESS
|
||||
MAIL_FROM_NAME
|
||||
MAIL_PASSWORD
|
||||
|
||||
PHANTOMJS_CLOUD_KEY='a-demo-key-with-low-quota-per-ip-address'
|
||||
LOG=single
|
||||
REQUIRE_HTTPS=false
|
||||
|
||||
GOOGLE_CLIENT_ID
|
||||
GOOGLE_CLIENT_SECRET
|
||||
GOOGLE_OAUTH_REDIRECT=http://ninja.dev/auth/google
|
8
.gitignore
vendored
8
.gitignore
vendored
@ -14,7 +14,7 @@
|
||||
/vendor
|
||||
/node_modules
|
||||
/.DS_Store
|
||||
/Thumbs.db
|
||||
Thumbs.db
|
||||
/.env
|
||||
/.env.development.php
|
||||
/.env.php
|
||||
@ -30,3 +30,9 @@
|
||||
/.idea
|
||||
/.project
|
||||
tests/_output/
|
||||
tests/_bootstrap.php
|
||||
|
||||
# composer stuff
|
||||
/c3.php
|
||||
|
||||
_ide_helper.php
|
@ -2,4 +2,7 @@
|
||||
RewriteEngine On
|
||||
RewriteRule "^.env" - [F,L]
|
||||
RewriteRule "^storage" - [F,L]
|
||||
|
||||
# https://coderwall.com/p/erbaig/laravel-s-htaccess-to-remove-public-from-url
|
||||
# RewriteRule ^(.*)$ public/$1 [L]
|
||||
</IfModule>
|
||||
|
52
Gruntfile.js
52
Gruntfile.js
@ -2,6 +2,39 @@ module.exports = function(grunt) {
|
||||
|
||||
grunt.initConfig({
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
dump_dir: (function() {
|
||||
var out = {};
|
||||
|
||||
grunt.file.expand({ filter: 'isDirectory'}, 'public/fonts/invoice-fonts/*').forEach(function(path) {
|
||||
var fontName = /[^/]*$/.exec(path)[0],
|
||||
files = {},
|
||||
license='';
|
||||
|
||||
// Add license text
|
||||
grunt.file.expand({ filter: 'isFile'}, path+'/*.txt').forEach(function(path) {
|
||||
var licenseText = grunt.file.read(path);
|
||||
|
||||
// Fix anything that could escape from the comment
|
||||
licenseText = licenseText.replace(/\*\//g,'*\\/');
|
||||
|
||||
license += "/*\n"+licenseText+"\n*/";
|
||||
});
|
||||
|
||||
// Create files list
|
||||
files['public/js/vfs_fonts/'+fontName+'.js'] = [path+'/*.ttf'];
|
||||
|
||||
out[fontName] = {
|
||||
options: {
|
||||
pre: license+'window.ninjaFontVfs=window.ninjaFontVfs||{};window.ninjaFontVfs.'+fontName+'=',
|
||||
rootPath: path+'/'
|
||||
},
|
||||
files: files
|
||||
};
|
||||
});
|
||||
|
||||
// Return the computed object
|
||||
return out;
|
||||
}()),
|
||||
concat: {
|
||||
options: {
|
||||
process: function(src, filepath) {
|
||||
@ -67,6 +100,7 @@ module.exports = function(grunt) {
|
||||
'public/vendor/spectrum/spectrum.js',
|
||||
'public/vendor/jspdf/dist/jspdf.min.js',
|
||||
'public/vendor/moment/min/moment.min.js',
|
||||
'public/vendor/moment-timezone/builds/moment-timezone-with-data.min.js',
|
||||
//'public/vendor/moment-duration-format/lib/moment-duration-format.js',
|
||||
//'public/vendor/handsontable/dist/jquery.handsontable.full.min.js',
|
||||
//'public/vendor/pdfmake/build/pdfmake.min.js',
|
||||
@ -119,25 +153,33 @@ module.exports = function(grunt) {
|
||||
src: [
|
||||
'public/vendor/bootstrap/dist/css/bootstrap.min.css',
|
||||
'public/vendor/font-awesome/css/font-awesome.min.css',
|
||||
/*
|
||||
'public/css/bootstrap.splash.css',
|
||||
'public/css/splash.css',
|
||||
*/
|
||||
'public/css/bootstrap-combobox.css',
|
||||
'public/vendor/datatables/media/css/jquery.dataTables.css',
|
||||
'public/vendor/datatables-bootstrap3/BS3/assets/css/datatables.css',
|
||||
'public/css/public.style.css',
|
||||
],
|
||||
dest: 'public/css/built.public.css',
|
||||
nonull: true,
|
||||
options: {
|
||||
process: false
|
||||
}
|
||||
},
|
||||
js_pdf: {
|
||||
src: [
|
||||
'public/js/pdf_viewer.js',
|
||||
'public/js/compatibility.js',
|
||||
'public/js/pdfmake.min.js',
|
||||
'public/js/vfs_fonts.js',
|
||||
],
|
||||
dest: 'public/js/pdf.built.js',
|
||||
nonull: true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
grunt.loadNpmTasks('grunt-contrib-concat');
|
||||
grunt.loadNpmTasks('grunt-dump-dir');
|
||||
|
||||
grunt.registerTask('default', ['concat']);
|
||||
grunt.registerTask('default', ['dump_dir', 'concat']);
|
||||
|
||||
};
|
||||
|
2
LICENSE
2
LICENSE
@ -1,5 +1,5 @@
|
||||
Attribution Assurance License
|
||||
Copyright (c) 2014 by Hillel Coren
|
||||
Copyright (c) 2015 by Hillel Coren
|
||||
http://www.hillelcoren.com
|
||||
|
||||
All Rights Reserved
|
||||
|
@ -1,7 +1,6 @@
|
||||
<?php namespace App\Commands;
|
||||
<?php namespace app\Commands;
|
||||
|
||||
abstract class Command {
|
||||
|
||||
//
|
||||
abstract class Command
|
||||
{
|
||||
|
||||
}
|
||||
|
@ -46,9 +46,83 @@ class CheckData extends Command {
|
||||
public function fire()
|
||||
{
|
||||
$this->info(date('Y-m-d') . ' Running CheckData...');
|
||||
$today = new DateTime();
|
||||
|
||||
if (!$this->option('client_id')) {
|
||||
$this->checkPaidToDate();
|
||||
}
|
||||
|
||||
$this->checkBalances();
|
||||
|
||||
$this->checkAccountData();
|
||||
|
||||
$this->info('Done');
|
||||
}
|
||||
|
||||
private function checkAccountData()
|
||||
{
|
||||
$tables = [
|
||||
'activities' => [
|
||||
ENTITY_INVOICE,
|
||||
ENTITY_CLIENT,
|
||||
ENTITY_CONTACT,
|
||||
ENTITY_PAYMENT,
|
||||
ENTITY_INVITATION,
|
||||
ENTITY_USER
|
||||
],
|
||||
'invoices' => [
|
||||
ENTITY_CLIENT,
|
||||
ENTITY_USER
|
||||
],
|
||||
'payments' => [
|
||||
ENTITY_INVOICE,
|
||||
ENTITY_CLIENT,
|
||||
ENTITY_USER,
|
||||
ENTITY_INVITATION,
|
||||
ENTITY_CONTACT
|
||||
],
|
||||
'tasks' => [
|
||||
ENTITY_INVOICE,
|
||||
ENTITY_CLIENT,
|
||||
ENTITY_USER
|
||||
],
|
||||
'credits' => [
|
||||
ENTITY_CLIENT,
|
||||
ENTITY_USER
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($tables as $table => $entityTypes) {
|
||||
foreach ($entityTypes as $entityType) {
|
||||
$records = DB::table($table)
|
||||
->join("{$entityType}s", "{$entityType}s.id", '=', "{$table}.{$entityType}_id");
|
||||
|
||||
if ($entityType != ENTITY_CLIENT) {
|
||||
$records = $records->join('clients', 'clients.id', '=', "{$table}.client_id");
|
||||
}
|
||||
|
||||
$records = $records->where("{$table}.account_id", '!=', DB::raw("{$entityType}s.account_id"))
|
||||
->get(["{$table}.id", "clients.account_id", "clients.user_id"]);
|
||||
|
||||
if (count($records)) {
|
||||
$this->info(count($records) . " {$table} records with incorrect {$entityType} account id");
|
||||
|
||||
if ($this->option('fix') == 'true') {
|
||||
foreach ($records as $record) {
|
||||
DB::table($table)
|
||||
->where('id', $record->id)
|
||||
->update([
|
||||
'account_id' => $record->account_id,
|
||||
'user_id' => $record->user_id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function checkPaidToDate()
|
||||
{
|
||||
// update client paid_to_date value
|
||||
$clients = DB::table('clients')
|
||||
->join('payments', 'payments.client_id', '=', 'clients.id')
|
||||
@ -69,6 +143,8 @@ class CheckData extends Command {
|
||||
}
|
||||
}
|
||||
|
||||
private function checkBalances()
|
||||
{
|
||||
// find all clients where the balance doesn't equal the sum of the outstanding invoices
|
||||
$clients = DB::table('clients')
|
||||
->join('invoices', 'invoices.client_id', '=', 'clients.id')
|
||||
@ -98,7 +174,7 @@ class CheckData extends Command {
|
||||
$activities = DB::table('activities')
|
||||
->where('client_id', '=', $client->id)
|
||||
->orderBy('activities.id')
|
||||
->get(['activities.id', 'activities.created_at', 'activities.activity_type_id', 'activities.message', 'activities.adjustment', 'activities.balance', 'activities.invoice_id']);
|
||||
->get(['activities.id', 'activities.created_at', 'activities.activity_type_id', 'activities.adjustment', 'activities.balance', 'activities.invoice_id']);
|
||||
//$this->info(var_dump($activities));
|
||||
|
||||
foreach ($activities as $activity) {
|
||||
@ -111,7 +187,7 @@ class CheckData extends Command {
|
||||
->first(['invoices.amount', 'invoices.is_recurring', 'invoices.is_quote', 'invoices.deleted_at', 'invoices.id', 'invoices.is_deleted']);
|
||||
|
||||
// Check if this invoice was once set as recurring invoice
|
||||
if (!$invoice->is_recurring && DB::table('invoices')
|
||||
if ($invoice && !$invoice->is_recurring && DB::table('invoices')
|
||||
->where('recurring_invoice_id', '=', $activity->invoice_id)
|
||||
->first(['invoices.id'])) {
|
||||
$invoice->is_recurring = 1;
|
||||
@ -197,7 +273,7 @@ class CheckData extends Command {
|
||||
$activityFix = 0;
|
||||
}
|
||||
} else if ($activity->activity_type_id == ACTIVITY_TYPE_DELETE_PAYMENT) {
|
||||
// **Fix for delting payment after deleting invoice**
|
||||
// **Fix for deleting payment after deleting invoice**
|
||||
if ($activity->adjustment != 0 && $invoice->is_deleted && $activity->created_at > $invoice->deleted_at) {
|
||||
$this->info("Incorrect adjustment for deleted payment adjustment:{$activity->adjustment}");
|
||||
$foundProblem = true;
|
||||
@ -235,7 +311,6 @@ class CheckData extends Command {
|
||||
'updated_at' => new Carbon,
|
||||
'account_id' => $client->account_id,
|
||||
'client_id' => $client->id,
|
||||
'message' => 'Recovered update to invoice [<a href="https://github.com/hillelcoren/invoice-ninja/releases/tag/v1.7.1" target="_blank">details</a>]',
|
||||
'adjustment' => $client->actual_balance - $activity->balance,
|
||||
'balance' => $client->actual_balance,
|
||||
]);
|
||||
@ -250,8 +325,6 @@ class CheckData extends Command {
|
||||
->update($data);
|
||||
}
|
||||
}
|
||||
|
||||
$this->info('Done');
|
||||
}
|
||||
|
||||
protected function getArguments()
|
||||
|
@ -1,88 +0,0 @@
|
||||
<?php namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
|
||||
class CreateRandomData extends Command {
|
||||
|
||||
protected $name = 'ninja:create-data';
|
||||
protected $description = 'Create random data';
|
||||
|
||||
public function fire()
|
||||
{
|
||||
$this->info(date('Y-m-d') . ' Running CreateRandomData...');
|
||||
|
||||
$user = User::first();
|
||||
|
||||
if (!$user) {
|
||||
$this->error("Error: please create user account by logging in");
|
||||
return;
|
||||
}
|
||||
|
||||
$productNames = ['Arkansas', 'New York', 'Arizona', 'California', 'Colorado', 'Alabama', 'Connecticut', 'Delaware', 'Florida', 'Georgia', 'Hawaii', 'Idaho', 'Illinois', 'Indiana', 'Iowa', 'Kansas', 'Kentucky', 'Louisiana', 'Maine', 'Maryland', 'Massachusetts', 'Michigan', 'Minnesota', 'Mississippi', 'Missouri', 'Montana', 'Nebraska', 'Nevada', 'New Hampshire', 'New Jersey', 'New Mexico', 'Alaska', 'North Carolina', 'North Dakota', 'Ohio', 'Oklahoma', 'Oregon', 'Pennsylvania', 'Rhode Island', 'South Carolina', 'South Dakota', 'Tennessee', 'Texas', 'Utah', 'Vermont', 'Virginia', 'Washington', 'West Virginia', 'Wisconsin', 'Wyoming'];
|
||||
$clientNames = ['IBM', 'Nestle', 'Mitsubishi UFJ Financial', 'Vodafone', 'Eni', 'Procter & Gamble', 'Johnson & Johnson', 'American International Group', 'Banco Santander', 'BHP Billiton', 'Pfizer', 'Itaú Unibanco Holding', 'Ford Motor', 'BMW Group', 'Commonwealth Bank', 'EDF', 'Statoil', 'Google', 'Siemens', 'Novartis', 'Royal Bank of Canada', 'Sumitomo Mitsui Financial', 'Comcast', 'Sberbank', 'Goldman Sachs Group', 'Westpac Banking Group', 'Nippon Telegraph & Tel', 'Ping An Insurance Group', 'Banco Bradesco', 'Anheuser-Busch InBev', 'Bank of Communications', 'China Life Insurance', 'General Motors', 'Telefónica', 'MetLife', 'Honda Motor', 'Enel', 'BASF', 'Softbank', 'National Australia Bank', 'ANZ', 'ConocoPhillips', 'TD Bank Group', 'Intel', 'UBS', 'Hewlett-Packard', 'Coca-Cola', 'Cisco Systems', 'UnitedHealth Group', 'Boeing', 'Zurich Insurance Group', 'Hyundai Motor', 'Sanofi', 'Credit Agricole', 'United Technologies', 'Roche Holding', 'Munich Re', 'PepsiCo', 'Oracle', 'Bank of Nova Scotia'];
|
||||
|
||||
foreach ($productNames as $i => $value) {
|
||||
$product = Product::createNew($user);
|
||||
$product->id = $i+1;
|
||||
$product->product_key = $value;
|
||||
$product->save();
|
||||
}
|
||||
|
||||
foreach ($clientNames as $i => $value) {
|
||||
$client = Client::createNew($user);
|
||||
$client->name = $value;
|
||||
$client->save();
|
||||
|
||||
$contact = Contact::createNew($user);
|
||||
$contact->email = "client@aol.com";
|
||||
$contact->is_primary = 1;
|
||||
$client->contacts()->save($contact);
|
||||
|
||||
$numInvoices = rand(1, 25);
|
||||
if ($numInvoices == 4 || $numInvoices == 10 || $numInvoices == 25) {
|
||||
// leave these
|
||||
} else if ($numInvoices % 3 == 0) {
|
||||
$numInvoices = 1;
|
||||
} else if ($numInvoices > 10) {
|
||||
$numInvoices = $numInvoices % 2;
|
||||
}
|
||||
|
||||
$paidUp = rand(0, 1) == 1;
|
||||
|
||||
for ($j=1; $j<=$numInvoices; $j++) {
|
||||
|
||||
$price = rand(10, 1000);
|
||||
if ($price < 900) {
|
||||
$price = rand(10, 150);
|
||||
}
|
||||
|
||||
$invoice = Invoice::createNew($user);
|
||||
$invoice->invoice_number = $user->account->getNextInvoiceNumber();
|
||||
$invoice->amount = $invoice->balance = $price;
|
||||
$invoice->created_at = date('Y-m-d', strtotime(date("Y-m-d") . ' - ' . rand(1, 100) . ' days'));
|
||||
$client->invoices()->save($invoice);
|
||||
|
||||
$productId = rand(0, 40);
|
||||
if ($productId > 20) {
|
||||
$productId = ($productId % 2) + rand(0, 2);
|
||||
}
|
||||
|
||||
$invoiceItem = InvoiceItem::createNew($user);
|
||||
$invoiceItem->product_id = $productId+1;
|
||||
$invoiceItem->product_key = $productNames[$invoiceItem->product_id];
|
||||
$invoiceItem->cost = $invoice->amount;
|
||||
$invoiceItem->qty = 1;
|
||||
$invoice->invoice_items()->save($invoiceItem);
|
||||
|
||||
if ($paidUp || rand(0,2) > 1) {
|
||||
$payment = Payment::createNew($user);
|
||||
$payment->invoice_id = $invoice->id;
|
||||
$payment->amount = $invoice->amount;
|
||||
$client->payments()->save($payment);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -33,16 +33,22 @@ class SendRecurringInvoices extends Command
|
||||
$today = new DateTime();
|
||||
|
||||
$invoices = Invoice::with('account.timezone', 'invoice_items', 'client', 'user')
|
||||
->whereRaw('is_deleted IS FALSE AND deleted_at IS NULL AND is_recurring IS TRUE AND start_date <= ? AND (end_date IS NULL OR end_date >= ?)', array($today, $today))->get();
|
||||
->whereRaw('is_deleted IS FALSE AND deleted_at IS NULL AND is_recurring IS TRUE AND frequency_id > 0 AND start_date <= ? AND (end_date IS NULL OR end_date >= ?)', array($today, $today))
|
||||
->orderBy('id', 'asc')
|
||||
->get();
|
||||
$this->info(count($invoices).' recurring invoice(s) found');
|
||||
|
||||
foreach ($invoices as $recurInvoice) {
|
||||
$this->info('Processing Invoice '.$recurInvoice->id.' - Should send '.($recurInvoice->shouldSendToday() ? 'YES' : 'NO'));
|
||||
if (!$recurInvoice->user->confirmed) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$recurInvoice->account->loadLocalizationSettings($recurInvoice->client);
|
||||
$this->info('Processing Invoice '.$recurInvoice->id.' - Should send '.($recurInvoice->shouldSendToday() ? 'YES' : 'NO'));
|
||||
$invoice = $this->invoiceRepo->createRecurringInvoice($recurInvoice);
|
||||
|
||||
if ($invoice) {
|
||||
$recurInvoice->account->loadLocalizationSettings();
|
||||
if ($invoice && !$invoice->isPaid()) {
|
||||
$this->info('Sending Invoice');
|
||||
$this->mailer->sendInvoice($invoice);
|
||||
}
|
||||
}
|
||||
|
70
app/Console/Commands/SendReminders.php
Normal file
70
app/Console/Commands/SendReminders.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php namespace App\Console\Commands;
|
||||
|
||||
use DB;
|
||||
use DateTime;
|
||||
use Illuminate\Console\Command;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use App\Models\Account;
|
||||
use App\Ninja\Mailers\ContactMailer as Mailer;
|
||||
use App\Ninja\Repositories\accountRepository;
|
||||
use App\Ninja\Repositories\InvoiceRepository;
|
||||
|
||||
class SendReminders extends Command
|
||||
{
|
||||
protected $name = 'ninja:send-reminders';
|
||||
protected $description = 'Send reminder emails';
|
||||
protected $mailer;
|
||||
protected $invoiceRepo;
|
||||
protected $accountRepo;
|
||||
|
||||
public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, AccountRepository $accountRepo)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->mailer = $mailer;
|
||||
$this->invoiceRepo = $invoiceRepo;
|
||||
$this->accountRepo = $accountRepo;
|
||||
}
|
||||
|
||||
public function fire()
|
||||
{
|
||||
$this->info(date('Y-m-d').' Running SendReminders...');
|
||||
$today = new DateTime();
|
||||
|
||||
$accounts = $this->accountRepo->findWithReminders();
|
||||
$this->info(count($accounts).' accounts found');
|
||||
|
||||
foreach ($accounts as $account) {
|
||||
if (!$account->isPro()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$invoices = $this->invoiceRepo->findNeedingReminding($account);
|
||||
$this->info($account->name . ': ' . count($invoices).' invoices found');
|
||||
|
||||
foreach ($invoices as $invoice) {
|
||||
if ($reminder = $account->getInvoiceReminder($invoice)) {
|
||||
$this->info('Send to ' . $invoice->id);
|
||||
$this->mailer->sendInvoice($invoice, $reminder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->info('Done');
|
||||
}
|
||||
|
||||
protected function getArguments()
|
||||
{
|
||||
return array(
|
||||
//array('example', InputArgument::REQUIRED, 'An example argument.'),
|
||||
);
|
||||
}
|
||||
|
||||
protected function getOptions()
|
||||
{
|
||||
return array(
|
||||
//array('example', null, InputOption::VALUE_OPTIONAL, 'An example option.', null),
|
||||
);
|
||||
}
|
||||
}
|
@ -28,14 +28,34 @@ class SendRenewalInvoices extends Command
|
||||
{
|
||||
$this->info(date('Y-m-d').' Running SendRenewalInvoices...');
|
||||
$today = new DateTime();
|
||||
$sentTo = [];
|
||||
|
||||
$accounts = Account::whereRaw('datediff(curdate(), pro_plan_paid) = 355')->get();
|
||||
// get all accounts with pro plans expiring in 10 days
|
||||
$accounts = Account::whereRaw('datediff(curdate(), pro_plan_paid) = 355')
|
||||
->orderBy('id')
|
||||
->get();
|
||||
$this->info(count($accounts).' accounts found');
|
||||
|
||||
foreach ($accounts as $account) {
|
||||
// don't send multiple invoices to multi-company users
|
||||
if ($userAccountId = $this->accountRepo->getUserAccountId($account)) {
|
||||
if (isset($sentTo[$userAccountId])) {
|
||||
continue;
|
||||
} else {
|
||||
$sentTo[$userAccountId] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$client = $this->accountRepo->getNinjaClient($account);
|
||||
$invitation = $this->accountRepo->createNinjaInvoice($client);
|
||||
$this->mailer->sendInvoice($invitation->invoice);
|
||||
|
||||
// set the due date to 10 days from now
|
||||
$invoice = $invitation->invoice;
|
||||
$invoice->due_date = date('Y-m-d', strtotime('+ 10 days'));
|
||||
$invoice->save();
|
||||
|
||||
$this->mailer->sendInvoice($invoice);
|
||||
$this->info("Sent invoice to {$client->getDisplayName()}");
|
||||
}
|
||||
|
||||
$this->info('Done');
|
||||
|
30
app/Console/Commands/TestOFX.php
Normal file
30
app/Console/Commands/TestOFX.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php namespace app\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Services\BankAccountService;
|
||||
|
||||
class TestOFX extends Command
|
||||
{
|
||||
protected $name = 'ninja:test-ofx';
|
||||
protected $description = 'Test OFX';
|
||||
|
||||
public function __construct(BankAccountService $bankAccountService)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->bankAccountService = $bankAccountService;
|
||||
}
|
||||
|
||||
public function fire()
|
||||
{
|
||||
$this->info(date('Y-m-d').' Running TestOFX...');
|
||||
|
||||
$bankId = env('TEST_BANK_ID');
|
||||
$username = env('TEST_BANK_USERNAME');
|
||||
$password = env('TEST_BANK_PASSWORD');
|
||||
|
||||
$data = $this->bankAccountService->loadBankAccounts($bankId, $username, $password, false);
|
||||
|
||||
print "<pre>".print_r($data, 1)."</pre>";
|
||||
}
|
||||
}
|
@ -1,10 +1,11 @@
|
||||
<?php namespace App\Console;
|
||||
<?php namespace app\Console;
|
||||
|
||||
use Utils;
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||
|
||||
class Kernel extends ConsoleKernel {
|
||||
|
||||
class Kernel extends ConsoleKernel
|
||||
{
|
||||
/**
|
||||
* The Artisan commands provided by your application.
|
||||
*
|
||||
@ -12,10 +13,11 @@ class Kernel extends ConsoleKernel {
|
||||
*/
|
||||
protected $commands = [
|
||||
'App\Console\Commands\SendRecurringInvoices',
|
||||
'App\Console\Commands\CreateRandomData',
|
||||
'App\Console\Commands\ResetData',
|
||||
'App\Console\Commands\CheckData',
|
||||
'App\Console\Commands\SendRenewalInvoices',
|
||||
'App\Console\Commands\SendReminders',
|
||||
'App\Console\Commands\TestOFX',
|
||||
];
|
||||
|
||||
/**
|
||||
@ -26,8 +28,24 @@ class Kernel extends ConsoleKernel {
|
||||
*/
|
||||
protected function schedule(Schedule $schedule)
|
||||
{
|
||||
// $schedule->command('inspire')
|
||||
// ->hourly();
|
||||
}
|
||||
$logFile = storage_path() . '/logs/cron.log';
|
||||
|
||||
$schedule
|
||||
->command('ninja:send-invoices --force')
|
||||
->sendOutputTo($logFile)
|
||||
->withoutOverlapping()
|
||||
->hourly();
|
||||
|
||||
$schedule
|
||||
->command('ninja:send-reminders --force')
|
||||
->sendOutputTo($logFile)
|
||||
->daily();
|
||||
|
||||
if (Utils::isNinja()) {
|
||||
$schedule
|
||||
->command('ninja:send-renewals --force')
|
||||
->sendOutputTo($logFile)
|
||||
->daily();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
21
app/Events/ClientWasArchived.php
Normal file
21
app/Events/ClientWasArchived.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ClientWasArchived extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public $client;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($client)
|
||||
{
|
||||
$this->client = $client;
|
||||
}
|
||||
}
|
21
app/Events/ClientWasCreated.php
Normal file
21
app/Events/ClientWasCreated.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ClientWasCreated extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public $client;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($client)
|
||||
{
|
||||
$this->client = $client;
|
||||
}
|
||||
}
|
21
app/Events/ClientWasDeleted.php
Normal file
21
app/Events/ClientWasDeleted.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ClientWasDeleted extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public $client;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($client)
|
||||
{
|
||||
$this->client = $client;
|
||||
}
|
||||
}
|
21
app/Events/ClientWasRestored.php
Normal file
21
app/Events/ClientWasRestored.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ClientWasRestored extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public $client;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($client)
|
||||
{
|
||||
$this->client = $client;
|
||||
}
|
||||
}
|
21
app/Events/ClientWasUpdated.php
Normal file
21
app/Events/ClientWasUpdated.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ClientWasUpdated extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public $client;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($client)
|
||||
{
|
||||
$this->client = $client;
|
||||
}
|
||||
}
|
23
app/Events/CreditWasArchived.php
Normal file
23
app/Events/CreditWasArchived.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class CreditWasArchived extends Event {
|
||||
|
||||
use SerializesModels;
|
||||
|
||||
public $credit;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($credit)
|
||||
{
|
||||
$this->credit = $credit;
|
||||
}
|
||||
|
||||
}
|
23
app/Events/CreditWasCreated.php
Normal file
23
app/Events/CreditWasCreated.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class CreditWasCreated extends Event {
|
||||
|
||||
use SerializesModels;
|
||||
|
||||
public $credit;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($credit)
|
||||
{
|
||||
$this->credit = $credit;
|
||||
}
|
||||
|
||||
}
|
23
app/Events/CreditWasDeleted.php
Normal file
23
app/Events/CreditWasDeleted.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class CreditWasDeleted extends Event {
|
||||
|
||||
use SerializesModels;
|
||||
|
||||
public $credit;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($credit)
|
||||
{
|
||||
$this->credit = $credit;
|
||||
}
|
||||
|
||||
}
|
23
app/Events/CreditWasRestored.php
Normal file
23
app/Events/CreditWasRestored.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class CreditWasRestored extends Event {
|
||||
|
||||
use SerializesModels;
|
||||
|
||||
public $credit;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($credit)
|
||||
{
|
||||
$this->credit = $credit;
|
||||
}
|
||||
|
||||
}
|
22
app/Events/ExpenseWasArchived.php
Normal file
22
app/Events/ExpenseWasArchived.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ExpenseWasArchived extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public $expense;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($expense)
|
||||
{
|
||||
$this->expense = $expense;
|
||||
}
|
||||
|
||||
}
|
21
app/Events/ExpenseWasCreated.php
Normal file
21
app/Events/ExpenseWasCreated.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ExpenseWasCreated extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public $expense;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($expense)
|
||||
{
|
||||
$this->expense = $expense;
|
||||
}
|
||||
}
|
23
app/Events/ExpenseWasDeleted.php
Normal file
23
app/Events/ExpenseWasDeleted.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ExpenseWasDeleted extends Event {
|
||||
// Expenses
|
||||
use SerializesModels;
|
||||
|
||||
public $expense;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($expense)
|
||||
{
|
||||
$this->expense = $expense;
|
||||
}
|
||||
|
||||
}
|
23
app/Events/ExpenseWasRestored.php
Normal file
23
app/Events/ExpenseWasRestored.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ExpenseWasRestored extends Event {
|
||||
// Expenses
|
||||
use SerializesModels;
|
||||
|
||||
public $expense;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($expense)
|
||||
{
|
||||
$this->expense = $expense;
|
||||
}
|
||||
|
||||
}
|
21
app/Events/ExpenseWasUpdated.php
Normal file
21
app/Events/ExpenseWasUpdated.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ExpenseWasUpdated extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public $expense;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($expense)
|
||||
{
|
||||
$this->expense = $expense;
|
||||
}
|
||||
}
|
23
app/Events/InvoiceInvitationWasEmailed.php
Normal file
23
app/Events/InvoiceInvitationWasEmailed.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class InvoiceInvitationWasEmailed extends Event {
|
||||
|
||||
use SerializesModels;
|
||||
|
||||
public $invitation;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($invitation)
|
||||
{
|
||||
$this->invitation = $invitation;
|
||||
}
|
||||
|
||||
}
|
25
app/Events/InvoiceInvitationWasViewed.php
Normal file
25
app/Events/InvoiceInvitationWasViewed.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class InvoiceInvitationWasViewed extends Event {
|
||||
|
||||
use SerializesModels;
|
||||
|
||||
public $invoice;
|
||||
public $invitation;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($invoice, $invitation)
|
||||
{
|
||||
$this->invoice = $invoice;
|
||||
$this->invitation = $invitation;
|
||||
}
|
||||
|
||||
}
|
22
app/Events/InvoiceWasArchived.php
Normal file
22
app/Events/InvoiceWasArchived.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class InvoiceWasArchived extends Event {
|
||||
|
||||
use SerializesModels;
|
||||
public $invoice;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($invoice)
|
||||
{
|
||||
$this->invoice = $invoice;
|
||||
}
|
||||
|
||||
}
|
@ -4,10 +4,9 @@ use App\Events\Event;
|
||||
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class InvoiceSent extends Event {
|
||||
class InvoiceWasCreated extends Event {
|
||||
|
||||
use SerializesModels;
|
||||
|
||||
public $invoice;
|
||||
|
||||
/**
|
@ -4,10 +4,9 @@ use App\Events\Event;
|
||||
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class InvoiceViewed extends Event {
|
||||
class InvoiceWasDeleted extends Event {
|
||||
|
||||
use SerializesModels;
|
||||
|
||||
public $invoice;
|
||||
|
||||
/**
|
22
app/Events/InvoiceWasEmailed.php
Normal file
22
app/Events/InvoiceWasEmailed.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class InvoiceWasEmailed extends Event {
|
||||
|
||||
use SerializesModels;
|
||||
public $invoice;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($invoice)
|
||||
{
|
||||
$this->invoice = $invoice;
|
||||
}
|
||||
|
||||
}
|
25
app/Events/InvoiceWasRestored.php
Normal file
25
app/Events/InvoiceWasRestored.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class InvoiceWasRestored extends Event {
|
||||
|
||||
use SerializesModels;
|
||||
|
||||
public $invoice;
|
||||
public $fromDeleted;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($invoice, $fromDeleted)
|
||||
{
|
||||
$this->invoice = $invoice;
|
||||
$this->fromDeleted = $fromDeleted;
|
||||
}
|
||||
|
||||
}
|
@ -4,10 +4,9 @@ use App\Events\Event;
|
||||
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class QuoteApproved extends Event {
|
||||
class InvoiceWasUpdated extends Event {
|
||||
|
||||
use SerializesModels;
|
||||
|
||||
public $invoice;
|
||||
|
||||
/**
|
@ -4,10 +4,9 @@ use App\Events\Event;
|
||||
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class InvoicePaid extends Event {
|
||||
class PaymentWasArchived extends Event {
|
||||
|
||||
use SerializesModels;
|
||||
|
||||
public $payment;
|
||||
|
||||
/**
|
22
app/Events/PaymentWasCreated.php
Normal file
22
app/Events/PaymentWasCreated.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class PaymentWasCreated extends Event {
|
||||
|
||||
use SerializesModels;
|
||||
public $payment;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($payment)
|
||||
{
|
||||
$this->payment = $payment;
|
||||
}
|
||||
|
||||
}
|
22
app/Events/PaymentWasDeleted.php
Normal file
22
app/Events/PaymentWasDeleted.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class PaymentWasDeleted extends Event {
|
||||
|
||||
use SerializesModels;
|
||||
public $payment;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($payment)
|
||||
{
|
||||
$this->payment = $payment;
|
||||
}
|
||||
|
||||
}
|
25
app/Events/PaymentWasRestored.php
Normal file
25
app/Events/PaymentWasRestored.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class PaymentWasRestored extends Event {
|
||||
|
||||
use SerializesModels;
|
||||
|
||||
public $payment;
|
||||
public $fromDeleted;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($payment, $fromDeleted)
|
||||
{
|
||||
$this->payment = $payment;
|
||||
$this->fromDeleted = $fromDeleted;
|
||||
}
|
||||
|
||||
}
|
27
app/Events/QuoteInvitationWasApproved.php
Normal file
27
app/Events/QuoteInvitationWasApproved.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class QuoteInvitationWasApproved extends Event {
|
||||
|
||||
use SerializesModels;
|
||||
|
||||
public $quote;
|
||||
public $invoice;
|
||||
public $invitation;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($quote, $invoice, $invitation)
|
||||
{
|
||||
$this->quote = $quote;
|
||||
$this->invoice = $invoice;
|
||||
$this->invitation = $invitation;
|
||||
}
|
||||
|
||||
}
|
23
app/Events/QuoteInvitationWasEmailed.php
Normal file
23
app/Events/QuoteInvitationWasEmailed.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class QuoteInvitationWasEmailed extends Event {
|
||||
|
||||
use SerializesModels;
|
||||
|
||||
public $invitation;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($invitation)
|
||||
{
|
||||
$this->invitation = $invitation;
|
||||
}
|
||||
|
||||
}
|
25
app/Events/QuoteInvitationWasViewed.php
Normal file
25
app/Events/QuoteInvitationWasViewed.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class QuoteInvitationWasViewed extends Event {
|
||||
|
||||
use SerializesModels;
|
||||
|
||||
public $quote;
|
||||
public $invitation;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($quote, $invitation)
|
||||
{
|
||||
$this->quote = $quote;
|
||||
$this->invitation = $invitation;
|
||||
}
|
||||
|
||||
}
|
22
app/Events/QuoteWasArchived.php
Normal file
22
app/Events/QuoteWasArchived.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class QuoteWasArchived extends Event {
|
||||
|
||||
use SerializesModels;
|
||||
public $quote;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($quote)
|
||||
{
|
||||
$this->quote = $quote;
|
||||
}
|
||||
|
||||
}
|
22
app/Events/QuoteWasCreated.php
Normal file
22
app/Events/QuoteWasCreated.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class QuoteWasCreated extends Event {
|
||||
|
||||
use SerializesModels;
|
||||
public $quote;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($quote)
|
||||
{
|
||||
$this->quote = $quote;
|
||||
}
|
||||
|
||||
}
|
22
app/Events/QuoteWasDeleted.php
Normal file
22
app/Events/QuoteWasDeleted.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class QuoteWasDeleted extends Event {
|
||||
|
||||
use SerializesModels;
|
||||
public $quote;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($quote)
|
||||
{
|
||||
$this->quote = $quote;
|
||||
}
|
||||
|
||||
}
|
22
app/Events/QuoteWasEmailed.php
Normal file
22
app/Events/QuoteWasEmailed.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class QuoteWasEmailed extends Event {
|
||||
|
||||
use SerializesModels;
|
||||
public $quote;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($quote)
|
||||
{
|
||||
$this->quote = $quote;
|
||||
}
|
||||
|
||||
}
|
22
app/Events/QuoteWasRestored.php
Normal file
22
app/Events/QuoteWasRestored.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class QuoteWasRestored extends Event {
|
||||
|
||||
use SerializesModels;
|
||||
public $quote;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($quote)
|
||||
{
|
||||
$this->quote = $quote;
|
||||
}
|
||||
|
||||
}
|
22
app/Events/QuoteWasUpdated.php
Normal file
22
app/Events/QuoteWasUpdated.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class QuoteWasUpdated extends Event {
|
||||
|
||||
use SerializesModels;
|
||||
public $quote;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($quote)
|
||||
{
|
||||
$this->quote = $quote;
|
||||
}
|
||||
|
||||
}
|
@ -8,14 +8,16 @@ class UserSettingsChanged extends Event {
|
||||
|
||||
use SerializesModels;
|
||||
|
||||
public $user;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
public function __construct($user = false)
|
||||
{
|
||||
//
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
}
|
||||
|
21
app/Events/UserSignedUp.php
Normal file
21
app/Events/UserSignedUp.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class UserSignedUp extends Event {
|
||||
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
}
|
22
app/Events/VendorWasArchived.php
Normal file
22
app/Events/VendorWasArchived.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class VendorWasArchived extends Event
|
||||
{
|
||||
// vendor
|
||||
use SerializesModels;
|
||||
|
||||
public $vendor;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($vendor)
|
||||
{
|
||||
$this->vendor = $vendor;
|
||||
}
|
||||
}
|
22
app/Events/VendorWasCreated.php
Normal file
22
app/Events/VendorWasCreated.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class VendorWasCreated extends Event
|
||||
{
|
||||
// vendor
|
||||
use SerializesModels;
|
||||
|
||||
public $vendor;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($vendor)
|
||||
{
|
||||
$this->vendor = $vendor;
|
||||
}
|
||||
}
|
22
app/Events/VendorWasDeleted.php
Normal file
22
app/Events/VendorWasDeleted.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class VendorWasDeleted extends Event
|
||||
{
|
||||
// vendor
|
||||
use SerializesModels;
|
||||
|
||||
public $vendor;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($vendor)
|
||||
{
|
||||
$this->vendor = $vendor;
|
||||
}
|
||||
}
|
22
app/Events/VendorWasRestored.php
Normal file
22
app/Events/VendorWasRestored.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class VendorWasRestored extends Event
|
||||
{
|
||||
// vendor
|
||||
use SerializesModels;
|
||||
|
||||
public $vendor;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($vendor)
|
||||
{
|
||||
$this->vendor = $vendor;
|
||||
}
|
||||
}
|
21
app/Events/VendorWasUpdated.php
Normal file
21
app/Events/VendorWasUpdated.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php namespace App\Events;
|
||||
// vendor
|
||||
use App\Events\Event;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class VendorWasUpdated extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public $vendor;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($vendor)
|
||||
{
|
||||
$this->vendor = $vendor;
|
||||
}
|
||||
}
|
@ -27,10 +27,12 @@ class Handler extends ExceptionHandler {
|
||||
*/
|
||||
public function report(Exception $e)
|
||||
{
|
||||
if (Utils::isNinja()) {
|
||||
Utils::logError(Utils::getErrorString($e));
|
||||
return false;
|
||||
|
||||
//return parent::report($e);
|
||||
} else {
|
||||
return parent::report($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -42,12 +44,20 @@ class Handler extends ExceptionHandler {
|
||||
*/
|
||||
public function render($request, Exception $e)
|
||||
{
|
||||
|
||||
if ($e instanceof ModelNotFoundException) {
|
||||
return Redirect::to('/');
|
||||
} elseif ($e instanceof \Illuminate\Session\TokenMismatchException) {
|
||||
// https://gist.github.com/jrmadsen67/bd0f9ad0ef1ed6bb594e
|
||||
return redirect()
|
||||
->back()
|
||||
->withInput($request->except('password', '_token'))
|
||||
->with([
|
||||
'warning' => trans('texts.token_expired')
|
||||
]);
|
||||
}
|
||||
|
||||
if (Utils::isNinjaProd()) {
|
||||
// In production, except for maintenance mode, we'll show a custom error screen
|
||||
if (Utils::isNinjaProd() && !Utils::isDownForMaintenance()) {
|
||||
$data = [
|
||||
'error' => get_class($e),
|
||||
'hideHeader' => true,
|
||||
|
104
app/Http/Controllers/AccountApiController.php
Normal file
104
app/Http/Controllers/AccountApiController.php
Normal file
@ -0,0 +1,104 @@
|
||||
<?php namespace App\Http\Controllers;
|
||||
|
||||
use Auth;
|
||||
use Utils;
|
||||
use Response;
|
||||
use Input;
|
||||
use Cache;
|
||||
use App\Models\Client;
|
||||
use App\Models\Account;
|
||||
use App\Models\AccountToken;
|
||||
use App\Ninja\Repositories\AccountRepository;
|
||||
use Illuminate\Http\Request;
|
||||
use League\Fractal;
|
||||
use League\Fractal\Manager;
|
||||
use App\Ninja\Serializers\ArraySerializer;
|
||||
use App\Ninja\Transformers\AccountTransformer;
|
||||
use App\Ninja\Transformers\UserAccountTransformer;
|
||||
use App\Http\Controllers\BaseAPIController;
|
||||
use Swagger\Annotations as SWG;
|
||||
|
||||
class AccountApiController extends BaseAPIController
|
||||
{
|
||||
protected $accountRepo;
|
||||
|
||||
public function __construct(AccountRepository $accountRepo)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->accountRepo = $accountRepo;
|
||||
}
|
||||
|
||||
public function login(Request $request)
|
||||
{
|
||||
if ( ! env(API_SECRET) || $request->api_secret !== env(API_SECRET)) {
|
||||
sleep(ERROR_DELAY);
|
||||
return 'Invalid secret';
|
||||
}
|
||||
|
||||
if (Auth::attempt(['email' => $request->email, 'password' => $request->password])) {
|
||||
return $this->processLogin($request);
|
||||
} else {
|
||||
sleep(ERROR_DELAY);
|
||||
return 'Invalid credentials';
|
||||
}
|
||||
}
|
||||
|
||||
private function processLogin(Request $request)
|
||||
{
|
||||
// Create a new token only if one does not already exist
|
||||
$user = Auth::user();
|
||||
$this->accountRepo->createTokens($user, $request->token_name);
|
||||
|
||||
$users = $this->accountRepo->findUsers($user, 'account.account_tokens');
|
||||
$transformer = new UserAccountTransformer($user->account, $request->serializer, $request->token_name);
|
||||
$data = $this->createCollection($users, $transformer, 'user_account');
|
||||
|
||||
return $this->response($data);
|
||||
}
|
||||
|
||||
public function show(Request $request)
|
||||
{
|
||||
$account = Auth::user()->account;
|
||||
$updatedAt = $request->updated_at ? date('Y-m-d H:i:s', $request->updated_at) : false;
|
||||
|
||||
$map = [
|
||||
'users' => [],
|
||||
'clients' => ['contacts'],
|
||||
'invoices' => ['invoice_items', 'user', 'client', 'payments'],
|
||||
'products' => [],
|
||||
'tax_rates' => [],
|
||||
];
|
||||
|
||||
foreach ($map as $key => $values) {
|
||||
$account->load([$key => function($query) use ($values, $updatedAt) {
|
||||
$query->withTrashed()->with($values);
|
||||
if ($updatedAt) {
|
||||
$query->where('updated_at', '>=', $updatedAt);
|
||||
}
|
||||
}]);
|
||||
}
|
||||
|
||||
$transformer = new AccountTransformer(null, $request->serializer);
|
||||
$account = $this->createItem($account, $transformer, 'account');
|
||||
|
||||
return $this->response($account);
|
||||
}
|
||||
|
||||
public function getStaticData()
|
||||
{
|
||||
$data = [];
|
||||
|
||||
$cachedTables = unserialize(CACHED_TABLES);
|
||||
foreach ($cachedTables as $name => $class) {
|
||||
$data[$name] = Cache::get($name);
|
||||
}
|
||||
|
||||
return $this->response($data);
|
||||
}
|
||||
|
||||
public function getUserAccounts(Request $request)
|
||||
{
|
||||
return $this->processLogin($request);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -16,59 +16,43 @@ use App\Models\Account;
|
||||
use App\Models\AccountGateway;
|
||||
|
||||
use App\Ninja\Repositories\AccountRepository;
|
||||
use App\Services\AccountGatewayService;
|
||||
|
||||
class AccountGatewayController extends BaseController
|
||||
{
|
||||
public function getDatatable()
|
||||
protected $accountGatewayService;
|
||||
|
||||
public function __construct(AccountGatewayService $accountGatewayService)
|
||||
{
|
||||
$query = DB::table('account_gateways')
|
||||
->join('gateways', 'gateways.id', '=', 'account_gateways.gateway_id')
|
||||
->where('account_gateways.deleted_at', '=', null)
|
||||
->where('account_gateways.account_id', '=', Auth::user()->account_id)
|
||||
->select('account_gateways.public_id', 'gateways.name', 'account_gateways.deleted_at', 'account_gateways.gateway_id');
|
||||
parent::__construct();
|
||||
|
||||
return Datatable::query($query)
|
||||
->addColumn('name', function ($model) { return link_to('gateways/'.$model->public_id.'/edit', $model->name); })
|
||||
->addColumn('payment_type', function ($model) { return Gateway::getPrettyPaymentType($model->gateway_id); })
|
||||
->addColumn('dropdown', function ($model) {
|
||||
$actions = '<div class="btn-group tr-action" style="visibility:hidden;">
|
||||
<button type="button" class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
'.trans('texts.select').' <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" role="menu">';
|
||||
|
||||
if (!$model->deleted_at) {
|
||||
$actions .= '<li><a href="'.URL::to('gateways/'.$model->public_id).'/edit">'.uctrans('texts.edit_gateway').'</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="javascript:deleteAccountGateway('.$model->public_id.')">'.uctrans('texts.delete_gateway').'</a></li>';
|
||||
$this->accountGatewayService = $accountGatewayService;
|
||||
}
|
||||
|
||||
$actions .= '</ul>
|
||||
</div>';
|
||||
public function index()
|
||||
{
|
||||
return Redirect::to('settings/' . ACCOUNT_PAYMENTS);
|
||||
}
|
||||
|
||||
return $actions;
|
||||
})
|
||||
->orderColumns(['name'])
|
||||
->make();
|
||||
public function getDatatable()
|
||||
{
|
||||
return $this->accountGatewayService->getDatatable(Auth::user()->account_id);
|
||||
}
|
||||
|
||||
public function edit($publicId)
|
||||
{
|
||||
$accountGateway = AccountGateway::scope($publicId)->firstOrFail();
|
||||
$config = $accountGateway->config;
|
||||
$selectedCards = $accountGateway->accepted_credit_cards;
|
||||
$config = $accountGateway->getConfig();
|
||||
|
||||
$configFields = json_decode($config);
|
||||
|
||||
foreach ($configFields as $configField => $value) {
|
||||
$configFields->$configField = str_repeat('*', strlen($value));
|
||||
foreach ($config as $field => $value) {
|
||||
$config->$field = str_repeat('*', strlen($value));
|
||||
}
|
||||
|
||||
$data = self::getViewModel($accountGateway);
|
||||
$data['url'] = 'gateways/'.$publicId;
|
||||
$data['method'] = 'PUT';
|
||||
$data['title'] = trans('texts.edit_gateway') . ' - ' . $accountGateway->gateway->name;
|
||||
$data['config'] = $configFields;
|
||||
$data['config'] = $config;
|
||||
$data['hiddenFields'] = Gateway::$hiddenFields;
|
||||
$data['paymentTypeId'] = $accountGateway->getPaymentType();
|
||||
$data['selectGateways'] = Gateway::where('id', '=', $accountGateway->gateway_id)->get();
|
||||
@ -97,7 +81,12 @@ class AccountGatewayController extends BaseController
|
||||
$data['url'] = 'gateways';
|
||||
$data['method'] = 'POST';
|
||||
$data['title'] = trans('texts.add_gateway');
|
||||
$data['selectGateways'] = Gateway::where('payment_library_id', '=', 1)->where('id', '!=', GATEWAY_PAYPAL_EXPRESS)->where('id', '!=', GATEWAY_PAYPAL_EXPRESS)->orderBy('name')->get();
|
||||
$data['selectGateways'] = Gateway::where('payment_library_id', '=', 1)
|
||||
->where('id', '!=', GATEWAY_PAYPAL_EXPRESS)
|
||||
->where('id', '!=', GATEWAY_BITPAY)
|
||||
->where('id', '!=', GATEWAY_GOCARDLESS)
|
||||
->where('id', '!=', GATEWAY_DWOLLA)
|
||||
->orderBy('name')->get();
|
||||
$data['hiddenFields'] = Gateway::$hiddenFields;
|
||||
|
||||
return View::make('accounts.account_gateway', $data);
|
||||
@ -116,6 +105,9 @@ class AccountGatewayController extends BaseController
|
||||
if ($type == PAYMENT_TYPE_BITCOIN) {
|
||||
$paymentTypes[$type] .= ' - BitPay';
|
||||
}
|
||||
if ($type == PAYMENT_TYPE_DIRECT_DEBIT) {
|
||||
$paymentTypes[$type] .= ' - GoCardless';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -155,21 +147,20 @@ class AccountGatewayController extends BaseController
|
||||
'gateways' => $gateways,
|
||||
'creditCardTypes' => $creditCards,
|
||||
'tokenBillingOptions' => $tokenBillingOptions,
|
||||
'showBreadcrumbs' => false,
|
||||
'countGateways' => count($currentGateways)
|
||||
];
|
||||
}
|
||||
|
||||
public function delete()
|
||||
|
||||
public function bulk()
|
||||
{
|
||||
$accountGatewayPublicId = Input::get('accountGatewayPublicId');
|
||||
$gateway = AccountGateway::scope($accountGatewayPublicId)->firstOrFail();
|
||||
$action = Input::get('bulk_action');
|
||||
$ids = Input::get('bulk_public_id');
|
||||
$count = $this->accountGatewayService->bulk($ids, $action);
|
||||
|
||||
$gateway->delete();
|
||||
Session::flash('message', trans('texts.archived_account_gateway'));
|
||||
|
||||
Session::flash('message', trans('texts.deleted_gateway'));
|
||||
|
||||
return Redirect::to('company/payments');
|
||||
return Redirect::to('settings/' . ACCOUNT_PAYMENTS);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -186,6 +177,8 @@ class AccountGatewayController extends BaseController
|
||||
$gatewayId = GATEWAY_PAYPAL_EXPRESS;
|
||||
} elseif ($paymentType == PAYMENT_TYPE_BITCOIN) {
|
||||
$gatewayId = GATEWAY_BITPAY;
|
||||
} elseif ($paymentType == PAYMENT_TYPE_DIRECT_DEBIT) {
|
||||
$gatewayId = GATEWAY_GOCARDLESS;
|
||||
} elseif ($paymentType == PAYMENT_TYPE_DWOLLA) {
|
||||
$gatewayId = GATEWAY_DWOLLA;
|
||||
}
|
||||
@ -229,7 +222,7 @@ class AccountGatewayController extends BaseController
|
||||
|
||||
if ($accountGatewayPublicId) {
|
||||
$accountGateway = AccountGateway::scope($accountGatewayPublicId)->firstOrFail();
|
||||
$oldConfig = json_decode($accountGateway->config);
|
||||
$oldConfig = $accountGateway->getConfig();
|
||||
} else {
|
||||
$accountGateway = AccountGateway::createNew();
|
||||
$accountGateway->gateway_id = $gatewayId;
|
||||
@ -239,7 +232,7 @@ class AccountGatewayController extends BaseController
|
||||
foreach ($fields as $field => $details) {
|
||||
$value = trim(Input::get($gateway->id.'_'.$field));
|
||||
// if the new value is masked use the original value
|
||||
if ($value && $value === str_repeat('*', strlen($value))) {
|
||||
if ($oldConfig && $value && $value === str_repeat('*', strlen($value))) {
|
||||
$value = $oldConfig->$field;
|
||||
}
|
||||
if (!$value && ($field == 'testMode' || $field == 'developerMode')) {
|
||||
@ -249,6 +242,13 @@ class AccountGatewayController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
$publishableKey = Input::get('publishable_key');
|
||||
if ($publishableKey = str_replace('*', '', $publishableKey)) {
|
||||
$config->publishableKey = $publishableKey;
|
||||
} elseif ($oldConfig && property_exists($oldConfig, 'publishableKey')) {
|
||||
$config->publishableKey = $oldConfig->publishableKey;
|
||||
}
|
||||
|
||||
$cardCount = 0;
|
||||
if ($creditcards) {
|
||||
foreach ($creditcards as $card => $value) {
|
||||
@ -259,7 +259,7 @@ class AccountGatewayController extends BaseController
|
||||
$accountGateway->accepted_credit_cards = $cardCount;
|
||||
$accountGateway->show_address = Input::get('show_address') ? true : false;
|
||||
$accountGateway->update_address = Input::get('update_address') ? true : false;
|
||||
$accountGateway->config = json_encode($config);
|
||||
$accountGateway->setConfig($config);
|
||||
|
||||
if ($accountGatewayPublicId) {
|
||||
$accountGateway->save();
|
||||
|
@ -5,29 +5,23 @@ use DB;
|
||||
use Datatable;
|
||||
use Utils;
|
||||
use View;
|
||||
use App\Models\Client;
|
||||
use App\Models\Activity;
|
||||
use App\Services\ActivityService;
|
||||
|
||||
class ActivityController extends BaseController
|
||||
{
|
||||
protected $activityService;
|
||||
|
||||
public function __construct(ActivityService $activityService)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->activityService = $activityService;
|
||||
}
|
||||
|
||||
public function getDatatable($clientPublicId)
|
||||
{
|
||||
$query = DB::table('activities')
|
||||
->join('clients', 'clients.id', '=', 'activities.client_id')
|
||||
->where('clients.public_id', '=', $clientPublicId)
|
||||
->where('activities.account_id', '=', Auth::user()->account_id)
|
||||
->select('activities.id', 'activities.message', 'activities.created_at', 'clients.currency_id', 'activities.balance', 'activities.adjustment');
|
||||
|
||||
return Datatable::query($query)
|
||||
->addColumn('activities.id', function ($model) { return Utils::timestampToDateTimeString(strtotime($model->created_at)); })
|
||||
->addColumn('message', function ($model) { return Utils::decodeActivity($model->message); })
|
||||
->addColumn('balance', function ($model) { return Utils::formatMoney($model->balance, $model->currency_id); })
|
||||
->addColumn('adjustment', function ($model) { return $model->adjustment != 0 ? self::wrapAdjustment($model->adjustment, $model->currency_id) : ''; })
|
||||
->make();
|
||||
}
|
||||
|
||||
private function wrapAdjustment($adjustment, $currencyId)
|
||||
{
|
||||
$class = $adjustment <= 0 ? 'success' : 'default';
|
||||
$adjustment = Utils::formatMoney($adjustment, $currencyId);
|
||||
return "<h4><div class=\"label label-{$class}\">$adjustment</div></h4>";
|
||||
return $this->activityService->getDatatable($clientPublicId);
|
||||
}
|
||||
}
|
||||
|
@ -9,27 +9,32 @@ use Exception;
|
||||
use Input;
|
||||
use Utils;
|
||||
use View;
|
||||
use Event;
|
||||
use Session;
|
||||
use Cookie;
|
||||
use Response;
|
||||
use Redirect;
|
||||
use App\Models\User;
|
||||
use App\Models\Account;
|
||||
use App\Models\Industry;
|
||||
use App\Ninja\Mailers\Mailer;
|
||||
use App\Ninja\Repositories\AccountRepository;
|
||||
use Redirect;
|
||||
use App\Events\UserSettingsChanged;
|
||||
use App\Services\EmailService;
|
||||
|
||||
class AppController extends BaseController
|
||||
{
|
||||
protected $accountRepo;
|
||||
protected $mailer;
|
||||
protected $emailService;
|
||||
|
||||
public function __construct(AccountRepository $accountRepo, Mailer $mailer)
|
||||
public function __construct(AccountRepository $accountRepo, Mailer $mailer, EmailService $emailService)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->accountRepo = $accountRepo;
|
||||
$this->mailer = $mailer;
|
||||
$this->emailService = $emailService;
|
||||
}
|
||||
|
||||
public function showSetup()
|
||||
@ -43,7 +48,7 @@ class AppController extends BaseController
|
||||
|
||||
public function doSetup()
|
||||
{
|
||||
if (Utils::isNinjaProd() || (Utils::isDatabaseSetup() && Account::count() > 0)) {
|
||||
if (Utils::isNinjaProd()) {
|
||||
return Redirect::to('/');
|
||||
}
|
||||
|
||||
@ -51,10 +56,11 @@ class AppController extends BaseController
|
||||
$test = Input::get('test');
|
||||
|
||||
$app = Input::get('app');
|
||||
$app['key'] = str_random(RANDOM_KEY_LENGTH);
|
||||
$app['key'] = env('APP_KEY') ?: str_random(RANDOM_KEY_LENGTH);
|
||||
$app['debug'] = Input::get('debug') ? 'true' : 'false';
|
||||
|
||||
$database = Input::get('database');
|
||||
$dbType = $database['default'];
|
||||
$dbType = 'mysql'; // $database['default'];
|
||||
$database['connections'] = [$dbType => $database['type']];
|
||||
|
||||
$mail = Input::get('mail');
|
||||
@ -73,8 +79,12 @@ class AppController extends BaseController
|
||||
return Redirect::to('/setup')->withInput();
|
||||
}
|
||||
|
||||
if (Utils::isDatabaseSetup() && Account::count() > 0) {
|
||||
return Redirect::to('/');
|
||||
}
|
||||
|
||||
$config = "APP_ENV=production\n".
|
||||
"APP_DEBUG=false\n".
|
||||
"APP_DEBUG={$app['debug']}\n".
|
||||
"APP_URL={$app['url']}\n".
|
||||
"APP_KEY={$app['key']}\n\n".
|
||||
"DB_TYPE={$dbType}\n".
|
||||
@ -88,7 +98,8 @@ class AppController extends BaseController
|
||||
"MAIL_HOST={$mail['host']}\n".
|
||||
"MAIL_USERNAME={$mail['username']}\n".
|
||||
"MAIL_FROM_NAME={$mail['from']['name']}\n".
|
||||
"MAIL_PASSWORD={$mail['password']}";
|
||||
"MAIL_PASSWORD={$mail['password']}\n\n".
|
||||
"PHANTOMJS_CLOUD_KEY='a-demo-key-with-low-quota-per-ip-address'";
|
||||
|
||||
// Write Config Settings
|
||||
$fp = fopen(base_path()."/.env", 'w');
|
||||
@ -114,17 +125,68 @@ class AppController extends BaseController
|
||||
return Redirect::to('/login');
|
||||
}
|
||||
|
||||
public function updateSetup()
|
||||
{
|
||||
if (Utils::isNinjaProd()) {
|
||||
return Redirect::to('/');
|
||||
}
|
||||
|
||||
if (!Auth::check() && Utils::isDatabaseSetup() && Account::count() > 0) {
|
||||
return Redirect::to('/');
|
||||
}
|
||||
|
||||
if ( ! $canUpdateEnv = @fopen(base_path()."/.env", 'w')) {
|
||||
Session::flash('error', 'Warning: Permission denied to write to .env config file, try running <code>sudo chown www-data:www-data /path/to/ninja/.env</code>');
|
||||
return Redirect::to('/settings/system_settings');
|
||||
}
|
||||
|
||||
$app = Input::get('app');
|
||||
$db = Input::get('database');
|
||||
$mail = Input::get('mail');
|
||||
|
||||
$_ENV['APP_URL'] = $app['url'];
|
||||
$_ENV['APP_DEBUG'] = Input::get('debug') ? 'true' : 'false';
|
||||
|
||||
$_ENV['DB_TYPE'] = 'mysql'; // $db['default'];
|
||||
$_ENV['DB_HOST'] = $db['type']['host'];
|
||||
$_ENV['DB_DATABASE'] = $db['type']['database'];
|
||||
$_ENV['DB_USERNAME'] = $db['type']['username'];
|
||||
$_ENV['DB_PASSWORD'] = $db['type']['password'];
|
||||
|
||||
if ($mail) {
|
||||
$_ENV['MAIL_DRIVER'] = $mail['driver'];
|
||||
$_ENV['MAIL_PORT'] = $mail['port'];
|
||||
$_ENV['MAIL_ENCRYPTION'] = $mail['encryption'];
|
||||
$_ENV['MAIL_HOST'] = $mail['host'];
|
||||
$_ENV['MAIL_USERNAME'] = $mail['username'];
|
||||
$_ENV['MAIL_FROM_NAME'] = $mail['from']['name'];
|
||||
$_ENV['MAIL_PASSWORD'] = $mail['password'];
|
||||
$_ENV['MAIL_FROM_ADDRESS'] = $mail['username'];
|
||||
}
|
||||
|
||||
$config = '';
|
||||
foreach ($_ENV as $key => $val) {
|
||||
$config .= "{$key}={$val}\n";
|
||||
}
|
||||
|
||||
$fp = fopen(base_path()."/.env", 'w');
|
||||
fwrite($fp, $config);
|
||||
fclose($fp);
|
||||
|
||||
Session::flash('message', trans('texts.updated_settings'));
|
||||
return Redirect::to('/settings/system_settings');
|
||||
}
|
||||
|
||||
private function testDatabase($database)
|
||||
{
|
||||
$dbType = $database['default'];
|
||||
|
||||
$dbType = 'mysql'; // $database['default'];
|
||||
Config::set('database.default', $dbType);
|
||||
|
||||
foreach ($database['connections'][$dbType] as $key => $val) {
|
||||
Config::set("database.connections.{$dbType}.{$key}", $val);
|
||||
}
|
||||
|
||||
try {
|
||||
DB::reconnect();
|
||||
$valid = DB::connection()->getDatabaseName() ? true : false;
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
@ -179,10 +241,13 @@ class AppController extends BaseController
|
||||
{
|
||||
if (!Utils::isNinjaProd()) {
|
||||
try {
|
||||
Cache::flush();
|
||||
Session::flush();
|
||||
Artisan::call('optimize', array('--force' => true));
|
||||
Artisan::call('migrate', array('--force' => true));
|
||||
Artisan::call('db:seed', array('--force' => true, '--class' => 'PaymentLibrariesSeeder'));
|
||||
Artisan::call('optimize', array('--force' => true));
|
||||
Cache::flush();
|
||||
Artisan::call('db:seed', array('--force' => true, '--class' => 'FontsSeeder'));
|
||||
Event::fire(new UserSettingsChanged());
|
||||
Session::flash('message', trans('texts.processed_updates'));
|
||||
} catch (Exception $e) {
|
||||
Response::make($e->getMessage(), 500);
|
||||
@ -191,4 +256,19 @@ class AppController extends BaseController
|
||||
|
||||
return Redirect::to('/');
|
||||
}
|
||||
|
||||
public function emailBounced()
|
||||
{
|
||||
$messageId = Input::get('MessageID');
|
||||
$error = Input::get('Name') . ': ' . Input::get('Description');
|
||||
return $this->emailService->markBounced($messageId, $error) ? RESULT_SUCCESS : RESULT_FAILURE;
|
||||
}
|
||||
|
||||
public function emailOpened()
|
||||
{
|
||||
$messageId = Input::get('MessageID');
|
||||
return $this->emailService->markOpened($messageId) ? RESULT_SUCCESS : RESULT_FAILURE;
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ use App\Models\User;
|
||||
use App\Events\UserLoggedIn;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Ninja\Repositories\AccountRepository;
|
||||
use App\Services\AuthService;
|
||||
use Illuminate\Contracts\Auth\Guard;
|
||||
use Illuminate\Contracts\Auth\Registrar;
|
||||
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;
|
||||
@ -30,6 +31,7 @@ class AuthController extends Controller {
|
||||
|
||||
protected $loginPath = '/login';
|
||||
protected $redirectTo = '/dashboard';
|
||||
protected $authService;
|
||||
protected $accountRepo;
|
||||
|
||||
/**
|
||||
@ -39,15 +41,29 @@ class AuthController extends Controller {
|
||||
* @param \Illuminate\Contracts\Auth\Registrar $registrar
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Guard $auth, Registrar $registrar, AccountRepository $repo)
|
||||
public function __construct(Guard $auth, Registrar $registrar, AccountRepository $repo, AuthService $authService)
|
||||
{
|
||||
$this->auth = $auth;
|
||||
$this->registrar = $registrar;
|
||||
$this->accountRepo = $repo;
|
||||
$this->authService = $authService;
|
||||
|
||||
//$this->middleware('guest', ['except' => 'getLogout']);
|
||||
}
|
||||
|
||||
public function authLogin($provider, Request $request)
|
||||
{
|
||||
return $this->authService->execute($provider, $request->has('code'));
|
||||
}
|
||||
|
||||
public function authUnlink()
|
||||
{
|
||||
$this->accountRepo->unlinkUserFromOauth(Auth::user());
|
||||
|
||||
Session::flash('message', trans('texts.updated_settings'));
|
||||
return redirect()->to('/settings/' . ACCOUNT_USER_DETAILS);
|
||||
}
|
||||
|
||||
public function getLoginWrapper()
|
||||
{
|
||||
if (!Utils::isNinja() && !User::count()) {
|
||||
@ -59,11 +75,12 @@ class AuthController extends Controller {
|
||||
|
||||
public function postLoginWrapper(Request $request)
|
||||
{
|
||||
|
||||
$userId = Auth::check() ? Auth::user()->id : null;
|
||||
$user = User::where('email', '=', $request->input('email'))->first();
|
||||
|
||||
if ($user && $user->failed_logins >= 3) {
|
||||
Session::flash('error', 'These credentials do not match our records.');
|
||||
if ($user && $user->failed_logins >= MAX_FAILED_LOGINS) {
|
||||
Session::flash('error', trans('texts.invalid_credentials'));
|
||||
return redirect()->to('login');
|
||||
}
|
||||
|
||||
@ -74,15 +91,15 @@ class AuthController extends Controller {
|
||||
|
||||
$users = false;
|
||||
// we're linking a new account
|
||||
if ($userId && Auth::user()->id != $userId) {
|
||||
if ($request->link_accounts && $userId && Auth::user()->id != $userId) {
|
||||
$users = $this->accountRepo->associateAccounts($userId, Auth::user()->id);
|
||||
Session::flash('message', trans('texts.associated_accounts'));
|
||||
// check if other accounts are linked
|
||||
} else {
|
||||
$users = $this->accountRepo->loadAccounts(Auth::user()->id);
|
||||
}
|
||||
|
||||
Session::put(SESSION_USER_ACCOUNTS, $users);
|
||||
|
||||
} elseif ($user) {
|
||||
$user->failed_logins = $user->failed_logins + 1;
|
||||
$user->save();
|
||||
@ -91,6 +108,7 @@ class AuthController extends Controller {
|
||||
return $response;
|
||||
}
|
||||
|
||||
|
||||
public function getLogoutWrapper()
|
||||
{
|
||||
if (Auth::check() && !Auth::user()->registered) {
|
||||
|
152
app/Http/Controllers/BankAccountController.php
Normal file
152
app/Http/Controllers/BankAccountController.php
Normal file
@ -0,0 +1,152 @@
|
||||
<?php namespace App\Http\Controllers;
|
||||
|
||||
use Crypt;
|
||||
use Cache;
|
||||
use Auth;
|
||||
use Datatable;
|
||||
use DB;
|
||||
use Input;
|
||||
use Redirect;
|
||||
use Session;
|
||||
use View;
|
||||
use Validator;
|
||||
use stdClass;
|
||||
use URL;
|
||||
use Utils;
|
||||
use App\Models\Gateway;
|
||||
use App\Models\Account;
|
||||
use App\Models\BankAccount;
|
||||
use App\Ninja\Repositories\AccountRepository;
|
||||
use App\Services\BankAccountService;
|
||||
|
||||
class BankAccountController extends BaseController
|
||||
{
|
||||
protected $bankAccountService;
|
||||
|
||||
public function __construct(BankAccountService $bankAccountService)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->bankAccountService = $bankAccountService;
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
return Redirect::to('settings/' . ACCOUNT_BANKS);
|
||||
}
|
||||
|
||||
public function getDatatable()
|
||||
{
|
||||
return $this->bankAccountService->getDatatable(Auth::user()->account_id);
|
||||
}
|
||||
|
||||
public function edit($publicId)
|
||||
{
|
||||
$bankAccount = BankAccount::scope($publicId)->firstOrFail();
|
||||
$bankAccount->username = str_repeat('*', 16);
|
||||
|
||||
$data = [
|
||||
'url' => 'bank_accounts/' . $publicId,
|
||||
'method' => 'PUT',
|
||||
'title' => trans('texts.edit_bank_account'),
|
||||
'banks' => Cache::get('banks'),
|
||||
'bankAccount' => $bankAccount,
|
||||
];
|
||||
|
||||
return View::make('accounts.bank_account', $data);
|
||||
}
|
||||
|
||||
public function update($publicId)
|
||||
{
|
||||
return $this->save($publicId);
|
||||
}
|
||||
|
||||
public function store()
|
||||
{
|
||||
return $this->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the form for account creation
|
||||
*
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$data = [
|
||||
'url' => 'bank_accounts',
|
||||
'method' => 'POST',
|
||||
'title' => trans('texts.add_bank_account'),
|
||||
'banks' => Cache::get('banks'),
|
||||
'bankAccount' => null,
|
||||
];
|
||||
|
||||
return View::make('accounts.bank_account', $data);
|
||||
}
|
||||
|
||||
public function bulk()
|
||||
{
|
||||
$action = Input::get('bulk_action');
|
||||
$ids = Input::get('bulk_public_id');
|
||||
$count = $this->bankAccountService->bulk($ids, $action);
|
||||
|
||||
Session::flash('message', trans('texts.archived_bank_account'));
|
||||
|
||||
return Redirect::to('settings/' . ACCOUNT_BANKS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores new account
|
||||
*
|
||||
*/
|
||||
public function save($bankAccountPublicId = false)
|
||||
{
|
||||
$account = Auth::user()->account;
|
||||
$bankId = Input::get('bank_id');
|
||||
$username = Input::get('bank_username');
|
||||
|
||||
$rules = [
|
||||
'bank_id' => $bankAccountPublicId ? '' : 'required',
|
||||
'bank_username' => 'required',
|
||||
];
|
||||
|
||||
$validator = Validator::make(Input::all(), $rules);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return Redirect::to('bank_accounts/create')
|
||||
->withErrors($validator)
|
||||
->withInput();
|
||||
} else {
|
||||
if ($bankAccountPublicId) {
|
||||
$bankAccount = BankAccount::scope($bankAccountPublicId)->firstOrFail();
|
||||
} else {
|
||||
$bankAccount = BankAccount::createNew();
|
||||
$bankAccount->bank_id = $bankId;
|
||||
}
|
||||
|
||||
if ($username != str_repeat('*', strlen($username))) {
|
||||
$bankAccount->username = Crypt::encrypt(trim($username));
|
||||
}
|
||||
|
||||
if ($bankAccountPublicId) {
|
||||
$bankAccount->save();
|
||||
$message = trans('texts.updated_bank_account');
|
||||
} else {
|
||||
$account->bank_accounts()->save($bankAccount);
|
||||
$message = trans('texts.created_bank_account');
|
||||
}
|
||||
|
||||
Session::flash('message', $message);
|
||||
return Redirect::to("bank_accounts/{$bankAccount->public_id}/edit");
|
||||
}
|
||||
}
|
||||
|
||||
public function test()
|
||||
{
|
||||
$bankId = Input::get('bank_id');
|
||||
$username = Input::get('bank_username');
|
||||
$password = Input::get('bank_password');
|
||||
|
||||
return json_encode($this->bankAccountService->loadBankAccounts($bankId, $username, $password, false));
|
||||
}
|
||||
|
||||
}
|
135
app/Http/Controllers/BaseAPIController.php
Normal file
135
app/Http/Controllers/BaseAPIController.php
Normal file
@ -0,0 +1,135 @@
|
||||
<?php namespace App\Http\Controllers;
|
||||
|
||||
use Session;
|
||||
use Utils;
|
||||
use Response;
|
||||
use Request;
|
||||
use League\Fractal;
|
||||
use League\Fractal\Manager;
|
||||
use League\Fractal\Resource\Item;
|
||||
use League\Fractal\Resource\Collection;
|
||||
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
||||
use App\Ninja\Serializers\ArraySerializer;
|
||||
use League\Fractal\Serializer\JsonApiSerializer;
|
||||
|
||||
/**
|
||||
* @SWG\Swagger(
|
||||
* schemes={"http","https"},
|
||||
* host="ninja.dev",
|
||||
* basePath="/api/v1",
|
||||
* @SWG\Info(
|
||||
* version="1.0.0",
|
||||
* title="Invoice Ninja API",
|
||||
* description="An open-source invoicing and time-tracking app built with Laravel",
|
||||
* termsOfService="",
|
||||
* @SWG\Contact(
|
||||
* email="contact@invoiceninja.com"
|
||||
* ),
|
||||
* @SWG\License(
|
||||
* name="Attribution Assurance License",
|
||||
* url="https://raw.githubusercontent.com/invoiceninja/invoiceninja/master/LICENSE"
|
||||
* )
|
||||
* ),
|
||||
* @SWG\ExternalDocumentation(
|
||||
* description="Find out more about Invoice Ninja",
|
||||
* url="https://www.invoiceninja.com"
|
||||
* ),
|
||||
* @SWG\SecurityScheme(
|
||||
* securityDefinition="api_key",
|
||||
* type="apiKey",
|
||||
* in="header",
|
||||
* name="TOKEN"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
class BaseAPIController extends Controller
|
||||
{
|
||||
protected $manager;
|
||||
protected $serializer;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->manager = new Manager();
|
||||
|
||||
if ($include = Request::get('include')) {
|
||||
$this->manager->parseIncludes($include);
|
||||
}
|
||||
|
||||
$this->serializer = Request::get('serializer') ?: API_SERIALIZER_ARRAY;
|
||||
|
||||
if ($this->serializer === API_SERIALIZER_JSON) {
|
||||
$this->manager->setSerializer(new JsonApiSerializer());
|
||||
} else {
|
||||
$this->manager->setSerializer(new ArraySerializer());
|
||||
}
|
||||
}
|
||||
|
||||
protected function createItem($data, $transformer, $entityType)
|
||||
{
|
||||
if ($this->serializer && $this->serializer != API_SERIALIZER_JSON) {
|
||||
$entityType = null;
|
||||
}
|
||||
|
||||
$resource = new Item($data, $transformer, $entityType);
|
||||
return $this->manager->createData($resource)->toArray();
|
||||
}
|
||||
|
||||
protected function createCollection($data, $transformer, $entityType, $paginator = false)
|
||||
{
|
||||
if ($this->serializer && $this->serializer != API_SERIALIZER_JSON) {
|
||||
$entityType = null;
|
||||
}
|
||||
|
||||
$resource = new Collection($data, $transformer, $entityType);
|
||||
|
||||
if ($paginator) {
|
||||
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
|
||||
}
|
||||
|
||||
return $this->manager->createData($resource)->toArray();
|
||||
}
|
||||
|
||||
protected function response($response)
|
||||
{
|
||||
$index = Request::get('index') ?: 'data';
|
||||
$meta = isset($response['meta']) ? $response['meta'] : null;
|
||||
$response = [
|
||||
$index => $response
|
||||
];
|
||||
if ($meta) {
|
||||
$response['meta'] = $meta;
|
||||
unset($response[$index]['meta']);
|
||||
}
|
||||
|
||||
$response = json_encode($response, JSON_PRETTY_PRINT);
|
||||
$headers = Utils::getApiHeaders();
|
||||
|
||||
return Response::make($response, 200, $headers);
|
||||
}
|
||||
|
||||
protected function getIncluded()
|
||||
{
|
||||
$data = ['user'];
|
||||
|
||||
$included = Request::get('include');
|
||||
$included = explode(',', $included);
|
||||
|
||||
foreach ($included as $include) {
|
||||
if ($include == 'invoices') {
|
||||
$data[] = 'invoices.invoice_items';
|
||||
$data[] = 'invoices.user';
|
||||
} elseif ($include == 'clients') {
|
||||
$data[] = 'clients.contacts';
|
||||
$data[] = 'clients.user';
|
||||
} elseif ($include == 'vendors') {
|
||||
$data[] = 'vendors.vendorcontacts';
|
||||
$data[] = 'vendors.user';
|
||||
}
|
||||
elseif ($include) {
|
||||
$data[] = $include;
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
@ -1,7 +1,11 @@
|
||||
<?php namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Foundation\Bus\DispatchesCommands;
|
||||
|
||||
class BaseController extends Controller
|
||||
{
|
||||
use DispatchesCommands;
|
||||
|
||||
/**
|
||||
* Setup the layout used by the controller.
|
||||
*
|
||||
|
@ -3,15 +3,21 @@
|
||||
use Utils;
|
||||
use Response;
|
||||
use Input;
|
||||
use Auth;
|
||||
use App\Models\Client;
|
||||
use App\Ninja\Repositories\ClientRepository;
|
||||
use App\Http\Requests\CreateClientRequest;
|
||||
use App\Http\Controllers\BaseAPIController;
|
||||
use App\Ninja\Transformers\ClientTransformer;
|
||||
|
||||
class ClientApiController extends Controller
|
||||
class ClientApiController extends BaseAPIController
|
||||
{
|
||||
protected $clientRepo;
|
||||
|
||||
public function __construct(ClientRepository $clientRepo)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->clientRepo = $clientRepo;
|
||||
}
|
||||
|
||||
@ -22,37 +28,80 @@ class ClientApiController extends Controller
|
||||
return Response::make('', 200, $headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @SWG\Get(
|
||||
* path="/clients",
|
||||
* summary="List of clients",
|
||||
* tags={"client"},
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="A list with clients",
|
||||
* @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/Client"))
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response="default",
|
||||
* description="an ""unexpected"" error"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$clients = Client::scope()
|
||||
->with('country', 'contacts', 'industry', 'size', 'currency')
|
||||
->orderBy('created_at', 'desc')
|
||||
->get();
|
||||
$clients = Utils::remapPublicIds($clients);
|
||||
->with($this->getIncluded())
|
||||
->orderBy('created_at', 'desc');
|
||||
|
||||
$response = json_encode($clients, JSON_PRETTY_PRINT);
|
||||
$headers = Utils::getApiHeaders(count($clients));
|
||||
// Filter by email
|
||||
if (Input::has('email')) {
|
||||
|
||||
$email = Input::get('email');
|
||||
$clients = $clients->whereHas('contacts', function ($query) use ($email) {
|
||||
$query->where('email', $email);
|
||||
});
|
||||
|
||||
return Response::make($response, 200, $headers);
|
||||
}
|
||||
|
||||
public function store()
|
||||
$clients = $clients->paginate();
|
||||
|
||||
$transformer = new ClientTransformer(Auth::user()->account, Input::get('serializer'));
|
||||
$paginator = Client::scope()->paginate();
|
||||
|
||||
$data = $this->createCollection($clients, $transformer, ENTITY_CLIENT, $paginator);
|
||||
|
||||
return $this->response($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @SWG\Post(
|
||||
* path="/clients",
|
||||
* tags={"client"},
|
||||
* summary="Create a client",
|
||||
* @SWG\Parameter(
|
||||
* in="body",
|
||||
* name="body",
|
||||
* @SWG\Schema(ref="#/definitions/Client")
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="New client",
|
||||
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Client"))
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response="default",
|
||||
* description="an ""unexpected"" error"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function store(CreateClientRequest $request)
|
||||
{
|
||||
$data = Input::all();
|
||||
$error = $this->clientRepo->getErrors($data);
|
||||
$client = $this->clientRepo->save($request->input());
|
||||
|
||||
if ($error) {
|
||||
$headers = Utils::getApiHeaders();
|
||||
$client = Client::scope($client->public_id)
|
||||
->with('country', 'contacts', 'industry', 'size', 'currency')
|
||||
->first();
|
||||
|
||||
return Response::make($error, 500, $headers);
|
||||
} else {
|
||||
$client = $this->clientRepo->save(isset($data['id']) ? $data['id'] : false, $data, false);
|
||||
$client = Client::scope($client->public_id)->with('country', 'contacts', 'industry', 'size', 'currency')->first();
|
||||
$client = Utils::remapPublicIds([$client]);
|
||||
$response = json_encode($client, JSON_PRETTY_PRINT);
|
||||
$headers = Utils::getApiHeaders();
|
||||
$transformer = new ClientTransformer(Auth::user()->account, Input::get('serializer'));
|
||||
$data = $this->createItem($client, $transformer, ENTITY_CLIENT);
|
||||
|
||||
return Response::make($response, 200, $headers);
|
||||
}
|
||||
return $this->response($data);
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ use Cache;
|
||||
|
||||
use App\Models\Activity;
|
||||
use App\Models\Client;
|
||||
use App\Models\Account;
|
||||
use App\Models\Contact;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Size;
|
||||
@ -21,18 +22,23 @@ use App\Models\Industry;
|
||||
use App\Models\Currency;
|
||||
use App\Models\Country;
|
||||
use App\Models\Task;
|
||||
|
||||
use App\Ninja\Repositories\ClientRepository;
|
||||
use App\Services\ClientService;
|
||||
|
||||
use App\Http\Requests\CreateClientRequest;
|
||||
use App\Http\Requests\UpdateClientRequest;
|
||||
|
||||
class ClientController extends BaseController
|
||||
{
|
||||
protected $clientService;
|
||||
protected $clientRepo;
|
||||
|
||||
public function __construct(ClientRepository $clientRepo)
|
||||
public function __construct(ClientRepository $clientRepo, ClientService $clientService)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->clientRepo = $clientRepo;
|
||||
$this->clientService = $clientService;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -46,56 +52,22 @@ class ClientController extends BaseController
|
||||
'entityType' => ENTITY_CLIENT,
|
||||
'title' => trans('texts.clients'),
|
||||
'sortCol' => '4',
|
||||
'columns' => Utils::trans(['checkbox', 'client', 'contact', 'email', 'date_created', 'last_login', 'balance', 'action']),
|
||||
'columns' => Utils::trans([
|
||||
'checkbox',
|
||||
'client',
|
||||
'contact',
|
||||
'email',
|
||||
'date_created',
|
||||
'last_login',
|
||||
'balance',
|
||||
''
|
||||
]),
|
||||
));
|
||||
}
|
||||
|
||||
public function getDatatable()
|
||||
{
|
||||
$clients = $this->clientRepo->find(Input::get('sSearch'));
|
||||
|
||||
return Datatable::query($clients)
|
||||
->addColumn('checkbox', function ($model) { return '<input type="checkbox" name="ids[]" value="'.$model->public_id.'" '.Utils::getEntityRowClass($model).'>'; })
|
||||
->addColumn('name', function ($model) { return link_to('clients/'.$model->public_id, $model->name); })
|
||||
->addColumn('first_name', function ($model) { return link_to('clients/'.$model->public_id, $model->first_name.' '.$model->last_name); })
|
||||
->addColumn('email', function ($model) { return link_to('clients/'.$model->public_id, $model->email); })
|
||||
->addColumn('clients.created_at', function ($model) { return Utils::timestampToDateString(strtotime($model->created_at)); })
|
||||
->addColumn('last_login', function ($model) { return Utils::timestampToDateString(strtotime($model->last_login)); })
|
||||
->addColumn('balance', function ($model) { return Utils::formatMoney($model->balance, $model->currency_id); })
|
||||
->addColumn('dropdown', function ($model) {
|
||||
if ($model->is_deleted) {
|
||||
return '<div style="height:38px"/>';
|
||||
}
|
||||
|
||||
$str = '<div class="btn-group tr-action" style="visibility:hidden;">
|
||||
<button type="button" class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
'.trans('texts.select').' <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" role="menu">';
|
||||
|
||||
if (!$model->deleted_at || $model->deleted_at == '0000-00-00') {
|
||||
$str .= '<li><a href="'.URL::to('clients/'.$model->public_id.'/edit').'">'.trans('texts.edit_client').'</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="'.URL::to('tasks/create/'.$model->public_id).'">'.trans('texts.new_task').'</a></li>
|
||||
<li><a href="'.URL::to('invoices/create/'.$model->public_id).'">'.trans('texts.new_invoice').'</a></li>';
|
||||
|
||||
if (Auth::user()->isPro()) {
|
||||
$str .= '<li><a href="'.URL::to('quotes/create/'.$model->public_id).'">'.trans('texts.new_quote').'</a></li>';
|
||||
}
|
||||
|
||||
$str .= '<li class="divider"></li>
|
||||
<li><a href="'.URL::to('payments/create/'.$model->public_id).'">'.trans('texts.enter_payment').'</a></li>
|
||||
<li><a href="'.URL::to('credits/create/'.$model->public_id).'">'.trans('texts.enter_credit').'</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="javascript:archiveEntity('.$model->public_id.')">'.trans('texts.archive_client').'</a></li>';
|
||||
} else {
|
||||
$str .= '<li><a href="javascript:restoreEntity('.$model->public_id.')">'.trans('texts.restore_client').'</a></li>';
|
||||
}
|
||||
|
||||
return $str.'<li><a href="javascript:deleteEntity('.$model->public_id.')">'.trans('texts.delete_client').'</a></li></ul>
|
||||
</div>';
|
||||
})
|
||||
->make();
|
||||
return $this->clientService->getDatatable(Input::get('sSearch'));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -103,9 +75,13 @@ class ClientController extends BaseController
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function store()
|
||||
public function store(CreateClientRequest $request)
|
||||
{
|
||||
return $this->save();
|
||||
$client = $this->clientService->save($request->input());
|
||||
|
||||
Session::flash('message', trans('texts.created_client'));
|
||||
|
||||
return redirect()->to($client->getRoute());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -128,8 +104,10 @@ class ClientController extends BaseController
|
||||
}
|
||||
|
||||
array_push($actionLinks,
|
||||
\DropdownButton::DIVIDER,
|
||||
['label' => trans('texts.enter_payment'), 'url' => '/payments/create/'.$client->public_id],
|
||||
['label' => trans('texts.enter_credit'), 'url' => '/credits/create/'.$client->public_id]
|
||||
['label' => trans('texts.enter_credit'), 'url' => '/credits/create/'.$client->public_id],
|
||||
['label' => trans('texts.enter_expense'), 'url' => '/expenses/create/0/'.$client->public_id]
|
||||
);
|
||||
|
||||
$data = array(
|
||||
@ -188,16 +166,25 @@ class ClientController extends BaseController
|
||||
|
||||
$data = array_merge($data, self::getViewModel());
|
||||
|
||||
if (Auth::user()->account->isNinjaAccount()) {
|
||||
if ($account = Account::whereId($client->public_id)->first()) {
|
||||
$data['proPlanPaid'] = $account['pro_plan_paid'];
|
||||
}
|
||||
}
|
||||
|
||||
return View::make('clients.edit', $data);
|
||||
}
|
||||
|
||||
private static function getViewModel()
|
||||
{
|
||||
return [
|
||||
'data' => Input::old('data'),
|
||||
'account' => Auth::user()->account,
|
||||
'sizes' => Cache::get('sizes'),
|
||||
'paymentTerms' => Cache::get('paymentTerms'),
|
||||
'industries' => Cache::get('industries'),
|
||||
'currencies' => Cache::get('currencies'),
|
||||
'languages' => Cache::get('languages'),
|
||||
'countries' => Cache::get('countries'),
|
||||
'customLabel1' => Auth::user()->account->custom_client_label1,
|
||||
'customLabel2' => Auth::user()->account->custom_client_label2,
|
||||
@ -210,97 +197,20 @@ class ClientController extends BaseController
|
||||
* @param int $id
|
||||
* @return Response
|
||||
*/
|
||||
public function update($publicId)
|
||||
public function update(UpdateClientRequest $request)
|
||||
{
|
||||
return $this->save($publicId);
|
||||
}
|
||||
$client = $this->clientService->save($request->input());
|
||||
|
||||
private function save($publicId = null)
|
||||
{
|
||||
$rules = array(
|
||||
'email' => 'email|required_without:first_name',
|
||||
'first_name' => 'required_without:email',
|
||||
);
|
||||
$validator = Validator::make(Input::all(), $rules);
|
||||
|
||||
if ($validator->fails()) {
|
||||
$url = $publicId ? 'clients/'.$publicId.'/edit' : 'clients/create';
|
||||
|
||||
return Redirect::to($url)
|
||||
->withErrors($validator)
|
||||
->withInput(Input::except('password'));
|
||||
} else {
|
||||
if ($publicId) {
|
||||
$client = Client::scope($publicId)->firstOrFail();
|
||||
} else {
|
||||
$client = Client::createNew();
|
||||
}
|
||||
|
||||
$client->name = trim(Input::get('name'));
|
||||
$client->id_number = trim(Input::get('id_number'));
|
||||
$client->vat_number = trim(Input::get('vat_number'));
|
||||
$client->work_phone = trim(Input::get('work_phone'));
|
||||
$client->custom_value1 = trim(Input::get('custom_value1'));
|
||||
$client->custom_value2 = trim(Input::get('custom_value2'));
|
||||
$client->address1 = trim(Input::get('address1'));
|
||||
$client->address2 = trim(Input::get('address2'));
|
||||
$client->city = trim(Input::get('city'));
|
||||
$client->state = trim(Input::get('state'));
|
||||
$client->postal_code = trim(Input::get('postal_code'));
|
||||
$client->country_id = Input::get('country_id') ?: null;
|
||||
$client->private_notes = trim(Input::get('private_notes'));
|
||||
$client->size_id = Input::get('size_id') ?: null;
|
||||
$client->industry_id = Input::get('industry_id') ?: null;
|
||||
$client->currency_id = Input::get('currency_id') ?: null;
|
||||
$client->payment_terms = Input::get('payment_terms') ?: 0;
|
||||
$client->website = trim(Input::get('website'));
|
||||
|
||||
$client->save();
|
||||
|
||||
$data = json_decode(Input::get('data'));
|
||||
$contactIds = [];
|
||||
$isPrimary = true;
|
||||
|
||||
foreach ($data->contacts as $contact) {
|
||||
if (isset($contact->public_id) && $contact->public_id) {
|
||||
$record = Contact::scope($contact->public_id)->firstOrFail();
|
||||
} else {
|
||||
$record = Contact::createNew();
|
||||
}
|
||||
|
||||
$record->email = trim($contact->email);
|
||||
$record->first_name = trim($contact->first_name);
|
||||
$record->last_name = trim($contact->last_name);
|
||||
$record->phone = trim($contact->phone);
|
||||
$record->is_primary = $isPrimary;
|
||||
$isPrimary = false;
|
||||
|
||||
$client->contacts()->save($record);
|
||||
$contactIds[] = $record->public_id;
|
||||
}
|
||||
|
||||
foreach ($client->contacts as $contact) {
|
||||
if (!in_array($contact->public_id, $contactIds)) {
|
||||
$contact->delete();
|
||||
}
|
||||
}
|
||||
|
||||
if ($publicId) {
|
||||
Session::flash('message', trans('texts.updated_client'));
|
||||
} else {
|
||||
Activity::createClient($client);
|
||||
Session::flash('message', trans('texts.created_client'));
|
||||
}
|
||||
|
||||
return Redirect::to('clients/'.$client->public_id);
|
||||
}
|
||||
return redirect()->to($client->getRoute());
|
||||
}
|
||||
|
||||
public function bulk()
|
||||
{
|
||||
$action = Input::get('action');
|
||||
$ids = Input::get('id') ? Input::get('id') : Input::get('ids');
|
||||
$count = $this->clientRepo->bulk($ids, $action);
|
||||
$ids = Input::get('public_id') ? Input::get('public_id') : Input::get('ids');
|
||||
$count = $this->clientService->bulk($ids, $action);
|
||||
|
||||
$message = Utils::pluralize($action.'d_client', $count);
|
||||
Session::flash('message', $message);
|
||||
|
@ -4,22 +4,26 @@ use Datatable;
|
||||
use Input;
|
||||
use Redirect;
|
||||
use Session;
|
||||
use URL;
|
||||
use Utils;
|
||||
use View;
|
||||
use Validator;
|
||||
use App\Models\Client;
|
||||
|
||||
use App\Services\CreditService;
|
||||
use App\Ninja\Repositories\CreditRepository;
|
||||
use App\Http\Requests\CreateCreditRequest;
|
||||
|
||||
class CreditController extends BaseController
|
||||
{
|
||||
protected $creditRepo;
|
||||
protected $creditService;
|
||||
|
||||
public function __construct(CreditRepository $creditRepo)
|
||||
public function __construct(CreditRepository $creditRepo, CreditService $creditService)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->creditRepo = $creditRepo;
|
||||
$this->creditService = $creditService;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -33,46 +37,21 @@ class CreditController extends BaseController
|
||||
'entityType' => ENTITY_CREDIT,
|
||||
'title' => trans('texts.credits'),
|
||||
'sortCol' => '4',
|
||||
'columns' => Utils::trans(['checkbox', 'client', 'credit_amount', 'credit_balance', 'credit_date', 'private_notes', 'action']),
|
||||
'columns' => Utils::trans([
|
||||
'checkbox',
|
||||
'client',
|
||||
'credit_amount',
|
||||
'credit_balance',
|
||||
'credit_date',
|
||||
'private_notes',
|
||||
''
|
||||
]),
|
||||
));
|
||||
}
|
||||
|
||||
public function getDatatable($clientPublicId = null)
|
||||
{
|
||||
$credits = $this->creditRepo->find($clientPublicId, Input::get('sSearch'));
|
||||
|
||||
$table = Datatable::query($credits);
|
||||
|
||||
if (!$clientPublicId) {
|
||||
$table->addColumn('checkbox', function ($model) { return '<input type="checkbox" name="ids[]" value="'.$model->public_id.'" '.Utils::getEntityRowClass($model).'>'; })
|
||||
->addColumn('client_name', function ($model) { return link_to('clients/'.$model->client_public_id, Utils::getClientDisplayName($model)); });
|
||||
}
|
||||
|
||||
return $table->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id).'<span '.Utils::getEntityRowClass($model).'/>'; })
|
||||
->addColumn('balance', function ($model) { return Utils::formatMoney($model->balance, $model->currency_id); })
|
||||
->addColumn('credit_date', function ($model) { return Utils::fromSqlDate($model->credit_date); })
|
||||
->addColumn('private_notes', function ($model) { return $model->private_notes; })
|
||||
->addColumn('dropdown', function ($model) {
|
||||
if ($model->is_deleted) {
|
||||
return '<div style="height:38px"/>';
|
||||
}
|
||||
|
||||
$str = '<div class="btn-group tr-action" style="visibility:hidden;">
|
||||
<button type="button" class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
'.trans('texts.select').' <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" role="menu">';
|
||||
|
||||
if (!$model->deleted_at || $model->deleted_at == '0000-00-00') {
|
||||
$str .= '<li><a href="javascript:archiveEntity('.$model->public_id.')">'.trans('texts.archive_credit').'</a></li>';
|
||||
} else {
|
||||
$str .= '<li><a href="javascript:restoreEntity('.$model->public_id.')">'.trans('texts.restore_credit').'</a></li>';
|
||||
}
|
||||
|
||||
return $str.'<li><a href="javascript:deleteEntity('.$model->public_id.')">'.trans('texts.delete_credit').'</a></li></ul>
|
||||
</div>';
|
||||
})
|
||||
->make();
|
||||
return $this->creditService->getDatatable($clientPublicId, Input::get('sSearch'));
|
||||
}
|
||||
|
||||
public function create($clientPublicId = 0)
|
||||
@ -106,46 +85,20 @@ class CreditController extends BaseController
|
||||
return View::make('credit.edit', $data);
|
||||
}
|
||||
|
||||
public function store()
|
||||
public function store(CreateCreditRequest $request)
|
||||
{
|
||||
return $this->save();
|
||||
}
|
||||
$credit = $this->creditRepo->save($request->input());
|
||||
|
||||
public function update($publicId)
|
||||
{
|
||||
return $this->save($publicId);
|
||||
}
|
||||
Session::flash('message', trans('texts.created_credit'));
|
||||
|
||||
private function save($publicId = null)
|
||||
{
|
||||
$rules = array(
|
||||
'client' => 'required',
|
||||
'amount' => 'required|positive',
|
||||
);
|
||||
|
||||
$validator = Validator::make(Input::all(), $rules);
|
||||
|
||||
if ($validator->fails()) {
|
||||
$url = $publicId ? 'credits/'.$publicId.'/edit' : 'credits/create';
|
||||
|
||||
return Redirect::to($url)
|
||||
->withErrors($validator)
|
||||
->withInput();
|
||||
} else {
|
||||
$this->creditRepo->save($publicId, Input::all());
|
||||
|
||||
$message = trans('texts.created_credit');
|
||||
Session::flash('message', $message);
|
||||
|
||||
return Redirect::to('clients/'.Input::get('client'));
|
||||
}
|
||||
return redirect()->to($credit->client->getRoute());
|
||||
}
|
||||
|
||||
public function bulk()
|
||||
{
|
||||
$action = Input::get('action');
|
||||
$ids = Input::get('id') ? Input::get('id') : Input::get('ids');
|
||||
$count = $this->creditRepo->bulk($ids, $action);
|
||||
$ids = Input::get('public_id') ? Input::get('public_id') : Input::get('ids');
|
||||
$count = $this->creditService->bulk($ids, $action);
|
||||
|
||||
if ($count > 0) {
|
||||
$message = Utils::pluralize($action.'d_credit', $count);
|
||||
|
@ -11,6 +11,7 @@ class DashboardController extends BaseController
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
|
||||
// total_income, billed_clients, invoice_sent and active_clients
|
||||
$select = DB::raw('COUNT(DISTINCT CASE WHEN invoices.id IS NOT NULL THEN clients.id ELSE null END) billed_clients,
|
||||
SUM(CASE WHEN invoices.invoice_status_id >= '.INVOICE_STATUS_SENT.' THEN 1 ELSE 0 END) invoices_sent,
|
||||
@ -62,6 +63,7 @@ class DashboardController extends BaseController
|
||||
->get();
|
||||
|
||||
$activities = Activity::where('activities.account_id', '=', Auth::user()->account_id)
|
||||
->with('client.contacts', 'user', 'invoice', 'payment', 'credit', 'account')
|
||||
->where('activity_type_id', '>', 0)
|
||||
->orderBy('created_at', 'desc')
|
||||
->take(50)
|
||||
@ -74,12 +76,13 @@ class DashboardController extends BaseController
|
||||
->where('clients.deleted_at', '=', null)
|
||||
->where('contacts.deleted_at', '=', null)
|
||||
->where('invoices.is_recurring', '=', false)
|
||||
->where('invoices.is_quote', '=', false)
|
||||
//->where('invoices.is_quote', '=', false)
|
||||
->where('invoices.balance', '>', 0)
|
||||
->where('invoices.is_deleted', '=', false)
|
||||
->where('invoices.deleted_at', '=', null)
|
||||
->where('contacts.is_primary', '=', true)
|
||||
->where('invoices.due_date', '<', date('Y-m-d'))
|
||||
->select(['invoices.due_date', 'invoices.balance', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id'])
|
||||
->select(['invoices.due_date', 'invoices.balance', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'is_quote'])
|
||||
->orderBy('invoices.due_date', 'asc')
|
||||
->take(50)
|
||||
->get();
|
||||
@ -90,15 +93,16 @@ class DashboardController extends BaseController
|
||||
->where('invoices.account_id', '=', Auth::user()->account_id)
|
||||
->where('clients.deleted_at', '=', null)
|
||||
->where('contacts.deleted_at', '=', null)
|
||||
->where('invoices.deleted_at', '=', null)
|
||||
->where('invoices.is_recurring', '=', false)
|
||||
->where('invoices.is_quote', '=', false)
|
||||
//->where('invoices.is_quote', '=', false)
|
||||
->where('invoices.balance', '>', 0)
|
||||
->where('invoices.is_deleted', '=', false)
|
||||
->where('contacts.is_primary', '=', true)
|
||||
->where('invoices.due_date', '>=', date('Y-m-d'))
|
||||
->orderBy('invoices.due_date', 'asc')
|
||||
->take(50)
|
||||
->select(['invoices.due_date', 'invoices.balance', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id'])
|
||||
->select(['invoices.due_date', 'invoices.balance', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id', 'is_quote'])
|
||||
->get();
|
||||
|
||||
$payments = DB::table('payments')
|
||||
@ -106,14 +110,24 @@ class DashboardController extends BaseController
|
||||
->leftJoin('contacts', 'contacts.client_id', '=', 'clients.id')
|
||||
->leftJoin('invoices', 'invoices.id', '=', 'payments.invoice_id')
|
||||
->where('payments.account_id', '=', Auth::user()->account_id)
|
||||
->where('payments.is_deleted', '=', false)
|
||||
->where('invoices.is_deleted', '=', false)
|
||||
->where('clients.deleted_at', '=', null)
|
||||
->where('contacts.deleted_at', '=', null)
|
||||
->where('contacts.is_primary', '=', true)
|
||||
->select(['payments.payment_date', 'payments.amount', 'invoices.public_id', 'invoices.invoice_number', 'clients.name as client_name', 'contacts.email', 'contacts.first_name', 'contacts.last_name', 'clients.currency_id', 'clients.public_id as client_public_id'])
|
||||
->orderBy('payments.id', 'desc')
|
||||
->orderBy('payments.payment_date', 'desc')
|
||||
->take(50)
|
||||
->get();
|
||||
|
||||
$hasQuotes = false;
|
||||
foreach ([$upcoming, $pastDue] as $data) {
|
||||
foreach ($data as $invoice) {
|
||||
if ($invoice->is_quote) {
|
||||
$hasQuotes = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$data = [
|
||||
'account' => Auth::user()->account,
|
||||
@ -127,6 +141,7 @@ class DashboardController extends BaseController
|
||||
'upcoming' => $upcoming,
|
||||
'payments' => $payments,
|
||||
'title' => trans('texts.dashboard'),
|
||||
'hasQuotes' => $hasQuotes,
|
||||
];
|
||||
|
||||
return View::make('dashboard', $data);
|
||||
|
252
app/Http/Controllers/ExpenseController.php
Normal file
252
app/Http/Controllers/ExpenseController.php
Normal file
@ -0,0 +1,252 @@
|
||||
<?php namespace App\Http\Controllers;
|
||||
|
||||
use Debugbar;
|
||||
use DB;
|
||||
use Auth;
|
||||
use Datatable;
|
||||
use Utils;
|
||||
use View;
|
||||
use URL;
|
||||
use Validator;
|
||||
use Input;
|
||||
use Session;
|
||||
use Redirect;
|
||||
use Cache;
|
||||
use App\Models\Vendor;
|
||||
use App\Models\Expense;
|
||||
use App\Models\Client;
|
||||
use App\Services\ExpenseService;
|
||||
use App\Ninja\Repositories\ExpenseRepository;
|
||||
use App\Http\Requests\CreateExpenseRequest;
|
||||
use App\Http\Requests\UpdateExpenseRequest;
|
||||
|
||||
class ExpenseController extends BaseController
|
||||
{
|
||||
// Expenses
|
||||
protected $expenseRepo;
|
||||
protected $expenseService;
|
||||
|
||||
public function __construct(ExpenseRepository $expenseRepo, ExpenseService $expenseService)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->expenseRepo = $expenseRepo;
|
||||
$this->expenseService = $expenseService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
return View::make('list', array(
|
||||
'entityType' => ENTITY_EXPENSE,
|
||||
'title' => trans('texts.expenses'),
|
||||
'sortCol' => '1',
|
||||
'columns' => Utils::trans([
|
||||
'checkbox',
|
||||
'vendor',
|
||||
'client',
|
||||
'expense_date',
|
||||
'amount',
|
||||
'public_notes',
|
||||
'status',
|
||||
''
|
||||
]),
|
||||
));
|
||||
}
|
||||
|
||||
public function getDatatable($expensePublicId = null)
|
||||
{
|
||||
return $this->expenseService->getDatatable($expensePublicId, Input::get('sSearch'));
|
||||
}
|
||||
|
||||
public function getDatatableVendor($vendorPublicId = null)
|
||||
{
|
||||
return $this->expenseService->getDatatableVendor($vendorPublicId);
|
||||
}
|
||||
|
||||
public function create($vendorPublicId = null, $clientPublicId = null)
|
||||
{
|
||||
if($vendorPublicId != 0) {
|
||||
$vendor = Vendor::scope($vendorPublicId)->with('vendorcontacts')->firstOrFail();
|
||||
} else {
|
||||
$vendor = null;
|
||||
}
|
||||
$data = array(
|
||||
'vendorPublicId' => Input::old('vendor') ? Input::old('vendor') : $vendorPublicId,
|
||||
'expense' => null,
|
||||
'method' => 'POST',
|
||||
'url' => 'expenses',
|
||||
'title' => trans('texts.new_expense'),
|
||||
'vendors' => Vendor::scope()->with('vendorcontacts')->orderBy('name')->get(),
|
||||
'vendor' => $vendor,
|
||||
'clients' => Client::scope()->with('contacts')->orderBy('name')->get(),
|
||||
'clientPublicId' => $clientPublicId,
|
||||
);
|
||||
|
||||
$data = array_merge($data, self::getViewModel());
|
||||
|
||||
return View::make('expenses.edit', $data);
|
||||
}
|
||||
|
||||
public function edit($publicId)
|
||||
{
|
||||
$expense = Expense::scope($publicId)->firstOrFail();
|
||||
$expense->expense_date = Utils::fromSqlDate($expense->expense_date);
|
||||
|
||||
$actions = [];
|
||||
if ($expense->invoice) {
|
||||
$actions[] = ['url' => URL::to("invoices/{$expense->invoice->public_id}/edit"), 'label' => trans("texts.view_invoice")];
|
||||
} else {
|
||||
$actions[] = ['url' => 'javascript:submitAction("invoice")', 'label' => trans("texts.invoice_expense")];
|
||||
|
||||
/*
|
||||
// check for any open invoices
|
||||
$invoices = $task->client_id ? $this->invoiceRepo->findOpenInvoices($task->client_id) : [];
|
||||
|
||||
foreach ($invoices as $invoice) {
|
||||
$actions[] = ['url' => 'javascript:submitAction("add_to_invoice", '.$invoice->public_id.')', 'label' => trans("texts.add_to_invoice", ["invoice" => $invoice->invoice_number])];
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
$actions[] = \DropdownButton::DIVIDER;
|
||||
if (!$expense->trashed()) {
|
||||
$actions[] = ['url' => 'javascript:submitAction("archive")', 'label' => trans('texts.archive_expense')];
|
||||
$actions[] = ['url' => 'javascript:onDeleteClick()', 'label' => trans('texts.delete_expense')];
|
||||
} else {
|
||||
$actions[] = ['url' => 'javascript:submitAction("restore")', 'label' => trans('texts.restore_expense')];
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'vendor' => null,
|
||||
'expense' => $expense,
|
||||
'method' => 'PUT',
|
||||
'url' => 'expenses/'.$publicId,
|
||||
'title' => 'Edit Expense',
|
||||
'actions' => $actions,
|
||||
'vendors' => Vendor::scope()->with('vendorcontacts')->orderBy('name')->get(),
|
||||
'vendorPublicId' => $expense->vendor ? $expense->vendor->public_id : null,
|
||||
'clients' => Client::scope()->with('contacts')->orderBy('name')->get(),
|
||||
'clientPublicId' => $expense->client ? $expense->client->public_id : null,
|
||||
);
|
||||
|
||||
$data = array_merge($data, self::getViewModel());
|
||||
|
||||
if (Auth::user()->account->isNinjaAccount()) {
|
||||
if ($account = Account::whereId($client->public_id)->first()) {
|
||||
$data['proPlanPaid'] = $account['pro_plan_paid'];
|
||||
}
|
||||
}
|
||||
|
||||
return View::make('expenses.edit', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param int $id
|
||||
* @return Response
|
||||
*/
|
||||
public function update(UpdateExpenseRequest $request)
|
||||
{
|
||||
$expense = $this->expenseRepo->save($request->input());
|
||||
|
||||
Session::flash('message', trans('texts.updated_expense'));
|
||||
|
||||
$action = Input::get('action');
|
||||
if (in_array($action, ['archive', 'delete', 'restore', 'invoice'])) {
|
||||
return self::bulk();
|
||||
}
|
||||
|
||||
return redirect()->to("expenses/{$expense->public_id}/edit");
|
||||
}
|
||||
|
||||
public function store(CreateExpenseRequest $request)
|
||||
{
|
||||
$expense = $this->expenseRepo->save($request->input());
|
||||
|
||||
Session::flash('message', trans('texts.created_expense'));
|
||||
|
||||
return redirect()->to("expenses/{$expense->public_id}/edit");
|
||||
}
|
||||
|
||||
public function bulk()
|
||||
{
|
||||
$action = Input::get('action');
|
||||
$ids = Input::get('public_id') ? Input::get('public_id') : Input::get('ids');
|
||||
|
||||
switch($action)
|
||||
{
|
||||
case 'invoice':
|
||||
$expenses = Expense::scope($ids)->get();
|
||||
$clientPublicId = null;
|
||||
$data = [];
|
||||
|
||||
// Validate that either all expenses do not have a client or if there is a client, it is the same client
|
||||
foreach ($expenses as $expense)
|
||||
{
|
||||
if ($expense->client_id) {
|
||||
if (!$clientPublicId) {
|
||||
$clientPublicId = $expense->client_id;
|
||||
} elseif ($clientPublicId != $expense->client_id) {
|
||||
Session::flash('error', trans('texts.expense_error_multiple_clients'));
|
||||
return Redirect::to('expenses');
|
||||
}
|
||||
}
|
||||
|
||||
if ($expense->invoice_id) {
|
||||
Session::flash('error', trans('texts.expense_error_invoiced'));
|
||||
return Redirect::to('expenses');
|
||||
}
|
||||
|
||||
$account = Auth::user()->account;
|
||||
$data[] = [
|
||||
'publicId' => $expense->public_id,
|
||||
'description' => $expense->public_notes,
|
||||
'qty' => 1,
|
||||
'cost' => $expense->present()->converted_amount,
|
||||
];
|
||||
}
|
||||
|
||||
return Redirect::to("invoices/create/{$clientPublicId}")->with('expenses', $data);
|
||||
break;
|
||||
|
||||
default:
|
||||
$count = $this->expenseService->bulk($ids, $action);
|
||||
}
|
||||
|
||||
if ($count > 0) {
|
||||
$message = Utils::pluralize($action.'d_expense', $count);
|
||||
Session::flash('message', $message);
|
||||
}
|
||||
|
||||
return Redirect::to('expenses');
|
||||
}
|
||||
|
||||
private static function getViewModel()
|
||||
{
|
||||
return [
|
||||
'data' => Input::old('data'),
|
||||
'account' => Auth::user()->account,
|
||||
'sizes' => Cache::get('sizes'),
|
||||
'paymentTerms' => Cache::get('paymentTerms'),
|
||||
'industries' => Cache::get('industries'),
|
||||
'currencies' => Cache::get('currencies'),
|
||||
'languages' => Cache::get('languages'),
|
||||
'countries' => Cache::get('countries'),
|
||||
'customLabel1' => Auth::user()->account->custom_vendor_label1,
|
||||
'customLabel2' => Auth::user()->account->custom_vendor_label2,
|
||||
];
|
||||
}
|
||||
|
||||
public function show($publicId)
|
||||
{
|
||||
Session::reflash();
|
||||
|
||||
return Redirect::to("expenses/{$publicId}/edit");
|
||||
}
|
||||
}
|
181
app/Http/Controllers/ExportController.php
Normal file
181
app/Http/Controllers/ExportController.php
Normal file
@ -0,0 +1,181 @@
|
||||
<?php namespace App\Http\Controllers;
|
||||
|
||||
use Auth;
|
||||
use Excel;
|
||||
use Illuminate\Http\Request;
|
||||
use League\Fractal\Manager;
|
||||
use League\Fractal\Resource\Item;
|
||||
use App\Ninja\Serializers\ArraySerializer;
|
||||
use App\Ninja\Transformers\AccountTransformer;
|
||||
use App\Models\Client;
|
||||
use App\Models\Contact;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Task;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\Vendor;
|
||||
use App\Models\VendorContact;
|
||||
|
||||
class ExportController extends BaseController
|
||||
{
|
||||
public function doExport(Request $request)
|
||||
{
|
||||
$format = $request->input('format');
|
||||
$date = date('Y-m-d');
|
||||
$fileName = "invoice-ninja-{$date}";
|
||||
|
||||
if ($format === 'JSON') {
|
||||
return $this->returnJSON($request, $fileName);
|
||||
} elseif ($format === 'CSV') {
|
||||
return $this->returnCSV($request, $fileName);
|
||||
} else {
|
||||
return $this->returnXLS($request, $fileName);
|
||||
}
|
||||
}
|
||||
|
||||
private function returnJSON($request, $fileName)
|
||||
{
|
||||
$output = fopen('php://output', 'w') or Utils::fatalError();
|
||||
header('Content-Type:application/json');
|
||||
header("Content-Disposition:attachment;filename={$fileName}.json");
|
||||
|
||||
$manager = new Manager();
|
||||
$manager->setSerializer(new ArraySerializer());
|
||||
|
||||
$account = Auth::user()->account;
|
||||
$account->loadAllData();
|
||||
|
||||
$resource = new Item($account, new AccountTransformer);
|
||||
$data = $manager->createData($resource)->toArray();
|
||||
|
||||
return response()->json($data);
|
||||
}
|
||||
|
||||
|
||||
private function returnCSV($request, $fileName)
|
||||
{
|
||||
$data = $this->getData($request);
|
||||
|
||||
return Excel::create($fileName, function($excel) use ($data) {
|
||||
$excel->sheet('', function($sheet) use ($data) {
|
||||
$sheet->loadView('export', $data);
|
||||
});
|
||||
})->download('csv');
|
||||
}
|
||||
|
||||
private function returnXLS($request, $fileName)
|
||||
{
|
||||
$user = Auth::user();
|
||||
$data = $this->getData($request);
|
||||
|
||||
return Excel::create($fileName, function($excel) use ($user, $data) {
|
||||
|
||||
$excel->setTitle($data['title'])
|
||||
->setCreator($user->getDisplayName())
|
||||
->setLastModifiedBy($user->getDisplayName())
|
||||
->setDescription('')
|
||||
->setSubject('')
|
||||
->setKeywords('')
|
||||
->setCategory('')
|
||||
->setManager('')
|
||||
->setCompany($user->account->getDisplayName());
|
||||
|
||||
foreach ($data as $key => $val) {
|
||||
if ($key === 'account' || $key === 'title' || $key === 'multiUser') {
|
||||
continue;
|
||||
}
|
||||
$label = trans("texts.{$key}");
|
||||
$excel->sheet($label, function($sheet) use ($key, $data) {
|
||||
if ($key === 'quotes') {
|
||||
$key = 'invoices';
|
||||
$data['entityType'] = ENTITY_QUOTE;
|
||||
}
|
||||
$sheet->loadView("export.{$key}", $data);
|
||||
});
|
||||
}
|
||||
})->download('xls');
|
||||
}
|
||||
|
||||
private function getData($request)
|
||||
{
|
||||
$account = Auth::user()->account;
|
||||
|
||||
$data = [
|
||||
'account' => $account,
|
||||
'title' => 'Invoice Ninja v' . NINJA_VERSION . ' - ' . $account->formatDateTime($account->getDateTime()),
|
||||
'multiUser' => $account->users->count() > 1
|
||||
];
|
||||
|
||||
if ($request->input(ENTITY_CLIENT)) {
|
||||
$data['clients'] = Client::scope()
|
||||
->with('user', 'contacts', 'country')
|
||||
->withTrashed()
|
||||
->where('is_deleted', '=', false)
|
||||
->get();
|
||||
|
||||
$data['contacts'] = Contact::scope()
|
||||
->with('user', 'client.contacts')
|
||||
->withTrashed()
|
||||
->get();
|
||||
|
||||
$data['credits'] = Credit::scope()
|
||||
->with('user', 'client.contacts')
|
||||
->get();
|
||||
}
|
||||
|
||||
if ($request->input(ENTITY_TASK)) {
|
||||
$data['tasks'] = Task::scope()
|
||||
->with('user', 'client.contacts')
|
||||
->withTrashed()
|
||||
->where('is_deleted', '=', false)
|
||||
->get();
|
||||
}
|
||||
|
||||
if ($request->input(ENTITY_INVOICE)) {
|
||||
$data['invoices'] = Invoice::scope()
|
||||
->with('user', 'client.contacts', 'invoice_status')
|
||||
->withTrashed()
|
||||
->where('is_deleted', '=', false)
|
||||
->where('is_quote', '=', false)
|
||||
->where('is_recurring', '=', false)
|
||||
->get();
|
||||
|
||||
$data['quotes'] = Invoice::scope()
|
||||
->with('user', 'client.contacts', 'invoice_status')
|
||||
->withTrashed()
|
||||
->where('is_deleted', '=', false)
|
||||
->where('is_quote', '=', true)
|
||||
->where('is_recurring', '=', false)
|
||||
->get();
|
||||
}
|
||||
|
||||
if ($request->input(ENTITY_PAYMENT)) {
|
||||
$data['payments'] = Payment::scope()
|
||||
->withTrashed()
|
||||
->where('is_deleted', '=', false)
|
||||
->with('user', 'client.contacts', 'payment_type', 'invoice', 'account_gateway.gateway')
|
||||
->get();
|
||||
}
|
||||
|
||||
|
||||
if ($request->input(ENTITY_VENDOR)) {
|
||||
$data['clients'] = Vendor::scope()
|
||||
->with('user', 'vendorcontacts', 'country')
|
||||
->withTrashed()
|
||||
->where('is_deleted', '=', false)
|
||||
->get();
|
||||
|
||||
$data['vendor_contacts'] = VendorContact::scope()
|
||||
->with('user', 'vendor.contacts')
|
||||
->withTrashed()
|
||||
->get();
|
||||
/*
|
||||
$data['expenses'] = Credit::scope()
|
||||
->with('user', 'client.contacts')
|
||||
->get();
|
||||
*/
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
<?php namespace App\Http\Controllers;
|
||||
|
||||
use Response;
|
||||
use Request;
|
||||
use Redirect;
|
||||
use Auth;
|
||||
use View;
|
||||
@ -41,6 +40,11 @@ class HomeController extends BaseController
|
||||
return View::make('public.terms', ['hideHeader' => true]);
|
||||
}
|
||||
|
||||
public function viewLogo()
|
||||
{
|
||||
return View::make('public.logo');
|
||||
}
|
||||
|
||||
public function invoiceNow()
|
||||
{
|
||||
if (Auth::check() && Input::get('new_company')) {
|
||||
@ -49,11 +53,18 @@ class HomeController extends BaseController
|
||||
Auth::logout();
|
||||
}
|
||||
|
||||
// Track the referral/campaign code
|
||||
foreach (['rc', 'utm_campaign'] as $code) {
|
||||
if (Input::has($code)) {
|
||||
Session::set(SESSION_REFERRAL_CODE, Input::get($code));
|
||||
}
|
||||
}
|
||||
|
||||
if (Auth::check()) {
|
||||
$redirectTo = Input::get('redirect_to', 'invoices/create');
|
||||
return Redirect::to($redirectTo)->with('sign_up', Input::get('sign_up'));
|
||||
} else {
|
||||
return View::make('public.header', ['invoiceNow' => true]);
|
||||
return View::make('public.invoice_now');
|
||||
}
|
||||
}
|
||||
|
||||
|
92
app/Http/Controllers/ImportController.php
Normal file
92
app/Http/Controllers/ImportController.php
Normal file
@ -0,0 +1,92 @@
|
||||
<?php namespace app\Http\Controllers;
|
||||
|
||||
use Utils;
|
||||
use View;
|
||||
use Exception;
|
||||
use Input;
|
||||
use Session;
|
||||
use Redirect;
|
||||
use App\Services\ImportService;
|
||||
use App\Http\Controllers\BaseController;
|
||||
|
||||
class ImportController extends BaseController
|
||||
{
|
||||
public function __construct(ImportService $importService)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->importService = $importService;
|
||||
}
|
||||
|
||||
public function doImport()
|
||||
{
|
||||
$source = Input::get('source');
|
||||
$files = [];
|
||||
|
||||
foreach (ImportService::$entityTypes as $entityType) {
|
||||
if (Input::file("{$entityType}_file")) {
|
||||
$files[$entityType] = Input::file("{$entityType}_file")->getRealPath();
|
||||
if ($source === IMPORT_CSV) {
|
||||
Session::forget("{$entityType}-data");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if ($source === IMPORT_CSV) {
|
||||
$data = $this->importService->mapCSV($files);
|
||||
return View::make('accounts.import_map', ['data' => $data]);
|
||||
} else {
|
||||
$results = $this->importService->import($source, $files);
|
||||
return $this->showResult($results);
|
||||
}
|
||||
} catch (Exception $exception) {
|
||||
Utils::logError($exception);
|
||||
Session::flash('error', $exception->getMessage());
|
||||
return Redirect::to('/settings/' . ACCOUNT_IMPORT_EXPORT);
|
||||
}
|
||||
}
|
||||
|
||||
public function doImportCSV()
|
||||
{
|
||||
$map = Input::get('map');
|
||||
$headers = Input::get('headers');
|
||||
|
||||
try {
|
||||
$results = $this->importService->importCSV($map, $headers);
|
||||
return $this->showResult($results);
|
||||
} catch (Exception $exception) {
|
||||
Utils::logError($exception);
|
||||
Session::flash('error', $exception->getMessage());
|
||||
return Redirect::to('/settings/' . ACCOUNT_IMPORT_EXPORT);
|
||||
}
|
||||
}
|
||||
|
||||
private function showResult($results)
|
||||
{
|
||||
$message = '';
|
||||
$skipped = [];
|
||||
|
||||
foreach ($results as $entityType => $entityResults) {
|
||||
if ($count = count($entityResults[RESULT_SUCCESS])) {
|
||||
$message .= trans("texts.created_{$entityType}s", ['count' => $count]) . '<br/>';
|
||||
}
|
||||
if (count($entityResults[RESULT_FAILURE])) {
|
||||
$skipped = array_merge($skipped, $entityResults[RESULT_FAILURE]);
|
||||
}
|
||||
}
|
||||
|
||||
if (count($skipped)) {
|
||||
$message .= '<p/>' . trans('texts.failed_to_import') . '<br/>';
|
||||
foreach ($skipped as $skip) {
|
||||
$message .= json_encode($skip) . '<br/>';
|
||||
}
|
||||
}
|
||||
|
||||
if ($message) {
|
||||
Session::flash('warning', $message);
|
||||
}
|
||||
|
||||
return Redirect::to('/settings/' . ACCOUNT_IMPORT_EXPORT);
|
||||
}
|
||||
}
|
@ -13,10 +13,11 @@ class IntegrationController extends Controller
|
||||
$eventId = Utils::lookupEventId(trim(Input::get('event')));
|
||||
|
||||
if (!$eventId) {
|
||||
return Response::json('', 500);
|
||||
return Response::json('Event is invalid', 500);
|
||||
}
|
||||
|
||||
$subscription = Subscription::where('account_id', '=', Auth::user()->account_id)->where('event_id', '=', $eventId)->first();
|
||||
$subscription = Subscription::where('account_id', '=', Auth::user()->account_id)
|
||||
->where('event_id', '=', $eventId)->first();
|
||||
|
||||
if (!$subscription) {
|
||||
$subscription = new Subscription();
|
||||
@ -27,6 +28,10 @@ class IntegrationController extends Controller
|
||||
$subscription->target_url = trim(Input::get('target_url'));
|
||||
$subscription->save();
|
||||
|
||||
if (!$subscription->id) {
|
||||
return Response::json('Failed to create subscription', 500);
|
||||
}
|
||||
|
||||
return Response::json('{"id":'.$subscription->id.'}', 201);
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
<?php namespace App\Http\Controllers;
|
||||
|
||||
use Auth;
|
||||
use Illuminate\Support\Facades\Request;
|
||||
use Utils;
|
||||
use Response;
|
||||
use Input;
|
||||
use Validator;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Client;
|
||||
use App\Models\Contact;
|
||||
@ -12,26 +14,57 @@ use App\Models\Invitation;
|
||||
use App\Ninja\Repositories\ClientRepository;
|
||||
use App\Ninja\Repositories\InvoiceRepository;
|
||||
use App\Ninja\Mailers\ContactMailer as Mailer;
|
||||
use App\Http\Controllers\BaseAPIController;
|
||||
use App\Ninja\Transformers\InvoiceTransformer;
|
||||
use App\Http\Requests\CreateInvoiceRequest;
|
||||
use App\Http\Requests\UpdateInvoiceRequest;
|
||||
|
||||
class InvoiceApiController extends Controller
|
||||
class InvoiceApiController extends BaseAPIController
|
||||
{
|
||||
protected $invoiceRepo;
|
||||
|
||||
public function __construct(InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, Mailer $mailer)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->invoiceRepo = $invoiceRepo;
|
||||
$this->clientRepo = $clientRepo;
|
||||
$this->mailer = $mailer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @SWG\Get(
|
||||
* path="/invoices",
|
||||
* summary="List of invoices",
|
||||
* tags={"invoice"},
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="A list with invoices",
|
||||
* @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/Invoice"))
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response="default",
|
||||
* description="an ""unexpected"" error"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$invoices = Invoice::scope()
|
||||
->with('client', 'invitations.account')
|
||||
->where('invoices.is_quote', '=', false)
|
||||
->orderBy('created_at', 'desc')
|
||||
->get();
|
||||
$paginator = Invoice::scope()->withTrashed();
|
||||
$invoices = Invoice::scope()->withTrashed()
|
||||
->with(array_merge(['invoice_items'], $this->getIncluded()));
|
||||
|
||||
if ($clientPublicId = Input::get('client_id')) {
|
||||
$filter = function($query) use ($clientPublicId) {
|
||||
$query->where('public_id', '=', $clientPublicId);
|
||||
};
|
||||
$invoices->whereHas('client', $filter);
|
||||
$paginator->whereHas('client', $filter);
|
||||
}
|
||||
|
||||
$invoices = $invoices->orderBy('created_at', 'desc')->paginate();
|
||||
|
||||
/*
|
||||
// Add the first invitation link to the data
|
||||
foreach ($invoices as $key => $invoice) {
|
||||
foreach ($invoice->invitations as $subKey => $invitation) {
|
||||
@ -39,39 +72,57 @@ class InvoiceApiController extends Controller
|
||||
}
|
||||
unset($invoice['invitations']);
|
||||
}
|
||||
*/
|
||||
|
||||
$invoices = Utils::remapPublicIds($invoices);
|
||||
$transformer = new InvoiceTransformer(Auth::user()->account, Input::get('serializer'));
|
||||
$paginator = $paginator->paginate();
|
||||
|
||||
$response = json_encode($invoices, JSON_PRETTY_PRINT);
|
||||
$headers = Utils::getApiHeaders(count($invoices));
|
||||
$data = $this->createCollection($invoices, $transformer, 'invoices', $paginator);
|
||||
|
||||
return Response::make($response, 200, $headers);
|
||||
return $this->response($data);
|
||||
}
|
||||
|
||||
public function store()
|
||||
|
||||
/**
|
||||
* @SWG\Post(
|
||||
* path="/invoices",
|
||||
* tags={"invoice"},
|
||||
* summary="Create an invoice",
|
||||
* @SWG\Parameter(
|
||||
* in="body",
|
||||
* name="body",
|
||||
* @SWG\Schema(ref="#/definitions/Invoice")
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="New invoice",
|
||||
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Invoice"))
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response="default",
|
||||
* description="an ""unexpected"" error"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function store(CreateInvoiceRequest $request)
|
||||
{
|
||||
$data = Input::all();
|
||||
$error = null;
|
||||
|
||||
// check if the invoice number is set and unique
|
||||
if (!isset($data['invoice_number']) && !isset($data['id'])) {
|
||||
$data['invoice_number'] = Auth::user()->account->getNextInvoiceNumber();
|
||||
} else if (isset($data['invoice_number'])) {
|
||||
$invoice = Invoice::scope()->where('invoice_number', '=', $data['invoice_number'])->first();
|
||||
if ($invoice) {
|
||||
$error = trans('validation.unique', ['attribute' => 'texts.invoice_number']);
|
||||
} else {
|
||||
$data['id'] = $invoice->public_id;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($data['email'])) {
|
||||
$client = Client::scope()->whereHas('contacts', function($query) use ($data) {
|
||||
$query->where('email', '=', $data['email']);
|
||||
$email = $data['email'];
|
||||
$client = Client::scope()->whereHas('contacts', function($query) use ($email) {
|
||||
$query->where('email', '=', $email);
|
||||
})->first();
|
||||
|
||||
if (!$client) {
|
||||
$clientData = ['contact' => ['email' => $data['email']]];
|
||||
$validator = Validator::make(['email'=>$email], ['email' => 'email']);
|
||||
if ($validator->fails()) {
|
||||
$messages = $validator->messages();
|
||||
return $messages->first();
|
||||
}
|
||||
|
||||
$clientData = ['contact' => ['email' => $email]];
|
||||
foreach (['name', 'private_notes'] as $field) {
|
||||
if (isset($data[$field])) {
|
||||
$clientData[$field] = $data[$field];
|
||||
@ -82,29 +133,16 @@ class InvoiceApiController extends Controller
|
||||
$clientData[$field] = $data[$field];
|
||||
}
|
||||
}
|
||||
$error = $this->clientRepo->getErrors($clientData);
|
||||
if (!$error) {
|
||||
$client = $this->clientRepo->save(false, $clientData, false);
|
||||
}
|
||||
|
||||
$client = $this->clientRepo->save($clientData);
|
||||
}
|
||||
} else if (isset($data['client_id'])) {
|
||||
$client = Client::scope($data['client_id'])->first();
|
||||
$client = Client::scope($data['client_id'])->firstOrFail();
|
||||
}
|
||||
|
||||
if (!$error) {
|
||||
if (!isset($data['client_id']) && !isset($data['email'])) {
|
||||
$error = trans('validation.', ['attribute' => 'client_id or email']);
|
||||
} else if (!$client) {
|
||||
$error = trans('validation.not_in', ['attribute' => 'client_id']);
|
||||
}
|
||||
}
|
||||
|
||||
if ($error) {
|
||||
$response = json_encode($error, JSON_PRETTY_PRINT);
|
||||
} else {
|
||||
$data = self::prepareData($data);
|
||||
$data = self::prepareData($data, $client);
|
||||
$data['client_id'] = $client->id;
|
||||
$invoice = $this->invoiceRepo->save(false, $data, false);
|
||||
$invoice = $this->invoiceRepo->save($data);
|
||||
|
||||
if (!isset($data['id'])) {
|
||||
$invitation = Invitation::createNew();
|
||||
@ -118,22 +156,17 @@ class InvoiceApiController extends Controller
|
||||
$this->mailer->sendInvoice($invoice);
|
||||
}
|
||||
|
||||
// prepare the return data
|
||||
$invoice = Invoice::scope($invoice->public_id)->with('client', 'invoice_items', 'invitations')->first();
|
||||
$invoice = Utils::remapPublicIds([$invoice]);
|
||||
$transformer = new InvoiceTransformer(\Auth::user()->account, Input::get('serializer'));
|
||||
$data = $this->createItem($invoice, $transformer, 'invoice');
|
||||
|
||||
$response = json_encode($invoice, JSON_PRETTY_PRINT);
|
||||
return $this->response($data);
|
||||
}
|
||||
|
||||
$headers = Utils::getApiHeaders();
|
||||
|
||||
return Response::make($response, $error ? 400 : 200, $headers);
|
||||
}
|
||||
|
||||
private function prepareData($data)
|
||||
private function prepareData($data, $client)
|
||||
{
|
||||
$account = Auth::user()->account;
|
||||
$account->loadLocalizationSettings();
|
||||
$account->loadLocalizationSettings($client);
|
||||
|
||||
// set defaults for optional fields
|
||||
$fields = [
|
||||
@ -165,18 +198,13 @@ class InvoiceApiController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
// hardcode some fields
|
||||
$fields = [
|
||||
'is_recurring' => false
|
||||
];
|
||||
|
||||
foreach ($fields as $key => $val) {
|
||||
$data[$key] = $val;
|
||||
}
|
||||
|
||||
// initialize the line items
|
||||
if (isset($data['product_key']) || isset($data['cost']) || isset($data['notes']) || isset($data['qty'])) {
|
||||
$data['invoice_items'] = [self::prepareItem($data)];
|
||||
|
||||
// make sure the tax isn't applied twice (for the invoice and the line item)
|
||||
unset($data['invoice_items'][0]['tax_name']);
|
||||
unset($data['invoice_items'][0]['tax_rate']);
|
||||
} else {
|
||||
foreach ($data['invoice_items'] as $index => $item) {
|
||||
$data['invoice_items'][$index] = self::prepareItem($item);
|
||||
@ -202,13 +230,13 @@ class InvoiceApiController extends Controller
|
||||
}
|
||||
|
||||
// if only the product key is set we'll load the cost and notes
|
||||
if ($item['product_key'] && (!$item['cost'] || !$item['notes'])) {
|
||||
if ($item['product_key'] && (is_null($item['cost']) || is_null($item['notes']))) {
|
||||
$product = Product::findProductByKey($item['product_key']);
|
||||
if ($product) {
|
||||
if (!$item['cost']) {
|
||||
if (is_null($item['cost'])) {
|
||||
$item['cost'] = $product->cost;
|
||||
}
|
||||
if (!$item['notes']) {
|
||||
if (is_null($item['notes'])) {
|
||||
$item['notes'] = $product->notes;
|
||||
}
|
||||
}
|
||||
@ -242,4 +270,89 @@ class InvoiceApiController extends Controller
|
||||
$headers = Utils::getApiHeaders();
|
||||
return Response::make($response, $error ? 400 : 200, $headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @SWG\Put(
|
||||
* path="/invoices",
|
||||
* tags={"invoice"},
|
||||
* summary="Update an invoice",
|
||||
* @SWG\Parameter(
|
||||
* in="body",
|
||||
* name="body",
|
||||
* @SWG\Schema(ref="#/definitions/Invoice")
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="Update invoice",
|
||||
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Invoice"))
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response="default",
|
||||
* description="an ""unexpected"" error"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function update(UpdateInvoiceRequest $request, $publicId)
|
||||
{
|
||||
if ($request->action == ACTION_ARCHIVE) {
|
||||
$invoice = Invoice::scope($publicId)->firstOrFail();
|
||||
$this->invoiceRepo->archive($invoice);
|
||||
/*
|
||||
$response = json_encode(RESULT_SUCCESS, JSON_PRETTY_PRINT);
|
||||
$headers = Utils::getApiHeaders();
|
||||
return Response::make($response, 200, $headers);
|
||||
*/
|
||||
$transformer = new InvoiceTransformer(\Auth::user()->account, Input::get('serializer'));
|
||||
$data = $this->createItem($invoice, $transformer, 'invoice');
|
||||
|
||||
return $this->response($data);
|
||||
}
|
||||
|
||||
$data = $request->input();
|
||||
$data['public_id'] = $publicId;
|
||||
$this->invoiceRepo->save($data);
|
||||
|
||||
$invoice = Invoice::scope($publicId)->with('client', 'invoice_items', 'invitations')->firstOrFail();
|
||||
$transformer = new InvoiceTransformer(\Auth::user()->account, Input::get('serializer'));
|
||||
$data = $this->createItem($invoice, $transformer, 'invoice');
|
||||
|
||||
return $this->response($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @SWG\Delete(
|
||||
* path="/invoices",
|
||||
* tags={"invoice"},
|
||||
* summary="Delete an invoice",
|
||||
* @SWG\Parameter(
|
||||
* in="body",
|
||||
* name="body",
|
||||
* @SWG\Schema(ref="#/definitions/Invoice")
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="Delete invoice",
|
||||
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Invoice"))
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response="default",
|
||||
* description="an ""unexpected"" error"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
|
||||
public function destroy($publicId)
|
||||
{
|
||||
$data['public_id'] = $publicId;
|
||||
$invoice = Invoice::scope($publicId)->firstOrFail();
|
||||
|
||||
$this->invoiceRepo->delete($invoice);
|
||||
|
||||
$transformer = new InvoiceTransformer(\Auth::user()->account, Input::get('serializer'));
|
||||
$data = $this->createItem($invoice, $transformer, 'invoice');
|
||||
|
||||
return $this->response($data);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -11,45 +11,39 @@ use DB;
|
||||
use Event;
|
||||
use URL;
|
||||
use Datatable;
|
||||
use finfo;
|
||||
use Request;
|
||||
use DropdownButton;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Invitation;
|
||||
use App\Models\Client;
|
||||
use App\Models\Account;
|
||||
use App\Models\Product;
|
||||
use App\Models\Country;
|
||||
use App\Models\TaxRate;
|
||||
use App\Models\Currency;
|
||||
use App\Models\Size;
|
||||
use App\Models\Industry;
|
||||
use App\Models\PaymentTerm;
|
||||
use App\Models\InvoiceDesign;
|
||||
use App\Models\AccountGateway;
|
||||
use App\Models\Activity;
|
||||
use App\Models\Gateway;
|
||||
use App\Ninja\Mailers\ContactMailer as Mailer;
|
||||
use App\Ninja\Repositories\InvoiceRepository;
|
||||
use App\Ninja\Repositories\ClientRepository;
|
||||
use App\Ninja\Repositories\TaxRateRepository;
|
||||
use App\Events\InvoiceViewed;
|
||||
use App\Services\InvoiceService;
|
||||
use App\Services\RecurringInvoiceService;
|
||||
use App\Http\Requests\SaveInvoiceWithClientRequest;
|
||||
|
||||
class InvoiceController extends BaseController
|
||||
{
|
||||
protected $mailer;
|
||||
protected $invoiceRepo;
|
||||
protected $clientRepo;
|
||||
protected $taxRateRepo;
|
||||
protected $invoiceService;
|
||||
protected $recurringInvoiceService;
|
||||
|
||||
public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, TaxRateRepository $taxRateRepo)
|
||||
public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, InvoiceService $invoiceService, RecurringInvoiceService $recurringInvoiceService)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->mailer = $mailer;
|
||||
$this->invoiceRepo = $invoiceRepo;
|
||||
$this->clientRepo = $clientRepo;
|
||||
$this->taxRateRepo = $taxRateRepo;
|
||||
$this->invoiceService = $invoiceService;
|
||||
$this->recurringInvoiceService = $recurringInvoiceService;
|
||||
}
|
||||
|
||||
public function index()
|
||||
@ -57,46 +51,20 @@ class InvoiceController extends BaseController
|
||||
$data = [
|
||||
'title' => trans('texts.invoices'),
|
||||
'entityType' => ENTITY_INVOICE,
|
||||
'columns' => Utils::trans(['checkbox', 'invoice_number', 'client', 'invoice_date', 'invoice_total', 'balance_due', 'due_date', 'status', 'action']),
|
||||
'columns' => Utils::trans([
|
||||
'checkbox',
|
||||
'invoice_number',
|
||||
'client',
|
||||
'invoice_date',
|
||||
'invoice_total',
|
||||
'balance_due',
|
||||
'due_date',
|
||||
'status',
|
||||
''
|
||||
]),
|
||||
];
|
||||
|
||||
$recurringInvoices = Invoice::scope()->where('is_recurring', '=', true);
|
||||
|
||||
if (Session::get('show_trash:invoice')) {
|
||||
$recurringInvoices->withTrashed();
|
||||
} else {
|
||||
$recurringInvoices->join('clients', 'clients.id', '=', 'invoices.client_id')
|
||||
->where('clients.deleted_at', '=', null);
|
||||
}
|
||||
|
||||
if ($recurringInvoices->count() > 0) {
|
||||
$data['secEntityType'] = ENTITY_RECURRING_INVOICE;
|
||||
$data['secColumns'] = Utils::trans(['checkbox', 'frequency', 'client', 'start_date', 'end_date', 'invoice_total', 'action']);
|
||||
}
|
||||
|
||||
return View::make('list', $data);
|
||||
}
|
||||
|
||||
public function clientIndex()
|
||||
{
|
||||
$invitationKey = Session::get('invitation_key');
|
||||
if (!$invitationKey) {
|
||||
return Redirect::to('/setup');
|
||||
}
|
||||
|
||||
$invitation = Invitation::with('account')->where('invitation_key', '=', $invitationKey)->first();
|
||||
$account = $invitation->account;
|
||||
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
|
||||
|
||||
$data = [
|
||||
'color' => $color,
|
||||
'hideLogo' => $account->isWhiteLabel(),
|
||||
'title' => trans('texts.invoices'),
|
||||
'entityType' => ENTITY_INVOICE,
|
||||
'columns' => Utils::trans(['invoice_number', 'invoice_date', 'invoice_total', 'balance_due', 'due_date']),
|
||||
];
|
||||
|
||||
return View::make('public_list', $data);
|
||||
return response()->view('list', $data);
|
||||
}
|
||||
|
||||
public function getDatatable($clientPublicId = null)
|
||||
@ -104,166 +72,24 @@ class InvoiceController extends BaseController
|
||||
$accountId = Auth::user()->account_id;
|
||||
$search = Input::get('sSearch');
|
||||
|
||||
return $this->invoiceRepo->getDatatable($accountId, $clientPublicId, ENTITY_INVOICE, $search);
|
||||
}
|
||||
|
||||
public function getClientDatatable()
|
||||
{
|
||||
//$accountId = Auth::user()->account_id;
|
||||
$search = Input::get('sSearch');
|
||||
$invitationKey = Session::get('invitation_key');
|
||||
$invitation = Invitation::where('invitation_key', '=', $invitationKey)->first();
|
||||
|
||||
if (!$invitation || $invitation->is_deleted) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$invoice = $invitation->invoice;
|
||||
|
||||
if (!$invoice || $invoice->is_deleted) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->invoiceRepo->getClientDatatable($invitation->contact_id, ENTITY_INVOICE, $search);
|
||||
return $this->invoiceService->getDatatable($accountId, $clientPublicId, ENTITY_INVOICE, $search);
|
||||
}
|
||||
|
||||
public function getRecurringDatatable($clientPublicId = null)
|
||||
{
|
||||
$query = $this->invoiceRepo->getRecurringInvoices(Auth::user()->account_id, $clientPublicId, Input::get('sSearch'));
|
||||
$table = Datatable::query($query);
|
||||
$accountId = Auth::user()->account_id;
|
||||
$search = Input::get('sSearch');
|
||||
|
||||
if (!$clientPublicId) {
|
||||
$table->addColumn('checkbox', function ($model) { return '<input type="checkbox" name="ids[]" value="'.$model->public_id.'" '.Utils::getEntityRowClass($model).'>'; });
|
||||
}
|
||||
|
||||
$table->addColumn('frequency', function ($model) { return link_to('invoices/'.$model->public_id, $model->frequency); });
|
||||
|
||||
if (!$clientPublicId) {
|
||||
$table->addColumn('client_name', function ($model) { return link_to('clients/'.$model->client_public_id, Utils::getClientDisplayName($model)); });
|
||||
}
|
||||
|
||||
return $table->addColumn('start_date', function ($model) { return Utils::fromSqlDate($model->start_date); })
|
||||
->addColumn('end_date', function ($model) { return Utils::fromSqlDate($model->end_date); })
|
||||
->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id); })
|
||||
->addColumn('dropdown', function ($model) {
|
||||
if ($model->is_deleted) {
|
||||
return '<div style="height:38px"/>';
|
||||
}
|
||||
|
||||
$str = '<div class="btn-group tr-action" style="visibility:hidden;">
|
||||
<button type="button" class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
'.trans('texts.select').' <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" role="menu">';
|
||||
|
||||
if (!$model->deleted_at || $model->deleted_at == '0000-00-00') {
|
||||
$str .= '<li><a href="'.URL::to('invoices/'.$model->public_id.'/edit').'">'.trans('texts.edit_invoice').'</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="javascript:archiveEntity('.$model->public_id.')">'.trans('texts.archive_invoice').'</a></li>
|
||||
<li><a href="javascript:deleteEntity('.$model->public_id.')">'.trans('texts.delete_invoice').'</a></li>';
|
||||
} else {
|
||||
$str .= '<li><a href="javascript:restoreEntity('.$model->public_id.')">'.trans('texts.restore_invoice').'</a></li>';
|
||||
}
|
||||
|
||||
return $str.'</ul>
|
||||
</div>';
|
||||
|
||||
})
|
||||
->make();
|
||||
}
|
||||
|
||||
public function view($invitationKey)
|
||||
{
|
||||
$invitation = Invitation::where('invitation_key', '=', $invitationKey)->firstOrFail();
|
||||
$invoice = $invitation->invoice;
|
||||
|
||||
if (!$invoice || $invoice->is_deleted) {
|
||||
return View::make('invoices.deleted');
|
||||
}
|
||||
|
||||
$invoice->load('user', 'invoice_items', 'invoice_design', 'account.country', 'client.contacts', 'client.country');
|
||||
$client = $invoice->client;
|
||||
$account = $client->account;
|
||||
|
||||
if (!$client || $client->is_deleted) {
|
||||
return View::make('invoices.deleted');
|
||||
}
|
||||
|
||||
if ($account->subdomain) {
|
||||
$server = explode('.', Request::server('HTTP_HOST'));
|
||||
$subdomain = $server[0];
|
||||
|
||||
if (!in_array($subdomain, ['app', 'www']) && $subdomain != $account->subdomain) {
|
||||
return View::make('invoices.deleted');
|
||||
}
|
||||
}
|
||||
|
||||
if (!Session::has($invitationKey) && (!Auth::check() || Auth::user()->account_id != $invoice->account_id)) {
|
||||
Activity::viewInvoice($invitation);
|
||||
Event::fire(new InvoiceViewed($invoice));
|
||||
}
|
||||
|
||||
Session::set($invitationKey, true);
|
||||
Session::set('invitation_key', $invitationKey);
|
||||
|
||||
$account->loadLocalizationSettings();
|
||||
|
||||
$invoice->invoice_date = Utils::fromSqlDate($invoice->invoice_date);
|
||||
$invoice->due_date = Utils::fromSqlDate($invoice->due_date);
|
||||
$invoice->is_pro = $account->isPro();
|
||||
|
||||
if ($invoice->invoice_design_id == CUSTOM_DESIGN) {
|
||||
$invoice->invoice_design->javascript = $account->custom_design;
|
||||
} else {
|
||||
$invoice->invoice_design->javascript = $invoice->invoice_design->pdfmake;
|
||||
}
|
||||
|
||||
$contact = $invitation->contact;
|
||||
$contact->setVisible([
|
||||
'first_name',
|
||||
'last_name',
|
||||
'email',
|
||||
'phone', ]);
|
||||
|
||||
// Determine payment options
|
||||
$paymentTypes = [];
|
||||
if ($client->getGatewayToken()) {
|
||||
$paymentTypes[] = [
|
||||
'url' => URL::to("payment/{$invitation->invitation_key}/token"), 'label' => trans('texts.use_card_on_file')
|
||||
];
|
||||
}
|
||||
foreach(Gateway::$paymentTypes as $type) {
|
||||
if ($account->getGatewayByType($type)) {
|
||||
$typeLink = strtolower(str_replace('PAYMENT_TYPE_', '', $type));
|
||||
$paymentTypes[] = [
|
||||
'url' => URL::to("/payment/{$invitation->invitation_key}/{$typeLink}"), 'label' => trans('texts.'.strtolower($type))
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$paymentURL = '';
|
||||
if (count($paymentTypes)) {
|
||||
$paymentURL = $paymentTypes[0]['url'];
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'isConverted' => $invoice->quote_invoice_id ? true : false,
|
||||
'showBreadcrumbs' => false,
|
||||
'hideLogo' => $account->isWhiteLabel(),
|
||||
'invoice' => $invoice->hidePrivateFields(),
|
||||
'invitation' => $invitation,
|
||||
'invoiceLabels' => $account->getInvoiceLabels(),
|
||||
'contact' => $contact,
|
||||
'paymentTypes' => $paymentTypes,
|
||||
'paymentURL' => $paymentURL,
|
||||
);
|
||||
|
||||
return View::make('invoices.view', $data);
|
||||
return $this->recurringInvoiceService->getDatatable($accountId, $clientPublicId, ENTITY_RECURRING_INVOICE, $search);
|
||||
}
|
||||
|
||||
public function edit($publicId, $clone = false)
|
||||
{
|
||||
$invoice = Invoice::scope($publicId)->withTrashed()->with('invitations', 'account.country', 'client.contacts', 'client.country', 'invoice_items')->firstOrFail();
|
||||
$account = Auth::user()->account;
|
||||
$invoice = Invoice::scope($publicId)
|
||||
->with('invitations', 'account.country', 'client.contacts', 'client.country', 'invoice_items')
|
||||
->withTrashed()
|
||||
->firstOrFail();
|
||||
$entityType = $invoice->getEntityType();
|
||||
|
||||
$contactIds = DB::table('invitations')
|
||||
@ -274,8 +100,8 @@ class InvoiceController extends BaseController
|
||||
->select('contacts.public_id')->lists('public_id');
|
||||
|
||||
if ($clone) {
|
||||
$invoice->id = null;
|
||||
$invoice->invoice_number = Auth::user()->account->getNextInvoiceNumber($invoice->is_quote);
|
||||
$invoice->id = $invoice->public_id = null;
|
||||
$invoice->invoice_number = $account->getNextInvoiceNumber($invoice);
|
||||
$invoice->balance = $invoice->amount;
|
||||
$invoice->invoice_status_id = 0;
|
||||
$invoice->invoice_date = date_create()->format('Y-m-d');
|
||||
@ -288,9 +114,11 @@ class InvoiceController extends BaseController
|
||||
}
|
||||
|
||||
$invoice->invoice_date = Utils::fromSqlDate($invoice->invoice_date);
|
||||
$invoice->recurring_due_date = $invoice->due_date;// Keep in SQL form
|
||||
$invoice->due_date = Utils::fromSqlDate($invoice->due_date);
|
||||
$invoice->start_date = Utils::fromSqlDate($invoice->start_date);
|
||||
$invoice->end_date = Utils::fromSqlDate($invoice->end_date);
|
||||
$invoice->last_sent_date = Utils::fromSqlDate($invoice->last_sent_date);
|
||||
$invoice->is_pro = Auth::user()->isPro();
|
||||
|
||||
$actions = [
|
||||
@ -329,10 +157,10 @@ class InvoiceController extends BaseController
|
||||
$lastSent = ($invoice->is_recurring && $invoice->last_sent_date) ? $invoice->recurring_invoices->last() : null;
|
||||
|
||||
$data = array(
|
||||
'clients' => Client::scope()->withTrashed()->with('contacts', 'country')->whereId($invoice->client_id)->get(),
|
||||
'entityType' => $entityType,
|
||||
'showBreadcrumbs' => $clone,
|
||||
'invoice' => $invoice,
|
||||
'data' => false,
|
||||
'method' => $method,
|
||||
'invitationContactIds' => $contactIds,
|
||||
'url' => $url,
|
||||
@ -343,45 +171,57 @@ class InvoiceController extends BaseController
|
||||
'lastSent' => $lastSent);
|
||||
$data = array_merge($data, self::getViewModel());
|
||||
|
||||
// Set the invitation link on the client's contacts
|
||||
if ($clone) {
|
||||
$data['formIsChanged'] = true;
|
||||
}
|
||||
|
||||
// Set the invitation data on the client's contacts
|
||||
if (!$clone) {
|
||||
$clients = $data['clients'];
|
||||
foreach ($clients as $client) {
|
||||
if ($client->id == $invoice->client->id) {
|
||||
if ($client->id != $invoice->client->id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($invoice->invitations as $invitation) {
|
||||
foreach ($client->contacts as $contact) {
|
||||
if ($invitation->contact_id == $contact->id) {
|
||||
$contact->email_error = $invitation->email_error;
|
||||
$contact->invitation_link = $invitation->getLink();
|
||||
$contact->invitation_viewed = $invitation->viewed_date && $invitation->viewed_date != '0000-00-00 00:00:00' ? $invitation->viewed_date : false;
|
||||
$contact->invitation_status = $contact->email_error ? false : $invitation->getStatus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return View::make('invoices.edit', $data);
|
||||
}
|
||||
|
||||
public function create($clientPublicId = 0, $isRecurring = false)
|
||||
{
|
||||
$client = null;
|
||||
$invoiceNumber = $isRecurring ? microtime(true) : Auth::user()->account->getNextInvoiceNumber();
|
||||
$account = Auth::user()->account;
|
||||
$entityType = $isRecurring ? ENTITY_RECURRING_INVOICE : ENTITY_INVOICE;
|
||||
$clientId = null;
|
||||
|
||||
if ($clientPublicId) {
|
||||
$client = Client::scope($clientPublicId)->firstOrFail();
|
||||
$clientId = Client::getPrivateId($clientPublicId);
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'entityType' => ENTITY_INVOICE,
|
||||
'invoice' => null,
|
||||
'data' => Input::old('data'),
|
||||
'invoiceNumber' => $invoiceNumber,
|
||||
$invoice = $account->createInvoice($entityType, $clientId);
|
||||
$invoice->public_id = 0;
|
||||
|
||||
$data = [
|
||||
'clients' => Client::scope()->with('contacts', 'country')->orderBy('name')->get(),
|
||||
'entityType' => $invoice->getEntityType(),
|
||||
'invoice' => $invoice,
|
||||
'method' => 'POST',
|
||||
'url' => 'invoices',
|
||||
'title' => trans('texts.new_invoice'),
|
||||
'isRecurring' => $isRecurring,
|
||||
'client' => $client);
|
||||
];
|
||||
$data = array_merge($data, self::getViewModel());
|
||||
|
||||
return View::make('invoices.edit', $data);
|
||||
@ -405,17 +245,66 @@ class InvoiceController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
$recurringDueDateHelp = '';
|
||||
foreach (preg_split("/((\r?\n)|(\r\n?))/", trans('texts.recurring_due_date_help')) as $line) {
|
||||
$parts = explode("=>", $line);
|
||||
if (count($parts) > 1) {
|
||||
$line = $parts[0].' => '.Utils::processVariables($parts[0]);
|
||||
$recurringDueDateHelp .= '<li>'.strip_tags($line).'</li>';
|
||||
} else {
|
||||
$recurringDueDateHelp .= $line;
|
||||
}
|
||||
}
|
||||
|
||||
// Create due date options
|
||||
$recurringDueDates = array(
|
||||
trans('texts.use_client_terms') => array('value' => '', 'class' => 'monthly weekly'),
|
||||
);
|
||||
|
||||
$ends = array('th','st','nd','rd','th','th','th','th','th','th');
|
||||
for($i = 1; $i < 31; $i++){
|
||||
if ($i >= 11 && $i <= 13) $ordinal = $i. 'th';
|
||||
else $ordinal = $i . $ends[$i % 10];
|
||||
|
||||
$dayStr = str_pad($i, 2, '0', STR_PAD_LEFT);
|
||||
$str = trans('texts.day_of_month', array('ordinal'=>$ordinal));
|
||||
|
||||
$recurringDueDates[$str] = array('value' => "1998-01-$dayStr", 'data-num' => $i, 'class' => 'monthly');
|
||||
}
|
||||
$recurringDueDates[trans('texts.last_day_of_month')] = array('value' => "1998-01-31", 'data-num' => 31, 'class' => 'monthly');
|
||||
|
||||
|
||||
$daysOfWeek = array(
|
||||
trans('texts.sunday'),
|
||||
trans('texts.monday'),
|
||||
trans('texts.tuesday'),
|
||||
trans('texts.wednesday'),
|
||||
trans('texts.thursday'),
|
||||
trans('texts.friday'),
|
||||
trans('texts.saturday'),
|
||||
);
|
||||
foreach(array('1st','2nd','3rd','4th') as $i=>$ordinal){
|
||||
foreach($daysOfWeek as $j=>$dayOfWeek){
|
||||
$str = trans('texts.day_of_week_after', array('ordinal' => $ordinal, 'day' => $dayOfWeek));
|
||||
|
||||
$day = $i * 7 + $j + 1;
|
||||
$dayStr = str_pad($day, 2, '0', STR_PAD_LEFT);
|
||||
$recurringDueDates[$str] = array('value' => "1998-02-$dayStr", 'data-num' => $day, 'class' => 'weekly');
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'data' => Input::old('data'),
|
||||
'account' => Auth::user()->account->load('country'),
|
||||
'products' => Product::scope()->orderBy('id')->get(array('product_key', 'notes', 'cost', 'qty')),
|
||||
'countries' => Cache::get('countries'),
|
||||
'clients' => Client::scope()->with('contacts', 'country')->orderBy('name')->get(),
|
||||
'products' => Product::scope()->with('default_tax_rate')->orderBy('id')->get(),
|
||||
'taxRates' => TaxRate::scope()->orderBy('name')->get(),
|
||||
'currencies' => Cache::get('currencies'),
|
||||
'languages' => Cache::get('languages'),
|
||||
'sizes' => Cache::get('sizes'),
|
||||
'paymentTerms' => Cache::get('paymentTerms'),
|
||||
'industries' => Cache::get('industries'),
|
||||
'invoiceDesigns' => InvoiceDesign::getDesigns(),
|
||||
'invoiceFonts' => Cache::get('fonts'),
|
||||
'frequencies' => array(
|
||||
1 => 'Weekly',
|
||||
2 => 'Two weeks',
|
||||
@ -425,9 +314,12 @@ class InvoiceController extends BaseController
|
||||
6 => 'Six months',
|
||||
7 => 'Annually',
|
||||
),
|
||||
'recurringDueDates' => $recurringDueDates,
|
||||
'recurringHelp' => $recurringHelp,
|
||||
'recurringDueDateHelp' => $recurringDueDateHelp,
|
||||
'invoiceLabels' => Auth::user()->account->getInvoiceLabels(),
|
||||
'tasks' => Session::get('tasks') ? json_encode(Session::get('tasks')) : null,
|
||||
'expenses' => Session::get('expenses') ? json_encode(Session::get('expenses')) : null,
|
||||
];
|
||||
|
||||
}
|
||||
@ -437,118 +329,109 @@ class InvoiceController extends BaseController
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function store()
|
||||
{
|
||||
return InvoiceController::save();
|
||||
}
|
||||
|
||||
private function save($publicId = null)
|
||||
public function store(SaveInvoiceWithClientRequest $request)
|
||||
{
|
||||
$action = Input::get('action');
|
||||
$entityType = Input::get('entityType');
|
||||
|
||||
if (in_array($action, ['archive', 'delete', 'mark', 'restore'])) {
|
||||
return InvoiceController::bulk($entityType);
|
||||
}
|
||||
$invoice = $this->invoiceService->save($request->input());
|
||||
$entityType = $invoice->getEntityType();
|
||||
$message = trans("texts.created_{$entityType}");
|
||||
|
||||
$input = json_decode(Input::get('data'));
|
||||
$invoice = $input->invoice;
|
||||
|
||||
if ($errors = $this->invoiceRepo->getErrors($invoice)) {
|
||||
Session::flash('error', trans('texts.invoice_error'));
|
||||
|
||||
return Redirect::to("{$entityType}s/create")
|
||||
->withInput()->withErrors($errors);
|
||||
} else {
|
||||
$this->taxRateRepo->save($input->tax_rates);
|
||||
|
||||
$clientData = (array) $invoice->client;
|
||||
$client = $this->clientRepo->save($invoice->client->public_id, $clientData);
|
||||
|
||||
$invoiceData = (array) $invoice;
|
||||
$invoiceData['client_id'] = $client->id;
|
||||
$invoice = $this->invoiceRepo->save($publicId, $invoiceData, $entityType);
|
||||
|
||||
$account = Auth::user()->account;
|
||||
if ($account->invoice_taxes != $input->invoice_taxes
|
||||
|| $account->invoice_item_taxes != $input->invoice_item_taxes
|
||||
|| $account->invoice_design_id != $input->invoice->invoice_design_id) {
|
||||
$account->invoice_taxes = $input->invoice_taxes;
|
||||
$account->invoice_item_taxes = $input->invoice_item_taxes;
|
||||
$account->invoice_design_id = $input->invoice->invoice_design_id;
|
||||
$account->save();
|
||||
}
|
||||
|
||||
$client->load('contacts');
|
||||
$sendInvoiceIds = [];
|
||||
|
||||
foreach ($client->contacts as $contact) {
|
||||
if ($contact->send_invoice || count($client->contacts) == 1) {
|
||||
$sendInvoiceIds[] = $contact->id;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($client->contacts as $contact) {
|
||||
$invitation = Invitation::scope()->whereContactId($contact->id)->whereInvoiceId($invoice->id)->first();
|
||||
|
||||
if (in_array($contact->id, $sendInvoiceIds) && !$invitation) {
|
||||
$invitation = Invitation::createNew();
|
||||
$invitation->invoice_id = $invoice->id;
|
||||
$invitation->contact_id = $contact->id;
|
||||
$invitation->invitation_key = str_random(RANDOM_KEY_LENGTH);
|
||||
$invitation->save();
|
||||
} elseif (!in_array($contact->id, $sendInvoiceIds) && $invitation) {
|
||||
$invitation->delete();
|
||||
}
|
||||
}
|
||||
|
||||
$message = trans($publicId ? "texts.updated_{$entityType}" : "texts.created_{$entityType}");
|
||||
if ($input->invoice->client->public_id == '-1') {
|
||||
// check if we created a new client with the invoice
|
||||
// TODO: replace with HistoryListener
|
||||
$input = $request->input();
|
||||
$clientPublicId = isset($input['client']['public_id']) ? $input['client']['public_id'] : false;
|
||||
if ($clientPublicId == '-1') {
|
||||
$message = $message.' '.trans('texts.and_created_client');
|
||||
|
||||
$url = URL::to('clients/'.$client->public_id);
|
||||
Utils::trackViewed($client->getDisplayName(), ENTITY_CLIENT, $url);
|
||||
$trackUrl = URL::to('clients/' . $invoice->client->public_id);
|
||||
Utils::trackViewed($invoice->client->getDisplayName(), ENTITY_CLIENT, $trackUrl);
|
||||
}
|
||||
|
||||
$pdfUpload = Input::get('pdfupload');
|
||||
if (!empty($pdfUpload) && strpos($pdfUpload, 'data:application/pdf;base64,') === 0) {
|
||||
$this->storePDF(Input::get('pdfupload'), $invoice);
|
||||
Session::flash('message', $message);
|
||||
|
||||
if ($action == 'email') {
|
||||
return $this->emailInvoice($invoice, Input::get('pdfupload'));
|
||||
}
|
||||
|
||||
return redirect()->to($invoice->getRoute());
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param int $id
|
||||
* @return Response
|
||||
*/
|
||||
public function update(SaveInvoiceWithClientRequest $request)
|
||||
{
|
||||
$action = Input::get('action');
|
||||
$entityType = Input::get('entityType');
|
||||
|
||||
$invoice = $this->invoiceService->save($request->input());
|
||||
$entityType = $invoice->getEntityType();
|
||||
$message = trans("texts.updated_{$entityType}");
|
||||
Session::flash('message', $message);
|
||||
|
||||
if ($action == 'clone') {
|
||||
return $this->cloneInvoice($publicId);
|
||||
return $this->cloneInvoice($invoice->public_id);
|
||||
} elseif ($action == 'convert') {
|
||||
return $this->convertQuote($publicId);
|
||||
return $this->convertQuote($invoice->public_id);
|
||||
} elseif ($action == 'email') {
|
||||
if (Auth::user()->confirmed && !Auth::user()->isDemo()) {
|
||||
return $this->emailInvoice($invoice, Input::get('pdfupload'));
|
||||
}
|
||||
|
||||
return redirect()->to($invoice->getRoute());
|
||||
}
|
||||
|
||||
|
||||
private function emailInvoice($invoice, $pdfUpload)
|
||||
{
|
||||
$entityType = $invoice->getEntityType();
|
||||
$pdfUpload = Utils::decodePDF($pdfUpload);
|
||||
|
||||
if (!Auth::user()->confirmed) {
|
||||
$errorMessage = trans(Auth::user()->registered ? 'texts.confirmation_required' : 'texts.registration_required');
|
||||
Session::flash('error', $errorMessage);
|
||||
return Redirect::to('invoices/'.$invoice->public_id.'/edit');
|
||||
}
|
||||
|
||||
if ($invoice->is_recurring) {
|
||||
if ($invoice->shouldSendToday()) {
|
||||
$invoice = $this->invoiceRepo->createRecurringInvoice($invoice);
|
||||
$response = $this->mailer->sendInvoice($invoice);
|
||||
$response = $this->emailRecurringInvoice($invoice);
|
||||
} else {
|
||||
$response = trans('texts.recurring_too_soon');
|
||||
}
|
||||
} else {
|
||||
$response = $this->mailer->sendInvoice($invoice);
|
||||
$response = $this->mailer->sendInvoice($invoice, false, $pdfUpload);
|
||||
}
|
||||
|
||||
if ($response === true) {
|
||||
$message = trans("texts.emailed_{$entityType}");
|
||||
Session::flash('message', $message);
|
||||
} else {
|
||||
Session::flash('error', $response);
|
||||
}
|
||||
} else {
|
||||
$errorMessage = trans(Auth::user()->registered ? 'texts.confirmation_required' : 'texts.registration_required');
|
||||
Session::flash('error', $errorMessage);
|
||||
Session::flash('message', $message);
|
||||
}
|
||||
} else {
|
||||
Session::flash('message', $message);
|
||||
|
||||
return Redirect::to("{$entityType}s/{$invoice->public_id}/edit");
|
||||
}
|
||||
|
||||
$url = "{$entityType}s/".$invoice->public_id.'/edit';
|
||||
private function emailRecurringInvoice(&$invoice)
|
||||
{
|
||||
if (!$invoice->shouldSendToday()) {
|
||||
if ($date = $invoice->getNextSendDate()) {
|
||||
$date = $invoice->account->formatDate($date);
|
||||
$date .= ' ' . DEFAULT_SEND_RECURRING_HOUR . ':00 am ' . $invoice->account->getTimezone();
|
||||
return trans('texts.recurring_too_soon', ['date' => $date]);
|
||||
} else {
|
||||
return trans('texts.no_longer_running');
|
||||
}
|
||||
}
|
||||
|
||||
return Redirect::to($url);
|
||||
// switch from the recurring invoice to the generated invoice
|
||||
$invoice = $this->invoiceRepo->createRecurringInvoice($invoice);
|
||||
|
||||
// in case auto-bill is enabled then a receipt has been sent
|
||||
if ($invoice->isPaid()) {
|
||||
return true;
|
||||
} else {
|
||||
return $this->mailer->sendInvoice($invoice);
|
||||
}
|
||||
}
|
||||
|
||||
@ -562,18 +445,7 @@ class InvoiceController extends BaseController
|
||||
{
|
||||
Session::reflash();
|
||||
|
||||
return Redirect::to('invoices/'.$publicId.'/edit');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param int $id
|
||||
* @return Response
|
||||
*/
|
||||
public function update($publicId)
|
||||
{
|
||||
return InvoiceController::save($publicId);
|
||||
return Redirect::to("invoices/{$publicId}/edit");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -584,13 +456,12 @@ class InvoiceController extends BaseController
|
||||
*/
|
||||
public function bulk($entityType = ENTITY_INVOICE)
|
||||
{
|
||||
$action = Input::get('action');
|
||||
$statusId = Input::get('statusId', INVOICE_STATUS_SENT);
|
||||
$ids = Input::get('id') ? Input::get('id') : Input::get('ids');
|
||||
$count = $this->invoiceRepo->bulk($ids, $action, $statusId);
|
||||
$action = Input::get('bulk_action') ?: Input::get('action');;
|
||||
$ids = Input::get('bulk_public_id') ?: (Input::get('public_id') ?: Input::get('ids'));
|
||||
$count = $this->invoiceService->bulk($ids, $action);
|
||||
|
||||
if ($count > 0) {
|
||||
$key = $action == 'mark' ? "updated_{$entityType}" : "{$action}d_{$entityType}";
|
||||
$key = $action == 'markSent' ? "updated_{$entityType}" : "{$action}d_{$entityType}";
|
||||
$message = Utils::pluralize($key, $count);
|
||||
Session::flash('message', $message);
|
||||
}
|
||||
@ -605,7 +476,7 @@ class InvoiceController extends BaseController
|
||||
public function convertQuote($publicId)
|
||||
{
|
||||
$invoice = Invoice::with('invoice_items')->scope($publicId)->firstOrFail();
|
||||
$clone = $this->invoiceRepo->cloneInvoice($invoice, $invoice->id);
|
||||
$clone = $this->invoiceService->convertQuote($invoice);
|
||||
|
||||
Session::flash('message', trans('texts.converted_to_invoice'));
|
||||
return Redirect::to('invoices/'.$clone->public_id);
|
||||
@ -613,15 +484,6 @@ class InvoiceController extends BaseController
|
||||
|
||||
public function cloneInvoice($publicId)
|
||||
{
|
||||
/*
|
||||
$invoice = Invoice::with('invoice_items')->scope($publicId)->firstOrFail();
|
||||
$clone = $this->invoiceRepo->cloneInvoice($invoice);
|
||||
$entityType = $invoice->getEntityType();
|
||||
|
||||
Session::flash('message', trans('texts.cloned_invoice'));
|
||||
return Redirect::to("{$entityType}s/" . $clone->public_id);
|
||||
*/
|
||||
|
||||
return self::edit($publicId, true);
|
||||
}
|
||||
|
||||
@ -639,7 +501,7 @@ class InvoiceController extends BaseController
|
||||
->where('activity_type_id', '=', $activityTypeId)
|
||||
->where('invoice_id', '=', $invoice->id)
|
||||
->orderBy('id', 'desc')
|
||||
->get(['id', 'created_at', 'user_id', 'json_backup', 'message']);
|
||||
->get(['id', 'created_at', 'user_id', 'json_backup']);
|
||||
|
||||
$versionsJson = [];
|
||||
$versionsSelect = [];
|
||||
@ -654,7 +516,7 @@ class InvoiceController extends BaseController
|
||||
$backup->account = $invoice->account->toArray();
|
||||
|
||||
$versionsJson[$activity->id] = $backup;
|
||||
$key = Utils::timestampToDateTimeString(strtotime($activity->created_at)) . ' - ' . Utils::decodeActivity($activity->message);
|
||||
$key = Utils::timestampToDateTimeString(strtotime($activity->created_at)) . ' - ' . $activity->user->getDisplayName();
|
||||
$versionsSelect[$lastId ? $lastId : 0] = $key;
|
||||
$lastId = $activity->id;
|
||||
}
|
||||
@ -666,14 +528,9 @@ class InvoiceController extends BaseController
|
||||
'versionsJson' => json_encode($versionsJson),
|
||||
'versionsSelect' => $versionsSelect,
|
||||
'invoiceDesigns' => InvoiceDesign::getDesigns(),
|
||||
'invoiceFonts' => Cache::get('fonts'),
|
||||
];
|
||||
|
||||
return View::make('invoices.history', $data);
|
||||
}
|
||||
|
||||
private function storePDF($encodedString, $invoice)
|
||||
{
|
||||
$encodedString = str_replace('data:application/pdf;base64,', '', $encodedString);
|
||||
file_put_contents($invoice->getPDFPath(), base64_decode($encodedString));
|
||||
}
|
||||
}
|
||||
|
@ -1,36 +1,86 @@
|
||||
<?php namespace App\Http\Controllers;
|
||||
|
||||
use Auth;
|
||||
use Input;
|
||||
use Utils;
|
||||
use Response;
|
||||
use App\Models\Payment;
|
||||
use App\Models\Invoice;
|
||||
use App\Ninja\Repositories\PaymentRepository;
|
||||
use App\Http\Controllers\BaseAPIController;
|
||||
use App\Ninja\Transformers\PaymentTransformer;
|
||||
|
||||
class PaymentApiController extends Controller
|
||||
class PaymentApiController extends BaseAPIController
|
||||
{
|
||||
protected $paymentRepo;
|
||||
|
||||
public function __construct(PaymentRepository $paymentRepo)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->paymentRepo = $paymentRepo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @SWG\Get(
|
||||
* path="/payments",
|
||||
* tags={"payment"},
|
||||
* summary="List of payments",
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="A list with payments",
|
||||
* @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/Payment"))
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response="default",
|
||||
* description="an ""unexpected"" error"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$paginator = Payment::scope();
|
||||
$payments = Payment::scope()
|
||||
->with('client', 'contact', 'invitation', 'user', 'invoice')
|
||||
->orderBy('created_at', 'desc')
|
||||
->get();
|
||||
$payments = Utils::remapPublicIds($payments);
|
||||
->with('client.contacts', 'invitation', 'user', 'invoice');
|
||||
|
||||
$response = json_encode($payments, JSON_PRETTY_PRINT);
|
||||
$headers = Utils::getApiHeaders(count($payments));
|
||||
|
||||
return Response::make($response, 200, $headers);
|
||||
if ($clientPublicId = Input::get('client_id')) {
|
||||
$filter = function($query) use ($clientPublicId) {
|
||||
$query->where('public_id', '=', $clientPublicId);
|
||||
};
|
||||
$payments->whereHas('client', $filter);
|
||||
$paginator->whereHas('client', $filter);
|
||||
}
|
||||
|
||||
$payments = $payments->orderBy('created_at', 'desc')->paginate();
|
||||
$paginator = $paginator->paginate();
|
||||
$transformer = new PaymentTransformer(Auth::user()->account, Input::get('serializer'));
|
||||
|
||||
$data = $this->createCollection($payments, $transformer, 'payments', $paginator);
|
||||
|
||||
return $this->response($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @SWG\Post(
|
||||
* path="/payments",
|
||||
* summary="Create a payment",
|
||||
* tags={"payment"},
|
||||
* @SWG\Parameter(
|
||||
* in="body",
|
||||
* name="body",
|
||||
* @SWG\Schema(ref="#/definitions/Payment")
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="New payment",
|
||||
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Payment"))
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response="default",
|
||||
* description="an ""unexpected"" error"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function store()
|
||||
{
|
||||
$data = Input::all();
|
||||
@ -40,8 +90,8 @@ class PaymentApiController extends Controller
|
||||
$invoice = Invoice::scope($data['invoice_id'])->with('client')->first();
|
||||
|
||||
if ($invoice) {
|
||||
$data['invoice'] = $invoice->public_id;
|
||||
$data['client'] = $invoice->client->public_id;
|
||||
$data['invoice_id'] = $invoice->id;
|
||||
$data['client_id'] = $invoice->client->id;
|
||||
} else {
|
||||
$error = trans('validation.not_in', ['attribute' => 'invoice_id']);
|
||||
}
|
||||
@ -53,15 +103,17 @@ class PaymentApiController extends Controller
|
||||
$data['transaction_reference'] = '';
|
||||
}
|
||||
|
||||
if (!$error) {
|
||||
$payment = $this->paymentRepo->save(false, $data);
|
||||
if ($error) {
|
||||
return $error;
|
||||
}
|
||||
|
||||
|
||||
$payment = $this->paymentRepo->save($data);
|
||||
$payment = Payment::scope($payment->public_id)->with('client', 'contact', 'user', 'invoice')->first();
|
||||
|
||||
$payment = Utils::remapPublicIds([$payment]);
|
||||
}
|
||||
$transformer = new PaymentTransformer(Auth::user()->account, Input::get('serializer'));
|
||||
$data = $this->createItem($payment, $transformer, 'payment');
|
||||
|
||||
$response = json_encode($error ?: $payment, JSON_PRETTY_PRINT);
|
||||
$headers = Utils::getApiHeaders();
|
||||
return Response::make($response, 200, $headers);
|
||||
return $this->response($data);
|
||||
}
|
||||
}
|
||||
|
@ -12,29 +12,25 @@ use Omnipay;
|
||||
use CreditCard;
|
||||
use URL;
|
||||
use Cache;
|
||||
use Event;
|
||||
use DateTime;
|
||||
use App\Models\Account;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Invitation;
|
||||
use App\Models\Client;
|
||||
use App\Models\PaymentType;
|
||||
use App\Models\Country;
|
||||
use App\Models\License;
|
||||
use App\Models\Payment;
|
||||
use App\Models\Affiliate;
|
||||
use App\Models\AccountGatewayToken;
|
||||
use App\Ninja\Repositories\PaymentRepository;
|
||||
use App\Ninja\Repositories\InvoiceRepository;
|
||||
use App\Ninja\Repositories\AccountRepository;
|
||||
use App\Ninja\Mailers\ContactMailer;
|
||||
use App\Events\InvoicePaid;
|
||||
use App\Services\PaymentService;
|
||||
|
||||
use App\Http\Requests\CreatePaymentRequest;
|
||||
use App\Http\Requests\UpdatePaymentRequest;
|
||||
|
||||
class PaymentController extends BaseController
|
||||
{
|
||||
protected $creditRepo;
|
||||
|
||||
public function __construct(PaymentRepository $paymentRepo, InvoiceRepository $invoiceRepo, AccountRepository $accountRepo, ContactMailer $contactMailer)
|
||||
public function __construct(PaymentRepository $paymentRepo, InvoiceRepository $invoiceRepo, AccountRepository $accountRepo, ContactMailer $contactMailer, PaymentService $paymentService)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
@ -42,6 +38,7 @@ class PaymentController extends BaseController
|
||||
$this->invoiceRepo = $invoiceRepo;
|
||||
$this->accountRepo = $accountRepo;
|
||||
$this->contactMailer = $contactMailer;
|
||||
$this->paymentService = $paymentService;
|
||||
}
|
||||
|
||||
public function index()
|
||||
@ -49,102 +46,22 @@ class PaymentController extends BaseController
|
||||
return View::make('list', array(
|
||||
'entityType' => ENTITY_PAYMENT,
|
||||
'title' => trans('texts.payments'),
|
||||
'columns' => Utils::trans(['checkbox', 'invoice', 'client', 'transaction_reference', 'method', 'payment_amount', 'payment_date', 'action']),
|
||||
'columns' => Utils::trans([
|
||||
'checkbox',
|
||||
'invoice',
|
||||
'client',
|
||||
'transaction_reference',
|
||||
'method',
|
||||
'payment_amount',
|
||||
'payment_date',
|
||||
''
|
||||
]),
|
||||
));
|
||||
}
|
||||
|
||||
public function clientIndex()
|
||||
{
|
||||
$invitationKey = Session::get('invitation_key');
|
||||
if (!$invitationKey) {
|
||||
return Redirect::to('/setup');
|
||||
}
|
||||
|
||||
$invitation = Invitation::with('account')->where('invitation_key', '=', $invitationKey)->first();
|
||||
$account = $invitation->account;
|
||||
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
|
||||
|
||||
$data = [
|
||||
'color' => $color,
|
||||
'hideLogo' => $account->isWhiteLabel(),
|
||||
'entityType' => ENTITY_PAYMENT,
|
||||
'title' => trans('texts.payments'),
|
||||
'columns' => Utils::trans(['invoice', 'transaction_reference', 'method', 'payment_amount', 'payment_date'])
|
||||
];
|
||||
|
||||
return View::make('public_list', $data);
|
||||
}
|
||||
|
||||
public function getDatatable($clientPublicId = null)
|
||||
{
|
||||
$payments = $this->paymentRepo->find($clientPublicId, Input::get('sSearch'));
|
||||
$table = Datatable::query($payments);
|
||||
|
||||
if (!$clientPublicId) {
|
||||
$table->addColumn('checkbox', function ($model) { return '<input type="checkbox" name="ids[]" value="'.$model->public_id.'" '.Utils::getEntityRowClass($model).'>'; });
|
||||
}
|
||||
|
||||
$table->addColumn('invoice_number', function ($model) { return $model->invoice_public_id ? link_to('invoices/'.$model->invoice_public_id.'/edit', $model->invoice_number, ['class' => Utils::getEntityRowClass($model)]) : ''; });
|
||||
|
||||
if (!$clientPublicId) {
|
||||
$table->addColumn('client_name', function ($model) { return link_to('clients/'.$model->client_public_id, Utils::getClientDisplayName($model)); });
|
||||
}
|
||||
|
||||
$table->addColumn('transaction_reference', function ($model) { return $model->transaction_reference ? $model->transaction_reference : '<i>Manual entry</i>'; })
|
||||
->addColumn('payment_type', function ($model) { return $model->payment_type ? $model->payment_type : ($model->account_gateway_id ? $model->gateway_name : ''); });
|
||||
|
||||
return $table->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id); })
|
||||
->addColumn('payment_date', function ($model) { return Utils::dateToString($model->payment_date); })
|
||||
->addColumn('dropdown', function ($model) {
|
||||
if ($model->is_deleted || $model->invoice_is_deleted) {
|
||||
return '<div style="height:38px"/>';
|
||||
}
|
||||
|
||||
$str = '<div class="btn-group tr-action" style="visibility:hidden;">
|
||||
<button type="button" class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
'.trans('texts.select').' <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" role="menu">';
|
||||
|
||||
if (!$model->deleted_at || $model->deleted_at == '0000-00-00') {
|
||||
$str .= '<li><a href="payments/'.$model->public_id.'/edit">'.trans('texts.edit_payment').'</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="javascript:archiveEntity('.$model->public_id.')">'.trans('texts.archive_payment').'</a></li>';
|
||||
} else {
|
||||
$str .= '<li><a href="javascript:restoreEntity('.$model->public_id.')">'.trans('texts.restore_payment').'</a></li>';
|
||||
}
|
||||
|
||||
return $str.'<li><a href="javascript:deleteEntity('.$model->public_id.')">'.trans('texts.delete_payment').'</a></li></ul>
|
||||
</div>';
|
||||
})
|
||||
->make();
|
||||
}
|
||||
|
||||
public function getClientDatatable()
|
||||
{
|
||||
$search = Input::get('sSearch');
|
||||
$invitationKey = Session::get('invitation_key');
|
||||
$invitation = Invitation::where('invitation_key', '=', $invitationKey)->with('contact.client')->first();
|
||||
|
||||
if (!$invitation) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$invoice = $invitation->invoice;
|
||||
|
||||
if (!$invoice || $invoice->is_deleted) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$payments = $this->paymentRepo->findForContact($invitation->contact->id, Input::get('sSearch'));
|
||||
|
||||
return Datatable::query($payments)
|
||||
->addColumn('invoice_number', function ($model) { return $model->invitation_key ? link_to('/view/'.$model->invitation_key, $model->invoice_number) : $model->invoice_number; })
|
||||
->addColumn('transaction_reference', function ($model) { return $model->transaction_reference ? $model->transaction_reference : '<i>Manual entry</i>'; })
|
||||
->addColumn('payment_type', function ($model) { return $model->payment_type ? $model->payment_type : ($model->account_gateway_id ? '<i>Online payment</i>' : ''); })
|
||||
->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id); })
|
||||
->addColumn('payment_date', function ($model) { return Utils::dateToString($model->payment_date); })
|
||||
->make();
|
||||
return $this->paymentService->getDatatable($clientPublicId, Input::get('sSearch'));
|
||||
}
|
||||
|
||||
public function create($clientPublicId = 0, $invoicePublicId = 0)
|
||||
@ -166,6 +83,7 @@ class PaymentController extends BaseController
|
||||
'url' => "payments",
|
||||
'title' => trans('texts.new_payment'),
|
||||
'paymentTypes' => Cache::get('paymentTypes'),
|
||||
'paymentTypeId' => Input::get('paymentTypeId'),
|
||||
'clients' => Client::scope()->with('contacts')->orderBy('name')->get(), );
|
||||
|
||||
return View::make('payments.edit', $data);
|
||||
@ -191,36 +109,9 @@ class PaymentController extends BaseController
|
||||
return View::make('payments.edit', $data);
|
||||
}
|
||||
|
||||
private function createGateway($accountGateway)
|
||||
{
|
||||
$gateway = Omnipay::create($accountGateway->gateway->provider);
|
||||
$config = json_decode($accountGateway->config);
|
||||
|
||||
foreach ($config as $key => $val) {
|
||||
if (!$val) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$function = "set".ucfirst($key);
|
||||
$gateway->$function($val);
|
||||
}
|
||||
|
||||
if ($accountGateway->gateway->id == GATEWAY_DWOLLA) {
|
||||
if ($gateway->getSandbox() && isset($_ENV['DWOLLA_SANDBOX_KEY']) && isset($_ENV['DWOLLA_SANSBOX_SECRET'])) {
|
||||
$gateway->setKey($_ENV['DWOLLA_SANDBOX_KEY']);
|
||||
$gateway->setSecret($_ENV['DWOLLA_SANSBOX_SECRET']);
|
||||
} elseif (isset($_ENV['DWOLLA_KEY']) && isset($_ENV['DWOLLA_SECRET'])) {
|
||||
$gateway->setKey($_ENV['DWOLLA_KEY']);
|
||||
$gateway->setSecret($_ENV['DWOLLA_SECRET']);
|
||||
}
|
||||
}
|
||||
|
||||
return $gateway;
|
||||
}
|
||||
|
||||
private function getLicensePaymentDetails($input, $affiliate)
|
||||
{
|
||||
$data = self::convertInputForOmnipay($input);
|
||||
$data = $this->paymentService->convertInputForOmnipay($input);
|
||||
$card = new CreditCard($data);
|
||||
|
||||
return [
|
||||
@ -232,69 +123,9 @@ class PaymentController extends BaseController
|
||||
];
|
||||
}
|
||||
|
||||
private function convertInputForOmnipay($input)
|
||||
{
|
||||
$data = [
|
||||
'firstName' => $input['first_name'],
|
||||
'lastName' => $input['last_name'],
|
||||
'number' => $input['card_number'],
|
||||
'expiryMonth' => $input['expiration_month'],
|
||||
'expiryYear' => $input['expiration_year'],
|
||||
'cvv' => $input['cvv'],
|
||||
];
|
||||
|
||||
if (isset($input['country_id'])) {
|
||||
$country = Country::find($input['country_id']);
|
||||
|
||||
$data = array_merge($data, [
|
||||
'billingAddress1' => $input['address1'],
|
||||
'billingAddress2' => $input['address2'],
|
||||
'billingCity' => $input['city'],
|
||||
'billingState' => $input['state'],
|
||||
'billingPostcode' => $input['postal_code'],
|
||||
'billingCountry' => $country->iso_3166_2,
|
||||
'shippingAddress1' => $input['address1'],
|
||||
'shippingAddress2' => $input['address2'],
|
||||
'shippingCity' => $input['city'],
|
||||
'shippingState' => $input['state'],
|
||||
'shippingPostcode' => $input['postal_code'],
|
||||
'shippingCountry' => $country->iso_3166_2
|
||||
]);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function getPaymentDetails($invitation, $input = null)
|
||||
{
|
||||
$invoice = $invitation->invoice;
|
||||
$account = $invoice->account;
|
||||
$key = $invoice->account_id.'-'.$invoice->invoice_number;
|
||||
$currencyCode = $invoice->client->currency ? $invoice->client->currency->code : ($invoice->account->currency ? $invoice->account->currency->code : 'USD');
|
||||
|
||||
if ($input) {
|
||||
$data = self::convertInputForOmnipay($input);
|
||||
Session::put($key, $data);
|
||||
} elseif (Session::get($key)) {
|
||||
$data = Session::get($key);
|
||||
} else {
|
||||
$data = [];
|
||||
}
|
||||
|
||||
$card = new CreditCard($data);
|
||||
|
||||
return [
|
||||
'amount' => $invoice->getRequestedAmount(),
|
||||
'card' => $card,
|
||||
'currency' => $currencyCode,
|
||||
'returnUrl' => URL::to('complete'),
|
||||
'cancelUrl' => $invitation->getLink(),
|
||||
'description' => trans('texts.' . $invoice->getEntityType()) . " {$invoice->invoice_number}",
|
||||
];
|
||||
}
|
||||
|
||||
public function show_payment($invitationKey, $paymentType = false)
|
||||
{
|
||||
|
||||
$invitation = Invitation::with('invoice.invoice_items', 'invoice.client.currency', 'invoice.client.account.account_gateways.gateway')->where('invitation_key', '=', $invitationKey)->firstOrFail();
|
||||
$invoice = $invitation->invoice;
|
||||
$client = $invoice->client;
|
||||
@ -304,16 +135,28 @@ class PaymentController extends BaseController
|
||||
if ($paymentType) {
|
||||
$paymentType = 'PAYMENT_TYPE_' . strtoupper($paymentType);
|
||||
} else {
|
||||
$paymentType = Session::get('payment_type', $account->account_gateways[0]->getPaymentType());
|
||||
$paymentType = Session::get($invitation->id . 'payment_type') ?:
|
||||
$account->account_gateways[0]->getPaymentType();
|
||||
}
|
||||
|
||||
if ($paymentType == PAYMENT_TYPE_TOKEN) {
|
||||
$useToken = true;
|
||||
$paymentType = PAYMENT_TYPE_CREDIT_CARD;
|
||||
}
|
||||
Session::put('payment_type', $paymentType);
|
||||
Session::put($invitation->id . 'payment_type', $paymentType);
|
||||
|
||||
$accountGateway = $invoice->client->account->getGatewayByType($paymentType);
|
||||
$gateway = $accountGateway->gateway;
|
||||
|
||||
$acceptedCreditCardTypes = $accountGateway->getCreditcardTypes();
|
||||
|
||||
|
||||
// Handle offsite payments
|
||||
if ($useToken || $paymentType != PAYMENT_TYPE_CREDIT_CARD) {
|
||||
if ($useToken || $paymentType != PAYMENT_TYPE_CREDIT_CARD
|
||||
|| $gateway->id == GATEWAY_EWAY
|
||||
|| $gateway->id == GATEWAY_TWO_CHECKOUT
|
||||
|| $gateway->id == GATEWAY_PAYFAST
|
||||
|| $gateway->id == GATEWAY_MOLLIE) {
|
||||
if (Session::has('error')) {
|
||||
Session::reflash();
|
||||
return Redirect::to('view/'.$invitationKey);
|
||||
@ -322,10 +165,6 @@ class PaymentController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
$accountGateway = $invoice->client->account->getGatewayByType($paymentType);
|
||||
$gateway = $accountGateway->gateway;
|
||||
$acceptedCreditCardTypes = $accountGateway->getCreditcardTypes();
|
||||
|
||||
$data = [
|
||||
'showBreadcrumbs' => false,
|
||||
'url' => 'payment/'.$invitationKey,
|
||||
@ -334,12 +173,16 @@ class PaymentController extends BaseController
|
||||
'client' => $client,
|
||||
'contact' => $invitation->contact,
|
||||
'gateway' => $gateway,
|
||||
'accountGateway' => $accountGateway,
|
||||
'acceptedCreditCardTypes' => $acceptedCreditCardTypes,
|
||||
'countries' => Cache::get('countries'),
|
||||
'currencyId' => $client->getCurrencyId(),
|
||||
'currencyCode' => $client->currency ? $client->currency->code : ($account->currency ? $account->currency->code : 'USD'),
|
||||
'account' => $client->account,
|
||||
'hideLogo' => $account->isWhiteLabel(),
|
||||
'hideHeader' => $account->isNinjaAccount(),
|
||||
'clientViewCSS' => $account->clientViewCSS(),
|
||||
'clientFontUrl' => $account->getFontsUrl(),
|
||||
'showAddress' => $accountGateway->show_address,
|
||||
];
|
||||
|
||||
@ -358,7 +201,11 @@ class PaymentController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
Session::set('product_id', Input::get('product_id', PRODUCT_ONE_CLICK_INSTALL));
|
||||
if (Input::has('product_id')) {
|
||||
Session::set('product_id', Input::get('product_id'));
|
||||
} else if (!Session::has('product_id')) {
|
||||
Session::set('product_id', PRODUCT_ONE_CLICK_INSTALL);
|
||||
}
|
||||
|
||||
if (!Session::get('affiliate_id')) {
|
||||
return Utils::fatalError();
|
||||
@ -370,7 +217,7 @@ class PaymentController extends BaseController
|
||||
|
||||
$account = $this->accountRepo->getNinjaAccount();
|
||||
$account->load('account_gateways.gateway');
|
||||
$accountGateway = $account->getGatewayByType(Session::get('payment_type'));
|
||||
$accountGateway = $account->getGatewayByType(PAYMENT_TYPE_CREDIT_CARD);
|
||||
$gateway = $accountGateway->gateway;
|
||||
$acceptedCreditCardTypes = $accountGateway->getCreditcardTypes();
|
||||
|
||||
@ -384,9 +231,12 @@ class PaymentController extends BaseController
|
||||
'client' => false,
|
||||
'contact' => false,
|
||||
'gateway' => $gateway,
|
||||
'account' => $account,
|
||||
'accountGateway' => $accountGateway,
|
||||
'acceptedCreditCardTypes' => $acceptedCreditCardTypes,
|
||||
'countries' => Cache::get('countries'),
|
||||
'currencyId' => 1,
|
||||
'currencyCode' => 'USD',
|
||||
'paymentTitle' => $affiliate->payment_title,
|
||||
'paymentSubtitle' => $affiliate->payment_subtitle,
|
||||
'showAddress' => true,
|
||||
@ -417,7 +267,8 @@ class PaymentController extends BaseController
|
||||
|
||||
if ($validator->fails()) {
|
||||
return Redirect::to('license')
|
||||
->withErrors($validator);
|
||||
->withErrors($validator)
|
||||
->withInput();
|
||||
}
|
||||
|
||||
$account = $this->accountRepo->getNinjaAccount();
|
||||
@ -430,21 +281,13 @@ class PaymentController extends BaseController
|
||||
if ($testMode) {
|
||||
$ref = 'TEST_MODE';
|
||||
} else {
|
||||
$gateway = self::createGateway($accountGateway);
|
||||
$gateway = $this->paymentService->createGateway($accountGateway);
|
||||
$details = self::getLicensePaymentDetails(Input::all(), $affiliate);
|
||||
$response = $gateway->purchase($details)->send();
|
||||
$ref = $response->getTransactionReference();
|
||||
|
||||
if (!$ref) {
|
||||
Session::flash('error', $response->getMessage());
|
||||
|
||||
return Redirect::to('license')->withInput();
|
||||
}
|
||||
|
||||
if (!$response->isSuccessful()) {
|
||||
Session::flash('error', $response->getMessage());
|
||||
Utils::logError($response->getMessage());
|
||||
|
||||
if (!$response->isSuccessful() || !$ref) {
|
||||
$this->error('License', $response->getMessage(), $accountGateway);
|
||||
return Redirect::to('license')->withInput();
|
||||
}
|
||||
}
|
||||
@ -465,7 +308,8 @@ class PaymentController extends BaseController
|
||||
'message' => $affiliate->payment_subtitle,
|
||||
'license' => $licenseKey,
|
||||
'hideHeader' => true,
|
||||
'productId' => $license->product_id
|
||||
'productId' => $license->product_id,
|
||||
'price' => $affiliate->price,
|
||||
];
|
||||
|
||||
$name = "{$license->first_name} {$license->last_name}";
|
||||
@ -478,10 +322,7 @@ class PaymentController extends BaseController
|
||||
|
||||
return View::make('public.license', $data);
|
||||
} catch (\Exception $e) {
|
||||
$errorMessage = trans('texts.payment_error');
|
||||
Session::flash('error', $errorMessage);
|
||||
Utils::logError(Utils::getErrorString($e));
|
||||
|
||||
$this->error('License-Uncaught', false, $accountGateway, $e);
|
||||
return Redirect::to('license')->withInput();
|
||||
}
|
||||
}
|
||||
@ -514,16 +355,25 @@ class PaymentController extends BaseController
|
||||
$invoice = $invitation->invoice;
|
||||
$client = $invoice->client;
|
||||
$account = $client->account;
|
||||
$accountGateway = $account->getGatewayByType(Session::get('payment_type'));
|
||||
$accountGateway = $account->getGatewayByType(Session::get($invitation->id . 'payment_type'));
|
||||
|
||||
|
||||
$rules = [
|
||||
'first_name' => 'required',
|
||||
'last_name' => 'required',
|
||||
];
|
||||
|
||||
if ( ! Input::get('stripeToken')) {
|
||||
$rules = array_merge(
|
||||
$rules,
|
||||
[
|
||||
'card_number' => 'required',
|
||||
'expiration_month' => 'required',
|
||||
'expiration_year' => 'required',
|
||||
'cvv' => 'required',
|
||||
];
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
if ($accountGateway->show_address) {
|
||||
$rules = array_merge($rules, [
|
||||
@ -539,13 +389,11 @@ class PaymentController extends BaseController
|
||||
$validator = Validator::make(Input::all(), $rules);
|
||||
|
||||
if ($validator->fails()) {
|
||||
Utils::logError('Payment Error [invalid]');
|
||||
return Redirect::to('payment/'.$invitationKey)
|
||||
->withErrors($validator)
|
||||
->withInput();
|
||||
->withInput(Request::except('cvv'));
|
||||
}
|
||||
|
||||
|
||||
if ($accountGateway->update_address) {
|
||||
$client->address1 = trim(Input::get('address1'));
|
||||
$client->address2 = trim(Input::get('address2'));
|
||||
@ -558,57 +406,65 @@ class PaymentController extends BaseController
|
||||
}
|
||||
|
||||
try {
|
||||
$gateway = self::createGateway($accountGateway);
|
||||
$details = self::getPaymentDetails($invitation, ($useToken || !$onSite) ? false : Input::all());
|
||||
|
||||
if ($accountGateway->gateway_id == GATEWAY_STRIPE) {
|
||||
if ($useToken) {
|
||||
$details['cardReference'] = $client->getGatewayToken();
|
||||
} elseif ($account->token_billing_type_id == TOKEN_BILLING_ALWAYS || Input::get('token_billing')) {
|
||||
$tokenResponse = $gateway->createCard($details)->send();
|
||||
$cardReference = $tokenResponse->getCardReference();
|
||||
|
||||
if ($cardReference) {
|
||||
$details['cardReference'] = $cardReference;
|
||||
|
||||
$token = AccountGatewayToken::where('client_id', '=', $client->id)
|
||||
->where('account_gateway_id', '=', $accountGateway->id)->first();
|
||||
|
||||
if (!$token) {
|
||||
$token = new AccountGatewayToken();
|
||||
$token->account_id = $account->id;
|
||||
$token->contact_id = $invitation->contact_id;
|
||||
$token->account_gateway_id = $accountGateway->id;
|
||||
$token->client_id = $client->id;
|
||||
// For offsite payments send the client's details on file
|
||||
// If we're using a token then we don't need to send any other data
|
||||
if (!$onSite || $useToken) {
|
||||
$data = false;
|
||||
} else {
|
||||
$data = Input::all();
|
||||
}
|
||||
|
||||
$token->token = $cardReference;
|
||||
$token->save();
|
||||
$gateway = $this->paymentService->createGateway($accountGateway);
|
||||
$details = $this->paymentService->getPaymentDetails($invitation, $accountGateway, $data);
|
||||
|
||||
// check if we're creating/using a billing token
|
||||
if ($accountGateway->gateway_id == GATEWAY_STRIPE) {
|
||||
if ($token = Input::get('stripeToken')) {
|
||||
$details['token'] = $token;
|
||||
unset($details['card']);
|
||||
}
|
||||
|
||||
if ($useToken) {
|
||||
$details['customerReference'] = $client->getGatewayToken();
|
||||
} elseif ($account->token_billing_type_id == TOKEN_BILLING_ALWAYS || Input::get('token_billing')) {
|
||||
$token = $this->paymentService->createToken($gateway, $details, $accountGateway, $client, $invitation->contact_id);
|
||||
if ($token) {
|
||||
$details['customerReference'] = $token;
|
||||
} else {
|
||||
Session::flash('error', $tokenResponse->getMessage());
|
||||
Utils::logError('Payment Error [no-token-ref]: ' . $tokenResponse->getMessage());
|
||||
return Redirect::to('payment/'.$invitationKey)->withInput();
|
||||
$this->error('Token-No-Ref', $this->paymentService->lastError, $accountGateway);
|
||||
return Redirect::to('payment/'.$invitationKey)->withInput(Request::except('cvv'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$response = $gateway->purchase($details)->send();
|
||||
|
||||
|
||||
if ($accountGateway->gateway_id == GATEWAY_EWAY) {
|
||||
$ref = $response->getData()['AccessCode'];
|
||||
} elseif ($accountGateway->gateway_id == GATEWAY_TWO_CHECKOUT) {
|
||||
$ref = $response->getData()['cart_order_id'];
|
||||
} elseif ($accountGateway->gateway_id == GATEWAY_PAYFAST) {
|
||||
$ref = $response->getData()['m_payment_id'];
|
||||
} elseif ($accountGateway->gateway_id == GATEWAY_GOCARDLESS) {
|
||||
$ref = $response->getData()['signature'];
|
||||
} else {
|
||||
$ref = $response->getTransactionReference();
|
||||
}
|
||||
|
||||
if (!$ref) {
|
||||
|
||||
Session::flash('error', $response->getMessage());
|
||||
Utils::logError('Payment Error [no-ref]: ' . $response->getMessage());
|
||||
$this->error('No-Ref', $response->getMessage(), $accountGateway);
|
||||
|
||||
if ($onSite) {
|
||||
return Redirect::to('payment/'.$invitationKey)->withInput();
|
||||
return Redirect::to('payment/'.$invitationKey)
|
||||
->withInput(Request::except('cvv'));
|
||||
} else {
|
||||
return Redirect::to('view/'.$invitationKey);
|
||||
}
|
||||
}
|
||||
|
||||
if ($response->isSuccessful()) {
|
||||
$payment = self::createPayment($invitation, $ref);
|
||||
$payment = $this->paymentService->createPayment($invitation, $accountGateway, $ref);
|
||||
Session::flash('message', trans('texts.applied_payment'));
|
||||
|
||||
if ($account->account_key == NINJA_ACCOUNT_KEY) {
|
||||
@ -618,70 +474,28 @@ class PaymentController extends BaseController
|
||||
|
||||
return Redirect::to('view/'.$payment->invitation->invitation_key);
|
||||
} elseif ($response->isRedirect()) {
|
||||
|
||||
$invitation->transaction_reference = $ref;
|
||||
$invitation->save();
|
||||
|
||||
Session::put('transaction_reference', $ref);
|
||||
Session::save();
|
||||
$response->redirect();
|
||||
} else {
|
||||
Session::flash('error', $response->getMessage());
|
||||
Utils::logError('Payment Error [fatal]: ' . $response->getMessage());
|
||||
|
||||
return Utils::fatalError('Sorry, there was an error processing your payment. Please try again later.<p>', $response->getMessage());
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$errorMessage = trans('texts.payment_error');
|
||||
Session::flash('error', $errorMessage."<p>".$e->getMessage());
|
||||
Utils::logError('Payment Error [uncaught]:' . Utils::getErrorString($e));
|
||||
|
||||
$this->error('Unknown', $response->getMessage(), $accountGateway);
|
||||
if ($onSite) {
|
||||
return Redirect::to('payment/'.$invitationKey)->withInput();
|
||||
return Redirect::to('payment/'.$invitationKey)->withInput(Request::except('cvv'));
|
||||
} else {
|
||||
return Redirect::to('view/'.$invitationKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function createPayment($invitation, $ref, $payerId = null)
|
||||
{
|
||||
$invoice = $invitation->invoice;
|
||||
$accountGateway = $invoice->client->account->getGatewayByType(Session::get('payment_type'));
|
||||
|
||||
if ($invoice->account->account_key == NINJA_ACCOUNT_KEY
|
||||
&& $invoice->amount == PRO_PLAN_PRICE) {
|
||||
$account = Account::with('users')->find($invoice->client->public_id);
|
||||
if ($account->pro_plan_paid && $account->pro_plan_paid != '0000-00-00') {
|
||||
$date = DateTime::createFromFormat('Y-m-d', $account->pro_plan_paid);
|
||||
$account->pro_plan_paid = $date->modify('+1 year')->format('Y-m-d');
|
||||
} catch (\Exception $e) {
|
||||
$this->error('Uncaught', false, $accountGateway, $e);
|
||||
if ($onSite) {
|
||||
return Redirect::to('payment/'.$invitationKey)->withInput(Request::except('cvv'));
|
||||
} else {
|
||||
$account->pro_plan_paid = date_create()->format('Y-m-d');
|
||||
return Redirect::to('view/'.$invitationKey);
|
||||
}
|
||||
$account->save();
|
||||
|
||||
$user = $account->users()->first();
|
||||
$this->accountRepo->syncAccounts($user->id, $account->pro_plan_paid);
|
||||
}
|
||||
|
||||
$payment = Payment::createNew($invitation);
|
||||
$payment->invitation_id = $invitation->id;
|
||||
$payment->account_gateway_id = $accountGateway->id;
|
||||
$payment->invoice_id = $invoice->id;
|
||||
$payment->amount = $invoice->getRequestedAmount();
|
||||
$payment->client_id = $invoice->client_id;
|
||||
$payment->contact_id = $invitation->contact_id;
|
||||
$payment->transaction_reference = $ref;
|
||||
$payment->payment_date = date_create()->format('Y-m-d');
|
||||
|
||||
if ($payerId) {
|
||||
$payment->payer_id = $payerId;
|
||||
}
|
||||
|
||||
$payment->save();
|
||||
|
||||
Event::fire(new InvoicePaid($payment));
|
||||
|
||||
return $payment;
|
||||
}
|
||||
|
||||
public function offsite_payment()
|
||||
@ -692,84 +506,77 @@ class PaymentController extends BaseController
|
||||
if (!$token) {
|
||||
$token = Session::pull('transaction_reference');
|
||||
}
|
||||
|
||||
if (!$token) {
|
||||
return redirect(NINJA_WEB_URL);
|
||||
}
|
||||
|
||||
$invitation = Invitation::with('invoice.client.currency', 'invoice.client.account.account_gateways.gateway')->where('transaction_reference', '=', $token)->firstOrFail();
|
||||
$invoice = $invitation->invoice;
|
||||
$client = $invoice->client;
|
||||
$account = $client->account;
|
||||
|
||||
$accountGateway = $invoice->client->account->getGatewayByType(Session::get('payment_type'));
|
||||
$gateway = self::createGateway($accountGateway);
|
||||
if ($payerId) {
|
||||
$paymentType = PAYMENT_TYPE_PAYPAL;
|
||||
} else {
|
||||
$paymentType = Session::get($invitation->id . 'payment_type');
|
||||
}
|
||||
if (!$paymentType) {
|
||||
$this->error('No-Payment-Type', false, false);
|
||||
return Redirect::to($invitation->getLink());
|
||||
}
|
||||
$accountGateway = $account->getGatewayByType($paymentType);
|
||||
$gateway = $this->paymentService->createGateway($accountGateway);
|
||||
|
||||
// Check for Dwolla payment error
|
||||
if ($accountGateway->isGateway(GATEWAY_DWOLLA) && Input::get('error')) {
|
||||
$errorMessage = trans('texts.payment_error')."\n\n".Input::get('error_description');
|
||||
Session::flash('error', $errorMessage);
|
||||
Utils::logError($errorMessage);
|
||||
return Redirect::to('view/'.$invitation->invitation_key);
|
||||
$this->error('Dwolla', Input::get('error_description'), $accountGateway);
|
||||
return Redirect::to($invitation->getLink());
|
||||
}
|
||||
|
||||
// PayFast transaction referencce
|
||||
if ($accountGateway->isGateway(GATEWAY_PAYFAST) && Request::has('pt')) {
|
||||
$token = Request::query('pt');
|
||||
}
|
||||
|
||||
try {
|
||||
if (method_exists($gateway, 'completePurchase')) {
|
||||
$details = self::getPaymentDetails($invitation);
|
||||
$response = $gateway->completePurchase($details)->send();
|
||||
$ref = $response->getTransactionReference();
|
||||
if (method_exists($gateway, 'completePurchase')
|
||||
&& !$accountGateway->isGateway(GATEWAY_TWO_CHECKOUT)
|
||||
&& !$accountGateway->isGateway(GATEWAY_CHECKOUT_COM)) {
|
||||
$details = $this->paymentService->getPaymentDetails($invitation, $accountGateway);
|
||||
|
||||
if ($response->isSuccessful()) {
|
||||
$payment = self::createPayment($invitation, $ref, $payerId);
|
||||
$response = $this->paymentService->completePurchase($gateway, $accountGateway, $details, $token);
|
||||
|
||||
$ref = $response->getTransactionReference() ?: $token;
|
||||
|
||||
if ($response->isCancelled()) {
|
||||
// do nothing
|
||||
} elseif ($response->isSuccessful()) {
|
||||
$payment = $this->paymentService->createPayment($invitation, $accountGateway, $ref, $payerId);
|
||||
Session::flash('message', trans('texts.applied_payment'));
|
||||
|
||||
return Redirect::to('view/'.$invitation->invitation_key);
|
||||
} else {
|
||||
$errorMessage = trans('texts.payment_error')."\n\n".$response->getMessage();
|
||||
Session::flash('error', $errorMessage);
|
||||
Utils::logError($errorMessage);
|
||||
|
||||
return Redirect::to('view/'.$invitation->invitation_key);
|
||||
$this->error('offsite', $response->getMessage(), $accountGateway);
|
||||
}
|
||||
return Redirect::to($invitation->getLink());
|
||||
} else {
|
||||
$payment = self::createPayment($invitation, $token, $payerId);
|
||||
$payment = $this->paymentService->createPayment($invitation, $accountGateway, $token, $payerId);
|
||||
Session::flash('message', trans('texts.applied_payment'));
|
||||
|
||||
return Redirect::to('view/'.$invitation->invitation_key);
|
||||
return Redirect::to($invitation->getLink());
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$errorMessage = trans('texts.payment_error');
|
||||
Session::flash('error', $errorMessage);
|
||||
Utils::logError($errorMessage."\n\n".$e->getMessage());
|
||||
|
||||
return Redirect::to('view/'.$invitation->invitation_key);
|
||||
$this->error('Offsite-uncaught', false, $accountGateway, $e);
|
||||
return Redirect::to($invitation->getLink());
|
||||
}
|
||||
}
|
||||
|
||||
public function store()
|
||||
public function store(CreatePaymentRequest $request)
|
||||
{
|
||||
return $this->save();
|
||||
}
|
||||
$input = $request->input();
|
||||
$input['invoice_id'] = Invoice::getPrivateId($input['invoice']);
|
||||
$input['client_id'] = Client::getPrivateId($input['client']);
|
||||
$payment = $this->paymentRepo->save($input);
|
||||
|
||||
public function update($publicId)
|
||||
{
|
||||
return $this->save($publicId);
|
||||
}
|
||||
|
||||
private function save($publicId = null)
|
||||
{
|
||||
if (!$publicId && $errors = $this->paymentRepo->getErrors(Input::all())) {
|
||||
$url = $publicId ? 'payments/'.$publicId.'/edit' : 'payments/create';
|
||||
|
||||
return Redirect::to($url)
|
||||
->withErrors($errors)
|
||||
->withInput();
|
||||
} else {
|
||||
$payment = $this->paymentRepo->save($publicId, Input::all());
|
||||
|
||||
if ($publicId) {
|
||||
Session::flash('message', trans('texts.updated_payment'));
|
||||
|
||||
return Redirect::to('payments/');
|
||||
} else {
|
||||
if (Input::get('email_receipt')) {
|
||||
$this->contactMailer->sendPaymentConfirmation($payment);
|
||||
Session::flash('message', trans('texts.created_payment_emailed_client'));
|
||||
@ -777,16 +584,24 @@ class PaymentController extends BaseController
|
||||
Session::flash('message', trans('texts.created_payment'));
|
||||
}
|
||||
|
||||
return Redirect::to('clients/'.Input::get('client'));
|
||||
}
|
||||
return redirect()->to($payment->client->getRoute());
|
||||
}
|
||||
|
||||
public function update(UpdatePaymentRequest $request)
|
||||
{
|
||||
$input = $request->input();
|
||||
$payment = $this->paymentRepo->save($input);
|
||||
|
||||
Session::flash('message', trans('texts.updated_payment'));
|
||||
|
||||
return redirect()->to($payment->getRoute());
|
||||
}
|
||||
|
||||
public function bulk()
|
||||
{
|
||||
$action = Input::get('action');
|
||||
$ids = Input::get('id') ? Input::get('id') : Input::get('ids');
|
||||
$count = $this->paymentRepo->bulk($ids, $action);
|
||||
$ids = Input::get('public_id') ? Input::get('public_id') : Input::get('ids');
|
||||
$count = $this->paymentService->bulk($ids, $action);
|
||||
|
||||
if ($count > 0) {
|
||||
$message = Utils::pluralize($action.'d_payment', $count);
|
||||
@ -795,4 +610,16 @@ class PaymentController extends BaseController
|
||||
|
||||
return Redirect::to('payments');
|
||||
}
|
||||
|
||||
private function error($type, $error, $accountGateway = false, $exception = false)
|
||||
{
|
||||
$message = '';
|
||||
if ($accountGateway && $accountGateway->gateway) {
|
||||
$message = $accountGateway->gateway->name . ': ';
|
||||
}
|
||||
$message .= $error ?: trans('texts.payment_error');
|
||||
|
||||
Session::flash('error', $message);
|
||||
Utils::logError("Payment Error [{$type}]: " . ($exception ? Utils::getErrorString($exception) : $message));
|
||||
}
|
||||
}
|
||||
|
103
app/Http/Controllers/PaymentTermController.php
Normal file
103
app/Http/Controllers/PaymentTermController.php
Normal file
@ -0,0 +1,103 @@
|
||||
<?php namespace App\Http\Controllers;
|
||||
|
||||
use Auth;
|
||||
use Str;
|
||||
use DB;
|
||||
use Datatable;
|
||||
use Utils;
|
||||
use URL;
|
||||
use View;
|
||||
use Input;
|
||||
use Session;
|
||||
use Redirect;
|
||||
|
||||
use App\Models\PaymentTerm;
|
||||
use App\Ninja\Repositories\VendorRepository;
|
||||
use App\Services\PaymentService;
|
||||
use App\Services\PaymentTermService;
|
||||
|
||||
class PaymentTermController extends BaseController
|
||||
{
|
||||
protected $paymentTermService;
|
||||
|
||||
public function __construct(PaymentTermService $paymentTermService)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->paymentTermService = $paymentTermService;
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
return Redirect::to('settings/' . ACCOUNT_PAYMENT_TERMS);
|
||||
}
|
||||
|
||||
public function getDatatable()
|
||||
{
|
||||
return $this->paymentTermService->getDatatable();
|
||||
}
|
||||
|
||||
public function edit($publicId)
|
||||
{
|
||||
$data = [
|
||||
'paymentTerm' => PaymentTerm::scope($publicId)->firstOrFail(),
|
||||
'method' => 'PUT',
|
||||
'url' => 'payment_terms/'.$publicId,
|
||||
'title' => trans('texts.edit_payment_term'),
|
||||
];
|
||||
|
||||
return View::make('accounts.payment_term', $data);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
$data = [
|
||||
'paymentTerm' => null,
|
||||
'method' => 'POST',
|
||||
'url' => 'payment_terms',
|
||||
'title' => trans('texts.create_payment_term'),
|
||||
];
|
||||
|
||||
return View::make('accounts.payment_term', $data);
|
||||
}
|
||||
|
||||
public function store()
|
||||
{
|
||||
return $this->save();
|
||||
}
|
||||
|
||||
public function update($publicId)
|
||||
{
|
||||
return $this->save($publicId);
|
||||
}
|
||||
|
||||
private function save($publicId = false)
|
||||
{
|
||||
if ($publicId) {
|
||||
$paymentTerm = PaymentTerm::scope($publicId)->firstOrFail();
|
||||
} else {
|
||||
$paymentTerm = PaymentTerm::createNew();
|
||||
}
|
||||
|
||||
$paymentTerm->name = trim(Input::get('name'));
|
||||
$paymentTerm->num_days = Utils::parseInt(Input::get('num_days'));
|
||||
$paymentTerm->save();
|
||||
|
||||
$message = $publicId ? trans('texts.updated_payment_term') : trans('texts.created_payment_term');
|
||||
Session::flash('message', $message);
|
||||
|
||||
return Redirect::to('settings/' . ACCOUNT_PAYMENT_TERMS);
|
||||
}
|
||||
|
||||
public function bulk()
|
||||
{
|
||||
$action = Input::get('bulk_action');
|
||||
$ids = Input::get('bulk_public_id');
|
||||
$count = $this->paymentTermService->bulk($ids, $action);
|
||||
|
||||
Session::flash('message', trans('texts.archived_payment_term'));
|
||||
|
||||
return Redirect::to('settings/' . ACCOUNT_PAYMENT_TERMS);
|
||||
}
|
||||
|
||||
}
|
@ -12,40 +12,37 @@ use Session;
|
||||
use Redirect;
|
||||
|
||||
use App\Models\Product;
|
||||
use App\Models\TaxRate;
|
||||
use App\Services\ProductService;
|
||||
|
||||
class ProductController extends BaseController
|
||||
{
|
||||
protected $productService;
|
||||
|
||||
public function __construct(ProductService $productService)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->productService = $productService;
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
return Redirect::to('settings/' . ACCOUNT_PRODUCTS);
|
||||
}
|
||||
|
||||
public function getDatatable()
|
||||
{
|
||||
$query = DB::table('products')
|
||||
->where('products.account_id', '=', Auth::user()->account_id)
|
||||
->where('products.deleted_at', '=', null)
|
||||
->select('products.public_id', 'products.product_key', 'products.notes', 'products.cost');
|
||||
|
||||
return Datatable::query($query)
|
||||
->addColumn('product_key', function ($model) { return link_to('products/'.$model->public_id.'/edit', $model->product_key); })
|
||||
->addColumn('notes', function ($model) { return nl2br(Str::limit($model->notes, 100)); })
|
||||
->addColumn('cost', function ($model) { return Utils::formatMoney($model->cost); })
|
||||
->addColumn('dropdown', function ($model) {
|
||||
return '<div class="btn-group tr-action" style="visibility:hidden;">
|
||||
<button type="button" class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
'.trans('texts.select').' <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li><a href="'.URL::to('products/'.$model->public_id).'/edit">'.uctrans('texts.edit_product').'</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="'.URL::to('products/'.$model->public_id).'/archive">'.uctrans('texts.archive_product').'</a></li>
|
||||
</ul>
|
||||
</div>';
|
||||
})
|
||||
->orderColumns(['cost', 'product_key', 'cost'])
|
||||
->make();
|
||||
return $this->productService->getDatatable(Auth::user()->account_id);
|
||||
}
|
||||
|
||||
public function edit($publicId)
|
||||
{
|
||||
$account = Auth::user()->account;
|
||||
|
||||
$data = [
|
||||
'showBreadcrumbs' => false,
|
||||
'account' => $account,
|
||||
'taxRates' => $account->invoice_item_taxes ? TaxRate::scope()->get(['id', 'name', 'rate']) : null,
|
||||
'product' => Product::scope($publicId)->firstOrFail(),
|
||||
'method' => 'PUT',
|
||||
'url' => 'products/'.$publicId,
|
||||
@ -57,8 +54,11 @@ class ProductController extends BaseController
|
||||
|
||||
public function create()
|
||||
{
|
||||
$account = Auth::user()->account;
|
||||
|
||||
$data = [
|
||||
'showBreadcrumbs' => false,
|
||||
'account' => $account,
|
||||
'taxRates' => $account->invoice_item_taxes ? TaxRate::scope()->get(['id', 'name', 'rate']) : null,
|
||||
'product' => null,
|
||||
'method' => 'POST',
|
||||
'url' => 'products',
|
||||
@ -89,21 +89,24 @@ class ProductController extends BaseController
|
||||
$product->product_key = trim(Input::get('product_key'));
|
||||
$product->notes = trim(Input::get('notes'));
|
||||
$product->cost = trim(Input::get('cost'));
|
||||
$product->default_tax_rate_id = Input::get('default_tax_rate_id');
|
||||
|
||||
$product->save();
|
||||
|
||||
$message = $productPublicId ? trans('texts.updated_product') : trans('texts.created_product');
|
||||
Session::flash('message', $message);
|
||||
|
||||
return Redirect::to('company/products');
|
||||
return Redirect::to('settings/' . ACCOUNT_PRODUCTS);
|
||||
}
|
||||
|
||||
public function archive($publicId)
|
||||
public function bulk()
|
||||
{
|
||||
$product = Product::scope($publicId)->firstOrFail();
|
||||
$product->delete();
|
||||
$action = Input::get('bulk_action');
|
||||
$ids = Input::get('bulk_public_id');
|
||||
$count = $this->productService->bulk($ids, $action);
|
||||
|
||||
Session::flash('message', trans('texts.archived_product'));
|
||||
|
||||
return Redirect::to('company/products');
|
||||
return Redirect::to('settings/' . ACCOUNT_PRODUCTS);
|
||||
}
|
||||
}
|
||||
|
347
app/Http/Controllers/PublicClientController.php
Normal file
347
app/Http/Controllers/PublicClientController.php
Normal file
@ -0,0 +1,347 @@
|
||||
<?php namespace App\Http\Controllers;
|
||||
|
||||
use Auth;
|
||||
use View;
|
||||
use DB;
|
||||
use URL;
|
||||
use Input;
|
||||
use Utils;
|
||||
use Request;
|
||||
use Session;
|
||||
use Datatable;
|
||||
use App\Models\Gateway;
|
||||
use App\Models\Invitation;
|
||||
use App\Ninja\Repositories\InvoiceRepository;
|
||||
use App\Ninja\Repositories\PaymentRepository;
|
||||
use App\Ninja\Repositories\ActivityRepository;
|
||||
use App\Events\InvoiceInvitationWasViewed;
|
||||
use App\Events\QuoteInvitationWasViewed;
|
||||
use App\Services\PaymentService;
|
||||
|
||||
class PublicClientController extends BaseController
|
||||
{
|
||||
private $invoiceRepo;
|
||||
private $paymentRepo;
|
||||
|
||||
public function __construct(InvoiceRepository $invoiceRepo, PaymentRepository $paymentRepo, ActivityRepository $activityRepo, PaymentService $paymentService)
|
||||
{
|
||||
$this->invoiceRepo = $invoiceRepo;
|
||||
$this->paymentRepo = $paymentRepo;
|
||||
$this->activityRepo = $activityRepo;
|
||||
$this->paymentService = $paymentService;
|
||||
}
|
||||
|
||||
public function view($invitationKey)
|
||||
{
|
||||
if (!$invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey)) {
|
||||
return response()->view('error', [
|
||||
'error' => trans('texts.invoice_not_found'),
|
||||
'hideHeader' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
$invoice = $invitation->invoice;
|
||||
$client = $invoice->client;
|
||||
$account = $invoice->account;
|
||||
|
||||
if (!$account->checkSubdomain(Request::server('HTTP_HOST'))) {
|
||||
return response()->view('error', [
|
||||
'error' => trans('texts.invoice_not_found'),
|
||||
'hideHeader' => true,
|
||||
'clientViewCSS' => $account->clientViewCSS(),
|
||||
'clientFontUrl' => $account->getFontsUrl(),
|
||||
]);
|
||||
}
|
||||
|
||||
if (!Input::has('phantomjs') && !Session::has($invitationKey) && (!Auth::check() || Auth::user()->account_id != $invoice->account_id)) {
|
||||
if ($invoice->is_quote) {
|
||||
event(new QuoteInvitationWasViewed($invoice, $invitation));
|
||||
} else {
|
||||
event(new InvoiceInvitationWasViewed($invoice, $invitation));
|
||||
}
|
||||
}
|
||||
|
||||
Session::put($invitationKey, true); // track this invitation has been seen
|
||||
Session::put('invitation_key', $invitationKey); // track current invitation
|
||||
|
||||
$account->loadLocalizationSettings($client);
|
||||
|
||||
$invoice->invoice_date = Utils::fromSqlDate($invoice->invoice_date);
|
||||
$invoice->due_date = Utils::fromSqlDate($invoice->due_date);
|
||||
$invoice->is_pro = $account->isPro();
|
||||
$invoice->invoice_fonts = $account->getFontsData();
|
||||
|
||||
if ($invoice->invoice_design_id == CUSTOM_DESIGN) {
|
||||
$invoice->invoice_design->javascript = $account->custom_design;
|
||||
} else {
|
||||
$invoice->invoice_design->javascript = $invoice->invoice_design->pdfmake;
|
||||
}
|
||||
$contact = $invitation->contact;
|
||||
$contact->setVisible([
|
||||
'first_name',
|
||||
'last_name',
|
||||
'email',
|
||||
'phone',
|
||||
]);
|
||||
|
||||
$paymentTypes = $this->getPaymentTypes($client, $invitation);
|
||||
$paymentURL = '';
|
||||
if (count($paymentTypes)) {
|
||||
$paymentURL = $paymentTypes[0]['url'];
|
||||
if (!$account->isGatewayConfigured(GATEWAY_PAYPAL_EXPRESS)) {
|
||||
$paymentURL = URL::to($paymentURL);
|
||||
}
|
||||
}
|
||||
|
||||
$showApprove = $invoice->quote_invoice_id ? false : true;
|
||||
if ($invoice->due_date) {
|
||||
$showApprove = time() < strtotime($invoice->due_date);
|
||||
}
|
||||
if ($invoice->invoice_status_id >= INVOICE_STATUS_APPROVED) {
|
||||
$showApprove = false;
|
||||
}
|
||||
|
||||
// Checkout.com requires first getting a payment token
|
||||
$checkoutComToken = false;
|
||||
$checkoutComKey = false;
|
||||
if ($accountGateway = $account->getGatewayConfig(GATEWAY_CHECKOUT_COM)) {
|
||||
if ($checkoutComToken = $this->paymentService->getCheckoutComToken($invitation)) {
|
||||
$checkoutComKey = $accountGateway->getConfigField('publicApiKey');
|
||||
$invitation->transaction_reference = $checkoutComToken;
|
||||
$invitation->save();
|
||||
}
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'account' => $account,
|
||||
'showApprove' => $showApprove,
|
||||
'showBreadcrumbs' => false,
|
||||
'hideLogo' => $account->isWhiteLabel(),
|
||||
'hideHeader' => $account->isNinjaAccount(),
|
||||
'clientViewCSS' => $account->clientViewCSS(),
|
||||
'clientFontUrl' => $account->getFontsUrl(),
|
||||
'invoice' => $invoice->hidePrivateFields(),
|
||||
'invitation' => $invitation,
|
||||
'invoiceLabels' => $account->getInvoiceLabels(),
|
||||
'contact' => $contact,
|
||||
'paymentTypes' => $paymentTypes,
|
||||
'paymentURL' => $paymentURL,
|
||||
'checkoutComToken' => $checkoutComToken,
|
||||
'checkoutComKey' => $checkoutComKey,
|
||||
'phantomjs' => Input::has('phantomjs'),
|
||||
);
|
||||
|
||||
return View::make('invoices.view', $data);
|
||||
}
|
||||
|
||||
private function getPaymentTypes($client, $invitation)
|
||||
{
|
||||
$paymentTypes = [];
|
||||
$account = $client->account;
|
||||
|
||||
if ($client->getGatewayToken()) {
|
||||
$paymentTypes[] = [
|
||||
'url' => URL::to("payment/{$invitation->invitation_key}/token"), 'label' => trans('texts.use_card_on_file')
|
||||
];
|
||||
}
|
||||
foreach(Gateway::$paymentTypes as $type) {
|
||||
if ($account->getGatewayByType($type)) {
|
||||
$typeLink = strtolower(str_replace('PAYMENT_TYPE_', '', $type));
|
||||
$url = URL::to("/payment/{$invitation->invitation_key}/{$typeLink}");
|
||||
|
||||
// PayPal doesn't allow being run in an iframe so we need to open in new tab
|
||||
if ($type === PAYMENT_TYPE_PAYPAL && $account->iframe_url) {
|
||||
$url = 'javascript:window.open("'.$url.'", "_blank")';
|
||||
}
|
||||
$paymentTypes[] = [
|
||||
'url' => $url, 'label' => trans('texts.'.strtolower($type))
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $paymentTypes;
|
||||
}
|
||||
|
||||
public function dashboard()
|
||||
{
|
||||
if (!$invitation = $this->getInvitation()) {
|
||||
return $this->returnError();
|
||||
}
|
||||
$account = $invitation->account;
|
||||
$invoice = $invitation->invoice;
|
||||
$client = $invoice->client;
|
||||
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
|
||||
|
||||
$data = [
|
||||
'color' => $color,
|
||||
'account' => $account,
|
||||
'client' => $client,
|
||||
'hideLogo' => $account->isWhiteLabel(),
|
||||
'clientViewCSS' => $account->clientViewCSS(),
|
||||
'clientFontUrl' => $account->getFontsUrl(),
|
||||
];
|
||||
|
||||
return response()->view('invited.dashboard', $data);
|
||||
}
|
||||
|
||||
public function activityDatatable()
|
||||
{
|
||||
if (!$invitation = $this->getInvitation()) {
|
||||
return false;
|
||||
}
|
||||
$invoice = $invitation->invoice;
|
||||
|
||||
$query = $this->activityRepo->findByClientId($invoice->client_id);
|
||||
$query->where('activities.adjustment', '!=', 0);
|
||||
|
||||
return Datatable::query($query)
|
||||
->addColumn('activities.id', function ($model) { return Utils::timestampToDateTimeString(strtotime($model->created_at)); })
|
||||
->addColumn('activity_type_id', function ($model) {
|
||||
$data = [
|
||||
'client' => Utils::getClientDisplayName($model),
|
||||
'user' => $model->is_system ? ('<i>' . trans('texts.system') . '</i>') : ($model->user_first_name . ' ' . $model->user_last_name),
|
||||
'invoice' => trans('texts.invoice') . ' ' . $model->invoice,
|
||||
'contact' => Utils::getClientDisplayName($model),
|
||||
'payment' => trans('texts.payment') . ($model->payment ? ' ' . $model->payment : ''),
|
||||
];
|
||||
|
||||
return trans("texts.activity_{$model->activity_type_id}", $data);
|
||||
})
|
||||
->addColumn('balance', function ($model) { return Utils::formatMoney($model->balance, $model->currency_id, $model->country_id); })
|
||||
->addColumn('adjustment', function ($model) { return $model->adjustment != 0 ? Utils::wrapAdjustment($model->adjustment, $model->currency_id, $model->country_id) : ''; })
|
||||
->make();
|
||||
}
|
||||
|
||||
public function invoiceIndex()
|
||||
{
|
||||
if (!$invitation = $this->getInvitation()) {
|
||||
return $this->returnError();
|
||||
}
|
||||
$account = $invitation->account;
|
||||
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
|
||||
|
||||
$data = [
|
||||
'color' => $color,
|
||||
'hideLogo' => $account->isWhiteLabel(),
|
||||
'clientViewCSS' => $account->clientViewCSS(),
|
||||
'clientFontUrl' => $account->getFontsUrl(),
|
||||
'title' => trans('texts.invoices'),
|
||||
'entityType' => ENTITY_INVOICE,
|
||||
'columns' => Utils::trans(['invoice_number', 'invoice_date', 'invoice_total', 'balance_due', 'due_date']),
|
||||
];
|
||||
|
||||
return response()->view('public_list', $data);
|
||||
}
|
||||
|
||||
public function invoiceDatatable()
|
||||
{
|
||||
if (!$invitation = $this->getInvitation()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->invoiceRepo->getClientDatatable($invitation->contact_id, ENTITY_INVOICE, Input::get('sSearch'));
|
||||
}
|
||||
|
||||
|
||||
public function paymentIndex()
|
||||
{
|
||||
if (!$invitation = $this->getInvitation()) {
|
||||
return $this->returnError();
|
||||
}
|
||||
$account = $invitation->account;
|
||||
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
|
||||
|
||||
$data = [
|
||||
'color' => $color,
|
||||
'hideLogo' => $account->isWhiteLabel(),
|
||||
'clientViewCSS' => $account->clientViewCSS(),
|
||||
'clientFontUrl' => $account->getFontsUrl(),
|
||||
'entityType' => ENTITY_PAYMENT,
|
||||
'title' => trans('texts.payments'),
|
||||
'columns' => Utils::trans(['invoice', 'transaction_reference', 'method', 'payment_amount', 'payment_date'])
|
||||
];
|
||||
|
||||
return response()->view('public_list', $data);
|
||||
}
|
||||
|
||||
public function paymentDatatable()
|
||||
{
|
||||
if (!$invitation = $this->getInvitation()) {
|
||||
return false;
|
||||
}
|
||||
$payments = $this->paymentRepo->findForContact($invitation->contact->id, Input::get('sSearch'));
|
||||
|
||||
return Datatable::query($payments)
|
||||
->addColumn('invoice_number', function ($model) { return $model->invitation_key ? link_to('/view/'.$model->invitation_key, $model->invoice_number) : $model->invoice_number; })
|
||||
->addColumn('transaction_reference', function ($model) { return $model->transaction_reference ? $model->transaction_reference : '<i>Manual entry</i>'; })
|
||||
->addColumn('payment_type', function ($model) { return $model->payment_type ? $model->payment_type : ($model->account_gateway_id ? '<i>Online payment</i>' : ''); })
|
||||
->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id, $model->country_id); })
|
||||
->addColumn('payment_date', function ($model) { return Utils::dateToString($model->payment_date); })
|
||||
->make();
|
||||
}
|
||||
|
||||
public function quoteIndex()
|
||||
{
|
||||
if (!$invitation = $this->getInvitation()) {
|
||||
return $this->returnError();
|
||||
}
|
||||
$account = $invitation->account;
|
||||
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
|
||||
|
||||
$data = [
|
||||
'color' => $color,
|
||||
'hideLogo' => $account->isWhiteLabel(),
|
||||
'clientViewCSS' => $account->clientViewCSS(),
|
||||
'clientFontUrl' => $account->getFontsUrl(),
|
||||
'title' => trans('texts.quotes'),
|
||||
'entityType' => ENTITY_QUOTE,
|
||||
'columns' => Utils::trans(['quote_number', 'quote_date', 'quote_total', 'due_date']),
|
||||
];
|
||||
|
||||
return response()->view('public_list', $data);
|
||||
}
|
||||
|
||||
|
||||
public function quoteDatatable()
|
||||
{
|
||||
if (!$invitation = $this->getInvitation()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->invoiceRepo->getClientDatatable($invitation->contact_id, ENTITY_QUOTE, Input::get('sSearch'));
|
||||
}
|
||||
|
||||
private function returnError()
|
||||
{
|
||||
return response()->view('error', [
|
||||
'error' => trans('texts.invoice_not_found'),
|
||||
'hideHeader' => true,
|
||||
'clientViewCSS' => $account->clientViewCSS(),
|
||||
'clientFontUrl' => $account->getFontsUrl(),
|
||||
]);
|
||||
}
|
||||
|
||||
private function getInvitation()
|
||||
{
|
||||
$invitationKey = session('invitation_key');
|
||||
|
||||
if (!$invitationKey) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$invitation = Invitation::where('invitation_key', '=', $invitationKey)->first();
|
||||
|
||||
if (!$invitation || $invitation->is_deleted) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$invoice = $invitation->invoice;
|
||||
|
||||
if (!$invoice || $invoice->is_deleted) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $invitation;
|
||||
}
|
||||
|
||||
}
|
@ -1,32 +1,64 @@
|
||||
<?php namespace App\Http\Controllers;
|
||||
|
||||
use Auth;
|
||||
use Input;
|
||||
use Utils;
|
||||
use Response;
|
||||
use App\Models\Invoice;
|
||||
use App\Ninja\Repositories\InvoiceRepository;
|
||||
use App\Http\Controllers\BaseAPIController;
|
||||
use App\Ninja\Transformers\QuoteTransformer;
|
||||
|
||||
class QuoteApiController extends Controller
|
||||
class QuoteApiController extends BaseAPIController
|
||||
{
|
||||
protected $invoiceRepo;
|
||||
|
||||
public function __construct(InvoiceRepository $invoiceRepo)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->invoiceRepo = $invoiceRepo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @SWG\Get(
|
||||
* path="/quotes",
|
||||
* tags={"quote"},
|
||||
* summary="List of quotes",
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="A list with quotes",
|
||||
* @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/Invoice"))
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response="default",
|
||||
* description="an ""unexpected"" error"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$paginator = Invoice::scope();
|
||||
$invoices = Invoice::scope()
|
||||
->with('client', 'user')
|
||||
->where('invoices.is_quote', '=', true)
|
||||
->orderBy('created_at', 'desc')
|
||||
->get();
|
||||
$invoices = Utils::remapPublicIds($invoices);
|
||||
->with('client', 'invitations', 'user', 'invoice_items')
|
||||
->where('invoices.is_quote', '=', true);
|
||||
|
||||
$response = json_encode($invoices, JSON_PRETTY_PRINT);
|
||||
$headers = Utils::getApiHeaders(count($invoices));
|
||||
if ($clientPublicId = Input::get('client_id')) {
|
||||
$filter = function($query) use ($clientPublicId) {
|
||||
$query->where('public_id', '=', $clientPublicId);
|
||||
};
|
||||
$invoices->whereHas('client', $filter);
|
||||
$paginator->whereHas('client', $filter);
|
||||
}
|
||||
|
||||
return Response::make($response, 200, $headers);
|
||||
$invoices = $invoices->orderBy('created_at', 'desc')->paginate();
|
||||
|
||||
$transformer = new QuoteTransformer(\Auth::user()->account, Input::get('serializer'));
|
||||
$paginator = $paginator->paginate();
|
||||
|
||||
$data = $this->createCollection($invoices, $transformer, 'quotes', $paginator);
|
||||
|
||||
return $this->response($data);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -24,24 +24,24 @@ use App\Models\Invoice;
|
||||
use App\Ninja\Mailers\ContactMailer as Mailer;
|
||||
use App\Ninja\Repositories\InvoiceRepository;
|
||||
use App\Ninja\Repositories\ClientRepository;
|
||||
use App\Ninja\Repositories\TaxRateRepository;
|
||||
use App\Events\QuoteApproved;
|
||||
use App\Events\QuoteInvitationWasApproved;
|
||||
use App\Services\InvoiceService;
|
||||
|
||||
class QuoteController extends BaseController
|
||||
{
|
||||
protected $mailer;
|
||||
protected $invoiceRepo;
|
||||
protected $clientRepo;
|
||||
protected $taxRateRepo;
|
||||
protected $invoiceService;
|
||||
|
||||
public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, TaxRateRepository $taxRateRepo)
|
||||
public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, InvoiceService $invoiceService)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->mailer = $mailer;
|
||||
$this->invoiceRepo = $invoiceRepo;
|
||||
$this->clientRepo = $clientRepo;
|
||||
$this->taxRateRepo = $taxRateRepo;
|
||||
$this->invoiceService = $invoiceService;
|
||||
}
|
||||
|
||||
public function index()
|
||||
@ -53,40 +53,19 @@ class QuoteController extends BaseController
|
||||
$data = [
|
||||
'title' => trans('texts.quotes'),
|
||||
'entityType' => ENTITY_QUOTE,
|
||||
'columns' => Utils::trans(['checkbox', 'quote_number', 'client', 'quote_date', 'quote_total', 'due_date', 'status', 'action']),
|
||||
'columns' => Utils::trans([
|
||||
'checkbox',
|
||||
'quote_number',
|
||||
'client',
|
||||
'quote_date',
|
||||
'quote_total',
|
||||
'valid_until',
|
||||
'status',
|
||||
'action'
|
||||
]),
|
||||
];
|
||||
|
||||
/*
|
||||
if (Invoice::scope()->where('is_recurring', '=', true)->count() > 0)
|
||||
{
|
||||
$data['secEntityType'] = ENTITY_RECURRING_INVOICE;
|
||||
$data['secColumns'] = Utils::trans(['checkbox', 'frequency', 'client', 'start_date', 'end_date', 'quote_total', 'action']);
|
||||
}
|
||||
*/
|
||||
|
||||
return View::make('list', $data);
|
||||
}
|
||||
|
||||
public function clientIndex()
|
||||
{
|
||||
$invitationKey = Session::get('invitation_key');
|
||||
if (!$invitationKey) {
|
||||
return Redirect::to('/setup');
|
||||
}
|
||||
|
||||
$invitation = Invitation::with('account')->where('invitation_key', '=', $invitationKey)->first();
|
||||
$account = $invitation->account;
|
||||
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
|
||||
|
||||
$data = [
|
||||
'color' => $color,
|
||||
'hideLogo' => $account->isWhiteLabel(),
|
||||
'title' => trans('texts.quotes'),
|
||||
'entityType' => ENTITY_QUOTE,
|
||||
'columns' => Utils::trans(['quote_number', 'quote_date', 'quote_total', 'due_date']),
|
||||
];
|
||||
|
||||
return View::make('public_list', $data);
|
||||
return response()->view('list', $data);
|
||||
}
|
||||
|
||||
public function getDatatable($clientPublicId = null)
|
||||
@ -94,26 +73,7 @@ class QuoteController extends BaseController
|
||||
$accountId = Auth::user()->account_id;
|
||||
$search = Input::get('sSearch');
|
||||
|
||||
return $this->invoiceRepo->getDatatable($accountId, $clientPublicId, ENTITY_QUOTE, $search);
|
||||
}
|
||||
|
||||
public function getClientDatatable()
|
||||
{
|
||||
$search = Input::get('sSearch');
|
||||
$invitationKey = Session::get('invitation_key');
|
||||
$invitation = Invitation::where('invitation_key', '=', $invitationKey)->first();
|
||||
|
||||
if (!$invitation || $invitation->is_deleted) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$invoice = $invitation->invoice;
|
||||
|
||||
if (!$invoice || $invoice->is_deleted) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->invoiceRepo->getClientDatatable($invitation->contact_id, ENTITY_QUOTE, $search);
|
||||
return $this->invoiceService->getDatatable($accountId, $clientPublicId, ENTITY_QUOTE, $search);
|
||||
}
|
||||
|
||||
public function create($clientPublicId = 0)
|
||||
@ -122,23 +82,22 @@ class QuoteController extends BaseController
|
||||
return Redirect::to('/invoices/create');
|
||||
}
|
||||
|
||||
$client = null;
|
||||
$invoiceNumber = Auth::user()->account->getNextInvoiceNumber(true);
|
||||
$account = Account::with('country')->findOrFail(Auth::user()->account_id);
|
||||
|
||||
$account = Auth::user()->account;
|
||||
$clientId = null;
|
||||
if ($clientPublicId) {
|
||||
$client = Client::scope($clientPublicId)->firstOrFail();
|
||||
$clientId = Client::getPrivateId($clientPublicId);
|
||||
}
|
||||
$invoice = $account->createInvoice(ENTITY_QUOTE, $clientId);
|
||||
$invoice->public_id = 0;
|
||||
|
||||
$data = array(
|
||||
'account' => $account,
|
||||
'invoice' => null,
|
||||
$data = [
|
||||
'entityType' => $invoice->getEntityType(),
|
||||
'invoice' => $invoice,
|
||||
'data' => Input::old('data'),
|
||||
'invoiceNumber' => $invoiceNumber,
|
||||
'method' => 'POST',
|
||||
'url' => 'invoices',
|
||||
'title' => trans('texts.new_quote'),
|
||||
'client' => $client, );
|
||||
];
|
||||
$data = array_merge($data, self::getViewModel());
|
||||
|
||||
return View::make('invoices.edit', $data);
|
||||
@ -156,8 +115,10 @@ class QuoteController extends BaseController
|
||||
'currencies' => Cache::get('currencies'),
|
||||
'sizes' => Cache::get('sizes'),
|
||||
'paymentTerms' => Cache::get('paymentTerms'),
|
||||
'languages' => Cache::get('languages'),
|
||||
'industries' => Cache::get('industries'),
|
||||
'invoiceDesigns' => InvoiceDesign::getDesigns(),
|
||||
'invoiceFonts' => Cache::get('fonts'),
|
||||
'invoiceLabels' => Auth::user()->account->getInvoiceLabels(),
|
||||
'isRecurring' => false,
|
||||
];
|
||||
@ -165,22 +126,21 @@ class QuoteController extends BaseController
|
||||
|
||||
public function bulk()
|
||||
{
|
||||
$action = Input::get('action');
|
||||
$action = Input::get('bulk_action') ?: Input::get('action');;
|
||||
$ids = Input::get('bulk_public_id') ?: (Input::get('public_id') ?: Input::get('ids'));
|
||||
|
||||
if ($action == 'convert') {
|
||||
$invoice = Invoice::with('invoice_items')->scope(Input::get('id'))->firstOrFail();
|
||||
$clone = $this->invoiceRepo->cloneInvoice($invoice, $invoice->id);
|
||||
$invoice = Invoice::with('invoice_items')->scope($ids)->firstOrFail();
|
||||
$clone = $this->invoiceService->convertQuote($invoice);
|
||||
|
||||
Session::flash('message', trans('texts.converted_to_invoice'));
|
||||
return Redirect::to('invoices/'.$clone->public_id);
|
||||
}
|
||||
|
||||
$statusId = Input::get('statusId');
|
||||
$ids = Input::get('id') ? Input::get('id') : Input::get('ids');
|
||||
$count = $this->invoiceRepo->bulk($ids, $action, $statusId);
|
||||
$count = $this->invoiceService->bulk($ids, $action);
|
||||
|
||||
if ($count > 0) {
|
||||
$key = $action == 'mark' ? "updated_quote" : "{$action}d_quote";
|
||||
$key = $action == 'markSent' ? "updated_quote" : "{$action}d_quote";
|
||||
$message = Utils::pluralize($key, $count);
|
||||
Session::flash('message', $message);
|
||||
}
|
||||
@ -197,19 +157,8 @@ class QuoteController extends BaseController
|
||||
$invitation = Invitation::with('invoice.invoice_items', 'invoice.invitations')->where('invitation_key', '=', $invitationKey)->firstOrFail();
|
||||
$invoice = $invitation->invoice;
|
||||
|
||||
if ($invoice->is_quote && !$invoice->quote_invoice_id) {
|
||||
Event::fire(new QuoteApproved($invoice));
|
||||
Activity::approveQuote($invitation);
|
||||
|
||||
$invoice = $this->invoiceRepo->cloneInvoice($invoice, $invoice->id);
|
||||
Session::flash('message', trans('texts.converted_to_invoice'));
|
||||
|
||||
foreach ($invoice->invitations as $invitationClone) {
|
||||
if ($invitation->contact_id == $invitationClone->contact_id) {
|
||||
$invitationKey = $invitationClone->invitation_key;
|
||||
}
|
||||
}
|
||||
}
|
||||
$invitationKey = $this->invoiceService->approveQuote($invoice, $invitation);
|
||||
Session::flash('message', trans('texts.quote_is_approved'));
|
||||
|
||||
return Redirect::to("view/{$invitationKey}");
|
||||
}
|
||||
|
36
app/Http/Controllers/RecurringInvoiceController.php
Normal file
36
app/Http/Controllers/RecurringInvoiceController.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php namespace App\Http\Controllers;
|
||||
|
||||
use Utils;
|
||||
use App\Ninja\Repositories\InvoiceRepository;
|
||||
|
||||
class RecurringInvoiceController extends BaseController
|
||||
{
|
||||
protected $invoiceRepo;
|
||||
|
||||
public function __construct(InvoiceRepository $invoiceRepo)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->invoiceRepo = $invoiceRepo;
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
$data = [
|
||||
'title' => trans('texts.recurring_invoices'),
|
||||
'entityType' => ENTITY_RECURRING_INVOICE,
|
||||
'columns' => Utils::trans([
|
||||
'checkbox',
|
||||
'frequency',
|
||||
'client',
|
||||
'start_date',
|
||||
'end_date',
|
||||
'invoice_total',
|
||||
'action'
|
||||
])
|
||||
];
|
||||
|
||||
return response()->view('list', $data);
|
||||
}
|
||||
|
||||
}
|
@ -9,7 +9,6 @@ use DateInterval;
|
||||
use DatePeriod;
|
||||
use Session;
|
||||
use View;
|
||||
|
||||
use App\Models\Account;
|
||||
|
||||
class ReportController extends BaseController
|
||||
@ -33,7 +32,6 @@ class ReportController extends BaseController
|
||||
}
|
||||
|
||||
$data = [
|
||||
'feature' => ACCOUNT_DATA_VISUALIZATIONS,
|
||||
'clients' => $clients,
|
||||
'message' => $message,
|
||||
];
|
||||
@ -56,119 +54,84 @@ class ReportController extends BaseController
|
||||
} else {
|
||||
$groupBy = 'MONTH';
|
||||
$chartType = 'Bar';
|
||||
$reportType = '';
|
||||
$reportType = ENTITY_INVOICE;
|
||||
$startDate = Utils::today(false)->modify('-3 month');
|
||||
$endDate = Utils::today(false);
|
||||
$enableReport = true;
|
||||
$enableChart = true;
|
||||
}
|
||||
|
||||
$dateTypes = [
|
||||
'DAYOFYEAR' => 'Daily',
|
||||
'WEEK' => 'Weekly',
|
||||
'MONTH' => 'Monthly',
|
||||
];
|
||||
|
||||
$chartTypes = [
|
||||
'Bar' => 'Bar',
|
||||
'Line' => 'Line',
|
||||
];
|
||||
|
||||
$reportTypes = [
|
||||
ENTITY_CLIENT => trans('texts.client'),
|
||||
ENTITY_INVOICE => trans('texts.invoice'),
|
||||
ENTITY_PAYMENT => trans('texts.payment'),
|
||||
];
|
||||
|
||||
$params = [
|
||||
'dateTypes' => $dateTypes,
|
||||
'chartTypes' => $chartTypes,
|
||||
'chartType' => $chartType,
|
||||
'startDate' => $startDate->format(Session::get(SESSION_DATE_FORMAT)),
|
||||
'endDate' => $endDate->format(Session::get(SESSION_DATE_FORMAT)),
|
||||
'groupBy' => $groupBy,
|
||||
'reportTypes' => $reportTypes,
|
||||
'reportType' => $reportType,
|
||||
'enableChart' => $enableChart,
|
||||
'enableReport' => $enableReport,
|
||||
'title' => trans('texts.charts_and_reports'),
|
||||
];
|
||||
|
||||
if (Auth::user()->account->isPro()) {
|
||||
if ($enableReport) {
|
||||
$params = array_merge($params, self::generateReport($reportType, $groupBy, $startDate, $endDate));
|
||||
|
||||
if ($action == 'export') {
|
||||
self::export($params['exportData'], $params['reportTotals']);
|
||||
}
|
||||
}
|
||||
if ($enableChart) {
|
||||
$params = array_merge($params, self::generateChart($groupBy, $startDate, $endDate));
|
||||
}
|
||||
} else {
|
||||
$params['columns'] = [];
|
||||
$params['displayData'] = [];
|
||||
$params['reportTotals'] = [
|
||||
'amount' => [],
|
||||
'balance' => [],
|
||||
'paid' => [],
|
||||
];
|
||||
$params['labels'] = [];
|
||||
$params['datasets'] = [];
|
||||
$params['scaleStepWidth'] = 100;
|
||||
}
|
||||
|
||||
return View::make('reports.chart_builder', $params);
|
||||
}
|
||||
|
||||
private function generateChart($groupBy, $startDate, $endDate)
|
||||
{
|
||||
$width = 10;
|
||||
$datasets = [];
|
||||
$labels = [];
|
||||
$maxTotals = 0;
|
||||
$width = 10;
|
||||
|
||||
$displayData = [];
|
||||
$exportData = [];
|
||||
$reportTotals = [
|
||||
'amount' => [],
|
||||
'balance' => [],
|
||||
'paid' => []
|
||||
];
|
||||
|
||||
if ($reportType) {
|
||||
$columns = ['client', 'amount', 'paid', 'balance'];
|
||||
} else {
|
||||
$columns = ['client', 'invoice_number', 'invoice_date', 'amount', 'paid', 'balance'];
|
||||
}
|
||||
|
||||
|
||||
if (Auth::user()->account->isPro()) {
|
||||
|
||||
if ($enableReport) {
|
||||
$query = DB::table('invoices')
|
||||
->join('clients', 'clients.id', '=', 'invoices.client_id')
|
||||
->join('contacts', 'contacts.client_id', '=', 'clients.id')
|
||||
->where('invoices.account_id', '=', Auth::user()->account_id)
|
||||
->where('invoices.is_deleted', '=', false)
|
||||
->where('clients.is_deleted', '=', false)
|
||||
->where('contacts.deleted_at', '=', null)
|
||||
->where('invoices.invoice_date', '>=', $startDate->format('Y-m-d'))
|
||||
->where('invoices.invoice_date', '<=', $endDate->format('Y-m-d'))
|
||||
->where('invoices.is_quote', '=', false)
|
||||
->where('invoices.is_recurring', '=', false)
|
||||
->where('contacts.is_primary', '=', true);
|
||||
|
||||
$select = ['clients.currency_id', 'contacts.first_name', 'contacts.last_name', 'contacts.email', 'clients.name as client_name', 'clients.public_id as client_public_id', 'invoices.public_id as invoice_public_id'];
|
||||
|
||||
if ($reportType) {
|
||||
$query->groupBy('clients.id');
|
||||
array_push($select, DB::raw('sum(invoices.amount) amount'), DB::raw('sum(invoices.balance) balance'), DB::raw('sum(invoices.amount - invoices.balance) paid'));
|
||||
} else {
|
||||
array_push($select, 'invoices.invoice_number', 'invoices.amount', 'invoices.balance', 'invoices.invoice_date', DB::raw('(invoices.amount - invoices.balance) paid'));
|
||||
$query->orderBy('invoices.id');
|
||||
}
|
||||
|
||||
$query->select($select);
|
||||
$data = $query->get();
|
||||
|
||||
foreach ($data as $record) {
|
||||
// web display data
|
||||
$displayRow = [link_to('/clients/'.$record->client_public_id, Utils::getClientDisplayName($record))];
|
||||
if (!$reportType) {
|
||||
array_push($displayRow,
|
||||
link_to('/invoices/'.$record->invoice_public_id, $record->invoice_number),
|
||||
Utils::fromSqlDate($record->invoice_date, true)
|
||||
);
|
||||
}
|
||||
array_push($displayRow,
|
||||
Utils::formatMoney($record->amount, $record->currency_id),
|
||||
Utils::formatMoney($record->paid, $record->currency_id),
|
||||
Utils::formatMoney($record->balance, $record->currency_id)
|
||||
);
|
||||
|
||||
// export data
|
||||
$exportRow = [trans('texts.client') => Utils::getClientDisplayName($record)];
|
||||
if (!$reportType) {
|
||||
$exportRow[trans('texts.invoice_number')] = $record->invoice_number;
|
||||
$exportRow[trans('texts.invoice_date')] = Utils::fromSqlDate($record->invoice_date, true);
|
||||
}
|
||||
$exportRow[trans('texts.amount')] = Utils::formatMoney($record->amount, $record->currency_id);
|
||||
$exportRow[trans('texts.paid')] = Utils::formatMoney($record->paid, $record->currency_id);
|
||||
$exportRow[trans('texts.balance')] = Utils::formatMoney($record->balance, $record->currency_id);
|
||||
|
||||
$displayData[] = $displayRow;
|
||||
$exportData[] = $exportRow;
|
||||
|
||||
$accountCurrencyId = Auth::user()->account->currency_id;
|
||||
$currencyId = $record->currency_id ? $record->currency_id : ($accountCurrencyId ? $accountCurrencyId : DEFAULT_CURRENCY);
|
||||
if (!isset($reportTotals['amount'][$currencyId])) {
|
||||
$reportTotals['amount'][$currencyId] = 0;
|
||||
$reportTotals['balance'][$currencyId] = 0;
|
||||
$reportTotals['paid'][$currencyId] = 0;
|
||||
}
|
||||
$reportTotals['amount'][$currencyId] += $record->amount;
|
||||
$reportTotals['paid'][$currencyId] += $record->paid;
|
||||
$reportTotals['balance'][$currencyId] += $record->balance;
|
||||
}
|
||||
|
||||
if ($action == 'export')
|
||||
{
|
||||
self::export($exportData, $reportTotals);
|
||||
}
|
||||
}
|
||||
|
||||
if ($enableChart)
|
||||
{
|
||||
foreach ([ENTITY_INVOICE, ENTITY_PAYMENT, ENTITY_CREDIT] as $entityType)
|
||||
{
|
||||
foreach ([ENTITY_INVOICE, ENTITY_PAYMENT, ENTITY_CREDIT] as $entityType) {
|
||||
// SQLite does not support the YEAR(), MONTH(), WEEK() and similar functions.
|
||||
// Let's see if SQLite is being used.
|
||||
if (Config::get('database.connections.'.Config::get('database.default').'.driver') == 'sqlite')
|
||||
{
|
||||
if (Config::get('database.connections.'.Config::get('database.default').'.driver') == 'sqlite') {
|
||||
// Replace the unsupported function with it's date format counterpart
|
||||
switch ($groupBy)
|
||||
{
|
||||
switch ($groupBy) {
|
||||
case 'MONTH':
|
||||
$dateFormat = '%m'; // returns 01-12
|
||||
break;
|
||||
@ -185,9 +148,7 @@ class ReportController extends BaseController
|
||||
|
||||
// Concatenate the year and the chosen timeframe (Month, Week or Day)
|
||||
$timeframe = 'strftime("%Y", '.$entityType.'_date) || strftime("'.$dateFormat.'", '.$entityType.'_date)';
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// Supported by Laravel's other DBMS drivers (MySQL, MSSQL and PostgreSQL)
|
||||
$timeframe = 'concat(YEAR('.$entityType.'_date), '.$groupBy.'('.$entityType.'_date))';
|
||||
}
|
||||
@ -200,8 +161,7 @@ class ReportController extends BaseController
|
||||
->where($entityType.'s.'.$entityType.'_date', '<=', $endDate->format('Y-m-d'))
|
||||
->groupBy($groupBy);
|
||||
|
||||
if ($entityType == ENTITY_INVOICE)
|
||||
{
|
||||
if ($entityType == ENTITY_INVOICE) {
|
||||
$records->where('is_quote', '=', false)
|
||||
->where('is_recurring', '=', false);
|
||||
}
|
||||
@ -218,15 +178,13 @@ class ReportController extends BaseController
|
||||
|
||||
$totals = [];
|
||||
|
||||
foreach ($period as $d)
|
||||
{
|
||||
foreach ($period as $d) {
|
||||
$dateFormat = $groupBy == 'DAYOFYEAR' ? 'z' : ($groupBy == 'WEEK' ? 'W' : 'n');
|
||||
// MySQL returns 1-366 for DAYOFYEAR, whereas PHP returns 0-365
|
||||
$date = $groupBy == 'DAYOFYEAR' ? $d->format('Y').($d->format($dateFormat) + 1) : $d->format('Y'.$dateFormat);
|
||||
$totals[] = isset($data[$date]) ? $data[$date] : 0;
|
||||
|
||||
if ($entityType == ENTITY_INVOICE)
|
||||
{
|
||||
if ($entityType == ENTITY_INVOICE) {
|
||||
$labelFormat = $groupBy == 'DAYOFYEAR' ? 'j' : ($groupBy == 'WEEK' ? 'W' : 'F');
|
||||
$label = $d->format($labelFormat);
|
||||
$labels[] = $label;
|
||||
@ -235,8 +193,7 @@ class ReportController extends BaseController
|
||||
|
||||
$max = max($totals);
|
||||
|
||||
if ($max > 0)
|
||||
{
|
||||
if ($max > 0) {
|
||||
$datasets[] = [
|
||||
'totals' => $totals,
|
||||
'colors' => $entityType == ENTITY_INVOICE ? '78,205,196' : ($entityType == ENTITY_CREDIT ? '199,244,100' : '255,107,107'),
|
||||
@ -247,47 +204,159 @@ class ReportController extends BaseController
|
||||
|
||||
$width = (ceil($maxTotals / 100) * 100) / 10;
|
||||
$width = max($width, 10);
|
||||
}
|
||||
}
|
||||
|
||||
$dateTypes = [
|
||||
'DAYOFYEAR' => 'Daily',
|
||||
'WEEK' => 'Weekly',
|
||||
'MONTH' => 'Monthly',
|
||||
];
|
||||
|
||||
$chartTypes = [
|
||||
'Bar' => 'Bar',
|
||||
'Line' => 'Line',
|
||||
];
|
||||
|
||||
$reportTypes = [
|
||||
'' => '',
|
||||
'Client' => trans('texts.client')
|
||||
];
|
||||
|
||||
$params = [
|
||||
'labels' => $labels,
|
||||
return [
|
||||
'datasets' => $datasets,
|
||||
'scaleStepWidth' => $width,
|
||||
'dateTypes' => $dateTypes,
|
||||
'chartTypes' => $chartTypes,
|
||||
'chartType' => $chartType,
|
||||
'startDate' => $startDate->format(Session::get(SESSION_DATE_FORMAT)),
|
||||
'endDate' => $endDate->format(Session::get(SESSION_DATE_FORMAT)),
|
||||
'groupBy' => $groupBy,
|
||||
'feature' => ACCOUNT_CHART_BUILDER,
|
||||
'displayData' => $displayData,
|
||||
'columns' => $columns,
|
||||
'reportTotals' => $reportTotals,
|
||||
'reportTypes' => $reportTypes,
|
||||
'reportType' => $reportType,
|
||||
'enableChart' => $enableChart,
|
||||
'enableReport' => $enableReport,
|
||||
'title' => trans('texts.charts_and_reports'),
|
||||
'labels' => $labels,
|
||||
];
|
||||
}
|
||||
|
||||
private function generateReport($reportType, $groupBy, $startDate, $endDate)
|
||||
{
|
||||
if ($reportType == ENTITY_CLIENT) {
|
||||
$columns = ['client', 'amount', 'paid', 'balance'];
|
||||
} elseif ($reportType == ENTITY_INVOICE) {
|
||||
$columns = ['client', 'invoice_number', 'invoice_date', 'amount', 'paid', 'balance'];
|
||||
} else {
|
||||
$columns = ['client', 'invoice_number', 'invoice_date', 'amount', 'payment_date', 'paid', 'method'];
|
||||
}
|
||||
|
||||
$query = DB::table('invoices')
|
||||
->join('accounts', 'accounts.id', '=', 'invoices.account_id')
|
||||
->join('clients', 'clients.id', '=', 'invoices.client_id')
|
||||
->join('contacts', 'contacts.client_id', '=', 'clients.id')
|
||||
->where('invoices.account_id', '=', Auth::user()->account_id)
|
||||
->where('invoices.is_deleted', '=', false)
|
||||
->where('clients.is_deleted', '=', false)
|
||||
->where('contacts.deleted_at', '=', null)
|
||||
->where('invoices.invoice_date', '>=', $startDate->format('Y-m-d'))
|
||||
->where('invoices.invoice_date', '<=', $endDate->format('Y-m-d'))
|
||||
->where('invoices.is_quote', '=', false)
|
||||
->where('invoices.is_recurring', '=', false)
|
||||
->where('contacts.is_primary', '=', true);
|
||||
|
||||
$select = [
|
||||
DB::raw('COALESCE(clients.currency_id, accounts.currency_id) currency_id'),
|
||||
'accounts.country_id',
|
||||
'contacts.first_name',
|
||||
'contacts.last_name',
|
||||
'contacts.email',
|
||||
'clients.name as client_name',
|
||||
'clients.public_id as client_public_id',
|
||||
'invoices.public_id as invoice_public_id'
|
||||
];
|
||||
|
||||
return View::make('reports.chart_builder', $params);
|
||||
if ($reportType == ENTITY_CLIENT) {
|
||||
$query->groupBy('clients.id');
|
||||
array_push($select, DB::raw('sum(invoices.amount) amount'), DB::raw('sum(invoices.balance) balance'), DB::raw('sum(invoices.amount - invoices.balance) paid'));
|
||||
} else {
|
||||
$query->orderBy('invoices.id');
|
||||
array_push($select, 'invoices.invoice_number', 'invoices.amount', 'invoices.balance', 'invoices.invoice_date');
|
||||
if ($reportType == ENTITY_INVOICE) {
|
||||
array_push($select, DB::raw('(invoices.amount - invoices.balance) paid'));
|
||||
} else {
|
||||
$query->join('payments', 'payments.invoice_id', '=', 'invoices.id')
|
||||
->leftJoin('payment_types', 'payment_types.id', '=', 'payments.payment_type_id')
|
||||
->leftJoin('account_gateways', 'account_gateways.id', '=', 'payments.account_gateway_id')
|
||||
->leftJoin('gateways', 'gateways.id', '=', 'account_gateways.gateway_id');
|
||||
array_push($select, 'payments.payment_date', 'payments.amount as paid', 'payment_types.name as payment_type', 'gateways.name as gateway');
|
||||
}
|
||||
}
|
||||
|
||||
$query->select($select);
|
||||
$data = $query->get();
|
||||
|
||||
$lastInvoiceId = null;
|
||||
$sameAsLast = false;
|
||||
$displayData = [];
|
||||
|
||||
$exportData = [];
|
||||
$reportTotals = [
|
||||
'amount' => [],
|
||||
'balance' => [],
|
||||
'paid' => [],
|
||||
];
|
||||
|
||||
foreach ($data as $record) {
|
||||
$sameAsLast = ($lastInvoiceId == $record->invoice_public_id);
|
||||
$lastInvoiceId = $record->invoice_public_id;
|
||||
|
||||
$displayRow = [];
|
||||
if ($sameAsLast) {
|
||||
array_push($displayRow, '', '', '', '');
|
||||
} else {
|
||||
array_push($displayRow, link_to('/clients/'.$record->client_public_id, Utils::getClientDisplayName($record)));
|
||||
if ($reportType != ENTITY_CLIENT) {
|
||||
array_push($displayRow,
|
||||
link_to('/invoices/'.$record->invoice_public_id, $record->invoice_number),
|
||||
Utils::fromSqlDate($record->invoice_date, true)
|
||||
);
|
||||
}
|
||||
array_push($displayRow, Utils::formatMoney($record->amount, $record->currency_id, $record->country_id));
|
||||
}
|
||||
if ($reportType != ENTITY_PAYMENT) {
|
||||
array_push($displayRow, Utils::formatMoney($record->paid, $record->currency_id, $record->country_id));
|
||||
}
|
||||
if ($reportType == ENTITY_PAYMENT) {
|
||||
array_push($displayRow,
|
||||
Utils::fromSqlDate($record->payment_date, true),
|
||||
Utils::formatMoney($record->paid, $record->currency_id, $record->country_id),
|
||||
$record->gateway ?: $record->payment_type
|
||||
);
|
||||
} else {
|
||||
array_push($displayRow, Utils::formatMoney($record->balance, $record->currency_id, $record->country_id));
|
||||
}
|
||||
|
||||
// export data
|
||||
$exportRow = [];
|
||||
if ($sameAsLast) {
|
||||
$exportRow[trans('texts.client')] = ' ';
|
||||
$exportRow[trans('texts.invoice_number')] = ' ';
|
||||
$exportRow[trans('texts.invoice_date')] = ' ';
|
||||
$exportRow[trans('texts.amount')] = ' ';
|
||||
} else {
|
||||
$exportRow[trans('texts.client')] = Utils::getClientDisplayName($record);
|
||||
if ($reportType != ENTITY_CLIENT) {
|
||||
$exportRow[trans('texts.invoice_number')] = $record->invoice_number;
|
||||
$exportRow[trans('texts.invoice_date')] = Utils::fromSqlDate($record->invoice_date, true);
|
||||
}
|
||||
$exportRow[trans('texts.amount')] = Utils::formatMoney($record->amount, $record->currency_id, $record->country_id);
|
||||
}
|
||||
if ($reportType != ENTITY_PAYMENT) {
|
||||
$exportRow[trans('texts.paid')] = Utils::formatMoney($record->paid, $record->currency_id, $record->country_id);
|
||||
}
|
||||
if ($reportType == ENTITY_PAYMENT) {
|
||||
$exportRow[trans('texts.payment_date')] = Utils::fromSqlDate($record->payment_date, true);
|
||||
$exportRow[trans('texts.payment_amount')] = Utils::formatMoney($record->paid, $record->currency_id, $record->country_id);
|
||||
$exportRow[trans('texts.method')] = $record->gateway ?: $record->payment_type;
|
||||
} else {
|
||||
$exportRow[trans('texts.balance')] = Utils::formatMoney($record->balance, $record->currency_id, $record->country_id);
|
||||
}
|
||||
|
||||
$displayData[] = $displayRow;
|
||||
$exportData[] = $exportRow;
|
||||
|
||||
$accountCurrencyId = Auth::user()->account->currency_id;
|
||||
$currencyId = $record->currency_id ? $record->currency_id : ($accountCurrencyId ? $accountCurrencyId : DEFAULT_CURRENCY);
|
||||
if (!isset($reportTotals['amount'][$currencyId])) {
|
||||
$reportTotals['amount'][$currencyId] = 0;
|
||||
$reportTotals['balance'][$currencyId] = 0;
|
||||
$reportTotals['paid'][$currencyId] = 0;
|
||||
}
|
||||
if (!$sameAsLast) {
|
||||
$reportTotals['amount'][$currencyId] += $record->amount;
|
||||
$reportTotals['balance'][$currencyId] += $record->balance;
|
||||
}
|
||||
$reportTotals['paid'][$currencyId] += $record->paid;
|
||||
}
|
||||
|
||||
return [
|
||||
'columns' => $columns,
|
||||
'displayData' => $displayData,
|
||||
'reportTotals' => $reportTotals,
|
||||
'exportData' => $exportData
|
||||
];
|
||||
}
|
||||
|
||||
private function export($data, $totals)
|
||||
|
101
app/Http/Controllers/TaskApiController.php
Normal file
101
app/Http/Controllers/TaskApiController.php
Normal file
@ -0,0 +1,101 @@
|
||||
<?php namespace App\Http\Controllers;
|
||||
|
||||
use Auth;
|
||||
use Utils;
|
||||
use Response;
|
||||
use Input;
|
||||
use App\Models\Task;
|
||||
use App\Ninja\Repositories\TaskRepository;
|
||||
use App\Http\Controllers\BaseAPIController;
|
||||
use App\Ninja\Transformers\TaskTransformer;
|
||||
|
||||
class TaskApiController extends BaseAPIController
|
||||
{
|
||||
protected $taskRepo;
|
||||
|
||||
public function __construct(TaskRepository $taskRepo)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->taskRepo = $taskRepo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @SWG\Get(
|
||||
* path="/tasks",
|
||||
* tags={"task"},
|
||||
* summary="List of tasks",
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="A list with tasks",
|
||||
* @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/Task"))
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response="default",
|
||||
* description="an ""unexpected"" error"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$paginator = Task::scope();
|
||||
$tasks = Task::scope()
|
||||
->with($this->getIncluded());
|
||||
|
||||
if ($clientPublicId = Input::get('client_id')) {
|
||||
$filter = function($query) use ($clientPublicId) {
|
||||
$query->where('public_id', '=', $clientPublicId);
|
||||
};
|
||||
$tasks->whereHas('client', $filter);
|
||||
$paginator->whereHas('client', $filter);
|
||||
}
|
||||
|
||||
$tasks = $tasks->orderBy('created_at', 'desc')->paginate();
|
||||
$paginator = $paginator->paginate();
|
||||
$transformer = new TaskTransformer(\Auth::user()->account, Input::get('serializer'));
|
||||
|
||||
$data = $this->createCollection($tasks, $transformer, 'tasks', $paginator);
|
||||
|
||||
return $this->response($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @SWG\Post(
|
||||
* path="/tasks",
|
||||
* tags={"task"},
|
||||
* summary="Create a task",
|
||||
* @SWG\Parameter(
|
||||
* in="body",
|
||||
* name="body",
|
||||
* @SWG\Schema(ref="#/definitions/Task")
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="New task",
|
||||
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Task"))
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response="default",
|
||||
* description="an ""unexpected"" error"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function store()
|
||||
{
|
||||
$data = Input::all();
|
||||
$taskId = isset($data['id']) ? $data['id'] : false;
|
||||
|
||||
if (isset($data['client_id']) && $data['client_id']) {
|
||||
$data['client'] = $data['client_id'];
|
||||
}
|
||||
|
||||
$task = $this->taskRepo->save($taskId, $data);
|
||||
$task = Task::scope($task->public_id)->with('client')->first();
|
||||
|
||||
$transformer = new TaskTransformer(Auth::user()->account, Input::get('serializer'));
|
||||
$data = $this->createItem($task, $transformer, 'task');
|
||||
|
||||
return $this->response($data);
|
||||
}
|
||||
|
||||
}
|
@ -16,17 +16,20 @@ use App\Models\Client;
|
||||
use App\Models\Task;
|
||||
use App\Ninja\Repositories\TaskRepository;
|
||||
use App\Ninja\Repositories\InvoiceRepository;
|
||||
use App\Services\TaskService;
|
||||
|
||||
class TaskController extends BaseController
|
||||
{
|
||||
protected $taskRepo;
|
||||
protected $taskService;
|
||||
|
||||
public function __construct(TaskRepository $taskRepo, InvoiceRepository $invoiceRepo)
|
||||
public function __construct(TaskRepository $taskRepo, InvoiceRepository $invoiceRepo, TaskService $taskService)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->taskRepo = $taskRepo;
|
||||
$this->invoiceRepo = $invoiceRepo;
|
||||
$this->taskService = $taskService;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -36,81 +39,27 @@ class TaskController extends BaseController
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
self::checkTimezone();
|
||||
|
||||
return View::make('list', array(
|
||||
'entityType' => ENTITY_TASK,
|
||||
'title' => trans('texts.tasks'),
|
||||
'sortCol' => '2',
|
||||
'columns' => Utils::trans(['checkbox', 'client', 'date', 'duration', 'description', 'status', 'action']),
|
||||
'columns' => Utils::trans([
|
||||
'checkbox',
|
||||
'client',
|
||||
'date',
|
||||
'duration',
|
||||
'description',
|
||||
'status',
|
||||
''
|
||||
]),
|
||||
));
|
||||
}
|
||||
|
||||
public function getDatatable($clientPublicId = null)
|
||||
{
|
||||
$tasks = $this->taskRepo->find($clientPublicId, Input::get('sSearch'));
|
||||
|
||||
$table = Datatable::query($tasks);
|
||||
|
||||
if (!$clientPublicId) {
|
||||
$table->addColumn('checkbox', function ($model) { return '<input type="checkbox" name="ids[]" value="'.$model->public_id.'" '.Utils::getEntityRowClass($model).'>'; })
|
||||
->addColumn('client_name', function ($model) { return $model->client_public_id ? link_to('clients/'.$model->client_public_id, Utils::getClientDisplayName($model)) : ''; });
|
||||
return $this->taskService->getDatatable($clientPublicId, Input::get('sSearch'));
|
||||
}
|
||||
|
||||
return $table->addColumn('created_at', function($model) { return Task::calcStartTime($model); })
|
||||
->addColumn('time_log', function($model) { return gmdate('H:i:s', Task::calcDuration($model)); })
|
||||
->addColumn('description', function($model) { return $model->description; })
|
||||
->addColumn('invoice_number', function($model) { return self::getStatusLabel($model); })
|
||||
->addColumn('dropdown', function ($model) {
|
||||
$str = '<div class="btn-group tr-action" style="visibility:hidden;">
|
||||
<button type="button" class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
'.trans('texts.select').' <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" role="menu">';
|
||||
|
||||
if (!$model->deleted_at || $model->deleted_at == '0000-00-00') {
|
||||
$str .= '<li><a href="'.URL::to('tasks/'.$model->public_id.'/edit').'">'.trans('texts.edit_task').'</a></li>';
|
||||
}
|
||||
|
||||
if ($model->invoice_number) {
|
||||
$str .= '<li>' . link_to("/invoices/{$model->invoice_public_id}/edit", trans('texts.view_invoice')) . '</li>';
|
||||
} elseif ($model->is_running) {
|
||||
$str .= '<li><a href="javascript:stopTask('.$model->public_id.')">'.trans('texts.stop_task').'</a></li>';
|
||||
} elseif (!$model->deleted_at || $model->deleted_at == '0000-00-00') {
|
||||
$str .= '<li><a href="javascript:invoiceTask('.$model->public_id.')">'.trans('texts.invoice_task').'</a></li>';
|
||||
}
|
||||
|
||||
if (!$model->deleted_at || $model->deleted_at == '0000-00-00') {
|
||||
$str .= '<li class="divider"></li>
|
||||
<li><a href="javascript:archiveEntity('.$model->public_id.')">'.trans('texts.archive_task').'</a></li>';
|
||||
} else {
|
||||
$str .= '<li><a href="javascript:restoreEntity('.$model->public_id.')">'.trans('texts.restore_task').'</a></li>';
|
||||
}
|
||||
|
||||
if (!$model->is_deleted) {
|
||||
$str .= '<li><a href="javascript:deleteEntity('.$model->public_id.')">'.trans('texts.delete_task').'</a></li></ul>';
|
||||
}
|
||||
|
||||
return $str . '</div>';
|
||||
})
|
||||
->make();
|
||||
}
|
||||
|
||||
private function getStatusLabel($model) {
|
||||
if ($model->invoice_number) {
|
||||
$class = 'success';
|
||||
$label = trans('texts.invoiced');
|
||||
} elseif ($model->is_running) {
|
||||
$class = 'primary';
|
||||
$label = trans('texts.running');
|
||||
} else {
|
||||
$class = 'default';
|
||||
$label = trans('texts.logged');
|
||||
}
|
||||
return "<h4><div class=\"label label-{$class}\">$label</div></h4>";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
@ -121,6 +70,13 @@ class TaskController extends BaseController
|
||||
return $this->save();
|
||||
}
|
||||
|
||||
public function show($publicId)
|
||||
{
|
||||
Session::reflash();
|
||||
|
||||
return Redirect::to("tasks/{$publicId}/edit");
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*
|
||||
@ -128,7 +84,7 @@ class TaskController extends BaseController
|
||||
*/
|
||||
public function create($clientPublicId = 0)
|
||||
{
|
||||
self::checkTimezone();
|
||||
$this->checkTimezone();
|
||||
|
||||
$data = [
|
||||
'task' => null,
|
||||
@ -136,7 +92,8 @@ class TaskController extends BaseController
|
||||
'method' => 'POST',
|
||||
'url' => 'tasks',
|
||||
'title' => trans('texts.new_task'),
|
||||
'minuteOffset' => Utils::getTiemstampOffset(),
|
||||
'timezone' => Auth::user()->account->timezone ? Auth::user()->account->timezone->name : DEFAULT_TIMEZONE,
|
||||
'datetimeFormat' => Auth::user()->account->getMomentDateTimeFormat(),
|
||||
];
|
||||
|
||||
$data = array_merge($data, self::getViewModel());
|
||||
@ -152,15 +109,15 @@ class TaskController extends BaseController
|
||||
*/
|
||||
public function edit($publicId)
|
||||
{
|
||||
self::checkTimezone();
|
||||
$this->checkTimezone();
|
||||
|
||||
$task = Task::scope($publicId)->with('client', 'invoice')->firstOrFail();
|
||||
$task = Task::scope($publicId)->with('client', 'invoice')->withTrashed()->firstOrFail();
|
||||
|
||||
$actions = [];
|
||||
if ($task->invoice) {
|
||||
$actions[] = ['url' => URL::to("inovices/{$task->invoice->public_id}/edit"), 'label' => trans("texts.view_invoice")];
|
||||
$actions[] = ['url' => URL::to("invoices/{$task->invoice->public_id}/edit"), 'label' => trans("texts.view_invoice")];
|
||||
} else {
|
||||
$actions[] = ['url' => 'javascript:submitAction("invoice")', 'label' => trans("texts.create_invoice")];
|
||||
$actions[] = ['url' => 'javascript:submitAction("invoice")', 'label' => trans("texts.invoice_task")];
|
||||
|
||||
// check for any open invoices
|
||||
$invoices = $task->client_id ? $this->invoiceRepo->findOpenInvoices($task->client_id) : [];
|
||||
@ -186,7 +143,8 @@ class TaskController extends BaseController
|
||||
'title' => trans('texts.edit_task'),
|
||||
'duration' => $task->is_running ? $task->getCurrentDuration() : $task->getDuration(),
|
||||
'actions' => $actions,
|
||||
'minuteOffset' => Utils::getTiemstampOffset(),
|
||||
'timezone' => Auth::user()->account->timezone ? Auth::user()->account->timezone->name : DEFAULT_TIMEZONE,
|
||||
'datetimeFormat' => Auth::user()->account->getMomentDateTimeFormat(),
|
||||
];
|
||||
|
||||
$data = array_merge($data, self::getViewModel());
|
||||
@ -208,7 +166,8 @@ class TaskController extends BaseController
|
||||
private static function getViewModel()
|
||||
{
|
||||
return [
|
||||
'clients' => Client::scope()->with('contacts')->orderBy('name')->get()
|
||||
'clients' => Client::scope()->with('contacts')->orderBy('name')->get(),
|
||||
'account' => Auth::user()->account,
|
||||
];
|
||||
}
|
||||
|
||||
@ -216,20 +175,32 @@ class TaskController extends BaseController
|
||||
{
|
||||
$action = Input::get('action');
|
||||
|
||||
if (in_array($action, ['archive', 'delete', 'invoice', 'restore', 'add_to_invoice'])) {
|
||||
if (in_array($action, ['archive', 'delete', 'restore'])) {
|
||||
return self::bulk();
|
||||
}
|
||||
|
||||
if ($validator = $this->taskRepo->getErrors(Input::all())) {
|
||||
$url = $publicId ? 'tasks/'.$publicId.'/edit' : 'tasks/create';
|
||||
Session::flash('error', trans('texts.task_errors'));
|
||||
return Redirect::to($url)
|
||||
->withErrors($validator)
|
||||
->withInput();
|
||||
}
|
||||
|
||||
$task = $this->taskRepo->save($publicId, Input::all());
|
||||
Session::flash('message', trans($publicId ? 'texts.updated_task' : 'texts.created_task'));
|
||||
|
||||
if (in_array($action, ['invoice', 'add_to_invoice'])) {
|
||||
return self::bulk();
|
||||
}
|
||||
|
||||
return Redirect::to("tasks/{$task->public_id}/edit");
|
||||
}
|
||||
|
||||
public function bulk()
|
||||
{
|
||||
$action = Input::get('action');
|
||||
$ids = Input::get('id') ? Input::get('id') : Input::get('ids');
|
||||
$ids = Input::get('public_id') ?: (Input::get('id') ?: Input::get('ids'));
|
||||
|
||||
if ($action == 'stop') {
|
||||
$this->taskRepo->save($ids, ['action' => $action]);
|
||||
@ -258,10 +229,10 @@ class TaskController extends BaseController
|
||||
return Redirect::to('tasks');
|
||||
}
|
||||
|
||||
$account = Auth::user()->account;
|
||||
$data[] = [
|
||||
'publicId' => $task->public_id,
|
||||
'description' => $task->description,
|
||||
'startTime' => $task->getStartTime(),
|
||||
'description' => $task->description . "\n\n" . $task->present()->times($account),
|
||||
'duration' => $task->getHours(),
|
||||
];
|
||||
}
|
||||
@ -289,7 +260,7 @@ class TaskController extends BaseController
|
||||
private function checkTimezone()
|
||||
{
|
||||
if (!Auth::user()->account->timezone) {
|
||||
$link = link_to('/company/details?focus=timezone_id', trans('texts.click_here'), ['target' => '_blank']);
|
||||
$link = link_to('/settings/localization?focus=timezone_id', trans('texts.click_here'), ['target' => '_blank']);
|
||||
Session::flash('warning', trans('texts.timezone_unset', ['link' => $link]));
|
||||
}
|
||||
}
|
||||
|
100
app/Http/Controllers/TaxRateController.php
Normal file
100
app/Http/Controllers/TaxRateController.php
Normal file
@ -0,0 +1,100 @@
|
||||
<?php namespace App\Http\Controllers;
|
||||
|
||||
use Auth;
|
||||
use Str;
|
||||
use DB;
|
||||
use Datatable;
|
||||
use Utils;
|
||||
use URL;
|
||||
use View;
|
||||
use Input;
|
||||
use Session;
|
||||
use Redirect;
|
||||
|
||||
use App\Models\TaxRate;
|
||||
use App\Services\TaxRateService;
|
||||
|
||||
class TaxRateController extends BaseController
|
||||
{
|
||||
protected $taxRateService;
|
||||
|
||||
public function __construct(TaxRateService $taxRateService)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->taxRateService = $taxRateService;
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
return Redirect::to('settings/' . ACCOUNT_TAX_RATES);
|
||||
}
|
||||
|
||||
public function getDatatable()
|
||||
{
|
||||
return $this->taxRateService->getDatatable(Auth::user()->account_id);
|
||||
}
|
||||
|
||||
public function edit($publicId)
|
||||
{
|
||||
$data = [
|
||||
'taxRate' => TaxRate::scope($publicId)->firstOrFail(),
|
||||
'method' => 'PUT',
|
||||
'url' => 'tax_rates/'.$publicId,
|
||||
'title' => trans('texts.edit_tax_rate'),
|
||||
];
|
||||
|
||||
return View::make('accounts.tax_rate', $data);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
$data = [
|
||||
'taxRate' => null,
|
||||
'method' => 'POST',
|
||||
'url' => 'tax_rates',
|
||||
'title' => trans('texts.create_tax_rate'),
|
||||
];
|
||||
|
||||
return View::make('accounts.tax_rate', $data);
|
||||
}
|
||||
|
||||
public function store()
|
||||
{
|
||||
return $this->save();
|
||||
}
|
||||
|
||||
public function update($publicId)
|
||||
{
|
||||
return $this->save($publicId);
|
||||
}
|
||||
|
||||
private function save($publicId = false)
|
||||
{
|
||||
if ($publicId) {
|
||||
$taxRate = TaxRate::scope($publicId)->firstOrFail();
|
||||
} else {
|
||||
$taxRate = TaxRate::createNew();
|
||||
}
|
||||
|
||||
$taxRate->name = trim(Input::get('name'));
|
||||
$taxRate->rate = Utils::parseFloat(Input::get('rate'));
|
||||
$taxRate->save();
|
||||
|
||||
$message = $publicId ? trans('texts.updated_tax_rate') : trans('texts.created_tax_rate');
|
||||
Session::flash('message', $message);
|
||||
|
||||
return Redirect::to('settings/' . ACCOUNT_TAX_RATES);
|
||||
}
|
||||
|
||||
public function bulk()
|
||||
{
|
||||
$action = Input::get('bulk_action');
|
||||
$ids = Input::get('bulk_public_id');
|
||||
$count = $this->taxRateService->bulk($ids, $action);
|
||||
|
||||
Session::flash('message', trans('texts.archived_tax_rate'));
|
||||
|
||||
return Redirect::to('settings/' . ACCOUNT_TAX_RATES);
|
||||
}
|
||||
}
|
@ -1,13 +1,4 @@
|
||||
<?php namespace App\Http\Controllers;
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Confide Controller Template
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This is the default Confide controller template for controlling user
|
||||
| authentication. Feel free to change to your needs.
|
||||
|
|
||||
*/
|
||||
|
||||
use Auth;
|
||||
use Session;
|
||||
@ -20,45 +11,28 @@ use Datatable;
|
||||
use URL;
|
||||
|
||||
use App\Models\AccountToken;
|
||||
|
||||
use App\Services\TokenService;
|
||||
use App\Ninja\Repositories\AccountRepository;
|
||||
|
||||
class TokenController extends BaseController
|
||||
{
|
||||
protected $tokenService;
|
||||
|
||||
public function __construct(TokenService $tokenService)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->tokenService = $tokenService;
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
return Redirect::to('settings/' . ACCOUNT_API_TOKENS);
|
||||
}
|
||||
|
||||
public function getDatatable()
|
||||
{
|
||||
$query = DB::table('account_tokens')
|
||||
->where('account_tokens.account_id', '=', Auth::user()->account_id);
|
||||
|
||||
if (!Session::get('show_trash:token')) {
|
||||
$query->where('account_tokens.deleted_at', '=', null);
|
||||
}
|
||||
|
||||
$query->select('account_tokens.public_id', 'account_tokens.name', 'account_tokens.token', 'account_tokens.public_id', 'account_tokens.deleted_at');
|
||||
|
||||
return Datatable::query($query)
|
||||
->addColumn('name', function ($model) { return link_to('tokens/'.$model->public_id.'/edit', $model->name); })
|
||||
->addColumn('token', function ($model) { return $model->token; })
|
||||
->addColumn('dropdown', function ($model) {
|
||||
$actions = '<div class="btn-group tr-action" style="visibility:hidden;">
|
||||
<button type="button" class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
'.trans('texts.select').' <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" role="menu">';
|
||||
|
||||
if (!$model->deleted_at) {
|
||||
$actions .= '<li><a href="'.URL::to('tokens/'.$model->public_id).'/edit">'.uctrans('texts.edit_token').'</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="javascript:deleteToken('.$model->public_id.')">'.uctrans('texts.delete_token').'</a></li>';
|
||||
}
|
||||
|
||||
$actions .= '</ul>
|
||||
</div>';
|
||||
|
||||
return $actions;
|
||||
})
|
||||
->orderColumns(['name', 'token'])
|
||||
->make();
|
||||
return $this->tokenService->getDatatable(Auth::user()->account_id);
|
||||
}
|
||||
|
||||
public function edit($publicId)
|
||||
@ -67,7 +41,6 @@ class TokenController extends BaseController
|
||||
->where('public_id', '=', $publicId)->firstOrFail();
|
||||
|
||||
$data = [
|
||||
'showBreadcrumbs' => false,
|
||||
'token' => $token,
|
||||
'method' => 'PUT',
|
||||
'url' => 'tokens/'.$publicId,
|
||||
@ -94,7 +67,6 @@ class TokenController extends BaseController
|
||||
public function create()
|
||||
{
|
||||
$data = [
|
||||
'showBreadcrumbs' => false,
|
||||
'token' => null,
|
||||
'method' => 'POST',
|
||||
'url' => 'tokens',
|
||||
@ -104,17 +76,15 @@ class TokenController extends BaseController
|
||||
return View::make('accounts.token', $data);
|
||||
}
|
||||
|
||||
public function delete()
|
||||
public function bulk()
|
||||
{
|
||||
$tokenPublicId = Input::get('tokenPublicId');
|
||||
$token = AccountToken::where('account_id', '=', Auth::user()->account_id)
|
||||
->where('public_id', '=', $tokenPublicId)->firstOrFail();
|
||||
$action = Input::get('bulk_action');
|
||||
$ids = Input::get('bulk_public_id');
|
||||
$count = $this->tokenService->bulk($ids, $action);
|
||||
|
||||
$token->delete();
|
||||
Session::flash('message', trans('texts.archived_token'));
|
||||
|
||||
Session::flash('message', trans('texts.deleted_token'));
|
||||
|
||||
return Redirect::to('company/advanced_settings/token_management');
|
||||
return Redirect::to('settings/' . ACCOUNT_API_TOKENS);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -142,13 +112,9 @@ class TokenController extends BaseController
|
||||
if ($tokenPublicId) {
|
||||
$token->name = trim(Input::get('name'));
|
||||
} else {
|
||||
$lastToken = AccountToken::withTrashed()->where('account_id', '=', Auth::user()->account_id)
|
||||
->orderBy('public_id', 'DESC')->first();
|
||||
|
||||
$token = AccountToken::createNew();
|
||||
$token->name = trim(Input::get('name'));
|
||||
$token->token = str_random(RANDOM_KEY_LENGTH);
|
||||
$token->public_id = $lastToken ? $lastToken->public_id + 1 : 1;
|
||||
}
|
||||
|
||||
$token->save();
|
||||
@ -162,7 +128,7 @@ class TokenController extends BaseController
|
||||
Session::flash('message', $message);
|
||||
}
|
||||
|
||||
return Redirect::to('company/advanced_settings/token_management');
|
||||
return Redirect::to('settings/' . ACCOUNT_API_TOKENS);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -19,65 +19,33 @@ use App\Http\Requests;
|
||||
use App\Ninja\Repositories\AccountRepository;
|
||||
use App\Ninja\Mailers\ContactMailer;
|
||||
use App\Ninja\Mailers\UserMailer;
|
||||
use App\Services\UserService;
|
||||
|
||||
class UserController extends BaseController
|
||||
{
|
||||
protected $accountRepo;
|
||||
protected $contactMailer;
|
||||
protected $userMailer;
|
||||
protected $userService;
|
||||
|
||||
public function __construct(AccountRepository $accountRepo, ContactMailer $contactMailer, UserMailer $userMailer)
|
||||
public function __construct(AccountRepository $accountRepo, ContactMailer $contactMailer, UserMailer $userMailer, UserService $userService)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->accountRepo = $accountRepo;
|
||||
$this->contactMailer = $contactMailer;
|
||||
$this->userMailer = $userMailer;
|
||||
$this->userService = $userService;
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
return Redirect::to('settings/' . ACCOUNT_USER_MANAGEMENT);
|
||||
}
|
||||
|
||||
public function getDatatable()
|
||||
{
|
||||
$query = DB::table('users')
|
||||
->where('users.account_id', '=', Auth::user()->account_id);
|
||||
|
||||
if (!Session::get('show_trash:user')) {
|
||||
$query->where('users.deleted_at', '=', null);
|
||||
}
|
||||
|
||||
$query->where('users.public_id', '>', 0)
|
||||
->select('users.public_id', 'users.first_name', 'users.last_name', 'users.email', 'users.confirmed', 'users.public_id', 'users.deleted_at');
|
||||
|
||||
return Datatable::query($query)
|
||||
->addColumn('first_name', function ($model) { return link_to('users/'.$model->public_id.'/edit', $model->first_name.' '.$model->last_name); })
|
||||
->addColumn('email', function ($model) { return $model->email; })
|
||||
->addColumn('confirmed', function ($model) { return $model->deleted_at ? trans('texts.deleted') : ($model->confirmed ? trans('texts.active') : trans('texts.pending')); })
|
||||
->addColumn('dropdown', function ($model) {
|
||||
$actions = '<div class="btn-group tr-action" style="visibility:hidden;">
|
||||
<button type="button" class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
'.trans('texts.select').' <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" role="menu">';
|
||||
|
||||
if ($model->deleted_at) {
|
||||
$actions .= '<li><a href="'.URL::to('restore_user/'.$model->public_id).'">'.uctrans('texts.restore_user').'</a></li>';
|
||||
} else {
|
||||
$actions .= '<li><a href="'.URL::to('users/'.$model->public_id).'/edit">'.uctrans('texts.edit_user').'</a></li>';
|
||||
|
||||
if (!$model->confirmed) {
|
||||
$actions .= '<li><a href="'.URL::to('send_confirmation/'.$model->public_id).'">'.uctrans('texts.send_invite').'</a></li>';
|
||||
}
|
||||
|
||||
$actions .= '<li class="divider"></li>
|
||||
<li><a href="javascript:deleteUser('.$model->public_id.')">'.uctrans('texts.delete_user').'</a></li>';
|
||||
}
|
||||
|
||||
$actions .= '</ul>
|
||||
</div>';
|
||||
|
||||
return $actions;
|
||||
})
|
||||
->orderColumns(['first_name', 'email', 'confirmed'])
|
||||
->make();
|
||||
return $this->userService->getDatatable(Auth::user()->account_id);
|
||||
}
|
||||
|
||||
public function setTheme()
|
||||
@ -106,7 +74,6 @@ class UserController extends BaseController
|
||||
->where('public_id', '=', $publicId)->firstOrFail();
|
||||
|
||||
$data = [
|
||||
'showBreadcrumbs' => false,
|
||||
'user' => $user,
|
||||
'method' => 'PUT',
|
||||
'url' => 'users/'.$publicId,
|
||||
@ -134,24 +101,22 @@ class UserController extends BaseController
|
||||
{
|
||||
if (!Auth::user()->registered) {
|
||||
Session::flash('error', trans('texts.register_to_add_user'));
|
||||
return Redirect::to('company/advanced_settings/user_management');
|
||||
return Redirect::to('settings/' . ACCOUNT_USER_MANAGEMENT);
|
||||
}
|
||||
if (!Auth::user()->confirmed) {
|
||||
Session::flash('error', trans('texts.confirmation_required'));
|
||||
return Redirect::to('company/advanced_settings/user_management');
|
||||
return Redirect::to('settings/' . ACCOUNT_USER_MANAGEMENT);
|
||||
}
|
||||
|
||||
if (Utils::isNinja()) {
|
||||
$count = User::where('account_id', '=', Auth::user()->account_id)->count();
|
||||
if ($count >= MAX_NUM_USERS) {
|
||||
Session::flash('error', trans('texts.limit_users'));
|
||||
|
||||
return Redirect::to('company/advanced_settings/user_management');
|
||||
return Redirect::to('settings/' . ACCOUNT_USER_MANAGEMENT);
|
||||
}
|
||||
}
|
||||
|
||||
$data = [
|
||||
'showBreadcrumbs' => false,
|
||||
'user' => null,
|
||||
'method' => 'POST',
|
||||
'url' => 'users',
|
||||
@ -161,17 +126,25 @@ class UserController extends BaseController
|
||||
return View::make('users.edit', $data);
|
||||
}
|
||||
|
||||
public function delete()
|
||||
public function bulk()
|
||||
{
|
||||
$userPublicId = Input::get('userPublicId');
|
||||
$action = Input::get('bulk_action');
|
||||
$id = Input::get('bulk_public_id');
|
||||
|
||||
$user = User::where('account_id', '=', Auth::user()->account_id)
|
||||
->where('public_id', '=', $userPublicId)->firstOrFail();
|
||||
->where('public_id', '=', $id)
|
||||
->withTrashed()
|
||||
->firstOrFail();
|
||||
|
||||
if ($action === 'archive') {
|
||||
$user->delete();
|
||||
} else {
|
||||
$user->restore();
|
||||
}
|
||||
|
||||
Session::flash('message', trans('texts.deleted_user'));
|
||||
Session::flash('message', trans("texts.{$action}d_user"));
|
||||
|
||||
return Redirect::to('company/advanced_settings/user_management');
|
||||
return Redirect::to('settings/' . ACCOUNT_USER_MANAGEMENT);
|
||||
}
|
||||
|
||||
public function restoreUser($userPublicId)
|
||||
@ -184,7 +157,7 @@ class UserController extends BaseController
|
||||
|
||||
Session::flash('message', trans('texts.restored_user'));
|
||||
|
||||
return Redirect::to('company/advanced_settings/user_management');
|
||||
return Redirect::to('settings/' . ACCOUNT_USER_MANAGEMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -247,7 +220,7 @@ class UserController extends BaseController
|
||||
Session::flash('message', $message);
|
||||
}
|
||||
|
||||
return Redirect::to('company/advanced_settings/user_management');
|
||||
return Redirect::to('settings/' . ACCOUNT_USER_MANAGEMENT);
|
||||
}
|
||||
|
||||
public function sendConfirmation($userPublicId)
|
||||
@ -258,7 +231,7 @@ class UserController extends BaseController
|
||||
$this->userMailer->sendConfirmation($user, Auth::user());
|
||||
Session::flash('message', trans('texts.sent_invite'));
|
||||
|
||||
return Redirect::to('company/advanced_settings/user_management');
|
||||
return Redirect::to('settings/' . ACCOUNT_USER_MANAGEMENT);
|
||||
}
|
||||
|
||||
|
||||
@ -384,4 +357,5 @@ class UserController extends BaseController
|
||||
{
|
||||
return View::make('users.account_management');
|
||||
}
|
||||
|
||||
}
|
||||
|
94
app/Http/Controllers/VendorApiController.php
Normal file
94
app/Http/Controllers/VendorApiController.php
Normal file
@ -0,0 +1,94 @@
|
||||
<?php namespace App\Http\Controllers;
|
||||
// vendor
|
||||
use Utils;
|
||||
use Response;
|
||||
use Input;
|
||||
use Auth;
|
||||
use App\Models\Vendor;
|
||||
use App\Ninja\Repositories\VendorRepository;
|
||||
use App\Http\Requests\CreateVendorRequest;
|
||||
use App\Http\Controllers\BaseAPIController;
|
||||
use App\Ninja\Transformers\VendorTransformer;
|
||||
|
||||
class VendorApiController extends BaseAPIController
|
||||
{
|
||||
protected $vendorRepo;
|
||||
|
||||
public function __construct(VendorRepository $vendorRepo)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->vendorRepo = $vendorRepo;
|
||||
}
|
||||
|
||||
public function ping()
|
||||
{
|
||||
$headers = Utils::getApiHeaders();
|
||||
|
||||
return Response::make('', 200, $headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @SWG\Get(
|
||||
* path="/vendors",
|
||||
* summary="List of vendors",
|
||||
* tags={"vendor"},
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="A list with vendors",
|
||||
* @SWG\Schema(type="array", @SWG\Items(ref="#/definitions/Vendor"))
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response="default",
|
||||
* description="an ""unexpected"" error"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$vendors = Vendor::scope()
|
||||
->with($this->getIncluded())
|
||||
->orderBy('created_at', 'desc')
|
||||
->paginate();
|
||||
|
||||
$transformer = new VendorTransformer(Auth::user()->account, Input::get('serializer'));
|
||||
$paginator = Vendor::scope()->paginate();
|
||||
$data = $this->createCollection($vendors, $transformer, ENTITY_VENDOR, $paginator);
|
||||
|
||||
return $this->response($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @SWG\Post(
|
||||
* path="/vendors",
|
||||
* tags={"vendor"},
|
||||
* summary="Create a vendor",
|
||||
* @SWG\Parameter(
|
||||
* in="body",
|
||||
* name="body",
|
||||
* @SWG\Schema(ref="#/definitions/Vendor")
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="New vendor",
|
||||
* @SWG\Schema(type="object", @SWG\Items(ref="#/definitions/Vendor"))
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response="default",
|
||||
* description="an ""unexpected"" error"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function store(CreateVendorRequest $request)
|
||||
{
|
||||
$vendor = $this->vendorRepo->save($request->input());
|
||||
|
||||
$vendor = Vendor::scope($vendor->public_id)
|
||||
->with('country', 'vendorcontacts', 'industry', 'size', 'currency')
|
||||
->first();
|
||||
|
||||
$transformer = new VendorTransformer(Auth::user()->account, Input::get('serializer'));
|
||||
$data = $this->createItem($vendor, $transformer, ENTITY_VENDOR);
|
||||
return $this->response($data);
|
||||
}
|
||||
}
|
204
app/Http/Controllers/VendorController.php
Normal file
204
app/Http/Controllers/VendorController.php
Normal file
@ -0,0 +1,204 @@
|
||||
<?php namespace App\Http\Controllers;
|
||||
|
||||
use Auth;
|
||||
use Datatable;
|
||||
use Utils;
|
||||
use View;
|
||||
use URL;
|
||||
use Validator;
|
||||
use Input;
|
||||
use Session;
|
||||
use Redirect;
|
||||
use Cache;
|
||||
|
||||
use App\Models\Activity;
|
||||
use App\Models\Vendor;
|
||||
use App\Models\Account;
|
||||
use App\Models\VendorContact;
|
||||
use App\Models\Size;
|
||||
use App\Models\PaymentTerm;
|
||||
use App\Models\Industry;
|
||||
use App\Models\Currency;
|
||||
use App\Models\Country;
|
||||
use App\Ninja\Repositories\VendorRepository;
|
||||
use App\Services\VendorService;
|
||||
|
||||
use App\Http\Requests\CreateVendorRequest;
|
||||
use App\Http\Requests\UpdateVendorRequest;
|
||||
// vendor
|
||||
class VendorController extends BaseController
|
||||
{
|
||||
protected $vendorService;
|
||||
protected $vendorRepo;
|
||||
|
||||
public function __construct(VendorRepository $vendorRepo, VendorService $vendorService)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->vendorRepo = $vendorRepo;
|
||||
$this->vendorService = $vendorService;
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
return View::make('list', array(
|
||||
'entityType' => 'vendor',
|
||||
'title' => trans('texts.vendors'),
|
||||
'sortCol' => '4',
|
||||
'columns' => Utils::trans([
|
||||
'checkbox',
|
||||
'vendor',
|
||||
'contact',
|
||||
'email',
|
||||
'date_created',
|
||||
''
|
||||
]),
|
||||
));
|
||||
}
|
||||
|
||||
public function getDatatable()
|
||||
{
|
||||
return $this->vendorService->getDatatable(Input::get('sSearch'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function store(CreateVendorRequest $request)
|
||||
{
|
||||
$vendor = $this->vendorService->save($request->input());
|
||||
|
||||
Session::flash('message', trans('texts.created_vendor'));
|
||||
|
||||
return redirect()->to($vendor->getRoute());
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param int $id
|
||||
* @return Response
|
||||
*/
|
||||
public function show($publicId)
|
||||
{
|
||||
$vendor = Vendor::withTrashed()->scope($publicId)->with('vendorcontacts', 'size', 'industry')->firstOrFail();
|
||||
Utils::trackViewed($vendor->getDisplayName(), 'vendor');
|
||||
|
||||
$actionLinks = [
|
||||
['label' => trans('texts.new_vendor'), 'url' => '/vendors/create/' . $vendor->public_id]
|
||||
];
|
||||
|
||||
$data = array(
|
||||
'actionLinks' => $actionLinks,
|
||||
'showBreadcrumbs' => false,
|
||||
'vendor' => $vendor,
|
||||
'totalexpense' => $vendor->getTotalExpense(),
|
||||
'title' => trans('texts.view_vendor'),
|
||||
'hasRecurringInvoices' => false,
|
||||
'hasQuotes' => false,
|
||||
'hasTasks' => false,
|
||||
);
|
||||
|
||||
return View::make('vendors.show', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
if (Vendor::scope()->count() > Auth::user()->getMaxNumVendors()) {
|
||||
return View::make('error', ['hideHeader' => true, 'error' => "Sorry, you've exceeded the limit of ".Auth::user()->getMaxNumVendors()." vendors"]);
|
||||
}
|
||||
|
||||
$data = [
|
||||
'vendor' => null,
|
||||
'method' => 'POST',
|
||||
'url' => 'vendors',
|
||||
'title' => trans('texts.new_vendor'),
|
||||
];
|
||||
|
||||
$data = array_merge($data, self::getViewModel());
|
||||
|
||||
return View::make('vendors.edit', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*
|
||||
* @param int $id
|
||||
* @return Response
|
||||
*/
|
||||
public function edit($publicId)
|
||||
{
|
||||
$vendor = Vendor::scope($publicId)->with('vendorcontacts')->firstOrFail();
|
||||
$data = [
|
||||
'vendor' => $vendor,
|
||||
'method' => 'PUT',
|
||||
'url' => 'vendors/'.$publicId,
|
||||
'title' => trans('texts.edit_vendor'),
|
||||
];
|
||||
|
||||
$data = array_merge($data, self::getViewModel());
|
||||
|
||||
if (Auth::user()->account->isNinjaAccount()) {
|
||||
if ($account = Account::whereId($vendor->public_id)->first()) {
|
||||
$data['proPlanPaid'] = $account['pro_plan_paid'];
|
||||
}
|
||||
}
|
||||
|
||||
return View::make('vendors.edit', $data);
|
||||
}
|
||||
|
||||
private static function getViewModel()
|
||||
{
|
||||
return [
|
||||
'data' => Input::old('data'),
|
||||
'account' => Auth::user()->account,
|
||||
'currencies' => Cache::get('currencies'),
|
||||
'countries' => Cache::get('countries'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param int $id
|
||||
* @return Response
|
||||
*/
|
||||
public function update(UpdateVendorRequest $request)
|
||||
{
|
||||
$vendor = $this->vendorService->save($request->input());
|
||||
|
||||
Session::flash('message', trans('texts.updated_vendor'));
|
||||
|
||||
return redirect()->to($vendor->getRoute());
|
||||
}
|
||||
|
||||
public function bulk()
|
||||
{
|
||||
$action = Input::get('action');
|
||||
$ids = Input::get('public_id') ? Input::get('public_id') : Input::get('ids');
|
||||
$count = $this->vendorService->bulk($ids, $action);
|
||||
|
||||
$message = Utils::pluralize($action.'d_vendor', $count);
|
||||
Session::flash('message', $message);
|
||||
|
||||
if ($action == 'restore' && $count == 1) {
|
||||
return Redirect::to('vendors/' . Utils::getFirst($ids));
|
||||
} else {
|
||||
return Redirect::to('vendors');
|
||||
}
|
||||
}
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
<?php namespace InvoiceNinja\Http\Controllers;
|
||||
|
||||
class HomeController extends Controller {
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Home Controller
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This controller renders your application's "dashboard" for users that
|
||||
| are authenticated. Of course, you are free to change or remove the
|
||||
| controller as you wish. It is just here to get your app started!
|
||||
|
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('auth');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the application dashboard to the user.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
return view('home');
|
||||
}
|
||||
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
<?php namespace InvoiceNinja\Http\Controllers;
|
||||
|
||||
class WelcomeController extends Controller {
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Welcome Controller
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This controller renders the "marketing page" for the application and
|
||||
| is configured to only allow guests. Like most of the other sample
|
||||
| controllers, you are free to modify or remove it as you desire.
|
||||
|
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('guest');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the application welcome screen to the user.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
return view('welcome');
|
||||
}
|
||||
|
||||
}
|
@ -21,8 +21,12 @@ class ApiCheck {
|
||||
*/
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
$loggingIn = $request->is('api/v1/login');
|
||||
$headers = Utils::getApiHeaders();
|
||||
|
||||
if ($loggingIn) {
|
||||
// do nothing
|
||||
} else {
|
||||
// check for a valid token
|
||||
$token = AccountToken::where('token', '=', Request::header('X-Ninja-Token'))->first(['id', 'user_id']);
|
||||
|
||||
@ -31,23 +35,24 @@ class ApiCheck {
|
||||
Session::set('token_id', $token->id);
|
||||
} else {
|
||||
sleep(3);
|
||||
return Response::make('Invalid token', 403, $headers);
|
||||
return Response::json('Invalid token', 403, $headers);
|
||||
}
|
||||
}
|
||||
|
||||
if (!Utils::isNinja()) {
|
||||
if (!Utils::isNinja() && !$loggingIn) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
if (!Utils::isPro()) {
|
||||
return Response::make('API requires pro plan', 403, $headers);
|
||||
if (!Utils::isPro() && !$loggingIn) {
|
||||
return Response::json('API requires pro plan', 403, $headers);
|
||||
} else {
|
||||
$accountId = Auth::user()->account->id;
|
||||
$key = Auth::check() ? Auth::user()->account->id : $request->getClientIp();
|
||||
|
||||
// http://stackoverflow.com/questions/1375501/how-do-i-throttle-my-sites-api-users
|
||||
$hour = 60 * 60;
|
||||
$hour_limit = 100; # users are limited to 100 requests/hour
|
||||
$hour_throttle = Cache::get("hour_throttle:{$accountId}", null);
|
||||
$last_api_request = Cache::get("last_api_request:{$accountId}", 0);
|
||||
$hour_throttle = Cache::get("hour_throttle:{$key}", null);
|
||||
$last_api_request = Cache::get("last_api_request:{$key}", 0);
|
||||
$last_api_diff = time() - $last_api_request;
|
||||
|
||||
if (is_null($hour_throttle)) {
|
||||
@ -63,14 +68,13 @@ class ApiCheck {
|
||||
if ($new_hour_throttle > $hour) {
|
||||
$wait = ceil($new_hour_throttle - $hour);
|
||||
sleep(1);
|
||||
return Response::make("Please wait {$wait} second(s)", 403, $headers);
|
||||
return Response::json("Please wait {$wait} second(s)", 403, $headers);
|
||||
}
|
||||
|
||||
Cache::put("hour_throttle:{$accountId}", $new_hour_throttle, 10);
|
||||
Cache::put("last_api_request:{$accountId}", time(), 10);
|
||||
Cache::put("hour_throttle:{$key}", $new_hour_throttle, 10);
|
||||
Cache::put("last_api_request:{$key}", time(), 10);
|
||||
}
|
||||
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ use Redirect;
|
||||
use Cache;
|
||||
use Session;
|
||||
use Event;
|
||||
use Schema;
|
||||
use App\Models\Language;
|
||||
use App\Models\InvoiceDesign;
|
||||
use App\Events\UserSettingsChanged;
|
||||
@ -25,11 +26,16 @@ class StartupCheck
|
||||
*/
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
// Ensure all request are over HTTPS in production
|
||||
if (App::environment() == ENV_PRODUCTION) {
|
||||
if (!Request::secure()) {
|
||||
return Redirect::secure(Request::getRequestUri());
|
||||
// Set up trusted X-Forwarded-Proto proxies
|
||||
// TRUSTED_PROXIES accepts a comma delimited list of subnets
|
||||
// ie, TRUSTED_PROXIES='10.0.0.0/8,172.16.0.0/12,192.168.0.0/16'
|
||||
if (isset($_ENV['TRUSTED_PROXIES'])) {
|
||||
Request::setTrustedProxies(array_map('trim', explode(',', env('TRUSTED_PROXIES'))));
|
||||
}
|
||||
|
||||
// Ensure all request are over HTTPS in production
|
||||
if (Utils::requireHTTPS() && !Request::secure()) {
|
||||
return Redirect::secure(Request::path());
|
||||
}
|
||||
|
||||
// If the database doens't yet exist we'll skip the rest
|
||||
@ -37,40 +43,19 @@ class StartupCheck
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
// Check data has been cached
|
||||
$cachedTables = [
|
||||
'currencies' => 'App\Models\Currency',
|
||||
'sizes' => 'App\Models\Size',
|
||||
'industries' => 'App\Models\Industry',
|
||||
'timezones' => 'App\Models\Timezone',
|
||||
'dateFormats' => 'App\Models\DateFormat',
|
||||
'datetimeFormats' => 'App\Models\DatetimeFormat',
|
||||
'languages' => 'App\Models\Language',
|
||||
'paymentTerms' => 'App\Models\PaymentTerm',
|
||||
'paymentTypes' => 'App\Models\PaymentType',
|
||||
'countries' => 'App\Models\Country',
|
||||
'invoiceDesigns' => 'App\Models\InvoiceDesign',
|
||||
];
|
||||
foreach ($cachedTables as $name => $class) {
|
||||
if (Input::has('clear_cache')) {
|
||||
Session::flash('message', 'Cache cleared');
|
||||
}
|
||||
if (Input::has('clear_cache') || !Cache::has($name)) {
|
||||
if ($name == 'paymentTerms') {
|
||||
$orderBy = 'num_days';
|
||||
} elseif (in_array($name, ['currencies', 'sizes', 'industries', 'languages', 'countries'])) {
|
||||
$orderBy = 'name';
|
||||
} else {
|
||||
$orderBy = 'id';
|
||||
}
|
||||
$tableData = $class::orderBy($orderBy)->get();
|
||||
if (count($tableData)) {
|
||||
Cache::forever($name, $tableData);
|
||||
}
|
||||
// Check if a new version was installed
|
||||
if (!Utils::isNinja()) {
|
||||
$file = storage_path() . '/version.txt';
|
||||
$version = @file_get_contents($file);
|
||||
if ($version != NINJA_VERSION) {
|
||||
$handle = fopen($file, 'w');
|
||||
fwrite($handle, NINJA_VERSION);
|
||||
fclose($handle);
|
||||
return Redirect::to('/update');
|
||||
}
|
||||
}
|
||||
|
||||
// check the application is up to date and for any news feed messages
|
||||
// Check the application is up to date and for any news feed messages
|
||||
if (Auth::check()) {
|
||||
$count = Session::get(SESSION_COUNTER, 0);
|
||||
Session::put(SESSION_COUNTER, ++$count);
|
||||
@ -91,11 +76,11 @@ class StartupCheck
|
||||
'releases_link' => link_to(RELEASES_URL, 'Invoice Ninja', ['target' => '_blank']),
|
||||
];
|
||||
Session::put('news_feed_id', NEW_VERSION_AVAILABLE);
|
||||
Session::put('news_feed_message', trans('texts.new_version_available', $params));
|
||||
Session::flash('news_feed_message', trans('texts.new_version_available', $params));
|
||||
} else {
|
||||
Session::put('news_feed_id', $data->id);
|
||||
if ($data->message && $data->id > Auth::user()->news_feed_id) {
|
||||
Session::put('news_feed_message', $data->message);
|
||||
Session::flash('news_feed_message', $data->message);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -118,8 +103,10 @@ class StartupCheck
|
||||
}
|
||||
}
|
||||
} elseif (Auth::check()) {
|
||||
$locale = Session::get(SESSION_LOCALE, DEFAULT_LOCALE);
|
||||
$locale = Auth::user()->account->language ? Auth::user()->account->language->locale : DEFAULT_LOCALE;
|
||||
App::setLocale($locale);
|
||||
} elseif (session(SESSION_LOCALE)) {
|
||||
App::setLocale(session(SESSION_LOCALE));
|
||||
}
|
||||
|
||||
// Make sure the account/user localization settings are in the session
|
||||
@ -142,10 +129,11 @@ class StartupCheck
|
||||
$design = new InvoiceDesign();
|
||||
$design->id = $item->id;
|
||||
$design->name = $item->name;
|
||||
$design->javascript = $item->javascript;
|
||||
$design->pdfmake = $item->pdfmake;
|
||||
$design->save();
|
||||
}
|
||||
|
||||
Cache::forget('invoiceDesigns');
|
||||
Session::flash('message', trans('texts.bought_designs'));
|
||||
}
|
||||
} elseif ($productId == PRODUCT_WHITE_LABEL) {
|
||||
@ -160,13 +148,40 @@ class StartupCheck
|
||||
}
|
||||
}
|
||||
|
||||
// Check data has been cached
|
||||
$cachedTables = unserialize(CACHED_TABLES);
|
||||
if (Input::has('clear_cache')) {
|
||||
Session::flash('message', 'Cache cleared');
|
||||
}
|
||||
foreach ($cachedTables as $name => $class) {
|
||||
if (Input::has('clear_cache') || !Cache::has($name)) {
|
||||
// check that the table exists in case the migration is pending
|
||||
if ( ! Schema::hasTable((new $class)->getTable())) {
|
||||
continue;
|
||||
}
|
||||
if ($name == 'paymentTerms') {
|
||||
$orderBy = 'num_days';
|
||||
} elseif ($name == 'fonts') {
|
||||
$orderBy = 'sort_order';
|
||||
} elseif (in_array($name, ['currencies', 'industries', 'languages', 'countries', 'banks'])) {
|
||||
$orderBy = 'name';
|
||||
} else {
|
||||
$orderBy = 'id';
|
||||
}
|
||||
$tableData = $class::orderBy($orderBy)->get();
|
||||
if (count($tableData)) {
|
||||
Cache::forever($name, $tableData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Show message to IE 8 and before users
|
||||
if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match('/(?i)msie [2-8]/', $_SERVER['HTTP_USER_AGENT'])) {
|
||||
Session::flash('error', trans('texts.old_browser'));
|
||||
}
|
||||
|
||||
// for security prevent displaying within an iframe
|
||||
$response = $next($request);
|
||||
$response->headers->set('X-Frame-Options', 'DENY');
|
||||
//$response->headers->set('X-Frame-Options', 'DENY');
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
@ -7,12 +7,17 @@ class VerifyCsrfToken extends BaseVerifier {
|
||||
|
||||
private $openRoutes = [
|
||||
'signup/register',
|
||||
'api/v1/login',
|
||||
'api/v1/clients',
|
||||
'api/v1/invoices/*',
|
||||
'api/v1/invoices',
|
||||
'api/v1/quotes',
|
||||
'api/v1/payments',
|
||||
'api/v1/tasks',
|
||||
'api/v1/email_invoice',
|
||||
'api/v1/hooks',
|
||||
'hook/email_opened',
|
||||
'hook/email_bounced',
|
||||
];
|
||||
|
||||
/**
|
||||
|
46
app/Http/Requests/CreateClientRequest.php
Normal file
46
app/Http/Requests/CreateClientRequest.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php namespace app\Http\Requests;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use Illuminate\Validation\Factory;
|
||||
|
||||
class CreateClientRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'contacts' => 'valid_contacts',
|
||||
];
|
||||
}
|
||||
|
||||
public function validator($factory)
|
||||
{
|
||||
// support submiting the form with a single contact record
|
||||
$input = $this->input();
|
||||
if (isset($input['contact'])) {
|
||||
$input['contacts'] = [$input['contact']];
|
||||
unset($input['contact']);
|
||||
$this->replace($input);
|
||||
}
|
||||
|
||||
return $factory->make(
|
||||
$this->input(),
|
||||
$this->container->call([$this, 'rules']),
|
||||
$this->messages()
|
||||
);
|
||||
}
|
||||
}
|
30
app/Http/Requests/CreateCreditRequest.php
Normal file
30
app/Http/Requests/CreateCreditRequest.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php namespace app\Http\Requests;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use Illuminate\Validation\Factory;
|
||||
|
||||
class CreateCreditRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'client' => 'required',
|
||||
'amount' => 'required|positive',
|
||||
];
|
||||
}
|
||||
}
|
30
app/Http/Requests/CreateExpenseRequest.php
Normal file
30
app/Http/Requests/CreateExpenseRequest.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php namespace app\Http\Requests;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use Illuminate\Validation\Factory;
|
||||
|
||||
class CreateExpenseRequest extends Request
|
||||
{
|
||||
// Expenses
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'amount' => 'positive',
|
||||
];
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user