mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-09-19 16:01:34 +02:00
Add Files
This commit is contained in:
parent
77922893d2
commit
04c392136e
266
app/Console/Commands/CheckData.php
Normal file
266
app/Console/Commands/CheckData.php
Normal file
@ -0,0 +1,266 @@
|
||||
<?php namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
|
||||
/*
|
||||
|
||||
##################################################################
|
||||
WARNING: Please backup your database before running this script
|
||||
##################################################################
|
||||
|
||||
Since the application was released a number of bugs have (inevitably) been found.
|
||||
Although the bugs have always been fixed in some cases they've caused the client's
|
||||
balance, paid to date and/or activity records to become inaccurate. This script will
|
||||
check for errors and correct the data.
|
||||
|
||||
If you have any questions please email us at contact@invoiceninja.com
|
||||
|
||||
Usage:
|
||||
|
||||
php artisan ninja:check-data
|
||||
|
||||
Options:
|
||||
|
||||
--client_id:<value>
|
||||
|
||||
Limits the script to a single client
|
||||
|
||||
--fix=true
|
||||
|
||||
By default the script only checks for errors, adding this option
|
||||
makes the script apply the fixes.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
class CheckData extends Command {
|
||||
|
||||
protected $name = 'ninja:check-data';
|
||||
protected $description = 'Check/fix data';
|
||||
|
||||
public function fire()
|
||||
{
|
||||
$this->info(date('Y-m-d') . ' Running CheckData...');
|
||||
$today = new DateTime();
|
||||
|
||||
if (!$this->option('client_id')) {
|
||||
// update client deletion activities with the client's current balance
|
||||
$activities = DB::table('activities')
|
||||
->join('clients', 'clients.id', '=', 'activities.client_id')
|
||||
->where('activities.activity_type_id', '=', ACTIVITY_TYPE_DELETE_CLIENT)
|
||||
->where('activities.balance', '=', 0)
|
||||
->where('clients.balance', '!=', 0)
|
||||
->get(['activities.id', 'clients.balance']);
|
||||
|
||||
$this->info(count($activities) . ' delete client activities with zero balance');
|
||||
|
||||
if ($this->option('fix') == 'true') {
|
||||
foreach ($activities as $activity) {
|
||||
DB::table('activities')
|
||||
->where('id', $activity->id)
|
||||
->update(['balance' => $activity->balance]);
|
||||
}
|
||||
}
|
||||
|
||||
// update client paid_to_date value
|
||||
$clients = DB::table('clients')
|
||||
->join('payments', 'payments.client_id', '=', 'clients.id')
|
||||
->join('invoices', 'invoices.id', '=', 'payments.invoice_id')
|
||||
->where('payments.is_deleted', '=', 0)
|
||||
->where('invoices.is_deleted', '=', 0)
|
||||
->groupBy('clients.id')
|
||||
->havingRaw('clients.paid_to_date != sum(payments.amount) and clients.paid_to_date != 999999999.9999')
|
||||
->get(['clients.id', 'clients.paid_to_date', DB::raw('sum(payments.amount) as amount')]);
|
||||
$this->info(count($clients) . ' clients with incorrect paid to date');
|
||||
|
||||
if ($this->option('fix') == 'true') {
|
||||
foreach ($clients as $client) {
|
||||
DB::table('clients')
|
||||
->where('id', $client->id)
|
||||
->update(['paid_to_date' => $client->amount]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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')
|
||||
->join('accounts', 'accounts.id', '=', 'clients.account_id');
|
||||
|
||||
if ($this->option('client_id')) {
|
||||
$clients->where('clients.id', '=', $this->option('client_id'));
|
||||
} else {
|
||||
$clients->where('invoices.is_deleted', '=', 0)
|
||||
->where('invoices.is_quote', '=', 0)
|
||||
->where('invoices.is_recurring', '=', 0)
|
||||
->havingRaw('abs(clients.balance - sum(invoices.balance)) > .01 and clients.balance != 999999999.9999');
|
||||
}
|
||||
|
||||
$clients = $clients->groupBy('clients.id', 'clients.balance', 'clients.created_at')
|
||||
->orderBy('clients.id', 'DESC')
|
||||
->get(['clients.id', 'clients.balance', 'clients.paid_to_date']);
|
||||
$this->info(count($clients) . ' clients with incorrect balance/activities');
|
||||
|
||||
foreach ($clients as $client) {
|
||||
$this->info("=== Client:{$client->id} Balance:{$client->balance} ===");
|
||||
$foundProblem = false;
|
||||
$lastBalance = 0;
|
||||
$clientFix = false;
|
||||
$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']);
|
||||
//$this->info(var_dump($activities));
|
||||
|
||||
foreach ($activities as $activity) {
|
||||
|
||||
$activityFix = false;
|
||||
|
||||
if ($activity->invoice_id) {
|
||||
$invoice = DB::table('invoices')
|
||||
->where('id', '=', $activity->invoice_id)
|
||||
->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')
|
||||
->where('recurring_invoice_id', '=', $activity->invoice_id)
|
||||
->first(['invoices.id'])) {
|
||||
$invoice->is_recurring = 1;
|
||||
|
||||
// **Fix for enabling a recurring invoice to be set as non-recurring**
|
||||
if ($this->option('fix') == 'true') {
|
||||
DB::table('invoices')
|
||||
->where('id', $invoice->id)
|
||||
->update(['is_recurring' => 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ($activity->activity_type_id == ACTIVITY_TYPE_CREATE_INVOICE
|
||||
|| $activity->activity_type_id == ACTIVITY_TYPE_CREATE_QUOTE) {
|
||||
|
||||
// Get original invoice amount
|
||||
$update = DB::table('activities')
|
||||
->where('invoice_id', '=', $activity->invoice_id)
|
||||
->where('activity_type_id', '=', ACTIVITY_TYPE_UPDATE_INVOICE)
|
||||
->orderBy('id')
|
||||
->first(['json_backup']);
|
||||
if ($update) {
|
||||
$backup = json_decode($update->json_backup);
|
||||
$invoice->amount = floatval($backup->amount);
|
||||
}
|
||||
|
||||
$noAdjustment = $activity->activity_type_id == ACTIVITY_TYPE_CREATE_INVOICE
|
||||
&& $activity->adjustment == 0
|
||||
&& $invoice->amount > 0;
|
||||
|
||||
// **Fix for allowing converting a recurring invoice to a normal one without updating the balance**
|
||||
if ($noAdjustment && !$invoice->is_quote && !$invoice->is_recurring) {
|
||||
$this->info("No adjustment for new invoice:{$activity->invoice_id} amount:{$invoice->amount} isQuote:{$invoice->is_quote} isRecurring:{$invoice->is_recurring}");
|
||||
$foundProblem = true;
|
||||
$clientFix += $invoice->amount;
|
||||
$activityFix = $invoice->amount;
|
||||
// **Fix for updating balance when creating a quote or recurring invoice**
|
||||
} elseif ($activity->adjustment != 0 && ($invoice->is_quote || $invoice->is_recurring)) {
|
||||
$this->info("Incorrect adjustment for new invoice:{$activity->invoice_id} adjustment:{$activity->adjustment} isQuote:{$invoice->is_quote} isRecurring:{$invoice->is_recurring}");
|
||||
$foundProblem = true;
|
||||
$clientFix -= $activity->adjustment;
|
||||
$activityFix = 0;
|
||||
}
|
||||
} elseif ($activity->activity_type_id == ACTIVITY_TYPE_DELETE_INVOICE) {
|
||||
// **Fix for updating balance when deleting a recurring invoice**
|
||||
if ($activity->adjustment != 0 && $invoice->is_recurring) {
|
||||
$this->info("Incorrect adjustment for deleted invoice adjustment:{$activity->adjustment}");
|
||||
$foundProblem = true;
|
||||
if ($activity->balance != $lastBalance) {
|
||||
$clientFix -= $activity->adjustment;
|
||||
}
|
||||
$activityFix = 0;
|
||||
}
|
||||
} elseif ($activity->activity_type_id == ACTIVITY_TYPE_ARCHIVE_INVOICE) {
|
||||
// **Fix for updating balance when archiving an invoice**
|
||||
if ($activity->adjustment != 0 && !$invoice->is_recurring) {
|
||||
$this->info("Incorrect adjustment for archiving invoice adjustment:{$activity->adjustment}");
|
||||
$foundProblem = true;
|
||||
$activityFix = 0;
|
||||
$clientFix += $activity->adjustment;
|
||||
}
|
||||
} elseif ($activity->activity_type_id == ACTIVITY_TYPE_UPDATE_INVOICE) {
|
||||
// **Fix for updating balance when updating recurring invoice**
|
||||
if ($activity->adjustment != 0 && $invoice->is_recurring) {
|
||||
$this->info("Incorrect adjustment for updated recurring invoice adjustment:{$activity->adjustment}");
|
||||
$foundProblem = true;
|
||||
$clientFix -= $activity->adjustment;
|
||||
$activityFix = 0;
|
||||
}
|
||||
} elseif ($activity->activity_type_id == ACTIVITY_TYPE_UPDATE_QUOTE) {
|
||||
// **Fix for updating balance when updating a quote**
|
||||
if ($activity->balance != $lastBalance) {
|
||||
$this->info("Incorrect adjustment for updated quote adjustment:{$activity->adjustment}");
|
||||
$foundProblem = true;
|
||||
$clientFix += $lastBalance - $activity->balance;
|
||||
$activityFix = 0;
|
||||
}
|
||||
} else if ($activity->activity_type_id == ACTIVITY_TYPE_DELETE_PAYMENT) {
|
||||
// **Fix for delting 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;
|
||||
$activityFix = 0;
|
||||
$clientFix -= $activity->adjustment;
|
||||
}
|
||||
}
|
||||
|
||||
if ($activityFix !== false || $clientFix !== false) {
|
||||
$data = [
|
||||
'balance' => $activity->balance + $clientFix
|
||||
];
|
||||
|
||||
if ($activityFix !== false) {
|
||||
$data['adjustment'] = $activityFix;
|
||||
}
|
||||
|
||||
if ($this->option('fix') == 'true') {
|
||||
DB::table('activities')
|
||||
->where('id', $activity->id)
|
||||
->update($data);
|
||||
}
|
||||
}
|
||||
|
||||
$lastBalance = $activity->balance;
|
||||
}
|
||||
|
||||
if ($clientFix !== false) {
|
||||
$balance = $activity->balance + $clientFix;
|
||||
$data = ['balance' => $balance];
|
||||
$this->info("Corrected balance:{$balance}");
|
||||
if ($this->option('fix') == 'true') {
|
||||
DB::table('clients')
|
||||
->where('id', $client->id)
|
||||
->update($data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->info('Done');
|
||||
}
|
||||
|
||||
protected function getArguments()
|
||||
{
|
||||
return array(
|
||||
//array('example', InputArgument::REQUIRED, 'An example argument.'),
|
||||
);
|
||||
}
|
||||
|
||||
protected function getOptions()
|
||||
{
|
||||
return array(
|
||||
array('fix', null, InputOption::VALUE_OPTIONAL, 'Fix data', null),
|
||||
array('client_id', null, InputOption::VALUE_OPTIONAL, 'Client id', null),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
88
app/Console/Commands/CreateRandomData.php
Normal file
88
app/Console/Commands/CreateRandomData.php
Normal file
@ -0,0 +1,88 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
335
app/Console/Commands/ImportTimesheetData.php
Normal file
335
app/Console/Commands/ImportTimesheetData.php
Normal file
@ -0,0 +1,335 @@
|
||||
<?php namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use PHPBenchTime\Timer;
|
||||
|
||||
class ImportTimesheetData extends Command {
|
||||
|
||||
protected $name = 'ninja:import-timesheet-data';
|
||||
protected $description = 'Import timesheet data';
|
||||
|
||||
public function fire() {
|
||||
$this->info(date('Y-m-d') . ' Running ImportTimesheetData...');
|
||||
|
||||
// Seems we are using the console timezone
|
||||
DB::statement("SET SESSION time_zone = '+00:00'");
|
||||
|
||||
// Get the Unix epoch
|
||||
$unix_epoch = new DateTime('1970-01-01T00:00:01', new DateTimeZone("UTC"));
|
||||
|
||||
// Create some initial sources we can test with
|
||||
$user = User::first();
|
||||
if (!$user) {
|
||||
$this->error("Error: please create user account by logging in");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Populate with own test data until test data has been created
|
||||
// Truncate the tables
|
||||
/*$this->info("Truncate tables");
|
||||
DB::statement('SET FOREIGN_KEY_CHECKS=0;');
|
||||
DB::table('projects')->truncate();
|
||||
DB::table('project_codes')->truncate();
|
||||
DB::table('timesheet_event_sources')->truncate();
|
||||
DB::table('timesheet_events')->truncate();
|
||||
DB::statement('SET FOREIGN_KEY_CHECKS=1;'); */
|
||||
|
||||
if (!Project::find(1)) {
|
||||
$this->info("Import old project codes");
|
||||
$oldcodes = json_decode(file_get_contents("/home/tlb/git/itktime/codes.json"), true);
|
||||
foreach ($oldcodes as $name => $options) {
|
||||
$project = Project::createNew($user);
|
||||
$project->name = $options['description'];
|
||||
$project->save();
|
||||
|
||||
$code = ProjectCode::createNew($user);
|
||||
$code->name = $name;
|
||||
$project->codes()->save($code);
|
||||
}
|
||||
}
|
||||
|
||||
if (!TimesheetEventSource::find(1)) {
|
||||
$this->info("Import old event sources");
|
||||
|
||||
$oldevent_sources = json_decode(file_get_contents("/home/tlb/git/itktime/employes.json"), true);
|
||||
|
||||
foreach ($oldevent_sources as $source) {
|
||||
$event_source = TimesheetEventSource::createNew($user);
|
||||
$event_source->name = $source['name'];
|
||||
$event_source->url = $source['url'];
|
||||
$event_source->owner = $source['owner'];
|
||||
$event_source->type = 'ical';
|
||||
//$event_source->from_date = new DateTime("2009-01-01");
|
||||
$event_source->save();
|
||||
}
|
||||
}
|
||||
|
||||
// Add all URL's to Curl
|
||||
$this->info("Download ICAL feeds");
|
||||
$T = new Timer;
|
||||
$T->start();
|
||||
|
||||
$T->lap("Get Event Sources");
|
||||
$event_sources = TimesheetEventSource::all(); // TODO: Filter based on ical feeds
|
||||
|
||||
$T->lap("Get ICAL responses");
|
||||
$urls = [];
|
||||
$event_sources->map(function($item) use(&$urls) {
|
||||
$urls[] = $item->url;
|
||||
});
|
||||
$icalresponses = TimesheetUtils::curlGetUrls($urls);
|
||||
|
||||
$T->lap("Fetch all codes so we can do a quick lookup");
|
||||
$codes = array();
|
||||
ProjectCode::all()->map(function($item) use(&$codes) {
|
||||
$codes[$item->name] = $item;
|
||||
});
|
||||
|
||||
$this->info("Start parsing ICAL files");
|
||||
foreach ($event_sources as $i => $event_source) {
|
||||
if (!is_array($icalresponses[$i])) {
|
||||
$this->info("Find events in " . $event_source->name);
|
||||
file_put_contents("/tmp/" . $event_source->name . ".ical", $icalresponses[$i]); // FIXME: Remove
|
||||
$T->lap("Split on events for ".$event_source->name);
|
||||
|
||||
// Check if the file is complete
|
||||
if(!preg_match("/^\s*BEGIN:VCALENDAR/", $icalresponses[$i]) || !preg_match("/END:VCALENDAR\s*$/", $icalresponses[$i])) {
|
||||
$this->error("Missing start or end of ical file");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract all events from ical file
|
||||
if (preg_match_all('/BEGIN:VEVENT\r?\n(.+?)\r?\nEND:VEVENT/s', $icalresponses[$i], $icalmatches)) {
|
||||
$this->info("Found ".(count($icalmatches[1])-1)." events");
|
||||
$T->lap("Fetch all uids and last updated at so we can do a quick lookup to find out if the event needs to be updated in the database".$event_source->name);
|
||||
$uids = [];
|
||||
$org_deleted = []; // Create list of events we know are deleted on the source, but still have in the db
|
||||
$event_source->events()->withTrashed()->get(['uid', 'org_updated_at', 'updated_data_at', 'org_deleted_at'])->map(function($item) use(&$uids, &$org_deleted) {
|
||||
if($item->org_updated_at > $item->updated_data_at) {
|
||||
$uids[$item->uid] = $item->org_updated_at;
|
||||
} else {
|
||||
$uids[$item->uid] = $item->updated_data_at;
|
||||
}
|
||||
if($item->org_deleted_at > '0000-00-00 00:00:00') {
|
||||
$org_deleted[$item->uid] = $item->updated_data_at;
|
||||
}
|
||||
});
|
||||
$deleted = $uids;
|
||||
|
||||
// Loop over all the found events
|
||||
$T->lap("Parse events for ".$event_source->name);
|
||||
foreach ($icalmatches[1] as $eventstr) {
|
||||
//print "---\n";
|
||||
//print $eventstr."\n";
|
||||
//print "---\n";
|
||||
//$this->info("Match event");
|
||||
# Fix lines broken by 76 char limit
|
||||
$eventstr = preg_replace('/\r?\n\s/s', '', $eventstr);
|
||||
//$this->info("Parse data");
|
||||
$data = TimesheetUtils::parseICALEvent($eventstr);
|
||||
if ($data) {
|
||||
// Extract code for summary so we only import events we use
|
||||
list($codename, $tags, $title) = TimesheetUtils::parseEventSummary($data['summary']);
|
||||
if ($codename != null) {
|
||||
$event = TimesheetEvent::createNew($user);
|
||||
|
||||
// Copy data to new object
|
||||
$event->uid = $data['uid'];
|
||||
$event->summary = $title;
|
||||
$event->org_data = $eventstr;
|
||||
$event->org_code = $codename;
|
||||
if(isset($data['description'])) {
|
||||
$event->description = $data['description'];
|
||||
}
|
||||
$event->owner = $event_source->owner;
|
||||
$event->timesheet_event_source_id = $event_source->id;
|
||||
if (isset($codes[$codename])) {
|
||||
$event->project_id = $codes[$codename]->project_id;
|
||||
$event->project_code_id = $codes[$codename]->id;
|
||||
}
|
||||
if (isset($data['location'])) {
|
||||
$event->location = $data['location'];
|
||||
}
|
||||
|
||||
|
||||
# Add RECURRENCE-ID to the UID to make sure the event is unique
|
||||
if (isset($data['recurrence-id'])) {
|
||||
$event->uid .= "::".$data['recurrence-id'];
|
||||
}
|
||||
|
||||
//TODO: Add support for recurring event, make limit on number of events created : https://github.com/tplaner/When
|
||||
// Bail on RRULE as we don't support that
|
||||
if(isset($event['rrule'])) {
|
||||
die("Recurring event not supported: {$event['summary']} - {$event['dtstart']}");
|
||||
}
|
||||
|
||||
// Convert to DateTime objects
|
||||
foreach (['dtstart', 'dtend', 'created', 'last-modified'] as $key) {
|
||||
// Parse and create DataTime object from ICAL format
|
||||
list($dt, $timezone) = TimesheetUtils::parseICALDate($data[$key]);
|
||||
|
||||
// Handle bad dates in created and last-modified
|
||||
if ($dt == null || $dt < $unix_epoch) {
|
||||
if ($key == 'created' || $key == 'last-modified') {
|
||||
$dt = $unix_epoch; // Default to UNIX epoch
|
||||
$event->import_warning = "Could not parse date for $key: '" . $data[$key] . "' so default to UNIX Epoc\n";
|
||||
} else {
|
||||
$event->import_error = "Could not parse date for $key: '" . $data[$key] . "' so default to UNIX Epoc\n";
|
||||
// TODO: Bail on this event or write to error table
|
||||
die("Could not parse date for $key: '" . $data[$key] . "'\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Assign DateTime object to
|
||||
switch ($key) {
|
||||
case 'dtstart':
|
||||
$event->start_date = $dt;
|
||||
if($timezone) {
|
||||
$event->org_start_date_timezone = $timezone;
|
||||
}
|
||||
break;
|
||||
case 'dtend':
|
||||
$event->end_date = $dt;
|
||||
if($timezone) {
|
||||
$event->org_end_date_timezone = $timezone;
|
||||
}
|
||||
break;
|
||||
case 'created':
|
||||
$event->org_created_at = $dt;
|
||||
break;
|
||||
case 'last-modified':
|
||||
$event->org_updated_at = $dt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check that we are witin the range
|
||||
if ($event_source->from_date != null) {
|
||||
$from_date = new DateTime($event_source->from_date, new DateTimeZone('UTC'));
|
||||
if ($from_date > $event->end_date) {
|
||||
// Skip this event
|
||||
echo "Skiped: $codename: $title\n";
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate number of hours
|
||||
$di = $event->end_date->diff($event->start_date);
|
||||
$event->hours = $di->h + $di->i / 60;
|
||||
|
||||
// Check for events we already have
|
||||
if (isset($uids[$event->uid])) {
|
||||
// Remove from deleted list
|
||||
unset($deleted[$event->uid]);
|
||||
|
||||
// See if the event has been updated compared to the one in the database
|
||||
$db_event_org_updated_at = new DateTime($uids[$event->uid], new DateTimeZone('UTC'));
|
||||
|
||||
// Check if same or older version of new event then skip
|
||||
if($event->org_updated_at <= $db_event_org_updated_at) {
|
||||
// SKIP
|
||||
|
||||
// Updated version of the event
|
||||
} else {
|
||||
// Get the old event from the database
|
||||
/* @var $db_event TimesheetEvent */
|
||||
$db_event = $event_source->events()->where('uid', $event->uid)->firstOrFail();
|
||||
$changes = $db_event->toChangesArray($event);
|
||||
|
||||
// Make sure it's more than the org_updated_at that has been changed
|
||||
if (count($changes) > 1) {
|
||||
// Check if we have manually changed the event in the database or used it in a timesheet
|
||||
if ($db_event->manualedit || $db_event->timesheet) {
|
||||
$this->info("Updated Data");
|
||||
$db_event->updated_data = $event->org_data;
|
||||
$db_event->updated_data_at = $event->org_updated_at;
|
||||
|
||||
// Update the db_event with the changes
|
||||
} else {
|
||||
$this->info("Updated Event");
|
||||
foreach ($changes as $key => $value) {
|
||||
if($value == null) {
|
||||
unset($db_event->$key);
|
||||
} else {
|
||||
$db_event->$key = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
$this->info("Nothing Changed");
|
||||
// Nothing has been changed so update the org_updated_at
|
||||
$db_event->org_updated_at = $changes['org_updated_at'];
|
||||
}
|
||||
$db_event->save();
|
||||
}
|
||||
|
||||
} else {
|
||||
try {
|
||||
$this->info("New event: " . $event->summary);
|
||||
$event->save();
|
||||
|
||||
} catch (Exception $ex) {
|
||||
echo "'" . $event->summary . "'\n";
|
||||
var_dump($data);
|
||||
echo $ex->getMessage();
|
||||
echo $ex->getTraceAsString();
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
// Add new uid to know uids
|
||||
$uids[$event->uid] = $event->org_updated_at;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Delete events in database that no longer exists in the source
|
||||
foreach($deleted as $uid => $lastupdated_date) {
|
||||
// Skip we already marked this a deleted
|
||||
if(isset($org_deleted[$uid])) {
|
||||
unset($deleted[$uid]);
|
||||
continue;
|
||||
}
|
||||
// Delete or update event in db
|
||||
$db_event = $event_source->events()->where('uid', $uid)->firstOrFail();
|
||||
if($db_event->timesheet_id === null && !$db_event->manualedit) {
|
||||
// Hard delete if this event has not been assigned to a timesheet or have been manually edited
|
||||
$db_event->forceDelete();
|
||||
|
||||
} else {
|
||||
// Mark as deleted in source
|
||||
$db_event->org_deleted_at = new DateTime('now', new DateTimeZone('UTC'));
|
||||
$db_event->save();
|
||||
|
||||
}
|
||||
}
|
||||
$this->info("Deleted ".count($deleted). " events");
|
||||
|
||||
} else {
|
||||
// TODO: Parse error
|
||||
}
|
||||
|
||||
} else {
|
||||
// TODO: Curl Error
|
||||
}
|
||||
}
|
||||
|
||||
foreach($T->end()['laps'] as $lap) {
|
||||
echo number_format($lap['total'], 3)." : {$lap['name']}\n";
|
||||
}
|
||||
|
||||
$this->info('Done');
|
||||
}
|
||||
|
||||
protected function getArguments() {
|
||||
return array(
|
||||
);
|
||||
}
|
||||
|
||||
protected function getOptions() {
|
||||
return array(
|
||||
);
|
||||
}
|
||||
|
||||
}
|
24
app/Console/Commands/ResetData.php
Normal file
24
app/Console/Commands/ResetData.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
|
||||
class ResetData extends Command {
|
||||
|
||||
protected $name = 'ninja:reset-data';
|
||||
protected $description = 'Reset data';
|
||||
|
||||
public function fire()
|
||||
{
|
||||
$this->info(date('Y-m-d') . ' Running ResetData...');
|
||||
|
||||
if (!Utils::isNinjaDev()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Artisan::call('migrate:reset');
|
||||
Artisan::call('migrate');
|
||||
Artisan::call('db:seed');
|
||||
}
|
||||
}
|
113
app/Console/Commands/SendRecurringInvoices.php
Normal file
113
app/Console/Commands/SendRecurringInvoices.php
Normal file
@ -0,0 +1,113 @@
|
||||
<?php namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use ninja\mailers\ContactMailer as Mailer;
|
||||
|
||||
class SendRecurringInvoices extends Command
|
||||
{
|
||||
protected $name = 'ninja:send-invoices';
|
||||
protected $description = 'Send recurring invoices';
|
||||
protected $mailer;
|
||||
|
||||
public function __construct(Mailer $mailer)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->mailer = $mailer;
|
||||
}
|
||||
|
||||
public function fire()
|
||||
{
|
||||
$this->info(date('Y-m-d').' Running SendRecurringInvoices...');
|
||||
$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();
|
||||
$this->info(count($invoices).' recurring invoice(s) found');
|
||||
|
||||
foreach ($invoices as $recurInvoice) {
|
||||
if ($recurInvoice->client->deleted_at) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$recurInvoice->user->confirmed) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->info('Processing Invoice '.$recurInvoice->id.' - Should send '.($recurInvoice->shouldSendToday() ? 'YES' : 'NO'));
|
||||
|
||||
if (!$recurInvoice->shouldSendToday()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$invoice = Invoice::createNew($recurInvoice);
|
||||
$invoice->client_id = $recurInvoice->client_id;
|
||||
$invoice->recurring_invoice_id = $recurInvoice->id;
|
||||
$invoice->invoice_number = 'R'.$recurInvoice->account->getNextInvoiceNumber();
|
||||
$invoice->amount = $recurInvoice->amount;
|
||||
$invoice->balance = $recurInvoice->amount;
|
||||
$invoice->invoice_date = date_create()->format('Y-m-d');
|
||||
$invoice->discount = $recurInvoice->discount;
|
||||
$invoice->po_number = $recurInvoice->po_number;
|
||||
$invoice->public_notes = $recurInvoice->public_notes;
|
||||
$invoice->terms = $recurInvoice->terms;
|
||||
$invoice->invoice_footer = $recurInvoice->invoice_footer;
|
||||
$invoice->tax_name = $recurInvoice->tax_name;
|
||||
$invoice->tax_rate = $recurInvoice->tax_rate;
|
||||
$invoice->invoice_design_id = $recurInvoice->invoice_design_id;
|
||||
$invoice->custom_value1 = $recurInvoice->custom_value1;
|
||||
$invoice->custom_value2 = $recurInvoice->custom_value2;
|
||||
$invoice->custom_taxes1 = $recurInvoice->custom_taxes1;
|
||||
$invoice->custom_taxes2 = $recurInvoice->custom_taxes2;
|
||||
$invoice->is_amount_discount = $recurInvoice->is_amount_discount;
|
||||
|
||||
if ($invoice->client->payment_terms) {
|
||||
$invoice->due_date = date_create()->modify($invoice->client->payment_terms.' day')->format('Y-m-d');
|
||||
}
|
||||
|
||||
$invoice->save();
|
||||
|
||||
foreach ($recurInvoice->invoice_items as $recurItem) {
|
||||
$item = InvoiceItem::createNew($recurItem);
|
||||
$item->product_id = $recurItem->product_id;
|
||||
$item->qty = $recurItem->qty;
|
||||
$item->cost = $recurItem->cost;
|
||||
$item->notes = Utils::processVariables($recurItem->notes);
|
||||
$item->product_key = Utils::processVariables($recurItem->product_key);
|
||||
$item->tax_name = $recurItem->tax_name;
|
||||
$item->tax_rate = $recurItem->tax_rate;
|
||||
$invoice->invoice_items()->save($item);
|
||||
}
|
||||
|
||||
foreach ($recurInvoice->invitations as $recurInvitation) {
|
||||
$invitation = Invitation::createNew($recurInvitation);
|
||||
$invitation->contact_id = $recurInvitation->contact_id;
|
||||
$invitation->invitation_key = str_random(RANDOM_KEY_LENGTH);
|
||||
$invoice->invitations()->save($invitation);
|
||||
}
|
||||
|
||||
$this->mailer->sendInvoice($invoice);
|
||||
|
||||
$recurInvoice->last_sent_date = Carbon::now()->toDateTimeString();
|
||||
$recurInvoice->save();
|
||||
}
|
||||
|
||||
$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),
|
||||
);
|
||||
}
|
||||
}
|
51
app/Handlers/InvoiceEventHandler.php
Normal file
51
app/Handlers/InvoiceEventHandler.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
use ninja\mailers\UserMailer;
|
||||
use ninja\mailers\ContactMailer;
|
||||
|
||||
class InvoiceEventHandler
|
||||
{
|
||||
protected $userMailer;
|
||||
protected $contactMailer;
|
||||
|
||||
public function __construct(UserMailer $userMailer, ContactMailer $contactMailer)
|
||||
{
|
||||
$this->userMailer = $userMailer;
|
||||
$this->contactMailer = $contactMailer;
|
||||
}
|
||||
|
||||
public function subscribe($events)
|
||||
{
|
||||
$events->listen('invoice.sent', 'InvoiceEventHandler@onSent');
|
||||
$events->listen('invoice.viewed', 'InvoiceEventHandler@onViewed');
|
||||
$events->listen('invoice.paid', 'InvoiceEventHandler@onPaid');
|
||||
}
|
||||
|
||||
public function onSent($invoice)
|
||||
{
|
||||
$this->sendNotifications($invoice, 'sent');
|
||||
}
|
||||
|
||||
public function onViewed($invoice)
|
||||
{
|
||||
$this->sendNotifications($invoice, 'viewed');
|
||||
}
|
||||
|
||||
public function onPaid($payment)
|
||||
{
|
||||
$this->contactMailer->sendPaymentConfirmation($payment);
|
||||
|
||||
$this->sendNotifications($payment->invoice, 'paid', $payment);
|
||||
}
|
||||
|
||||
private function sendNotifications($invoice, $type, $payment = null)
|
||||
{
|
||||
foreach ($invoice->account->users as $user)
|
||||
{
|
||||
if ($user->{'notify_' . $type})
|
||||
{
|
||||
$this->userMailer->sendNotification($user, $invoice, $type, $payment);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
30
app/Handlers/UserEventHandler.php
Normal file
30
app/Handlers/UserEventHandler.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
class UserEventHandler
|
||||
{
|
||||
public function subscribe($events)
|
||||
{
|
||||
$events->listen('user.signup', 'UserEventHandler@onSignup');
|
||||
$events->listen('user.login', 'UserEventHandler@onLogin');
|
||||
|
||||
$events->listen('user.refresh', 'UserEventHandler@onRefresh');
|
||||
}
|
||||
|
||||
public function onSignup()
|
||||
{
|
||||
}
|
||||
|
||||
public function onLogin()
|
||||
{
|
||||
$account = Auth::user()->account;
|
||||
$account->last_login = Carbon::now()->toDateTimeString();
|
||||
$account->save();
|
||||
|
||||
Event::fire('user.refresh');
|
||||
}
|
||||
|
||||
public function onRefresh()
|
||||
{
|
||||
Auth::user()->account->loadLocalizationSettings();
|
||||
}
|
||||
}
|
0
app/Http/Controllers/.gitkeep
Normal file
0
app/Http/Controllers/.gitkeep
Normal file
715
app/Http/Controllers/AccountController.php
Normal file
715
app/Http/Controllers/AccountController.php
Normal file
@ -0,0 +1,715 @@
|
||||
<?php
|
||||
|
||||
use ninja\repositories\AccountRepository;
|
||||
use ninja\mailers\UserMailer;
|
||||
use ninja\mailers\ContactMailer;
|
||||
|
||||
class AccountController extends \BaseController
|
||||
{
|
||||
protected $accountRepo;
|
||||
protected $userMailer;
|
||||
protected $contactMailer;
|
||||
|
||||
public function __construct(AccountRepository $accountRepo, UserMailer $userMailer, ContactMailer $contactMailer)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->accountRepo = $accountRepo;
|
||||
$this->userMailer = $userMailer;
|
||||
$this->contactMailer = $contactMailer;
|
||||
}
|
||||
|
||||
public function demo()
|
||||
{
|
||||
$demoAccountId = Utils::getDemoAccountId();
|
||||
|
||||
if (!$demoAccountId) {
|
||||
return Redirect::to('/');
|
||||
}
|
||||
|
||||
$account = Account::find($demoAccountId);
|
||||
$user = $account->users()->first();
|
||||
|
||||
Auth::login($user, true);
|
||||
|
||||
return Redirect::to('invoices/create');
|
||||
}
|
||||
|
||||
public function getStarted()
|
||||
{
|
||||
if (Auth::check()) {
|
||||
return Redirect::to('invoices/create');
|
||||
} elseif (!Utils::isNinja() && Account::count() > 0) {
|
||||
return Redirect::to('/login');
|
||||
}
|
||||
|
||||
$user = false;
|
||||
$guestKey = Input::get('guest_key');
|
||||
|
||||
if ($guestKey) {
|
||||
$user = User::where('password', '=', $guestKey)->first();
|
||||
|
||||
if ($user && $user->registered) {
|
||||
return Redirect::to('/');
|
||||
}
|
||||
}
|
||||
|
||||
if (!$user) {
|
||||
$account = $this->accountRepo->create();
|
||||
$user = $account->users()->first();
|
||||
|
||||
Session::forget(RECENTLY_VIEWED);
|
||||
}
|
||||
|
||||
Auth::login($user, true);
|
||||
Event::fire('user.login');
|
||||
|
||||
return Redirect::to('invoices/create')->with('sign_up', Input::get('sign_up'));
|
||||
}
|
||||
|
||||
public function enableProPlan()
|
||||
{
|
||||
$invitation = $this->accountRepo->enableProPlan();
|
||||
|
||||
/*
|
||||
if ($invoice)
|
||||
{
|
||||
$this->contactMailer->sendInvoice($invoice);
|
||||
}
|
||||
*/
|
||||
|
||||
return $invitation->invitation_key;
|
||||
}
|
||||
|
||||
public function setTrashVisible($entityType, $visible)
|
||||
{
|
||||
Session::put("show_trash:{$entityType}", $visible == 'true');
|
||||
|
||||
if ($entityType == 'user') {
|
||||
return Redirect::to('company/'.ACCOUNT_ADVANCED_SETTINGS.'/'.ACCOUNT_USER_MANAGEMENT);
|
||||
} elseif ($entityType == 'token') {
|
||||
return Redirect::to('company/'.ACCOUNT_ADVANCED_SETTINGS.'/'.ACCOUNT_TOKEN_MANAGEMENT);
|
||||
} else {
|
||||
return Redirect::to("{$entityType}s");
|
||||
}
|
||||
}
|
||||
|
||||
public function getSearchData()
|
||||
{
|
||||
$data = $this->accountRepo->getSearchData();
|
||||
return Response::json($data);
|
||||
}
|
||||
|
||||
public function showSection($section = ACCOUNT_DETAILS, $subSection = false)
|
||||
{
|
||||
if ($section == ACCOUNT_DETAILS) {
|
||||
$data = [
|
||||
'account' => Account::with('users')->findOrFail(Auth::user()->account_id),
|
||||
'countries' => Country::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
|
||||
'sizes' => Size::remember(DEFAULT_QUERY_CACHE)->orderBy('id')->get(),
|
||||
'industries' => Industry::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
|
||||
'timezones' => Timezone::remember(DEFAULT_QUERY_CACHE)->orderBy('location')->get(),
|
||||
'dateFormats' => DateFormat::remember(DEFAULT_QUERY_CACHE)->get(),
|
||||
'datetimeFormats' => DatetimeFormat::remember(DEFAULT_QUERY_CACHE)->get(),
|
||||
'currencies' => Currency::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
|
||||
'languages' => Language::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
|
||||
'showUser' => Auth::user()->id === Auth::user()->account->users()->first()->id,
|
||||
];
|
||||
|
||||
return View::make('accounts.details', $data);
|
||||
} elseif ($section == ACCOUNT_PAYMENTS) {
|
||||
|
||||
$account = Auth::user()->account;
|
||||
$account->load('account_gateways');
|
||||
$count = count($account->account_gateways);
|
||||
|
||||
if ($count == 0) {
|
||||
return Redirect::to('gateways/create');
|
||||
} else {
|
||||
return View::make('accounts.payments', ['showAdd' => $count < 2]);
|
||||
}
|
||||
} elseif ($section == ACCOUNT_NOTIFICATIONS) {
|
||||
$data = [
|
||||
'account' => Account::with('users')->findOrFail(Auth::user()->account_id),
|
||||
];
|
||||
|
||||
return View::make('accounts.notifications', $data);
|
||||
} elseif ($section == ACCOUNT_IMPORT_EXPORT) {
|
||||
return View::make('accounts.import_export');
|
||||
} elseif ($section == ACCOUNT_ADVANCED_SETTINGS) {
|
||||
$account = Auth::user()->account;
|
||||
$data = [
|
||||
'account' => $account,
|
||||
'feature' => $subSection,
|
||||
];
|
||||
|
||||
if ($subSection == ACCOUNT_INVOICE_DESIGN) {
|
||||
$invoice = new stdClass();
|
||||
$client = new stdClass();
|
||||
$invoiceItem = new stdClass();
|
||||
|
||||
$client->name = 'Sample Client';
|
||||
$client->address1 = '';
|
||||
$client->city = '';
|
||||
$client->state = '';
|
||||
$client->postal_code = '';
|
||||
$client->work_phone = '';
|
||||
$client->work_email = '';
|
||||
|
||||
$invoice->invoice_number = Auth::user()->account->getNextInvoiceNumber();
|
||||
$invoice->invoice_date = date_create()->format('Y-m-d');
|
||||
$invoice->account = json_decode(Auth::user()->account->toJson());
|
||||
$invoice->amount = $invoice->balance = 100;
|
||||
|
||||
$invoiceItem->cost = 100;
|
||||
$invoiceItem->qty = 1;
|
||||
$invoiceItem->notes = 'Notes';
|
||||
$invoiceItem->product_key = 'Item';
|
||||
|
||||
$invoice->client = $client;
|
||||
$invoice->invoice_items = [$invoiceItem];
|
||||
|
||||
$data['invoice'] = $invoice;
|
||||
$data['invoiceDesigns'] = InvoiceDesign::remember(DEFAULT_QUERY_CACHE, 'invoice_designs_cache_'.Auth::user()->maxInvoiceDesignId())
|
||||
->where('id', '<=', Auth::user()->maxInvoiceDesignId())->orderBy('id')->get();
|
||||
} else if ($subSection == ACCOUNT_EMAIL_TEMPLATES) {
|
||||
$data['invoiceEmail'] = $account->getEmailTemplate(ENTITY_INVOICE);
|
||||
$data['quoteEmail'] = $account->getEmailTemplate(ENTITY_QUOTE);
|
||||
$data['paymentEmail'] = $account->getEmailTemplate(ENTITY_PAYMENT);
|
||||
$data['emailFooter'] = $account->getEmailFooter();
|
||||
}
|
||||
|
||||
return View::make("accounts.{$subSection}", $data);
|
||||
} elseif ($section == ACCOUNT_PRODUCTS) {
|
||||
$data = [
|
||||
'account' => Auth::user()->account,
|
||||
];
|
||||
|
||||
return View::make('accounts.products', $data);
|
||||
}
|
||||
}
|
||||
|
||||
public function doSection($section = ACCOUNT_DETAILS, $subSection = false)
|
||||
{
|
||||
if ($section == ACCOUNT_DETAILS) {
|
||||
return AccountController::saveDetails();
|
||||
} elseif ($section == ACCOUNT_IMPORT_EXPORT) {
|
||||
return AccountController::importFile();
|
||||
} elseif ($section == ACCOUNT_MAP) {
|
||||
return AccountController::mapFile();
|
||||
} elseif ($section == ACCOUNT_NOTIFICATIONS) {
|
||||
return AccountController::saveNotifications();
|
||||
} elseif ($section == ACCOUNT_EXPORT) {
|
||||
return AccountController::export();
|
||||
} elseif ($section == ACCOUNT_ADVANCED_SETTINGS) {
|
||||
if ($subSection == ACCOUNT_INVOICE_SETTINGS) {
|
||||
return AccountController::saveInvoiceSettings();
|
||||
} elseif ($subSection == ACCOUNT_INVOICE_DESIGN) {
|
||||
return AccountController::saveInvoiceDesign();
|
||||
} elseif ($subSection == ACCOUNT_EMAIL_TEMPLATES) {
|
||||
return AccountController::saveEmailTemplates();
|
||||
}
|
||||
} elseif ($section == ACCOUNT_PRODUCTS) {
|
||||
return AccountController::saveProducts();
|
||||
}
|
||||
}
|
||||
|
||||
private function saveEmailTemplates()
|
||||
{
|
||||
if (Auth::user()->account->isPro()) {
|
||||
$account = Auth::user()->account;
|
||||
|
||||
$account->email_template_invoice = Input::get('email_template_invoice', $account->getEmailTemplate(ENTITY_INVOICE));
|
||||
$account->email_template_quote = Input::get('email_template_quote', $account->getEmailTemplate(ENTITY_QUOTE));
|
||||
$account->email_template_payment = Input::get('email_template_payment', $account->getEmailTemplate(ENTITY_PAYMENT));
|
||||
|
||||
$account->save();
|
||||
|
||||
Session::flash('message', trans('texts.updated_settings'));
|
||||
}
|
||||
|
||||
return Redirect::to('company/advanced_settings/email_templates');
|
||||
}
|
||||
|
||||
private function saveProducts()
|
||||
{
|
||||
$account = Auth::user()->account;
|
||||
|
||||
$account->fill_products = Input::get('fill_products') ? true : false;
|
||||
$account->update_products = Input::get('update_products') ? true : false;
|
||||
$account->save();
|
||||
|
||||
Session::flash('message', trans('texts.updated_settings'));
|
||||
return Redirect::to('company/products');
|
||||
}
|
||||
|
||||
private function saveInvoiceSettings()
|
||||
{
|
||||
if (Auth::user()->account->isPro()) {
|
||||
$account = Auth::user()->account;
|
||||
|
||||
$account->custom_label1 = trim(Input::get('custom_label1'));
|
||||
$account->custom_value1 = trim(Input::get('custom_value1'));
|
||||
$account->custom_label2 = trim(Input::get('custom_label2'));
|
||||
$account->custom_value2 = trim(Input::get('custom_value2'));
|
||||
$account->custom_client_label1 = trim(Input::get('custom_client_label1'));
|
||||
$account->custom_client_label2 = trim(Input::get('custom_client_label2'));
|
||||
$account->custom_invoice_label1 = trim(Input::get('custom_invoice_label1'));
|
||||
$account->custom_invoice_label2 = trim(Input::get('custom_invoice_label2'));
|
||||
$account->custom_invoice_taxes1 = Input::get('custom_invoice_taxes1') ? true : false;
|
||||
$account->custom_invoice_taxes2 = Input::get('custom_invoice_taxes2') ? true : false;
|
||||
|
||||
$account->invoice_number_prefix = Input::get('invoice_number_prefix');
|
||||
$account->invoice_number_counter = Input::get('invoice_number_counter');
|
||||
$account->quote_number_prefix = Input::get('quote_number_prefix');
|
||||
$account->share_counter = Input::get('share_counter') ? true : false;
|
||||
|
||||
if (!$account->share_counter) {
|
||||
$account->quote_number_counter = Input::get('quote_number_counter');
|
||||
}
|
||||
|
||||
if (!$account->share_counter && $account->invoice_number_prefix == $account->quote_number_prefix) {
|
||||
Session::flash('error', trans('texts.invalid_counter'));
|
||||
|
||||
return Redirect::to('company/advanced_settings/invoice_settings')->withInput();
|
||||
} else {
|
||||
$account->save();
|
||||
Session::flash('message', trans('texts.updated_settings'));
|
||||
}
|
||||
}
|
||||
|
||||
return Redirect::to('company/advanced_settings/invoice_settings');
|
||||
}
|
||||
|
||||
private function saveInvoiceDesign()
|
||||
{
|
||||
if (Auth::user()->account->isPro()) {
|
||||
$account = Auth::user()->account;
|
||||
$account->hide_quantity = Input::get('hide_quantity') ? true : false;
|
||||
$account->hide_paid_to_date = Input::get('hide_paid_to_date') ? true : false;
|
||||
$account->primary_color = Input::get('primary_color');
|
||||
$account->secondary_color = Input::get('secondary_color');
|
||||
$account->invoice_design_id = Input::get('invoice_design_id');
|
||||
$account->save();
|
||||
|
||||
Session::flash('message', trans('texts.updated_settings'));
|
||||
}
|
||||
|
||||
return Redirect::to('company/advanced_settings/invoice_design');
|
||||
}
|
||||
|
||||
private function export()
|
||||
{
|
||||
$output = fopen('php://output', 'w') or Utils::fatalError();
|
||||
header('Content-Type:application/csv');
|
||||
header('Content-Disposition:attachment;filename=export.csv');
|
||||
|
||||
$clients = Client::scope()->get();
|
||||
AccountController::exportData($output, $clients->toArray());
|
||||
|
||||
$contacts = Contact::scope()->get();
|
||||
AccountController::exportData($output, $contacts->toArray());
|
||||
|
||||
$invoices = Invoice::scope()->get();
|
||||
AccountController::exportData($output, $invoices->toArray());
|
||||
|
||||
$invoiceItems = InvoiceItem::scope()->get();
|
||||
AccountController::exportData($output, $invoiceItems->toArray());
|
||||
|
||||
$payments = Payment::scope()->get();
|
||||
AccountController::exportData($output, $payments->toArray());
|
||||
|
||||
$credits = Credit::scope()->get();
|
||||
AccountController::exportData($output, $credits->toArray());
|
||||
|
||||
fclose($output);
|
||||
exit;
|
||||
}
|
||||
|
||||
private function exportData($output, $data)
|
||||
{
|
||||
if (count($data) > 0) {
|
||||
fputcsv($output, array_keys($data[0]));
|
||||
}
|
||||
|
||||
foreach ($data as $record) {
|
||||
fputcsv($output, $record);
|
||||
}
|
||||
|
||||
fwrite($output, "\n");
|
||||
}
|
||||
|
||||
private function importFile()
|
||||
{
|
||||
$data = Session::get('data');
|
||||
Session::forget('data');
|
||||
|
||||
$map = Input::get('map');
|
||||
$count = 0;
|
||||
$hasHeaders = Input::get('header_checkbox');
|
||||
|
||||
$countries = Country::remember(DEFAULT_QUERY_CACHE)->get();
|
||||
$countryMap = [];
|
||||
|
||||
foreach ($countries as $country) {
|
||||
$countryMap[strtolower($country->name)] = $country->id;
|
||||
}
|
||||
|
||||
foreach ($data as $row) {
|
||||
if ($hasHeaders) {
|
||||
$hasHeaders = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
$client = Client::createNew();
|
||||
$contact = Contact::createNew();
|
||||
$contact->is_primary = true;
|
||||
$contact->send_invoice = true;
|
||||
$count++;
|
||||
|
||||
foreach ($row as $index => $value) {
|
||||
$field = $map[$index];
|
||||
$value = trim($value);
|
||||
|
||||
if ($field == Client::$fieldName && !$client->name) {
|
||||
$client->name = $value;
|
||||
} elseif ($field == Client::$fieldPhone && !$client->work_phone) {
|
||||
$client->work_phone = $value;
|
||||
} elseif ($field == Client::$fieldAddress1 && !$client->address1) {
|
||||
$client->address1 = $value;
|
||||
} elseif ($field == Client::$fieldAddress2 && !$client->address2) {
|
||||
$client->address2 = $value;
|
||||
} elseif ($field == Client::$fieldCity && !$client->city) {
|
||||
$client->city = $value;
|
||||
} elseif ($field == Client::$fieldState && !$client->state) {
|
||||
$client->state = $value;
|
||||
} elseif ($field == Client::$fieldPostalCode && !$client->postal_code) {
|
||||
$client->postal_code = $value;
|
||||
} elseif ($field == Client::$fieldCountry && !$client->country_id) {
|
||||
$value = strtolower($value);
|
||||
$client->country_id = isset($countryMap[$value]) ? $countryMap[$value] : null;
|
||||
} elseif ($field == Client::$fieldNotes && !$client->private_notes) {
|
||||
$client->private_notes = $value;
|
||||
} elseif ($field == Contact::$fieldFirstName && !$contact->first_name) {
|
||||
$contact->first_name = $value;
|
||||
} elseif ($field == Contact::$fieldLastName && !$contact->last_name) {
|
||||
$contact->last_name = $value;
|
||||
} elseif ($field == Contact::$fieldPhone && !$contact->phone) {
|
||||
$contact->phone = $value;
|
||||
} elseif ($field == Contact::$fieldEmail && !$contact->email) {
|
||||
$contact->email = strtolower($value);
|
||||
}
|
||||
}
|
||||
|
||||
$client->save();
|
||||
$client->contacts()->save($contact);
|
||||
Activity::createClient($client, false);
|
||||
}
|
||||
|
||||
$message = Utils::pluralize('created_client', $count);
|
||||
Session::flash('message', $message);
|
||||
|
||||
return Redirect::to('clients');
|
||||
}
|
||||
|
||||
private function mapFile()
|
||||
{
|
||||
$file = Input::file('file');
|
||||
|
||||
if ($file == null) {
|
||||
Session::flash('error', trans('texts.select_file'));
|
||||
|
||||
return Redirect::to('company/import_export');
|
||||
}
|
||||
|
||||
$name = $file->getRealPath();
|
||||
|
||||
require_once app_path().'/includes/parsecsv.lib.php';
|
||||
$csv = new parseCSV();
|
||||
$csv->heading = false;
|
||||
$csv->auto($name);
|
||||
|
||||
if (count($csv->data) + Client::scope()->count() > Auth::user()->getMaxNumClients()) {
|
||||
$message = trans('texts.limit_clients', ['count' => Auth::user()->getMaxNumClients()]);
|
||||
Session::flash('error', $message);
|
||||
|
||||
return Redirect::to('company/import_export');
|
||||
}
|
||||
|
||||
Session::put('data', $csv->data);
|
||||
|
||||
$headers = false;
|
||||
$hasHeaders = false;
|
||||
$mapped = array();
|
||||
$columns = array('',
|
||||
Client::$fieldName,
|
||||
Client::$fieldPhone,
|
||||
Client::$fieldAddress1,
|
||||
Client::$fieldAddress2,
|
||||
Client::$fieldCity,
|
||||
Client::$fieldState,
|
||||
Client::$fieldPostalCode,
|
||||
Client::$fieldCountry,
|
||||
Client::$fieldNotes,
|
||||
Contact::$fieldFirstName,
|
||||
Contact::$fieldLastName,
|
||||
Contact::$fieldPhone,
|
||||
Contact::$fieldEmail,
|
||||
);
|
||||
|
||||
if (count($csv->data) > 0) {
|
||||
$headers = $csv->data[0];
|
||||
foreach ($headers as $title) {
|
||||
if (strpos(strtolower($title), 'name') > 0) {
|
||||
$hasHeaders = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for ($i = 0; $i<count($headers); $i++) {
|
||||
$title = strtolower($headers[$i]);
|
||||
$mapped[$i] = '';
|
||||
|
||||
if ($hasHeaders) {
|
||||
$map = array(
|
||||
'first' => Contact::$fieldFirstName,
|
||||
'last' => Contact::$fieldLastName,
|
||||
'email' => Contact::$fieldEmail,
|
||||
'mobile' => Contact::$fieldPhone,
|
||||
'phone' => Client::$fieldPhone,
|
||||
'name|organization' => Client::$fieldName,
|
||||
'street|address|address1' => Client::$fieldAddress1,
|
||||
'street2|address2' => Client::$fieldAddress2,
|
||||
'city' => Client::$fieldCity,
|
||||
'state|province' => Client::$fieldState,
|
||||
'zip|postal|code' => Client::$fieldPostalCode,
|
||||
'country' => Client::$fieldCountry,
|
||||
'note' => Client::$fieldNotes,
|
||||
);
|
||||
|
||||
foreach ($map as $search => $column) {
|
||||
foreach (explode("|", $search) as $string) {
|
||||
if (strpos($title, 'sec') === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strpos($title, $string) !== false) {
|
||||
$mapped[$i] = $column;
|
||||
break(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'data' => $csv->data,
|
||||
'headers' => $headers,
|
||||
'hasHeaders' => $hasHeaders,
|
||||
'columns' => $columns,
|
||||
'mapped' => $mapped,
|
||||
);
|
||||
|
||||
return View::make('accounts.import_map', $data);
|
||||
}
|
||||
|
||||
private function saveNotifications()
|
||||
{
|
||||
$account = Auth::user()->account;
|
||||
$account->invoice_terms = Input::get('invoice_terms');
|
||||
$account->invoice_footer = Input::get('invoice_footer');
|
||||
$account->email_footer = Input::get('email_footer');
|
||||
$account->save();
|
||||
|
||||
$user = Auth::user();
|
||||
$user->notify_sent = Input::get('notify_sent');
|
||||
$user->notify_viewed = Input::get('notify_viewed');
|
||||
$user->notify_paid = Input::get('notify_paid');
|
||||
$user->save();
|
||||
|
||||
Session::flash('message', trans('texts.updated_settings'));
|
||||
|
||||
return Redirect::to('company/notifications');
|
||||
}
|
||||
|
||||
private function saveDetails()
|
||||
{
|
||||
$rules = array(
|
||||
'name' => 'required',
|
||||
);
|
||||
|
||||
$user = Auth::user()->account->users()->first();
|
||||
|
||||
if (Auth::user()->id === $user->id) {
|
||||
$rules['email'] = 'email|required|unique:users,email,'.$user->id.',id';
|
||||
}
|
||||
|
||||
$validator = Validator::make(Input::all(), $rules);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return Redirect::to('company/details')
|
||||
->withErrors($validator)
|
||||
->withInput();
|
||||
} else {
|
||||
$account = Auth::user()->account;
|
||||
$account->name = trim(Input::get('name'));
|
||||
$account->id_number = trim(Input::get('id_number'));
|
||||
$account->vat_number = trim(Input::get('vat_number'));
|
||||
$account->work_email = trim(Input::get('work_email'));
|
||||
$account->work_phone = trim(Input::get('work_phone'));
|
||||
$account->address1 = trim(Input::get('address1'));
|
||||
$account->address2 = trim(Input::get('address2'));
|
||||
$account->city = trim(Input::get('city'));
|
||||
$account->state = trim(Input::get('state'));
|
||||
$account->postal_code = trim(Input::get('postal_code'));
|
||||
$account->country_id = Input::get('country_id') ? Input::get('country_id') : null;
|
||||
$account->size_id = Input::get('size_id') ? Input::get('size_id') : null;
|
||||
$account->industry_id = Input::get('industry_id') ? Input::get('industry_id') : null;
|
||||
$account->timezone_id = Input::get('timezone_id') ? Input::get('timezone_id') : null;
|
||||
$account->date_format_id = Input::get('date_format_id') ? Input::get('date_format_id') : null;
|
||||
$account->datetime_format_id = Input::get('datetime_format_id') ? Input::get('datetime_format_id') : null;
|
||||
$account->currency_id = Input::get('currency_id') ? Input::get('currency_id') : 1; // US Dollar
|
||||
$account->language_id = Input::get('language_id') ? Input::get('language_id') : 1; // English
|
||||
$account->save();
|
||||
|
||||
if (Auth::user()->id === $user->id) {
|
||||
$user->first_name = trim(Input::get('first_name'));
|
||||
$user->last_name = trim(Input::get('last_name'));
|
||||
$user->username = trim(Input::get('email'));
|
||||
$user->email = trim(strtolower(Input::get('email')));
|
||||
$user->phone = trim(Input::get('phone'));
|
||||
$user->save();
|
||||
}
|
||||
|
||||
/* Logo image file */
|
||||
if ($file = Input::file('logo')) {
|
||||
$path = Input::file('logo')->getRealPath();
|
||||
File::delete('logo/'.$account->account_key.'.jpg');
|
||||
|
||||
$image = Image::make($path);
|
||||
$mimeType = $file->getMimeType();
|
||||
|
||||
if ($image->width == 200 && $mimeType == 'image/jpeg') {
|
||||
$file->move('logo/', $account->account_key . '.jpg');
|
||||
} else {
|
||||
$image->resize(200, 120, true, false);
|
||||
Image::canvas($image->width, $image->height, '#FFFFFF')->insert($image)->save($account->getLogoPath());
|
||||
}
|
||||
|
||||
//$image = Image::make($path)->resize(200, 120, true, false);
|
||||
//Image::canvas($image->width, $image->height, '#FFFFFF')->insert($image)->save($account->getLogoPath());
|
||||
}
|
||||
|
||||
Event::fire('user.refresh');
|
||||
Session::flash('message', trans('texts.updated_settings'));
|
||||
|
||||
return Redirect::to('company/details');
|
||||
}
|
||||
}
|
||||
|
||||
public function removeLogo()
|
||||
{
|
||||
File::delete('logo/'.Auth::user()->account->account_key.'.jpg');
|
||||
|
||||
Session::flash('message', trans('texts.removed_logo'));
|
||||
|
||||
return Redirect::to('company/details');
|
||||
}
|
||||
|
||||
public function checkEmail()
|
||||
{
|
||||
$email = User::withTrashed()->where('email', '=', Input::get('email'))->where('id', '<>', Auth::user()->id)->first();
|
||||
|
||||
if ($email) {
|
||||
return "taken";
|
||||
} else {
|
||||
return "available";
|
||||
}
|
||||
}
|
||||
|
||||
public function submitSignup()
|
||||
{
|
||||
$rules = array(
|
||||
'new_first_name' => 'required',
|
||||
'new_last_name' => 'required',
|
||||
'new_password' => 'required|min:6',
|
||||
'new_email' => 'email|required|unique:users,email,'.Auth::user()->id.',id',
|
||||
);
|
||||
|
||||
$validator = Validator::make(Input::all(), $rules);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$user = Auth::user();
|
||||
$user->first_name = trim(Input::get('new_first_name'));
|
||||
$user->last_name = trim(Input::get('new_last_name'));
|
||||
$user->email = trim(strtolower(Input::get('new_email')));
|
||||
$user->username = $user->email;
|
||||
$user->password = trim(Input::get('new_password'));
|
||||
$user->password_confirmation = trim(Input::get('new_password'));
|
||||
$user->registered = true;
|
||||
$user->amend();
|
||||
|
||||
if (Utils::isNinja()) {
|
||||
$this->userMailer->sendConfirmation($user);
|
||||
} else {
|
||||
$this->accountRepo->registerUser($user);
|
||||
}
|
||||
|
||||
$activities = Activity::scope()->get();
|
||||
foreach ($activities as $activity) {
|
||||
$activity->message = str_replace('Guest', $user->getFullName(), $activity->message);
|
||||
$activity->save();
|
||||
}
|
||||
|
||||
if (Input::get('go_pro') == 'true') {
|
||||
Session::set(REQUESTED_PRO_PLAN, true);
|
||||
}
|
||||
|
||||
Session::set(SESSION_COUNTER, -1);
|
||||
|
||||
return "{$user->first_name} {$user->last_name}";
|
||||
}
|
||||
|
||||
public function doRegister()
|
||||
{
|
||||
$affiliate = Affiliate::where('affiliate_key', '=', SELF_HOST_AFFILIATE_KEY)->first();
|
||||
|
||||
$license = new License();
|
||||
$license->first_name = Input::get('first_name');
|
||||
$license->last_name = Input::get('last_name');
|
||||
$license->email = Input::get('email');
|
||||
$license->transaction_reference = Request::getClientIp();
|
||||
$license->license_key = Utils::generateLicense();
|
||||
$license->affiliate_id = $affiliate->id;
|
||||
$license->product_id = PRODUCT_SELF_HOST;
|
||||
$license->is_claimed = 1;
|
||||
$license->save();
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
public function cancelAccount()
|
||||
{
|
||||
if ($reason = trim(Input::get('reason'))) {
|
||||
$email = Auth::user()->email;
|
||||
$name = Auth::user()->getDisplayName();
|
||||
|
||||
$data = [
|
||||
'text' => $reason,
|
||||
];
|
||||
|
||||
$this->userMailer->sendTo(CONTACT_EMAIL, $email, $name, 'Invoice Ninja Feedback [Canceled Account]', 'contact', $data);
|
||||
}
|
||||
|
||||
$account = Auth::user()->account;
|
||||
$account->forceDelete();
|
||||
|
||||
Confide::logout();
|
||||
|
||||
return Redirect::to('/')->with('clearGuestKey', true);
|
||||
}
|
||||
}
|
292
app/Http/Controllers/AccountGatewayController.php
Normal file
292
app/Http/Controllers/AccountGatewayController.php
Normal file
@ -0,0 +1,292 @@
|
||||
<?php
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Confide Controller Template
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This is the default Confide controller template for controlling user
|
||||
| authentication. Feel free to change to your needs.
|
||||
|
|
||||
*/
|
||||
|
||||
use ninja\repositories\AccountRepository;
|
||||
|
||||
class AccountGatewayController extends BaseController
|
||||
{
|
||||
public function getDatatable()
|
||||
{
|
||||
$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');
|
||||
|
||||
return Datatable::query($query)
|
||||
->addColumn('name', function ($model) { return link_to('gateways/'.$model->public_id.'/edit', $model->name); })
|
||||
->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>';
|
||||
}
|
||||
|
||||
$actions .= '</ul>
|
||||
</div>';
|
||||
|
||||
return $actions;
|
||||
})
|
||||
->orderColumns(['name'])
|
||||
->make();
|
||||
}
|
||||
|
||||
public function edit($publicId)
|
||||
{
|
||||
$accountGateway = AccountGateway::scope($publicId)->firstOrFail();
|
||||
$config = $accountGateway->config;
|
||||
$selectedCards = $accountGateway->accepted_credit_cards;
|
||||
|
||||
$configFields = json_decode($config);
|
||||
|
||||
foreach ($configFields as $configField => $value) {
|
||||
$configFields->$configField = 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;
|
||||
|
||||
return View::make('accounts.account_gateway', $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 = self::getViewModel();
|
||||
$data['url'] = 'gateways';
|
||||
$data['method'] = 'POST';
|
||||
$data['title'] = trans('texts.add_gateway');
|
||||
|
||||
return View::make('accounts.account_gateway', $data);
|
||||
}
|
||||
|
||||
private function getViewModel($accountGateway = false)
|
||||
{
|
||||
$selectedCards = $accountGateway ? $accountGateway->accepted_credit_cards : 0;
|
||||
$account = Auth::user()->account;
|
||||
|
||||
$recommendedGateways = Gateway::remember(DEFAULT_QUERY_CACHE)
|
||||
->where('recommended', '=', '1')
|
||||
->orderBy('sort_order')
|
||||
->get();
|
||||
$recommendedGatewayArray = array();
|
||||
|
||||
foreach ($recommendedGateways as $recommendedGateway) {
|
||||
$arrayItem = array(
|
||||
'value' => $recommendedGateway->id,
|
||||
'other' => 'false',
|
||||
'data-imageUrl' => asset($recommendedGateway->getLogoUrl()),
|
||||
'data-siteUrl' => $recommendedGateway->site_url,
|
||||
);
|
||||
$recommendedGatewayArray[$recommendedGateway->name] = $arrayItem;
|
||||
}
|
||||
|
||||
$creditCardsArray = unserialize(CREDIT_CARDS);
|
||||
$creditCards = [];
|
||||
foreach ($creditCardsArray as $card => $name) {
|
||||
if ($selectedCards > 0 && ($selectedCards & $card) == $card) {
|
||||
$creditCards[$name['text']] = ['value' => $card, 'data-imageUrl' => asset($name['card']), 'checked' => 'checked'];
|
||||
} else {
|
||||
$creditCards[$name['text']] = ['value' => $card, 'data-imageUrl' => asset($name['card'])];
|
||||
}
|
||||
}
|
||||
|
||||
$otherItem = array(
|
||||
'value' => 1000000,
|
||||
'other' => 'true',
|
||||
'data-imageUrl' => '',
|
||||
'data-siteUrl' => '',
|
||||
);
|
||||
$recommendedGatewayArray['Other Options'] = $otherItem;
|
||||
|
||||
$account->load('account_gateways');
|
||||
$currentGateways = $account->account_gateways;
|
||||
$gateways = Gateway::where('payment_library_id', '=', 1)->orderBy('name');
|
||||
$onlyPayPal = false;
|
||||
if (!$accountGateway) {
|
||||
if (count($currentGateways) > 0) {
|
||||
$currentGateway = $currentGateways[0];
|
||||
if ($currentGateway->isPayPal()) {
|
||||
$gateways->where('id', '!=', GATEWAY_PAYPAL_EXPRESS);
|
||||
} else {
|
||||
$gateways->where('id', '=', GATEWAY_PAYPAL_EXPRESS);
|
||||
$onlyPayPal = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
$gateways = $gateways->get();
|
||||
|
||||
foreach ($gateways as $gateway) {
|
||||
$paymentLibrary = $gateway->paymentlibrary;
|
||||
$gateway->fields = $gateway->getFields();
|
||||
|
||||
if ($accountGateway && $accountGateway->gateway_id == $gateway->id) {
|
||||
$accountGateway->fields = $gateway->fields;
|
||||
}
|
||||
}
|
||||
|
||||
$tokenBillingOptions = [];
|
||||
for ($i=1; $i<=4; $i++) {
|
||||
$tokenBillingOptions[$i] = trans("texts.token_billing_{$i}");
|
||||
}
|
||||
|
||||
return [
|
||||
'account' => $account,
|
||||
'accountGateway' => $accountGateway,
|
||||
'config' => false,
|
||||
'gateways' => $gateways,
|
||||
'recommendedGateways' => $recommendedGatewayArray,
|
||||
'creditCardTypes' => $creditCards,
|
||||
'tokenBillingOptions' => $tokenBillingOptions,
|
||||
'showBreadcrumbs' => false,
|
||||
'onlyPayPal' => $onlyPayPal,
|
||||
'countGateways' => count($currentGateways)
|
||||
];
|
||||
}
|
||||
|
||||
public function delete()
|
||||
{
|
||||
$accountGatewayPublicId = Input::get('accountGatewayPublicId');
|
||||
$gateway = AccountGateway::scope($accountGatewayPublicId)->firstOrFail();
|
||||
|
||||
$gateway->delete();
|
||||
|
||||
Session::flash('message', trans('texts.deleted_gateway'));
|
||||
|
||||
return Redirect::to('company/payments');
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores new account
|
||||
*
|
||||
*/
|
||||
public function save($accountGatewayPublicId = false)
|
||||
{
|
||||
$rules = array();
|
||||
$recommendedId = Input::get('recommendedGateway_id');
|
||||
|
||||
$gatewayId = ($recommendedId == 1000000 ? Input::get('gateway_id') : $recommendedId);
|
||||
|
||||
if (!$gatewayId) {
|
||||
Session::flash('error', trans('validation.required', ['attribute' => 'gateway']));
|
||||
return Redirect::to('gateways/create')
|
||||
->withInput();
|
||||
}
|
||||
|
||||
$gateway = Gateway::findOrFail($gatewayId);
|
||||
$paymentLibrary = $gateway->paymentlibrary;
|
||||
$fields = $gateway->getFields();
|
||||
|
||||
foreach ($fields as $field => $details) {
|
||||
if (!in_array($field, ['testMode', 'developerMode', 'headerImageUrl', 'solutionType', 'landingPage', 'brandName', 'logoImageUrl', 'borderColor'])) {
|
||||
if (strtolower($gateway->name) == 'beanstream') {
|
||||
if (in_array($field, ['merchant_id', 'passCode'])) {
|
||||
$rules[$gateway->id.'_'.$field] = 'required';
|
||||
}
|
||||
} else {
|
||||
$rules[$gateway->id.'_'.$field] = 'required';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$creditcards = Input::get('creditCardTypes');
|
||||
$validator = Validator::make(Input::all(), $rules);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return Redirect::to('gateways/create')
|
||||
->withErrors($validator)
|
||||
->withInput();
|
||||
} else {
|
||||
$account = Account::with('account_gateways')->findOrFail(Auth::user()->account_id);
|
||||
|
||||
if ($accountGatewayPublicId) {
|
||||
$accountGateway = AccountGateway::scope($accountGatewayPublicId)->firstOrFail();
|
||||
} else {
|
||||
$accountGateway = AccountGateway::createNew();
|
||||
$accountGateway->gateway_id = $gatewayId;
|
||||
}
|
||||
|
||||
$isMasked = false;
|
||||
|
||||
$config = new stdClass();
|
||||
foreach ($fields as $field => $details) {
|
||||
$value = trim(Input::get($gateway->id.'_'.$field));
|
||||
|
||||
if ($value && $value === str_repeat('*', strlen($value))) {
|
||||
$isMasked = true;
|
||||
}
|
||||
|
||||
$config->$field = $value;
|
||||
}
|
||||
|
||||
$cardCount = 0;
|
||||
if ($creditcards) {
|
||||
foreach ($creditcards as $card => $value) {
|
||||
$cardCount += intval($value);
|
||||
}
|
||||
}
|
||||
|
||||
// if the values haven't changed don't update the config
|
||||
if ($isMasked && $accountGatewayPublicId) {
|
||||
$accountGateway->accepted_credit_cards = $cardCount;
|
||||
$accountGateway->save();
|
||||
// if there's an existing config for this gateway update it
|
||||
} elseif (!$isMasked && $accountGatewayPublicId && $accountGateway->gateway_id == $gatewayId) {
|
||||
$accountGateway->accepted_credit_cards = $cardCount;
|
||||
$accountGateway->config = json_encode($config);
|
||||
$accountGateway->save();
|
||||
// otherwise, create a new gateway config
|
||||
} else {
|
||||
$accountGateway->config = json_encode($config);
|
||||
$accountGateway->accepted_credit_cards = $cardCount;
|
||||
$account->account_gateways()->save($accountGateway);
|
||||
}
|
||||
|
||||
if (Input::get('token_billing_type_id')) {
|
||||
$account->token_billing_type_id = Input::get('token_billing_type_id');
|
||||
$account->save();
|
||||
}
|
||||
|
||||
if ($accountGatewayPublicId) {
|
||||
$message = trans('texts.updated_gateway');
|
||||
} else {
|
||||
$message = trans('texts.created_gateway');
|
||||
}
|
||||
|
||||
Session::flash('message', $message);
|
||||
|
||||
return Redirect::to('company/payments');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
20
app/Http/Controllers/ActivityController.php
Normal file
20
app/Http/Controllers/ActivityController.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
class ActivityController extends \BaseController
|
||||
{
|
||||
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('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 ? Utils::formatMoney($model->adjustment, $model->currency_id) : ''; })
|
||||
->make();
|
||||
}
|
||||
}
|
171
app/Http/Controllers/AppController.php
Normal file
171
app/Http/Controllers/AppController.php
Normal file
@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
use ninja\mailers\Mailer;
|
||||
use ninja\repositories\AccountRepository;
|
||||
|
||||
class AppController extends BaseController
|
||||
{
|
||||
protected $accountRepo;
|
||||
protected $mailer;
|
||||
|
||||
public function __construct(AccountRepository $accountRepo, Mailer $mailer)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->accountRepo = $accountRepo;
|
||||
$this->mailer = $mailer;
|
||||
}
|
||||
|
||||
public function showSetup()
|
||||
{
|
||||
if (Utils::isNinja() || Utils::isDatabaseSetup()) {
|
||||
return Redirect::to('/');
|
||||
}
|
||||
|
||||
return View::make('setup');
|
||||
}
|
||||
|
||||
public function doSetup()
|
||||
{
|
||||
if (Utils::isNinja() || Utils::isDatabaseSetup()) {
|
||||
return Redirect::to('/');
|
||||
}
|
||||
|
||||
$valid = false;
|
||||
$test = Input::get('test');
|
||||
|
||||
$app = Input::get('app');
|
||||
$app['key'] = str_random(RANDOM_KEY_LENGTH);
|
||||
$app['debug'] = false;
|
||||
|
||||
$database = Input::get('database');
|
||||
$dbType = $database['default'];
|
||||
$database['connections'] = [$dbType => $database['type']];
|
||||
unset($database['type']);
|
||||
|
||||
$mail = Input::get('mail');
|
||||
$email = $mail['username'];
|
||||
$mail['from']['address'] = $email;
|
||||
|
||||
if ($test == 'mail') {
|
||||
return self::testMail($mail);
|
||||
}
|
||||
|
||||
$valid = self::testDatabase($database);
|
||||
|
||||
if ($test == 'db') {
|
||||
return $valid === true ? 'Success' : $valid;
|
||||
} elseif (!$valid) {
|
||||
return Redirect::to('/setup')->withInput();
|
||||
}
|
||||
|
||||
$content = "<?php return 'production';";
|
||||
$fp = fopen(base_path()."/bootstrap/environment.php", 'w');
|
||||
fwrite($fp, $content);
|
||||
fclose($fp);
|
||||
|
||||
$configDir = app_path().'/config/production';
|
||||
if (!file_exists($configDir)) {
|
||||
mkdir($configDir);
|
||||
}
|
||||
|
||||
foreach (['app' => $app, 'database' => $database, 'mail' => $mail] as $key => $config) {
|
||||
$content = '<?php return '.var_export($config, true).';';
|
||||
$fp = fopen(app_path()."/config/production/{$key}.php", 'w');
|
||||
fwrite($fp, $content);
|
||||
fclose($fp);
|
||||
}
|
||||
|
||||
Artisan::call('migrate');
|
||||
Artisan::call('db:seed');
|
||||
|
||||
$account = $this->accountRepo->create();
|
||||
$user = $account->users()->first();
|
||||
|
||||
$user->first_name = trim(Input::get('first_name'));
|
||||
$user->last_name = trim(Input::get('last_name'));
|
||||
$user->email = trim(strtolower(Input::get('email')));
|
||||
$user->username = $user->email;
|
||||
$user->password = trim(Input::get('password'));
|
||||
$user->password_confirmation = trim(Input::get('password'));
|
||||
$user->registered = true;
|
||||
$user->amend();
|
||||
|
||||
//Auth::login($user, true);
|
||||
$this->accountRepo->registerUser($user);
|
||||
|
||||
return Redirect::to('/invoices/create');
|
||||
}
|
||||
|
||||
private function testDatabase($database)
|
||||
{
|
||||
$dbType = $database['default'];
|
||||
|
||||
Config::set('database.default', $dbType);
|
||||
|
||||
foreach ($database['connections'][$dbType] as $key => $val) {
|
||||
Config::set("database.connections.{$dbType}.{$key}", $val);
|
||||
}
|
||||
|
||||
try {
|
||||
$valid = DB::connection()->getDatabaseName() ? true : false;
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
return $valid;
|
||||
}
|
||||
|
||||
private function testMail($mail)
|
||||
{
|
||||
$email = $mail['username'];
|
||||
$fromName = $mail['from']['name'];
|
||||
|
||||
foreach ($mail as $key => $val) {
|
||||
Config::set("mail.{$key}", $val);
|
||||
}
|
||||
|
||||
Config::set('mail.from.address', $email);
|
||||
Config::set('mail.from.name', $fromName);
|
||||
|
||||
$data = [
|
||||
'text' => 'Test email',
|
||||
];
|
||||
|
||||
try {
|
||||
$this->mailer->sendTo($email, $email, $fromName, 'Test email', 'contact', $data);
|
||||
|
||||
return 'Sent';
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public function install()
|
||||
{
|
||||
if (!Utils::isNinja() && !Utils::isDatabaseSetup()) {
|
||||
try {
|
||||
Artisan::call('migrate');
|
||||
Artisan::call('db:seed');
|
||||
} catch (Exception $e) {
|
||||
Response::make($e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
return Redirect::to('/');
|
||||
}
|
||||
|
||||
public function update()
|
||||
{
|
||||
if (!Utils::isNinja()) {
|
||||
try {
|
||||
Artisan::call('migrate');
|
||||
Cache::flush();
|
||||
} catch (Exception $e) {
|
||||
Response::make($e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
return Redirect::to('/');
|
||||
}
|
||||
}
|
21
app/Http/Controllers/BaseController.php
Normal file
21
app/Http/Controllers/BaseController.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php namespace App\Http\Controllers;
|
||||
|
||||
class BaseController extends Controller
|
||||
{
|
||||
/**
|
||||
* Setup the layout used by the controller.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function setupLayout()
|
||||
{
|
||||
if (! is_null($this->layout)) {
|
||||
$this->layout = View::make($this->layout);
|
||||
}
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->beforeFilter('csrf', array('on' => array('post', 'delete', 'put')));
|
||||
}
|
||||
}
|
51
app/Http/Controllers/ClientApiController.php
Normal file
51
app/Http/Controllers/ClientApiController.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
use ninja\repositories\ClientRepository;
|
||||
|
||||
class ClientApiController extends Controller
|
||||
{
|
||||
protected $clientRepo;
|
||||
|
||||
public function __construct(ClientRepository $clientRepo)
|
||||
{
|
||||
$this->clientRepo = $clientRepo;
|
||||
}
|
||||
|
||||
public function ping()
|
||||
{
|
||||
$headers = Utils::getApiHeaders();
|
||||
|
||||
return Response::make('', 200, $headers);
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
$clients = Client::scope()->with('contacts')->orderBy('created_at', 'desc')->get();
|
||||
$clients = Utils::remapPublicIds($clients->toArray());
|
||||
|
||||
$response = json_encode($clients, JSON_PRETTY_PRINT);
|
||||
$headers = Utils::getApiHeaders(count($clients));
|
||||
|
||||
return Response::make($response, 200, $headers);
|
||||
}
|
||||
|
||||
public function store()
|
||||
{
|
||||
$data = Input::all();
|
||||
$error = $this->clientRepo->getErrors($data);
|
||||
|
||||
if ($error) {
|
||||
$headers = Utils::getApiHeaders();
|
||||
|
||||
return Response::make($error, 500, $headers);
|
||||
} else {
|
||||
$client = $this->clientRepo->save(false, $data, false);
|
||||
$client->load('contacts');
|
||||
$client = Utils::remapPublicIds($client->toArray());
|
||||
$response = json_encode($client, JSON_PRETTY_PRINT);
|
||||
$headers = Utils::getApiHeaders();
|
||||
|
||||
return Response::make($response, 200, $headers);
|
||||
}
|
||||
}
|
||||
}
|
278
app/Http/Controllers/ClientController.php
Normal file
278
app/Http/Controllers/ClientController.php
Normal file
@ -0,0 +1,278 @@
|
||||
<?php
|
||||
|
||||
use ninja\repositories\ClientRepository;
|
||||
|
||||
class ClientController extends \BaseController
|
||||
{
|
||||
protected $clientRepo;
|
||||
|
||||
public function __construct(ClientRepository $clientRepo)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->clientRepo = $clientRepo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
return View::make('list', array(
|
||||
'entityType' => ENTITY_CLIENT,
|
||||
'title' => trans('texts.clients'),
|
||||
'columns' => Utils::trans(['checkbox', 'client', 'contact', 'email', 'date_created', 'last_login', 'balance', 'action']),
|
||||
));
|
||||
}
|
||||
|
||||
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('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('invoices/create/'.$model->public_id).'">'.trans('texts.new_invoice').'</a></li>
|
||||
<li><a href="'.URL::to('payments/create/'.$model->public_id).'">'.trans('texts.new_payment').'</a></li>
|
||||
<li><a href="'.URL::to('credits/create/'.$model->public_id).'">'.trans('texts.new_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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function store()
|
||||
{
|
||||
return $this->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param int $id
|
||||
* @return Response
|
||||
*/
|
||||
public function show($publicId)
|
||||
{
|
||||
$client = Client::withTrashed()->scope($publicId)->with('contacts', 'size', 'industry')->firstOrFail();
|
||||
Utils::trackViewed($client->getDisplayName(), ENTITY_CLIENT);
|
||||
|
||||
$actionLinks = [
|
||||
[trans('texts.create_invoice'), URL::to('invoices/create/'.$client->public_id)],
|
||||
[trans('texts.enter_payment'), URL::to('payments/create/'.$client->public_id)],
|
||||
[trans('texts.enter_credit'), URL::to('credits/create/'.$client->public_id)],
|
||||
];
|
||||
|
||||
if (Utils::isPro()) {
|
||||
array_unshift($actionLinks, [trans('texts.create_quote'), URL::to('quotes/create/'.$client->public_id)]);
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'actionLinks' => $actionLinks,
|
||||
'showBreadcrumbs' => false,
|
||||
'client' => $client,
|
||||
'credit' => $client->getTotalCredit(),
|
||||
'title' => trans('texts.view_client'),
|
||||
'hasRecurringInvoices' => Invoice::scope()->where('is_recurring', '=', true)->whereClientId($client->id)->count() > 0,
|
||||
'gatewayLink' => $client->getGatewayLink(),
|
||||
);
|
||||
|
||||
return View::make('clients.show', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
if (Client::scope()->count() > Auth::user()->getMaxNumClients()) {
|
||||
return View::make('error', ['hideHeader' => true, 'error' => "Sorry, you've exceeded the limit of ".Auth::user()->getMaxNumClients()." clients"]);
|
||||
}
|
||||
|
||||
$data = [
|
||||
'client' => null,
|
||||
'method' => 'POST',
|
||||
'url' => 'clients',
|
||||
'title' => trans('texts.new_client'),
|
||||
];
|
||||
|
||||
$data = array_merge($data, self::getViewModel());
|
||||
|
||||
return View::make('clients.edit', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*
|
||||
* @param int $id
|
||||
* @return Response
|
||||
*/
|
||||
public function edit($publicId)
|
||||
{
|
||||
$client = Client::scope($publicId)->with('contacts')->firstOrFail();
|
||||
$data = [
|
||||
'client' => $client,
|
||||
'method' => 'PUT',
|
||||
'url' => 'clients/'.$publicId,
|
||||
'title' => trans('texts.edit_client'),
|
||||
];
|
||||
|
||||
$data = array_merge($data, self::getViewModel());
|
||||
|
||||
return View::make('clients.edit', $data);
|
||||
}
|
||||
|
||||
private static function getViewModel()
|
||||
{
|
||||
return [
|
||||
'sizes' => Size::remember(DEFAULT_QUERY_CACHE)->orderBy('id')->get(),
|
||||
'paymentTerms' => PaymentTerm::remember(DEFAULT_QUERY_CACHE)->orderBy('num_days')->get(['name', 'num_days']),
|
||||
'industries' => Industry::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
|
||||
'currencies' => Currency::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
|
||||
'countries' => Country::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
|
||||
'customLabel1' => Auth::user()->account->custom_client_label1,
|
||||
'customLabel2' => Auth::user()->account->custom_client_label2,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param int $id
|
||||
* @return Response
|
||||
*/
|
||||
public function update($publicId)
|
||||
{
|
||||
return $this->save($publicId);
|
||||
}
|
||||
|
||||
private function save($publicId = null)
|
||||
{
|
||||
$rules = array(
|
||||
'email' => 'required',
|
||||
);
|
||||
$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(strtolower($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);
|
||||
}
|
||||
}
|
||||
|
||||
public function bulk()
|
||||
{
|
||||
$action = Input::get('action');
|
||||
$ids = Input::get('id') ? Input::get('id') : Input::get('ids');
|
||||
$count = $this->clientRepo->bulk($ids, $action);
|
||||
|
||||
$message = Utils::pluralize($action.'d_client', $count);
|
||||
Session::flash('message', $message);
|
||||
|
||||
if ($action == 'restore' && $count == 1) {
|
||||
return Redirect::to('clients/'.$ids[0]);
|
||||
} else {
|
||||
return Redirect::to('clients');
|
||||
}
|
||||
}
|
||||
}
|
11
app/Http/Controllers/Controller.php
Normal file
11
app/Http/Controllers/Controller.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Foundation\Bus\DispatchesCommands;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
|
||||
abstract class Controller extends BaseController {
|
||||
|
||||
use DispatchesCommands, ValidatesRequests;
|
||||
|
||||
}
|
149
app/Http/Controllers/CreditController.php
Normal file
149
app/Http/Controllers/CreditController.php
Normal file
@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
use ninja\repositories\CreditRepository;
|
||||
|
||||
class CreditController extends \BaseController
|
||||
{
|
||||
protected $creditRepo;
|
||||
|
||||
public function __construct(CreditRepository $creditRepo)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->creditRepo = $creditRepo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
return View::make('list', array(
|
||||
'entityType' => ENTITY_CREDIT,
|
||||
'title' => trans('texts.credits'),
|
||||
'columns' => Utils::trans(['checkbox', 'client', 'credit_amount', 'credit_balance', 'credit_date', 'private_notes', 'action']),
|
||||
));
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
public function create($clientPublicId = 0)
|
||||
{
|
||||
$data = array(
|
||||
'clientPublicId' => Input::old('client') ? Input::old('client') : $clientPublicId,
|
||||
//'invoicePublicId' => Input::old('invoice') ? Input::old('invoice') : $invoicePublicId,
|
||||
'credit' => null,
|
||||
'method' => 'POST',
|
||||
'url' => 'credits',
|
||||
'title' => trans('texts.new_credit'),
|
||||
//'currencies' => Currency::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
|
||||
//'invoices' => Invoice::scope()->with('client', 'invoice_status')->orderBy('invoice_number')->get(),
|
||||
'clients' => Client::scope()->with('contacts')->orderBy('name')->get(), );
|
||||
|
||||
return View::make('credits.edit', $data);
|
||||
}
|
||||
|
||||
public function edit($publicId)
|
||||
{
|
||||
$credit = Credit::scope($publicId)->firstOrFail();
|
||||
$credit->credit_date = Utils::fromSqlDate($credit->credit_date);
|
||||
|
||||
$data = array(
|
||||
'client' => null,
|
||||
'credit' => $credit,
|
||||
'method' => 'PUT',
|
||||
'url' => 'credits/'.$publicId,
|
||||
'title' => 'Edit Credit',
|
||||
//'currencies' => Currency::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
|
||||
'clients' => Client::scope()->with('contacts')->orderBy('name')->get(), );
|
||||
|
||||
return View::make('credit.edit', $data);
|
||||
}
|
||||
|
||||
public function store()
|
||||
{
|
||||
return $this->save();
|
||||
}
|
||||
|
||||
public function update($publicId)
|
||||
{
|
||||
return $this->save($publicId);
|
||||
}
|
||||
|
||||
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'));
|
||||
}
|
||||
}
|
||||
|
||||
public function bulk()
|
||||
{
|
||||
$action = Input::get('action');
|
||||
$ids = Input::get('id') ? Input::get('id') : Input::get('ids');
|
||||
$count = $this->creditRepo->bulk($ids, $action);
|
||||
|
||||
if ($count > 0) {
|
||||
$message = Utils::pluralize($action.'d_credit', $count);
|
||||
Session::flash('message', $message);
|
||||
}
|
||||
|
||||
return Redirect::to('credits');
|
||||
}
|
||||
}
|
79
app/Http/Controllers/DashboardController.php
Normal file
79
app/Http/Controllers/DashboardController.php
Normal file
@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
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,
|
||||
COUNT(DISTINCT clients.id) active_clients');
|
||||
$metrics = DB::table('accounts')
|
||||
->select($select)
|
||||
->leftJoin('clients', 'accounts.id', '=', 'clients.account_id')
|
||||
->leftJoin('invoices', 'clients.id', '=', 'invoices.client_id')
|
||||
->where('accounts.id', '=', Auth::user()->account_id)
|
||||
->where('clients.is_deleted', '=', false)
|
||||
->where('invoices.is_deleted', '=', false)
|
||||
->where('invoices.is_recurring', '=', false)
|
||||
->where('invoices.is_quote', '=', false)
|
||||
->groupBy('accounts.id')
|
||||
->first();
|
||||
|
||||
$select = DB::raw('SUM(clients.paid_to_date) as value, clients.currency_id as currency_id');
|
||||
$paidToDate = DB::table('accounts')
|
||||
->select($select)
|
||||
->leftJoin('clients', 'accounts.id', '=', 'clients.account_id')
|
||||
->where('accounts.id', '=', Auth::user()->account_id)
|
||||
->where('clients.is_deleted', '=', false)
|
||||
->groupBy('accounts.id')
|
||||
->groupBy(DB::raw('CASE WHEN clients.currency_id IS NULL THEN CASE WHEN accounts.currency_id IS NULL THEN 1 ELSE accounts.currency_id END ELSE clients.currency_id END'))
|
||||
->get();
|
||||
|
||||
$select = DB::raw('AVG(invoices.amount) as invoice_avg, clients.currency_id as currency_id');
|
||||
$averageInvoice = DB::table('accounts')
|
||||
->select($select)
|
||||
->leftJoin('clients', 'accounts.id', '=', 'clients.account_id')
|
||||
->leftJoin('invoices', 'clients.id', '=', 'invoices.client_id')
|
||||
->where('accounts.id', '=', Auth::user()->account_id)
|
||||
->where('clients.is_deleted', '=', false)
|
||||
->where('invoices.is_deleted', '=', false)
|
||||
->groupBy('accounts.id')
|
||||
->groupBy(DB::raw('CASE WHEN clients.currency_id IS NULL THEN CASE WHEN accounts.currency_id IS NULL THEN 1 ELSE accounts.currency_id END ELSE clients.currency_id END'))
|
||||
->get();
|
||||
|
||||
|
||||
|
||||
$activities = Activity::where('activities.account_id', '=', Auth::user()->account_id)
|
||||
->orderBy('created_at', 'desc')->take(6)->get();
|
||||
|
||||
$pastDue = Invoice::scope()
|
||||
->where('due_date', '<', date('Y-m-d'))
|
||||
->where('balance', '>', 0)
|
||||
->where('is_recurring', '=', false)
|
||||
->where('is_quote', '=', false)
|
||||
->where('is_deleted', '=', false)
|
||||
->orderBy('due_date', 'asc')->take(6)->get();
|
||||
|
||||
$upcoming = Invoice::scope()
|
||||
->where('due_date', '>=', date('Y-m-d'))
|
||||
->where('balance', '>', 0)
|
||||
->where('is_recurring', '=', false)
|
||||
->where('is_quote', '=', false)
|
||||
->where('is_deleted', '=', false)
|
||||
->orderBy('due_date', 'asc')->take(6)->get();
|
||||
|
||||
$data = [
|
||||
'paidToDate' => $paidToDate,
|
||||
'averageInvoice' => $averageInvoice,
|
||||
'billedClients' => $metrics ? $metrics->billed_clients : 0,
|
||||
'invoicesSent' => $metrics ? $metrics->invoices_sent : 0,
|
||||
'activeClients' => $metrics ? $metrics->active_clients : 0,
|
||||
'activities' => $activities,
|
||||
'pastDue' => $pastDue,
|
||||
'upcoming' => $upcoming,
|
||||
];
|
||||
|
||||
return View::make('dashboard', $data);
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
<?php
|
||||
<?php namespace App\Http\Controllers;
|
||||
|
||||
use App\libraries\Utils;
|
||||
use ninja\mailers\Mailer;
|
||||
|
||||
class HomeController extends BaseController
|
||||
|
26
app/Http/Controllers/IntegrationController.php
Normal file
26
app/Http/Controllers/IntegrationController.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
class IntegrationController extends Controller
|
||||
{
|
||||
public function subscribe()
|
||||
{
|
||||
$eventId = Utils::lookupEventId(trim(Input::get('event')));
|
||||
|
||||
if (!$eventId) {
|
||||
return Response::json('', 500);
|
||||
}
|
||||
|
||||
$subscription = Subscription::where('account_id', '=', Auth::user()->account_id)->where('event_id', '=', $eventId)->first();
|
||||
|
||||
if (!$subscription) {
|
||||
$subscription = new Subscription();
|
||||
$subscription->account_id = Auth::user()->account_id;
|
||||
$subscription->event_id = $eventId;
|
||||
}
|
||||
|
||||
$subscription->target_url = trim(Input::get('target_url'));
|
||||
$subscription->save();
|
||||
|
||||
return Response::json('{"id":'.$subscription->id.'}', 201);
|
||||
}
|
||||
}
|
192
app/Http/Controllers/InvoiceApiController.php
Normal file
192
app/Http/Controllers/InvoiceApiController.php
Normal file
@ -0,0 +1,192 @@
|
||||
<?php
|
||||
|
||||
use ninja\repositories\InvoiceRepository;
|
||||
use ninja\mailers\ContactMailer as Mailer;
|
||||
|
||||
class InvoiceApiController extends Controller
|
||||
{
|
||||
protected $invoiceRepo;
|
||||
|
||||
public function __construct(InvoiceRepository $invoiceRepo, Mailer $mailer)
|
||||
{
|
||||
$this->invoiceRepo = $invoiceRepo;
|
||||
$this->mailer = $mailer;
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
$invoices = Invoice::scope()->where('invoices.is_quote', '=', false)->orderBy('created_at', 'desc')->get();
|
||||
$invoices = Utils::remapPublicIds($invoices->toArray());
|
||||
|
||||
$response = json_encode($invoices, JSON_PRETTY_PRINT);
|
||||
$headers = Utils::getApiHeaders(count($invoices));
|
||||
|
||||
return Response::make($response, 200, $headers);
|
||||
}
|
||||
|
||||
public function store()
|
||||
{
|
||||
$data = Input::all();
|
||||
$error = null;
|
||||
|
||||
// check if the invoice number is set and unique
|
||||
if (!isset($data['invoice_number'])) {
|
||||
$data['invoice_number'] = Auth::user()->account->getNextInvoiceNumber();
|
||||
} else {
|
||||
$invoice = Invoice::scope()->where('invoice_number', '=', $data['invoice_number'])->first();
|
||||
if ($invoice) {
|
||||
$error = trans('validation.unique', ['attribute' => 'texts.invoice_number']);
|
||||
}
|
||||
}
|
||||
|
||||
// check the client id is set and exists
|
||||
if (!isset($data['client_id'])) {
|
||||
$error = trans('validation.required', ['attribute' => 'client_id']);
|
||||
} else {
|
||||
$client = Client::scope($data['client_id'])->first();
|
||||
if (!$client) {
|
||||
$error = trans('validation.not_in', ['attribute' => 'client_id']);
|
||||
}
|
||||
}
|
||||
|
||||
if ($error) {
|
||||
$response = json_encode($error, JSON_PRETTY_PRINT);
|
||||
} else {
|
||||
$data = self::prepareData($data);
|
||||
$invoice = $this->invoiceRepo->save(false, $data, false);
|
||||
|
||||
$invitation = Invitation::createNew();
|
||||
$invitation->invoice_id = $invoice->id;
|
||||
$invitation->contact_id = $client->contacts[0]->id;
|
||||
$invitation->invitation_key = str_random(RANDOM_KEY_LENGTH);
|
||||
$invitation->save();
|
||||
|
||||
// prepare the return data
|
||||
$invoice->load('invoice_items');
|
||||
$invoice = $invoice->toArray();
|
||||
$invoice['link'] = $invitation->getLink();
|
||||
unset($invoice['account']);
|
||||
unset($invoice['client']);
|
||||
$invoice = Utils::remapPublicIds($invoice);
|
||||
$invoice['client_id'] = $client->public_id;
|
||||
|
||||
$response = json_encode($invoice, JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
$headers = Utils::getApiHeaders();
|
||||
|
||||
return Response::make($response, $error ? 400 : 200, $headers);
|
||||
}
|
||||
|
||||
private function prepareData($data)
|
||||
{
|
||||
$account = Auth::user()->account;
|
||||
$account->loadLocalizationSettings();
|
||||
|
||||
// set defaults for optional fields
|
||||
$fields = [
|
||||
'discount' => 0,
|
||||
'is_amount_discount' => false,
|
||||
'terms' => '',
|
||||
'invoice_footer' => '',
|
||||
'public_notes' => '',
|
||||
'po_number' => '',
|
||||
'invoice_design_id' => $account->invoice_design_id,
|
||||
'invoice_items' => [],
|
||||
'custom_value1' => 0,
|
||||
'custom_value2' => 0,
|
||||
'custom_taxes1' => false,
|
||||
'custom_taxes2' => false,
|
||||
];
|
||||
|
||||
if (!isset($data['invoice_date'])) {
|
||||
$fields['invoice_date_sql'] = date_create()->format('Y-m-d');
|
||||
}
|
||||
if (!isset($data['due_date'])) {
|
||||
$fields['due_date_sql'] = false;
|
||||
}
|
||||
|
||||
foreach ($fields as $key => $val) {
|
||||
if (!isset($data[$key])) {
|
||||
$data[$key] = $val;
|
||||
}
|
||||
}
|
||||
|
||||
// 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)];
|
||||
} else {
|
||||
foreach ($data['invoice_items'] as $index => $item) {
|
||||
$data['invoice_items'][$index] = self::prepareItem($item);
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function prepareItem($item)
|
||||
{
|
||||
$fields = [
|
||||
'cost' => 0,
|
||||
'product_key' => '',
|
||||
'notes' => '',
|
||||
'qty' => 1
|
||||
];
|
||||
|
||||
foreach ($fields as $key => $val) {
|
||||
if (!isset($item[$key])) {
|
||||
$item[$key] = $val;
|
||||
}
|
||||
}
|
||||
|
||||
// if only the product key is set we'll load the cost and notes
|
||||
if ($item['product_key'] && (!$item['cost'] || !$item['notes'])) {
|
||||
$product = Product::findProductByKey($item['product_key']);
|
||||
if ($product) {
|
||||
if (!$item['cost']) {
|
||||
$item['cost'] = $product->cost;
|
||||
}
|
||||
if (!$item['notes']) {
|
||||
$item['notes'] = $product->notes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
public function emailInvoice()
|
||||
{
|
||||
$data = Input::all();
|
||||
$error = null;
|
||||
|
||||
if (!isset($data['id'])) {
|
||||
$error = trans('validation.required', ['attribute' => 'id']);
|
||||
} else {
|
||||
$invoice = Invoice::scope($data['id'])->first();
|
||||
if (!$invoice) {
|
||||
$error = trans('validation.not_in', ['attribute' => 'id']);
|
||||
} else {
|
||||
$this->mailer->sendInvoice($invoice);
|
||||
}
|
||||
}
|
||||
|
||||
if ($error) {
|
||||
$response = json_encode($error, JSON_PRETTY_PRINT);
|
||||
} else {
|
||||
$response = json_encode(RESULT_SUCCESS, JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
$headers = Utils::getApiHeaders();
|
||||
return Response::make($response, $error ? 400 : 200, $headers);
|
||||
}
|
||||
}
|
542
app/Http/Controllers/InvoiceController.php
Normal file
542
app/Http/Controllers/InvoiceController.php
Normal file
@ -0,0 +1,542 @@
|
||||
<?php
|
||||
|
||||
use ninja\mailers\ContactMailer as Mailer;
|
||||
use ninja\repositories\InvoiceRepository;
|
||||
use ninja\repositories\ClientRepository;
|
||||
use ninja\repositories\TaxRateRepository;
|
||||
|
||||
class InvoiceController extends \BaseController
|
||||
{
|
||||
protected $mailer;
|
||||
protected $invoiceRepo;
|
||||
protected $clientRepo;
|
||||
protected $taxRateRepo;
|
||||
|
||||
public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, TaxRateRepository $taxRateRepo)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->mailer = $mailer;
|
||||
$this->invoiceRepo = $invoiceRepo;
|
||||
$this->clientRepo = $clientRepo;
|
||||
$this->taxRateRepo = $taxRateRepo;
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
$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']),
|
||||
];
|
||||
|
||||
$recurringInvoices = Invoice::scope()->where('is_recurring', '=', true);
|
||||
|
||||
if (Session::get('show_trash:invoice')) {
|
||||
$recurringInvoices->withTrashed();
|
||||
}
|
||||
|
||||
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();
|
||||
$color = $invitation->account->primary_color ? $invitation->account->primary_color : '#0b4d78';
|
||||
|
||||
$data = [
|
||||
'color' => $color,
|
||||
'hideLogo' => Session::get('white_label'),
|
||||
'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);
|
||||
}
|
||||
|
||||
public function getDatatable($clientPublicId = null)
|
||||
{
|
||||
$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);
|
||||
}
|
||||
|
||||
public function getRecurringDatatable($clientPublicId = null)
|
||||
{
|
||||
$query = $this->invoiceRepo->getRecurringInvoices(Auth::user()->account_id, $clientPublicId, Input::get('sSearch'));
|
||||
$table = Datatable::query($query);
|
||||
|
||||
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;
|
||||
|
||||
if (!$client || $client->is_deleted) {
|
||||
return View::make('invoices.deleted');
|
||||
}
|
||||
|
||||
if (!Session::has($invitationKey) && (!Auth::check() || Auth::user()->account_id != $invoice->account_id)) {
|
||||
Activity::viewInvoice($invitation);
|
||||
Event::fire('invoice.viewed', $invoice);
|
||||
}
|
||||
|
||||
Session::set($invitationKey, true);
|
||||
Session::set('invitation_key', $invitationKey);
|
||||
Session::set('white_label', $client->account->isWhiteLabel());
|
||||
|
||||
$client->account->loadLocalizationSettings();
|
||||
|
||||
$invoice->invoice_date = Utils::fromSqlDate($invoice->invoice_date);
|
||||
$invoice->due_date = Utils::fromSqlDate($invoice->due_date);
|
||||
$invoice->is_pro = $client->account->isPro();
|
||||
|
||||
$contact = $invitation->contact;
|
||||
$contact->setVisible([
|
||||
'first_name',
|
||||
'last_name',
|
||||
'email',
|
||||
'phone', ]);
|
||||
|
||||
$data = array(
|
||||
'isConverted' => $invoice->quote_invoice_id ? true : false,
|
||||
'showBreadcrumbs' => false,
|
||||
'hideLogo' => $client->account->isWhiteLabel(),
|
||||
'invoice' => $invoice->hidePrivateFields(),
|
||||
'invitation' => $invitation,
|
||||
'invoiceLabels' => $client->account->getInvoiceLabels(),
|
||||
'contact' => $contact,
|
||||
'hasToken' => $client->getGatewayToken(),
|
||||
'countGateways' => AccountGateway::scope(false, $client->account->id)->count(),
|
||||
);
|
||||
|
||||
return View::make('invoices.view', $data);
|
||||
}
|
||||
|
||||
public function edit($publicId, $clone = false)
|
||||
{
|
||||
$invoice = Invoice::scope($publicId)->withTrashed()->with('invitations', 'account.country', 'client.contacts', 'client.country', 'invoice_items')->firstOrFail();
|
||||
$entityType = $invoice->getEntityType();
|
||||
|
||||
$contactIds = DB::table('invitations')
|
||||
->join('contacts', 'contacts.id', '=', 'invitations.contact_id')
|
||||
->where('invitations.invoice_id', '=', $invoice->id)
|
||||
->where('invitations.account_id', '=', Auth::user()->account_id)
|
||||
->where('invitations.deleted_at', '=', null)
|
||||
->select('contacts.public_id')->lists('public_id');
|
||||
|
||||
if ($clone) {
|
||||
$invoice->id = null;
|
||||
$invoice->invoice_number = Auth::user()->account->getNextInvoiceNumber($invoice->is_quote);
|
||||
$invoice->balance = $invoice->amount;
|
||||
$invoice->invoice_status_id = 0;
|
||||
$invoice->invoice_date = date_create()->format('Y-m-d');
|
||||
$method = 'POST';
|
||||
$url = "{$entityType}s";
|
||||
} else {
|
||||
Utils::trackViewed($invoice->invoice_number.' - '.$invoice->client->getDisplayName(), $invoice->getEntityType());
|
||||
$method = 'PUT';
|
||||
$url = "{$entityType}s/{$publicId}";
|
||||
}
|
||||
|
||||
$invoice->invoice_date = Utils::fromSqlDate($invoice->invoice_date);
|
||||
$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->is_pro = Auth::user()->isPro();
|
||||
|
||||
$data = array(
|
||||
'entityType' => $entityType,
|
||||
'showBreadcrumbs' => $clone,
|
||||
'account' => $invoice->account,
|
||||
'invoice' => $invoice,
|
||||
'data' => false,
|
||||
'method' => $method,
|
||||
'invitationContactIds' => $contactIds,
|
||||
'url' => $url,
|
||||
'title' => trans("texts.edit_{$entityType}"),
|
||||
'client' => $invoice->client, );
|
||||
$data = array_merge($data, self::getViewModel());
|
||||
|
||||
// Set the invitation link on the client's contacts
|
||||
if (!$clone) {
|
||||
$clients = $data['clients'];
|
||||
foreach ($clients as $client) {
|
||||
if ($client->id == $invoice->client->id) {
|
||||
foreach ($invoice->invitations as $invitation) {
|
||||
foreach ($client->contacts as $contact) {
|
||||
if ($invitation->contact_id == $contact->id) {
|
||||
$contact->invitation_link = $invitation->getLink();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return View::make('invoices.edit', $data);
|
||||
}
|
||||
|
||||
public function create($clientPublicId = 0)
|
||||
{
|
||||
$client = null;
|
||||
$invoiceNumber = Auth::user()->account->getNextInvoiceNumber();
|
||||
$account = Account::with('country')->findOrFail(Auth::user()->account_id);
|
||||
|
||||
if ($clientPublicId) {
|
||||
$client = Client::scope($clientPublicId)->firstOrFail();
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'entityType' => ENTITY_INVOICE,
|
||||
'account' => $account,
|
||||
'invoice' => null,
|
||||
'data' => Input::old('data'),
|
||||
'invoiceNumber' => $invoiceNumber,
|
||||
'method' => 'POST',
|
||||
'url' => 'invoices',
|
||||
'title' => trans('texts.new_invoice'),
|
||||
'client' => $client, );
|
||||
$data = array_merge($data, self::getViewModel());
|
||||
|
||||
return View::make('invoices.edit', $data);
|
||||
}
|
||||
|
||||
private static function getViewModel()
|
||||
{
|
||||
$recurringHelp = '';
|
||||
foreach (preg_split("/((\r?\n)|(\r\n?))/", trans('texts.recurring_help')) as $line) {
|
||||
$parts = explode("=>", $line);
|
||||
if (count($parts) > 1) {
|
||||
$line = $parts[0].' => '.Utils::processVariables($parts[0]);
|
||||
$recurringHelp .= '<li>'.strip_tags($line).'</li>';
|
||||
} else {
|
||||
$recurringHelp .= $line;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'account' => Auth::user()->account,
|
||||
'products' => Product::scope()->orderBy('id')->get(array('product_key', 'notes', 'cost', 'qty')),
|
||||
'countries' => Country::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
|
||||
'clients' => Client::scope()->with('contacts', 'country')->orderBy('name')->get(),
|
||||
'taxRates' => TaxRate::scope()->orderBy('name')->get(),
|
||||
'currencies' => Currency::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
|
||||
'sizes' => Size::remember(DEFAULT_QUERY_CACHE)->orderBy('id')->get(),
|
||||
'paymentTerms' => PaymentTerm::remember(DEFAULT_QUERY_CACHE)->orderBy('num_days')->get(['name', 'num_days']),
|
||||
'industries' => Industry::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
|
||||
'invoiceDesigns' => InvoiceDesign::remember(DEFAULT_QUERY_CACHE, 'invoice_designs_cache_'.Auth::user()->maxInvoiceDesignId())
|
||||
->where('id', '<=', Auth::user()->maxInvoiceDesignId())->orderBy('id')->get(),
|
||||
'frequencies' => array(
|
||||
1 => 'Weekly',
|
||||
2 => 'Two weeks',
|
||||
3 => 'Four weeks',
|
||||
4 => 'Monthly',
|
||||
5 => 'Three months',
|
||||
6 => 'Six months',
|
||||
7 => 'Annually',
|
||||
),
|
||||
'recurringHelp' => $recurringHelp
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function store()
|
||||
{
|
||||
return InvoiceController::save();
|
||||
}
|
||||
|
||||
private function save($publicId = null)
|
||||
{
|
||||
$action = Input::get('action');
|
||||
$entityType = Input::get('entityType');
|
||||
|
||||
if (in_array($action, ['archive', 'delete', 'mark', 'restore'])) {
|
||||
return InvoiceController::bulk($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') {
|
||||
$message = $message.' '.trans('texts.and_created_client');
|
||||
|
||||
$url = URL::to('clients/'.$client->public_id);
|
||||
Utils::trackViewed($client->getDisplayName(), ENTITY_CLIENT, $url);
|
||||
}
|
||||
|
||||
if ($action == 'clone') {
|
||||
return $this->cloneInvoice($publicId);
|
||||
} elseif ($action == 'convert') {
|
||||
return $this->convertQuote($publicId);
|
||||
} elseif ($action == 'email') {
|
||||
if (Auth::user()->confirmed && !Auth::user()->isDemo()) {
|
||||
$message = trans("texts.emailed_{$entityType}");
|
||||
$this->mailer->sendInvoice($invoice);
|
||||
Session::flash('message', $message);
|
||||
} 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);
|
||||
}
|
||||
|
||||
$url = "{$entityType}s/".$invoice->public_id.'/edit';
|
||||
|
||||
return Redirect::to($url);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param int $id
|
||||
* @return Response
|
||||
*/
|
||||
public function show($publicId)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param int $id
|
||||
* @return Response
|
||||
*/
|
||||
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);
|
||||
|
||||
if ($count > 0) {
|
||||
$key = $action == 'mark' ? "updated_{$entityType}" : "{$action}d_{$entityType}";
|
||||
$message = Utils::pluralize($key, $count);
|
||||
Session::flash('message', $message);
|
||||
}
|
||||
|
||||
if ($action == 'restore' && $count == 1) {
|
||||
return Redirect::to("{$entityType}s/".$ids[0]);
|
||||
} else {
|
||||
return Redirect::to("{$entityType}s");
|
||||
}
|
||||
}
|
||||
|
||||
public function convertQuote($publicId)
|
||||
{
|
||||
$invoice = Invoice::with('invoice_items')->scope($publicId)->firstOrFail();
|
||||
$clone = $this->invoiceRepo->cloneInvoice($invoice, $invoice->id);
|
||||
|
||||
Session::flash('message', trans('texts.converted_to_invoice'));
|
||||
return Redirect::to('invoices/'.$clone->public_id);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public function invoiceHistory($publicId)
|
||||
{
|
||||
$invoice = Invoice::withTrashed()->scope($publicId)->firstOrFail();
|
||||
$invoice->load('user', 'invoice_items', 'account.country', 'client.contacts', 'client.country');
|
||||
$invoice->invoice_date = Utils::fromSqlDate($invoice->invoice_date);
|
||||
$invoice->due_date = Utils::fromSqlDate($invoice->due_date);
|
||||
$invoice->is_pro = Auth::user()->isPro();
|
||||
$invoice->is_quote = intval($invoice->is_quote);
|
||||
|
||||
$activityTypeId = $invoice->is_quote ? ACTIVITY_TYPE_UPDATE_QUOTE : ACTIVITY_TYPE_UPDATE_INVOICE;
|
||||
$activities = Activity::scope(false, $invoice->account_id)
|
||||
->where('activity_type_id', '=', $activityTypeId)
|
||||
->where('invoice_id', '=', $invoice->id)
|
||||
->orderBy('id', 'desc')
|
||||
->get(['id', 'created_at', 'user_id', 'json_backup', 'message']);
|
||||
|
||||
$versionsJson = [];
|
||||
$versionsSelect = [];
|
||||
$lastId = false;
|
||||
|
||||
foreach ($activities as $activity) {
|
||||
$backup = json_decode($activity->json_backup);
|
||||
$backup->invoice_date = Utils::fromSqlDate($backup->invoice_date);
|
||||
$backup->due_date = Utils::fromSqlDate($backup->due_date);
|
||||
$backup->is_pro = Auth::user()->isPro();
|
||||
$backup->is_quote = isset($backup->is_quote) && intval($backup->is_quote);
|
||||
$backup->account = $invoice->account->toArray();
|
||||
|
||||
$versionsJson[$activity->id] = $backup;
|
||||
$key = Utils::timestampToDateTimeString(strtotime($activity->created_at)) . ' - ' . Utils::decodeActivity($activity->message);
|
||||
$versionsSelect[$lastId ? $lastId : 0] = $key;
|
||||
$lastId = $activity->id;
|
||||
}
|
||||
|
||||
$versionsSelect[$lastId] = Utils::timestampToDateTimeString(strtotime($invoice->created_at)) . ' - ' . $invoice->user->getDisplayName();
|
||||
|
||||
$data = [
|
||||
'invoice' => $invoice,
|
||||
'versionsJson' => json_encode($versionsJson),
|
||||
'versionsSelect' => $versionsSelect,
|
||||
'invoiceDesigns' => InvoiceDesign::remember(DEFAULT_QUERY_CACHE, 'invoice_designs_cache_'.Auth::user()->maxInvoiceDesignId())->where('id', '<=', Auth::user()->maxInvoiceDesignId())->orderBy('id')->get(),
|
||||
];
|
||||
|
||||
return View::make('invoices.history', $data);
|
||||
}
|
||||
}
|
36
app/Http/Controllers/PaymentApiController.php
Normal file
36
app/Http/Controllers/PaymentApiController.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use ninja\repositories\PaymentRepository;
|
||||
|
||||
class PaymentApiController extends Controller
|
||||
{
|
||||
protected $paymentRepo;
|
||||
|
||||
public function __construct(PaymentRepository $paymentRepo)
|
||||
{
|
||||
$this->paymentRepo = $paymentRepo;
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
$payments = Payment::scope()->orderBy('created_at', 'desc')->get();
|
||||
$payments = Utils::remapPublicIds($payments->toArray());
|
||||
|
||||
$response = json_encode($payments, JSON_PRETTY_PRINT);
|
||||
$headers = Utils::getApiHeaders(count($payments));
|
||||
|
||||
return Response::make($response, 200, $headers);
|
||||
}
|
||||
|
||||
/*
|
||||
public function store()
|
||||
{
|
||||
$data = Input::all();
|
||||
$invoice = $this->invoiceRepo->save(false, $data, false);
|
||||
|
||||
$response = json_encode($invoice, JSON_PRETTY_PRINT);
|
||||
$headers = Utils::getApiHeaders();
|
||||
return Response::make($response, 200, $headers);
|
||||
}
|
||||
*/
|
||||
}
|
748
app/Http/Controllers/PaymentController.php
Normal file
748
app/Http/Controllers/PaymentController.php
Normal file
@ -0,0 +1,748 @@
|
||||
<?php
|
||||
|
||||
use ninja\repositories\PaymentRepository;
|
||||
use ninja\repositories\InvoiceRepository;
|
||||
use ninja\repositories\AccountRepository;
|
||||
use ninja\mailers\ContactMailer;
|
||||
|
||||
class PaymentController extends \BaseController
|
||||
{
|
||||
protected $creditRepo;
|
||||
|
||||
public function __construct(PaymentRepository $paymentRepo, InvoiceRepository $invoiceRepo, AccountRepository $accountRepo, ContactMailer $contactMailer)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->paymentRepo = $paymentRepo;
|
||||
$this->invoiceRepo = $invoiceRepo;
|
||||
$this->accountRepo = $accountRepo;
|
||||
$this->contactMailer = $contactMailer;
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
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']),
|
||||
));
|
||||
}
|
||||
|
||||
public function clientIndex()
|
||||
{
|
||||
$invitationKey = Session::get('invitation_key');
|
||||
if (!$invitationKey) {
|
||||
return Redirect::to('/setup');
|
||||
}
|
||||
|
||||
$invitation = Invitation::with('account')->where('invitation_key', '=', $invitationKey)->first();
|
||||
$color = $invitation->account->primary_color ? $invitation->account->primary_color : '#0b4d78';
|
||||
|
||||
$data = [
|
||||
'color' => $color,
|
||||
'hideLogo' => Session::get('white_label'),
|
||||
'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 ? '<i>Online payment</i>' : ''); });
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
public function create($clientPublicId = 0, $invoicePublicId = 0)
|
||||
{
|
||||
$data = array(
|
||||
'clientPublicId' => Input::old('client') ? Input::old('client') : $clientPublicId,
|
||||
'invoicePublicId' => Input::old('invoice') ? Input::old('invoice') : $invoicePublicId,
|
||||
'invoice' => null,
|
||||
'invoices' => Invoice::scope()->where('is_recurring', '=', false)->where('is_quote', '=', false)
|
||||
->with('client', 'invoice_status')->orderBy('invoice_number')->get(),
|
||||
'payment' => null,
|
||||
'method' => 'POST',
|
||||
'url' => "payments",
|
||||
'title' => trans('texts.new_payment'),
|
||||
//'currencies' => Currency::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
|
||||
'paymentTypes' => PaymentType::remember(DEFAULT_QUERY_CACHE)->orderBy('id')->get(),
|
||||
'clients' => Client::scope()->with('contacts')->orderBy('name')->get(), );
|
||||
|
||||
return View::make('payments.edit', $data);
|
||||
}
|
||||
|
||||
public function edit($publicId)
|
||||
{
|
||||
$payment = Payment::scope($publicId)->firstOrFail();
|
||||
$payment->payment_date = Utils::fromSqlDate($payment->payment_date);
|
||||
|
||||
$data = array(
|
||||
'client' => null,
|
||||
'invoice' => null,
|
||||
'invoices' => Invoice::scope()->where('is_recurring', '=', false)->where('is_quote', '=', false)
|
||||
->with('client', 'invoice_status')->orderBy('invoice_number')->get(),
|
||||
'payment' => $payment,
|
||||
'method' => 'PUT',
|
||||
'url' => 'payments/'.$publicId,
|
||||
'title' => trans('texts.edit_payment'),
|
||||
//'currencies' => Currency::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
|
||||
'paymentTypes' => PaymentType::remember(DEFAULT_QUERY_CACHE)->orderBy('id')->get(),
|
||||
'clients' => Client::scope()->with('contacts')->orderBy('name')->get(), );
|
||||
|
||||
return View::make('payments.edit', $data);
|
||||
}
|
||||
|
||||
private function createGateway($accountGateway)
|
||||
{
|
||||
$gateway = Omnipay::create($accountGateway->gateway->provider);
|
||||
$config = json_decode($accountGateway->config);
|
||||
|
||||
/*
|
||||
$gateway->setSolutionType("Sole");
|
||||
$gateway->setLandingPage("Billing");
|
||||
*/
|
||||
|
||||
foreach ($config as $key => $val) {
|
||||
if (!$val) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$function = "set".ucfirst($key);
|
||||
$gateway->$function($val);
|
||||
}
|
||||
|
||||
if (Utils::isNinjaDev()) {
|
||||
$gateway->setTestMode(true);
|
||||
}
|
||||
|
||||
return $gateway;
|
||||
}
|
||||
|
||||
private function getLicensePaymentDetails($input, $affiliate)
|
||||
{
|
||||
$data = self::convertInputForOmnipay($input);
|
||||
$card = new CreditCard($data);
|
||||
|
||||
return [
|
||||
'amount' => $affiliate->price,
|
||||
'card' => $card,
|
||||
'currency' => 'USD',
|
||||
'returnUrl' => URL::to('license_complete'),
|
||||
'cancelUrl' => URL::to('/')
|
||||
];
|
||||
}
|
||||
|
||||
private function convertInputForOmnipay($input)
|
||||
{
|
||||
return [
|
||||
'firstName' => $input['first_name'],
|
||||
'lastName' => $input['last_name'],
|
||||
'number' => $input['card_number'],
|
||||
'expiryMonth' => $input['expiration_month'],
|
||||
'expiryYear' => $input['expiration_year'],
|
||||
'cvv' => $input['cvv'],
|
||||
'billingAddress1' => $input['address1'],
|
||||
'billingAddress2' => $input['address2'],
|
||||
'billingCity' => $input['city'],
|
||||
'billingState' => $input['state'],
|
||||
'billingPostcode' => $input['postal_code'],
|
||||
'shippingAddress1' => $input['address1'],
|
||||
'shippingAddress2' => $input['address2'],
|
||||
'shippingCity' => $input['city'],
|
||||
'shippingState' => $input['state'],
|
||||
'shippingPostcode' => $input['postal_code']
|
||||
];
|
||||
}
|
||||
|
||||
private function getPaymentDetails($invitation, $input = null)
|
||||
{
|
||||
$invoice = $invitation->invoice;
|
||||
$key = $invoice->invoice_number.'_details';
|
||||
$gateway = $invoice->client->account->getGatewayByType(Session::get('payment_type'))->gateway;
|
||||
$paymentLibrary = $gateway->paymentlibrary;
|
||||
$currencyCode = $invoice->client->currency ? $invoice->client->currency->code : ($invoice->account->currency ? $invoice->account->currency->code : 'USD');
|
||||
|
||||
if ($input && $paymentLibrary->id == PAYMENT_LIBRARY_OMNIPAY) {
|
||||
$data = self::convertInputForOmnipay($input);
|
||||
|
||||
Session::put($key, $data);
|
||||
} elseif ($input && $paymentLibrary->id == PAYMENT_LIBRARY_PHP_PAYMENTS) {
|
||||
$input = Input::all();
|
||||
$data = [
|
||||
'first_name' => $input['first_name'],
|
||||
'last_name' => $input['last_name'],
|
||||
'cc_number' => $input['card_number'],
|
||||
'cc_exp' => $input['expiration_month'].$input['expiration_year'],
|
||||
'cc_code' => $input['cvv'],
|
||||
'street' => $input['address1'],
|
||||
'street2' => $input['address2'],
|
||||
'city' => $input['city'],
|
||||
'state' => $input['state'],
|
||||
'postal_code' => $input['postal_code'],
|
||||
'amt' => $invoice->balance,
|
||||
'ship_to_street' => $input['address1'],
|
||||
'ship_to_city' => $input['city'],
|
||||
'ship_to_state' => $input['state'],
|
||||
'ship_to_postal_code' => $input['postal_code'],
|
||||
'currency_code' => $currencyCode,
|
||||
];
|
||||
|
||||
switch ($gateway->id) {
|
||||
case GATEWAY_BEANSTREAM:
|
||||
$data['phone'] = $input['phone'];
|
||||
$data['email'] = $input['email'];
|
||||
$data['country'] = $input['country'];
|
||||
$data['ship_to_country'] = $input['country'];
|
||||
break;
|
||||
case GATEWAY_BRAINTREE:
|
||||
$data['ship_to_state'] = 'Ohio'; //$input['state'];
|
||||
break;
|
||||
}
|
||||
|
||||
if (strlen($data['cc_exp']) == 5) {
|
||||
$data['cc_exp'] = '0'.$data['cc_exp'];
|
||||
}
|
||||
|
||||
Session::put($key, $data);
|
||||
|
||||
return $data;
|
||||
} elseif (Session::get($key)) {
|
||||
$data = Session::get($key);
|
||||
} else {
|
||||
$data = [];
|
||||
}
|
||||
|
||||
if ($paymentLibrary->id == PAYMENT_LIBRARY_OMNIPAY) {
|
||||
$card = new CreditCard($data);
|
||||
|
||||
return [
|
||||
'amount' => $invoice->balance,
|
||||
'card' => $card,
|
||||
'currency' => $currencyCode,
|
||||
'returnUrl' => URL::to('complete'),
|
||||
'cancelUrl' => $invitation->getLink(),
|
||||
'description' => trans('texts.' . $invoice->getEntityType()) . " {$invoice->invoice_number}",
|
||||
];
|
||||
} else {
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
public function show_payment($invitationKey)
|
||||
{
|
||||
// Handle token billing
|
||||
if (Input::get('use_token') == 'true') {
|
||||
return self::do_payment($invitationKey, false, true);
|
||||
}
|
||||
|
||||
if (Input::has('use_paypal')) {
|
||||
Session::put('payment_type', Input::get('use_paypal') == 'true' ? PAYMENT_TYPE_PAYPAL : PAYMENT_TYPE_CREDIT_CARD);
|
||||
} elseif (!Session::has('payment_type')) {
|
||||
Session::put('payment_type', PAYMENT_TYPE_ANY);
|
||||
}
|
||||
|
||||
// For PayPal we redirect straight to their site
|
||||
$usePayPal = false;
|
||||
if ($usePayPal = Input::get('use_paypal')) {
|
||||
$usePayPal = $usePayPal == 'true';
|
||||
} else {
|
||||
$invitation = Invitation::with('invoice.client.account', 'invoice.client.account.account_gateways.gateway')->where('invitation_key', '=', $invitationKey)->firstOrFail();
|
||||
$account = $invitation->invoice->client->account;
|
||||
if (count($account->account_gateways) == 1 && $account->getGatewayByType(PAYMENT_TYPE_PAYPAL)) {
|
||||
$usePayPal = true;
|
||||
}
|
||||
}
|
||||
if ($usePayPal) {
|
||||
if (Session::has('error')) {
|
||||
Session::reflash();
|
||||
return Redirect::to('view/'.$invitationKey);
|
||||
} else {
|
||||
return self::do_payment($invitationKey, 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;
|
||||
$accountGateway = $invoice->client->account->getGatewayByType(Session::get('payment_type'));
|
||||
$gateway = $invoice->client->account->getGatewayByType(Session::get('payment_type'))->gateway;
|
||||
$paymentLibrary = $gateway->paymentlibrary;
|
||||
$acceptedCreditCardTypes = $accountGateway->getCreditcardTypes();
|
||||
|
||||
$data = [
|
||||
'showBreadcrumbs' => false,
|
||||
'url' => 'payment/'.$invitationKey,
|
||||
'amount' => $invoice->balance,
|
||||
'invoiceNumber' => $invoice->invoice_number,
|
||||
'client' => $client,
|
||||
'contact' => $invitation->contact,
|
||||
'paymentLibrary' => $paymentLibrary,
|
||||
'gateway' => $gateway,
|
||||
'acceptedCreditCardTypes' => $acceptedCreditCardTypes,
|
||||
'countries' => Country::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
|
||||
'currencyId' => $client->currency_id,
|
||||
'account' => $client->account
|
||||
];
|
||||
|
||||
return View::make('payments.payment', $data);
|
||||
}
|
||||
|
||||
public function show_license_payment()
|
||||
{
|
||||
if (Input::has('return_url')) {
|
||||
Session::set('return_url', Input::get('return_url'));
|
||||
}
|
||||
|
||||
if (Input::has('affiliate_key')) {
|
||||
if ($affiliate = Affiliate::where('affiliate_key', '=', Input::get('affiliate_key'))->first()) {
|
||||
Session::set('affiliate_id', $affiliate->id);
|
||||
}
|
||||
}
|
||||
|
||||
Session::set('product_id', Input::get('product_id', PRODUCT_ONE_CLICK_INSTALL));
|
||||
|
||||
if (!Session::get('affiliate_id')) {
|
||||
return Utils::fatalError();
|
||||
}
|
||||
|
||||
if (Utils::isNinjaDev() && Input::has('test_mode')) {
|
||||
Session::set('test_mode', Input::get('test_mode'));
|
||||
}
|
||||
|
||||
$account = $this->accountRepo->getNinjaAccount();
|
||||
$account->load('account_gateways.gateway');
|
||||
$accountGateway = $account->getGatewayByType(Session::get('payment_type'));
|
||||
$gateway = $accountGateway->gateway;
|
||||
$paymentLibrary = $gateway->paymentlibrary;
|
||||
$acceptedCreditCardTypes = $accountGateway->getCreditcardTypes();
|
||||
|
||||
$affiliate = Affiliate::find(Session::get('affiliate_id'));
|
||||
|
||||
$data = [
|
||||
'showBreadcrumbs' => false,
|
||||
'hideHeader' => true,
|
||||
'url' => 'license',
|
||||
'amount' => $affiliate->price,
|
||||
'client' => false,
|
||||
'contact' => false,
|
||||
'paymentLibrary' => $paymentLibrary,
|
||||
'gateway' => $gateway,
|
||||
'acceptedCreditCardTypes' => $acceptedCreditCardTypes,
|
||||
'countries' => Country::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
|
||||
'currencyId' => 1,
|
||||
'paymentTitle' => $affiliate->payment_title,
|
||||
'paymentSubtitle' => $affiliate->payment_subtitle,
|
||||
];
|
||||
|
||||
return View::make('payments.payment', $data);
|
||||
}
|
||||
|
||||
public function do_license_payment()
|
||||
{
|
||||
$testMode = Session::get('test_mode') === 'true';
|
||||
|
||||
$rules = array(
|
||||
'first_name' => 'required',
|
||||
'last_name' => 'required',
|
||||
'card_number' => 'required',
|
||||
'expiration_month' => 'required',
|
||||
'expiration_year' => 'required',
|
||||
'cvv' => 'required',
|
||||
'address1' => 'required',
|
||||
'city' => 'required',
|
||||
'state' => 'required',
|
||||
'postal_code' => 'required',
|
||||
);
|
||||
|
||||
$validator = Validator::make(Input::all(), $rules);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return Redirect::to('license')
|
||||
->withErrors($validator);
|
||||
}
|
||||
|
||||
$account = $this->accountRepo->getNinjaAccount();
|
||||
$account->load('account_gateways.gateway');
|
||||
$accountGateway = $account->getGatewayByType(PAYMENT_TYPE_CREDIT_CARD);
|
||||
|
||||
try {
|
||||
$affiliate = Affiliate::find(Session::get('affiliate_id'));
|
||||
|
||||
if ($testMode) {
|
||||
$ref = 'TEST_MODE';
|
||||
} else {
|
||||
$gateway = self::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());
|
||||
|
||||
return Redirect::to('license')->withInput();
|
||||
}
|
||||
}
|
||||
|
||||
$licenseKey = Utils::generateLicense();
|
||||
|
||||
$license = new License();
|
||||
$license->first_name = Input::get('first_name');
|
||||
$license->last_name = Input::get('last_name');
|
||||
$license->email = Input::get('email');
|
||||
$license->transaction_reference = $ref;
|
||||
$license->license_key = $licenseKey;
|
||||
$license->affiliate_id = Session::get('affiliate_id');
|
||||
$license->product_id = Session::get('product_id');
|
||||
$license->save();
|
||||
|
||||
$data = [
|
||||
'message' => $affiliate->payment_subtitle,
|
||||
'license' => $licenseKey,
|
||||
'hideHeader' => true,
|
||||
];
|
||||
|
||||
$name = "{$license->first_name} {$license->last_name}";
|
||||
$this->contactMailer->sendLicensePaymentConfirmation($name, $license->email, $affiliate->price, $license->license_key, $license->product_id);
|
||||
|
||||
if (Session::has('return_url')) {
|
||||
return Redirect::away(Session::get('return_url')."?license_key={$license->license_key}&product_id=".Session::get('product_id'));
|
||||
} else {
|
||||
return View::make('public.license', $data);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$errorMessage = trans('texts.payment_error');
|
||||
Session::flash('error', $errorMessage);
|
||||
Utils::logError(Utils::getErrorString($e));
|
||||
|
||||
return Redirect::to('license')->withInput();
|
||||
}
|
||||
}
|
||||
|
||||
public function claim_license()
|
||||
{
|
||||
$licenseKey = Input::get('license_key');
|
||||
$productId = Input::get('product_id', PRODUCT_ONE_CLICK_INSTALL);
|
||||
|
||||
$license = License::where('license_key', '=', $licenseKey)
|
||||
->where('is_claimed', '=', false)
|
||||
->where('product_id', '=', $productId)
|
||||
->first();
|
||||
|
||||
if ($license) {
|
||||
if ($license->transaction_reference != 'TEST_MODE') {
|
||||
$license->is_claimed = true;
|
||||
$license->save();
|
||||
}
|
||||
|
||||
return $productId == PRODUCT_INVOICE_DESIGNS ? $_ENV['INVOICE_DESIGNS'] : 'valid';
|
||||
} else {
|
||||
return 'invalid';
|
||||
}
|
||||
}
|
||||
|
||||
public function do_payment($invitationKey, $onSite = true, $useToken = false)
|
||||
{
|
||||
$rules = array(
|
||||
'first_name' => 'required',
|
||||
'last_name' => 'required',
|
||||
'card_number' => 'required',
|
||||
'expiration_month' => 'required',
|
||||
'expiration_year' => 'required',
|
||||
'cvv' => 'required',
|
||||
'address1' => 'required',
|
||||
'city' => 'required',
|
||||
'state' => 'required',
|
||||
'postal_code' => 'required',
|
||||
);
|
||||
|
||||
if ($onSite) {
|
||||
$validator = Validator::make(Input::all(), $rules);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return Redirect::to('payment/'.$invitationKey)
|
||||
->withErrors($validator);
|
||||
}
|
||||
}
|
||||
|
||||
$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;
|
||||
$account = $client->account;
|
||||
$accountGateway = $account->getGatewayByType(Session::get('payment_type'));
|
||||
$paymentLibrary = $accountGateway->gateway->paymentlibrary;
|
||||
|
||||
if ($onSite) {
|
||||
$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->save();
|
||||
}
|
||||
|
||||
try {
|
||||
if ($paymentLibrary->id == PAYMENT_LIBRARY_OMNIPAY) {
|
||||
$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();
|
||||
$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;
|
||||
}
|
||||
|
||||
$token->token = $cardReference;
|
||||
$token->save();
|
||||
}
|
||||
}
|
||||
|
||||
$response = $gateway->purchase($details)->send();
|
||||
$ref = $response->getTransactionReference();
|
||||
|
||||
if (!$ref) {
|
||||
|
||||
Session::flash('error', $response->getMessage());
|
||||
|
||||
if ($onSite) {
|
||||
return Redirect::to('payment/'.$invitationKey)->withInput();
|
||||
} else {
|
||||
return Redirect::to('view/'.$invitationKey);
|
||||
}
|
||||
}
|
||||
|
||||
if ($response->isSuccessful()) {
|
||||
$payment = self::createPayment($invitation, $ref);
|
||||
Session::flash('message', trans('texts.applied_payment'));
|
||||
|
||||
return Redirect::to('view/'.$payment->invitation->invitation_key);
|
||||
} elseif ($response->isRedirect()) {
|
||||
$invitation->transaction_reference = $ref;
|
||||
$invitation->save();
|
||||
|
||||
Session::save();
|
||||
$response->redirect();
|
||||
} else {
|
||||
Session::flash('error', $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(Utils::getErrorString($e));
|
||||
|
||||
if ($onSite) {
|
||||
return Redirect::to('payment/'.$invitationKey)->withInput();
|
||||
} 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) {
|
||||
$account = Account::find($invoice->client->public_id);
|
||||
$account->pro_plan_paid = date_create()->format('Y-m-d');
|
||||
$account->save();
|
||||
}
|
||||
|
||||
$payment = Payment::createNew($invitation);
|
||||
$payment->invitation_id = $invitation->id;
|
||||
$payment->account_gateway_id = $accountGateway->id;
|
||||
$payment->invoice_id = $invoice->id;
|
||||
$payment->amount = $invoice->balance;
|
||||
$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('invoice.paid', $payment);
|
||||
|
||||
return $payment;
|
||||
}
|
||||
|
||||
public function offsite_payment()
|
||||
{
|
||||
$payerId = Request::query('PayerID');
|
||||
$token = Request::query('token');
|
||||
|
||||
$invitation = Invitation::with('invoice.client.currency', 'invoice.client.account.account_gateways.gateway')->where('transaction_reference', '=', $token)->firstOrFail();
|
||||
$invoice = $invitation->invoice;
|
||||
|
||||
$accountGateway = $invoice->client->account->getGatewayByType(Session::get('payment_type'));
|
||||
$gateway = self::createGateway($accountGateway);
|
||||
|
||||
try {
|
||||
$details = self::getPaymentDetails($invitation);
|
||||
$response = $gateway->completePurchase($details)->send();
|
||||
$ref = $response->getTransactionReference();
|
||||
|
||||
if ($response->isSuccessful()) {
|
||||
$payment = self::createPayment($invitation, $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);
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
||||
public function store()
|
||||
{
|
||||
return $this->save();
|
||||
}
|
||||
|
||||
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 {
|
||||
$this->paymentRepo->save($publicId, Input::all());
|
||||
|
||||
if ($publicId) {
|
||||
Session::flash('message', trans('texts.updated_payment'));
|
||||
|
||||
return Redirect::to('payments/');
|
||||
} else {
|
||||
Session::flash('message', trans('texts.created_payment'));
|
||||
|
||||
return Redirect::to('clients/'.Input::get('client'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function bulk()
|
||||
{
|
||||
$action = Input::get('action');
|
||||
$ids = Input::get('id') ? Input::get('id') : Input::get('ids');
|
||||
$count = $this->paymentRepo->bulk($ids, $action);
|
||||
|
||||
if ($count > 0) {
|
||||
$message = Utils::pluralize($action.'d_payment', $count);
|
||||
Session::flash('message', $message);
|
||||
}
|
||||
|
||||
return Redirect::to('payments');
|
||||
}
|
||||
}
|
96
app/Http/Controllers/ProductController.php
Normal file
96
app/Http/Controllers/ProductController.php
Normal file
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
class ProductController extends \BaseController
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
public function edit($publicId)
|
||||
{
|
||||
$data = [
|
||||
'showBreadcrumbs' => false,
|
||||
'product' => Product::scope($publicId)->firstOrFail(),
|
||||
'method' => 'PUT',
|
||||
'url' => 'products/'.$publicId,
|
||||
'title' => trans('texts.edit_product'),
|
||||
];
|
||||
|
||||
return View::make('accounts.product', $data);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
$data = [
|
||||
'showBreadcrumbs' => false,
|
||||
'product' => null,
|
||||
'method' => 'POST',
|
||||
'url' => 'products',
|
||||
'title' => trans('texts.create_product'),
|
||||
];
|
||||
|
||||
return View::make('accounts.product', $data);
|
||||
}
|
||||
|
||||
public function store()
|
||||
{
|
||||
return $this->save();
|
||||
}
|
||||
|
||||
public function update($publicId)
|
||||
{
|
||||
return $this->save($publicId);
|
||||
}
|
||||
|
||||
private function save($productPublicId = false)
|
||||
{
|
||||
if ($productPublicId) {
|
||||
$product = Product::scope($productPublicId)->firstOrFail();
|
||||
} else {
|
||||
$product = Product::createNew();
|
||||
}
|
||||
|
||||
$product->product_key = trim(Input::get('product_key'));
|
||||
$product->notes = trim(Input::get('notes'));
|
||||
$product->cost = trim(Input::get('cost'));
|
||||
$product->save();
|
||||
|
||||
$message = $productPublicId ? trans('texts.updated_product') : trans('texts.created_product');
|
||||
Session::flash('message', $message);
|
||||
|
||||
return Redirect::to('company/products');
|
||||
}
|
||||
|
||||
public function archive($publicId)
|
||||
{
|
||||
$product = Product::scope($publicId)->firstOrFail();
|
||||
$product->delete();
|
||||
|
||||
Session::flash('message', trans('texts.archived_product'));
|
||||
|
||||
return Redirect::to('company/products');
|
||||
}
|
||||
}
|
36
app/Http/Controllers/QuoteApiController.php
Normal file
36
app/Http/Controllers/QuoteApiController.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use ninja\repositories\InvoiceRepository;
|
||||
|
||||
class QuoteApiController extends Controller
|
||||
{
|
||||
protected $invoiceRepo;
|
||||
|
||||
public function __construct(InvoiceRepository $invoiceRepo)
|
||||
{
|
||||
$this->invoiceRepo = $invoiceRepo;
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
$invoices = Invoice::scope()->where('invoices.is_quote', '=', true)->orderBy('created_at', 'desc')->get();
|
||||
$invoices = Utils::remapPublicIds($invoices->toArray());
|
||||
|
||||
$response = json_encode($invoices, JSON_PRETTY_PRINT);
|
||||
$headers = Utils::getApiHeaders(count($invoices));
|
||||
|
||||
return Response::make($response, 200, $headers);
|
||||
}
|
||||
|
||||
/*
|
||||
public function store()
|
||||
{
|
||||
$data = Input::all();
|
||||
$invoice = $this->invoiceRepo->save(false, $data, false);
|
||||
|
||||
$response = json_encode($invoice, JSON_PRETTY_PRINT);
|
||||
$headers = Utils::getApiHeaders();
|
||||
return Response::make($response, 200, $headers);
|
||||
}
|
||||
*/
|
||||
}
|
187
app/Http/Controllers/QuoteController.php
Normal file
187
app/Http/Controllers/QuoteController.php
Normal file
@ -0,0 +1,187 @@
|
||||
<?php
|
||||
|
||||
use ninja\mailers\ContactMailer as Mailer;
|
||||
use ninja\repositories\InvoiceRepository;
|
||||
use ninja\repositories\ClientRepository;
|
||||
use ninja\repositories\TaxRateRepository;
|
||||
|
||||
class QuoteController extends \BaseController
|
||||
{
|
||||
protected $mailer;
|
||||
protected $invoiceRepo;
|
||||
protected $clientRepo;
|
||||
protected $taxRateRepo;
|
||||
|
||||
public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, TaxRateRepository $taxRateRepo)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->mailer = $mailer;
|
||||
$this->invoiceRepo = $invoiceRepo;
|
||||
$this->clientRepo = $clientRepo;
|
||||
$this->taxRateRepo = $taxRateRepo;
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
if (!Utils::isPro()) {
|
||||
return Redirect::to('/invoices/create');
|
||||
}
|
||||
|
||||
$data = [
|
||||
'title' => trans('texts.quotes'),
|
||||
'entityType' => ENTITY_QUOTE,
|
||||
'columns' => Utils::trans(['checkbox', 'quote_number', 'client', 'quote_date', 'quote_total', 'due_date', '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();
|
||||
$color = $invitation->account->primary_color ? $invitation->account->primary_color : '#0b4d78';
|
||||
|
||||
$data = [
|
||||
'color' => $color,
|
||||
'hideLogo' => Session::get('white_label'),
|
||||
'title' => trans('texts.quotes'),
|
||||
'entityType' => ENTITY_QUOTE,
|
||||
'columns' => Utils::trans(['quote_number', 'quote_date', 'quote_total', 'due_date']),
|
||||
];
|
||||
|
||||
return View::make('public_list', $data);
|
||||
}
|
||||
|
||||
public function getDatatable($clientPublicId = null)
|
||||
{
|
||||
$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);
|
||||
}
|
||||
|
||||
public function create($clientPublicId = 0)
|
||||
{
|
||||
if (!Utils::isPro()) {
|
||||
return Redirect::to('/invoices/create');
|
||||
}
|
||||
|
||||
$client = null;
|
||||
$invoiceNumber = Auth::user()->account->getNextInvoiceNumber(true);
|
||||
$account = Account::with('country')->findOrFail(Auth::user()->account_id);
|
||||
|
||||
if ($clientPublicId) {
|
||||
$client = Client::scope($clientPublicId)->firstOrFail();
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'account' => $account,
|
||||
'invoice' => null,
|
||||
'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);
|
||||
}
|
||||
|
||||
private static function getViewModel()
|
||||
{
|
||||
return [
|
||||
'entityType' => ENTITY_QUOTE,
|
||||
'account' => Auth::user()->account,
|
||||
'products' => Product::scope()->orderBy('id')->get(array('product_key', 'notes', 'cost', 'qty')),
|
||||
'countries' => Country::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
|
||||
'clients' => Client::scope()->with('contacts', 'country')->orderBy('name')->get(),
|
||||
'taxRates' => TaxRate::scope()->orderBy('name')->get(),
|
||||
'currencies' => Currency::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
|
||||
'sizes' => Size::remember(DEFAULT_QUERY_CACHE)->orderBy('id')->get(),
|
||||
'paymentTerms' => PaymentTerm::remember(DEFAULT_QUERY_CACHE)->orderBy('num_days')->get(['name', 'num_days']),
|
||||
'industries' => Industry::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(),
|
||||
'invoiceDesigns' => InvoiceDesign::remember(DEFAULT_QUERY_CACHE, 'invoice_designs_cache_'.Auth::user()->maxInvoiceDesignId())
|
||||
->where('id', '<=', Auth::user()->maxInvoiceDesignId())->orderBy('id')->get(),
|
||||
'invoiceLabels' => Auth::user()->account->getInvoiceLabels()
|
||||
];
|
||||
}
|
||||
|
||||
public function bulk()
|
||||
{
|
||||
$action = Input::get('action');
|
||||
|
||||
if ($action == 'convert') {
|
||||
$invoice = Invoice::with('invoice_items')->scope(Input::get('id'))->firstOrFail();
|
||||
$clone = $this->invoiceRepo->cloneInvoice($invoice, $invoice->id);
|
||||
|
||||
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);
|
||||
|
||||
if ($count > 0) {
|
||||
$key = $action == 'mark' ? "updated_quote" : "{$action}d_quote";
|
||||
$message = Utils::pluralize($key, $count);
|
||||
Session::flash('message', $message);
|
||||
}
|
||||
|
||||
return Redirect::to('quotes');
|
||||
}
|
||||
|
||||
public function approve($invitationKey)
|
||||
{
|
||||
$invitation = Invitation::with('invoice.invoice_items', 'invoice.invitations')->where('invitation_key', '=', $invitationKey)->firstOrFail();
|
||||
$invoice = $invitation->invoice;
|
||||
|
||||
if ($invoice->is_quote && !$invoice->quote_invoice_id) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Redirect::to("view/{$invitationKey}");
|
||||
}
|
||||
}
|
127
app/Http/Controllers/ReportController.php
Normal file
127
app/Http/Controllers/ReportController.php
Normal file
@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
class ReportController extends \BaseController
|
||||
{
|
||||
public function d3()
|
||||
{
|
||||
$message = '';
|
||||
|
||||
if (Auth::user()->account->isPro()) {
|
||||
$account = Account::where('id', '=', Auth::user()->account->id)->with(['clients.invoices.invoice_items', 'clients.contacts'])->first();
|
||||
$account = $account->hideFieldsForViz();
|
||||
$clients = $account->clients->toJson();
|
||||
} elseif (isset($_ENV['DATA_VIZ_SAMPLE'])) {
|
||||
$clients = $_ENV['DATA_VIZ_SAMPLE'];
|
||||
$message = trans('texts.sample_data');
|
||||
} else {
|
||||
$clients = '[]';
|
||||
}
|
||||
|
||||
$data = [
|
||||
'feature' => ACCOUNT_DATA_VISUALIZATIONS,
|
||||
'clients' => $clients,
|
||||
'message' => $message,
|
||||
];
|
||||
|
||||
return View::make('reports.d3', $data);
|
||||
}
|
||||
|
||||
public function report()
|
||||
{
|
||||
if (Input::all()) {
|
||||
$groupBy = Input::get('group_by');
|
||||
$chartType = Input::get('chart_type');
|
||||
$startDate = Utils::toSqlDate(Input::get('start_date'), false);
|
||||
$endDate = Utils::toSqlDate(Input::get('end_date'), false);
|
||||
} else {
|
||||
$groupBy = 'MONTH';
|
||||
$chartType = 'Bar';
|
||||
$startDate = Utils::today(false)->modify('-3 month');
|
||||
$endDate = Utils::today(false);
|
||||
}
|
||||
|
||||
$padding = $groupBy == 'DAYOFYEAR' ? 'day' : ($groupBy == 'WEEK' ? 'week' : 'month');
|
||||
$endDate->modify('+1 '.$padding);
|
||||
$datasets = [];
|
||||
$labels = [];
|
||||
$maxTotals = 0;
|
||||
$width = 10;
|
||||
|
||||
if (Auth::user()->account->isPro()) {
|
||||
foreach ([ENTITY_INVOICE, ENTITY_PAYMENT, ENTITY_CREDIT] as $entityType) {
|
||||
$records = DB::table($entityType.'s')
|
||||
->select(DB::raw('sum(amount) as total, '.$groupBy.'('.$entityType.'_date) as '.$groupBy))
|
||||
->where('account_id', '=', Auth::user()->account_id)
|
||||
->where($entityType.'s.is_deleted', '=', false)
|
||||
->where($entityType.'s.'.$entityType.'_date', '>=', $startDate->format('Y-m-d'))
|
||||
->where($entityType.'s.'.$entityType.'_date', '<=', $endDate->format('Y-m-d'))
|
||||
->groupBy($groupBy);
|
||||
|
||||
if ($entityType == ENTITY_INVOICE) {
|
||||
$records->where('is_quote', '=', false)
|
||||
->where('is_recurring', '=', false);
|
||||
}
|
||||
|
||||
$totals = $records->lists('total');
|
||||
$dates = $records->lists($groupBy);
|
||||
$data = array_combine($dates, $totals);
|
||||
|
||||
$interval = new DateInterval('P1'.substr($groupBy, 0, 1));
|
||||
$period = new DatePeriod($startDate, $interval, $endDate);
|
||||
|
||||
$totals = [];
|
||||
|
||||
foreach ($period as $d) {
|
||||
$dateFormat = $groupBy == 'DAYOFYEAR' ? 'z' : ($groupBy == 'WEEK' ? 'W' : 'n');
|
||||
$date = $d->format($dateFormat);
|
||||
$totals[] = isset($data[$date]) ? $data[$date] : 0;
|
||||
|
||||
if ($entityType == ENTITY_INVOICE) {
|
||||
$labelFormat = $groupBy == 'DAYOFYEAR' ? 'j' : ($groupBy == 'WEEK' ? 'W' : 'F');
|
||||
$label = $d->format($labelFormat);
|
||||
$labels[] = $label;
|
||||
}
|
||||
}
|
||||
|
||||
$max = max($totals);
|
||||
|
||||
if ($max > 0) {
|
||||
$datasets[] = [
|
||||
'totals' => $totals,
|
||||
'colors' => $entityType == ENTITY_INVOICE ? '78,205,196' : ($entityType == ENTITY_CREDIT ? '199,244,100' : '255,107,107'),
|
||||
];
|
||||
$maxTotals = max($max, $maxTotals);
|
||||
}
|
||||
}
|
||||
|
||||
$width = (ceil($maxTotals / 100) * 100) / 10;
|
||||
$width = max($width, 10);
|
||||
}
|
||||
|
||||
$dateTypes = [
|
||||
'DAYOFYEAR' => 'Daily',
|
||||
'WEEK' => 'Weekly',
|
||||
'MONTH' => 'Monthly',
|
||||
];
|
||||
|
||||
$chartTypes = [
|
||||
'Bar' => 'Bar',
|
||||
'Line' => 'Line',
|
||||
];
|
||||
|
||||
$params = [
|
||||
'labels' => $labels,
|
||||
'datasets' => $datasets,
|
||||
'scaleStepWidth' => $width,
|
||||
'dateTypes' => $dateTypes,
|
||||
'chartTypes' => $chartTypes,
|
||||
'chartType' => $chartType,
|
||||
'startDate' => $startDate->format(Session::get(SESSION_DATE_FORMAT)),
|
||||
'endDate' => $endDate->modify('-1'.$padding)->format(Session::get(SESSION_DATE_FORMAT)),
|
||||
'groupBy' => $groupBy,
|
||||
'feature' => ACCOUNT_CHART_BUILDER,
|
||||
];
|
||||
|
||||
return View::make('reports.report_builder', $params);
|
||||
}
|
||||
}
|
93
app/Http/Controllers/TimesheetController.php
Normal file
93
app/Http/Controllers/TimesheetController.php
Normal file
@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
class TimesheetController extends \BaseController {
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$data = [
|
||||
'showBreadcrumbs' => false,
|
||||
'timesheet' => [
|
||||
'timesheet_number' => 1
|
||||
]
|
||||
];
|
||||
|
||||
return View::make('timesheets.edit', $data);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function store()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param int $id
|
||||
* @return Response
|
||||
*/
|
||||
public function show($id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*
|
||||
* @param int $id
|
||||
* @return Response
|
||||
*/
|
||||
public function edit($id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param int $id
|
||||
* @return Response
|
||||
*/
|
||||
public function update($id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param int $id
|
||||
* @return Response
|
||||
*/
|
||||
public function destroy($id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
|
||||
}
|
161
app/Http/Controllers/TokenController.php
Normal file
161
app/Http/Controllers/TokenController.php
Normal file
@ -0,0 +1,161 @@
|
||||
<?php
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Confide Controller Template
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This is the default Confide controller template for controlling user
|
||||
| authentication. Feel free to change to your needs.
|
||||
|
|
||||
*/
|
||||
|
||||
use ninja\repositories\AccountRepository;
|
||||
|
||||
class TokenController extends BaseController
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
public function edit($publicId)
|
||||
{
|
||||
$token = AccountToken::where('account_id', '=', Auth::user()->account_id)
|
||||
->where('public_id', '=', $publicId)->firstOrFail();
|
||||
|
||||
$data = [
|
||||
'showBreadcrumbs' => false,
|
||||
'token' => $token,
|
||||
'method' => 'PUT',
|
||||
'url' => 'tokens/'.$publicId,
|
||||
'title' => trans('texts.edit_token'),
|
||||
];
|
||||
|
||||
return View::make('accounts.token', $data);
|
||||
}
|
||||
|
||||
public function update($publicId)
|
||||
{
|
||||
return $this->save($publicId);
|
||||
}
|
||||
|
||||
public function store()
|
||||
{
|
||||
return $this->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the form for account creation
|
||||
*
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
if (!Auth::user()->confirmed) {
|
||||
Session::flash('error', trans('texts.register_to_add_user'));
|
||||
return Redirect::to('company/advanced_settings/user_management');
|
||||
}
|
||||
|
||||
$data = [
|
||||
'showBreadcrumbs' => false,
|
||||
'token' => null,
|
||||
'method' => 'POST',
|
||||
'url' => 'tokens',
|
||||
'title' => trans('texts.add_token'),
|
||||
];
|
||||
|
||||
return View::make('accounts.token', $data);
|
||||
}
|
||||
|
||||
public function delete()
|
||||
{
|
||||
$tokenPublicId = Input::get('tokenPublicId');
|
||||
$token = AccountToken::where('account_id', '=', Auth::user()->account_id)
|
||||
->where('public_id', '=', $tokenPublicId)->firstOrFail();
|
||||
|
||||
$token->delete();
|
||||
|
||||
Session::flash('message', trans('texts.deleted_token'));
|
||||
|
||||
return Redirect::to('company/advanced_settings/token_management');
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores new account
|
||||
*
|
||||
*/
|
||||
public function save($tokenPublicId = false)
|
||||
{
|
||||
if (Auth::user()->account->isPro()) {
|
||||
$rules = [
|
||||
'name' => 'required',
|
||||
];
|
||||
|
||||
if ($tokenPublicId) {
|
||||
$token = AccountToken::where('account_id', '=', Auth::user()->account_id)
|
||||
->where('public_id', '=', $tokenPublicId)->firstOrFail();
|
||||
}
|
||||
|
||||
$validator = Validator::make(Input::all(), $rules);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return Redirect::to($tokenPublicId ? 'tokens/edit' : 'tokens/create')->withInput()->withErrors($validator);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
if ($tokenPublicId) {
|
||||
$message = trans('texts.updated_token');
|
||||
} else {
|
||||
$message = trans('texts.created_token');
|
||||
}
|
||||
|
||||
Session::flash('message', $message);
|
||||
}
|
||||
|
||||
return Redirect::to('company/advanced_settings/token_management');
|
||||
}
|
||||
|
||||
}
|
507
app/Http/Controllers/UserController.php
Normal file
507
app/Http/Controllers/UserController.php
Normal file
@ -0,0 +1,507 @@
|
||||
<?php
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Confide Controller Template
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This is the default Confide controller template for controlling user
|
||||
| authentication. Feel free to change to your needs.
|
||||
|
|
||||
*/
|
||||
|
||||
use ninja\repositories\AccountRepository;
|
||||
use ninja\mailers\ContactMailer;
|
||||
use ninja\mailers\UserMailer;
|
||||
|
||||
class UserController extends BaseController
|
||||
{
|
||||
protected $accountRepo;
|
||||
protected $contactMailer;
|
||||
protected $userMailer;
|
||||
|
||||
public function __construct(AccountRepository $accountRepo, ContactMailer $contactMailer, UserMailer $userMailer)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->accountRepo = $accountRepo;
|
||||
$this->contactMailer = $contactMailer;
|
||||
$this->userMailer = $userMailer;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
public function setTheme()
|
||||
{
|
||||
$user = User::find(Auth::user()->id);
|
||||
$user->theme_id = Input::get('theme_id');
|
||||
$user->save();
|
||||
|
||||
return Redirect::to(Input::get('path'));
|
||||
}
|
||||
|
||||
public function forcePDFJS()
|
||||
{
|
||||
$user = Auth::user();
|
||||
$user->force_pdfjs = true;
|
||||
$user->save();
|
||||
|
||||
Session::flash('message', trans('texts.confide.updated_settings'));
|
||||
|
||||
return Redirect::to('/dashboard');
|
||||
}
|
||||
|
||||
public function edit($publicId)
|
||||
{
|
||||
$user = User::where('account_id', '=', Auth::user()->account_id)
|
||||
->where('public_id', '=', $publicId)->firstOrFail();
|
||||
|
||||
$data = [
|
||||
'showBreadcrumbs' => false,
|
||||
'user' => $user,
|
||||
'method' => 'PUT',
|
||||
'url' => 'users/'.$publicId,
|
||||
'title' => trans('texts.edit_user'),
|
||||
];
|
||||
|
||||
return View::make('users.edit', $data);
|
||||
}
|
||||
|
||||
public function update($publicId)
|
||||
{
|
||||
return $this->save($publicId);
|
||||
}
|
||||
|
||||
public function store()
|
||||
{
|
||||
return $this->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the form for account creation
|
||||
*
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
if (!Auth::user()->confirmed) {
|
||||
Session::flash('error', trans('texts.register_to_add_user'));
|
||||
|
||||
return Redirect::to('company/advanced_settings/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');
|
||||
}
|
||||
}
|
||||
|
||||
$data = [
|
||||
'showBreadcrumbs' => false,
|
||||
'user' => null,
|
||||
'method' => 'POST',
|
||||
'url' => 'users',
|
||||
'title' => trans('texts.add_user'),
|
||||
];
|
||||
|
||||
return View::make('users.edit', $data);
|
||||
}
|
||||
|
||||
public function delete()
|
||||
{
|
||||
$userPublicId = Input::get('userPublicId');
|
||||
$user = User::where('account_id', '=', Auth::user()->account_id)
|
||||
->where('public_id', '=', $userPublicId)->firstOrFail();
|
||||
|
||||
$user->delete();
|
||||
|
||||
Session::flash('message', trans('texts.deleted_user'));
|
||||
|
||||
return Redirect::to('company/advanced_settings/user_management');
|
||||
}
|
||||
|
||||
public function restoreUser($userPublicId)
|
||||
{
|
||||
$user = User::where('account_id', '=', Auth::user()->account_id)
|
||||
->where('public_id', '=', $userPublicId)
|
||||
->withTrashed()->firstOrFail();
|
||||
|
||||
$user->restore();
|
||||
|
||||
Session::flash('message', trans('texts.restored_user'));
|
||||
|
||||
return Redirect::to('company/advanced_settings/user_management');
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores new account
|
||||
*
|
||||
*/
|
||||
public function save($userPublicId = false)
|
||||
{
|
||||
if (Auth::user()->account->isPro()) {
|
||||
$rules = [
|
||||
'first_name' => 'required',
|
||||
'last_name' => 'required',
|
||||
];
|
||||
|
||||
if ($userPublicId) {
|
||||
$user = User::where('account_id', '=', Auth::user()->account_id)
|
||||
->where('public_id', '=', $userPublicId)->firstOrFail();
|
||||
|
||||
$rules['email'] = 'required|email|unique:users,email,'.$user->id.',id';
|
||||
} else {
|
||||
$rules['email'] = 'required|email|unique:users';
|
||||
}
|
||||
|
||||
$validator = Validator::make(Input::all(), $rules);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return Redirect::to($userPublicId ? 'users/edit' : 'users/create')->withInput()->withErrors($validator);
|
||||
}
|
||||
|
||||
if ($userPublicId) {
|
||||
$user->first_name = trim(Input::get('first_name'));
|
||||
$user->last_name = trim(Input::get('last_name'));
|
||||
$user->username = trim(Input::get('email'));
|
||||
$user->email = trim(Input::get('email'));
|
||||
} else {
|
||||
$lastUser = User::withTrashed()->where('account_id', '=', Auth::user()->account_id)
|
||||
->orderBy('public_id', 'DESC')->first();
|
||||
|
||||
$user = new User();
|
||||
$user->account_id = Auth::user()->account_id;
|
||||
$user->first_name = trim(Input::get('first_name'));
|
||||
$user->last_name = trim(Input::get('last_name'));
|
||||
$user->username = trim(Input::get('email'));
|
||||
$user->email = trim(Input::get('email'));
|
||||
$user->registered = true;
|
||||
$user->password = str_random(RANDOM_KEY_LENGTH);
|
||||
$user->password_confirmation = $user->password;
|
||||
$user->public_id = $lastUser->public_id + 1;
|
||||
}
|
||||
|
||||
$user->save();
|
||||
|
||||
if (!$user->confirmed) {
|
||||
$this->userMailer->sendConfirmation($user, Auth::user());
|
||||
$message = trans('texts.sent_invite');
|
||||
} else {
|
||||
$message = trans('texts.updated_user');
|
||||
}
|
||||
|
||||
Session::flash('message', $message);
|
||||
}
|
||||
|
||||
return Redirect::to('company/advanced_settings/user_management');
|
||||
}
|
||||
|
||||
public function sendConfirmation($userPublicId)
|
||||
{
|
||||
$user = User::where('account_id', '=', Auth::user()->account_id)
|
||||
->where('public_id', '=', $userPublicId)->firstOrFail();
|
||||
|
||||
$this->userMailer->sendConfirmation($user, Auth::user());
|
||||
Session::flash('message', trans('texts.sent_invite'));
|
||||
|
||||
return Redirect::to('company/advanced_settings/user_management');
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the login form
|
||||
*
|
||||
*/
|
||||
public function login()
|
||||
{
|
||||
if (Confide::user()) {
|
||||
Event::fire('user.login');
|
||||
Session::reflash();
|
||||
|
||||
return Redirect::to('/dashboard');
|
||||
|
||||
/*
|
||||
$invoice = Invoice::scope()->orderBy('id', 'desc')->first();
|
||||
|
||||
if ($invoice)
|
||||
{
|
||||
return Redirect::to('/invoices/' . $invoice->public_id);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Redirect::to('/dashboard');
|
||||
}
|
||||
*/
|
||||
} else {
|
||||
return View::make(Config::get('confide::login_form'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to do login
|
||||
*
|
||||
*/
|
||||
public function do_login()
|
||||
{
|
||||
$input = array(
|
||||
'email' => Input::get('login_email'), // May be the username too
|
||||
'username' => Input::get('login_email'), // so we have to pass both
|
||||
'password' => Input::get('login_password'),
|
||||
'remember' => true,
|
||||
);
|
||||
|
||||
// If you wish to only allow login from confirmed users, call logAttempt
|
||||
// with the second parameter as true.
|
||||
// logAttempt will check if the 'email' perhaps is the username.
|
||||
// Get the value from the config file instead of changing the controller
|
||||
if (Input::get('login_email') && Confide::logAttempt($input, false)) {
|
||||
Event::fire('user.login');
|
||||
// Redirect the user to the URL they were trying to access before
|
||||
// caught by the authentication filter IE Redirect::guest('user/login').
|
||||
// Otherwise fallback to '/'
|
||||
// Fix pull #145
|
||||
return Redirect::intended('/dashboard'); // change it to '/admin', '/dashboard' or something
|
||||
} else {
|
||||
//$user = new User;
|
||||
|
||||
// Check if there was too many login attempts
|
||||
if (Confide::isThrottled($input)) {
|
||||
$err_msg = trans('texts.confide.too_many_attempts');
|
||||
}
|
||||
/*
|
||||
elseif( $user->checkUserExists( $input ) and ! $user->isConfirmed( $input ) )
|
||||
{
|
||||
$err_msg = Lang::get('confide::confide.alerts.not_confirmed');
|
||||
}
|
||||
*/
|
||||
else {
|
||||
$err_msg = trans('texts.confide.wrong_credentials');
|
||||
}
|
||||
|
||||
return Redirect::action('UserController@login')
|
||||
->withInput(Input::except('login_password'))
|
||||
->with('error', $err_msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to confirm account with code
|
||||
*
|
||||
* @param string $code
|
||||
*/
|
||||
public function confirm($code)
|
||||
{
|
||||
if (Confide::confirm($code)) {
|
||||
$notice_msg = trans('texts.confide.confirmation');
|
||||
|
||||
$user = User::where('confirmation_code', '=', $code)->get()->first();
|
||||
$user->confirmation_code = '';
|
||||
$user->save();
|
||||
|
||||
if ($user->public_id) {
|
||||
Auth::login($user);
|
||||
|
||||
return Redirect::to('user/reset');
|
||||
} else {
|
||||
if (Session::has(REQUESTED_PRO_PLAN)) {
|
||||
Session::forget(REQUESTED_PRO_PLAN);
|
||||
$invitation = $this->accountRepo->enableProPlan();
|
||||
|
||||
return Redirect::to($invitation->getLink());
|
||||
} else {
|
||||
return Redirect::action('UserController@login')->with('message', $notice_msg);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$error_msg = trans('texts.confide.wrong_confirmation');
|
||||
|
||||
return Redirect::action('UserController@login')->with('error', $error_msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the forgot password form
|
||||
*
|
||||
*/
|
||||
public function forgot_password()
|
||||
{
|
||||
return View::make(Config::get('confide::forgot_password_form'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to send change password link to the given email
|
||||
*
|
||||
*/
|
||||
public function do_forgot_password()
|
||||
{
|
||||
Confide::forgotPassword(Input::get('email'));
|
||||
|
||||
$notice_msg = trans('texts.confide.password_forgot');
|
||||
|
||||
return Redirect::action('UserController@login')
|
||||
->with('notice', $notice_msg);
|
||||
|
||||
/*
|
||||
if( Confide::forgotPassword( Input::get( 'email' ) ) )
|
||||
{
|
||||
$notice_msg = Lang::get('confide::confide.alerts.password_forgot');
|
||||
return Redirect::action('UserController@login')
|
||||
->with( 'notice', $notice_msg );
|
||||
}
|
||||
else
|
||||
{
|
||||
$error_msg = Lang::get('confide::confide.alerts.wrong_password_forgot');
|
||||
return Redirect::action('UserController@forgot_password')
|
||||
->withInput()
|
||||
->with( 'error', $error_msg );
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the change password form with the given token
|
||||
*
|
||||
*/
|
||||
public function reset_password($token = false)
|
||||
{
|
||||
return View::make(Config::get('confide::reset_password_form'))
|
||||
->with('token', $token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt change password of the user
|
||||
*
|
||||
*/
|
||||
public function do_reset_password()
|
||||
{
|
||||
if (Auth::check()) {
|
||||
$rules = [
|
||||
'password' => 'required|between:4,11|confirmed',
|
||||
'password_confirmation' => 'between:4,11',
|
||||
];
|
||||
$validator = Validator::make(Input::all(), $rules);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return Redirect::to('user/reset')->withInput()->withErrors($validator);
|
||||
}
|
||||
|
||||
$user = Auth::user();
|
||||
$user->password = Input::get('password');
|
||||
$user->save();
|
||||
|
||||
Session::flash('message', trans('texts.confide.password_reset'));
|
||||
|
||||
return Redirect::to('/dashboard');
|
||||
} else {
|
||||
$input = array(
|
||||
'token' => Input::get('token'),
|
||||
'password' => Input::get('password'),
|
||||
'password_confirmation' => Input::get('password_confirmation'),
|
||||
);
|
||||
|
||||
// By passing an array with the token, password and confirmation
|
||||
if (Confide::resetPassword($input)) {
|
||||
$notice_msg = trans('texts.confide.password_reset');
|
||||
|
||||
return Redirect::action('UserController@login')
|
||||
->with('notice', $notice_msg);
|
||||
} else {
|
||||
$error_msg = trans('texts.confide.wrong_password_reset');
|
||||
|
||||
return Redirect::action('UserController@reset_password', array('token' => $input['token']))
|
||||
->withInput()
|
||||
->with('error', $error_msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the user out of the application.
|
||||
*
|
||||
*/
|
||||
public function logout()
|
||||
{
|
||||
if (Auth::check()) {
|
||||
if (!Auth::user()->registered) {
|
||||
$account = Auth::user()->account;
|
||||
$account->forceDelete();
|
||||
}
|
||||
}
|
||||
|
||||
Session::forget('news_feed_id');
|
||||
Session::forget('news_feed_message');
|
||||
|
||||
Confide::logout();
|
||||
|
||||
return Redirect::to('/')->with('clearGuestKey', true);
|
||||
}
|
||||
|
||||
public function changePassword()
|
||||
{
|
||||
// check the current password is correct
|
||||
if (!Auth::validate([
|
||||
'email' => Auth::user()->email,
|
||||
'password' => Input::get('current_password')
|
||||
])) {
|
||||
return trans('texts.password_error_incorrect');
|
||||
}
|
||||
|
||||
// validate the new password
|
||||
$password = Input::get('new_password');
|
||||
$confirm = Input::get('confirm_password');
|
||||
|
||||
if (strlen($password) < 6 || $password != $confirm) {
|
||||
return trans('texts.password_error_invalid');
|
||||
}
|
||||
|
||||
// save the new password
|
||||
$user = Auth::user();
|
||||
$user->password = $password;
|
||||
$user->save();
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
}
|
38
app/Http/Controllers/old/Auth/AuthController.php
Normal file
38
app/Http/Controllers/old/Auth/AuthController.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php namespace InvoiceNinja\Http\Controllers\Auth;
|
||||
|
||||
use InvoiceNinja\Http\Controllers\Controller;
|
||||
use Illuminate\Contracts\Auth\Guard;
|
||||
use Illuminate\Contracts\Auth\Registrar;
|
||||
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;
|
||||
|
||||
class AuthController extends Controller {
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Registration & Login Controller
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This controller handles the registration of new users, as well as the
|
||||
| authentication of existing users. By default, this controller uses
|
||||
| a simple trait to add these behaviors. Why don't you explore it?
|
||||
|
|
||||
*/
|
||||
|
||||
use AuthenticatesAndRegistersUsers;
|
||||
|
||||
/**
|
||||
* Create a new authentication controller instance.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Auth\Guard $auth
|
||||
* @param \Illuminate\Contracts\Auth\Registrar $registrar
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Guard $auth, Registrar $registrar)
|
||||
{
|
||||
$this->auth = $auth;
|
||||
$this->registrar = $registrar;
|
||||
|
||||
$this->middleware('guest', ['except' => 'getLogout']);
|
||||
}
|
||||
|
||||
}
|
38
app/Http/Controllers/old/Auth/PasswordController.php
Normal file
38
app/Http/Controllers/old/Auth/PasswordController.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php namespace InvoiceNinja\Http\Controllers\Auth;
|
||||
|
||||
use InvoiceNinja\Http\Controllers\Controller;
|
||||
use Illuminate\Contracts\Auth\Guard;
|
||||
use Illuminate\Contracts\Auth\PasswordBroker;
|
||||
use Illuminate\Foundation\Auth\ResetsPasswords;
|
||||
|
||||
class PasswordController extends Controller {
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Password Reset Controller
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This controller is responsible for handling password reset requests
|
||||
| and uses a simple trait to include this behavior. You're free to
|
||||
| explore this trait and override any methods you wish to tweak.
|
||||
|
|
||||
*/
|
||||
|
||||
use ResetsPasswords;
|
||||
|
||||
/**
|
||||
* Create a new password controller instance.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Auth\Guard $auth
|
||||
* @param \Illuminate\Contracts\Auth\PasswordBroker $passwords
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Guard $auth, PasswordBroker $passwords)
|
||||
{
|
||||
$this->auth = $auth;
|
||||
$this->passwords = $passwords;
|
||||
|
||||
$this->middleware('guest');
|
||||
}
|
||||
|
||||
}
|
36
app/Http/Controllers/old/HomeController.php
Normal file
36
app/Http/Controllers/old/HomeController.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?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');
|
||||
}
|
||||
|
||||
}
|
36
app/Http/Controllers/old/WelcomeController.php
Normal file
36
app/Http/Controllers/old/WelcomeController.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?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');
|
||||
}
|
||||
|
||||
}
|
@ -64,7 +64,7 @@ Route::get('user/reset/{token?}', 'UserController@reset_password');
|
||||
Route::post('user/reset', 'UserController@do_reset_password');
|
||||
Route::get('logout', 'UserController@logout');
|
||||
|
||||
if (Utils::isNinja()) {
|
||||
if (\App\libraries\Utils::isNinja()) {
|
||||
Route::post('/signup/register', 'AccountController@doRegister');
|
||||
Route::get('/news_feed/{user_type}/{version}/', 'HomeController@newsFeed');
|
||||
Route::get('/demo', 'AccountController@demo');
|
||||
|
605
app/Libraries/Utils.php
Normal file
605
app/Libraries/Utils.php
Normal file
@ -0,0 +1,605 @@
|
||||
<?php namespace App\Libraries;
|
||||
|
||||
|
||||
class Utils
|
||||
{
|
||||
public static function isRegistered()
|
||||
{
|
||||
return Auth::check() && Auth::user()->registered;
|
||||
}
|
||||
|
||||
public static function isConfirmed()
|
||||
{
|
||||
return Auth::check() && Auth::user()->confirmed;
|
||||
}
|
||||
|
||||
public static function isDatabaseSetup()
|
||||
{
|
||||
try {
|
||||
if (Schema::hasTable('accounts')) {
|
||||
return true;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static function isProd()
|
||||
{
|
||||
return App::environment() == ENV_PRODUCTION;
|
||||
}
|
||||
|
||||
public static function isNinja()
|
||||
{
|
||||
return self::isNinjaProd() || self::isNinjaDev();
|
||||
}
|
||||
|
||||
public static function isNinjaProd()
|
||||
{
|
||||
return isset($_ENV['NINJA_PROD']) && $_ENV['NINJA_PROD'];
|
||||
}
|
||||
|
||||
public static function isNinjaDev()
|
||||
{
|
||||
return isset($_ENV['NINJA_DEV']) && $_ENV['NINJA_DEV'];
|
||||
}
|
||||
|
||||
public static function isPro()
|
||||
{
|
||||
return Auth::check() && Auth::user()->isPro();
|
||||
}
|
||||
|
||||
public static function getUserType()
|
||||
{
|
||||
if (Utils::isNinja()) {
|
||||
return USER_TYPE_CLOUD_HOST;
|
||||
} else {
|
||||
return USER_TYPE_SELF_HOST;
|
||||
}
|
||||
}
|
||||
|
||||
public static function getDemoAccountId()
|
||||
{
|
||||
return isset($_ENV[DEMO_ACCOUNT_ID]) ? $_ENV[DEMO_ACCOUNT_ID] : false;
|
||||
}
|
||||
|
||||
public static function isDemo()
|
||||
{
|
||||
return Auth::check() && Auth::user()->isDemo();
|
||||
}
|
||||
|
||||
public static function getNewsFeedResponse($userType = false)
|
||||
{
|
||||
if (!$userType) {
|
||||
$userType = Utils::getUserType();
|
||||
}
|
||||
|
||||
$response = new stdClass();
|
||||
$response->message = isset($_ENV["{$userType}_MESSAGE"]) ? $_ENV["{$userType}_MESSAGE"] : '';
|
||||
$response->id = isset($_ENV["{$userType}_ID"]) ? $_ENV["{$userType}_ID"] : '';
|
||||
$response->version = NINJA_VERSION;
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public static function getProLabel($feature)
|
||||
{
|
||||
if (Auth::check()
|
||||
&& !Auth::user()->isPro()
|
||||
&& $feature == ACCOUNT_ADVANCED_SETTINGS) {
|
||||
return ' <sup class="pro-label">PRO</sup>';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
public static function basePath()
|
||||
{
|
||||
return substr($_SERVER['SCRIPT_NAME'], 0, strrpos($_SERVER['SCRIPT_NAME'], '/') + 1);
|
||||
}
|
||||
|
||||
public static function trans($input)
|
||||
{
|
||||
$data = [];
|
||||
|
||||
foreach ($input as $field) {
|
||||
if ($field == "checkbox") {
|
||||
$data[] = $field;
|
||||
} else {
|
||||
$data[] = trans("texts.$field");
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public static function fatalError($message = false, $exception = false)
|
||||
{
|
||||
if (!$message) {
|
||||
$message = "An error occurred, please try again later.";
|
||||
}
|
||||
|
||||
static::logError($message.' '.$exception);
|
||||
|
||||
$data = [
|
||||
'showBreadcrumbs' => false,
|
||||
'hideHeader' => true,
|
||||
];
|
||||
|
||||
return View::make('error', $data)->with('error', $message);
|
||||
}
|
||||
|
||||
public static function getErrorString($exception)
|
||||
{
|
||||
return "{$exception->getFile()} [Line {$exception->getLine()}] => {$exception->getMessage()}";
|
||||
}
|
||||
|
||||
public static function logError($error, $context = 'PHP')
|
||||
{
|
||||
$count = Session::get('error_count', 0);
|
||||
Session::put('error_count', ++$count);
|
||||
if ($count > 100) {
|
||||
return 'logged';
|
||||
}
|
||||
|
||||
$data = [
|
||||
'context' => $context,
|
||||
'user_id' => Auth::check() ? Auth::user()->id : 0,
|
||||
'user_name' => Auth::check() ? Auth::user()->getDisplayName() : '',
|
||||
'url' => Input::get('url', Request::url()),
|
||||
'user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '',
|
||||
'ip' => Request::getClientIp(),
|
||||
'count' => Session::get('error_count', 0),
|
||||
];
|
||||
|
||||
Log::error($error."\n", $data);
|
||||
|
||||
/*
|
||||
Mail::queue('emails.error', ['message'=>$error.' '.json_encode($data)], function($message)
|
||||
{
|
||||
$message->to($email)->subject($subject);
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
public static function parseFloat($value)
|
||||
{
|
||||
$value = preg_replace('/[^0-9\.\-]/', '', $value);
|
||||
|
||||
return floatval($value);
|
||||
}
|
||||
|
||||
public static function formatPhoneNumber($phoneNumber)
|
||||
{
|
||||
$phoneNumber = preg_replace('/[^0-9a-zA-Z]/', '', $phoneNumber);
|
||||
|
||||
if (!$phoneNumber) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (strlen($phoneNumber) > 10) {
|
||||
$countryCode = substr($phoneNumber, 0, strlen($phoneNumber)-10);
|
||||
$areaCode = substr($phoneNumber, -10, 3);
|
||||
$nextThree = substr($phoneNumber, -7, 3);
|
||||
$lastFour = substr($phoneNumber, -4, 4);
|
||||
|
||||
$phoneNumber = '+'.$countryCode.' ('.$areaCode.') '.$nextThree.'-'.$lastFour;
|
||||
} elseif (strlen($phoneNumber) == 10 && in_array(substr($phoneNumber, 0, 3), array(653, 656, 658, 659))) {
|
||||
/**
|
||||
* SG country code are 653, 656, 658, 659
|
||||
* US area code consist of 650, 651 and 657
|
||||
* @see http://en.wikipedia.org/wiki/Telephone_numbers_in_Singapore#Numbering_plan
|
||||
* @see http://www.bennetyee.org/ucsd-pages/area.html
|
||||
*/
|
||||
$countryCode = substr($phoneNumber, 0, 2);
|
||||
$nextFour = substr($phoneNumber, 2, 4);
|
||||
$lastFour = substr($phoneNumber, 6, 4);
|
||||
|
||||
$phoneNumber = '+'.$countryCode.' '.$nextFour.' '.$lastFour;
|
||||
} elseif (strlen($phoneNumber) == 10) {
|
||||
$areaCode = substr($phoneNumber, 0, 3);
|
||||
$nextThree = substr($phoneNumber, 3, 3);
|
||||
$lastFour = substr($phoneNumber, 6, 4);
|
||||
|
||||
$phoneNumber = '('.$areaCode.') '.$nextThree.'-'.$lastFour;
|
||||
} elseif (strlen($phoneNumber) == 7) {
|
||||
$nextThree = substr($phoneNumber, 0, 3);
|
||||
$lastFour = substr($phoneNumber, 3, 4);
|
||||
|
||||
$phoneNumber = $nextThree.'-'.$lastFour;
|
||||
}
|
||||
|
||||
return $phoneNumber;
|
||||
}
|
||||
|
||||
public static function formatMoney($value, $currencyId = false)
|
||||
{
|
||||
if (!$currencyId) {
|
||||
$currencyId = Session::get(SESSION_CURRENCY);
|
||||
}
|
||||
|
||||
$currency = Currency::remember(DEFAULT_QUERY_CACHE)->find($currencyId);
|
||||
|
||||
if (!$currency) {
|
||||
$currency = Currency::remember(DEFAULT_QUERY_CACHE)->find(1);
|
||||
}
|
||||
|
||||
return $currency->symbol.number_format($value, $currency->precision, $currency->decimal_separator, $currency->thousand_separator);
|
||||
}
|
||||
|
||||
public static function pluralize($string, $count)
|
||||
{
|
||||
$field = $count == 1 ? $string : $string.'s';
|
||||
$string = trans("texts.$field", ['count' => $count]);
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
public static function toArray($data)
|
||||
{
|
||||
return json_decode(json_encode((array) $data), true);
|
||||
}
|
||||
|
||||
public static function toSpaceCase($camelStr)
|
||||
{
|
||||
return preg_replace('/([a-z])([A-Z])/s', '$1 $2', $camelStr);
|
||||
}
|
||||
|
||||
public static function timestampToDateTimeString($timestamp)
|
||||
{
|
||||
$timezone = Session::get(SESSION_TIMEZONE, DEFAULT_TIMEZONE);
|
||||
$format = Session::get(SESSION_DATETIME_FORMAT, DEFAULT_DATETIME_FORMAT);
|
||||
|
||||
return Utils::timestampToString($timestamp, $timezone, $format);
|
||||
}
|
||||
|
||||
public static function timestampToDateString($timestamp)
|
||||
{
|
||||
$timezone = Session::get(SESSION_TIMEZONE, DEFAULT_TIMEZONE);
|
||||
$format = Session::get(SESSION_DATE_FORMAT, DEFAULT_DATE_FORMAT);
|
||||
|
||||
return Utils::timestampToString($timestamp, $timezone, $format);
|
||||
}
|
||||
|
||||
public static function dateToString($date)
|
||||
{
|
||||
$dateTime = new DateTime($date);
|
||||
$timestamp = $dateTime->getTimestamp();
|
||||
$format = Session::get(SESSION_DATE_FORMAT, DEFAULT_DATE_FORMAT);
|
||||
|
||||
return Utils::timestampToString($timestamp, false, $format);
|
||||
}
|
||||
|
||||
public static function timestampToString($timestamp, $timezone = false, $format)
|
||||
{
|
||||
if (!$timestamp) {
|
||||
return '';
|
||||
}
|
||||
$date = Carbon::createFromTimeStamp($timestamp);
|
||||
if ($timezone) {
|
||||
$date->tz = $timezone;
|
||||
}
|
||||
if ($date->year < 1900) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $date->format($format);
|
||||
}
|
||||
|
||||
public static function toSqlDate($date, $formatResult = true)
|
||||
{
|
||||
if (!$date) {
|
||||
return;
|
||||
}
|
||||
|
||||
$timezone = Session::get(SESSION_TIMEZONE);
|
||||
$format = Session::get(SESSION_DATE_FORMAT);
|
||||
|
||||
$dateTime = DateTime::createFromFormat($format, $date, new DateTimeZone($timezone));
|
||||
|
||||
return $formatResult ? $dateTime->format('Y-m-d') : $dateTime;
|
||||
}
|
||||
|
||||
public static function fromSqlDate($date, $formatResult = true)
|
||||
{
|
||||
if (!$date || $date == '0000-00-00') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$timezone = Session::get(SESSION_TIMEZONE);
|
||||
$format = Session::get(SESSION_DATE_FORMAT);
|
||||
|
||||
$dateTime = DateTime::createFromFormat('Y-m-d', $date, new DateTimeZone($timezone));
|
||||
|
||||
return $formatResult ? $dateTime->format($format) : $dateTime;
|
||||
}
|
||||
|
||||
public static function today($formatResult = true)
|
||||
{
|
||||
$timezone = Session::get(SESSION_TIMEZONE);
|
||||
$format = Session::get(SESSION_DATE_FORMAT);
|
||||
$date = date_create(null, new DateTimeZone($timezone));
|
||||
|
||||
if ($formatResult) {
|
||||
return $date->format($format);
|
||||
} else {
|
||||
return $date;
|
||||
}
|
||||
}
|
||||
|
||||
public static function trackViewed($name, $type, $url = false)
|
||||
{
|
||||
if (!$url) {
|
||||
$url = Request::url();
|
||||
}
|
||||
|
||||
$viewed = Session::get(RECENTLY_VIEWED);
|
||||
|
||||
if (!$viewed) {
|
||||
$viewed = [];
|
||||
}
|
||||
|
||||
$object = new stdClass();
|
||||
$object->url = $url;
|
||||
$object->name = ucwords($type).': '.$name;
|
||||
|
||||
$data = [];
|
||||
|
||||
for ($i = 0; $i<count($viewed); $i++) {
|
||||
$item = $viewed[$i];
|
||||
|
||||
if ($object->url == $item->url || $object->name == $item->name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
array_unshift($data, $item);
|
||||
}
|
||||
|
||||
array_unshift($data, $object);
|
||||
|
||||
if (count($data) > RECENTLY_VIEWED_LIMIT) {
|
||||
array_pop($data);
|
||||
}
|
||||
|
||||
Session::put(RECENTLY_VIEWED, $data);
|
||||
}
|
||||
|
||||
public static function processVariables($str)
|
||||
{
|
||||
if (!$str) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$variables = ['MONTH', 'QUARTER', 'YEAR'];
|
||||
for ($i = 0; $i<count($variables); $i++) {
|
||||
$variable = $variables[$i];
|
||||
$regExp = '/:'.$variable.'[+-]?[\d]*/';
|
||||
preg_match_all($regExp, $str, $matches);
|
||||
$matches = $matches[0];
|
||||
if (count($matches) == 0) {
|
||||
continue;
|
||||
}
|
||||
foreach ($matches as $match) {
|
||||
$offset = 0;
|
||||
$addArray = explode('+', $match);
|
||||
$minArray = explode('-', $match);
|
||||
if (count($addArray) > 1) {
|
||||
$offset = intval($addArray[1]);
|
||||
} elseif (count($minArray) > 1) {
|
||||
$offset = intval($minArray[1]) * -1;
|
||||
}
|
||||
|
||||
$val = Utils::getDatePart($variable, $offset);
|
||||
$str = str_replace($match, $val, $str);
|
||||
}
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
private static function getDatePart($part, $offset)
|
||||
{
|
||||
$offset = intval($offset);
|
||||
if ($part == 'MONTH') {
|
||||
return Utils::getMonth($offset);
|
||||
} elseif ($part == 'QUARTER') {
|
||||
return Utils::getQuarter($offset);
|
||||
} elseif ($part == 'YEAR') {
|
||||
return Utils::getYear($offset);
|
||||
}
|
||||
}
|
||||
|
||||
private static function getMonth($offset)
|
||||
{
|
||||
$months = [ "January", "February", "March", "April", "May", "June",
|
||||
"July", "August", "September", "October", "November", "December", ];
|
||||
|
||||
$month = intval(date('n')) - 1;
|
||||
|
||||
$month += $offset;
|
||||
$month = $month % 12;
|
||||
|
||||
if ($month < 0) {
|
||||
$month += 12;
|
||||
}
|
||||
|
||||
return $months[$month];
|
||||
}
|
||||
|
||||
private static function getQuarter($offset)
|
||||
{
|
||||
$month = intval(date('n')) - 1;
|
||||
$quarter = floor(($month + 3) / 3);
|
||||
$quarter += $offset;
|
||||
$quarter = $quarter % 4;
|
||||
if ($quarter == 0) {
|
||||
$quarter = 4;
|
||||
}
|
||||
|
||||
return 'Q'.$quarter;
|
||||
}
|
||||
|
||||
private static function getYear($offset)
|
||||
{
|
||||
$year = intval(date('Y'));
|
||||
|
||||
return $year + $offset;
|
||||
}
|
||||
|
||||
public static function getEntityName($entityType)
|
||||
{
|
||||
return ucwords(str_replace('_', ' ', $entityType));
|
||||
}
|
||||
|
||||
public static function getClientDisplayName($model)
|
||||
{
|
||||
if ($model->client_name) {
|
||||
return $model->client_name;
|
||||
} elseif ($model->first_name || $model->last_name) {
|
||||
return $model->first_name.' '.$model->last_name;
|
||||
} else {
|
||||
return $model->email;
|
||||
}
|
||||
}
|
||||
|
||||
public static function encodeActivity($person = null, $action, $entity = null, $otherPerson = null)
|
||||
{
|
||||
$person = $person ? $person->getDisplayName() : '<i>System</i>';
|
||||
$entity = $entity ? '['.$entity->getActivityKey().']' : '';
|
||||
$otherPerson = $otherPerson ? 'to '.$otherPerson->getDisplayName() : '';
|
||||
$token = Session::get('token_id') ? ' ('.trans('texts.token').')' : '';
|
||||
|
||||
return trim("$person $token $action $entity $otherPerson");
|
||||
}
|
||||
|
||||
public static function decodeActivity($message)
|
||||
{
|
||||
$pattern = '/\[([\w]*):([\d]*):(.*)\]/i';
|
||||
preg_match($pattern, $message, $matches);
|
||||
|
||||
if (count($matches) > 0) {
|
||||
$match = $matches[0];
|
||||
$type = $matches[1];
|
||||
$publicId = $matches[2];
|
||||
$name = $matches[3];
|
||||
|
||||
$link = link_to($type.'s/'.$publicId, $name);
|
||||
$message = str_replace($match, "$type $link", $message);
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
public static function generateLicense()
|
||||
{
|
||||
$parts = [];
|
||||
for ($i = 0; $i<5; $i++) {
|
||||
$parts[] = strtoupper(str_random(4));
|
||||
}
|
||||
|
||||
return implode('-', $parts);
|
||||
}
|
||||
|
||||
public static function lookupEventId($eventName)
|
||||
{
|
||||
if ($eventName == 'create_client') {
|
||||
return EVENT_CREATE_CLIENT;
|
||||
} elseif ($eventName == 'create_invoice') {
|
||||
return EVENT_CREATE_INVOICE;
|
||||
} elseif ($eventName == 'create_quote') {
|
||||
return EVENT_CREATE_QUOTE;
|
||||
} elseif ($eventName == 'create_payment') {
|
||||
return EVENT_CREATE_PAYMENT;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static function notifyZapier($subscription, $data)
|
||||
{
|
||||
$curl = curl_init();
|
||||
|
||||
$jsonEncodedData = json_encode($data->toJson());
|
||||
$opts = [
|
||||
CURLOPT_URL => $subscription->target_url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_CUSTOMREQUEST => 'POST',
|
||||
CURLOPT_POST => 1,
|
||||
CURLOPT_POSTFIELDS => $jsonEncodedData,
|
||||
CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Content-Length: '.strlen($jsonEncodedData)],
|
||||
];
|
||||
|
||||
curl_setopt_array($curl, $opts);
|
||||
|
||||
$result = curl_exec($curl);
|
||||
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
||||
|
||||
curl_close($curl);
|
||||
|
||||
if ($status == 410) {
|
||||
$subscription->delete();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static function remapPublicIds(array $data)
|
||||
{
|
||||
$return = [];
|
||||
|
||||
foreach ($data as $key => $val) {
|
||||
if ($key === 'public_id') {
|
||||
$key = 'id';
|
||||
} elseif (strpos($key, '_id')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_array($val)) {
|
||||
$val = Utils::remapPublicIds($val);
|
||||
}
|
||||
|
||||
$return[$key] = $val;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
public static function getApiHeaders($count = 0)
|
||||
{
|
||||
return [
|
||||
'Content-Type' => 'application/json',
|
||||
//'Access-Control-Allow-Origin' => '*',
|
||||
//'Access-Control-Allow-Methods' => 'GET',
|
||||
//'Access-Control-Allow-Headers' => 'Origin, Content-Type, Accept, Authorization, X-Requested-With',
|
||||
//'Access-Control-Allow-Credentials' => 'true',
|
||||
'X-Total-Count' => $count,
|
||||
//'X-Rate-Limit-Limit' - The number of allowed requests in the current period
|
||||
//'X-Rate-Limit-Remaining' - The number of remaining requests in the current period
|
||||
//'X-Rate-Limit-Reset' - The number of seconds left in the current period,
|
||||
];
|
||||
}
|
||||
|
||||
public static function startsWith($haystack, $needle)
|
||||
{
|
||||
return $needle === "" || strpos($haystack, $needle) === 0;
|
||||
}
|
||||
|
||||
public static function endsWith($haystack, $needle)
|
||||
{
|
||||
return $needle === "" || substr($haystack, -strlen($needle)) === $needle;
|
||||
}
|
||||
|
||||
public static function getEntityRowClass($model)
|
||||
{
|
||||
$str = $model->is_deleted || ($model->deleted_at && $model->deleted_at != '0000-00-00') ? 'DISABLED ' : '';
|
||||
|
||||
if ($model->is_deleted) {
|
||||
$str .= 'ENTITY_DELETED ';
|
||||
}
|
||||
|
||||
if ($model->deleted_at && $model->deleted_at != '0000-00-00') {
|
||||
$str .= 'ENTITY_ARCHIVED ';
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
}
|
8
app/Libraries/entity.php
Normal file
8
app/Libraries/entity.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?
|
||||
|
||||
class Entity
|
||||
{
|
||||
public $id;
|
||||
public $type;
|
||||
}
|
||||
|
119
app/Libraries/timesheet_utils.php
Normal file
119
app/Libraries/timesheet_utils.php
Normal file
@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
class TimesheetUtils
|
||||
{
|
||||
public static function parseEventSummary($summary) {
|
||||
if (preg_match('/^\s*([^\s:\/]+)(?:\/([^:]+))?\s*:\s*([^)].*$|$)$/s', $summary, $matches)) {
|
||||
return [strtoupper($matches[1]), strtolower($matches[2]), $matches[3]];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static function parseICALEvent($eventstr) {
|
||||
if (preg_match_all('/(?:^|\r?\n)([^;:]+)[;:]([^\r\n]+)/s', $eventstr, $matches)) {
|
||||
// Build ICAL event array
|
||||
$data = ['summary' => ''];
|
||||
foreach ($matches[1] as $i => $key) {
|
||||
# Convert escaped linebreakes to linebreak
|
||||
$value = preg_replace("/\r?\n\s/", "", $matches[2][$i]);
|
||||
# Unescape , and ;
|
||||
$value = preg_replace('/\\\\([,;])/s', '$1', $value);
|
||||
$data[strtolower($key)] = $value;
|
||||
}
|
||||
return $data;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static function parseICALDate($datestr) {
|
||||
$dt = null;
|
||||
$timezone = null;
|
||||
if (preg_match('/^TZID=(.+?):([12]\d\d\d)(\d\d)(\d\d)T(\d\d)(\d\d)(\d\d)$/', $datestr, $m)) {
|
||||
$timezone = $m[1];
|
||||
$dt = new DateTime("{$m[2]}-{$m[3]}-{$m[4]}T{$m[5]}:{$m[6]}:{$m[7]}", new DateTimeZone($m[1]));
|
||||
|
||||
} else if (preg_match('/^VALUE=DATE:([12]\d\d\d)(\d\d)(\d\d)$/', $datestr, $m)) {
|
||||
$dt = new DateTime("{$m[1]}-{$m[2]}-{$m[3]}T00:00:00", new DateTimeZone("UTC"));
|
||||
|
||||
} else if (preg_match('/^([12]\d\d\d)(\d\d)(\d\d)T(\d\d)(\d\d)(\d\d)Z$/', $datestr, $m)) {
|
||||
$dt = new DateTime("{$m[1]}-{$m[2]}-{$m[3]}T{$m[4]}:{$m[5]}:{$m[6]}", new DateTimeZone("UTC"));
|
||||
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Convert all to UTC
|
||||
if($dt->getTimezone()->getName() != 'UTC') {
|
||||
$dt->setTimezone(new DateTimeZone('UTC'));
|
||||
}
|
||||
|
||||
return [$dt, $timezone];
|
||||
}
|
||||
|
||||
public static function curlGetUrls($urls = [], $timeout = 30) {
|
||||
// Create muxer
|
||||
$results = [];
|
||||
$multi = curl_multi_init();
|
||||
$handles = [];
|
||||
$ch2idx = [];
|
||||
try {
|
||||
foreach ($urls as $i => $url) {
|
||||
// Create new handle and add to muxer
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_ENCODING, "gzip");
|
||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); //timeout in seconds
|
||||
|
||||
curl_multi_add_handle($multi, $ch);
|
||||
$handles[(int) $ch] = $ch;
|
||||
$ch2idx[(int) $ch] = $i;
|
||||
}
|
||||
|
||||
// Do initial connect
|
||||
$still_running = true;
|
||||
while ($still_running) {
|
||||
// Do curl stuff
|
||||
while (($mrc = curl_multi_exec($multi, $still_running)) === CURLM_CALL_MULTI_PERFORM);
|
||||
if ($mrc !== CURLM_OK) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Try to read from handles that are ready
|
||||
while ($info = curl_multi_info_read($multi)) {
|
||||
if ($info["result"] == CURLE_OK) {
|
||||
$results[$ch2idx[(int) $info["handle"]]] = curl_multi_getcontent($info["handle"]);
|
||||
} else {
|
||||
if (CURLE_UNSUPPORTED_PROTOCOL == $info["result"]) {
|
||||
$results[$ch2idx[(int) $info["handle"]]] = [$info["result"], "Unsupported protocol"];
|
||||
} else if (CURLE_URL_MALFORMAT == $info["result"]) {
|
||||
$results[$ch2idx[(int) $info["handle"]]] = [$info["result"], "Malform url"];
|
||||
} else if (CURLE_COULDNT_RESOLVE_HOST == $info["result"]) {
|
||||
$results[$ch2idx[(int) $info["handle"]]] = [$info["result"], "Could not resolve host"];
|
||||
} else if (CURLE_OPERATION_TIMEDOUT == $info["result"]) {
|
||||
$results[$ch2idx[(int) $info["handle"]]] = [$info["result"], "Timed out waiting for operations to finish"];
|
||||
} else {
|
||||
$results[$ch2idx[(int) $info["handle"]]] = [$info["result"], "Unknown curl error code"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sleep until
|
||||
if (($rs = curl_multi_select($multi)) === -1) {
|
||||
usleep(20); // select failed for some reason, so we sleep for 20ms and run some more curl stuff
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
foreach ($handles as $chi => $ch) {
|
||||
curl_multi_remove_handle($multi, $ch);
|
||||
}
|
||||
|
||||
curl_multi_close($multi);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
}
|
363
app/Models/Account.php
Normal file
363
app/Models/Account.php
Normal file
@ -0,0 +1,363 @@
|
||||
<?php
|
||||
|
||||
class Account extends Eloquent
|
||||
{
|
||||
protected $softDelete = true;
|
||||
|
||||
public function users()
|
||||
{
|
||||
return $this->hasMany('User');
|
||||
}
|
||||
|
||||
public function clients()
|
||||
{
|
||||
return $this->hasMany('Client');
|
||||
}
|
||||
|
||||
public function invoices()
|
||||
{
|
||||
return $this->hasMany('Invoice');
|
||||
}
|
||||
|
||||
public function account_gateways()
|
||||
{
|
||||
return $this->hasMany('AccountGateway');
|
||||
}
|
||||
|
||||
public function tax_rates()
|
||||
{
|
||||
return $this->hasMany('TaxRate');
|
||||
}
|
||||
|
||||
public function country()
|
||||
{
|
||||
return $this->belongsTo('Country');
|
||||
}
|
||||
|
||||
public function timezone()
|
||||
{
|
||||
return $this->belongsTo('Timezone');
|
||||
}
|
||||
|
||||
public function language()
|
||||
{
|
||||
return $this->belongsTo('Language');
|
||||
}
|
||||
|
||||
public function date_format()
|
||||
{
|
||||
return $this->belongsTo('DateFormat');
|
||||
}
|
||||
|
||||
public function datetime_format()
|
||||
{
|
||||
return $this->belongsTo('DatetimeFormat');
|
||||
}
|
||||
|
||||
public function size()
|
||||
{
|
||||
return $this->belongsTo('Size');
|
||||
}
|
||||
|
||||
public function industry()
|
||||
{
|
||||
return $this->belongsTo('Industry');
|
||||
}
|
||||
|
||||
public function isGatewayConfigured($gatewayId = 0)
|
||||
{
|
||||
$this->load('account_gateways');
|
||||
|
||||
if ($gatewayId) {
|
||||
return $this->getGatewayConfig($gatewayId) != false;
|
||||
} else {
|
||||
return count($this->account_gateways) > 0;
|
||||
}
|
||||
}
|
||||
|
||||
public function getDisplayName()
|
||||
{
|
||||
if ($this->name) {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
$this->load('users');
|
||||
$user = $this->users()->first();
|
||||
|
||||
return $user->getDisplayName();
|
||||
}
|
||||
|
||||
public function getTimezone()
|
||||
{
|
||||
if ($this->timezone) {
|
||||
return $this->timezone->name;
|
||||
} else {
|
||||
return 'US/Eastern';
|
||||
}
|
||||
}
|
||||
|
||||
public function getGatewayByType($type = PAYMENT_TYPE_ANY)
|
||||
{
|
||||
foreach ($this->account_gateways as $gateway) {
|
||||
if ($type == PAYMENT_TYPE_ANY) {
|
||||
return $gateway;
|
||||
} elseif ($gateway->isPayPal() && $type == PAYMENT_TYPE_PAYPAL) {
|
||||
return $gateway;
|
||||
} elseif (!$gateway->isPayPal() && $type == PAYMENT_TYPE_CREDIT_CARD) {
|
||||
return $gateway;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getGatewayConfig($gatewayId)
|
||||
{
|
||||
foreach ($this->account_gateways as $gateway) {
|
||||
if ($gateway->gateway_id == $gatewayId) {
|
||||
return $gateway;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getLogoPath()
|
||||
{
|
||||
return 'logo/'.$this->account_key.'.jpg';
|
||||
}
|
||||
|
||||
public function getLogoWidth()
|
||||
{
|
||||
$path = $this->getLogoPath();
|
||||
if (!file_exists($path)) {
|
||||
return 0;
|
||||
}
|
||||
list($width, $height) = getimagesize($path);
|
||||
|
||||
return $width;
|
||||
}
|
||||
|
||||
public function getLogoHeight()
|
||||
{
|
||||
$path = $this->getLogoPath();
|
||||
if (!file_exists($path)) {
|
||||
return 0;
|
||||
}
|
||||
list($width, $height) = getimagesize($path);
|
||||
|
||||
return $height;
|
||||
}
|
||||
|
||||
public function getNextInvoiceNumber($isQuote = false)
|
||||
{
|
||||
$counter = $isQuote && !$this->share_counter ? $this->quote_number_counter : $this->invoice_number_counter;
|
||||
$prefix = $isQuote ? $this->quote_number_prefix : $this->invoice_number_prefix;
|
||||
|
||||
return $prefix.str_pad($counter, 4, "0", STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
public function incrementCounter($invoiceNumber, $isQuote = false, $isRecurring)
|
||||
{
|
||||
// check if the user modified the invoice number
|
||||
if (!$isRecurring && $invoiceNumber != $this->getNextInvoiceNumber($isQuote)) {
|
||||
$number = intval(preg_replace('/[^0-9]/', '', $invoiceNumber));
|
||||
if ($isQuote && !$this->share_counter) {
|
||||
$this->quote_number_counter = $number + 1;
|
||||
} else {
|
||||
$this->invoice_number_counter = $number + 1;
|
||||
}
|
||||
// otherwise, just increment the counter
|
||||
} else {
|
||||
if ($isQuote && !$this->share_counter) {
|
||||
$this->quote_number_counter += 1;
|
||||
} else {
|
||||
$this->invoice_number_counter += 1;
|
||||
}
|
||||
}
|
||||
|
||||
$this->save();
|
||||
}
|
||||
|
||||
public function getLocale()
|
||||
{
|
||||
$language = Language::remember(DEFAULT_QUERY_CACHE)->where('id', '=', $this->account->language_id)->first();
|
||||
|
||||
return $language->locale;
|
||||
}
|
||||
|
||||
public function loadLocalizationSettings()
|
||||
{
|
||||
$this->load('timezone', 'date_format', 'datetime_format', 'language');
|
||||
|
||||
Session::put(SESSION_TIMEZONE, $this->timezone ? $this->timezone->name : DEFAULT_TIMEZONE);
|
||||
Session::put(SESSION_DATE_FORMAT, $this->date_format ? $this->date_format->format : DEFAULT_DATE_FORMAT);
|
||||
Session::put(SESSION_DATE_PICKER_FORMAT, $this->date_format ? $this->date_format->picker_format : DEFAULT_DATE_PICKER_FORMAT);
|
||||
Session::put(SESSION_DATETIME_FORMAT, $this->datetime_format ? $this->datetime_format->format : DEFAULT_DATETIME_FORMAT);
|
||||
Session::put(SESSION_CURRENCY, $this->currency_id ? $this->currency_id : DEFAULT_CURRENCY);
|
||||
Session::put(SESSION_LOCALE, $this->language_id ? $this->language->locale : DEFAULT_LOCALE);
|
||||
}
|
||||
|
||||
public function getInvoiceLabels()
|
||||
{
|
||||
$data = [];
|
||||
$fields = [
|
||||
'invoice',
|
||||
'invoice_date',
|
||||
'due_date',
|
||||
'invoice_number',
|
||||
'po_number',
|
||||
'discount',
|
||||
'taxes',
|
||||
'tax',
|
||||
'item',
|
||||
'description',
|
||||
'unit_cost',
|
||||
'quantity',
|
||||
'line_total',
|
||||
'subtotal',
|
||||
'paid_to_date',
|
||||
'balance_due',
|
||||
'terms',
|
||||
'your_invoice',
|
||||
'quote',
|
||||
'your_quote',
|
||||
'quote_date',
|
||||
'quote_number',
|
||||
'total',
|
||||
'invoice_issued_to',
|
||||
];
|
||||
|
||||
foreach ($fields as $field) {
|
||||
$data[$field] = trans("texts.$field");
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function isPro()
|
||||
{
|
||||
if (!Utils::isNinjaProd()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->account_key == NINJA_ACCOUNT_KEY) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$datePaid = $this->pro_plan_paid;
|
||||
|
||||
if (!$datePaid || $datePaid == '0000-00-00') {
|
||||
return false;
|
||||
} elseif ($datePaid == NINJA_DATE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$today = new DateTime('now');
|
||||
$datePaid = DateTime::createFromFormat('Y-m-d', $datePaid);
|
||||
$interval = $today->diff($datePaid);
|
||||
|
||||
return $interval->y == 0;
|
||||
}
|
||||
|
||||
public function isWhiteLabel()
|
||||
{
|
||||
if (Utils::isNinjaProd()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->pro_plan_paid == NINJA_DATE;
|
||||
}
|
||||
|
||||
public function getSubscription($eventId)
|
||||
{
|
||||
return Subscription::where('account_id', '=', $this->id)->where('event_id', '=', $eventId)->first();
|
||||
}
|
||||
|
||||
public function hideFieldsForViz()
|
||||
{
|
||||
foreach ($this->clients as $client) {
|
||||
$client->setVisible([
|
||||
'public_id',
|
||||
'name',
|
||||
'balance',
|
||||
'paid_to_date',
|
||||
'invoices',
|
||||
'contacts',
|
||||
]);
|
||||
|
||||
foreach ($client->invoices as $invoice) {
|
||||
$invoice->setVisible([
|
||||
'public_id',
|
||||
'invoice_number',
|
||||
'amount',
|
||||
'balance',
|
||||
'invoice_status_id',
|
||||
'invoice_items',
|
||||
'created_at',
|
||||
]);
|
||||
|
||||
foreach ($invoice->invoice_items as $invoiceItem) {
|
||||
$invoiceItem->setVisible([
|
||||
'product_key',
|
||||
'cost',
|
||||
'qty',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($client->contacts as $contact) {
|
||||
$contact->setVisible([
|
||||
'public_id',
|
||||
'first_name',
|
||||
'last_name',
|
||||
'email', ]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getEmailTemplate($entityType, $message = false)
|
||||
{
|
||||
$field = "email_template_$entityType";
|
||||
$template = $this->$field;
|
||||
|
||||
if ($template) {
|
||||
return $template;
|
||||
}
|
||||
|
||||
$template = "\$client,<p/>\r\n\r\n" .
|
||||
trans("texts.{$entityType}_message", ['amount' => '$amount']) . "<p/>\r\n\r\n";
|
||||
|
||||
if ($entityType != ENTITY_PAYMENT) {
|
||||
$template .= "<a href=\"\$link\">\$link</a><p/>\r\n\r\n";
|
||||
}
|
||||
|
||||
if ($message) {
|
||||
$template .= "$message<p/>\r\n\r\n";
|
||||
}
|
||||
|
||||
return $template . "\$footer";
|
||||
}
|
||||
|
||||
public function getEmailFooter()
|
||||
{
|
||||
if ($this->email_footer) {
|
||||
return $this->email_footer;
|
||||
} else {
|
||||
return "<p>" . trans('texts.email_signature') . "<br>\$account</p>";
|
||||
}
|
||||
}
|
||||
|
||||
public function showTokenCheckbox()
|
||||
{
|
||||
return $this->token_billing_type_id == TOKEN_BILLING_OPT_IN
|
||||
|| $this->token_billing_type_id == TOKEN_BILLING_OPT_OUT;
|
||||
}
|
||||
|
||||
public function selectTokenCheckbox()
|
||||
{
|
||||
return $this->token_billing_type_id == TOKEN_BILLING_OPT_OUT;
|
||||
}
|
||||
}
|
28
app/Models/AccountGateway.php
Normal file
28
app/Models/AccountGateway.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
class AccountGateway extends EntityModel
|
||||
{
|
||||
public function gateway()
|
||||
{
|
||||
return $this->belongsTo('Gateway');
|
||||
}
|
||||
|
||||
public function getCreditcardTypes()
|
||||
{
|
||||
$flags = unserialize(CREDIT_CARDS);
|
||||
$arrayOfImages = [];
|
||||
|
||||
foreach ($flags as $card => $name) {
|
||||
if (($this->accepted_credit_cards & $card) == $card) {
|
||||
$arrayOfImages[] = ['source' => asset($name['card']), 'alt' => $name['text']];
|
||||
}
|
||||
}
|
||||
|
||||
return $arrayOfImages;
|
||||
}
|
||||
|
||||
public function isPayPal() {
|
||||
return $this->gateway_id == GATEWAY_PAYPAL_EXPRESS;
|
||||
}
|
||||
}
|
||||
|
7
app/Models/AccountGatewayToken.php
Normal file
7
app/Models/AccountGatewayToken.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
class AccountGatewayToken extends Eloquent
|
||||
{
|
||||
protected $softDelete = true;
|
||||
public $timestamps = true;
|
||||
}
|
9
app/Models/AccountToken.php
Normal file
9
app/Models/AccountToken.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
class AccountToken extends EntityModel
|
||||
{
|
||||
public function account()
|
||||
{
|
||||
return $this->belongsTo('Account');
|
||||
}
|
||||
}
|
470
app/Models/Activity.php
Normal file
470
app/Models/Activity.php
Normal file
@ -0,0 +1,470 @@
|
||||
<?php
|
||||
|
||||
class Activity extends Eloquent
|
||||
{
|
||||
public $timestamps = true;
|
||||
protected $softDelete = false;
|
||||
|
||||
public function scopeScope($query)
|
||||
{
|
||||
return $query->whereAccountId(Auth::user()->account_id);
|
||||
}
|
||||
|
||||
public function account()
|
||||
{
|
||||
return $this->belongsTo('Account');
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo('User');
|
||||
}
|
||||
|
||||
private static function getBlank($entity = false)
|
||||
{
|
||||
$activity = new Activity();
|
||||
|
||||
if ($entity) {
|
||||
$activity->user_id = $entity instanceof User ? $entity->id : $entity->user_id;
|
||||
$activity->account_id = $entity->account_id;
|
||||
} elseif (Auth::check()) {
|
||||
$activity->user_id = Auth::user()->id;
|
||||
$activity->account_id = Auth::user()->account_id;
|
||||
} else {
|
||||
Utils::fatalError();
|
||||
}
|
||||
|
||||
$activity->token_id = Session::get('token_id', null);
|
||||
$activity->ip = Request::getClientIp();
|
||||
|
||||
return $activity;
|
||||
}
|
||||
|
||||
public static function createClient($client, $notify = true)
|
||||
{
|
||||
$activity = Activity::getBlank();
|
||||
$activity->client_id = $client->id;
|
||||
$activity->activity_type_id = ACTIVITY_TYPE_CREATE_CLIENT;
|
||||
$activity->message = Utils::encodeActivity(Auth::user(), 'created', $client);
|
||||
$activity->save();
|
||||
|
||||
if ($notify) {
|
||||
Activity::checkSubscriptions(EVENT_CREATE_CLIENT, $client);
|
||||
}
|
||||
}
|
||||
|
||||
public static function updateClient($client)
|
||||
{
|
||||
if ($client->is_deleted && !$client->getOriginal('is_deleted')) {
|
||||
$activity = Activity::getBlank();
|
||||
$activity->client_id = $client->id;
|
||||
$activity->activity_type_id = ACTIVITY_TYPE_DELETE_CLIENT;
|
||||
$activity->message = Utils::encodeActivity(Auth::user(), 'deleted', $client);
|
||||
$activity->balance = $client->balance;
|
||||
$activity->save();
|
||||
}
|
||||
}
|
||||
|
||||
public static function archiveClient($client)
|
||||
{
|
||||
if (!$client->is_deleted) {
|
||||
$activity = Activity::getBlank();
|
||||
$activity->client_id = $client->id;
|
||||
$activity->activity_type_id = ACTIVITY_TYPE_ARCHIVE_CLIENT;
|
||||
$activity->message = Utils::encodeActivity(Auth::user(), 'archived', $client);
|
||||
$activity->balance = $client->balance;
|
||||
$activity->save();
|
||||
}
|
||||
}
|
||||
|
||||
public static function restoreClient($client)
|
||||
{
|
||||
$activity = Activity::getBlank();
|
||||
$activity->client_id = $client->id;
|
||||
$activity->activity_type_id = ACTIVITY_TYPE_RESTORE_CLIENT;
|
||||
$activity->message = Utils::encodeActivity(Auth::user(), 'restored', $client);
|
||||
$activity->balance = $client->balance;
|
||||
$activity->save();
|
||||
}
|
||||
|
||||
public static function createInvoice($invoice)
|
||||
{
|
||||
if (Auth::check()) {
|
||||
$message = Utils::encodeActivity(Auth::user(), 'created', $invoice);
|
||||
} else {
|
||||
$message = Utils::encodeActivity(null, 'created', $invoice);
|
||||
}
|
||||
|
||||
$adjustment = 0;
|
||||
$client = $invoice->client;
|
||||
if (!$invoice->is_quote && !$invoice->is_recurring) {
|
||||
$adjustment = $invoice->amount;
|
||||
$client->balance = $client->balance + $adjustment;
|
||||
$client->save();
|
||||
}
|
||||
|
||||
$activity = Activity::getBlank($invoice);
|
||||
$activity->invoice_id = $invoice->id;
|
||||
$activity->client_id = $invoice->client_id;
|
||||
$activity->activity_type_id = $invoice->is_quote ? ACTIVITY_TYPE_CREATE_QUOTE : ACTIVITY_TYPE_CREATE_INVOICE;
|
||||
$activity->message = $message;
|
||||
$activity->balance = $client->balance;
|
||||
$activity->adjustment = $adjustment;
|
||||
$activity->save();
|
||||
|
||||
Activity::checkSubscriptions($invoice->is_quote ? EVENT_CREATE_QUOTE : EVENT_CREATE_INVOICE, $invoice);
|
||||
}
|
||||
|
||||
public static function archiveInvoice($invoice)
|
||||
{
|
||||
if (!$invoice->is_deleted) {
|
||||
$activity = Activity::getBlank();
|
||||
$activity->invoice_id = $invoice->id;
|
||||
$activity->client_id = $invoice->client_id;
|
||||
$activity->activity_type_id = $invoice->is_quote ? ACTIVITY_TYPE_ARCHIVE_QUOTE : ACTIVITY_TYPE_ARCHIVE_INVOICE;
|
||||
$activity->message = Utils::encodeActivity(Auth::user(), 'archived', $invoice);
|
||||
$activity->balance = $invoice->client->balance;
|
||||
|
||||
$activity->save();
|
||||
}
|
||||
}
|
||||
|
||||
public static function restoreInvoice($invoice)
|
||||
{
|
||||
$activity = Activity::getBlank();
|
||||
$activity->invoice_id = $invoice->id;
|
||||
$activity->client_id = $invoice->client_id;
|
||||
$activity->activity_type_id = $invoice->is_quote ? ACTIVITY_TYPE_RESTORE_QUOTE : ACTIVITY_TYPE_RESTORE_INVOICE;
|
||||
$activity->message = Utils::encodeActivity(Auth::user(), 'restored', $invoice);
|
||||
$activity->balance = $invoice->client->balance;
|
||||
|
||||
$activity->save();
|
||||
}
|
||||
|
||||
public static function emailInvoice($invitation)
|
||||
{
|
||||
$adjustment = 0;
|
||||
$client = $invitation->invoice->client;
|
||||
|
||||
$activity = Activity::getBlank($invitation);
|
||||
$activity->client_id = $invitation->invoice->client_id;
|
||||
$activity->invoice_id = $invitation->invoice_id;
|
||||
$activity->contact_id = $invitation->contact_id;
|
||||
$activity->activity_type_id = $invitation->invoice ? ACTIVITY_TYPE_EMAIL_QUOTE : ACTIVITY_TYPE_EMAIL_INVOICE;
|
||||
$activity->message = Utils::encodeActivity(Auth::check() ? Auth::user() : null, 'emailed', $invitation->invoice, $invitation->contact);
|
||||
$activity->balance = $client->balance;
|
||||
$activity->save();
|
||||
}
|
||||
|
||||
public static function updateInvoice($invoice)
|
||||
{
|
||||
$client = $invoice->client;
|
||||
|
||||
if ($invoice->is_deleted && !$invoice->getOriginal('is_deleted')) {
|
||||
$adjustment = 0;
|
||||
if (!$invoice->is_quote && !$invoice->is_recurring) {
|
||||
$adjustment = $invoice->balance * -1;
|
||||
$client->balance = $client->balance - $invoice->balance;
|
||||
$client->paid_to_date = $client->paid_to_date - ($invoice->amount - $invoice->balance);
|
||||
$client->save();
|
||||
}
|
||||
|
||||
$activity = Activity::getBlank();
|
||||
$activity->client_id = $invoice->client_id;
|
||||
$activity->invoice_id = $invoice->id;
|
||||
$activity->activity_type_id = $invoice->is_quote ? ACTIVITY_TYPE_DELETE_QUOTE : ACTIVITY_TYPE_DELETE_INVOICE;
|
||||
$activity->message = Utils::encodeActivity(Auth::user(), 'deleted', $invoice);
|
||||
$activity->balance = $invoice->client->balance;
|
||||
$activity->adjustment = $adjustment;
|
||||
$activity->save();
|
||||
} else {
|
||||
$diff = floatval($invoice->amount) - floatval($invoice->getOriginal('amount'));
|
||||
|
||||
$fieldChanged = false;
|
||||
foreach (['invoice_number', 'po_number', 'invoice_date', 'due_date', 'terms', 'public_notes', 'invoice_footer'] as $field) {
|
||||
if ($invoice->$field != $invoice->getOriginal($field)) {
|
||||
$fieldChanged = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($diff > 0 || $fieldChanged) {
|
||||
$backupInvoice = Invoice::with('invoice_items', 'client.account', 'client.contacts')->find($invoice->id);
|
||||
|
||||
if ($diff > 0 && !$invoice->is_quote && !$invoice->is_recurring) {
|
||||
$client->balance = $client->balance + $diff;
|
||||
$client->save();
|
||||
}
|
||||
|
||||
$activity = Activity::getBlank($invoice);
|
||||
$activity->client_id = $invoice->client_id;
|
||||
$activity->invoice_id = $invoice->id;
|
||||
$activity->activity_type_id = $invoice->is_quote ? ACTIVITY_TYPE_UPDATE_QUOTE : ACTIVITY_TYPE_UPDATE_INVOICE;
|
||||
$activity->message = Utils::encodeActivity(Auth::user(), 'updated', $invoice);
|
||||
$activity->balance = $client->balance;
|
||||
$activity->adjustment = $invoice->is_quote || $invoice->is_recurring ? 0 : $diff;
|
||||
$activity->json_backup = $backupInvoice->hidePrivateFields()->toJSON();
|
||||
$activity->save();
|
||||
|
||||
if ($invoice->isPaid() && $invoice->balance > 0) {
|
||||
$invoice->invoice_status_id = INVOICE_STATUS_PARTIAL;
|
||||
$invoice->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function viewInvoice($invitation)
|
||||
{
|
||||
if (Session::get($invitation->invitation_key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Session::put($invitation->invitation_key, true);
|
||||
$invoice = $invitation->invoice;
|
||||
|
||||
if (!$invoice->isViewed()) {
|
||||
$invoice->invoice_status_id = INVOICE_STATUS_VIEWED;
|
||||
$invoice->save();
|
||||
}
|
||||
|
||||
$now = Carbon::now()->toDateTimeString();
|
||||
|
||||
$invitation->viewed_date = $now;
|
||||
$invitation->save();
|
||||
|
||||
$client = $invoice->client;
|
||||
$client->last_login = $now;
|
||||
$client->save();
|
||||
|
||||
$activity = Activity::getBlank($invitation);
|
||||
$activity->client_id = $invitation->invoice->client_id;
|
||||
$activity->invitation_id = $invitation->id;
|
||||
$activity->contact_id = $invitation->contact_id;
|
||||
$activity->invoice_id = $invitation->invoice_id;
|
||||
$activity->activity_type_id = $invitation->invoice->is_quote ? ACTIVITY_TYPE_VIEW_QUOTE : ACTIVITY_TYPE_VIEW_INVOICE;
|
||||
$activity->message = Utils::encodeActivity($invitation->contact, 'viewed', $invitation->invoice);
|
||||
$activity->balance = $invitation->invoice->client->balance;
|
||||
$activity->save();
|
||||
}
|
||||
|
||||
public static function approveQuote($invitation) {
|
||||
|
||||
$activity = Activity::getBlank($invitation);
|
||||
$activity->client_id = $invitation->invoice->client_id;
|
||||
$activity->invitation_id = $invitation->id;
|
||||
$activity->contact_id = $invitation->contact_id;
|
||||
$activity->invoice_id = $invitation->invoice_id;
|
||||
$activity->activity_type_id = ACTIVITY_TYPE_APPROVE_QUOTE;
|
||||
$activity->message = Utils::encodeActivity($invitation->contact, 'approved', $invitation->invoice);
|
||||
$activity->balance = $invitation->invoice->client->balance;
|
||||
$activity->save();
|
||||
}
|
||||
|
||||
public static function createPayment($payment)
|
||||
{
|
||||
$client = $payment->client;
|
||||
$client->balance = $client->balance - $payment->amount;
|
||||
$client->paid_to_date = $client->paid_to_date + $payment->amount;
|
||||
$client->save();
|
||||
|
||||
if ($payment->contact_id) {
|
||||
$activity = Activity::getBlank($client);
|
||||
$activity->contact_id = $payment->contact_id;
|
||||
$activity->message = Utils::encodeActivity($payment->invitation->contact, 'entered '.$payment->getName().' for ', $payment->invoice);
|
||||
} else {
|
||||
$activity = Activity::getBlank($client);
|
||||
$message = $payment->payment_type_id == PAYMENT_TYPE_CREDIT ? 'applied credit for ' : 'entered '.$payment->getName().' for ';
|
||||
$activity->message = Utils::encodeActivity(Auth::user(), $message, $payment->invoice);
|
||||
}
|
||||
|
||||
$activity->payment_id = $payment->id;
|
||||
|
||||
if ($payment->invoice_id) {
|
||||
$activity->invoice_id = $payment->invoice_id;
|
||||
|
||||
$invoice = $payment->invoice;
|
||||
$invoice->balance = $invoice->balance - $payment->amount;
|
||||
$invoice->invoice_status_id = ($invoice->balance > 0) ? INVOICE_STATUS_PARTIAL : INVOICE_STATUS_PAID;
|
||||
$invoice->save();
|
||||
}
|
||||
|
||||
$activity->payment_id = $payment->id;
|
||||
$activity->client_id = $payment->client_id;
|
||||
$activity->activity_type_id = ACTIVITY_TYPE_CREATE_PAYMENT;
|
||||
$activity->balance = $client->balance;
|
||||
$activity->adjustment = $payment->amount * -1;
|
||||
$activity->save();
|
||||
|
||||
Activity::checkSubscriptions(EVENT_CREATE_PAYMENT, $payment);
|
||||
}
|
||||
|
||||
public static function updatePayment($payment)
|
||||
{
|
||||
if ($payment->is_deleted && !$payment->getOriginal('is_deleted')) {
|
||||
$client = $payment->client;
|
||||
$client->balance = $client->balance + $payment->amount;
|
||||
$client->paid_to_date = $client->paid_to_date - $payment->amount;
|
||||
$client->save();
|
||||
|
||||
$invoice = $payment->invoice;
|
||||
$invoice->balance = $invoice->balance + $payment->amount;
|
||||
$invoice->save();
|
||||
|
||||
$activity = Activity::getBlank();
|
||||
$activity->payment_id = $payment->id;
|
||||
$activity->client_id = $invoice->client_id;
|
||||
$activity->invoice_id = $invoice->id;
|
||||
$activity->activity_type_id = ACTIVITY_TYPE_DELETE_PAYMENT;
|
||||
$activity->message = Utils::encodeActivity(Auth::user(), 'deleted '.$payment->getName());
|
||||
$activity->balance = $client->balance;
|
||||
$activity->adjustment = $payment->amount;
|
||||
$activity->save();
|
||||
} else {
|
||||
/*
|
||||
$diff = floatval($invoice->amount) - floatval($invoice->getOriginal('amount'));
|
||||
|
||||
if ($diff == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$client = $invoice->client;
|
||||
$client->balance = $client->balance + $diff;
|
||||
$client->save();
|
||||
|
||||
$activity = Activity::getBlank($invoice);
|
||||
$activity->client_id = $invoice->client_id;
|
||||
$activity->invoice_id = $invoice->id;
|
||||
$activity->activity_type_id = ACTIVITY_TYPE_UPDATE_INVOICE;
|
||||
$activity->message = Utils::encodeActivity(Auth::user(), 'updated', $invoice);
|
||||
$activity->balance = $client->balance;
|
||||
$activity->adjustment = $diff;
|
||||
$activity->json_backup = $backupInvoice->hidePrivateFields()->toJSON();
|
||||
$activity->save();
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
public static function archivePayment($payment)
|
||||
{
|
||||
if ($payment->is_deleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
$client = $payment->client;
|
||||
$invoice = $payment->invoice;
|
||||
|
||||
$activity = Activity::getBlank();
|
||||
$activity->payment_id = $payment->id;
|
||||
$activity->invoice_id = $invoice->id;
|
||||
$activity->client_id = $client->id;
|
||||
$activity->activity_type_id = ACTIVITY_TYPE_ARCHIVE_PAYMENT;
|
||||
$activity->message = Utils::encodeActivity(Auth::user(), 'archived '.$payment->getName());
|
||||
$activity->balance = $client->balance;
|
||||
$activity->adjustment = 0;
|
||||
$activity->save();
|
||||
}
|
||||
|
||||
public static function restorePayment($payment)
|
||||
{
|
||||
$client = $payment->client;
|
||||
$invoice = $payment->invoice;
|
||||
|
||||
$activity = Activity::getBlank();
|
||||
$activity->payment_id = $payment->id;
|
||||
$activity->invoice_id = $invoice->id;
|
||||
$activity->client_id = $client->id;
|
||||
$activity->activity_type_id = ACTIVITY_TYPE_RESTORE_PAYMENT;
|
||||
$activity->message = Utils::encodeActivity(Auth::user(), 'restored '.$payment->getName());
|
||||
$activity->balance = $client->balance;
|
||||
$activity->adjustment = 0;
|
||||
$activity->save();
|
||||
}
|
||||
|
||||
public static function createCredit($credit)
|
||||
{
|
||||
$activity = Activity::getBlank();
|
||||
$activity->message = Utils::encodeActivity(Auth::user(), 'entered '.Utils::formatMoney($credit->amount, $credit->client->currency_id).' credit');
|
||||
$activity->credit_id = $credit->id;
|
||||
$activity->client_id = $credit->client_id;
|
||||
$activity->activity_type_id = ACTIVITY_TYPE_CREATE_CREDIT;
|
||||
$activity->balance = $credit->client->balance;
|
||||
$activity->save();
|
||||
}
|
||||
|
||||
public static function updateCredit($credit)
|
||||
{
|
||||
if ($credit->is_deleted && !$credit->getOriginal('is_deleted')) {
|
||||
$activity = Activity::getBlank();
|
||||
$activity->credit_id = $credit->id;
|
||||
$activity->client_id = $credit->client_id;
|
||||
$activity->activity_type_id = ACTIVITY_TYPE_DELETE_CREDIT;
|
||||
$activity->message = Utils::encodeActivity(Auth::user(), 'deleted '.Utils::formatMoney($credit->balance, $credit->client->currency_id).' credit');
|
||||
$activity->balance = $credit->client->balance;
|
||||
$activity->save();
|
||||
} else {
|
||||
/*
|
||||
$diff = floatval($invoice->amount) - floatval($invoice->getOriginal('amount'));
|
||||
|
||||
if ($diff == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$client = $invoice->client;
|
||||
$client->balance = $client->balance + $diff;
|
||||
$client->save();
|
||||
|
||||
$activity = Activity::getBlank($invoice);
|
||||
$activity->client_id = $invoice->client_id;
|
||||
$activity->invoice_id = $invoice->id;
|
||||
$activity->activity_type_id = ACTIVITY_TYPE_UPDATE_INVOICE;
|
||||
$activity->message = Utils::encodeActivity(Auth::user(), 'updated', $invoice);
|
||||
$activity->balance = $client->balance;
|
||||
$activity->adjustment = $diff;
|
||||
$activity->json_backup = $backupInvoice->hidePrivateFields()->toJSON();
|
||||
$activity->save();
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
public static function archiveCredit($credit)
|
||||
{
|
||||
if ($credit->is_deleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
$activity = Activity::getBlank();
|
||||
$activity->client_id = $credit->client_id;
|
||||
$activity->credit_id = $credit->id;
|
||||
$activity->activity_type_id = ACTIVITY_TYPE_ARCHIVE_CREDIT;
|
||||
$activity->message = Utils::encodeActivity(Auth::user(), 'archived '.Utils::formatMoney($credit->balance, $credit->client->currency_id).' credit');
|
||||
$activity->balance = $credit->client->balance;
|
||||
$activity->save();
|
||||
}
|
||||
|
||||
public static function restoreCredit($credit)
|
||||
{
|
||||
$activity = Activity::getBlank();
|
||||
$activity->client_id = $credit->client_id;
|
||||
$activity->credit_id = $credit->id;
|
||||
$activity->activity_type_id = ACTIVITY_TYPE_RESTORE_CREDIT;
|
||||
$activity->message = Utils::encodeActivity(Auth::user(), 'restored '.Utils::formatMoney($credit->balance, $credit->client->currency_id).' credit');
|
||||
$activity->balance = $credit->client->balance;
|
||||
$activity->save();
|
||||
}
|
||||
|
||||
private static function checkSubscriptions($event, $data)
|
||||
{
|
||||
if (!Auth::check()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$subscription = Auth::user()->account->getSubscription($event);
|
||||
|
||||
if ($subscription) {
|
||||
Utils::notifyZapier($subscription, $data);
|
||||
}
|
||||
}
|
||||
}
|
7
app/Models/Affiliate.php
Normal file
7
app/Models/Affiliate.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
class Affiliate extends Eloquent
|
||||
{
|
||||
public $timestamps = true;
|
||||
protected $softDelete = true;
|
||||
}
|
268
app/Models/Client.php
Normal file
268
app/Models/Client.php
Normal file
@ -0,0 +1,268 @@
|
||||
<?php
|
||||
|
||||
class Client extends EntityModel
|
||||
{
|
||||
public static $fieldName = 'Client - Name';
|
||||
public static $fieldPhone = 'Client - Phone';
|
||||
public static $fieldAddress1 = 'Client - Street';
|
||||
public static $fieldAddress2 = 'Client - Apt/Floor';
|
||||
public static $fieldCity = 'Client - City';
|
||||
public static $fieldState = 'Client - State';
|
||||
public static $fieldPostalCode = 'Client - Postal Code';
|
||||
public static $fieldNotes = 'Client - Notes';
|
||||
public static $fieldCountry = 'Client - Country';
|
||||
|
||||
public function account()
|
||||
{
|
||||
return $this->belongsTo('Account');
|
||||
}
|
||||
|
||||
public function invoices()
|
||||
{
|
||||
return $this->hasMany('Invoice');
|
||||
}
|
||||
|
||||
public function payments()
|
||||
{
|
||||
return $this->hasMany('Payment');
|
||||
}
|
||||
|
||||
public function contacts()
|
||||
{
|
||||
return $this->hasMany('Contact');
|
||||
}
|
||||
|
||||
public function projects()
|
||||
{
|
||||
return $this->hasMany('Project');
|
||||
}
|
||||
|
||||
public function country()
|
||||
{
|
||||
return $this->belongsTo('Country');
|
||||
}
|
||||
|
||||
public function currency()
|
||||
{
|
||||
return $this->belongsTo('Currency');
|
||||
}
|
||||
|
||||
public function size()
|
||||
{
|
||||
return $this->belongsTo('Size');
|
||||
}
|
||||
|
||||
public function industry()
|
||||
{
|
||||
return $this->belongsTo('Industry');
|
||||
}
|
||||
|
||||
public function getTotalCredit()
|
||||
{
|
||||
return DB::table('credits')
|
||||
->where('client_id', '=', $this->id)
|
||||
->whereNull('deleted_at')
|
||||
->sum('balance');
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return $this->getDisplayName();
|
||||
}
|
||||
|
||||
public function getDisplayName()
|
||||
{
|
||||
if ($this->name) {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
$this->load('contacts');
|
||||
$contact = $this->contacts()->first();
|
||||
|
||||
return $contact->getDisplayName();
|
||||
}
|
||||
|
||||
public function getEntityType()
|
||||
{
|
||||
return ENTITY_CLIENT;
|
||||
}
|
||||
|
||||
public function getAddress()
|
||||
{
|
||||
$str = '';
|
||||
|
||||
if ($this->address1) {
|
||||
$str .= $this->address1.'<br/>';
|
||||
}
|
||||
if ($this->address2) {
|
||||
$str .= $this->address2.'<br/>';
|
||||
}
|
||||
if ($this->city) {
|
||||
$str .= $this->city.', ';
|
||||
}
|
||||
if ($this->state) {
|
||||
$str .= $this->state.' ';
|
||||
}
|
||||
if ($this->postal_code) {
|
||||
$str .= $this->postal_code;
|
||||
}
|
||||
if ($this->country) {
|
||||
$str .= '<br/>'.$this->country->name;
|
||||
}
|
||||
|
||||
if ($str) {
|
||||
$str = '<p>'.$str.'</p>';
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
public function getPhone()
|
||||
{
|
||||
$str = '';
|
||||
|
||||
if ($this->work_phone) {
|
||||
$str .= '<i class="fa fa-phone" style="width: 20px"></i>'.Utils::formatPhoneNumber($this->work_phone);
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
public function getIdNumber()
|
||||
{
|
||||
$str = '';
|
||||
|
||||
if ($this->id_number) {
|
||||
$str .= '<i class="fa fa-id-number" style="width: 20px"></i>'.trans('texts.id_number').': '.$this->id_number;
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
public function getVatNumber()
|
||||
{
|
||||
$str = '';
|
||||
|
||||
if ($this->vat_number) {
|
||||
$str .= '<i class="fa fa-vat-number" style="width: 20px"></i>'.trans('texts.vat_number').': '.$this->vat_number;
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
public function getNotes()
|
||||
{
|
||||
$str = '';
|
||||
|
||||
if ($this->private_notes) {
|
||||
$str .= '<i>'.$this->private_notes.'</i>';
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
public function getIndustry()
|
||||
{
|
||||
$str = '';
|
||||
|
||||
if ($this->client_industry) {
|
||||
$str .= $this->client_industry->name.' ';
|
||||
}
|
||||
|
||||
if ($this->client_size) {
|
||||
$str .= $this->client_size->name;
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
public function getCustomFields()
|
||||
{
|
||||
$str = '';
|
||||
$account = $this->account;
|
||||
|
||||
if ($account->custom_client_label1 && $this->custom_value1) {
|
||||
$str .= "{$account->custom_client_label1}: {$this->custom_value1}<br/>";
|
||||
}
|
||||
|
||||
if ($account->custom_client_label2 && $this->custom_value2) {
|
||||
$str .= "{$account->custom_client_label2}: {$this->custom_value2}<br/>";
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
public function getWebsite()
|
||||
{
|
||||
if (!$this->website) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$link = $this->website;
|
||||
$title = $this->website;
|
||||
$prefix = 'http://';
|
||||
|
||||
if (strlen($link) > 7 && substr($link, 0, 7) === $prefix) {
|
||||
$title = substr($title, 7);
|
||||
} else {
|
||||
$link = $prefix.$link;
|
||||
}
|
||||
|
||||
return link_to($link, $title, array('target' => '_blank'));
|
||||
}
|
||||
|
||||
public function getDateCreated()
|
||||
{
|
||||
if ($this->created_at == '0000-00-00 00:00:00') {
|
||||
return '---';
|
||||
} else {
|
||||
return $this->created_at->format('m/d/y h:i a');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function getGatewayToken()
|
||||
{
|
||||
$this->account->load('account_gateways');
|
||||
|
||||
if (!count($this->account->account_gateways)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$accountGateway = $this->account->getGatewayConfig(GATEWAY_STRIPE);
|
||||
|
||||
if (!$accountGateway) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$token = AccountGatewayToken::where('client_id', '=', $this->id)
|
||||
->where('account_gateway_id', '=', $accountGateway->id)->first();
|
||||
|
||||
return $token ? $token->token : false;
|
||||
}
|
||||
|
||||
public function getGatewayLink()
|
||||
{
|
||||
$token = $this->getGatewayToken();
|
||||
return $token ? "https://dashboard.stripe.com/customers/{$token}" : false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Client::created(function($client)
|
||||
{
|
||||
Activity::createClient($client);
|
||||
});
|
||||
*/
|
||||
|
||||
Client::updating(function ($client) {
|
||||
Activity::updateClient($client);
|
||||
});
|
||||
|
||||
Client::deleting(function ($client) {
|
||||
Activity::archiveClient($client);
|
||||
});
|
||||
|
||||
Client::restoring(function ($client) {
|
||||
Activity::restoreClient($client);
|
||||
});
|
74
app/Models/Contact.php
Normal file
74
app/Models/Contact.php
Normal file
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
class Contact extends EntityModel
|
||||
{
|
||||
public static $fieldFirstName = 'Contact - First Name';
|
||||
public static $fieldLastName = 'Contact - Last Name';
|
||||
public static $fieldEmail = 'Contact - Email';
|
||||
public static $fieldPhone = 'Contact - Phone';
|
||||
|
||||
public function client()
|
||||
{
|
||||
return $this->belongsTo('Client');
|
||||
}
|
||||
|
||||
public function getPersonType()
|
||||
{
|
||||
return PERSON_CONTACT;
|
||||
}
|
||||
|
||||
/*
|
||||
public function getLastLogin()
|
||||
{
|
||||
if ($this->last_login == '0000-00-00 00:00:00')
|
||||
{
|
||||
return '---';
|
||||
}
|
||||
else
|
||||
{
|
||||
return $this->last_login->format('m/d/y h:i a');
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
public function getDisplayName()
|
||||
{
|
||||
if ($this->getFullName()) {
|
||||
return $this->getFullName();
|
||||
} else {
|
||||
return $this->email;
|
||||
}
|
||||
}
|
||||
|
||||
public function getFullName()
|
||||
{
|
||||
if ($this->first_name || $this->last_name) {
|
||||
return $this->first_name.' '.$this->last_name;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
public function getDetails()
|
||||
{
|
||||
$str = '';
|
||||
|
||||
if ($this->first_name || $this->last_name) {
|
||||
$str .= '<b>'.$this->first_name.' '.$this->last_name.'</b><br/>';
|
||||
}
|
||||
|
||||
if ($this->email) {
|
||||
$str .= '<i class="fa fa-envelope" style="width: 20px"></i>'.HTML::mailto($this->email, $this->email).'<br/>';
|
||||
}
|
||||
|
||||
if ($this->phone) {
|
||||
$str .= '<i class="fa fa-phone" style="width: 20px"></i>'.Utils::formatPhoneNumber($this->phone);
|
||||
}
|
||||
|
||||
if ($str) {
|
||||
$str = '<p>'.$str.'</p>';
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
}
|
9
app/Models/Country.php
Normal file
9
app/Models/Country.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
class Country extends Eloquent
|
||||
{
|
||||
public $timestamps = false;
|
||||
protected $softDelete = false;
|
||||
|
||||
protected $visible = ['id', 'name'];
|
||||
}
|
55
app/Models/Credit.php
Normal file
55
app/Models/Credit.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
class Credit extends EntityModel
|
||||
{
|
||||
public function invoice()
|
||||
{
|
||||
return $this->belongsTo('Invoice')->withTrashed();
|
||||
}
|
||||
|
||||
public function client()
|
||||
{
|
||||
return $this->belongsTo('Client')->withTrashed();
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getEntityType()
|
||||
{
|
||||
return ENTITY_CREDIT;
|
||||
}
|
||||
|
||||
public function apply($amount)
|
||||
{
|
||||
if ($amount > $this->balance) {
|
||||
$applied = $this->balance;
|
||||
$this->balance = 0;
|
||||
} else {
|
||||
$applied = $amount;
|
||||
$this->balance = $this->balance - $amount;
|
||||
}
|
||||
|
||||
$this->save();
|
||||
|
||||
return $applied;
|
||||
}
|
||||
}
|
||||
|
||||
Credit::created(function ($credit) {
|
||||
Activity::createCredit($credit);
|
||||
});
|
||||
|
||||
Credit::updating(function ($credit) {
|
||||
Activity::updateCredit($credit);
|
||||
});
|
||||
|
||||
Credit::deleting(function ($credit) {
|
||||
Activity::archiveCredit($credit);
|
||||
});
|
||||
|
||||
Credit::restoring(function ($credit) {
|
||||
Activity::restoreCredit($credit);
|
||||
});
|
7
app/Models/Currency.php
Normal file
7
app/Models/Currency.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
class Currency extends Eloquent
|
||||
{
|
||||
public $timestamps = false;
|
||||
protected $softDelete = false;
|
||||
}
|
7
app/Models/DateFormat.php
Normal file
7
app/Models/DateFormat.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
class DateFormat extends Eloquent
|
||||
{
|
||||
public $timestamps = false;
|
||||
protected $softDelete = false;
|
||||
}
|
7
app/Models/DatetimeFormat.php
Normal file
7
app/Models/DatetimeFormat.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
class DatetimeFormat extends Eloquent
|
||||
{
|
||||
public $timestamps = false;
|
||||
protected $softDelete = false;
|
||||
}
|
78
app/Models/EntityModel.php
Normal file
78
app/Models/EntityModel.php
Normal file
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
class EntityModel extends Eloquent
|
||||
{
|
||||
protected $softDelete = true;
|
||||
public $timestamps = true;
|
||||
|
||||
protected $hidden = ['id'];
|
||||
|
||||
public static function createNew($parent = false)
|
||||
{
|
||||
$className = get_called_class();
|
||||
$entity = new $className();
|
||||
|
||||
if ($parent) {
|
||||
$entity->user_id = $parent instanceof User ? $parent->id : $parent->user_id;
|
||||
$entity->account_id = $parent->account_id;
|
||||
} elseif (Auth::check()) {
|
||||
$entity->user_id = Auth::user()->id;
|
||||
$entity->account_id = Auth::user()->account_id;
|
||||
} else {
|
||||
Utils::fatalError();
|
||||
}
|
||||
|
||||
$lastEntity = $className::withTrashed()->scope(false, $entity->account_id)->orderBy('public_id', 'DESC')->first();
|
||||
|
||||
if ($lastEntity) {
|
||||
$entity->public_id = $lastEntity->public_id + 1;
|
||||
} else {
|
||||
$entity->public_id = 1;
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
public static function getPrivateId($publicId)
|
||||
{
|
||||
$className = get_called_class();
|
||||
|
||||
return $className::scope($publicId)->pluck('id');
|
||||
}
|
||||
|
||||
public function getActivityKey()
|
||||
{
|
||||
return $this->getEntityType().':'.$this->public_id.':'.$this->getName();
|
||||
}
|
||||
|
||||
/*
|
||||
public function getEntityType()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getNmae()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
*/
|
||||
|
||||
public function scopeScope($query, $publicId = false, $accountId = false)
|
||||
{
|
||||
if (!$accountId) {
|
||||
$accountId = Auth::user()->account_id;
|
||||
}
|
||||
|
||||
$query->whereAccountId($accountId);
|
||||
|
||||
if ($publicId) {
|
||||
if (is_array($publicId)) {
|
||||
$query->whereIn('public_id', $publicId);
|
||||
} else {
|
||||
$query->wherePublicId($publicId);
|
||||
}
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
7
app/Models/Frequency.php
Normal file
7
app/Models/Frequency.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
class Frequency extends Eloquent
|
||||
{
|
||||
public $timestamps = false;
|
||||
protected $softDelete = false;
|
||||
}
|
52
app/Models/Gateway.php
Normal file
52
app/Models/Gateway.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
class Gateway extends Eloquent
|
||||
{
|
||||
public $timestamps = true;
|
||||
protected $softDelete = false;
|
||||
|
||||
public function paymentlibrary()
|
||||
{
|
||||
return $this->belongsTo('PaymentLibrary', 'payment_library_id');
|
||||
}
|
||||
|
||||
public function getLogoUrl()
|
||||
{
|
||||
return '/images/gateways/logo_'.$this->provider.'.png';
|
||||
}
|
||||
|
||||
public function getHelp()
|
||||
{
|
||||
$link = '';
|
||||
|
||||
if ($this->id == GATEWAY_AUTHORIZE_NET || $this->id == GATEWAY_AUTHORIZE_NET_SIM) {
|
||||
$link = 'http://reseller.authorize.net/application/?id=5560364';
|
||||
} elseif ($this->id == GATEWAY_PAYPAL_EXPRESS) {
|
||||
$link = 'https://www.paypal.com/us/cgi-bin/webscr?cmd=_login-api-run';
|
||||
} elseif ($this->id == GATEWAY_TWO_CHECKOUT) {
|
||||
$link = 'https://www.2checkout.com/referral?r=2c37ac2298';
|
||||
}
|
||||
|
||||
$key = 'texts.gateway_help_'.$this->id;
|
||||
$str = trans($key, ['link' => "<a href='$link' target='_blank'>Click here</a>"]);
|
||||
|
||||
return $key != $str ? $str : '';
|
||||
}
|
||||
|
||||
public function getFields()
|
||||
{
|
||||
$paymentLibrary = $this->paymentlibrary;
|
||||
|
||||
if ($paymentLibrary->id == PAYMENT_LIBRARY_OMNIPAY) {
|
||||
$fields = Omnipay::create($this->provider)->getDefaultParameters();
|
||||
} else {
|
||||
$fields = Payment_Utility::load('config', 'drivers/'.strtolower($this->provider));
|
||||
}
|
||||
|
||||
if ($fields == null) {
|
||||
$fields = array();
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
}
|
7
app/Models/Industry.php
Normal file
7
app/Models/Industry.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
class Industry extends Eloquent
|
||||
{
|
||||
public $timestamps = false;
|
||||
protected $softDelete = false;
|
||||
}
|
29
app/Models/Invitation.php
Normal file
29
app/Models/Invitation.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
class Invitation extends EntityModel
|
||||
{
|
||||
public function invoice()
|
||||
{
|
||||
return $this->belongsTo('Invoice');
|
||||
}
|
||||
|
||||
public function contact()
|
||||
{
|
||||
return $this->belongsTo('Contact')->withTrashed();
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo('User')->withTrashed();
|
||||
}
|
||||
|
||||
public function account()
|
||||
{
|
||||
return $this->belongsTo('Account');
|
||||
}
|
||||
|
||||
public function getLink()
|
||||
{
|
||||
return SITE_URL.'/view/'.$this->invitation_key;
|
||||
}
|
||||
}
|
230
app/Models/Invoice.php
Normal file
230
app/Models/Invoice.php
Normal file
@ -0,0 +1,230 @@
|
||||
<?php
|
||||
|
||||
class Invoice extends EntityModel
|
||||
{
|
||||
public function account()
|
||||
{
|
||||
return $this->belongsTo('Account');
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo('User');
|
||||
}
|
||||
|
||||
public function client()
|
||||
{
|
||||
return $this->belongsTo('Client')->withTrashed();
|
||||
}
|
||||
|
||||
public function invoice_items()
|
||||
{
|
||||
return $this->hasMany('InvoiceItem')->orderBy('id');
|
||||
}
|
||||
|
||||
public function invoice_status()
|
||||
{
|
||||
return $this->belongsTo('InvoiceStatus');
|
||||
}
|
||||
|
||||
public function invoice_design()
|
||||
{
|
||||
return $this->belongsTo('InvoiceDesign');
|
||||
}
|
||||
|
||||
public function invitations()
|
||||
{
|
||||
return $this->hasMany('Invitation')->orderBy('invitations.contact_id');
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return $this->invoice_number;
|
||||
}
|
||||
|
||||
public function getLink()
|
||||
{
|
||||
return link_to('invoices/'.$this->public_id, $this->invoice_number);
|
||||
}
|
||||
|
||||
public function getEntityType()
|
||||
{
|
||||
return $this->is_quote ? ENTITY_QUOTE : ENTITY_INVOICE;
|
||||
}
|
||||
|
||||
public function isSent()
|
||||
{
|
||||
return $this->invoice_status_id >= INVOICE_STATUS_SENT;
|
||||
}
|
||||
|
||||
public function isViewed()
|
||||
{
|
||||
return $this->invoice_status_id >= INVOICE_STATUS_VIEWED;
|
||||
}
|
||||
|
||||
public function isPaid()
|
||||
{
|
||||
return $this->invoice_status_id >= INVOICE_STATUS_PAID;
|
||||
}
|
||||
|
||||
public function hidePrivateFields()
|
||||
{
|
||||
$this->setVisible([
|
||||
'invoice_number',
|
||||
'discount',
|
||||
'is_amount_discount',
|
||||
'po_number',
|
||||
'invoice_date',
|
||||
'due_date',
|
||||
'terms',
|
||||
'invoice_footer',
|
||||
'public_notes',
|
||||
'amount',
|
||||
'balance',
|
||||
'invoice_items',
|
||||
'client',
|
||||
'tax_name',
|
||||
'tax_rate',
|
||||
'account',
|
||||
'invoice_design',
|
||||
'invoice_design_id',
|
||||
'is_pro',
|
||||
'is_quote',
|
||||
'custom_value1',
|
||||
'custom_value2',
|
||||
'custom_taxes1',
|
||||
'custom_taxes2', ]);
|
||||
|
||||
$this->client->setVisible([
|
||||
'name',
|
||||
'id_number',
|
||||
'vat_number',
|
||||
'address1',
|
||||
'address2',
|
||||
'city',
|
||||
'state',
|
||||
'postal_code',
|
||||
'work_phone',
|
||||
'payment_terms',
|
||||
'contacts',
|
||||
'country',
|
||||
'currency_id',
|
||||
'custom_value1',
|
||||
'custom_value2', ]);
|
||||
|
||||
$this->account->setVisible([
|
||||
'name',
|
||||
'id_number',
|
||||
'vat_number',
|
||||
'address1',
|
||||
'address2',
|
||||
'city',
|
||||
'state',
|
||||
'postal_code',
|
||||
'work_phone',
|
||||
'work_email',
|
||||
'country',
|
||||
'currency_id',
|
||||
'custom_label1',
|
||||
'custom_value1',
|
||||
'custom_label2',
|
||||
'custom_value2',
|
||||
'custom_client_label1',
|
||||
'custom_client_label2',
|
||||
'primary_color',
|
||||
'secondary_color',
|
||||
'hide_quantity',
|
||||
'hide_paid_to_date',
|
||||
'custom_invoice_label1',
|
||||
'custom_invoice_label2', ]);
|
||||
|
||||
foreach ($this->invoice_items as $invoiceItem) {
|
||||
$invoiceItem->setVisible([
|
||||
'product_key',
|
||||
'notes',
|
||||
'cost',
|
||||
'qty',
|
||||
'tax_name',
|
||||
'tax_rate', ]);
|
||||
}
|
||||
|
||||
foreach ($this->client->contacts as $contact) {
|
||||
$contact->setVisible([
|
||||
'first_name',
|
||||
'last_name',
|
||||
'email',
|
||||
'phone', ]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function shouldSendToday()
|
||||
{
|
||||
if (!$this->start_date || strtotime($this->start_date) > strtotime('now')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->end_date && strtotime($this->end_date) < strtotime('now')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$dayOfWeekToday = date('w');
|
||||
$dayOfWeekStart = date('w', strtotime($this->start_date));
|
||||
|
||||
$dayOfMonthToday = date('j');
|
||||
$dayOfMonthStart = date('j', strtotime($this->start_date));
|
||||
|
||||
if (!$this->last_sent_date) {
|
||||
return true;
|
||||
} else {
|
||||
$date1 = new DateTime($this->last_sent_date);
|
||||
$date2 = new DateTime();
|
||||
$diff = $date2->diff($date1);
|
||||
$daysSinceLastSent = $diff->format("%a");
|
||||
$monthsSinceLastSent = ($diff->format('%y') * 12) + $diff->format('%m');
|
||||
|
||||
if ($daysSinceLastSent == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
switch ($this->frequency_id) {
|
||||
case FREQUENCY_WEEKLY:
|
||||
return $daysSinceLastSent >= 7;
|
||||
case FREQUENCY_TWO_WEEKS:
|
||||
return $daysSinceLastSent >= 14;
|
||||
case FREQUENCY_FOUR_WEEKS:
|
||||
return $daysSinceLastSent >= 28;
|
||||
case FREQUENCY_MONTHLY:
|
||||
return $monthsSinceLastSent >= 1;
|
||||
case FREQUENCY_THREE_MONTHS:
|
||||
return $monthsSinceLastSent >= 3;
|
||||
case FREQUENCY_SIX_MONTHS:
|
||||
return $monthsSinceLastSent >= 6;
|
||||
case FREQUENCY_ANNUALLY:
|
||||
return $monthsSinceLastSent >= 12;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Invoice::created(function ($invoice) {
|
||||
$invoice->account->incrementCounter($invoice->invoice_number, $invoice->is_quote, $invoice->recurring_invoice_id);
|
||||
Activity::createInvoice($invoice);
|
||||
});
|
||||
|
||||
Invoice::updating(function ($invoice) {
|
||||
Activity::updateInvoice($invoice);
|
||||
});
|
||||
|
||||
Invoice::deleting(function ($invoice) {
|
||||
Activity::archiveInvoice($invoice);
|
||||
});
|
||||
|
||||
Invoice::restoring(function ($invoice) {
|
||||
Activity::restoreInvoice($invoice);
|
||||
});
|
7
app/Models/InvoiceDesign.php
Normal file
7
app/Models/InvoiceDesign.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
class InvoiceDesign extends Eloquent
|
||||
{
|
||||
public $timestamps = false;
|
||||
protected $softDelete = false;
|
||||
}
|
14
app/Models/InvoiceItem.php
Normal file
14
app/Models/InvoiceItem.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
class InvoiceItem extends EntityModel
|
||||
{
|
||||
public function invoice()
|
||||
{
|
||||
return $this->belongsTo('Invoice');
|
||||
}
|
||||
|
||||
public function product()
|
||||
{
|
||||
return $this->belongsTo('Product');
|
||||
}
|
||||
}
|
7
app/Models/InvoiceStatus.php
Normal file
7
app/Models/InvoiceStatus.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
class InvoiceStatus extends Eloquent
|
||||
{
|
||||
public $timestamps = false;
|
||||
protected $softDelete = false;
|
||||
}
|
7
app/Models/Language.php
Normal file
7
app/Models/Language.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
class Language extends Eloquent
|
||||
{
|
||||
public $timestamps = false;
|
||||
protected $softDelete = false;
|
||||
}
|
7
app/Models/License.php
Normal file
7
app/Models/License.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
class License extends Eloquent
|
||||
{
|
||||
public $timestamps = true;
|
||||
protected $softDelete = true;
|
||||
}
|
60
app/Models/Payment.php
Normal file
60
app/Models/Payment.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
class Payment extends EntityModel
|
||||
{
|
||||
public function invoice()
|
||||
{
|
||||
return $this->belongsTo('Invoice')->withTrashed();
|
||||
}
|
||||
|
||||
public function invitation()
|
||||
{
|
||||
return $this->belongsTo('Invitation');
|
||||
}
|
||||
|
||||
public function client()
|
||||
{
|
||||
return $this->belongsTo('Client')->withTrashed();
|
||||
}
|
||||
|
||||
public function account()
|
||||
{
|
||||
return $this->belongsTo('Account');
|
||||
}
|
||||
|
||||
public function contact()
|
||||
{
|
||||
return $this->belongsTo('Contact');
|
||||
}
|
||||
|
||||
public function getAmount()
|
||||
{
|
||||
return Utils::formatMoney($this->amount, $this->client->currency_id);
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return trim("payment {$this->transaction_reference}");
|
||||
}
|
||||
|
||||
public function getEntityType()
|
||||
{
|
||||
return ENTITY_PAYMENT;
|
||||
}
|
||||
}
|
||||
|
||||
Payment::created(function ($payment) {
|
||||
Activity::createPayment($payment);
|
||||
});
|
||||
|
||||
Payment::updating(function ($payment) {
|
||||
Activity::updatePayment($payment);
|
||||
});
|
||||
|
||||
Payment::deleting(function ($payment) {
|
||||
Activity::archivePayment($payment);
|
||||
});
|
||||
|
||||
Payment::restoring(function ($payment) {
|
||||
Activity::restorePayment($payment);
|
||||
});
|
12
app/Models/PaymentLibrary.php
Normal file
12
app/Models/PaymentLibrary.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
class PaymentLibrary extends Eloquent
|
||||
{
|
||||
protected $table = 'payment_libraries';
|
||||
public $timestamps = true;
|
||||
|
||||
public function gateways()
|
||||
{
|
||||
return $this->hasMany('Gateway', 'payment_library_id');
|
||||
}
|
||||
}
|
7
app/Models/PaymentTerm.php
Normal file
7
app/Models/PaymentTerm.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
class PaymentTerm extends Eloquent
|
||||
{
|
||||
public $timestamps = false;
|
||||
protected $softDelete = false;
|
||||
}
|
7
app/Models/PaymentType.php
Normal file
7
app/Models/PaymentType.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
class PaymentType extends Eloquent
|
||||
{
|
||||
public $timestamps = false;
|
||||
protected $softDelete = false;
|
||||
}
|
9
app/Models/Product.php
Normal file
9
app/Models/Product.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
class Product extends EntityModel
|
||||
{
|
||||
public static function findProductByKey($key)
|
||||
{
|
||||
return Product::scope()->where('product_key', '=', $key)->first();
|
||||
}
|
||||
}
|
45
app/Models/Project.php
Normal file
45
app/Models/Project.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
class Project extends Eloquent
|
||||
{
|
||||
public $timestamps = true;
|
||||
protected $softDelete = true;
|
||||
|
||||
public function account()
|
||||
{
|
||||
return $this->belongsTo('Account');
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo('User');
|
||||
}
|
||||
|
||||
public function client()
|
||||
{
|
||||
return $this->belongsTo('Client');
|
||||
}
|
||||
|
||||
public function codes()
|
||||
{
|
||||
return $this->hasMany('ProjectCode');
|
||||
}
|
||||
|
||||
public static function createNew($parent = false)
|
||||
{
|
||||
$className = get_called_class();
|
||||
$entity = new $className();
|
||||
|
||||
if ($parent) {
|
||||
$entity->user_id = $parent instanceof User ? $parent->id : $parent->user_id;
|
||||
$entity->account_id = $parent->account_id;
|
||||
} elseif (Auth::check()) {
|
||||
$entity->user_id = Auth::user()->id;
|
||||
$entity->account_id = Auth::user()->account_id;
|
||||
} else {
|
||||
Utils::fatalError();
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
}
|
45
app/Models/ProjectCode.php
Normal file
45
app/Models/ProjectCode.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
class ProjectCode extends Eloquent
|
||||
{
|
||||
public $timestamps = true;
|
||||
protected $softDelete = true;
|
||||
|
||||
public function account()
|
||||
{
|
||||
return $this->belongsTo('Account');
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo('User');
|
||||
}
|
||||
|
||||
public function project()
|
||||
{
|
||||
return $this->belongsTo('Project');
|
||||
}
|
||||
|
||||
public function events()
|
||||
{
|
||||
return $this->hasMany('TimesheetEvent');
|
||||
}
|
||||
|
||||
public static function createNew($parent = false)
|
||||
{
|
||||
$className = get_called_class();
|
||||
$entity = new $className();
|
||||
|
||||
if ($parent) {
|
||||
$entity->user_id = $parent instanceof User ? $parent->id : $parent->user_id;
|
||||
$entity->account_id = $parent->account_id;
|
||||
} elseif (Auth::check()) {
|
||||
$entity->user_id = Auth::user()->id;
|
||||
$entity->account_id = Auth::user()->account_id;
|
||||
} else {
|
||||
Utils::fatalError();
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
}
|
7
app/Models/Size.php
Normal file
7
app/Models/Size.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
class Size extends Eloquent
|
||||
{
|
||||
public $timestamps = false;
|
||||
protected $softDelete = false;
|
||||
}
|
7
app/Models/Subscription.php
Normal file
7
app/Models/Subscription.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
class Subscription extends Eloquent
|
||||
{
|
||||
public $timestamps = true;
|
||||
protected $softDelete = true;
|
||||
}
|
5
app/Models/TaxRate.php
Normal file
5
app/Models/TaxRate.php
Normal file
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
class TaxRate extends EntityModel
|
||||
{
|
||||
}
|
7
app/Models/Theme.php
Normal file
7
app/Models/Theme.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
class Theme extends Eloquent
|
||||
{
|
||||
public $timestamps = false;
|
||||
protected $softDelete = false;
|
||||
}
|
22
app/Models/Timesheet.php
Normal file
22
app/Models/Timesheet.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
class Timesheet extends Eloquent
|
||||
{
|
||||
public $timestamps = true;
|
||||
protected $softDelete = true;
|
||||
|
||||
public function account()
|
||||
{
|
||||
return $this->belongsTo('Account');
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo('User');
|
||||
}
|
||||
|
||||
public function timesheet_events()
|
||||
{
|
||||
return $this->hasMany('TimeSheetEvent');
|
||||
}
|
||||
}
|
122
app/Models/TimesheetEvent.php
Normal file
122
app/Models/TimesheetEvent.php
Normal file
@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
class TimesheetEvent extends Eloquent
|
||||
{
|
||||
public $timestamps = true;
|
||||
protected $softDelete = true;
|
||||
|
||||
/* protected $dates = array('org_updated_at');
|
||||
|
||||
public function getDates() {
|
||||
return array('created_at', 'updated_at', 'deleted_at');
|
||||
} */
|
||||
|
||||
/* public function setOrgUpdatedAtAttribute($value)
|
||||
{
|
||||
var_dump($value);
|
||||
$this->attributes['org_updated_at'] = $value->getTimestamp();
|
||||
}*/
|
||||
|
||||
public function account()
|
||||
{
|
||||
return $this->belongsTo('Account');
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo('User');
|
||||
}
|
||||
|
||||
public function source()
|
||||
{
|
||||
return $this->belongsTo('TimesheetEventSource');
|
||||
}
|
||||
|
||||
public function timesheet()
|
||||
{
|
||||
return $this->belongsTo('Timesheet');
|
||||
}
|
||||
|
||||
public function project()
|
||||
{
|
||||
return $this->belongsTo('Project');
|
||||
}
|
||||
|
||||
public function project_code()
|
||||
{
|
||||
return $this->belongsTo('ProjectCode');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TimesheetEvent
|
||||
*/
|
||||
public static function createNew($parent = false)
|
||||
{
|
||||
$className = get_called_class();
|
||||
$entity = new $className();
|
||||
|
||||
if ($parent) {
|
||||
$entity->user_id = $parent instanceof User ? $parent->id : $parent->user_id;
|
||||
$entity->account_id = $parent->account_id;
|
||||
} elseif (Auth::check()) {
|
||||
$entity->user_id = Auth::user()->id;
|
||||
$entity->account_id = Auth::user()->account_id;
|
||||
} else {
|
||||
Utils::fatalError();
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
public function toChangesArray(TimesheetEvent $other)
|
||||
{
|
||||
$attributes_old = parent::toArray();
|
||||
$attributes_new = $other->toArray();
|
||||
|
||||
$skip_keys = ['id' => 1, 'created_at' => 1, 'updated_at' => 1, 'deleted_at' => 1, 'org_data' => 1, 'update_data' => 1];
|
||||
$zeroisempty_keys = ['discount' => 1];
|
||||
|
||||
$result = [];
|
||||
// Find all the values that where changed or deleted
|
||||
foreach ($attributes_old as $key => $value) {
|
||||
// Skip null values, keys we don't care about and 0 value keys that means they are not used
|
||||
if (empty($value) || isset($skip_keys[$key]) || (isset($zeroisempty_keys[$key]) && $value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Compare values if it exists in the new array
|
||||
if (isset($attributes_new[$key]) || array_key_exists($key, $attributes_new)) {
|
||||
if ($value instanceof \DateTime && $attributes_new[$key] instanceof \DateTime) {
|
||||
if ($value != $attributes_new[$key]) {
|
||||
$result[$key] = $attributes_new[$key]->format("Y-m-d H:i:s");
|
||||
}
|
||||
} elseif ($value instanceof \DateTime && is_string($attributes_new[$key])) {
|
||||
if ($value->format("Y-m-d H:i:s") != $attributes_new[$key]) {
|
||||
$result[$key] = $attributes_new[$key];
|
||||
}
|
||||
} elseif (is_string($value) && $attributes_new[$key] instanceof \DateTime) {
|
||||
if ($attributes_new[$key]->format("Y-m-d H:i:s") != $value) {
|
||||
$result[$key] = $attributes_new[$key]->format("Y-m-d H:i:s");
|
||||
}
|
||||
} elseif ($value != $attributes_new[$key]) {
|
||||
$result[$key] = $attributes_new[$key];
|
||||
}
|
||||
} else {
|
||||
$result[$key] = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Find all the values that where deleted
|
||||
foreach ($attributes_new as $key => $value) {
|
||||
if (isset($skip_keys[$key])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($attributes_old[$key])) {
|
||||
$result[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
40
app/Models/TimesheetEventSource.php
Normal file
40
app/Models/TimesheetEventSource.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
class TimesheetEventSource extends Eloquent
|
||||
{
|
||||
public $timestamps = true;
|
||||
protected $softDelete = true;
|
||||
|
||||
public function account()
|
||||
{
|
||||
return $this->belongsTo('Account');
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo('User');
|
||||
}
|
||||
|
||||
public function events()
|
||||
{
|
||||
return $this->hasMany('TimesheetEvent');
|
||||
}
|
||||
|
||||
public static function createNew($parent = false)
|
||||
{
|
||||
$className = get_called_class();
|
||||
$entity = new $className();
|
||||
|
||||
if ($parent) {
|
||||
$entity->user_id = $parent instanceof User ? $parent->id : $parent->user_id;
|
||||
$entity->account_id = $parent->account_id;
|
||||
} elseif (Auth::check()) {
|
||||
$entity->user_id = Auth::user()->id;
|
||||
$entity->account_id = Auth::user()->account_id;
|
||||
} else {
|
||||
Utils::fatalError();
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
}
|
7
app/Models/Timezone.php
Normal file
7
app/Models/Timezone.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
class Timezone extends Eloquent
|
||||
{
|
||||
public $timestamps = false;
|
||||
protected $softDelete = false;
|
||||
}
|
170
app/Models/User.php
Normal file
170
app/Models/User.php
Normal file
@ -0,0 +1,170 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Auth\UserInterface;
|
||||
use Illuminate\Auth\Reminders\RemindableInterface;
|
||||
use Zizaco\Confide\ConfideUser;
|
||||
|
||||
class User extends ConfideUser implements UserInterface, RemindableInterface
|
||||
{
|
||||
protected $softDelete = true;
|
||||
|
||||
public static $rules = array(
|
||||
/*
|
||||
'username' => 'required|unique:users',
|
||||
'password' => 'required|between:6,32|confirmed',
|
||||
'password_confirmation' => 'between:6,32',
|
||||
*/
|
||||
);
|
||||
|
||||
protected $updateRules = array(
|
||||
/*
|
||||
'email' => 'required|unique:users',
|
||||
'username' => 'required|unique:users',
|
||||
*/
|
||||
);
|
||||
|
||||
/**
|
||||
* The database table used by the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'users';
|
||||
|
||||
public function account()
|
||||
{
|
||||
return $this->belongsTo('Account');
|
||||
}
|
||||
|
||||
public function theme()
|
||||
{
|
||||
return $this->belongsTo('Theme');
|
||||
}
|
||||
|
||||
public function getPersonType()
|
||||
{
|
||||
return PERSON_USER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the unique identifier for the user.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getAuthIdentifier()
|
||||
{
|
||||
return $this->getKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the password for the user.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getAuthPassword()
|
||||
{
|
||||
return $this->password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the e-mail address where password reminders are sent.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getReminderEmail()
|
||||
{
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
public function isPro()
|
||||
{
|
||||
return $this->account->isPro();
|
||||
}
|
||||
|
||||
public function isDemo()
|
||||
{
|
||||
return $this->account->id == Utils::getDemoAccountId();
|
||||
}
|
||||
|
||||
public function maxInvoiceDesignId()
|
||||
{
|
||||
return $this->isPro() ? 10 : COUNT_FREE_DESIGNS;
|
||||
}
|
||||
|
||||
public function getDisplayName()
|
||||
{
|
||||
if ($this->getFullName()) {
|
||||
return $this->getFullName();
|
||||
} elseif ($this->email) {
|
||||
return $this->email;
|
||||
} else {
|
||||
return 'Guest';
|
||||
}
|
||||
}
|
||||
|
||||
public function getFullName()
|
||||
{
|
||||
if ($this->first_name || $this->last_name) {
|
||||
return $this->first_name.' '.$this->last_name;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
public function showGreyBackground()
|
||||
{
|
||||
return !$this->theme_id || in_array($this->theme_id, [2, 3, 5, 6, 7, 8, 10, 11, 12]);
|
||||
}
|
||||
|
||||
public function getRequestsCount()
|
||||
{
|
||||
return Session::get(SESSION_COUNTER, 0);
|
||||
}
|
||||
|
||||
public function getPopOverText()
|
||||
{
|
||||
if (!Utils::isNinja() || !Auth::check() || Session::has('error')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$count = self::getRequestsCount();
|
||||
|
||||
if ($count == 1 || $count % 5 == 0) {
|
||||
if (!Utils::isRegistered()) {
|
||||
return trans('texts.sign_up_to_save');
|
||||
} elseif (!Auth::user()->account->name) {
|
||||
return trans('texts.set_name');
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function afterSave($success = true, $forced = false)
|
||||
{
|
||||
if ($this->email) {
|
||||
return parent::afterSave($success = true, $forced = false);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public function getMaxNumClients()
|
||||
{
|
||||
return $this->isPro() ? MAX_NUM_CLIENTS_PRO : MAX_NUM_CLIENTS;
|
||||
}
|
||||
|
||||
public function getRememberToken()
|
||||
{
|
||||
return $this->remember_token;
|
||||
}
|
||||
|
||||
public function setRememberToken($value)
|
||||
{
|
||||
$this->remember_token = $value;
|
||||
}
|
||||
|
||||
public function getRememberTokenName()
|
||||
{
|
||||
return 'remember_token';
|
||||
}
|
||||
}
|
303
app/Providers/RouteServiceProvider.php.bak
Normal file
303
app/Providers/RouteServiceProvider.php.bak
Normal file
@ -0,0 +1,303 @@
|
||||
<?php namespace App\Providers;
|
||||
use App;
|
||||
use Illuminate\Routing\Router;
|
||||
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
|
||||
|
||||
class RouteServiceProvider extends ServiceProvider {
|
||||
|
||||
/**
|
||||
* This namespace is applied to the controller routes in your routes file.
|
||||
*
|
||||
* In addition, it is set as the URL generator's root namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'App\Http\Controllers';
|
||||
|
||||
/**
|
||||
* Define your route model bindings, pattern filters, etc.
|
||||
*
|
||||
* @param \Illuminate\Routing\Router $router
|
||||
* @return void
|
||||
*/
|
||||
public function boot(Router $router)
|
||||
{
|
||||
parent::boot($router);
|
||||
|
||||
|
||||
|
||||
|
||||
App::before(function($request)
|
||||
{
|
||||
// Ensure all request are over HTTPS in production
|
||||
if (App::environment() == ENV_PRODUCTION)
|
||||
{
|
||||
if (!Request::secure())
|
||||
{
|
||||
return Redirect::secure(Request::getRequestUri());
|
||||
}
|
||||
}
|
||||
|
||||
// If the database doens't yet exist we'll skip the rest
|
||||
if (!Utils::isNinja() && !Utils::isDatabaseSetup())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
if (!Utils::startsWith($_SERVER['REQUEST_URI'], '/news_feed') && !Session::has('news_feed_id')) {
|
||||
$data = false;
|
||||
if (Utils::isNinja()) {
|
||||
$data = Utils::getNewsFeedResponse();
|
||||
} else {
|
||||
$file = @file_get_contents(NINJA_APP_URL . '/news_feed/' . Utils::getUserType() . '/' . NINJA_VERSION);
|
||||
$data = @json_decode($file);
|
||||
}
|
||||
if ($data) {
|
||||
if ($data->version != NINJA_VERSION) {
|
||||
$params = [
|
||||
'user_version' => NINJA_VERSION,
|
||||
'latest_version'=> $data->version,
|
||||
'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));
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Session::put('news_feed_id', true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we're requesting to change the account's language
|
||||
if (Input::has('lang'))
|
||||
{
|
||||
$locale = Input::get('lang');
|
||||
App::setLocale($locale);
|
||||
Session::set(SESSION_LOCALE, $locale);
|
||||
|
||||
if (Auth::check())
|
||||
{
|
||||
if ($language = Language::whereLocale($locale)->first())
|
||||
{
|
||||
$account = Auth::user()->account;
|
||||
$account->language_id = $language->id;
|
||||
$account->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (Auth::check())
|
||||
{
|
||||
$locale = Session::get(SESSION_LOCALE, DEFAULT_LOCALE);
|
||||
App::setLocale($locale);
|
||||
}
|
||||
|
||||
// Make sure the account/user localization settings are in the session
|
||||
if (Auth::check() && !Session::has(SESSION_TIMEZONE))
|
||||
{
|
||||
Event::fire('user.refresh');
|
||||
}
|
||||
|
||||
// Check if the user is claiming a license (ie, additional invoices, white label, etc.)
|
||||
$claimingLicense = Utils::startsWith($_SERVER['REQUEST_URI'], '/claim_license');
|
||||
if (!$claimingLicense && Input::has('license_key') && Input::has('product_id'))
|
||||
{
|
||||
$licenseKey = Input::get('license_key');
|
||||
$productId = Input::get('product_id');
|
||||
|
||||
$data = trim(file_get_contents((Utils::isNinjaDev() ? 'http://ninja.dev' : NINJA_APP_URL) . "/claim_license?license_key={$licenseKey}&product_id={$productId}"));
|
||||
|
||||
if ($productId == PRODUCT_INVOICE_DESIGNS)
|
||||
{
|
||||
if ($data = json_decode($data))
|
||||
{
|
||||
foreach ($data as $item)
|
||||
{
|
||||
$design = new InvoiceDesign();
|
||||
$design->id = $item->id;
|
||||
$design->name = $item->name;
|
||||
$design->javascript = $item->javascript;
|
||||
$design->save();
|
||||
}
|
||||
|
||||
if (!Utils::isNinjaProd()) {
|
||||
Cache::forget('invoice_designs_cache_' . Auth::user()->maxInvoiceDesignId());
|
||||
}
|
||||
|
||||
Session::flash('message', trans('texts.bought_designs'));
|
||||
}
|
||||
}
|
||||
else if ($productId == PRODUCT_WHITE_LABEL)
|
||||
{
|
||||
if ($data == 'valid')
|
||||
{
|
||||
$account = Auth::user()->account;
|
||||
$account->pro_plan_paid = NINJA_DATE;
|
||||
$account->save();
|
||||
|
||||
Session::flash('message', trans('texts.bought_white_label'));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Authentication Filters
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following filters are used to verify that the user of the current
|
||||
| session is logged into this application. The "basic" filter easily
|
||||
| integrates HTTP Basic authentication for quick, simple checking.
|
||||
|
|
||||
*/
|
||||
|
||||
$router->filter('auth', function()
|
||||
{
|
||||
if (Auth::guest())
|
||||
{
|
||||
if (Utils::isNinja() || Account::count() == 0)
|
||||
{
|
||||
return Redirect::guest('/');
|
||||
}
|
||||
else
|
||||
{
|
||||
return Redirect::guest('/login');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$router->filter('auth.basic', function()
|
||||
{
|
||||
return Auth::basic();
|
||||
});
|
||||
|
||||
$router->filter('api.access', function()
|
||||
{
|
||||
$headers = Utils::getApiHeaders();
|
||||
|
||||
// check for a valid token
|
||||
$token = AccountToken::where('token', '=', Request::header('X-Ninja-Token'))->first(['id', 'user_id']);
|
||||
|
||||
if ($token) {
|
||||
Auth::loginUsingId($token->user_id);
|
||||
Session::set('token_id', $token->id);
|
||||
} else {
|
||||
sleep(3);
|
||||
return Response::make('Invalid token', 403, $headers);
|
||||
}
|
||||
|
||||
if (!Utils::isNinja()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!Utils::isPro()) {
|
||||
return Response::make('API requires pro plan', 403, $headers);
|
||||
} else {
|
||||
$accountId = Auth::user()->account->id;
|
||||
|
||||
// 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);
|
||||
$last_api_diff = time() - $last_api_request;
|
||||
|
||||
if (is_null($hour_throttle)) {
|
||||
$new_hour_throttle = 0;
|
||||
} else {
|
||||
$new_hour_throttle = $hour_throttle - $last_api_diff;
|
||||
$new_hour_throttle = $new_hour_throttle < 0 ? 0 : $new_hour_throttle;
|
||||
$new_hour_throttle += $hour / $hour_limit;
|
||||
$hour_hits_remaining = floor(( $hour - $new_hour_throttle ) * $hour_limit / $hour);
|
||||
$hour_hits_remaining = $hour_hits_remaining >= 0 ? $hour_hits_remaining : 0;
|
||||
}
|
||||
|
||||
if ($new_hour_throttle > $hour) {
|
||||
$wait = ceil($new_hour_throttle - $hour);
|
||||
sleep(1);
|
||||
return Response::make("Please wait {$wait} second(s)", 403, $headers);
|
||||
}
|
||||
|
||||
Cache::put("hour_throttle:{$accountId}", $new_hour_throttle, 10);
|
||||
Cache::put("last_api_request:{$accountId}", time(), 10);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Guest Filter
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The "guest" filter is the counterpart of the authentication filters as
|
||||
| it simply checks that the current user is not logged in. A redirect
|
||||
| response will be issued if they are, which you may freely change.
|
||||
|
|
||||
*/
|
||||
|
||||
$router->filter('guest', function()
|
||||
{
|
||||
if (Auth::check()) return Redirect::to('/');
|
||||
});
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| CSRF Protection Filter
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The CSRF filter is responsible for protecting your application against
|
||||
| cross-site request forgery attacks. If this special token in a user
|
||||
| session does not match the one given in this request, we'll bail.
|
||||
|
|
||||
*/
|
||||
|
||||
$router->filter('csrf', function()
|
||||
{
|
||||
if ($_SERVER['REQUEST_URI'] != '/signup/register')
|
||||
{
|
||||
$token = Request::ajax() ? Request::header('X-CSRF-Token') : Input::get('_token');
|
||||
|
||||
if (Session::token() != $token)
|
||||
{
|
||||
Session::flash('warning', trans('texts.session_expired'));
|
||||
|
||||
return Redirect::to('/');
|
||||
//throw new Illuminate\Session\TokenMismatchException;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the routes for the application.
|
||||
*
|
||||
* @param \Illuminate\Routing\Router $router
|
||||
* @return void
|
||||
*/
|
||||
public function map(Router $router)
|
||||
{
|
||||
$router->group(['namespace' => $this->namespace], function($router)
|
||||
{
|
||||
require app_path('Http/routes.php');
|
||||
});
|
||||
}
|
||||
|
||||
}
|
695
app/includes/parsecsv.lib.php
Normal file
695
app/includes/parsecsv.lib.php
Normal file
@ -0,0 +1,695 @@
|
||||
<?php
|
||||
|
||||
class parseCSV {
|
||||
|
||||
/*
|
||||
|
||||
Class: parseCSV v0.3.2
|
||||
http://code.google.com/p/parsecsv-for-php/
|
||||
|
||||
|
||||
Fully conforms to the specifications lined out on wikipedia:
|
||||
- http://en.wikipedia.org/wiki/Comma-separated_values
|
||||
|
||||
Based on the concept of Ming Hong Ng's CsvFileParser class:
|
||||
- http://minghong.blogspot.com/2006/07/csv-parser-for-php.html
|
||||
|
||||
|
||||
|
||||
Copyright (c) 2007 Jim Myhrberg (jim@zydev.info).
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
|
||||
Code Examples
|
||||
----------------
|
||||
# general usage
|
||||
$csv = new parseCSV('data.csv');
|
||||
print_r($csv->data);
|
||||
----------------
|
||||
# tab delimited, and encoding conversion
|
||||
$csv = new parseCSV();
|
||||
$csv->encoding('UTF-16', 'UTF-8');
|
||||
$csv->delimiter = "\t";
|
||||
$csv->parse('data.tsv');
|
||||
print_r($csv->data);
|
||||
----------------
|
||||
# auto-detect delimiter character
|
||||
$csv = new parseCSV();
|
||||
$csv->auto('data.csv');
|
||||
print_r($csv->data);
|
||||
----------------
|
||||
# modify data in a csv file
|
||||
$csv = new parseCSV();
|
||||
$csv->sort_by = 'id';
|
||||
$csv->parse('data.csv');
|
||||
# "4" is the value of the "id" column of the CSV row
|
||||
$csv->data[4] = array('firstname' => 'John', 'lastname' => 'Doe', 'email' => 'john@doe.com');
|
||||
$csv->save();
|
||||
----------------
|
||||
# add row/entry to end of CSV file
|
||||
# - only recommended when you know the extact sctructure of the file
|
||||
$csv = new parseCSV();
|
||||
$csv->save('data.csv', array('1986', 'Home', 'Nowhere', ''), true);
|
||||
----------------
|
||||
# convert 2D array to csv data and send headers
|
||||
# to browser to treat output as a file and download it
|
||||
$csv = new parseCSV();
|
||||
$csv->output (true, 'movies.csv', $array);
|
||||
----------------
|
||||
|
||||
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Configuration
|
||||
* - set these options with $object->var_name = 'value';
|
||||
*/
|
||||
|
||||
# use first line/entry as field names
|
||||
var $heading = true;
|
||||
|
||||
# override field names
|
||||
var $fields = array();
|
||||
|
||||
# sort entries by this field
|
||||
var $sort_by = null;
|
||||
var $sort_reverse = false;
|
||||
|
||||
# delimiter (comma) and enclosure (double quote)
|
||||
var $delimiter = ',';
|
||||
var $enclosure = '"';
|
||||
|
||||
# basic SQL-like conditions for row matching
|
||||
var $conditions = null;
|
||||
|
||||
# number of rows to ignore from beginning of data
|
||||
var $offset = null;
|
||||
|
||||
# limits the number of returned rows to specified amount
|
||||
var $limit = null;
|
||||
|
||||
# number of rows to analyze when attempting to auto-detect delimiter
|
||||
var $auto_depth = 15;
|
||||
|
||||
# characters to ignore when attempting to auto-detect delimiter
|
||||
var $auto_non_chars = "a-zA-Z0-9\n\r";
|
||||
|
||||
# preferred delimiter characters, only used when all filtering method
|
||||
# returns multiple possible delimiters (happens very rarely)
|
||||
var $auto_preferred = ",;\t.:|";
|
||||
|
||||
# character encoding options
|
||||
var $convert_encoding = false;
|
||||
var $input_encoding = 'ISO-8859-1';
|
||||
var $output_encoding = 'ISO-8859-1';
|
||||
|
||||
# used by unparse(), save(), and output() functions
|
||||
var $linefeed = "\r\n";
|
||||
|
||||
# only used by output() function
|
||||
var $output_delimiter = ',';
|
||||
var $output_filename = 'data.csv';
|
||||
|
||||
|
||||
/**
|
||||
* Internal variables
|
||||
*/
|
||||
|
||||
# current file
|
||||
var $file;
|
||||
|
||||
# loaded file contents
|
||||
var $file_data;
|
||||
|
||||
# array of field values in data parsed
|
||||
var $titles = array();
|
||||
|
||||
# two dimentional array of CSV data
|
||||
var $data = array();
|
||||
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param input CSV file or string
|
||||
* @return nothing
|
||||
*/
|
||||
function parseCSV ($input = null, $offset = null, $limit = null, $conditions = null) {
|
||||
if ( $offset !== null ) $this->offset = $offset;
|
||||
if ( $limit !== null ) $this->limit = $limit;
|
||||
if ( count($conditions) > 0 ) $this->conditions = $conditions;
|
||||
if ( !empty($input) ) $this->parse($input);
|
||||
}
|
||||
|
||||
|
||||
// ==============================================
|
||||
// ----- [ Main Functions ] ---------------------
|
||||
// ==============================================
|
||||
|
||||
/**
|
||||
* Parse CSV file or string
|
||||
* @param input CSV file or string
|
||||
* @return nothing
|
||||
*/
|
||||
function parse ($input = null, $offset = null, $limit = null, $conditions = null) {
|
||||
if ( !empty($input) ) {
|
||||
if ( $offset !== null ) $this->offset = $offset;
|
||||
if ( $limit !== null ) $this->limit = $limit;
|
||||
if ( count($conditions) > 0 ) $this->conditions = $conditions;
|
||||
if ( is_readable($input) ) {
|
||||
$this->data = $this->parse_file($input);
|
||||
} else {
|
||||
$this->file_data = &$input;
|
||||
$this->data = $this->parse_string();
|
||||
}
|
||||
if ( $this->data === false ) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save changes, or new file and/or data
|
||||
* @param file file to save to
|
||||
* @param data 2D array with data
|
||||
* @param append append current data to end of target CSV if exists
|
||||
* @param fields field names
|
||||
* @return true or false
|
||||
*/
|
||||
function save ($file = null, $data = array(), $append = false, $fields = array()) {
|
||||
if ( empty($file) ) $file = &$this->file;
|
||||
$mode = ( $append ) ? 'at' : 'wt' ;
|
||||
$is_php = ( preg_match('/\.php$/i', $file) ) ? true : false ;
|
||||
return $this->_wfile($file, $this->unparse($data, $fields, $append, $is_php), $mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate CSV based string for output
|
||||
* @param output if true, prints headers and strings to browser
|
||||
* @param filename filename sent to browser in headers if output is true
|
||||
* @param data 2D array with data
|
||||
* @param fields field names
|
||||
* @param delimiter delimiter used to separate data
|
||||
* @return CSV data using delimiter of choice, or default
|
||||
*/
|
||||
function output ($output = true, $filename = null, $data = array(), $fields = array(), $delimiter = null) {
|
||||
if ( empty($filename) ) $filename = $this->output_filename;
|
||||
if ( $delimiter === null ) $delimiter = $this->output_delimiter;
|
||||
$data = $this->unparse($data, $fields, null, null, $delimiter);
|
||||
if ( $output ) {
|
||||
header('Content-type: application/csv');
|
||||
header('Content-Disposition: inline; filename="'.$filename.'"');
|
||||
echo $data;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert character encoding
|
||||
* @param input input character encoding, uses default if left blank
|
||||
* @param output output character encoding, uses default if left blank
|
||||
* @return nothing
|
||||
*/
|
||||
function encoding ($input = null, $output = null) {
|
||||
$this->convert_encoding = true;
|
||||
if ( $input !== null ) $this->input_encoding = $input;
|
||||
if ( $output !== null ) $this->output_encoding = $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-Detect Delimiter: Find delimiter by analyzing a specific number of
|
||||
* rows to determine most probable delimiter character
|
||||
* @param file local CSV file
|
||||
* @param parse true/false parse file directly
|
||||
* @param search_depth number of rows to analyze
|
||||
* @param preferred preferred delimiter characters
|
||||
* @param enclosure enclosure character, default is double quote (").
|
||||
* @return delimiter character
|
||||
*/
|
||||
function auto ($file = null, $parse = true, $search_depth = null, $preferred = null, $enclosure = null) {
|
||||
|
||||
if ( $file === null ) $file = $this->file;
|
||||
if ( empty($search_depth) ) $search_depth = $this->auto_depth;
|
||||
if ( $enclosure === null ) $enclosure = $this->enclosure;
|
||||
|
||||
if ( $preferred === null ) $preferred = $this->auto_preferred;
|
||||
|
||||
if ( empty($this->file_data) ) {
|
||||
if ( $this->_check_data($file) ) {
|
||||
$data = &$this->file_data;
|
||||
} else return false;
|
||||
} else {
|
||||
$data = &$this->file_data;
|
||||
}
|
||||
|
||||
$chars = array();
|
||||
$strlen = strlen($data);
|
||||
$enclosed = false;
|
||||
$n = 1;
|
||||
$to_end = true;
|
||||
|
||||
// walk specific depth finding posssible delimiter characters
|
||||
for ( $i=0; $i < $strlen; $i++ ) {
|
||||
$ch = $data{$i};
|
||||
$nch = ( isset($data{$i+1}) ) ? $data{$i+1} : false ;
|
||||
$pch = ( isset($data{$i-1}) ) ? $data{$i-1} : false ;
|
||||
|
||||
// open and closing quotes
|
||||
if ( $ch == $enclosure && (!$enclosed || $nch != $enclosure) ) {
|
||||
$enclosed = ( $enclosed ) ? false : true ;
|
||||
|
||||
// inline quotes
|
||||
} elseif ( $ch == $enclosure && $enclosed ) {
|
||||
$i++;
|
||||
|
||||
// end of row
|
||||
} elseif ( ($ch == "\n" && $pch != "\r" || $ch == "\r") && !$enclosed ) {
|
||||
if ( $n >= $search_depth ) {
|
||||
$strlen = 0;
|
||||
$to_end = false;
|
||||
} else {
|
||||
$n++;
|
||||
}
|
||||
|
||||
// count character
|
||||
} elseif (!$enclosed) {
|
||||
if ( !preg_match('/['.preg_quote($this->auto_non_chars, '/').']/i', $ch) ) {
|
||||
if ( !isset($chars[$ch][$n]) ) {
|
||||
$chars[$ch][$n] = 1;
|
||||
} else {
|
||||
$chars[$ch][$n]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// filtering
|
||||
$depth = ( $to_end ) ? $n-1 : $n ;
|
||||
$filtered = array();
|
||||
foreach( $chars as $char => $value ) {
|
||||
if ( $match = $this->_check_count($char, $value, $depth, $preferred) ) {
|
||||
$filtered[$match] = $char;
|
||||
}
|
||||
}
|
||||
|
||||
// capture most probable delimiter
|
||||
ksort($filtered);
|
||||
$delimiter = reset($filtered);
|
||||
$this->delimiter = $delimiter;
|
||||
|
||||
// parse data
|
||||
if ( $parse ) $this->data = $this->parse_string();
|
||||
|
||||
return $delimiter;
|
||||
|
||||
}
|
||||
|
||||
|
||||
// ==============================================
|
||||
// ----- [ Core Functions ] ---------------------
|
||||
// ==============================================
|
||||
|
||||
/**
|
||||
* Read file to string and call parse_string()
|
||||
* @param file local CSV file
|
||||
* @return 2D array with CSV data, or false on failure
|
||||
*/
|
||||
function parse_file ($file = null) {
|
||||
if ( $file === null ) $file = $this->file;
|
||||
if ( empty($this->file_data) ) $this->load_data($file);
|
||||
return ( !empty($this->file_data) ) ? $this->parse_string() : false ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse CSV strings to arrays
|
||||
* @param data CSV string
|
||||
* @return 2D array with CSV data, or false on failure
|
||||
*/
|
||||
function parse_string ($data = null) {
|
||||
if ( empty($data) ) {
|
||||
if ( $this->_check_data() ) {
|
||||
$data = &$this->file_data;
|
||||
} else return false;
|
||||
}
|
||||
|
||||
$rows = array();
|
||||
$row = array();
|
||||
$row_count = 0;
|
||||
$current = '';
|
||||
$head = ( !empty($this->fields) ) ? $this->fields : array() ;
|
||||
$col = 0;
|
||||
$enclosed = false;
|
||||
$was_enclosed = false;
|
||||
$strlen = strlen($data);
|
||||
|
||||
// walk through each character
|
||||
for ( $i=0; $i < $strlen; $i++ ) {
|
||||
$ch = $data{$i};
|
||||
$nch = ( isset($data{$i+1}) ) ? $data{$i+1} : false ;
|
||||
$pch = ( isset($data{$i-1}) ) ? $data{$i-1} : false ;
|
||||
|
||||
// open and closing quotes
|
||||
if ( $ch == $this->enclosure && (!$enclosed || $nch != $this->enclosure) ) {
|
||||
$enclosed = ( $enclosed ) ? false : true ;
|
||||
if ( $enclosed ) $was_enclosed = true;
|
||||
|
||||
// inline quotes
|
||||
} elseif ( $ch == $this->enclosure && $enclosed ) {
|
||||
$current .= $ch;
|
||||
$i++;
|
||||
|
||||
// end of field/row
|
||||
} elseif ( ($ch == $this->delimiter || ($ch == "\n" && $pch != "\r") || $ch == "\r") && !$enclosed ) {
|
||||
if ( !$was_enclosed ) $current = trim($current);
|
||||
$key = ( !empty($head[$col]) ) ? $head[$col] : $col ;
|
||||
$row[$key] = $current;
|
||||
$current = '';
|
||||
$col++;
|
||||
|
||||
// end of row
|
||||
if ( $ch == "\n" || $ch == "\r" ) {
|
||||
if ( $this->_validate_offset($row_count) && $this->_validate_row_conditions($row, $this->conditions) ) {
|
||||
if ( $this->heading && empty($head) ) {
|
||||
$head = $row;
|
||||
} elseif ( empty($this->fields) || (!empty($this->fields) && (($this->heading && $row_count > 0) || !$this->heading)) ) {
|
||||
if ( !empty($this->sort_by) && !empty($row[$this->sort_by]) ) {
|
||||
if ( isset($rows[$row[$this->sort_by]]) ) {
|
||||
$rows[$row[$this->sort_by].'_0'] = &$rows[$row[$this->sort_by]];
|
||||
unset($rows[$row[$this->sort_by]]);
|
||||
for ( $sn=1; isset($rows[$row[$this->sort_by].'_'.$sn]); $sn++ ) {}
|
||||
$rows[$row[$this->sort_by].'_'.$sn] = $row;
|
||||
} else $rows[$row[$this->sort_by]] = $row;
|
||||
} else $rows[] = $row;
|
||||
}
|
||||
}
|
||||
$row = array();
|
||||
$col = 0;
|
||||
$row_count++;
|
||||
if ( $this->sort_by === null && $this->limit !== null && count($rows) == $this->limit ) {
|
||||
$i = $strlen;
|
||||
}
|
||||
}
|
||||
|
||||
// append character to current field
|
||||
} else {
|
||||
$current .= $ch;
|
||||
}
|
||||
}
|
||||
$this->titles = $head;
|
||||
if ( !empty($this->sort_by) ) {
|
||||
( $this->sort_reverse ) ? krsort($rows) : ksort($rows) ;
|
||||
if ( $this->offset !== null || $this->limit !== null ) {
|
||||
$rows = array_slice($rows, ($this->offset === null ? 0 : $this->offset) , $this->limit, true);
|
||||
}
|
||||
}
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create CSV data from array
|
||||
* @param data 2D array with data
|
||||
* @param fields field names
|
||||
* @param append if true, field names will not be output
|
||||
* @param is_php if a php die() call should be put on the first
|
||||
* line of the file, this is later ignored when read.
|
||||
* @param delimiter field delimiter to use
|
||||
* @return CSV data (text string)
|
||||
*/
|
||||
function unparse ( $data = array(), $fields = array(), $append = false , $is_php = false, $delimiter = null) {
|
||||
if ( !is_array($data) || empty($data) ) $data = &$this->data;
|
||||
if ( !is_array($fields) || empty($fields) ) $fields = &$this->titles;
|
||||
if ( $delimiter === null ) $delimiter = $this->delimiter;
|
||||
|
||||
$string = ( $is_php ) ? "<?php header('Status: 403'); die(' '); ?>".$this->linefeed : '' ;
|
||||
$entry = array();
|
||||
|
||||
// create heading
|
||||
if ( $this->heading && !$append ) {
|
||||
foreach( $fields as $key => $value ) {
|
||||
$entry[] = $this->_enclose_value($value);
|
||||
}
|
||||
$string .= implode($delimiter, $entry).$this->linefeed;
|
||||
$entry = array();
|
||||
}
|
||||
|
||||
// create data
|
||||
foreach( $data as $key => $row ) {
|
||||
foreach( $row as $field => $value ) {
|
||||
$entry[] = $this->_enclose_value($value);
|
||||
}
|
||||
$string .= implode($delimiter, $entry).$this->linefeed;
|
||||
$entry = array();
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load local file or string
|
||||
* @param input local CSV file
|
||||
* @return true or false
|
||||
*/
|
||||
function load_data ($input = null) {
|
||||
$data = null;
|
||||
$file = null;
|
||||
if ( $input === null ) {
|
||||
$file = $this->file;
|
||||
} elseif ( file_exists($input) ) {
|
||||
$file = $input;
|
||||
} else {
|
||||
$data = $input;
|
||||
}
|
||||
if ( !empty($data) || $data = $this->_rfile($file) ) {
|
||||
if ( $this->file != $file ) $this->file = $file;
|
||||
if ( preg_match('/\.php$/i', $file) && preg_match('/<\?.*?\?>(.*)/ims', $data, $strip) ) {
|
||||
$data = ltrim($strip[1]);
|
||||
}
|
||||
if ( $this->convert_encoding ) $data = iconv($this->input_encoding, $this->output_encoding, $data);
|
||||
if ( substr($data, -1) != "\n" ) $data .= "\n";
|
||||
$this->file_data = &$data;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// ==============================================
|
||||
// ----- [ Internal Functions ] -----------------
|
||||
// ==============================================
|
||||
|
||||
/**
|
||||
* Validate a row against specified conditions
|
||||
* @param row array with values from a row
|
||||
* @param conditions specified conditions that the row must match
|
||||
* @return true of false
|
||||
*/
|
||||
function _validate_row_conditions ($row = array(), $conditions = null) {
|
||||
if ( !empty($row) ) {
|
||||
if ( !empty($conditions) ) {
|
||||
$conditions = (strpos($conditions, ' OR ') !== false) ? explode(' OR ', $conditions) : array($conditions) ;
|
||||
$or = '';
|
||||
foreach( $conditions as $key => $value ) {
|
||||
if ( strpos($value, ' AND ') !== false ) {
|
||||
$value = explode(' AND ', $value);
|
||||
$and = '';
|
||||
foreach( $value as $k => $v ) {
|
||||
$and .= $this->_validate_row_condition($row, $v);
|
||||
}
|
||||
$or .= (strpos($and, '0') !== false) ? '0' : '1' ;
|
||||
} else {
|
||||
$or .= $this->_validate_row_condition($row, $value);
|
||||
}
|
||||
}
|
||||
return (strpos($or, '1') !== false) ? true : false ;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a row against a single condition
|
||||
* @param row array with values from a row
|
||||
* @param condition specified condition that the row must match
|
||||
* @return true of false
|
||||
*/
|
||||
function _validate_row_condition ($row, $condition) {
|
||||
$operators = array(
|
||||
'=', 'equals', 'is',
|
||||
'!=', 'is not',
|
||||
'<', 'is less than',
|
||||
'>', 'is greater than',
|
||||
'<=', 'is less than or equals',
|
||||
'>=', 'is greater than or equals',
|
||||
'contains',
|
||||
'does not contain',
|
||||
);
|
||||
$operators_regex = array();
|
||||
foreach( $operators as $value ) {
|
||||
$operators_regex[] = preg_quote($value, '/');
|
||||
}
|
||||
$operators_regex = implode('|', $operators_regex);
|
||||
if ( preg_match('/^(.+) ('.$operators_regex.') (.+)$/i', trim($condition), $capture) ) {
|
||||
$field = $capture[1];
|
||||
$op = $capture[2];
|
||||
$value = $capture[3];
|
||||
if ( preg_match('/^([\'\"]{1})(.*)([\'\"]{1})$/i', $value, $capture) ) {
|
||||
if ( $capture[1] == $capture[3] ) {
|
||||
$value = $capture[2];
|
||||
$value = str_replace("\\n", "\n", $value);
|
||||
$value = str_replace("\\r", "\r", $value);
|
||||
$value = str_replace("\\t", "\t", $value);
|
||||
$value = stripslashes($value);
|
||||
}
|
||||
}
|
||||
if ( array_key_exists($field, $row) ) {
|
||||
if ( ($op == '=' || $op == 'equals' || $op == 'is') && $row[$field] == $value ) {
|
||||
return '1';
|
||||
} elseif ( ($op == '!=' || $op == 'is not') && $row[$field] != $value ) {
|
||||
return '1';
|
||||
} elseif ( ($op == '<' || $op == 'is less than' ) && $row[$field] < $value ) {
|
||||
return '1';
|
||||
} elseif ( ($op == '>' || $op == 'is greater than') && $row[$field] > $value ) {
|
||||
return '1';
|
||||
} elseif ( ($op == '<=' || $op == 'is less than or equals' ) && $row[$field] <= $value ) {
|
||||
return '1';
|
||||
} elseif ( ($op == '>=' || $op == 'is greater than or equals') && $row[$field] >= $value ) {
|
||||
return '1';
|
||||
} elseif ( $op == 'contains' && preg_match('/'.preg_quote($value, '/').'/i', $row[$field]) ) {
|
||||
return '1';
|
||||
} elseif ( $op == 'does not contain' && !preg_match('/'.preg_quote($value, '/').'/i', $row[$field]) ) {
|
||||
return '1';
|
||||
} else {
|
||||
return '0';
|
||||
}
|
||||
}
|
||||
}
|
||||
return '1';
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if the row is within the offset or not if sorting is disabled
|
||||
* @param current_row the current row number being processed
|
||||
* @return true of false
|
||||
*/
|
||||
function _validate_offset ($current_row) {
|
||||
if ( $this->sort_by === null && $this->offset !== null && $current_row < $this->offset ) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enclose values if needed
|
||||
* - only used by unparse()
|
||||
* @param value string to process
|
||||
* @return Processed value
|
||||
*/
|
||||
function _enclose_value ($value = null) {
|
||||
if ( $value !== null && $value != '' ) {
|
||||
$delimiter = preg_quote($this->delimiter, '/');
|
||||
$enclosure = preg_quote($this->enclosure, '/');
|
||||
if ( preg_match("/".$delimiter."|".$enclosure."|\n|\r/i", $value) || ($value{0} == ' ' || substr($value, -1) == ' ') ) {
|
||||
$value = str_replace($this->enclosure, $this->enclosure.$this->enclosure, $value);
|
||||
$value = $this->enclosure.$value.$this->enclosure;
|
||||
}
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check file data
|
||||
* @param file local filename
|
||||
* @return true or false
|
||||
*/
|
||||
function _check_data ($file = null) {
|
||||
if ( empty($this->file_data) ) {
|
||||
if ( $file === null ) $file = $this->file;
|
||||
return $this->load_data($file);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if passed info might be delimiter
|
||||
* - only used by find_delimiter()
|
||||
* @return special string used for delimiter selection, or false
|
||||
*/
|
||||
function _check_count ($char, $array, $depth, $preferred) {
|
||||
if ( $depth == count($array) ) {
|
||||
$first = null;
|
||||
$equal = null;
|
||||
$almost = false;
|
||||
foreach( $array as $key => $value ) {
|
||||
if ( $first == null ) {
|
||||
$first = $value;
|
||||
} elseif ( $value == $first && $equal !== false) {
|
||||
$equal = true;
|
||||
} elseif ( $value == $first+1 && $equal !== false ) {
|
||||
$equal = true;
|
||||
$almost = true;
|
||||
} else {
|
||||
$equal = false;
|
||||
}
|
||||
}
|
||||
if ( $equal ) {
|
||||
$match = ( $almost ) ? 2 : 1 ;
|
||||
$pref = strpos($preferred, $char);
|
||||
$pref = ( $pref !== false ) ? str_pad($pref, 3, '0', STR_PAD_LEFT) : '999' ;
|
||||
return $pref.$match.'.'.(99999 - str_pad($first, 5, '0', STR_PAD_LEFT));
|
||||
} else return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read local file
|
||||
* @param file local filename
|
||||
* @return Data from file, or false on failure
|
||||
*/
|
||||
function _rfile ($file = null) {
|
||||
if ( is_readable($file) ) {
|
||||
if ( !($fh = fopen($file, 'r')) ) return false;
|
||||
$data = fread($fh, filesize($file));
|
||||
fclose($fh);
|
||||
return $data;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write to local file
|
||||
* @param file local filename
|
||||
* @param string data to write to file
|
||||
* @param mode fopen() mode
|
||||
* @param lock flock() mode
|
||||
* @return true or false
|
||||
*/
|
||||
function _wfile ($file, $string = '', $mode = 'wb', $lock = 2) {
|
||||
if ( $fp = fopen($file, $mode) ) {
|
||||
flock($fp, $lock);
|
||||
$re = fwrite($fp, $string);
|
||||
$re2 = fclose($fp);
|
||||
if ( $re != false && $re2 != false ) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
604
app/libraries/Utils.php
Normal file
604
app/libraries/Utils.php
Normal file
@ -0,0 +1,604 @@
|
||||
<?php namespace App\libraries;
|
||||
|
||||
class Utils
|
||||
{
|
||||
public static function isRegistered()
|
||||
{
|
||||
return Auth::check() && Auth::user()->registered;
|
||||
}
|
||||
|
||||
public static function isConfirmed()
|
||||
{
|
||||
return Auth::check() && Auth::user()->confirmed;
|
||||
}
|
||||
|
||||
public static function isDatabaseSetup()
|
||||
{
|
||||
try {
|
||||
if (Schema::hasTable('accounts')) {
|
||||
return true;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static function isProd()
|
||||
{
|
||||
return App::environment() == ENV_PRODUCTION;
|
||||
}
|
||||
|
||||
public static function isNinja()
|
||||
{
|
||||
return self::isNinjaProd() || self::isNinjaDev();
|
||||
}
|
||||
|
||||
public static function isNinjaProd()
|
||||
{
|
||||
return isset($_ENV['NINJA_PROD']) && $_ENV['NINJA_PROD'];
|
||||
}
|
||||
|
||||
public static function isNinjaDev()
|
||||
{
|
||||
return isset($_ENV['NINJA_DEV']) && $_ENV['NINJA_DEV'];
|
||||
}
|
||||
|
||||
public static function isPro()
|
||||
{
|
||||
return Auth::check() && Auth::user()->isPro();
|
||||
}
|
||||
|
||||
public static function getUserType()
|
||||
{
|
||||
if (Utils::isNinja()) {
|
||||
return USER_TYPE_CLOUD_HOST;
|
||||
} else {
|
||||
return USER_TYPE_SELF_HOST;
|
||||
}
|
||||
}
|
||||
|
||||
public static function getDemoAccountId()
|
||||
{
|
||||
return isset($_ENV[DEMO_ACCOUNT_ID]) ? $_ENV[DEMO_ACCOUNT_ID] : false;
|
||||
}
|
||||
|
||||
public static function isDemo()
|
||||
{
|
||||
return Auth::check() && Auth::user()->isDemo();
|
||||
}
|
||||
|
||||
public static function getNewsFeedResponse($userType = false)
|
||||
{
|
||||
if (!$userType) {
|
||||
$userType = Utils::getUserType();
|
||||
}
|
||||
|
||||
$response = new stdClass();
|
||||
$response->message = isset($_ENV["{$userType}_MESSAGE"]) ? $_ENV["{$userType}_MESSAGE"] : '';
|
||||
$response->id = isset($_ENV["{$userType}_ID"]) ? $_ENV["{$userType}_ID"] : '';
|
||||
$response->version = NINJA_VERSION;
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public static function getProLabel($feature)
|
||||
{
|
||||
if (Auth::check()
|
||||
&& !Auth::user()->isPro()
|
||||
&& $feature == ACCOUNT_ADVANCED_SETTINGS) {
|
||||
return ' <sup class="pro-label">PRO</sup>';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
public static function basePath()
|
||||
{
|
||||
return substr($_SERVER['SCRIPT_NAME'], 0, strrpos($_SERVER['SCRIPT_NAME'], '/') + 1);
|
||||
}
|
||||
|
||||
public static function trans($input)
|
||||
{
|
||||
$data = [];
|
||||
|
||||
foreach ($input as $field) {
|
||||
if ($field == "checkbox") {
|
||||
$data[] = $field;
|
||||
} else {
|
||||
$data[] = trans("texts.$field");
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public static function fatalError($message = false, $exception = false)
|
||||
{
|
||||
if (!$message) {
|
||||
$message = "An error occurred, please try again later.";
|
||||
}
|
||||
|
||||
static::logError($message.' '.$exception);
|
||||
|
||||
$data = [
|
||||
'showBreadcrumbs' => false,
|
||||
'hideHeader' => true,
|
||||
];
|
||||
|
||||
return View::make('error', $data)->with('error', $message);
|
||||
}
|
||||
|
||||
public static function getErrorString($exception)
|
||||
{
|
||||
return "{$exception->getFile()} [Line {$exception->getLine()}] => {$exception->getMessage()}";
|
||||
}
|
||||
|
||||
public static function logError($error, $context = 'PHP')
|
||||
{
|
||||
$count = Session::get('error_count', 0);
|
||||
Session::put('error_count', ++$count);
|
||||
if ($count > 100) {
|
||||
return 'logged';
|
||||
}
|
||||
|
||||
$data = [
|
||||
'context' => $context,
|
||||
'user_id' => Auth::check() ? Auth::user()->id : 0,
|
||||
'user_name' => Auth::check() ? Auth::user()->getDisplayName() : '',
|
||||
'url' => Input::get('url', Request::url()),
|
||||
'user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '',
|
||||
'ip' => Request::getClientIp(),
|
||||
'count' => Session::get('error_count', 0),
|
||||
];
|
||||
|
||||
Log::error($error."\n", $data);
|
||||
|
||||
/*
|
||||
Mail::queue('emails.error', ['message'=>$error.' '.json_encode($data)], function($message)
|
||||
{
|
||||
$message->to($email)->subject($subject);
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
public static function parseFloat($value)
|
||||
{
|
||||
$value = preg_replace('/[^0-9\.\-]/', '', $value);
|
||||
|
||||
return floatval($value);
|
||||
}
|
||||
|
||||
public static function formatPhoneNumber($phoneNumber)
|
||||
{
|
||||
$phoneNumber = preg_replace('/[^0-9a-zA-Z]/', '', $phoneNumber);
|
||||
|
||||
if (!$phoneNumber) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (strlen($phoneNumber) > 10) {
|
||||
$countryCode = substr($phoneNumber, 0, strlen($phoneNumber)-10);
|
||||
$areaCode = substr($phoneNumber, -10, 3);
|
||||
$nextThree = substr($phoneNumber, -7, 3);
|
||||
$lastFour = substr($phoneNumber, -4, 4);
|
||||
|
||||
$phoneNumber = '+'.$countryCode.' ('.$areaCode.') '.$nextThree.'-'.$lastFour;
|
||||
} elseif (strlen($phoneNumber) == 10 && in_array(substr($phoneNumber, 0, 3), array(653, 656, 658, 659))) {
|
||||
/**
|
||||
* SG country code are 653, 656, 658, 659
|
||||
* US area code consist of 650, 651 and 657
|
||||
* @see http://en.wikipedia.org/wiki/Telephone_numbers_in_Singapore#Numbering_plan
|
||||
* @see http://www.bennetyee.org/ucsd-pages/area.html
|
||||
*/
|
||||
$countryCode = substr($phoneNumber, 0, 2);
|
||||
$nextFour = substr($phoneNumber, 2, 4);
|
||||
$lastFour = substr($phoneNumber, 6, 4);
|
||||
|
||||
$phoneNumber = '+'.$countryCode.' '.$nextFour.' '.$lastFour;
|
||||
} elseif (strlen($phoneNumber) == 10) {
|
||||
$areaCode = substr($phoneNumber, 0, 3);
|
||||
$nextThree = substr($phoneNumber, 3, 3);
|
||||
$lastFour = substr($phoneNumber, 6, 4);
|
||||
|
||||
$phoneNumber = '('.$areaCode.') '.$nextThree.'-'.$lastFour;
|
||||
} elseif (strlen($phoneNumber) == 7) {
|
||||
$nextThree = substr($phoneNumber, 0, 3);
|
||||
$lastFour = substr($phoneNumber, 3, 4);
|
||||
|
||||
$phoneNumber = $nextThree.'-'.$lastFour;
|
||||
}
|
||||
|
||||
return $phoneNumber;
|
||||
}
|
||||
|
||||
public static function formatMoney($value, $currencyId = false)
|
||||
{
|
||||
if (!$currencyId) {
|
||||
$currencyId = Session::get(SESSION_CURRENCY);
|
||||
}
|
||||
|
||||
$currency = Currency::remember(DEFAULT_QUERY_CACHE)->find($currencyId);
|
||||
|
||||
if (!$currency) {
|
||||
$currency = Currency::remember(DEFAULT_QUERY_CACHE)->find(1);
|
||||
}
|
||||
|
||||
return $currency->symbol.number_format($value, $currency->precision, $currency->decimal_separator, $currency->thousand_separator);
|
||||
}
|
||||
|
||||
public static function pluralize($string, $count)
|
||||
{
|
||||
$field = $count == 1 ? $string : $string.'s';
|
||||
$string = trans("texts.$field", ['count' => $count]);
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
public static function toArray($data)
|
||||
{
|
||||
return json_decode(json_encode((array) $data), true);
|
||||
}
|
||||
|
||||
public static function toSpaceCase($camelStr)
|
||||
{
|
||||
return preg_replace('/([a-z])([A-Z])/s', '$1 $2', $camelStr);
|
||||
}
|
||||
|
||||
public static function timestampToDateTimeString($timestamp)
|
||||
{
|
||||
$timezone = Session::get(SESSION_TIMEZONE, DEFAULT_TIMEZONE);
|
||||
$format = Session::get(SESSION_DATETIME_FORMAT, DEFAULT_DATETIME_FORMAT);
|
||||
|
||||
return Utils::timestampToString($timestamp, $timezone, $format);
|
||||
}
|
||||
|
||||
public static function timestampToDateString($timestamp)
|
||||
{
|
||||
$timezone = Session::get(SESSION_TIMEZONE, DEFAULT_TIMEZONE);
|
||||
$format = Session::get(SESSION_DATE_FORMAT, DEFAULT_DATE_FORMAT);
|
||||
|
||||
return Utils::timestampToString($timestamp, $timezone, $format);
|
||||
}
|
||||
|
||||
public static function dateToString($date)
|
||||
{
|
||||
$dateTime = new DateTime($date);
|
||||
$timestamp = $dateTime->getTimestamp();
|
||||
$format = Session::get(SESSION_DATE_FORMAT, DEFAULT_DATE_FORMAT);
|
||||
|
||||
return Utils::timestampToString($timestamp, false, $format);
|
||||
}
|
||||
|
||||
public static function timestampToString($timestamp, $timezone = false, $format)
|
||||
{
|
||||
if (!$timestamp) {
|
||||
return '';
|
||||
}
|
||||
$date = Carbon::createFromTimeStamp($timestamp);
|
||||
if ($timezone) {
|
||||
$date->tz = $timezone;
|
||||
}
|
||||
if ($date->year < 1900) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $date->format($format);
|
||||
}
|
||||
|
||||
public static function toSqlDate($date, $formatResult = true)
|
||||
{
|
||||
if (!$date) {
|
||||
return;
|
||||
}
|
||||
|
||||
$timezone = Session::get(SESSION_TIMEZONE);
|
||||
$format = Session::get(SESSION_DATE_FORMAT);
|
||||
|
||||
$dateTime = DateTime::createFromFormat($format, $date, new DateTimeZone($timezone));
|
||||
|
||||
return $formatResult ? $dateTime->format('Y-m-d') : $dateTime;
|
||||
}
|
||||
|
||||
public static function fromSqlDate($date, $formatResult = true)
|
||||
{
|
||||
if (!$date || $date == '0000-00-00') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$timezone = Session::get(SESSION_TIMEZONE);
|
||||
$format = Session::get(SESSION_DATE_FORMAT);
|
||||
|
||||
$dateTime = DateTime::createFromFormat('Y-m-d', $date, new DateTimeZone($timezone));
|
||||
|
||||
return $formatResult ? $dateTime->format($format) : $dateTime;
|
||||
}
|
||||
|
||||
public static function today($formatResult = true)
|
||||
{
|
||||
$timezone = Session::get(SESSION_TIMEZONE);
|
||||
$format = Session::get(SESSION_DATE_FORMAT);
|
||||
$date = date_create(null, new DateTimeZone($timezone));
|
||||
|
||||
if ($formatResult) {
|
||||
return $date->format($format);
|
||||
} else {
|
||||
return $date;
|
||||
}
|
||||
}
|
||||
|
||||
public static function trackViewed($name, $type, $url = false)
|
||||
{
|
||||
if (!$url) {
|
||||
$url = Request::url();
|
||||
}
|
||||
|
||||
$viewed = Session::get(RECENTLY_VIEWED);
|
||||
|
||||
if (!$viewed) {
|
||||
$viewed = [];
|
||||
}
|
||||
|
||||
$object = new stdClass();
|
||||
$object->url = $url;
|
||||
$object->name = ucwords($type).': '.$name;
|
||||
|
||||
$data = [];
|
||||
|
||||
for ($i = 0; $i<count($viewed); $i++) {
|
||||
$item = $viewed[$i];
|
||||
|
||||
if ($object->url == $item->url || $object->name == $item->name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
array_unshift($data, $item);
|
||||
}
|
||||
|
||||
array_unshift($data, $object);
|
||||
|
||||
if (count($data) > RECENTLY_VIEWED_LIMIT) {
|
||||
array_pop($data);
|
||||
}
|
||||
|
||||
Session::put(RECENTLY_VIEWED, $data);
|
||||
}
|
||||
|
||||
public static function processVariables($str)
|
||||
{
|
||||
if (!$str) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$variables = ['MONTH', 'QUARTER', 'YEAR'];
|
||||
for ($i = 0; $i<count($variables); $i++) {
|
||||
$variable = $variables[$i];
|
||||
$regExp = '/:'.$variable.'[+-]?[\d]*/';
|
||||
preg_match_all($regExp, $str, $matches);
|
||||
$matches = $matches[0];
|
||||
if (count($matches) == 0) {
|
||||
continue;
|
||||
}
|
||||
foreach ($matches as $match) {
|
||||
$offset = 0;
|
||||
$addArray = explode('+', $match);
|
||||
$minArray = explode('-', $match);
|
||||
if (count($addArray) > 1) {
|
||||
$offset = intval($addArray[1]);
|
||||
} elseif (count($minArray) > 1) {
|
||||
$offset = intval($minArray[1]) * -1;
|
||||
}
|
||||
|
||||
$val = Utils::getDatePart($variable, $offset);
|
||||
$str = str_replace($match, $val, $str);
|
||||
}
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
private static function getDatePart($part, $offset)
|
||||
{
|
||||
$offset = intval($offset);
|
||||
if ($part == 'MONTH') {
|
||||
return Utils::getMonth($offset);
|
||||
} elseif ($part == 'QUARTER') {
|
||||
return Utils::getQuarter($offset);
|
||||
} elseif ($part == 'YEAR') {
|
||||
return Utils::getYear($offset);
|
||||
}
|
||||
}
|
||||
|
||||
private static function getMonth($offset)
|
||||
{
|
||||
$months = [ "January", "February", "March", "April", "May", "June",
|
||||
"July", "August", "September", "October", "November", "December", ];
|
||||
|
||||
$month = intval(date('n')) - 1;
|
||||
|
||||
$month += $offset;
|
||||
$month = $month % 12;
|
||||
|
||||
if ($month < 0) {
|
||||
$month += 12;
|
||||
}
|
||||
|
||||
return $months[$month];
|
||||
}
|
||||
|
||||
private static function getQuarter($offset)
|
||||
{
|
||||
$month = intval(date('n')) - 1;
|
||||
$quarter = floor(($month + 3) / 3);
|
||||
$quarter += $offset;
|
||||
$quarter = $quarter % 4;
|
||||
if ($quarter == 0) {
|
||||
$quarter = 4;
|
||||
}
|
||||
|
||||
return 'Q'.$quarter;
|
||||
}
|
||||
|
||||
private static function getYear($offset)
|
||||
{
|
||||
$year = intval(date('Y'));
|
||||
|
||||
return $year + $offset;
|
||||
}
|
||||
|
||||
public static function getEntityName($entityType)
|
||||
{
|
||||
return ucwords(str_replace('_', ' ', $entityType));
|
||||
}
|
||||
|
||||
public static function getClientDisplayName($model)
|
||||
{
|
||||
if ($model->client_name) {
|
||||
return $model->client_name;
|
||||
} elseif ($model->first_name || $model->last_name) {
|
||||
return $model->first_name.' '.$model->last_name;
|
||||
} else {
|
||||
return $model->email;
|
||||
}
|
||||
}
|
||||
|
||||
public static function encodeActivity($person = null, $action, $entity = null, $otherPerson = null)
|
||||
{
|
||||
$person = $person ? $person->getDisplayName() : '<i>System</i>';
|
||||
$entity = $entity ? '['.$entity->getActivityKey().']' : '';
|
||||
$otherPerson = $otherPerson ? 'to '.$otherPerson->getDisplayName() : '';
|
||||
$token = Session::get('token_id') ? ' ('.trans('texts.token').')' : '';
|
||||
|
||||
return trim("$person $token $action $entity $otherPerson");
|
||||
}
|
||||
|
||||
public static function decodeActivity($message)
|
||||
{
|
||||
$pattern = '/\[([\w]*):([\d]*):(.*)\]/i';
|
||||
preg_match($pattern, $message, $matches);
|
||||
|
||||
if (count($matches) > 0) {
|
||||
$match = $matches[0];
|
||||
$type = $matches[1];
|
||||
$publicId = $matches[2];
|
||||
$name = $matches[3];
|
||||
|
||||
$link = link_to($type.'s/'.$publicId, $name);
|
||||
$message = str_replace($match, "$type $link", $message);
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
public static function generateLicense()
|
||||
{
|
||||
$parts = [];
|
||||
for ($i = 0; $i<5; $i++) {
|
||||
$parts[] = strtoupper(str_random(4));
|
||||
}
|
||||
|
||||
return implode('-', $parts);
|
||||
}
|
||||
|
||||
public static function lookupEventId($eventName)
|
||||
{
|
||||
if ($eventName == 'create_client') {
|
||||
return EVENT_CREATE_CLIENT;
|
||||
} elseif ($eventName == 'create_invoice') {
|
||||
return EVENT_CREATE_INVOICE;
|
||||
} elseif ($eventName == 'create_quote') {
|
||||
return EVENT_CREATE_QUOTE;
|
||||
} elseif ($eventName == 'create_payment') {
|
||||
return EVENT_CREATE_PAYMENT;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static function notifyZapier($subscription, $data)
|
||||
{
|
||||
$curl = curl_init();
|
||||
|
||||
$jsonEncodedData = json_encode($data->toJson());
|
||||
$opts = [
|
||||
CURLOPT_URL => $subscription->target_url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_CUSTOMREQUEST => 'POST',
|
||||
CURLOPT_POST => 1,
|
||||
CURLOPT_POSTFIELDS => $jsonEncodedData,
|
||||
CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Content-Length: '.strlen($jsonEncodedData)],
|
||||
];
|
||||
|
||||
curl_setopt_array($curl, $opts);
|
||||
|
||||
$result = curl_exec($curl);
|
||||
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
||||
|
||||
curl_close($curl);
|
||||
|
||||
if ($status == 410) {
|
||||
$subscription->delete();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static function remapPublicIds(array $data)
|
||||
{
|
||||
$return = [];
|
||||
|
||||
foreach ($data as $key => $val) {
|
||||
if ($key === 'public_id') {
|
||||
$key = 'id';
|
||||
} elseif (strpos($key, '_id')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_array($val)) {
|
||||
$val = Utils::remapPublicIds($val);
|
||||
}
|
||||
|
||||
$return[$key] = $val;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
public static function getApiHeaders($count = 0)
|
||||
{
|
||||
return [
|
||||
'Content-Type' => 'application/json',
|
||||
//'Access-Control-Allow-Origin' => '*',
|
||||
//'Access-Control-Allow-Methods' => 'GET',
|
||||
//'Access-Control-Allow-Headers' => 'Origin, Content-Type, Accept, Authorization, X-Requested-With',
|
||||
//'Access-Control-Allow-Credentials' => 'true',
|
||||
'X-Total-Count' => $count,
|
||||
//'X-Rate-Limit-Limit' - The number of allowed requests in the current period
|
||||
//'X-Rate-Limit-Remaining' - The number of remaining requests in the current period
|
||||
//'X-Rate-Limit-Reset' - The number of seconds left in the current period,
|
||||
];
|
||||
}
|
||||
|
||||
public static function startsWith($haystack, $needle)
|
||||
{
|
||||
return $needle === "" || strpos($haystack, $needle) === 0;
|
||||
}
|
||||
|
||||
public static function endsWith($haystack, $needle)
|
||||
{
|
||||
return $needle === "" || substr($haystack, -strlen($needle)) === $needle;
|
||||
}
|
||||
|
||||
public static function getEntityRowClass($model)
|
||||
{
|
||||
$str = $model->is_deleted || ($model->deleted_at && $model->deleted_at != '0000-00-00') ? 'DISABLED ' : '';
|
||||
|
||||
if ($model->is_deleted) {
|
||||
$str .= 'ENTITY_DELETED ';
|
||||
}
|
||||
|
||||
if ($model->deleted_at && $model->deleted_at != '0000-00-00') {
|
||||
$str .= 'ENTITY_ARCHIVED ';
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
}
|
104
app/ninja/mailers/ContactMailer.php
Normal file
104
app/ninja/mailers/ContactMailer.php
Normal file
@ -0,0 +1,104 @@
|
||||
<?php namespace ninja\mailers;
|
||||
|
||||
use Invoice;
|
||||
use Payment;
|
||||
use Contact;
|
||||
use Invitation;
|
||||
use Activity;
|
||||
use Utils;
|
||||
|
||||
class ContactMailer extends Mailer
|
||||
{
|
||||
public function sendInvoice(Invoice $invoice)
|
||||
{
|
||||
$invoice->load('invitations', 'client', 'account');
|
||||
$entityType = $invoice->getEntityType();
|
||||
|
||||
$view = 'invoice';
|
||||
$subject = trans("texts.{$entityType}_subject", ['invoice' => $invoice->invoice_number, 'account' => $invoice->account->getDisplayName()]);
|
||||
$accountName = $invoice->account->getDisplayName();
|
||||
$emailTemplate = $invoice->account->getEmailTemplate($entityType);
|
||||
$invoiceAmount = Utils::formatMoney($invoice->amount, $invoice->client->currency_id);
|
||||
|
||||
foreach ($invoice->invitations as $invitation) {
|
||||
if (!$invitation->user || !$invitation->user->email) {
|
||||
return false;
|
||||
}
|
||||
if (!$invitation->contact || !$invitation->contact->email) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$invitation->sent_date = \Carbon::now()->toDateTimeString();
|
||||
$invitation->save();
|
||||
|
||||
$variables = [
|
||||
'$footer' => $invoice->account->getEmailFooter(),
|
||||
'$link' => $invitation->getLink(),
|
||||
'$client' => $invoice->client->getDisplayName(),
|
||||
'$account' => $accountName,
|
||||
'$contact' => $invitation->contact->getDisplayName(),
|
||||
'$amount' => $invoiceAmount
|
||||
];
|
||||
|
||||
$data['body'] = str_replace(array_keys($variables), array_values($variables), $emailTemplate);
|
||||
$data['link'] = $invitation->getLink();
|
||||
$data['entityType'] = $entityType;
|
||||
|
||||
$fromEmail = $invitation->user->email;
|
||||
$this->sendTo($invitation->contact->email, $fromEmail, $accountName, $subject, $view, $data);
|
||||
|
||||
Activity::emailInvoice($invitation);
|
||||
}
|
||||
|
||||
if (!$invoice->isSent()) {
|
||||
$invoice->invoice_status_id = INVOICE_STATUS_SENT;
|
||||
$invoice->save();
|
||||
}
|
||||
|
||||
\Event::fire('invoice.sent', $invoice);
|
||||
}
|
||||
|
||||
public function sendPaymentConfirmation(Payment $payment)
|
||||
{
|
||||
$invoice = $payment->invoice;
|
||||
$view = 'payment_confirmation';
|
||||
$subject = trans('texts.payment_subject', ['invoice' => $invoice->invoice_number]);
|
||||
$accountName = $payment->account->getDisplayName();
|
||||
$emailTemplate = $invoice->account->getEmailTemplate(ENTITY_PAYMENT);
|
||||
|
||||
$variables = [
|
||||
'$footer' => $payment->account->getEmailFooter(),
|
||||
'$client' => $payment->client->getDisplayName(),
|
||||
'$account' => $accountName,
|
||||
'$amount' => Utils::formatMoney($payment->amount, $payment->client->currency_id)
|
||||
];
|
||||
|
||||
$data = ['body' => str_replace(array_keys($variables), array_values($variables), $emailTemplate)];
|
||||
|
||||
$user = $payment->invitation->user;
|
||||
$this->sendTo($payment->contact->email, $user->email, $accountName, $subject, $view, $data);
|
||||
}
|
||||
|
||||
public function sendLicensePaymentConfirmation($name, $email, $amount, $license, $productId)
|
||||
{
|
||||
$view = 'license_confirmation';
|
||||
$subject = trans('texts.payment_subject');
|
||||
|
||||
if ($productId == PRODUCT_ONE_CLICK_INSTALL) {
|
||||
$license = "Softaculous install license: $license";
|
||||
} elseif ($productId == PRODUCT_INVOICE_DESIGNS) {
|
||||
$license = "Invoice designs license: $license";
|
||||
} elseif ($productId == PRODUCT_WHITE_LABEL) {
|
||||
$license = "White label license: $license";
|
||||
}
|
||||
|
||||
$data = [
|
||||
'account' => trans('texts.email_from'),
|
||||
'client' => $name,
|
||||
'amount' => Utils::formatMoney($amount, 1),
|
||||
'license' => $license
|
||||
];
|
||||
|
||||
$this->sendTo($email, CONTACT_EMAIL, CONTACT_NAME, $subject, $view, $data);
|
||||
}
|
||||
}
|
27
app/ninja/mailers/Mailer.php
Normal file
27
app/ninja/mailers/Mailer.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php namespace ninja\mailers;
|
||||
|
||||
use Mail;
|
||||
use Utils;
|
||||
|
||||
class Mailer
|
||||
{
|
||||
public function sendTo($toEmail, $fromEmail, $fromName, $subject, $view, $data = [])
|
||||
{
|
||||
$views = [
|
||||
'emails.'.$view.'_html',
|
||||
'emails.'.$view.'_text',
|
||||
];
|
||||
|
||||
Mail::send($views, $data, function ($message) use ($toEmail, $fromEmail, $fromName, $subject) {
|
||||
$replyEmail = $fromEmail;
|
||||
|
||||
// http://stackoverflow.com/questions/2421234/gmail-appearing-to-ignore-reply-to
|
||||
if (Utils::isNinja() && $toEmail != CONTACT_EMAIL) {
|
||||
$fromEmail = NINJA_FROM_EMAIL;
|
||||
}
|
||||
|
||||
//$message->setEncoder(\Swift_Encoding::get8BitEncoding());
|
||||
$message->to($toEmail)->from($fromEmail, $fromName)->replyTo($replyEmail, $fromName)->subject($subject);
|
||||
});
|
||||
}
|
||||
}
|
62
app/ninja/mailers/UserMailer.php
Normal file
62
app/ninja/mailers/UserMailer.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php namespace ninja\mailers;
|
||||
|
||||
use Invoice;
|
||||
use Payment;
|
||||
use User;
|
||||
use Utils;
|
||||
|
||||
class UserMailer extends Mailer
|
||||
{
|
||||
public function sendConfirmation(User $user, User $invitor = null)
|
||||
{
|
||||
if (!$user->email) {
|
||||
return;
|
||||
}
|
||||
|
||||
$view = 'confirm';
|
||||
$subject = trans('texts.confirmation_subject');
|
||||
|
||||
$data = [
|
||||
'user' => $user,
|
||||
'invitationMessage' => $invitor ? trans('texts.invitation_message', ['invitor' => $invitor->getDisplayName()]) : '',
|
||||
];
|
||||
|
||||
if ($invitor) {
|
||||
$fromEmail = $invitor->email;
|
||||
$fromName = $invitor->getDisplayName();
|
||||
} else {
|
||||
$fromEmail = CONTACT_EMAIL;
|
||||
$fromName = CONTACT_NAME;
|
||||
}
|
||||
|
||||
$this->sendTo($user->email, $fromEmail, $fromName, $subject, $view, $data);
|
||||
}
|
||||
|
||||
public function sendNotification(User $user, Invoice $invoice, $notificationType, Payment $payment = null)
|
||||
{
|
||||
if (!$user->email) {
|
||||
return;
|
||||
}
|
||||
|
||||
$view = 'invoice_'.$notificationType;
|
||||
$entityType = $invoice->getEntityType();
|
||||
|
||||
$data = [
|
||||
'entityType' => $entityType,
|
||||
'clientName' => $invoice->client->getDisplayName(),
|
||||
'accountName' => $invoice->account->getDisplayName(),
|
||||
'userName' => $user->getDisplayName(),
|
||||
'invoiceAmount' => Utils::formatMoney($invoice->amount, $invoice->client->currency_id),
|
||||
'invoiceNumber' => $invoice->invoice_number,
|
||||
'invoiceLink' => SITE_URL."/{$entityType}s/{$invoice->public_id}",
|
||||
];
|
||||
|
||||
if ($payment) {
|
||||
$data['paymentAmount'] = Utils::formatMoney($payment->amount, $invoice->client->currency_id);
|
||||
}
|
||||
|
||||
$subject = trans("texts.notification_{$entityType}_{$notificationType}_subject", ['invoice' => $invoice->invoice_number, 'client' => $invoice->client->getDisplayName()]);
|
||||
|
||||
$this->sendTo($user->email, CONTACT_EMAIL, CONTACT_NAME, $subject, $view, $data);
|
||||
}
|
||||
}
|
234
app/ninja/repositories/AccountRepository.php
Normal file
234
app/ninja/repositories/AccountRepository.php
Normal file
@ -0,0 +1,234 @@
|
||||
<?php namespace ninja\repositories;
|
||||
|
||||
use Client;
|
||||
use Contact;
|
||||
use Account;
|
||||
use Request;
|
||||
use Session;
|
||||
use Language;
|
||||
use User;
|
||||
use Auth;
|
||||
use Invitation;
|
||||
use Invoice;
|
||||
use InvoiceItem;
|
||||
use AccountGateway;
|
||||
use Utils;
|
||||
|
||||
class AccountRepository
|
||||
{
|
||||
public function create()
|
||||
{
|
||||
$account = new Account();
|
||||
$account->ip = Request::getClientIp();
|
||||
$account->account_key = str_random(RANDOM_KEY_LENGTH);
|
||||
|
||||
if (Session::has(SESSION_LOCALE)) {
|
||||
$locale = Session::get(SESSION_LOCALE);
|
||||
if ($language = Language::whereLocale($locale)->first()) {
|
||||
$account->language_id = $language->id;
|
||||
}
|
||||
}
|
||||
|
||||
$account->save();
|
||||
|
||||
$random = str_random(RANDOM_KEY_LENGTH);
|
||||
|
||||
$user = new User();
|
||||
$user->password = $random;
|
||||
$user->password_confirmation = $random;
|
||||
$user->username = $random;
|
||||
$user->confirmed = !Utils::isNinja();
|
||||
$account->users()->save($user, []);
|
||||
|
||||
return $account;
|
||||
}
|
||||
|
||||
public function getSearchData()
|
||||
{
|
||||
$clients = \DB::table('clients')
|
||||
->where('clients.deleted_at', '=', null)
|
||||
->where('clients.account_id', '=', \Auth::user()->account_id)
|
||||
->whereRaw("clients.name <> ''")
|
||||
->select(\DB::raw("'Clients' as type, clients.public_id, clients.name, '' as token"));
|
||||
|
||||
$contacts = \DB::table('clients')
|
||||
->join('contacts', 'contacts.client_id', '=', 'clients.id')
|
||||
->where('clients.deleted_at', '=', null)
|
||||
->where('clients.account_id', '=', \Auth::user()->account_id)
|
||||
->whereRaw("CONCAT(contacts.first_name, contacts.last_name, contacts.email) <> ''")
|
||||
->select(\DB::raw("'Contacts' as type, clients.public_id, CONCAT(contacts.first_name, ' ', contacts.last_name, ' ', contacts.email) as name, '' as token"));
|
||||
|
||||
$invoices = \DB::table('clients')
|
||||
->join('invoices', 'invoices.client_id', '=', 'clients.id')
|
||||
->where('clients.account_id', '=', \Auth::user()->account_id)
|
||||
->where('clients.deleted_at', '=', null)
|
||||
->where('invoices.deleted_at', '=', null)
|
||||
->select(\DB::raw("'Invoices' as type, invoices.public_id, CONCAT(invoices.invoice_number, ': ', clients.name) as name, invoices.invoice_number as token"));
|
||||
|
||||
$data = [];
|
||||
|
||||
foreach ($clients->union($contacts)->union($invoices)->get() as $row) {
|
||||
$type = $row->type;
|
||||
|
||||
if (!isset($data[$type])) {
|
||||
$data[$type] = [];
|
||||
}
|
||||
|
||||
$tokens = explode(' ', $row->name);
|
||||
$tokens[] = $type;
|
||||
|
||||
if ($type == 'Invoices') {
|
||||
$tokens[] = intVal($row->token).'';
|
||||
}
|
||||
|
||||
$data[$type][] = [
|
||||
'value' => $row->name,
|
||||
'public_id' => $row->public_id,
|
||||
'tokens' => $tokens,
|
||||
];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function enableProPlan()
|
||||
{
|
||||
if (Auth::user()->isPro()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$ninjaAccount = $this->getNinjaAccount();
|
||||
$lastInvoice = Invoice::withTrashed()->whereAccountId($ninjaAccount->id)->orderBy('public_id', 'DESC')->first();
|
||||
$publicId = $lastInvoice ? ($lastInvoice->public_id + 1) : 1;
|
||||
|
||||
$ninjaClient = $this->getNinjaClient($ninjaAccount);
|
||||
$invitation = $this->createNinjaInvoice($publicId, $ninjaAccount, $ninjaClient);
|
||||
|
||||
return $invitation;
|
||||
}
|
||||
|
||||
private function createNinjaInvoice($publicId, $account, $client)
|
||||
{
|
||||
$invoice = new Invoice();
|
||||
$invoice->account_id = $account->id;
|
||||
$invoice->user_id = $account->users()->first()->id;
|
||||
$invoice->public_id = $publicId;
|
||||
$invoice->client_id = $client->id;
|
||||
$invoice->invoice_number = $account->getNextInvoiceNumber();
|
||||
$invoice->invoice_date = date_create()->format('Y-m-d');
|
||||
$invoice->amount = PRO_PLAN_PRICE;
|
||||
$invoice->balance = PRO_PLAN_PRICE;
|
||||
$invoice->save();
|
||||
|
||||
$item = new InvoiceItem();
|
||||
$item->account_id = $account->id;
|
||||
$item->user_id = $account->users()->first()->id;
|
||||
$item->public_id = $publicId;
|
||||
$item->qty = 1;
|
||||
$item->cost = PRO_PLAN_PRICE;
|
||||
$item->notes = trans('texts.pro_plan_description');
|
||||
$item->product_key = trans('texts.pro_plan_product');
|
||||
$invoice->invoice_items()->save($item);
|
||||
|
||||
$invitation = new Invitation();
|
||||
$invitation->account_id = $account->id;
|
||||
$invitation->user_id = $account->users()->first()->id;
|
||||
$invitation->public_id = $publicId;
|
||||
$invitation->invoice_id = $invoice->id;
|
||||
$invitation->contact_id = $client->contacts()->first()->id;
|
||||
$invitation->invitation_key = str_random(RANDOM_KEY_LENGTH);
|
||||
$invitation->save();
|
||||
|
||||
return $invitation;
|
||||
}
|
||||
|
||||
public function getNinjaAccount()
|
||||
{
|
||||
$account = Account::whereAccountKey(NINJA_ACCOUNT_KEY)->first();
|
||||
|
||||
if ($account) {
|
||||
return $account;
|
||||
} else {
|
||||
$account = new Account();
|
||||
$account->name = 'Invoice Ninja';
|
||||
$account->work_email = 'contact@invoiceninja.com';
|
||||
$account->work_phone = '(800) 763-1948';
|
||||
$account->account_key = NINJA_ACCOUNT_KEY;
|
||||
$account->save();
|
||||
|
||||
$random = str_random(RANDOM_KEY_LENGTH);
|
||||
$user = new User();
|
||||
$user->registered = true;
|
||||
$user->confirmed = true;
|
||||
$user->email = 'contact@invoiceninja.com';
|
||||
$user->password = $random;
|
||||
$user->password_confirmation = $random;
|
||||
$user->username = $random;
|
||||
$user->first_name = 'Invoice';
|
||||
$user->last_name = 'Ninja';
|
||||
$user->notify_sent = true;
|
||||
$user->notify_paid = true;
|
||||
$account->users()->save($user);
|
||||
|
||||
$accountGateway = new AccountGateway();
|
||||
$accountGateway->user_id = $user->id;
|
||||
$accountGateway->gateway_id = NINJA_GATEWAY_ID;
|
||||
$accountGateway->public_id = 1;
|
||||
$accountGateway->config = NINJA_GATEWAY_CONFIG;
|
||||
$account->account_gateways()->save($accountGateway);
|
||||
}
|
||||
|
||||
return $account;
|
||||
}
|
||||
|
||||
private function getNinjaClient($ninjaAccount)
|
||||
{
|
||||
$client = Client::whereAccountId($ninjaAccount->id)->wherePublicId(Auth::user()->account_id)->first();
|
||||
|
||||
if (!$client) {
|
||||
$client = new Client();
|
||||
$client->public_id = Auth::user()->account_id;
|
||||
$client->user_id = $ninjaAccount->users()->first()->id;
|
||||
$client->currency_id = 1;
|
||||
foreach (['name', 'address1', 'address2', 'city', 'state', 'postal_code', 'country_id', 'work_phone'] as $field) {
|
||||
$client->$field = Auth::user()->account->$field;
|
||||
}
|
||||
$ninjaAccount->clients()->save($client);
|
||||
|
||||
$contact = new Contact();
|
||||
$contact->user_id = $ninjaAccount->users()->first()->id;
|
||||
$contact->account_id = $ninjaAccount->id;
|
||||
$contact->public_id = Auth::user()->account_id;
|
||||
$contact->is_primary = true;
|
||||
foreach (['first_name', 'last_name', 'email', 'phone'] as $field) {
|
||||
$contact->$field = Auth::user()->$field;
|
||||
}
|
||||
$client->contacts()->save($contact);
|
||||
}
|
||||
|
||||
return $client;
|
||||
}
|
||||
|
||||
public function registerUser($user)
|
||||
{
|
||||
$url = NINJA_APP_URL.'/signup/register';
|
||||
$data = '';
|
||||
$fields = [
|
||||
'first_name' => urlencode($user->first_name),
|
||||
'last_name' => urlencode($user->last_name),
|
||||
'email' => urlencode($user->email),
|
||||
];
|
||||
|
||||
foreach ($fields as $key => $value) {
|
||||
$data .= $key.'='.$value.'&';
|
||||
}
|
||||
rtrim($data, '&');
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_POST, count($fields));
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
|
||||
curl_exec($ch);
|
||||
curl_close($ch);
|
||||
}
|
||||
}
|
202
app/ninja/repositories/ClientRepository.php
Normal file
202
app/ninja/repositories/ClientRepository.php
Normal file
@ -0,0 +1,202 @@
|
||||
<?php namespace ninja\repositories;
|
||||
|
||||
use Client;
|
||||
use Contact;
|
||||
|
||||
class ClientRepository
|
||||
{
|
||||
public function find($filter = null)
|
||||
{
|
||||
$query = \DB::table('clients')
|
||||
->join('contacts', 'contacts.client_id', '=', 'clients.id')
|
||||
->where('clients.account_id', '=', \Auth::user()->account_id)
|
||||
->where('contacts.is_primary', '=', true)
|
||||
->where('contacts.deleted_at', '=', null)
|
||||
->select('clients.public_id', 'clients.name', 'contacts.first_name', 'contacts.last_name', 'clients.balance', 'clients.last_login', 'clients.created_at', 'clients.work_phone', 'contacts.email', 'clients.currency_id', 'clients.deleted_at', 'clients.is_deleted');
|
||||
|
||||
if (!\Session::get('show_trash:client')) {
|
||||
$query->where('clients.deleted_at', '=', null);
|
||||
}
|
||||
|
||||
if ($filter) {
|
||||
$query->where(function ($query) use ($filter) {
|
||||
$query->where('clients.name', 'like', '%'.$filter.'%')
|
||||
->orWhere('contacts.first_name', 'like', '%'.$filter.'%')
|
||||
->orWhere('contacts.last_name', 'like', '%'.$filter.'%')
|
||||
->orWhere('contacts.email', 'like', '%'.$filter.'%');
|
||||
});
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function getErrors($data)
|
||||
{
|
||||
$contact = isset($data['contacts']) ? (array) $data['contacts'][0] : (isset($data['contact']) ? $data['contact'] : []);
|
||||
$validator = \Validator::make($contact, ['email' => 'required|email']);
|
||||
if ($validator->fails()) {
|
||||
return $validator->messages();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function save($publicId, $data, $notify = true)
|
||||
{
|
||||
if (!$publicId || $publicId == "-1") {
|
||||
$client = Client::createNew();
|
||||
$client->currency_id = 1;
|
||||
$contact = Contact::createNew();
|
||||
$contact->is_primary = true;
|
||||
$contact->send_invoice = true;
|
||||
} else {
|
||||
$client = Client::scope($publicId)->with('contacts')->firstOrFail();
|
||||
$contact = $client->contacts()->where('is_primary', '=', true)->firstOrFail();
|
||||
}
|
||||
|
||||
if (isset($data['name'])) {
|
||||
$client->name = trim($data['name']);
|
||||
}
|
||||
if (isset($data['id_number'])) {
|
||||
$client->id_number = trim($data['id_number']);
|
||||
}
|
||||
if (isset($data['vat_number'])) {
|
||||
$client->vat_number = trim($data['vat_number']);
|
||||
}
|
||||
if (isset($data['work_phone'])) {
|
||||
$client->work_phone = trim($data['work_phone']);
|
||||
}
|
||||
if (isset($data['custom_value1'])) {
|
||||
$client->custom_value1 = trim($data['custom_value1']);
|
||||
}
|
||||
if (isset($data['custom_value2'])) {
|
||||
$client->custom_value2 = trim($data['custom_value2']);
|
||||
}
|
||||
if (isset($data['address1'])) {
|
||||
$client->address1 = trim($data['address1']);
|
||||
}
|
||||
if (isset($data['address2'])) {
|
||||
$client->address2 = trim($data['address2']);
|
||||
}
|
||||
if (isset($data['city'])) {
|
||||
$client->city = trim($data['city']);
|
||||
}
|
||||
if (isset($data['state'])) {
|
||||
$client->state = trim($data['state']);
|
||||
}
|
||||
if (isset($data['postal_code'])) {
|
||||
$client->postal_code = trim($data['postal_code']);
|
||||
}
|
||||
if (isset($data['country_id'])) {
|
||||
$client->country_id = $data['country_id'] ? $data['country_id'] : null;
|
||||
}
|
||||
if (isset($data['private_notes'])) {
|
||||
$client->private_notes = trim($data['private_notes']);
|
||||
}
|
||||
if (isset($data['size_id'])) {
|
||||
$client->size_id = $data['size_id'] ? $data['size_id'] : null;
|
||||
}
|
||||
if (isset($data['industry_id'])) {
|
||||
$client->industry_id = $data['industry_id'] ? $data['industry_id'] : null;
|
||||
}
|
||||
if (isset($data['currency_id'])) {
|
||||
$client->currency_id = $data['currency_id'] ? $data['currency_id'] : null;
|
||||
}
|
||||
if (isset($data['payment_terms'])) {
|
||||
$client->payment_terms = $data['payment_terms'];
|
||||
}
|
||||
if (isset($data['website'])) {
|
||||
$client->website = trim($data['website']);
|
||||
}
|
||||
|
||||
$client->save();
|
||||
|
||||
$isPrimary = true;
|
||||
$contactIds = [];
|
||||
|
||||
if (isset($data['contact'])) {
|
||||
$info = $data['contact'];
|
||||
if (isset($info['email'])) {
|
||||
$contact->email = trim(strtolower($info['email']));
|
||||
}
|
||||
if (isset($info['first_name'])) {
|
||||
$contact->first_name = trim($info['first_name']);
|
||||
}
|
||||
if (isset($info['last_name'])) {
|
||||
$contact->last_name = trim($info['last_name']);
|
||||
}
|
||||
if (isset($info['phone'])) {
|
||||
$contact->phone = trim($info['phone']);
|
||||
}
|
||||
$contact->is_primary = true;
|
||||
$contact->send_invoice = true;
|
||||
$client->contacts()->save($contact);
|
||||
} else {
|
||||
foreach ($data['contacts'] as $record) {
|
||||
$record = (array) $record;
|
||||
|
||||
if ($publicId != "-1" && isset($record['public_id']) && $record['public_id']) {
|
||||
$contact = Contact::scope($record['public_id'])->firstOrFail();
|
||||
} else {
|
||||
$contact = Contact::createNew();
|
||||
}
|
||||
|
||||
if (isset($record['email'])) {
|
||||
$contact->email = trim(strtolower($record['email']));
|
||||
}
|
||||
if (isset($record['first_name'])) {
|
||||
$contact->first_name = trim($record['first_name']);
|
||||
}
|
||||
if (isset($record['last_name'])) {
|
||||
$contact->last_name = trim($record['last_name']);
|
||||
}
|
||||
if (isset($record['phone'])) {
|
||||
$contact->phone = trim($record['phone']);
|
||||
}
|
||||
$contact->is_primary = $isPrimary;
|
||||
$contact->send_invoice = isset($record['send_invoice']) ? $record['send_invoice'] : true;
|
||||
$isPrimary = false;
|
||||
|
||||
$client->contacts()->save($contact);
|
||||
$contactIds[] = $contact->public_id;
|
||||
}
|
||||
|
||||
foreach ($client->contacts as $contact) {
|
||||
if (!in_array($contact->public_id, $contactIds)) {
|
||||
$contact->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$client->save();
|
||||
|
||||
if (!$publicId || $publicId == "-1") {
|
||||
\Activity::createClient($client, $notify);
|
||||
}
|
||||
|
||||
return $client;
|
||||
}
|
||||
|
||||
public function bulk($ids, $action)
|
||||
{
|
||||
$clients = Client::withTrashed()->scope($ids)->get();
|
||||
|
||||
foreach ($clients as $client) {
|
||||
if ($action == 'restore') {
|
||||
$client->restore();
|
||||
|
||||
$client->is_deleted = false;
|
||||
$client->save();
|
||||
} else {
|
||||
if ($action == 'delete') {
|
||||
$client->is_deleted = true;
|
||||
$client->save();
|
||||
}
|
||||
|
||||
$client->delete();
|
||||
}
|
||||
}
|
||||
|
||||
return count($clients);
|
||||
}
|
||||
}
|
77
app/ninja/repositories/CreditRepository.php
Normal file
77
app/ninja/repositories/CreditRepository.php
Normal file
@ -0,0 +1,77 @@
|
||||
<?php namespace ninja\repositories;
|
||||
|
||||
use Credit;
|
||||
use Client;
|
||||
use Utils;
|
||||
|
||||
class CreditRepository
|
||||
{
|
||||
public function find($clientPublicId = null, $filter = null)
|
||||
{
|
||||
$query = \DB::table('credits')
|
||||
->join('clients', 'clients.id', '=', 'credits.client_id')
|
||||
->join('contacts', 'contacts.client_id', '=', 'clients.id')
|
||||
->where('clients.account_id', '=', \Auth::user()->account_id)
|
||||
->where('clients.deleted_at', '=', null)
|
||||
->where('contacts.is_primary', '=', true)
|
||||
->select('credits.public_id', 'clients.name as client_name', 'clients.public_id as client_public_id', 'credits.amount', 'credits.balance', 'credits.credit_date', 'clients.currency_id', 'contacts.first_name', 'contacts.last_name', 'contacts.email', 'credits.private_notes', 'credits.deleted_at', 'credits.is_deleted');
|
||||
|
||||
if ($clientPublicId) {
|
||||
$query->where('clients.public_id', '=', $clientPublicId);
|
||||
}
|
||||
|
||||
if (!\Session::get('show_trash:credit')) {
|
||||
$query->where('credits.deleted_at', '=', null);
|
||||
}
|
||||
|
||||
if ($filter) {
|
||||
$query->where(function ($query) use ($filter) {
|
||||
$query->where('clients.name', 'like', '%'.$filter.'%');
|
||||
});
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function save($publicId = null, $input)
|
||||
{
|
||||
if ($publicId) {
|
||||
$credit = Credit::scope($publicId)->firstOrFail();
|
||||
} else {
|
||||
$credit = Credit::createNew();
|
||||
}
|
||||
|
||||
$credit->client_id = Client::getPrivateId($input['client']);
|
||||
$credit->credit_date = Utils::toSqlDate($input['credit_date']);
|
||||
$credit->amount = Utils::parseFloat($input['amount']);
|
||||
$credit->balance = Utils::parseFloat($input['amount']);
|
||||
$credit->private_notes = trim($input['private_notes']);
|
||||
$credit->save();
|
||||
|
||||
return $credit;
|
||||
}
|
||||
|
||||
public function bulk($ids, $action)
|
||||
{
|
||||
if (!$ids) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$credits = Credit::withTrashed()->scope($ids)->get();
|
||||
|
||||
foreach ($credits as $credit) {
|
||||
if ($action == 'restore') {
|
||||
$credit->restore();
|
||||
} else {
|
||||
if ($action == 'delete') {
|
||||
$credit->is_deleted = true;
|
||||
$credit->save();
|
||||
}
|
||||
|
||||
$credit->delete();
|
||||
}
|
||||
}
|
||||
|
||||
return count($credits);
|
||||
}
|
||||
}
|
490
app/ninja/repositories/InvoiceRepository.php
Normal file
490
app/ninja/repositories/InvoiceRepository.php
Normal file
@ -0,0 +1,490 @@
|
||||
<?php namespace ninja\repositories;
|
||||
|
||||
use Invoice;
|
||||
use InvoiceItem;
|
||||
use Invitation;
|
||||
use Product;
|
||||
use Utils;
|
||||
|
||||
class InvoiceRepository
|
||||
{
|
||||
public function getInvoices($accountId, $clientPublicId = false, $entityType = ENTITY_INVOICE, $filter = false)
|
||||
{
|
||||
$query = \DB::table('invoices')
|
||||
->join('clients', 'clients.id', '=', 'invoices.client_id')
|
||||
->join('invoice_statuses', 'invoice_statuses.id', '=', 'invoices.invoice_status_id')
|
||||
->join('contacts', 'contacts.client_id', '=', 'clients.id')
|
||||
->where('invoices.account_id', '=', $accountId)
|
||||
->where('clients.deleted_at', '=', null)
|
||||
->where('contacts.deleted_at', '=', null)
|
||||
->where('invoices.is_recurring', '=', false)
|
||||
->where('contacts.is_primary', '=', true)
|
||||
->select('clients.public_id as client_public_id', 'invoice_number', 'invoice_status_id', 'clients.name as client_name', 'invoices.public_id', 'amount', 'invoices.balance', 'invoice_date', 'due_date', 'invoice_statuses.name as invoice_status_name', 'clients.currency_id', 'contacts.first_name', 'contacts.last_name', 'contacts.email', 'quote_id', 'quote_invoice_id', 'invoices.deleted_at', 'invoices.is_deleted');
|
||||
|
||||
if (!\Session::get('show_trash:'.$entityType)) {
|
||||
$query->where('invoices.deleted_at', '=', null);
|
||||
}
|
||||
|
||||
if ($clientPublicId) {
|
||||
$query->where('clients.public_id', '=', $clientPublicId);
|
||||
}
|
||||
|
||||
if ($filter) {
|
||||
$query->where(function ($query) use ($filter) {
|
||||
$query->where('clients.name', 'like', '%'.$filter.'%')
|
||||
->orWhere('invoices.invoice_number', 'like', '%'.$filter.'%')
|
||||
->orWhere('invoice_statuses.name', 'like', '%'.$filter.'%')
|
||||
->orWhere('contacts.first_name', 'like', '%'.$filter.'%')
|
||||
->orWhere('contacts.last_name', 'like', '%'.$filter.'%')
|
||||
->orWhere('contacts.email', 'like', '%'.$filter.'%');
|
||||
});
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function getRecurringInvoices($accountId, $clientPublicId = false, $filter = false)
|
||||
{
|
||||
$query = \DB::table('invoices')
|
||||
->join('clients', 'clients.id', '=', 'invoices.client_id')
|
||||
->join('frequencies', 'frequencies.id', '=', 'invoices.frequency_id')
|
||||
->join('contacts', 'contacts.client_id', '=', 'clients.id')
|
||||
->where('invoices.account_id', '=', $accountId)
|
||||
->where('invoices.is_quote', '=', false)
|
||||
->where('clients.deleted_at', '=', null)
|
||||
->where('contacts.deleted_at', '=', null)
|
||||
->where('invoices.is_recurring', '=', true)
|
||||
->where('contacts.is_primary', '=', true)
|
||||
->select('clients.public_id as client_public_id', 'clients.name as client_name', 'invoices.public_id', 'amount', 'frequencies.name as frequency', 'start_date', 'end_date', 'clients.currency_id', 'contacts.first_name', 'contacts.last_name', 'contacts.email', 'invoices.deleted_at', 'invoices.is_deleted');
|
||||
|
||||
if ($clientPublicId) {
|
||||
$query->where('clients.public_id', '=', $clientPublicId);
|
||||
}
|
||||
|
||||
if (!\Session::get('show_trash:invoice')) {
|
||||
$query->where('invoices.deleted_at', '=', null);
|
||||
}
|
||||
|
||||
if ($filter) {
|
||||
$query->where(function ($query) use ($filter) {
|
||||
$query->where('clients.name', 'like', '%'.$filter.'%')
|
||||
->orWhere('invoices.invoice_number', 'like', '%'.$filter.'%');
|
||||
});
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function getClientDatatable($contactId, $entityType, $search)
|
||||
{
|
||||
$query = \DB::table('invitations')
|
||||
->join('invoices', 'invoices.id', '=', 'invitations.invoice_id')
|
||||
->join('clients', 'clients.id', '=', 'invoices.client_id')
|
||||
->where('invitations.contact_id', '=', $contactId)
|
||||
->where('invitations.deleted_at', '=', null)
|
||||
->where('invoices.is_quote', '=', $entityType == ENTITY_QUOTE)
|
||||
->where('invoices.is_deleted', '=', false)
|
||||
->where('clients.deleted_at', '=', null)
|
||||
->where('invoices.is_recurring', '=', false)
|
||||
->select('invitation_key', 'invoice_number', 'invoice_date', 'invoices.balance as balance', 'due_date', 'clients.public_id as client_public_id', 'clients.name as client_name', 'invoices.public_id', 'amount', 'start_date', 'end_date', 'clients.currency_id');
|
||||
|
||||
$table = \Datatable::query($query)
|
||||
->addColumn('invoice_number', function ($model) use ($entityType) { return link_to('/view/'.$model->invitation_key, $model->invoice_number); })
|
||||
->addColumn('invoice_date', function ($model) { return Utils::fromSqlDate($model->invoice_date); })
|
||||
->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id); });
|
||||
|
||||
if ($entityType == ENTITY_INVOICE) {
|
||||
$table->addColumn('balance', function ($model) { return Utils::formatMoney($model->balance, $model->currency_id); });
|
||||
}
|
||||
|
||||
return $table->addColumn('due_date', function ($model) { return Utils::fromSqlDate($model->due_date); })
|
||||
->make();
|
||||
}
|
||||
|
||||
public function getDatatable($accountId, $clientPublicId = null, $entityType, $search)
|
||||
{
|
||||
$query = $this->getInvoices($accountId, $clientPublicId, $entityType, $search)
|
||||
->where('invoices.is_quote', '=', $entityType == ENTITY_QUOTE ? true : false);
|
||||
|
||||
$table = \Datatable::query($query);
|
||||
|
||||
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) use ($entityType) { return link_to("{$entityType}s/".$model->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("invoice_date", function ($model) { return Utils::fromSqlDate($model->invoice_date); })
|
||||
->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id); });
|
||||
|
||||
if ($entityType == ENTITY_INVOICE) {
|
||||
$table->addColumn('balance', function ($model) { return Utils::formatMoney($model->balance, $model->currency_id); });
|
||||
}
|
||||
|
||||
return $table->addColumn('due_date', function ($model) { return Utils::fromSqlDate($model->due_date); })
|
||||
->addColumn('invoice_status_name', function ($model) { return $model->quote_invoice_id ? link_to("invoices/{$model->quote_invoice_id}/edit", trans('texts.converted')) : $model->invoice_status_name; })
|
||||
->addColumn('dropdown', function ($model) use ($entityType) {
|
||||
|
||||
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("{$entityType}s/".$model->public_id.'/edit').'">'.trans("texts.edit_{$entityType}").'</a></li>
|
||||
<li><a href="'.\URL::to("{$entityType}s/".$model->public_id.'/clone').'">'.trans("texts.clone_{$entityType}").'</a></li>
|
||||
<li><a href="' . \URL::to("{$entityType}s/{$entityType}_history/{$model->public_id}") . '">' . trans("texts.view_history") . '</a></li>
|
||||
<li class="divider"></li>';
|
||||
|
||||
if ($model->invoice_status_id < INVOICE_STATUS_SENT) {
|
||||
$str .= '<li><a href="javascript:markEntity('.$model->public_id.', '.INVOICE_STATUS_SENT.')">'.trans("texts.mark_sent").'</a></li>';
|
||||
}
|
||||
|
||||
if ($entityType == ENTITY_INVOICE) {
|
||||
if ($model->invoice_status_id < INVOICE_STATUS_PAID) {
|
||||
$str .= '<li><a href="'.\URL::to('payments/create/'.$model->client_public_id.'/'.$model->public_id).'">'.trans('texts.enter_payment').'</a></li>';
|
||||
}
|
||||
|
||||
if ($model->quote_id) {
|
||||
$str .= '<li><a href="'.\URL::to("quotes/{$model->quote_id}/edit").'">'.trans("texts.view_quote").'</a></li>';
|
||||
}
|
||||
} elseif ($entityType == ENTITY_QUOTE) {
|
||||
if ($model->quote_invoice_id) {
|
||||
$str .= '<li><a href="'.\URL::to("invoices/{$model->quote_invoice_id}/edit").'">'.trans("texts.view_invoice").'</a></li>';
|
||||
} else {
|
||||
$str .= '<li><a href="javascript:convertEntity('.$model->public_id.')">'.trans("texts.convert_to_invoice").'</a></li>';
|
||||
}
|
||||
}
|
||||
|
||||
$str .= '<li class="divider"></li>
|
||||
<li><a href="javascript:archiveEntity('.$model->public_id.')">'.trans("texts.archive_{$entityType}").'</a></li>
|
||||
<li><a href="javascript:deleteEntity('.$model->public_id.')">'.trans("texts.delete_{$entityType}").'</a></li>';
|
||||
} else {
|
||||
$str .= '<li><a href="javascript:restoreEntity('.$model->public_id.')">'.trans("texts.restore_{$entityType}").'</a></li>
|
||||
<li><a href="javascript:deleteEntity('.$model->public_id.')">'.trans("texts.delete_{$entityType}").'</a></li>';
|
||||
}
|
||||
|
||||
return $str.'</ul>
|
||||
</div>';
|
||||
})
|
||||
->make();
|
||||
}
|
||||
|
||||
public function getErrors($input)
|
||||
{
|
||||
$contact = (array) $input->client->contacts[0];
|
||||
$rules = ['email' => 'required|email'];
|
||||
$validator = \Validator::make($contact, $rules);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return $validator;
|
||||
}
|
||||
|
||||
$invoice = (array) $input;
|
||||
$invoiceId = isset($invoice['public_id']) && $invoice['public_id'] ? Invoice::getPrivateId($invoice['public_id']) : null;
|
||||
$rules = [
|
||||
'invoice_number' => 'required|unique:invoices,invoice_number,'.$invoiceId.',id,account_id,'.\Auth::user()->account_id,
|
||||
'discount' => 'positive',
|
||||
];
|
||||
|
||||
if ($invoice['is_recurring'] && $invoice['start_date'] && $invoice['end_date']) {
|
||||
$rules['end_date'] = 'after:'.$invoice['start_date'];
|
||||
}
|
||||
|
||||
$validator = \Validator::make($invoice, $rules);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return $validator;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function save($publicId, $data, $entityType)
|
||||
{
|
||||
if ($publicId) {
|
||||
$invoice = Invoice::scope($publicId)->firstOrFail();
|
||||
} else {
|
||||
$invoice = Invoice::createNew();
|
||||
|
||||
if ($entityType == ENTITY_QUOTE) {
|
||||
$invoice->is_quote = true;
|
||||
}
|
||||
}
|
||||
|
||||
$account = \Auth::user()->account;
|
||||
|
||||
$invoice->client_id = $data['client_id'];
|
||||
$invoice->discount = round(Utils::parseFloat($data['discount']), 2);
|
||||
$invoice->is_amount_discount = $data['is_amount_discount'] ? true : false;
|
||||
$invoice->invoice_number = trim($data['invoice_number']);
|
||||
$invoice->is_recurring = $data['is_recurring'] && !Utils::isDemo() ? true : false;
|
||||
$invoice->invoice_date = isset($data['invoice_date_sql']) ? $data['invoice_date_sql'] : Utils::toSqlDate($data['invoice_date']);
|
||||
|
||||
if ($invoice->is_recurring) {
|
||||
$invoice->frequency_id = $data['frequency_id'] ? $data['frequency_id'] : 0;
|
||||
$invoice->start_date = Utils::toSqlDate($data['start_date']);
|
||||
$invoice->end_date = Utils::toSqlDate($data['end_date']);
|
||||
$invoice->due_date = null;
|
||||
} else {
|
||||
$invoice->due_date = isset($data['due_date_sql']) ? $data['due_date_sql'] : Utils::toSqlDate($data['due_date']);
|
||||
$invoice->frequency_id = 0;
|
||||
$invoice->start_date = null;
|
||||
$invoice->end_date = null;
|
||||
}
|
||||
|
||||
$invoice->terms = trim($data['terms']) ? trim($data['terms']) : ($account->invoice_terms ? $account->invoice_terms : '');
|
||||
$invoice->invoice_footer = trim($data['invoice_footer']) ? trim($data['invoice_footer']) : $account->invoice_footer;
|
||||
$invoice->public_notes = trim($data['public_notes']);
|
||||
$invoice->po_number = trim($data['po_number']);
|
||||
$invoice->invoice_design_id = $data['invoice_design_id'];
|
||||
|
||||
if (isset($data['tax_name']) && isset($data['tax_rate']) && $data['tax_name']) {
|
||||
$invoice->tax_rate = Utils::parseFloat($data['tax_rate']);
|
||||
$invoice->tax_name = trim($data['tax_name']);
|
||||
} else {
|
||||
$invoice->tax_rate = 0;
|
||||
$invoice->tax_name = '';
|
||||
}
|
||||
|
||||
$total = 0;
|
||||
|
||||
foreach ($data['invoice_items'] as $item) {
|
||||
$item = (array) $item;
|
||||
if (!$item['cost'] && !$item['product_key'] && !$item['notes']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$invoiceItemCost = round(Utils::parseFloat($item['cost']), 2);
|
||||
$invoiceItemQty = round(Utils::parseFloat($item['qty']), 2);
|
||||
$invoiceItemTaxRate = 0;
|
||||
|
||||
if (isset($item['tax_rate']) && Utils::parseFloat($item['tax_rate']) > 0) {
|
||||
$invoiceItemTaxRate = Utils::parseFloat($item['tax_rate']);
|
||||
}
|
||||
|
||||
$lineTotal = $invoiceItemCost * $invoiceItemQty;
|
||||
|
||||
$total += round($lineTotal + ($lineTotal * $invoiceItemTaxRate / 100), 2);
|
||||
}
|
||||
|
||||
if ($invoice->discount > 0) {
|
||||
if ($invoice->is_amount_discount) {
|
||||
$total -= $invoice->discount;
|
||||
} else {
|
||||
$total *= (100 - $invoice->discount) / 100;
|
||||
}
|
||||
}
|
||||
|
||||
$invoice->custom_value1 = round($data['custom_value1'], 2);
|
||||
$invoice->custom_value2 = round($data['custom_value2'], 2);
|
||||
$invoice->custom_taxes1 = $data['custom_taxes1'] ? true : false;
|
||||
$invoice->custom_taxes2 = $data['custom_taxes2'] ? true : false;
|
||||
|
||||
// custom fields charged taxes
|
||||
if ($invoice->custom_value1 && $invoice->custom_taxes1) {
|
||||
$total += $invoice->custom_value1;
|
||||
}
|
||||
if ($invoice->custom_value2 && $invoice->custom_taxes2) {
|
||||
$total += $invoice->custom_value2;
|
||||
}
|
||||
|
||||
$total += $total * $invoice->tax_rate / 100;
|
||||
$total = round($total, 2);
|
||||
|
||||
// custom fields not charged taxes
|
||||
if ($invoice->custom_value1 && !$invoice->custom_taxes1) {
|
||||
$total += $invoice->custom_value1;
|
||||
}
|
||||
if ($invoice->custom_value2 && !$invoice->custom_taxes2) {
|
||||
$total += $invoice->custom_value2;
|
||||
}
|
||||
|
||||
if ($publicId) {
|
||||
$invoice->balance = $total - ($invoice->amount - $invoice->balance);
|
||||
} else {
|
||||
$invoice->balance = $total;
|
||||
}
|
||||
|
||||
$invoice->amount = $total;
|
||||
$invoice->save();
|
||||
|
||||
if ($publicId) {
|
||||
$invoice->invoice_items()->forceDelete();
|
||||
}
|
||||
|
||||
foreach ($data['invoice_items'] as $item) {
|
||||
$item = (array) $item;
|
||||
if (!$item['cost'] && !$item['product_key'] && !$item['notes']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($item['product_key']) {
|
||||
$product = Product::findProductByKey(trim($item['product_key']));
|
||||
|
||||
if (!$product) {
|
||||
$product = Product::createNew();
|
||||
$product->product_key = trim($item['product_key']);
|
||||
}
|
||||
|
||||
if (\Auth::user()->account->update_products) {
|
||||
$product->notes = $item['notes'];
|
||||
$product->cost = $item['cost'];
|
||||
}
|
||||
|
||||
$product->save();
|
||||
}
|
||||
|
||||
$invoiceItem = InvoiceItem::createNew();
|
||||
$invoiceItem->product_id = isset($product) ? $product->id : null;
|
||||
$invoiceItem->product_key = trim($invoice->is_recurring ? $item->product_key : Utils::processVariables($item['product_key']));
|
||||
$invoiceItem->notes = trim($invoice->is_recurring ? $item['notes'] : Utils::processVariables($item['notes']));
|
||||
$invoiceItem->cost = Utils::parseFloat($item['cost']);
|
||||
$invoiceItem->qty = Utils::parseFloat($item['qty']);
|
||||
$invoiceItem->tax_rate = 0;
|
||||
|
||||
if (isset($item['tax_rate']) && isset($item['tax_name']) && $item['tax_name']) {
|
||||
$invoiceItem['tax_rate'] = Utils::parseFloat($item['tax_rate']);
|
||||
$invoiceItem['tax_name'] = trim($item['tax_name']);
|
||||
}
|
||||
|
||||
$invoice->invoice_items()->save($invoiceItem);
|
||||
}
|
||||
|
||||
if ((isset($data['set_default_terms']) && $data['set_default_terms'])
|
||||
|| (isset($data['set_default_footer']) && $data['set_default_footer'])) {
|
||||
if (isset($data['set_default_terms']) && $data['set_default_terms']) {
|
||||
$account->invoice_terms = trim($data['terms']);
|
||||
}
|
||||
if (isset($data['set_default_footer']) && $data['set_default_footer']) {
|
||||
$account->invoice_footer = trim($data['invoice_footer']);
|
||||
}
|
||||
$account->save();
|
||||
}
|
||||
|
||||
return $invoice;
|
||||
}
|
||||
|
||||
public function cloneInvoice($invoice, $quotePublicId = null)
|
||||
{
|
||||
$invoice->load('invitations', 'invoice_items');
|
||||
$account = $invoice->account;
|
||||
|
||||
$clone = Invoice::createNew($invoice);
|
||||
$clone->balance = $invoice->amount;
|
||||
|
||||
// if the invoice prefix is diff than quote prefix, use the same number for the invoice
|
||||
if (($account->invoice_number_prefix || $account->quote_number_prefix)
|
||||
&& $account->invoice_number_prefix != $account->quote_number_prefix
|
||||
&& $account->share_counter) {
|
||||
|
||||
$invoiceNumber = $invoice->invoice_number;
|
||||
if (strpos($invoiceNumber, $account->quote_number_prefix) === 0) {
|
||||
$invoiceNumber = substr($invoiceNumber, strlen($account->quote_number_prefix));
|
||||
}
|
||||
$clone->invoice_number = $account->invoice_number_prefix.$invoiceNumber;
|
||||
} else {
|
||||
$clone->invoice_number = $account->getNextInvoiceNumber();
|
||||
}
|
||||
|
||||
foreach ([
|
||||
'client_id',
|
||||
'discount',
|
||||
'is_amount_discount',
|
||||
'invoice_date',
|
||||
'po_number',
|
||||
'due_date',
|
||||
'is_recurring',
|
||||
'frequency_id',
|
||||
'start_date',
|
||||
'end_date',
|
||||
'terms',
|
||||
'invoice_footer',
|
||||
'public_notes',
|
||||
'invoice_design_id',
|
||||
'tax_name',
|
||||
'tax_rate',
|
||||
'amount',
|
||||
'is_quote',
|
||||
'custom_value1',
|
||||
'custom_value2',
|
||||
'custom_taxes1',
|
||||
'custom_taxes2', ] as $field) {
|
||||
$clone->$field = $invoice->$field;
|
||||
}
|
||||
|
||||
if ($quotePublicId) {
|
||||
$clone->is_quote = false;
|
||||
$clone->quote_id = $quotePublicId;
|
||||
}
|
||||
|
||||
$clone->save();
|
||||
|
||||
if ($quotePublicId) {
|
||||
$invoice->quote_invoice_id = $clone->public_id;
|
||||
$invoice->save();
|
||||
}
|
||||
|
||||
foreach ($invoice->invoice_items as $item) {
|
||||
$cloneItem = InvoiceItem::createNew($invoice);
|
||||
|
||||
foreach ([
|
||||
'product_id',
|
||||
'product_key',
|
||||
'notes',
|
||||
'cost',
|
||||
'qty',
|
||||
'tax_name',
|
||||
'tax_rate', ] as $field) {
|
||||
$cloneItem->$field = $item->$field;
|
||||
}
|
||||
|
||||
$clone->invoice_items()->save($cloneItem);
|
||||
}
|
||||
|
||||
foreach ($invoice->invitations as $invitation) {
|
||||
$cloneInvitation = Invitation::createNew($invoice);
|
||||
$cloneInvitation->contact_id = $invitation->contact_id;
|
||||
$cloneInvitation->invitation_key = str_random(RANDOM_KEY_LENGTH);
|
||||
$clone->invitations()->save($cloneInvitation);
|
||||
}
|
||||
|
||||
return $clone;
|
||||
}
|
||||
|
||||
public function bulk($ids, $action, $statusId = false)
|
||||
{
|
||||
if (!$ids) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$invoices = Invoice::withTrashed()->scope($ids)->get();
|
||||
|
||||
foreach ($invoices as $invoice) {
|
||||
if ($action == 'mark') {
|
||||
$invoice->invoice_status_id = $statusId;
|
||||
$invoice->save();
|
||||
} elseif ($action == 'restore') {
|
||||
$invoice->restore();
|
||||
} else {
|
||||
if ($action == 'delete') {
|
||||
$invoice->is_deleted = true;
|
||||
$invoice->save();
|
||||
}
|
||||
|
||||
$invoice->delete();
|
||||
}
|
||||
}
|
||||
|
||||
return count($invoices);
|
||||
}
|
||||
}
|
154
app/ninja/repositories/PaymentRepository.php
Normal file
154
app/ninja/repositories/PaymentRepository.php
Normal file
@ -0,0 +1,154 @@
|
||||
<?php namespace ninja\repositories;
|
||||
|
||||
use Payment;
|
||||
use Credit;
|
||||
use Invoice;
|
||||
use Client;
|
||||
use Utils;
|
||||
|
||||
class PaymentRepository
|
||||
{
|
||||
public function find($clientPublicId = null, $filter = null)
|
||||
{
|
||||
$query = \DB::table('payments')
|
||||
->join('clients', 'clients.id', '=', 'payments.client_id')
|
||||
->join('invoices', 'invoices.id', '=', 'payments.invoice_id')
|
||||
->join('contacts', 'contacts.client_id', '=', 'clients.id')
|
||||
->leftJoin('payment_types', 'payment_types.id', '=', 'payments.payment_type_id')
|
||||
->where('payments.account_id', '=', \Auth::user()->account_id)
|
||||
->where('clients.deleted_at', '=', null)
|
||||
->where('contacts.is_primary', '=', true)
|
||||
->where('contacts.deleted_at', '=', null)
|
||||
->select('payments.public_id', 'payments.transaction_reference', 'clients.name as client_name', 'clients.public_id as client_public_id', 'payments.amount', 'payments.payment_date', 'invoices.public_id as invoice_public_id', 'invoices.invoice_number', 'clients.currency_id', 'contacts.first_name', 'contacts.last_name', 'contacts.email', 'payment_types.name as payment_type', 'payments.account_gateway_id', 'payments.deleted_at', 'payments.is_deleted', 'invoices.is_deleted as invoice_is_deleted');
|
||||
|
||||
if (!\Session::get('show_trash:payment')) {
|
||||
$query->where('payments.deleted_at', '=', null)
|
||||
->where('invoices.deleted_at', '=', null);
|
||||
}
|
||||
|
||||
if ($clientPublicId) {
|
||||
$query->where('clients.public_id', '=', $clientPublicId);
|
||||
}
|
||||
|
||||
if ($filter) {
|
||||
$query->where(function ($query) use ($filter) {
|
||||
$query->where('clients.name', 'like', '%'.$filter.'%');
|
||||
});
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function findForContact($contactId = null, $filter = null)
|
||||
{
|
||||
$query = \DB::table('payments')
|
||||
->join('clients', 'clients.id', '=', 'payments.client_id')
|
||||
->join('invoices', 'invoices.id', '=', 'payments.invoice_id')
|
||||
->join('contacts', 'contacts.client_id', '=', 'clients.id')
|
||||
->leftJoin('invitations', function ($join) {
|
||||
$join->on('invitations.invoice_id', '=', 'invoices.id')
|
||||
->on('invitations.contact_id', '=', 'contacts.id');
|
||||
})
|
||||
->leftJoin('payment_types', 'payment_types.id', '=', 'payments.payment_type_id')
|
||||
->where('clients.is_deleted', '=', false)
|
||||
->where('payments.is_deleted', '=', false)
|
||||
->where('invitations.deleted_at', '=', null)
|
||||
->where('invoices.deleted_at', '=', null)
|
||||
->where('invitations.contact_id', '=', $contactId)
|
||||
->select('invitations.invitation_key', 'payments.public_id', 'payments.transaction_reference', 'clients.name as client_name', 'clients.public_id as client_public_id', 'payments.amount', 'payments.payment_date', 'invoices.public_id as invoice_public_id', 'invoices.invoice_number', 'clients.currency_id', 'contacts.first_name', 'contacts.last_name', 'contacts.email', 'payment_types.name as payment_type', 'payments.account_gateway_id');
|
||||
|
||||
if ($filter) {
|
||||
$query->where(function ($query) use ($filter) {
|
||||
$query->where('clients.name', 'like', '%'.$filter.'%');
|
||||
});
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function getErrors($input)
|
||||
{
|
||||
$rules = array(
|
||||
'client' => 'required',
|
||||
'invoice' => 'required',
|
||||
'amount' => 'required',
|
||||
);
|
||||
|
||||
if ($input['payment_type_id'] == PAYMENT_TYPE_CREDIT) {
|
||||
$rules['payment_type_id'] = 'has_credit:'.$input['client'].','.$input['amount'];
|
||||
}
|
||||
|
||||
$validator = \Validator::make($input, $rules);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return $validator;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function save($publicId = null, $input)
|
||||
{
|
||||
if ($publicId) {
|
||||
$payment = Payment::scope($publicId)->firstOrFail();
|
||||
} else {
|
||||
$payment = Payment::createNew();
|
||||
}
|
||||
|
||||
$paymentTypeId = $input['payment_type_id'] ? $input['payment_type_id'] : null;
|
||||
$payment->payment_type_id = $paymentTypeId;
|
||||
$payment->payment_date = Utils::toSqlDate($input['payment_date']);
|
||||
$payment->transaction_reference = trim($input['transaction_reference']);
|
||||
|
||||
if (!$publicId) {
|
||||
$clientId = Client::getPrivateId($input['client']);
|
||||
$amount = Utils::parseFloat($input['amount']);
|
||||
|
||||
if ($paymentTypeId == PAYMENT_TYPE_CREDIT) {
|
||||
$credits = Credit::scope()->where('client_id', '=', $clientId)
|
||||
->where('balance', '>', 0)->orderBy('created_at')->get();
|
||||
$applied = 0;
|
||||
|
||||
foreach ($credits as $credit) {
|
||||
$applied += $credit->apply($amount);
|
||||
|
||||
if ($applied >= $amount) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$payment->client_id = $clientId;
|
||||
$payment->invoice_id = isset($input['invoice']) && $input['invoice'] != "-1" ? Invoice::getPrivateId($input['invoice']) : null;
|
||||
$payment->amount = $amount;
|
||||
}
|
||||
|
||||
$payment->save();
|
||||
|
||||
return $payment;
|
||||
}
|
||||
|
||||
public function bulk($ids, $action)
|
||||
{
|
||||
if (!$ids) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$payments = Payment::withTrashed()->scope($ids)->get();
|
||||
|
||||
foreach ($payments as $payment) {
|
||||
if ($action == 'restore') {
|
||||
$payment->restore();
|
||||
} else {
|
||||
if ($action == 'delete') {
|
||||
$payment->is_deleted = true;
|
||||
$payment->save();
|
||||
}
|
||||
|
||||
$payment->delete();
|
||||
}
|
||||
}
|
||||
|
||||
return count($payments);
|
||||
}
|
||||
}
|
42
app/ninja/repositories/TaxRateRepository.php
Normal file
42
app/ninja/repositories/TaxRateRepository.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php namespace ninja\repositories;
|
||||
|
||||
use TaxRate;
|
||||
use Utils;
|
||||
|
||||
class TaxRateRepository
|
||||
{
|
||||
public function save($taxRates)
|
||||
{
|
||||
$taxRateIds = [];
|
||||
|
||||
foreach ($taxRates as $record) {
|
||||
if (!isset($record->rate) || (isset($record->is_deleted) && $record->is_deleted)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($record->name) || !trim($record->name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($record->public_id) {
|
||||
$taxRate = TaxRate::scope($record->public_id)->firstOrFail();
|
||||
} else {
|
||||
$taxRate = TaxRate::createNew();
|
||||
}
|
||||
|
||||
$taxRate->rate = Utils::parseFloat($record->rate);
|
||||
$taxRate->name = trim($record->name);
|
||||
$taxRate->save();
|
||||
|
||||
$taxRateIds[] = $taxRate->public_id;
|
||||
}
|
||||
|
||||
$taxRates = TaxRate::scope()->get();
|
||||
|
||||
foreach ($taxRates as $taxRate) {
|
||||
if (!in_array($taxRate->public_id, $taxRateIds)) {
|
||||
$taxRate->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -57,9 +57,9 @@
|
||||
"autoload": {
|
||||
"classmap": [
|
||||
"app/Console/Commands",
|
||||
"app/libraries",
|
||||
"app/Http/Controllers",
|
||||
"app/Models",
|
||||
"app/libraries",
|
||||
"app/ninja",
|
||||
"vendor/calvinfroedge/PHP-Payments/lib",
|
||||
"database"
|
||||
|
6220
composer.lock
generated
Normal file
6220
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -171,7 +171,44 @@ return [
|
||||
|
||||
'aliases' => [
|
||||
|
||||
'App' => 'Illuminate\Support\Facades\App',
|
||||
'App' => 'Illuminate\Support\Facades\App',
|
||||
'Artisan' => 'Illuminate\Support\Facades\Artisan',
|
||||
'Auth' => 'Illuminate\Support\Facades\Auth',
|
||||
'Blade' => 'Illuminate\Support\Facades\Blade',
|
||||
'Cache' => 'Illuminate\Support\Facades\Cache',
|
||||
'ClassLoader' => 'Illuminate\Support\ClassLoader',
|
||||
'Config' => 'Illuminate\Support\Facades\Config',
|
||||
'Controller' => 'Illuminate\Routing\Controller',
|
||||
'Cookie' => 'Illuminate\Support\Facades\Cookie',
|
||||
'Crypt' => 'Illuminate\Support\Facades\Crypt',
|
||||
'DB' => 'Illuminate\Support\Facades\DB',
|
||||
'Eloquent' => 'Illuminate\Database\Eloquent\Model',
|
||||
'Event' => 'Illuminate\Support\Facades\Event',
|
||||
'File' => 'Illuminate\Support\Facades\File',
|
||||
//'Form' => 'Illuminate\Support\Facades\Form',
|
||||
'Hash' => 'Illuminate\Support\Facades\Hash',
|
||||
'HTML' => 'Illuminate\Support\Facades\HTML',
|
||||
'Input' => 'Illuminate\Support\Facades\Input',
|
||||
'Lang' => 'Illuminate\Support\Facades\Lang',
|
||||
'Log' => 'Illuminate\Support\Facades\Log',
|
||||
'Mail' => 'Illuminate\Support\Facades\Mail',
|
||||
//'Paginator' => 'Illuminate\Support\Facades\Paginator',
|
||||
'Password' => 'Illuminate\Support\Facades\Password',
|
||||
'Queue' => 'Illuminate\Support\Facades\Queue',
|
||||
'Redirect' => 'Illuminate\Support\Facades\Redirect',
|
||||
'Redis' => 'Illuminate\Support\Facades\Redis',
|
||||
'Request' => 'Illuminate\Support\Facades\Request',
|
||||
'Response' => 'Illuminate\Support\Facades\Response',
|
||||
'Route' => 'Illuminate\Support\Facades\Route',
|
||||
'Schema' => 'Illuminate\Support\Facades\Schema',
|
||||
'Seeder' => 'Illuminate\Database\Seeder',
|
||||
'Session' => 'Illuminate\Support\Facades\Session',
|
||||
'Str' => 'Illuminate\Support\Str',
|
||||
'URL' => 'Illuminate\Support\Facades\URL',
|
||||
'Validator' => 'Illuminate\Support\Facades\Validator',
|
||||
'View' => 'Illuminate\Support\Facades\View',
|
||||
|
||||
/*'App' => 'Illuminate\Support\Facades\App',
|
||||
'Artisan' => 'Illuminate\Support\Facades\Artisan',
|
||||
'Auth' => 'Illuminate\Support\Facades\Auth',
|
||||
'Blade' => 'Illuminate\Support\Facades\Blade',
|
||||
@ -202,7 +239,7 @@ return [
|
||||
'Storage' => 'Illuminate\Support\Facades\Storage',
|
||||
'URL' => 'Illuminate\Support\Facades\URL',
|
||||
'Validator' => 'Illuminate\Support\Facades\Validator',
|
||||
'View' => 'Illuminate\Support\Facades\View',
|
||||
'View' => 'Illuminate\Support\Facades\View',*/
|
||||
|
||||
// Added Class Aliases
|
||||
|
||||
|
@ -0,0 +1,550 @@
|
||||
<?php
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class ConfideSetupUsersTable extends Migration {
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::dropIfExists('payment_terms');
|
||||
Schema::dropIfExists('themes');
|
||||
Schema::dropIfExists('credits');
|
||||
Schema::dropIfExists('activities');
|
||||
Schema::dropIfExists('invitations');
|
||||
Schema::dropIfExists('payments');
|
||||
Schema::dropIfExists('account_gateways');
|
||||
Schema::dropIfExists('invoice_items');
|
||||
Schema::dropIfExists('products');
|
||||
Schema::dropIfExists('tax_rates');
|
||||
Schema::dropIfExists('contacts');
|
||||
Schema::dropIfExists('invoices');
|
||||
Schema::dropIfExists('password_reminders');
|
||||
Schema::dropIfExists('clients');
|
||||
Schema::dropIfExists('users');
|
||||
Schema::dropIfExists('accounts');
|
||||
Schema::dropIfExists('currencies');
|
||||
Schema::dropIfExists('invoice_statuses');
|
||||
Schema::dropIfExists('countries');
|
||||
Schema::dropIfExists('timezones');
|
||||
Schema::dropIfExists('frequencies');
|
||||
Schema::dropIfExists('date_formats');
|
||||
Schema::dropIfExists('datetime_formats');
|
||||
Schema::dropIfExists('sizes');
|
||||
Schema::dropIfExists('industries');
|
||||
Schema::dropIfExists('gateways');
|
||||
Schema::dropIfExists('payment_types');
|
||||
|
||||
Schema::create('countries', function($table)
|
||||
{
|
||||
$table->increments('id');
|
||||
$table->string('capital', 255)->nullable();
|
||||
$table->string('citizenship', 255)->nullable();
|
||||
$table->string('country_code', 3)->default('');
|
||||
$table->string('currency', 255)->nullable();
|
||||
$table->string('currency_code', 255)->nullable();
|
||||
$table->string('currency_sub_unit', 255)->nullable();
|
||||
$table->string('full_name', 255)->nullable();
|
||||
$table->string('iso_3166_2', 2)->default('');
|
||||
$table->string('iso_3166_3', 3)->default('');
|
||||
$table->string('name', 255)->default('');
|
||||
$table->string('region_code', 3)->default('');
|
||||
$table->string('sub_region_code', 3)->default('');
|
||||
$table->boolean('eea')->default(0);
|
||||
});
|
||||
|
||||
Schema::create('themes', function($t)
|
||||
{
|
||||
$t->increments('id');
|
||||
$t->string('name');
|
||||
});
|
||||
|
||||
Schema::create('payment_types', function($t)
|
||||
{
|
||||
$t->increments('id');
|
||||
$t->string('name');
|
||||
});
|
||||
|
||||
Schema::create('payment_terms', function($t)
|
||||
{
|
||||
$t->increments('id');
|
||||
$t->integer('num_days');
|
||||
$t->string('name');
|
||||
});
|
||||
|
||||
Schema::create('timezones', function($t)
|
||||
{
|
||||
$t->increments('id');
|
||||
$t->string('name');
|
||||
$t->string('location');
|
||||
});
|
||||
|
||||
Schema::create('date_formats', function($t)
|
||||
{
|
||||
$t->increments('id');
|
||||
$t->string('format');
|
||||
$t->string('picker_format');
|
||||
$t->string('label');
|
||||
});
|
||||
|
||||
Schema::create('datetime_formats', function($t)
|
||||
{
|
||||
$t->increments('id');
|
||||
$t->string('format');
|
||||
$t->string('label');
|
||||
});
|
||||
|
||||
Schema::create('currencies', function($t)
|
||||
{
|
||||
$t->increments('id');
|
||||
|
||||
$t->string('name');
|
||||
$t->string('symbol');
|
||||
$t->string('precision');
|
||||
$t->string('thousand_separator');
|
||||
$t->string('decimal_separator');
|
||||
$t->string('code');
|
||||
});
|
||||
|
||||
Schema::create('sizes', function($t)
|
||||
{
|
||||
$t->increments('id');
|
||||
$t->string('name');
|
||||
});
|
||||
|
||||
Schema::create('industries', function($t)
|
||||
{
|
||||
$t->increments('id');
|
||||
$t->string('name');
|
||||
});
|
||||
|
||||
Schema::create('accounts', function($t)
|
||||
{
|
||||
$t->increments('id');
|
||||
$t->unsignedInteger('timezone_id')->nullable();
|
||||
$t->unsignedInteger('date_format_id')->nullable();
|
||||
$t->unsignedInteger('datetime_format_id')->nullable();
|
||||
$t->unsignedInteger('currency_id')->nullable();
|
||||
|
||||
$t->timestamps();
|
||||
$t->softDeletes();
|
||||
|
||||
$t->string('name')->nullable();
|
||||
$t->string('ip');
|
||||
$t->string('account_key')->unique();
|
||||
$t->timestamp('last_login')->nullable();
|
||||
|
||||
$t->string('address1')->nullable();
|
||||
$t->string('address2')->nullable();
|
||||
$t->string('city')->nullable();
|
||||
$t->string('state')->nullable();
|
||||
$t->string('postal_code')->nullable();
|
||||
$t->unsignedInteger('country_id')->nullable();
|
||||
$t->text('invoice_terms')->nullable();
|
||||
$t->text('email_footer')->nullable();
|
||||
$t->unsignedInteger('industry_id')->nullable();
|
||||
$t->unsignedInteger('size_id')->nullable();
|
||||
|
||||
$t->boolean('invoice_taxes')->default(true);
|
||||
$t->boolean('invoice_item_taxes')->default(false);
|
||||
|
||||
$t->foreign('timezone_id')->references('id')->on('timezones');
|
||||
$t->foreign('date_format_id')->references('id')->on('date_formats');
|
||||
$t->foreign('datetime_format_id')->references('id')->on('datetime_formats');
|
||||
$t->foreign('country_id')->references('id')->on('countries');
|
||||
$t->foreign('currency_id')->references('id')->on('currencies');
|
||||
$t->foreign('industry_id')->references('id')->on('industries');
|
||||
$t->foreign('size_id')->references('id')->on('sizes');
|
||||
});
|
||||
|
||||
Schema::create('gateways', function($t)
|
||||
{
|
||||
$t->increments('id');
|
||||
$t->timestamps();
|
||||
|
||||
$t->string('name');
|
||||
$t->string('provider');
|
||||
$t->boolean('visible')->default(true);
|
||||
});
|
||||
|
||||
Schema::create('users', function($t)
|
||||
{
|
||||
$t->increments('id');
|
||||
$t->unsignedInteger('account_id')->index();
|
||||
$t->timestamps();
|
||||
$t->softDeletes();
|
||||
|
||||
$t->string('first_name')->nullable();
|
||||
$t->string('last_name')->nullable();
|
||||
$t->string('phone')->nullable();
|
||||
$t->string('username')->unique();
|
||||
$t->string('email')->nullable();
|
||||
$t->string('password');
|
||||
$t->string('confirmation_code');
|
||||
$t->boolean('registered')->default(false);
|
||||
$t->boolean('confirmed')->default(false);
|
||||
$t->integer('theme_id')->nullable();
|
||||
|
||||
$t->boolean('notify_sent')->default(true);
|
||||
$t->boolean('notify_viewed')->default(false);
|
||||
$t->boolean('notify_paid')->default(true);
|
||||
|
||||
$t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
|
||||
|
||||
$t->unsignedInteger('public_id')->nullable();
|
||||
$t->unique( array('account_id','public_id') );
|
||||
});
|
||||
|
||||
Schema::create('account_gateways', function($t)
|
||||
{
|
||||
$t->increments('id');
|
||||
$t->unsignedInteger('account_id');
|
||||
$t->unsignedInteger('user_id');
|
||||
$t->unsignedInteger('gateway_id');
|
||||
$t->timestamps();
|
||||
$t->softDeletes();
|
||||
|
||||
$t->text('config');
|
||||
|
||||
$t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
|
||||
$t->foreign('gateway_id')->references('id')->on('gateways');
|
||||
$t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
|
||||
|
||||
$t->unsignedInteger('public_id')->index();
|
||||
$t->unique( array('account_id','public_id') );
|
||||
});
|
||||
|
||||
|
||||
Schema::create('password_reminders', function($t)
|
||||
{
|
||||
$t->string('email');
|
||||
$t->timestamps();
|
||||
|
||||
$t->string('token');
|
||||
});
|
||||
|
||||
Schema::create('clients', function($t)
|
||||
{
|
||||
$t->increments('id');
|
||||
$t->unsignedInteger('user_id');
|
||||
$t->unsignedInteger('account_id')->index();
|
||||
$t->unsignedInteger('currency_id')->nullable();
|
||||
$t->timestamps();
|
||||
$t->softDeletes();
|
||||
|
||||
$t->string('name')->nullable();
|
||||
$t->string('address1')->nullable();
|
||||
$t->string('address2')->nullable();
|
||||
$t->string('city')->nullable();
|
||||
$t->string('state')->nullable();
|
||||
$t->string('postal_code')->nullable();
|
||||
$t->unsignedInteger('country_id')->nullable();
|
||||
$t->string('work_phone')->nullable();
|
||||
$t->text('private_notes')->nullable();
|
||||
$t->decimal('balance', 13, 2)->nullable();
|
||||
$t->decimal('paid_to_date', 13, 2)->nullable();
|
||||
$t->timestamp('last_login')->nullable();
|
||||
$t->string('website')->nullable();
|
||||
$t->unsignedInteger('industry_id')->nullable();
|
||||
$t->unsignedInteger('size_id')->nullable();
|
||||
$t->boolean('is_deleted')->default(false);
|
||||
$t->integer('payment_terms')->nullable();
|
||||
|
||||
$t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
|
||||
$t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
|
||||
$t->foreign('country_id')->references('id')->on('countries');
|
||||
$t->foreign('industry_id')->references('id')->on('industries');
|
||||
$t->foreign('size_id')->references('id')->on('sizes');
|
||||
$t->foreign('currency_id')->references('id')->on('currencies');
|
||||
|
||||
$t->unsignedInteger('public_id')->index();
|
||||
$t->unique( array('account_id','public_id') );
|
||||
});
|
||||
|
||||
Schema::create('contacts', function($t)
|
||||
{
|
||||
$t->increments('id');
|
||||
$t->unsignedInteger('account_id');
|
||||
$t->unsignedInteger('user_id');
|
||||
$t->unsignedInteger('client_id')->index();
|
||||
$t->timestamps();
|
||||
$t->softDeletes();
|
||||
|
||||
$t->boolean('is_primary')->default(0);
|
||||
$t->boolean('send_invoice')->default(0);
|
||||
$t->string('first_name')->nullable();
|
||||
$t->string('last_name')->nullable();
|
||||
$t->string('email')->nullable();
|
||||
$t->string('phone')->nullable();
|
||||
$t->timestamp('last_login')->nullable();
|
||||
|
||||
$t->foreign('client_id')->references('id')->on('clients')->onDelete('cascade');
|
||||
$t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');;
|
||||
|
||||
$t->unsignedInteger('public_id')->nullable();
|
||||
$t->unique( array('account_id','public_id') );
|
||||
});
|
||||
|
||||
Schema::create('invoice_statuses', function($t)
|
||||
{
|
||||
$t->increments('id');
|
||||
$t->string('name');
|
||||
});
|
||||
|
||||
Schema::create('frequencies', function($t)
|
||||
{
|
||||
$t->increments('id');
|
||||
$t->string('name');
|
||||
});
|
||||
|
||||
Schema::create('invoices', function($t)
|
||||
{
|
||||
$t->increments('id');
|
||||
$t->unsignedInteger('client_id')->index();
|
||||
$t->unsignedInteger('user_id');
|
||||
$t->unsignedInteger('account_id')->index();
|
||||
$t->unsignedInteger('invoice_status_id')->default(1);
|
||||
$t->timestamps();
|
||||
$t->softDeletes();
|
||||
|
||||
$t->string('invoice_number');
|
||||
$t->float('discount');
|
||||
$t->string('po_number');
|
||||
$t->date('invoice_date')->nullable();
|
||||
$t->date('due_date')->nullable();
|
||||
$t->text('terms');
|
||||
$t->text('public_notes');
|
||||
$t->boolean('is_deleted')->default(false);
|
||||
$t->boolean('is_recurring');
|
||||
$t->unsignedInteger('frequency_id');
|
||||
$t->date('start_date')->nullable();
|
||||
$t->date('end_date')->nullable();
|
||||
$t->timestamp('last_sent_date')->nullable();
|
||||
$t->unsignedInteger('recurring_invoice_id')->index()->nullable();
|
||||
|
||||
$t->string('tax_name');
|
||||
$t->decimal('tax_rate', 13, 2);
|
||||
|
||||
$t->decimal('amount', 13, 2);
|
||||
$t->decimal('balance', 13, 2);
|
||||
|
||||
$t->foreign('client_id')->references('id')->on('clients')->onDelete('cascade');
|
||||
$t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
|
||||
$t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
|
||||
$t->foreign('invoice_status_id')->references('id')->on('invoice_statuses');
|
||||
$t->foreign('recurring_invoice_id')->references('id')->on('invoices')->onDelete('cascade');
|
||||
|
||||
$t->unsignedInteger('public_id')->index();
|
||||
$t->unique( array('account_id','public_id') );
|
||||
$t->unique( array('account_id','invoice_number') );
|
||||
});
|
||||
|
||||
|
||||
Schema::create('invitations', function($t)
|
||||
{
|
||||
$t->increments('id');
|
||||
$t->unsignedInteger('account_id');
|
||||
$t->unsignedInteger('user_id');
|
||||
$t->unsignedInteger('contact_id');
|
||||
$t->unsignedInteger('invoice_id')->index();
|
||||
$t->string('invitation_key')->index()->unique();
|
||||
$t->timestamps();
|
||||
$t->softDeletes();
|
||||
|
||||
$t->string('transaction_reference')->nullable();
|
||||
$t->timestamp('sent_date');
|
||||
$t->timestamp('viewed_date');
|
||||
|
||||
$t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');;
|
||||
$t->foreign('contact_id')->references('id')->on('contacts')->onDelete('cascade');
|
||||
$t->foreign('invoice_id')->references('id')->on('invoices')->onDelete('cascade');
|
||||
|
||||
$t->unsignedInteger('public_id')->index();
|
||||
$t->unique( array('account_id','public_id') );
|
||||
});
|
||||
|
||||
Schema::create('tax_rates', function($t)
|
||||
{
|
||||
$t->increments('id');
|
||||
$t->unsignedInteger('account_id')->index();
|
||||
$t->unsignedInteger('user_id');
|
||||
$t->timestamps();
|
||||
$t->softDeletes();
|
||||
|
||||
$t->string('name');
|
||||
$t->decimal('rate', 13, 2);
|
||||
|
||||
$t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
|
||||
$t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');;
|
||||
|
||||
$t->unsignedInteger('public_id');
|
||||
$t->unique( array('account_id','public_id') );
|
||||
});
|
||||
|
||||
Schema::create('products', function($t)
|
||||
{
|
||||
$t->increments('id');
|
||||
$t->unsignedInteger('account_id')->index();
|
||||
$t->unsignedInteger('user_id');
|
||||
$t->timestamps();
|
||||
$t->softDeletes();
|
||||
|
||||
$t->string('product_key');
|
||||
$t->text('notes');
|
||||
$t->decimal('cost', 13, 2);
|
||||
$t->decimal('qty', 13, 2)->nullable();
|
||||
|
||||
$t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
|
||||
$t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');;
|
||||
|
||||
$t->unsignedInteger('public_id');
|
||||
$t->unique( array('account_id','public_id') );
|
||||
});
|
||||
|
||||
|
||||
Schema::create('invoice_items', function($t)
|
||||
{
|
||||
$t->increments('id');
|
||||
$t->unsignedInteger('account_id');
|
||||
$t->unsignedInteger('user_id');
|
||||
$t->unsignedInteger('invoice_id')->index();
|
||||
$t->unsignedInteger('product_id')->nullable();
|
||||
$t->timestamps();
|
||||
$t->softDeletes();
|
||||
|
||||
$t->string('product_key');
|
||||
$t->text('notes');
|
||||
$t->decimal('cost', 13, 2);
|
||||
$t->decimal('qty', 13, 2)->nullable();
|
||||
|
||||
$t->string('tax_name')->nullable();
|
||||
$t->decimal('tax_rate', 13, 2)->nullable();
|
||||
|
||||
$t->foreign('invoice_id')->references('id')->on('invoices')->onDelete('cascade');
|
||||
$t->foreign('product_id')->references('id')->on('products')->onDelete('cascade');
|
||||
$t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');;
|
||||
|
||||
$t->unsignedInteger('public_id');
|
||||
$t->unique( array('account_id','public_id') );
|
||||
});
|
||||
|
||||
Schema::create('payments', function($t)
|
||||
{
|
||||
$t->increments('id');
|
||||
$t->unsignedInteger('invoice_id')->nullable();
|
||||
$t->unsignedInteger('account_id')->index();
|
||||
$t->unsignedInteger('client_id')->index();
|
||||
$t->unsignedInteger('contact_id')->nullable();
|
||||
$t->unsignedInteger('invitation_id')->nullable();
|
||||
$t->unsignedInteger('user_id')->nullable();
|
||||
$t->unsignedInteger('account_gateway_id')->nullable();
|
||||
$t->unsignedInteger('payment_type_id')->nullable();
|
||||
$t->timestamps();
|
||||
$t->softDeletes();
|
||||
|
||||
$t->boolean('is_deleted')->default(false);
|
||||
$t->decimal('amount', 13, 2);
|
||||
$t->date('payment_date')->nullable();
|
||||
$t->string('transaction_reference')->nullable();
|
||||
$t->string('payer_id')->nullable();
|
||||
|
||||
$t->foreign('invoice_id')->references('id')->on('invoices')->onDelete('cascade');
|
||||
$t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
|
||||
$t->foreign('client_id')->references('id')->on('clients')->onDelete('cascade');
|
||||
$t->foreign('contact_id')->references('id')->on('contacts')->onDelete('cascade');
|
||||
$t->foreign('account_gateway_id')->references('id')->on('account_gateways')->onDelete('cascade');
|
||||
$t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');;
|
||||
$t->foreign('payment_type_id')->references('id')->on('payment_types');
|
||||
|
||||
$t->unsignedInteger('public_id')->index();
|
||||
$t->unique( array('account_id','public_id') );
|
||||
});
|
||||
|
||||
Schema::create('credits', function($t)
|
||||
{
|
||||
$t->increments('id');
|
||||
$t->unsignedInteger('account_id')->index();
|
||||
$t->unsignedInteger('client_id')->index();
|
||||
$t->unsignedInteger('user_id');
|
||||
$t->timestamps();
|
||||
$t->softDeletes();
|
||||
|
||||
$t->boolean('is_deleted')->default(false);
|
||||
$t->decimal('amount', 13, 2);
|
||||
$t->decimal('balance', 13, 2);
|
||||
$t->date('credit_date')->nullable();
|
||||
$t->string('credit_number')->nullable();
|
||||
$t->text('private_notes');
|
||||
|
||||
$t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
|
||||
$t->foreign('client_id')->references('id')->on('clients')->onDelete('cascade');
|
||||
$t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');;
|
||||
|
||||
$t->unsignedInteger('public_id')->index();
|
||||
$t->unique( array('account_id','public_id') );
|
||||
});
|
||||
|
||||
Schema::create('activities', function($t)
|
||||
{
|
||||
$t->increments('id');
|
||||
$t->timestamps();
|
||||
|
||||
$t->unsignedInteger('account_id');
|
||||
$t->unsignedInteger('client_id');
|
||||
$t->unsignedInteger('user_id');
|
||||
$t->unsignedInteger('contact_id')->nullable();
|
||||
$t->unsignedInteger('payment_id')->nullable();
|
||||
$t->unsignedInteger('invoice_id')->nullable();
|
||||
$t->unsignedInteger('credit_id')->nullable();
|
||||
$t->unsignedInteger('invitation_id')->nullable();
|
||||
|
||||
$t->text('message')->nullable();
|
||||
$t->text('json_backup')->nullable();
|
||||
$t->integer('activity_type_id');
|
||||
$t->decimal('adjustment', 13, 2)->nullable();
|
||||
$t->decimal('balance', 13, 2)->nullable();
|
||||
|
||||
$t->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
|
||||
$t->foreign('client_id')->references('id')->on('clients')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('payment_terms');
|
||||
Schema::dropIfExists('themes');
|
||||
Schema::dropIfExists('credits');
|
||||
Schema::dropIfExists('activities');
|
||||
Schema::dropIfExists('invitations');
|
||||
Schema::dropIfExists('payments');
|
||||
Schema::dropIfExists('account_gateways');
|
||||
Schema::dropIfExists('invoice_items');
|
||||
Schema::dropIfExists('products');
|
||||
Schema::dropIfExists('tax_rates');
|
||||
Schema::dropIfExists('contacts');
|
||||
Schema::dropIfExists('invoices');
|
||||
Schema::dropIfExists('password_reminders');
|
||||
Schema::dropIfExists('clients');
|
||||
Schema::dropIfExists('users');
|
||||
Schema::dropIfExists('accounts');
|
||||
Schema::dropIfExists('currencies');
|
||||
Schema::dropIfExists('invoice_statuses');
|
||||
Schema::dropIfExists('countries');
|
||||
Schema::dropIfExists('timezones');
|
||||
Schema::dropIfExists('frequencies');
|
||||
Schema::dropIfExists('date_formats');
|
||||
Schema::dropIfExists('datetime_formats');
|
||||
Schema::dropIfExists('sizes');
|
||||
Schema::dropIfExists('industries');
|
||||
Schema::dropIfExists('gateways');
|
||||
Schema::dropIfExists('payment_types');
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class SetupCountriesTable extends Migration {
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
// Creates the users table
|
||||
/*
|
||||
Schema::create('countries', function($table)
|
||||
{
|
||||
$table->integer('id')->index();
|
||||
$table->string('capital', 255)->nullable();
|
||||
$table->string('citizenship', 255)->nullable();
|
||||
$table->string('country_code', 3)->default('');
|
||||
$table->string('currency', 255)->nullable();
|
||||
$table->string('currency_code', 255)->nullable();
|
||||
$table->string('currency_sub_unit', 255)->nullable();
|
||||
$table->string('full_name', 255)->nullable();
|
||||
$table->string('iso_3166_2', 2)->default('');
|
||||
$table->string('iso_3166_3', 3)->default('');
|
||||
$table->string('name', 255)->default('');
|
||||
$table->string('region_code', 3)->default('');
|
||||
$table->string('sub_region_code', 3)->default('');
|
||||
$table->boolean('eea')->default(0);
|
||||
|
||||
$table->primary('id');
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//Schema::drop('countries');
|
||||
}
|
||||
|
||||
}
|
32
database/migrations/2014_02_13_151500_add_cascase_drops.php
Normal file
32
database/migrations/2014_02_13_151500_add_cascase_drops.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddCascaseDrops extends Migration {
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('invoices', function($table)
|
||||
{
|
||||
$table->dropForeign('invoices_account_id_foreign');
|
||||
$table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddSupportForInvoiceDesigns extends Migration {
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('invoice_designs', function($table)
|
||||
{
|
||||
$table->increments('id');
|
||||
$table->string('name');
|
||||
});
|
||||
|
||||
DB::table('invoice_designs')->insert(['name' => 'Clean']);
|
||||
DB::table('invoice_designs')->insert(['name' => 'Bold']);
|
||||
DB::table('invoice_designs')->insert(['name' => 'Modern']);
|
||||
DB::table('invoice_designs')->insert(['name' => 'Plain']);
|
||||
|
||||
Schema::table('invoices', function($table)
|
||||
{
|
||||
$table->unsignedInteger('invoice_design_id')->default(1);
|
||||
});
|
||||
|
||||
Schema::table('accounts', function($table)
|
||||
{
|
||||
$table->unsignedInteger('invoice_design_id')->default(1);
|
||||
});
|
||||
|
||||
DB::table('invoices')->update(['invoice_design_id' => 1]);
|
||||
DB::table('accounts')->update(['invoice_design_id' => 1]);
|
||||
|
||||
Schema::table('invoices', function($table)
|
||||
{
|
||||
$table->foreign('invoice_design_id')->references('id')->on('invoice_designs');
|
||||
});
|
||||
|
||||
Schema::table('accounts', function($table)
|
||||
{
|
||||
$table->foreign('invoice_design_id')->references('id')->on('invoice_designs');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('invoices', function($table)
|
||||
{
|
||||
$table->dropForeign('invoices_invoice_design_id_foreign');
|
||||
$table->dropColumn('invoice_design_id');
|
||||
});
|
||||
|
||||
Schema::table('accounts', function($table)
|
||||
{
|
||||
$table->dropForeign('accounts_invoice_design_id_foreign');
|
||||
$table->dropColumn('invoice_design_id');
|
||||
});
|
||||
|
||||
Schema::dropIfExists('invoice_designs');
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddPhoneToAccount extends Migration {
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('accounts', function($table)
|
||||
{
|
||||
$table->string('work_phone')->nullable();
|
||||
$table->string('work_email')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('accounts', function($table)
|
||||
{
|
||||
$table->dropColumn('work_phone');
|
||||
$table->dropColumn('work_email');
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddLanguageSupport extends Migration {
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('languages', function($table)
|
||||
{
|
||||
$table->increments('id');
|
||||
$table->string('name');
|
||||
$table->string('locale');
|
||||
});
|
||||
|
||||
DB::table('languages')->insert(['name' => 'English', 'locale' => 'en']);
|
||||
DB::table('languages')->insert(['name' => 'Italian', 'locale' => 'it']);
|
||||
DB::table('languages')->insert(['name' => 'German', 'locale' => 'de']);
|
||||
DB::table('languages')->insert(['name' => 'French', 'locale' => 'fr']);
|
||||
DB::table('languages')->insert(['name' => 'Brazilian Portuguese', 'locale' => 'pt_BR']);
|
||||
DB::table('languages')->insert(['name' => 'Dutch', 'locale' => 'nl']);
|
||||
DB::table('languages')->insert(['name' => 'Spanish', 'locale' => 'es']);
|
||||
DB::table('languages')->insert(['name' => 'Norwegian', 'locale' => 'nb_NO']);
|
||||
|
||||
Schema::table('accounts', function($table)
|
||||
{
|
||||
$table->unsignedInteger('language_id')->default(1);
|
||||
});
|
||||
|
||||
DB::table('accounts')->update(['language_id' => 1]);
|
||||
|
||||
Schema::table('accounts', function($table)
|
||||
{
|
||||
$table->foreign('language_id')->references('id')->on('languages');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('accounts', function($table)
|
||||
{
|
||||
$table->dropForeign('accounts_language_id_foreign');
|
||||
$table->dropColumn('language_id');
|
||||
});
|
||||
|
||||
Schema::drop('languages');
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user