mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-09-19 16:01:34 +02:00
Minor fixes
This commit is contained in:
commit
e8dad88d15
38
Gruntfile.js
38
Gruntfile.js
@ -5,9 +5,37 @@ module.exports = function(grunt) {
|
||||
concat: {
|
||||
options: {
|
||||
process: function(src, filepath) {
|
||||
// Fix path for image and font resources
|
||||
var basepath = filepath.substring(7, filepath.lastIndexOf('/') + 1);
|
||||
|
||||
console.log(filepath);
|
||||
// Fix relative paths for css files
|
||||
if(filepath.indexOf('.css', filepath.length - 4) !== -1) {
|
||||
return src.replace(/\.\.\/(images|fonts)\//g, '$1/');
|
||||
return src.replace(/(url\s*[\("']+)\s*([^'"\)]+)(['"\)]+;?)/gi, function(match, start, url, end, offset, string) {
|
||||
if(url.indexOf('data:') === 0) {
|
||||
// Skip data urls
|
||||
return match;
|
||||
|
||||
} else if(url.indexOf('/') === 0) {
|
||||
// Skip absolute urls
|
||||
return match;
|
||||
|
||||
} else {
|
||||
return start + basepath + url + end;
|
||||
}
|
||||
});
|
||||
|
||||
// Fix source maps locations
|
||||
} else if(filepath.indexOf('.js', filepath.length - 4) !== -1) {
|
||||
return src.replace(/(\/[*\/][#@]\s*sourceMappingURL=)([^\s]+)/gi, function(match, start, url, offset, string) {
|
||||
if(url.indexOf('/') === 0) {
|
||||
// Skip absolute urls
|
||||
return match;
|
||||
|
||||
} else {
|
||||
return start + basepath + url;
|
||||
}
|
||||
});
|
||||
|
||||
// Don't do anything for unknown file types
|
||||
} else {
|
||||
return src;
|
||||
@ -17,7 +45,7 @@ module.exports = function(grunt) {
|
||||
js: {
|
||||
src: [
|
||||
'public/vendor/jquery/dist/jquery.js',
|
||||
'public/vendor/jquery-ui/ui/jquery-ui.js',
|
||||
'public/vendor/jquery-ui/jquery-ui.min.js',
|
||||
'public/vendor/bootstrap/dist/js/bootstrap.min.js',
|
||||
'public/vendor/datatables/media/js/jquery.dataTables.js',
|
||||
'public/vendor/datatables-bootstrap3/BS3/assets/js/datatables.js',
|
||||
@ -32,6 +60,7 @@ module.exports = function(grunt) {
|
||||
'public/js/bootstrap-combobox.js',
|
||||
'public/vendor/jspdf/dist/jspdf.min.js',
|
||||
'public/vendor/lightbox2/js/lightbox.min.js',
|
||||
'public/vendor/handsontable/dist/jquery.handsontable.full.min.js',
|
||||
'public/js/script.js',
|
||||
],
|
||||
dest: 'public/built.js',
|
||||
@ -42,7 +71,7 @@ module.exports = function(grunt) {
|
||||
'public/js/simpleexpand.js',
|
||||
'public/js/valign.js',
|
||||
'public/js/bootstrap.min.js',
|
||||
'public/js/simpleexpand.js',
|
||||
'public/js/simpleexpand.js',
|
||||
],
|
||||
dest: 'public/js/built.public.js',
|
||||
nonull: true
|
||||
@ -58,6 +87,7 @@ module.exports = function(grunt) {
|
||||
'public/css/bootstrap-combobox.css',
|
||||
'public/css/typeahead.js-bootstrap.css',
|
||||
'public/vendor/lightbox2/css/lightbox.css',
|
||||
'public/vendor/handsontable/dist/jquery.handsontable.full.css',
|
||||
'public/css/style.css',
|
||||
],
|
||||
dest: 'public/built.css',
|
||||
|
@ -89,6 +89,9 @@ Add public/ to your web server root
|
||||
sudo npm install -g bower
|
||||
sudo ln -s /usr/local/lib/node_modules/bower/bin/bower /usr/local/bin/bower
|
||||
|
||||
# Install Grunt (For development only)
|
||||
npm install -g grunt-cli
|
||||
|
||||
### Frameworks/Libraries
|
||||
* [laravel/laravel](https://github.com/laravel/laravel) - A PHP Framework For Web Artisans
|
||||
* [twbs/bootstrap](https://github.com/twbs/bootstrap) - Sleek, intuitive, and powerful front-end framework for faster and easier web development.
|
||||
|
14351
_ide_helper.php
Normal file
14351
_ide_helper.php
Normal file
File diff suppressed because it is too large
Load Diff
1239
_ide_helper_models.php
Normal file
1239
_ide_helper_models.php
Normal file
File diff suppressed because it is too large
Load Diff
@ -23,16 +23,16 @@ class CreateRandomData extends Command {
|
||||
$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'];
|
||||
|
||||
for ($i=1; $i<=40; $i++) {
|
||||
foreach ($productNames as $i => $value) {
|
||||
$product = Product::createNew($user);
|
||||
$product->id = $i;
|
||||
$product->product_key = $productNames[$i-1];
|
||||
$product->id = $i+1;
|
||||
$product->product_key = $value;
|
||||
$product->save();
|
||||
}
|
||||
|
||||
for ($i=0; $i<60; $i++) {
|
||||
foreach ($clientNames as $i => $value) {
|
||||
$client = Client::createNew($user);
|
||||
$client->name = $clientNames[$i];
|
||||
$client->name = $value;
|
||||
$client->save();
|
||||
|
||||
$contact = Contact::createNew($user);
|
||||
|
335
app/commands/ImportTimesheetData.php
Normal file
335
app/commands/ImportTimesheetData.php
Normal file
@ -0,0 +1,335 @@
|
||||
<?php
|
||||
|
||||
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(
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -122,7 +122,8 @@ return array(
|
||||
'Barryvdh\Debugbar\ServiceProvider',
|
||||
'Chumper\Datatable\DatatableServiceProvider',
|
||||
'Intervention\Image\ImageServiceProvider',
|
||||
'Webpatser\Countries\CountriesServiceProvider'
|
||||
'Webpatser\Countries\CountriesServiceProvider',
|
||||
'Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider',
|
||||
),
|
||||
|
||||
/*
|
||||
|
@ -818,6 +818,7 @@ class AccountController extends \BaseController {
|
||||
{
|
||||
$account = Auth::user()->account;
|
||||
$account->name = trim(Input::get('name'));
|
||||
$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'));
|
||||
|
@ -199,6 +199,7 @@ class ClientController extends \BaseController {
|
||||
}
|
||||
|
||||
$client->name = trim(Input::get('name'));
|
||||
$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'));
|
||||
|
93
app/controllers/TimesheetController.php
Normal file
93
app/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)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddCompanyVatNumber extends Migration {
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('accounts', function($table)
|
||||
{
|
||||
$table->string('vat_number')->nullable();
|
||||
});
|
||||
|
||||
Schema::table('clients', function($table)
|
||||
{
|
||||
$table->string('vat_number')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('accounts', function($table)
|
||||
{
|
||||
$table->dropColumn('vat_number');
|
||||
});
|
||||
Schema::table('clients', function($table)
|
||||
{
|
||||
$table->dropColumn('vat_number');
|
||||
});
|
||||
}
|
||||
|
||||
}
|
152
app/database/migrations/2014_10_06_103529_add_timesheets.php
Normal file
152
app/database/migrations/2014_10_06_103529_add_timesheets.php
Normal file
@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddTimesheets extends Migration {
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('projects', function($t) {
|
||||
$t->increments('id');
|
||||
$t->unsignedInteger('user_id');
|
||||
$t->unsignedInteger('account_id')->index();
|
||||
$t->unsignedInteger('client_id')->nullable();
|
||||
$t->timestamps();
|
||||
$t->softDeletes();
|
||||
|
||||
$t->string('name');
|
||||
$t->string('description');
|
||||
|
||||
$t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
|
||||
$t->foreign('account_id')->references('id')->on('accounts');
|
||||
|
||||
$t->unique( array('account_id','name') );
|
||||
});
|
||||
|
||||
Schema::create('project_codes', function($t) {
|
||||
$t->increments('id');
|
||||
$t->unsignedInteger('user_id');
|
||||
$t->unsignedInteger('account_id')->index();
|
||||
$t->unsignedInteger('project_id');
|
||||
$t->timestamps();
|
||||
$t->softDeletes();
|
||||
|
||||
$t->string('name');
|
||||
$t->string('description');
|
||||
|
||||
$t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
|
||||
$t->foreign('account_id')->references('id')->on('accounts');
|
||||
$t->foreign('project_id')->references('id')->on('projects')->onDelete('cascade');
|
||||
|
||||
$t->unique( array('account_id','name') );
|
||||
});
|
||||
|
||||
|
||||
Schema::create('timesheets', function($t) {
|
||||
$t->increments('id');
|
||||
$t->unsignedInteger('user_id');
|
||||
$t->unsignedInteger('account_id')->index();
|
||||
$t->timestamps();
|
||||
$t->softDeletes();
|
||||
|
||||
$t->dateTime('start_date');
|
||||
$t->dateTime('end_date');
|
||||
$t->float('discount');
|
||||
|
||||
$t->decimal('hours');
|
||||
|
||||
$t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
|
||||
$t->foreign('account_id')->references('id')->on('accounts');
|
||||
|
||||
$t->unsignedInteger('public_id');
|
||||
$t->unique( array('account_id','public_id') );
|
||||
});
|
||||
|
||||
Schema::create('timesheet_event_sources', function($t) {
|
||||
$t->increments('id');
|
||||
$t->unsignedInteger('user_id');
|
||||
$t->unsignedInteger('account_id')->index();
|
||||
$t->timestamps();
|
||||
$t->softDeletes();
|
||||
|
||||
$t->string('owner');
|
||||
$t->string('name');
|
||||
$t->string('url');
|
||||
$t->enum('type', array('ical', 'googlejson'));
|
||||
|
||||
$t->dateTime('from_date')->nullable();
|
||||
$t->dateTime('to_date')->nullable();
|
||||
|
||||
$t->foreign('account_id')->references('id')->on('accounts');
|
||||
$t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
|
||||
});
|
||||
|
||||
Schema::create('timesheet_events', function($t) {
|
||||
$t->increments('id');
|
||||
$t->unsignedInteger('user_id');
|
||||
$t->unsignedInteger('account_id')->index();
|
||||
$t->unsignedInteger('timesheet_event_source_id');
|
||||
$t->unsignedInteger('timesheet_id')->nullable()->index();
|
||||
$t->unsignedInteger('project_id')->nullable()->index();
|
||||
$t->unsignedInteger('project_code_id')->nullable()->index();
|
||||
$t->timestamps();
|
||||
$t->softDeletes();
|
||||
|
||||
// Basic fields
|
||||
$t->string('uid');
|
||||
$t->string('summary');
|
||||
$t->text('description');
|
||||
$t->string('location');
|
||||
$t->string('owner');
|
||||
$t->dateTime('start_date');
|
||||
$t->dateTime('end_date');
|
||||
|
||||
# Calculated values
|
||||
$t->decimal('hours');
|
||||
$t->float('discount');
|
||||
$t->boolean('manualedit');
|
||||
|
||||
// Original data
|
||||
$t->string('org_code');
|
||||
$t->timeStamp('org_created_at');
|
||||
$t->timeStamp('org_updated_at');
|
||||
$t->timeStamp('org_deleted_at')->default('0000-00-00T00:00:00');
|
||||
$t->string('org_start_date_timezone')->nullable();
|
||||
$t->string('org_end_date_timezone')->nullable();
|
||||
$t->text('org_data');
|
||||
|
||||
// Error and merge handling
|
||||
$t->string('import_error')->nullable();
|
||||
$t->string('import_warning')->nullable();
|
||||
$t->text('updated_data')->nullable();
|
||||
$t->timeStamp('updated_data_at')->default('0000-00-00T00:00:00');
|
||||
|
||||
$t->foreign('account_id')->references('id')->on('accounts');
|
||||
$t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
|
||||
$t->foreign('timesheet_event_source_id')->references('id')->on('timesheet_event_sources')->onDelete('cascade');
|
||||
|
||||
$t->unique( array('timesheet_event_source_id', 'uid') );
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::drop('timesheet_events');
|
||||
Schema::drop('timesheet_event_sources');
|
||||
Schema::drop('timesheets');
|
||||
Schema::drop('project_codes');
|
||||
Schema::drop('projects');
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddDanishTranslation extends Migration {
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
DB::table('languages')->insert(['name' => 'Danish', 'locale' => 'da']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
$language = Language::whereLocale('da')->first();
|
||||
$language->delete();
|
||||
}
|
||||
|
||||
}
|
20
app/lang/da/pagination.php
Normal file
20
app/lang/da/pagination.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
return array(
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Pagination Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines are used by the paginator library to build
|
||||
| the simple pagination links. You are free to change them to anything
|
||||
| you want to customize your views to better match your application.
|
||||
|
|
||||
*/
|
||||
|
||||
'previous' => '« Forrige',
|
||||
|
||||
'next' => 'Næste »',
|
||||
|
||||
);
|
26
app/lang/da/reminders.php
Normal file
26
app/lang/da/reminders.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
return array(
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Password Reminder Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines are the default lines which match reasons
|
||||
| that are given by the password broker for a password update attempt
|
||||
| has failed, such as for an invalid token or invalid new password.
|
||||
|
|
||||
*/
|
||||
|
||||
"password" => "Passwords skal minimum være 6 tegn og matche sikkerhedstjekket.",
|
||||
|
||||
"user" => "Vi kan ikke finde en bruger med den email-adresse.",
|
||||
|
||||
"token" => "Password-nulstillingskoden er ugyldig.",
|
||||
|
||||
"sent" => "Password-påmindelse sendt!",
|
||||
|
||||
"reset" => "Password has been reset!",
|
||||
|
||||
);
|
447
app/lang/da/texts.php
Normal file
447
app/lang/da/texts.php
Normal file
@ -0,0 +1,447 @@
|
||||
<?php
|
||||
|
||||
return array(
|
||||
|
||||
// client
|
||||
'organization' => 'Organisation',
|
||||
'name' => 'Navn',
|
||||
'vat_number' => 'CVR nummer',
|
||||
'website' => 'Webside',
|
||||
'work_phone' => 'Telefon',
|
||||
'address' => 'Adresse',
|
||||
'address1' => 'Gade',
|
||||
'address2' => 'Nummer',
|
||||
'city' => 'By',
|
||||
'state' => 'Område',
|
||||
'postal_code' => 'Postnummer',
|
||||
'country_id' => 'Land',
|
||||
'contacts' => 'Kontakter',
|
||||
'first_name' => 'Fornavn',
|
||||
'last_name' => 'Efternavn',
|
||||
'phone' => 'Telefon',
|
||||
'email' => 'E-mail',
|
||||
'additional_info' => 'Ekstra information',
|
||||
'payment_terms' => 'Betalingsvilkår',
|
||||
'currency_id' => 'Valuta',
|
||||
'size_id' => 'Størrelse',
|
||||
'industry_id' => 'Sektor',
|
||||
'private_notes' => 'Private notater',
|
||||
|
||||
// invoice
|
||||
'invoice' => 'Faktura',
|
||||
'client' => 'Klient',
|
||||
'invoice_date' => 'Faktureringsdato',
|
||||
'due_date' => 'Betalingsfrist',
|
||||
'invoice_number' => 'Fakturanummer',
|
||||
'invoice_number_short' => 'Faktura #',
|
||||
'po_number' => 'Ordrenummer',
|
||||
'po_number_short' => 'Ordre #',
|
||||
'frequency_id' => 'Frekvens',
|
||||
'discount' => 'Rabat',
|
||||
'taxes' => 'Skatter',
|
||||
'tax' => 'Skat',
|
||||
'item' => 'Produkttype',
|
||||
'description' => 'Beskrivelse',
|
||||
'unit_cost' => 'Pris',
|
||||
'quantity' => 'Stk.',
|
||||
'line_total' => 'Sum',
|
||||
'subtotal' => 'Subtotal',
|
||||
'paid_to_date' => 'Betalt',
|
||||
'balance_due' => 'Udestående beløb',
|
||||
'invoice_design_id' => 'Design',
|
||||
'terms' => 'Vilkår',
|
||||
'your_invoice' => 'Din faktura',
|
||||
|
||||
'remove_contact' => 'Fjern kontakt',
|
||||
'add_contact' => 'Tilføj til kontakt',
|
||||
'create_new_client' => 'Opret ny klient',
|
||||
'edit_client_details' => 'Ændre klientdetaljer',
|
||||
'enable' => 'Aktiver',
|
||||
'learn_more' => 'Lær mere',
|
||||
'manage_rates' => 'Administrer priser',
|
||||
'note_to_client' => 'Note til klient',
|
||||
'invoice_terms' => 'Vilkår for fakturaen',
|
||||
'save_as_default_terms' => 'Gem som standard vilkår',
|
||||
'download_pdf' => 'Download PDF',
|
||||
'pay_now' => 'Betal nu',
|
||||
'save_invoice' => 'Gem faktura',
|
||||
'clone_invoice' => 'Kopier faktura',
|
||||
'archive_invoice' => 'Arkiver faktura',
|
||||
'delete_invoice' => 'Slet faktura',
|
||||
'email_invoice' => 'Send faktura som e-mail',
|
||||
'enter_payment' => 'Tilføj betaling',
|
||||
'tax_rates' => 'Skattesatser',
|
||||
'rate' => 'Sats',
|
||||
'settings' => 'Indstillinger',
|
||||
'enable_invoice_tax' => 'Aktiver for at specificere en <b>faktura skat</b>',
|
||||
'enable_line_item_tax' => 'Aktiver for at specificere per <b>faktura linje skat</b>',
|
||||
|
||||
// navigation
|
||||
'dashboard' => 'Dashboard',
|
||||
'clients' => 'Klienter',
|
||||
'invoices' => 'Fakturaer',
|
||||
'payments' => 'Betalinger',
|
||||
'credits' => 'Kreditter',
|
||||
'history' => 'Historie',
|
||||
'search' => 'Søg',
|
||||
'sign_up' => 'Registrer dig',
|
||||
'guest' => 'Gjest',
|
||||
'company_details' => 'Firmainformation',
|
||||
'online_payments' => 'Online betaling',
|
||||
'notifications' => 'Varsler',
|
||||
'import_export' => 'Import/Export',
|
||||
'done' => 'Færdig',
|
||||
'save' => 'Gem',
|
||||
'create' => 'Opret',
|
||||
'upload' => 'Upload',
|
||||
'import' => 'Importer',
|
||||
'download' => 'Download',
|
||||
'cancel' => 'Afbrud',
|
||||
'close' => 'Luk',
|
||||
'provide_email' => 'Venligst opgiv en gyldig e-mail',
|
||||
'powered_by' => 'Drevet af',
|
||||
'no_items' => 'Ingen elementer',
|
||||
|
||||
// recurring invoices
|
||||
'recurring_invoices' => 'Gentagende fakturaer',
|
||||
'recurring_help' => '<p>Automatisk send klienter de samme fakturaene ugentlig, bi-månedlig, månedlig, kvartalsvis eller årlig.</p>
|
||||
<p>Brug :MONTH, :QUARTER eller :YEAR for dynamiske datoer. Grundliggende matematik fungerer også, for eksempel :MONTH-1.</p>
|
||||
<p>Eksempler på dynamiske faktura variabler:</p>
|
||||
<ul>
|
||||
<li>"Træningsmedlemskab for måneden :MONTH" => "Træningsmedlemskab for måneden Juli"</li>
|
||||
<li>":YEAR+1 årlig abonnement" => "2015 årlig abonnement"</li>
|
||||
<li>"Forudbetaling for :QUARTER+1" => "Forudbetaling for Q2"</li>
|
||||
</ul>',
|
||||
|
||||
// dashboard
|
||||
'in_total_revenue' => 'totale indtægter',
|
||||
'billed_client' => 'faktureret klient',
|
||||
'billed_clients' => 'fakturerede klienter',
|
||||
'active_client' => 'aktiv klient',
|
||||
'active_clients' => 'aktive klienter',
|
||||
'invoices_past_due' => 'Forfaldte fakturaer',
|
||||
'upcoming_invoices' => 'Forestående fakturaer',
|
||||
'average_invoice' => 'Gennemsnitlige fakturaer',
|
||||
|
||||
// list pages
|
||||
'archive' => 'Arkiv',
|
||||
'delete' => 'Slet',
|
||||
'archive_client' => 'Arkiver klient',
|
||||
'delete_client' => 'Slet klient',
|
||||
'archive_payment' => 'Arkiver betaling',
|
||||
'delete_payment' => 'Slet betaling',
|
||||
'archive_credit' => 'Arkiver kredit',
|
||||
'delete_credit' => 'Slet kredit',
|
||||
'show_archived_deleted' => 'Vis slettet/arkiverede',
|
||||
'filter' => 'Filter',
|
||||
'new_client' => 'Ny klient',
|
||||
'new_invoice' => 'Ny faktura',
|
||||
'new_payment' => 'Ny betaling',
|
||||
'new_credit' => 'Ny kredit',
|
||||
'contact' => 'Kontakt',
|
||||
'date_created' => 'Dato oprette',
|
||||
'last_login' => 'Sidst på-logging',
|
||||
'balance' => 'Balanse',
|
||||
'action' => 'Handling',
|
||||
'status' => 'Status',
|
||||
'invoice_total' => 'Faktura total',
|
||||
'frequency' => 'Frekvens',
|
||||
'start_date' => 'Startdato',
|
||||
'end_date' => 'Slutdato',
|
||||
'transaction_reference' => 'Transaktionsreference',
|
||||
'method' => 'Betalingsmåde',
|
||||
'payment_amount' => 'Beløb',
|
||||
'payment_date' => 'Betalingsdato',
|
||||
'credit_amount' => 'Kreditbeløb',
|
||||
'credit_balance' => 'Kreditsaldo',
|
||||
'credit_date' => 'Kreditdato',
|
||||
'empty_table' => 'Ingen data er tilgængelige i tabellen',
|
||||
'select' => 'Vælg',
|
||||
'edit_client' => 'Rediger klient',
|
||||
'edit_invoice' => 'Rediger faktura',
|
||||
|
||||
// client view page
|
||||
'create_invoice' => 'Lag faktura',
|
||||
'enter_credit' => 'Tilføj kredit',
|
||||
'last_logged_in' => 'Sidst på-logget',
|
||||
'details' => 'Detaljer',
|
||||
'standing' => 'Stående',
|
||||
'credit' => 'Kredit',
|
||||
'activity' => 'Aktivitet',
|
||||
'date' => 'Dato',
|
||||
'message' => 'Besked',
|
||||
'adjustment' => 'Justering',
|
||||
'are_you_sure' => 'Er du sikker?',
|
||||
|
||||
// payment pages
|
||||
'payment_type_id' => 'Betalingsmetode',
|
||||
'amount' => 'Beløb',
|
||||
|
||||
// account/company pages
|
||||
'work_email' => 'E-mail',
|
||||
'language_id' => 'Sprog',
|
||||
'timezone_id' => 'Tidszone',
|
||||
'date_format_id' => 'Dato format',
|
||||
'datetime_format_id' => 'Dato/Tidsformat',
|
||||
'users' => 'Brugere',
|
||||
'localization' => 'Lokalisering',
|
||||
'remove_logo' => 'Fjern logo',
|
||||
'logo_help' => 'Understøttede filtyper: JPEG, GIF og PNG. Anbefalet størrelse: 200px brede og 120px højde',
|
||||
'payment_gateway' => 'Betalingsløsning',
|
||||
'gateway_id' => 'Tilbyder',
|
||||
'email_notifications' => 'Varsel via e-mail',
|
||||
'email_sent' => 'Varsle når en faktura er <b>sendt</b>',
|
||||
'email_viewed' => 'Varsle når en faktura er <b>set</b>',
|
||||
'email_paid' => 'Varsle når en faktura er <b>betalt</b>',
|
||||
'site_updates' => 'Webside opdateringer',
|
||||
'custom_messages' => 'Tilpassede meldinger',
|
||||
'default_invoice_terms' => 'Sæt standard fakturavilkår',
|
||||
'default_email_footer' => 'Sæt standard e-mailsignatur',
|
||||
'import_clients' => 'Importer klientdata',
|
||||
'csv_file' => 'Vælg CSV-fil',
|
||||
'export_clients' => 'Eksporter klientdata',
|
||||
'select_file' => 'Venligst vælg en fil',
|
||||
'first_row_headers' => 'Brug første række som overskrifter',
|
||||
'column' => 'Kolonne',
|
||||
'sample' => 'Eksempel',
|
||||
'import_to' => 'Importer til',
|
||||
'client_will_create' => 'Klient vil blive oprettet',
|
||||
'clients_will_create' => 'Klienter vil blive oprettet',
|
||||
|
||||
// application messages
|
||||
'created_client' => 'Klient oprettet succesfuldt',
|
||||
'created_clients' => 'Klienter oprettet succesfuldt',
|
||||
'updated_settings' => 'Indstiller opdateret',
|
||||
'removed_logo' => 'Logo fjernet',
|
||||
'sent_message' => 'Melding sendt',
|
||||
'invoice_error' => 'Venligst vælge en klient og rette eventuelle fejl',
|
||||
'limit_clients' => 'Desværre, dette vil overstige græsen på :count klienter',
|
||||
'payment_error' => 'Det opstod en fejl under din betaling. Venligst prøv igen senere.',
|
||||
'registration_required' => 'Venligst registrer dig for at sende e-mailfaktura',
|
||||
'confirmation_required' => 'Venligst bekræft din e-mail',
|
||||
|
||||
'updated_client' => 'Klient opdateret',
|
||||
'created_client' => 'Klient lagret',
|
||||
'archived_client' => 'Klient arkiveret',
|
||||
'archived_clients' => 'Arkiverede :count klienter',
|
||||
'deleted_client' => 'Klient slettet',
|
||||
'deleted_clients' => 'Slettet :count klienter',
|
||||
|
||||
'updated_invoice' => 'Faktura opdateret',
|
||||
'created_invoice' => 'Faktura oprettet',
|
||||
'cloned_invoice' => 'Faktura kopieret',
|
||||
'emailed_invoice' => 'E-mailfaktura sendt',
|
||||
'and_created_client' => 'og klient oprettet',
|
||||
'archived_invoice' => 'Faktura arkiveret',
|
||||
'archived_invoices' => 'Arkiverede :count fakturaer',
|
||||
'deleted_invoice' => 'Faktura slettet',
|
||||
'deleted_invoices' => 'Slettet :count fakturaer',
|
||||
|
||||
'created_payment' => 'Betaling oprettet',
|
||||
'archived_payment' => 'Betaling arkiveret',
|
||||
'archived_payments' => 'Arkiverede :count betalinger',
|
||||
'deleted_payment' => 'Betaling slettet',
|
||||
'deleted_payments' => 'Slettet :count betalinger',
|
||||
'applied_payment' => 'Betaling lagret',
|
||||
|
||||
'created_credit' => 'Kredit oprettet',
|
||||
'archived_credit' => 'Kredit arkiveret',
|
||||
'archived_credits' => 'Arkiverede :count kreditter',
|
||||
'deleted_credit' => 'Kredit slettet',
|
||||
'deleted_credits' => 'Slettet :count kreditter',
|
||||
|
||||
// Emails
|
||||
'confirmation_subject' => 'Invoice Ninja kontobekræftelse',
|
||||
'confirmation_header' => 'Kontobekræftelse',
|
||||
'confirmation_message' => 'Venligst åbne linket nedenfor for å bekræfte din konto.',
|
||||
'invoice_subject' => 'Ny faktura fra :account',
|
||||
'invoice_message' => 'For at se din faktura på :amount, klik på linket nedenfor.',
|
||||
'payment_subject' => 'Betaling modtaget',
|
||||
'payment_message' => 'Tak for din betaling pålydende :amount.',
|
||||
'email_salutation' => 'Kære :name,',
|
||||
'email_signature' => 'Med venlig hilsen,',
|
||||
'email_from' => 'The InvoiceNinja Team',
|
||||
'user_email_footer' => 'For at justere varslingsindstillingene venligst besøg '.SITE_URL.'/company/notifications',
|
||||
'invoice_link_message' => 'Hvis du vil se din klientfaktura klik på linket under:',
|
||||
'notification_invoice_paid_subject' => 'Faktura :invoice betalt af :client',
|
||||
'notification_invoice_sent_subject' => 'Faktura :invoice sendt til :client',
|
||||
'notification_invoice_viewed_subject' => 'Faktura :invoice set av :client',
|
||||
'notification_invoice_paid' => 'En betaling pålydende :amount blev gjort af :client for faktura :invoice.',
|
||||
'notification_invoice_sent' => 'E-mail er blevet sendt til :client - Faktura :invoice pålydende :amount.',
|
||||
'notification_invoice_viewed' => ':client har set faktura :invoice pålydende :amount.',
|
||||
'reset_password' => 'Du kan nulstille din adgangskode ved at besøge følgende link:',
|
||||
'reset_password_footer' => 'Hvis du ikke bad om at få nulstillet din adgangskode venligst kontakt kundeservice: ' . CONTACT_EMAIL,
|
||||
|
||||
|
||||
// Payment page
|
||||
'secure_payment' => 'Sikker betaling',
|
||||
'card_number' => 'Kortnummer',
|
||||
'expiration_month' => 'Udløbsdato',
|
||||
'expiration_year' => 'Udløbsår',
|
||||
'cvv' => 'CVV',
|
||||
|
||||
// Security alerts
|
||||
'confide' => [
|
||||
'too_many_attempts' => 'For mange forsøg. Prøv igen om nogen få minutter.',
|
||||
'wrong_credentials' => 'Fejl e-mail eller adgangskode.',
|
||||
'confirmation' => 'Din konto har blevet bekræftet!',
|
||||
'wrong_confirmation' => 'Fejl bekræftelseskode.',
|
||||
'password_forgot' => 'Informationen om nulstilling af din adgangskode er blevet sendt til din e-mail.',
|
||||
'password_reset' => 'Adgangskode ændret',
|
||||
'wrong_password_reset' => 'Ugyldig adgangskode. Prøv på ny',
|
||||
],
|
||||
|
||||
// Pro Plan
|
||||
'pro_plan' => [
|
||||
'remove_logo' => ':link for å fjerne Invoice Ninja-logoen, oppgrader til en Pro Plan',
|
||||
'remove_logo_link' => 'Klik her',
|
||||
],
|
||||
|
||||
'logout' => 'Log ud',
|
||||
'sign_up_to_save' => 'Registrer dig for at gemme dit arbejde',
|
||||
'agree_to_terms' =>'Jeg acceptere Invoice Ninja :terms',
|
||||
'terms_of_service' => 'vilkår for brug',
|
||||
'email_taken' => 'E-mailadressen er allerede registreret',
|
||||
'working' => 'Arbejder',
|
||||
'success' => 'Succes',
|
||||
|
||||
'success_message' => 'Du er blevet registreret. Venligst gå ind på linket som du har modtaget i e-mailbekræftelsen for at bekræfte e-mailaddressen.',
|
||||
'erase_data' => 'Dette vil permanent slette dine data.',
|
||||
'password' => 'Adgangskode',
|
||||
|
||||
'pro_plan_product' => 'Pro Plan',
|
||||
'pro_plan_description' => 'Et års indmelding i Invoice Ninja Pro Plan.',
|
||||
'pro_plan_success' => 'Tak for at du valgte Invoice Ninja\'s Pro plan!<p/> <br/>
|
||||
<b>De næste skridt</b><p/>En betalbar faktura er send til e-emailaddesse
|
||||
som er tilknyttet din konto. For at låse op for alle de utrolige
|
||||
Pro-funktioner, kan du følge instruktionerne på fakturaen til at
|
||||
betale for et år med Pro-niveau funktionerer.<p/>
|
||||
Kan du ikke finde fakturaen? Har behov for mere hjælp? Vi hjælper dig gerne om det skulle være noget
|
||||
-- kontakt os på contact@invoiceninja.com',
|
||||
|
||||
'unsaved_changes' => 'Du har ugemente ændringer',
|
||||
'custom_fields' => 'Egendefineret felt',
|
||||
'company_fields' => 'Selskabets felt',
|
||||
'client_fields' => 'Klientens felt',
|
||||
'field_label' => 'Felt etikette',
|
||||
'field_value' => 'Feltets værdi',
|
||||
'edit' => 'Rediger',
|
||||
'set_name' => 'Sæt dit firmanavn',
|
||||
'view_as_recipient' => 'Vis som modtager',
|
||||
|
||||
// product management
|
||||
'product_library' => 'Produktbibliotek',
|
||||
'product' => 'Produkt',
|
||||
'products' => 'Produkter',
|
||||
'fill_products' => 'Automatisk-udfyld produkter',
|
||||
'fill_products_help' => 'Valg af produkt vil automatisk udfylde <b>beskrivelse og pris</b>',
|
||||
'update_products' => 'Automatisk opdatering af produkter',
|
||||
'update_products_help' => 'En opdatering af en faktura vil automatisk <b>opdaterer Produktbiblioteket</b>',
|
||||
'create_product' => 'Opret nyt produkt',
|
||||
'edit_product' => 'Rediger produkt',
|
||||
'archive_product' => 'Arkiver produkt',
|
||||
'updated_product' => 'Produkt opdateret',
|
||||
'created_product' => 'Produkt lagret',
|
||||
'archived_product' => 'Produkt arkiveret',
|
||||
'pro_plan_custom_fields' => ':link for at aktivere egendefinerede felter ved at bliver medlem af Pro Plan',
|
||||
|
||||
'advanced_settings' => 'avancerede indstillinger',
|
||||
'pro_plan_advanced_settings' => ':link for at aktivere avancerede indstillinger felter ved at bliver medlem af Pro Plan',
|
||||
'invoice_design' => 'Fakturadesign',
|
||||
'specify_colors' => 'Egendefineret farve',
|
||||
'specify_colors_label' => 'Vælg farver som bruges i fakturaen',
|
||||
|
||||
'chart_builder' => 'Diagram bygger',
|
||||
'ninja_email_footer' => 'Brug :sitet til at fakturere dine kunder og få betalt på nettet - gratis!',
|
||||
'go_pro' => 'Vælg Pro',
|
||||
|
||||
// Quotes
|
||||
'quote' => 'Pristilbud',
|
||||
'quotes' => 'Pristilbud',
|
||||
'quote_number' => 'Tilbuds nummer',
|
||||
'quote_number_short' => 'Tilbuds #',
|
||||
'quote_date' => 'Tilbudsdato',
|
||||
'quote_total' => 'Tilbud total',
|
||||
'your_quote' => 'Dit tilbud',
|
||||
'total' => 'Total',
|
||||
'clone' => 'Kopier',
|
||||
|
||||
'new_quote' => 'Nyt tilbud',
|
||||
'create_quote' => 'Gem tilbud',
|
||||
'edit_quote' => 'Rediger tilbud',
|
||||
'archive_quote' => 'Arkiver tilbud',
|
||||
'delete_quote' => 'Slet tilbud',
|
||||
'save_quote' => 'Lagre tilbud',
|
||||
'email_quote' => 'E-mail tilbudet',
|
||||
'clone_quote' => 'Kopier tilbud',
|
||||
'convert_to_invoice' => 'Konverter til en faktura',
|
||||
'view_invoice' => 'Se faktura',
|
||||
'view_client' => 'Vis klient',
|
||||
'view_quote' => 'Se tilbud',
|
||||
|
||||
'updated_quote' => 'Tilbud opdatert',
|
||||
'created_quote' => 'Tilbud oprettet',
|
||||
'cloned_quote' => 'Tilbud kopieret',
|
||||
'emailed_quote' => 'Tilbud sendt som e-mail',
|
||||
'archived_quote' => 'Tilbud arkiveret',
|
||||
'archived_quotes' => 'Arkiverede :count tilbud',
|
||||
'deleted_quote' => 'Tilbud slettet',
|
||||
'deleted_quotes' => 'Slettet :count tilbud',
|
||||
'converted_to_invoice' => 'Tilbud konverteret til faktura',
|
||||
|
||||
'quote_subject' => 'Nyt tilbud fra :account',
|
||||
'quote_message' => 'For å se dit tilbud pålydende :amount, klik på linket nedenfor.',
|
||||
'quote_link_message' => 'Hvis du vil se din klients tilbud, klik på linket under:',
|
||||
'notification_quote_sent_subject' => 'Tilbud :invoice sendt til :client',
|
||||
'notification_quote_viewed_subject' => 'Tilbudet :invoice er set af :client',
|
||||
'notification_quote_sent' => 'Følgende klient :client blev sendt tilbudsfaktura :invoice pålydende :amount.',
|
||||
'notification_quote_viewed' => 'Følgende klient :client har set tilbudsfakturaen :invoice pålydende :amount.',
|
||||
|
||||
'session_expired' => 'Session er udløbet.',
|
||||
|
||||
'invoice_fields' => 'Faktura felt',
|
||||
'invoice_options' => 'Faktura alternativer',
|
||||
'hide_quantity' => 'Skjul antal',
|
||||
'hide_quantity_help' => 'Hvis du altid kun har 1 som antal på dine fakturalinjer på fakturaen, kan du vælge ikke at vise antal på fakturaen.',
|
||||
'hide_paid_to_date' => 'Skjul delbetalinger',
|
||||
'hide_paid_to_date_help' => 'Vis kun delbetalinger hvis der er forekommet en delbetaling.',
|
||||
|
||||
'charge_taxes' => 'Inkluder skat',
|
||||
'user_management' => 'Brugerhåndtering',
|
||||
'add_user' => 'Tilføj bruger',
|
||||
'send_invite' => 'Send invitation',
|
||||
'sent_invite' => 'Invitation sendt',
|
||||
'updated_user' => 'Bruger opdateret',
|
||||
'invitation_message' => 'Du har blevet inviteret af :invitor. ',
|
||||
'register_to_add_user' => 'Venligst registrer dig for at oprette en bruger',
|
||||
'user_state' => 'Status',
|
||||
'edit_user' => 'Rediger bruger',
|
||||
'delete_user' => 'Slet bruger',
|
||||
'active' => 'Aktiv',
|
||||
'pending' => 'Afventer',
|
||||
'deleted_user' => 'Bruger slettet',
|
||||
'limit_users' => 'Desværre, vil dette overstiger grænsen på ' . MAX_NUM_USERS . ' brugere',
|
||||
|
||||
'confirm_email_invoice' => 'Er du sikker på at du vil sende denne faktura?',
|
||||
'confirm_email_quote' => 'Er du sikker på du ville sende dette tilbud?',
|
||||
'confirm_recurring_email_invoice' => 'Gentagende er slået til, er du sikker på du vil have denne faktura sendt?',
|
||||
|
||||
'cancel_account' => 'Annuller konto',
|
||||
'cancel_account_message' => 'Advarsel: Dette ville slette alle dine data og kan ikke omgøres',
|
||||
'go_back' => 'Go Back',
|
||||
|
||||
'data_visualizations' => 'Data visualisering',
|
||||
'sample_data' => 'Eksempel data vis',
|
||||
'hide' => 'Skjul',
|
||||
'new_version_available' => 'En ny version af :releases_link er tilgængelig. Din nuværende version er v:user_version, den nyeste version er v:latest_version',
|
||||
|
||||
'invoice_settings' => 'Faktura indstillinger',
|
||||
'invoice_number_prefix' => 'Faktura nummer præfiks',
|
||||
'invoice_number_counter' => 'Faktura nummer tæller',
|
||||
'quote_number_prefix' => 'Tilbuds nummer præfiks',
|
||||
'quote_number_counter' => 'Tilbuds nummer tæller',
|
||||
'share_invoice_counter' => 'Del faktura nummer tæller',
|
||||
'invoice_issued_to' => 'Faktura udstedt til',
|
||||
'invalid_counter' => 'For at undgå mulige overlap, sæt venligst et faktura eller tilbuds nummer præfiks',
|
||||
'mark_sent' => 'Markering sendt',
|
||||
);
|
106
app/lang/da/validation.php
Normal file
106
app/lang/da/validation.php
Normal file
@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
return array(
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Validation Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines contain the default error messages used by
|
||||
| the validator class. Some of these rules have multiple versions such
|
||||
| such as the size rules. Feel free to tweak each of these messages.
|
||||
|
|
||||
*/
|
||||
|
||||
"accepted" => ":attribute skal accepteres.",
|
||||
"active_url" => ":attribute er ikke en valid URL.",
|
||||
"after" => ":attribute skal være en dato efter :date.",
|
||||
"alpha" => ":attribute må kun bestå af bogstaver.",
|
||||
"alpha_dash" => ":attribute må kun bestå af bogstaver, tal og bindestreger.",
|
||||
"alpha_num" => ":attribute må kun bestå af bogstaver og tal.",
|
||||
"array" => ":attribute skal være et array.",
|
||||
"before" => ":attribute skal være en dato før :date.",
|
||||
"between" => array(
|
||||
"numeric" => ":attribute skal være imellem :min - :max.",
|
||||
"file" => ":attribute skal være imellem :min - :max kilobytes.",
|
||||
"string" => ":attribute skal være imellem :min - :max tegn.",
|
||||
"array" => ":attribute skal indeholde mellem :min - :max elementer."
|
||||
),
|
||||
"boolean" => ":attribute skal være sandt eller falsk",
|
||||
"confirmed" => ":attribute er ikke det samme som bekræftelsesfeltet.",
|
||||
"date" => ":attribute er ikke en gyldig dato.",
|
||||
"date_format" => ":attribute matcher ikke formatet :format.",
|
||||
"different" => ":attribute og :other skal være forskellige.",
|
||||
"digits" => ":attribute skal have :digits cifre.",
|
||||
"digits_between" => ":attribute skal have mellem :min og :max cifre.",
|
||||
"email" => ":attribute skal være en gyldig e-mailadresse.",
|
||||
"exists" => "Det valgte :attribute er ugyldig.",
|
||||
"image" => ":attribute skal være et billede.",
|
||||
"in" => "Det valgte :attribute er ugyldig.",
|
||||
"integer" => ":attribute skal være et heltal.",
|
||||
"ip" => ":attribute skal være en gyldig IP adresse.",
|
||||
"max" => array(
|
||||
"numeric" => ":attribute skal være højest :max.",
|
||||
"file" => ":attribute skal være højest :max kilobytes.",
|
||||
"string" => ":attribute skal være højest :max tegn.",
|
||||
"array" => ":attribute må ikke indeholde mere end :max elementer."
|
||||
),
|
||||
"mimes" => ":attribute skal være en fil af typen: :values.",
|
||||
"min" => array(
|
||||
"numeric" => ":attribute skal være mindst :min.",
|
||||
"file" => ":attribute skal være mindst :min kilobytes.",
|
||||
"string" => ":attribute skal være mindst :min tegn.",
|
||||
"array" => ":attribute skal indeholde mindst :min elementer."
|
||||
),
|
||||
"not_in" => "Den valgte :attribute er ugyldig.",
|
||||
"numeric" => ":attribute skal være et tal.",
|
||||
"regex" => ":attribute formatet er ugyldigt.",
|
||||
"required" => ":attribute skal udfyldes.",
|
||||
"required_if" => ":attribute skal udfyldes når :other er :value.",
|
||||
"required_with" => ":attribute skal udfyldes når :values er udfyldt.",
|
||||
"required_with_all" => ":attribute skal udfyldes når :values er udfyldt.",
|
||||
"required_without" => ":attribute skal udfyldes når :values ikke er udfyldt.",
|
||||
"required_without_all" => ":attribute skal udfyldes når ingen af :values er udfyldt.",
|
||||
"same" => ":attribute og :other skal være ens.",
|
||||
"size" => array(
|
||||
"numeric" => ":attribute skal være :size.",
|
||||
"file" => ":attribute skal være :size kilobytes.",
|
||||
"string" => ":attribute skal være :size tegn lang.",
|
||||
"array" => ":attribute skal indeholde :size elementer."
|
||||
),
|
||||
"timezone" => "The :attribute must be a valid zone.",
|
||||
"unique" => ":attribute er allerede taget.",
|
||||
"url" => ":attribute formatet er ugyldigt.",
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Custom Validation Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify custom validation messages for attributes using the
|
||||
| convention "attribute.rule" to name the lines. This makes it quick to
|
||||
| specify a specific custom language line for a given attribute rule.
|
||||
|
|
||||
*/
|
||||
|
||||
'custom' => array(
|
||||
'attribute-name' => array(
|
||||
'rule-name' => 'custom-message',
|
||||
),
|
||||
),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Custom Validation Attributes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines are used to swap attribute place-holders
|
||||
| with something more reader friendly such as E-Mail Address instead
|
||||
| of "email". This simply helps us make messages a little cleaner.
|
||||
|
|
||||
*/
|
||||
|
||||
'attributes' => array(),
|
||||
|
||||
);
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php
|
||||
|
||||
return array(
|
||||
|
||||
@ -117,11 +117,11 @@ return array(
|
||||
'billed_client' => 'billed client',
|
||||
'billed_clients' => 'billed clients',
|
||||
'active_client' => 'active client',
|
||||
'active_clients' => 'active clients',
|
||||
'active_clients' => 'active clients',
|
||||
'invoices_past_due' => 'Invoices Past Due',
|
||||
'upcoming_invoices' => 'Upcoming invoices',
|
||||
'average_invoice' => 'Average invoice',
|
||||
|
||||
|
||||
// list pages
|
||||
'archive' => 'Archive',
|
||||
'delete' => 'Delete',
|
||||
@ -267,7 +267,7 @@ return array(
|
||||
'notification_invoice_viewed_subject' => 'Invoice :invoice was viewed by :client',
|
||||
'notification_invoice_paid' => 'A payment of :amount was made by client :client towards Invoice :invoice.',
|
||||
'notification_invoice_sent' => 'The following client :client was emailed Invoice :invoice for :amount.',
|
||||
'notification_invoice_viewed' => 'The following client :client viewed Invoice :invoice for :amount.',
|
||||
'notification_invoice_viewed' => 'The following client :client viewed Invoice :invoice for :amount.',
|
||||
'reset_password' => 'You can reset your account password by clicking the following link:',
|
||||
'reset_password_footer' => 'If you did not request this password reset please email our support: ' . CONTACT_EMAIL,
|
||||
|
||||
@ -275,10 +275,10 @@ return array(
|
||||
// Payment page
|
||||
'secure_payment' => 'Secure Payment',
|
||||
'card_number' => 'Card number',
|
||||
'expiration_month' => 'Expiration month',
|
||||
'expiration_month' => 'Expiration month',
|
||||
'expiration_year' => 'Expiration year',
|
||||
'cvv' => 'CVV',
|
||||
|
||||
|
||||
// Security alerts
|
||||
'confide' => [
|
||||
'too_many_attempts' => 'Too many attempts. Try again in few minutes.',
|
||||
@ -289,7 +289,7 @@ return array(
|
||||
'password_reset' => 'Your password has been changed successfully.',
|
||||
'wrong_password_reset' => 'Invalid password. Try again',
|
||||
],
|
||||
|
||||
|
||||
// Pro Plan
|
||||
'pro_plan' => [
|
||||
'remove_logo' => ':link to remove the Invoice Ninja logo by joining the Pro Plan',
|
||||
@ -310,11 +310,11 @@ return array(
|
||||
'pro_plan_product' => 'Pro Plan',
|
||||
'pro_plan_description' => 'One year enrollment in the Invoice Ninja Pro Plan.',
|
||||
'pro_plan_success' => 'Thanks for choosing Invoice Ninja\'s Pro plan!<p/> <br/>
|
||||
<b>Next Steps</b><p/>A payable invoice has been sent to the email
|
||||
address associated with your account. To unlock all of the awesome
|
||||
Pro features, please follow the instructions on the invoice to pay
|
||||
<b>Next Steps</b><p/>A payable invoice has been sent to the email
|
||||
address associated with your account. To unlock all of the awesome
|
||||
Pro features, please follow the instructions on the invoice to pay
|
||||
for a year of Pro-level invoicing.<p/>
|
||||
Can\'t find the invoice? Need further assistance? We\'re happy to help
|
||||
Can\'t find the invoice? Need further assistance? We\'re happy to help
|
||||
-- email us at contact@invoiceninja.com',
|
||||
|
||||
'unsaved_changes' => 'You have unsaved changes',
|
||||
@ -340,7 +340,7 @@ return array(
|
||||
'archive_product' => 'Archive Product',
|
||||
'updated_product' => 'Successfully updated product',
|
||||
'created_product' => 'Successfully created product',
|
||||
'archived_product' => 'Successfully archived product',
|
||||
'archived_product' => 'Successfully archived product',
|
||||
'pro_plan_custom_fields' => ':link to enable custom fields by joining the Pro Plan',
|
||||
|
||||
'advanced_settings' => 'Advanced Settings',
|
||||
@ -393,8 +393,8 @@ return array(
|
||||
'notification_quote_sent_subject' => 'Quote :invoice was sent to :client',
|
||||
'notification_quote_viewed_subject' => 'Quote :invoice was viewed by :client',
|
||||
'notification_quote_sent' => 'The following client :client was emailed Quote :invoice for :amount.',
|
||||
'notification_quote_viewed' => 'The following client :client viewed Quote :invoice for :amount.',
|
||||
|
||||
'notification_quote_viewed' => 'The following client :client viewed Quote :invoice for :amount.',
|
||||
|
||||
'session_expired' => 'Your session has expired.',
|
||||
|
||||
'invoice_fields' => 'Invoice Fields',
|
||||
@ -440,7 +440,7 @@ return array(
|
||||
'quote_number_counter' => 'Quote Number Counter',
|
||||
'share_invoice_counter' => 'Share invoice counter',
|
||||
'invoice_issued_to' => 'Invoice issued to',
|
||||
'invalid_counter' => 'To prevent a possible conflict please set either an invoice or quote number prefix',
|
||||
'invalid_counter' => 'To prevent a possible conflict please set either an invoice or quote number prefix',
|
||||
'mark_sent' => 'Mark sent',
|
||||
|
||||
'gateway_help_1' => ':link to sign up for Authorize.net.',
|
||||
@ -457,7 +457,7 @@ return array(
|
||||
'more_designs_self_host_text' => '',
|
||||
'buy' => 'Buy',
|
||||
'bought_designs' => 'Successfully added additional invoice designs',
|
||||
|
||||
|
||||
'sent' => 'sent',
|
||||
|
||||
);
|
||||
'timesheets' => 'Timesheets',
|
||||
);
|
||||
|
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;
|
||||
}
|
||||
}
|
@ -31,6 +31,11 @@ class Client extends EntityModel
|
||||
{
|
||||
return $this->hasMany('Contact');
|
||||
}
|
||||
|
||||
public function projects()
|
||||
{
|
||||
return $this->hasMany('Project');
|
||||
}
|
||||
|
||||
public function country()
|
||||
{
|
||||
@ -125,6 +130,19 @@ class Client extends EntityModel
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
public function getVatNumber()
|
||||
{
|
||||
$str = '';
|
||||
|
||||
if ($this->work_phone)
|
||||
{
|
||||
$str .= '<i class="fa fa-vat-number" style="width: 20px"></i>' . $this->vat_number;
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
|
||||
public function getNotes()
|
||||
{
|
||||
|
@ -94,7 +94,8 @@ class Invoice extends EntityModel
|
||||
'custom_taxes2']);
|
||||
|
||||
$this->client->setVisible([
|
||||
'name',
|
||||
'name',
|
||||
'vat_number',
|
||||
'address1',
|
||||
'address2',
|
||||
'city',
|
||||
@ -109,7 +110,8 @@ class Invoice extends EntityModel
|
||||
'custom_value2']);
|
||||
|
||||
$this->account->setVisible([
|
||||
'name',
|
||||
'name',
|
||||
'vat_number',
|
||||
'address1',
|
||||
'address2',
|
||||
'city',
|
||||
|
51
app/models/Project.php
Normal file
51
app/models/Project.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?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;
|
||||
}
|
||||
else if (Auth::check())
|
||||
{
|
||||
$entity->user_id = Auth::user()->id;
|
||||
$entity->account_id = Auth::user()->account_id;
|
||||
}
|
||||
else
|
||||
{
|
||||
Utils::fatalError();
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
}
|
||||
|
50
app/models/ProjectCode.php
Normal file
50
app/models/ProjectCode.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?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;
|
||||
}
|
||||
else if (Auth::check())
|
||||
{
|
||||
$entity->user_id = Auth::user()->id;
|
||||
$entity->account_id = Auth::user()->account_id;
|
||||
}
|
||||
else
|
||||
{
|
||||
Utils::fatalError();
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
}
|
22
app/models/Timesheet.php
Executable file
22
app/models/Timesheet.php
Executable 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');
|
||||
}
|
||||
}
|
129
app/models/TimesheetEvent.php
Normal file
129
app/models/TimesheetEvent.php
Normal file
@ -0,0 +1,129 @@
|
||||
<?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;
|
||||
}
|
||||
else if (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;
|
||||
}
|
||||
|
||||
}
|
46
app/models/TimesheetEventSource.php
Normal file
46
app/models/TimesheetEventSource.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?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;
|
||||
}
|
||||
else if (Auth::check())
|
||||
{
|
||||
$entity->user_id = Auth::user()->id;
|
||||
$entity->account_id = Auth::user()->account_id;
|
||||
}
|
||||
else
|
||||
{
|
||||
Utils::fatalError();
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
}
|
@ -62,6 +62,9 @@ class ClientRepository
|
||||
if (isset($data['name'])) {
|
||||
$client->name = trim($data['name']);
|
||||
}
|
||||
if (isset($data['vat_number'])) {
|
||||
$client->vat_number = trim($data['vat_number']);
|
||||
}
|
||||
if (isset($data['work_phone'])) {
|
||||
$client->work_phone = trim($data['work_phone']);
|
||||
}
|
||||
|
@ -131,6 +131,8 @@ Route::group(array('before' => 'auth'), function()
|
||||
Route::get('credits/create/{client_id?}/{invoice_id?}', 'CreditController@create');
|
||||
Route::get('api/credits/{client_id?}', array('as'=>'api.credits', 'uses'=>'CreditController@getDatatable'));
|
||||
Route::post('credits/bulk', 'CreditController@bulk');
|
||||
|
||||
//Route::resource('timesheets', 'TimesheetController');
|
||||
});
|
||||
|
||||
// Route group for API
|
||||
|
@ -14,3 +14,4 @@
|
||||
Artisan::resolve('SendRecurringInvoices');
|
||||
Artisan::resolve('CreateRandomData');
|
||||
Artisan::resolve('ResetData');
|
||||
Artisan::resolve('ImportTimesheetData');
|
||||
|
@ -10,8 +10,7 @@ class ExampleTest extends TestCase {
|
||||
public function testBasicExample()
|
||||
{
|
||||
$crawler = $this->client->request('GET', '/');
|
||||
|
||||
$this->assertTrue($this->client->getResponse()->isOk());
|
||||
$this->assertTrue($this->client->getResponse()->isRedirect());
|
||||
}
|
||||
|
||||
}
|
26
app/tests/TimesheetUtilsTest.php
Normal file
26
app/tests/TimesheetUtilsTest.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
class TimesheetUtilTest extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
public function testParseEventSummary() {
|
||||
list($code, $codes, $title) = TimesheetUtils::parseEventSummary('Riga :)');
|
||||
$this->assertSame(null, $code);
|
||||
|
||||
list($code, $tags, $title) = TimesheetUtils::parseEventSummary('Test:');
|
||||
$this->assertSame("TEST", $code);
|
||||
|
||||
list($code, $tags, $title) = TimesheetUtils::parseEventSummary('Test: ');
|
||||
$this->assertSame("TEST", $code);
|
||||
|
||||
list($code, $tags, $title) = TimesheetUtils::parseEventSummary('Test::');
|
||||
$this->assertSame("TEST", $code);
|
||||
|
||||
list($code, $tags, $title) = TimesheetUtils::parseEventSummary('TEST: Hello :)');
|
||||
$this->assertSame("TEST", $code);
|
||||
|
||||
list($code, $tags, $title) = TimesheetUtils::parseEventSummary('Test/tags: ');
|
||||
$this->assertSame('TEST', $code);
|
||||
$this->assertSame('tags', $tags);
|
||||
}
|
||||
|
||||
}
|
@ -29,6 +29,7 @@
|
||||
|
||||
{{ Former::legend('details') }}
|
||||
{{ Former::text('name') }}
|
||||
{{ Former::text('vat_number') }}
|
||||
{{ Former::text('work_email') }}
|
||||
{{ Former::text('work_phone') }}
|
||||
{{ Former::file('logo')->max(2, 'MB')->accept('image')->inlineHelp(trans('texts.logo_help')) }}
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
{{ Former::legend('Organization') }}
|
||||
{{ Former::text('name') }}
|
||||
{{ Former::text('vat_number') }}
|
||||
{{ Former::text('work_phone')->label('Phone') }}
|
||||
{{ Former::textarea('notes') }}
|
||||
|
||||
|
@ -23,7 +23,8 @@
|
||||
|
||||
{{ Former::legend('organization') }}
|
||||
{{ Former::text('name')->data_bind("attr { placeholder: placeholderName }") }}
|
||||
{{ Former::text('website') }}
|
||||
{{ Former::text('vat_number') }}
|
||||
{{ Former::text('website') }}
|
||||
{{ Former::text('work_phone') }}
|
||||
|
||||
@if (Auth::user()->isPro())
|
||||
|
@ -39,7 +39,8 @@
|
||||
|
||||
<div class="col-md-3">
|
||||
<h3>{{ trans('texts.details') }}</h3>
|
||||
<p>{{ $client->getAddress() }}</p>
|
||||
<p>{{ $client->getVatNumber() }}</p>
|
||||
<p>{{ $client->getAddress() }}</p>
|
||||
<p>{{ $client->getCustomFields() }}</p>
|
||||
<p>{{ $client->getPhone() }}</p>
|
||||
<p>{{ $client->getNotes() }}</p>
|
||||
|
@ -5,7 +5,7 @@
|
||||
<style type="text/css">
|
||||
|
||||
html, body {
|
||||
height: 100%
|
||||
height: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
|
@ -341,6 +341,8 @@
|
||||
|
||||
{{ Former::legend('organization') }}
|
||||
{{ Former::text('name')->data_bind("value: name, valueUpdate: 'afterkeydown', attr { placeholder: name.placeholder }") }}
|
||||
{{ Former::text('vat_number')->data_bind("value: vat_number, valueUpdate: 'afterkeydown'") }}
|
||||
|
||||
{{ Former::text('website')->data_bind("value: website, valueUpdate: 'afterkeydown'") }}
|
||||
{{ Former::text('work_phone')->data_bind("value: work_phone, valueUpdate: 'afterkeydown'") }}
|
||||
|
||||
@ -1220,6 +1222,7 @@
|
||||
var self = this;
|
||||
self.public_id = ko.observable(0);
|
||||
self.name = ko.observable('');
|
||||
self.vat_number = ko.observable('');
|
||||
self.work_phone = ko.observable('');
|
||||
self.custom_value1 = ko.observable('');
|
||||
self.custom_value2 = ko.observable('');
|
||||
@ -1551,15 +1554,15 @@
|
||||
}
|
||||
model.invoice().addItem();
|
||||
//model.addTaxRate();
|
||||
@else
|
||||
@else
|
||||
// TODO: Add the first tax rate for new invoices by adding a new db field to the tax codes types to set the default
|
||||
//if(model.invoice_taxes() && model.tax_rates().length > 2) {
|
||||
// var tax = model.tax_rates()[1];
|
||||
// model.invoice().tax(tax);
|
||||
//}
|
||||
model.invoice().custom_taxes1({{ $account->custom_invoice_taxes1 ? 'true' : 'false' }});
|
||||
model.invoice().custom_taxes2({{ $account->custom_invoice_taxes2 ? 'true' : 'false' }});
|
||||
@endif
|
||||
// Add the first tax rate for new invoices
|
||||
//if(model.invoice_taxes() && model.tax_rates().length > 0) {
|
||||
// var tax = model.tax_rates()[0];
|
||||
// model.invoice().tax(tax);
|
||||
//}
|
||||
@endif
|
||||
|
||||
model.invoice().tax(model.getTaxRate(model.invoice().tax_name(), model.invoice().tax_rate()));
|
||||
|
@ -21,13 +21,13 @@
|
||||
|
||||
<center id="designThumbs">
|
||||
<p> </p>
|
||||
<a href="/images/designs/business.png" data-lightbox="more-designs" data-title="Business"><img src="/images/designs/business_thumb.png"/></a>
|
||||
<a href="/images/designs/creative.png" data-lightbox="more-designs" data-title="Creative"><img src="/images/designs/creative_thumb.png"/></a>
|
||||
<a href="/images/designs/elegant.png" data-lightbox="more-designs" data-title="Elegant"><img src="/images/designs/elegant_thumb.png"/></a>
|
||||
<a href="{{ asset('/images/designs/business.png') }}" data-lightbox="more-designs" data-title="Business"><img src="{{ asset('/images/designs/business_thumb.png') }}"/></a>
|
||||
<a href="{{ asset('/images/designs/creative.png') }}" data-lightbox="more-designs" data-title="Creative"><img src="{{ asset('/images/designs/creative_thumb.png') }}"/></a>
|
||||
<a href="{{ asset('/images/designs/elegant.png') }}" data-lightbox="more-designs" data-title="Elegant"><img src="{{ asset('/images/designs/elegant_thumb.png') }}"/></a>
|
||||
<p> </p>
|
||||
<a href="/images/designs/hipster.png" data-lightbox="more-designs" data-title="Hipster"><img src="/images/designs/hipster_thumb.png"/></a>
|
||||
<a href="/images/designs/playful.png" data-lightbox="more-designs" data-title="Playful"><img src="/images/designs/playful_thumb.png"/></a>
|
||||
<a href="/images/designs/photo.png" data-lightbox="more-designs" data-title="Photo"><img src="/images/designs/photo_thumb.png"/></a>
|
||||
<a href="{{ asset('/images/designs/hipster.png') }}" data-lightbox="more-designs" data-title="Hipster"><img src="{{ asset('/images/designs/hipster_thumb.png') }}"/></a>
|
||||
<a href="{{ asset('/images/designs/playful.png') }}" data-lightbox="more-designs" data-title="Playful"><img src="{{ asset('/images/designs/playful_thumb.png') }}"/></a>
|
||||
<a href="{{ asset('/images/designs/photo.png') }}" data-lightbox="more-designs" data-title="Photo"><img src="{{ asset('/images/designs/photo_thumb.png') }}"/></a>
|
||||
<p> </p>
|
||||
</center>
|
||||
|
||||
|
76
app/views/timesheets/edit.blade.php
Normal file
76
app/views/timesheets/edit.blade.php
Normal file
@ -0,0 +1,76 @@
|
||||
@extends('header')
|
||||
|
||||
@section('head')
|
||||
@parent
|
||||
@stop
|
||||
|
||||
@section('content')
|
||||
|
||||
<ol class="breadcrumb">
|
||||
<li>{{ link_to('timesheets', trans('texts.timesheets')) }}</li>
|
||||
<li class='active'>{{ 1 }}</li>
|
||||
</ol>
|
||||
<div id="timesheet_table" style="height: 800px; overflow: auto">
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
|
||||
var table = $("#timesheet_table");
|
||||
table.handsontable({
|
||||
colHeaders: false,//['','Date', 'From', 'To', 'Hours', 'Desciption', 'Owner', 'Project', 'Code', 'Tags' ],
|
||||
startRows: 6,
|
||||
startCols: 4,
|
||||
minSpareRows: 1,
|
||||
stretchH: 'last',
|
||||
colWidths: [25, 97, 50, 50, 50, 400, 150, 150, 50,50],
|
||||
columns: [
|
||||
{ data: "selected", type: 'checkbox' },
|
||||
{ data: "date", type: 'date', dateFormat: 'yy-mm-dd' },
|
||||
{ data: "from" },
|
||||
{ data: "to" },
|
||||
{ data: "hours", type: 'numeric', format: '0.00' },
|
||||
{ data: "summary" },
|
||||
{ data: "owner" },
|
||||
{ data: "project", type: 'dropdown', source: ["Hobby", "Large Development"], strict: true },
|
||||
{ data: "code", type: 'dropdown', source: ["DAMCO", "MYDAMCO", "SAXO", "STUDIE"], strict: true },
|
||||
{ data: "tags", type: 'dropdown', source: ["design", "development", "bug"], strict: false },
|
||||
],
|
||||
//cells: function(row, col, prop) {
|
||||
// return {
|
||||
// type : {
|
||||
// renderer : function (instance, td, row, col, prop, value, cellProperties) {
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
//}
|
||||
});
|
||||
|
||||
|
||||
var data = [
|
||||
{ selected: true, date:'2014-01-01', from:'10:00', to:'11:00', hours:'1', summary: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut vitae aliquet risus. Etiam posuere urna dictum ipsum imperdiet ullamcorper. Integer suscipit arcu sed iaculis tristique', owner:'Troels Liebe Bentsen', project:'Hobby', code:'TEST', tags:'design'},
|
||||
{ selected: true, date:'2014-01-02', from:'00:00', to:'23:00', hours:'11', summary: 'Suspendisse tellus nunc, vehicula eget purus eu, tempor volutpat ligula. ', owner:'Troels Liebe Bentsen', project:'Hobby', code:'TEST', tags:'design'},
|
||||
{ selected: true, date:'2014-01-03', from:'10:00', to:'11:00', hours:'1', summary: 'Quisque id facilisis tellus. Curabitur diam ante, venenatis sit amet lectus vitae, viverra bibendum lectus.', owner:'Troels Liebe Bentsen', project:'Hobby', code:'TEST', tags:'design'},
|
||||
{ selected: true, date:'2014-01-04', from:'23:00', to:'24:00', hours:'1', summary: 'Do some stuff', owner:'Troels Liebe Bentsen', project:'Hobby', code:'TEST', tags:'design'},
|
||||
{ selected: true, date:'2014-01-01', from:'10:00', to:'11:00', hours:'1', summary: 'Morbi commodo tellus justo, id elementum est blandit sit amet.', owner:'Troels Liebe Bentsen', project:'Hobby', code:'TEST', tags:'design'},
|
||||
{ selected: true, date:'2014-01-02', from:'00:00', to:'23:00', hours:'11', summary: 'Do some stuff', owner:'Troels Liebe Bentsen', project:'Hobby', code:'TEST', tags:'design'},
|
||||
{ selected: true, date:'2014-01-03', from:'10:00', to:'11:00', hours:'1', summary: 'Do some stuff', owner:'Troels Liebe Bentsen', project:'Hobby', code:'TEST', tags:'design'},
|
||||
{ selected: true, date:'2014-01-04', from:'23:00', to:'24:00', hours:'1', summary: 'Mauris elementum mi non tincidunt facilisisin', owner:'Troels Liebe Bentsen', project:'Hobby', code:'TEST', tags:'design'},
|
||||
{ selected: true, date:'2014-01-01', from:'10:00', to:'11:00', hours:'1', summary: 'Redo inital load as the CDC cleanup task is failing and it might be due to DDL changes that the CDC service did not handle', owner:'Troels Liebe Bentsen', project:'Hobby', code:'TEST', tags:'design'},
|
||||
];
|
||||
|
||||
table.handsontable("loadData", data);
|
||||
|
||||
|
||||
|
||||
/*
|
||||
$.ajax({
|
||||
url: "/api/events",
|
||||
dataType: 'json',
|
||||
type: 'GET',
|
||||
success: function (res) {
|
||||
$("#dataTable").data("handsontable").loadData(res);
|
||||
}
|
||||
});
|
||||
*/
|
||||
|
||||
</script>
|
||||
@stop
|
@ -18,7 +18,8 @@
|
||||
"accounting": "~0.*",
|
||||
"spectrum": "~1.3.4",
|
||||
"d3": "~3.4.11",
|
||||
"lightbox2": "~2.7.1"
|
||||
"lightbox2": "~2.7.1",
|
||||
"handsontable": "*"
|
||||
},
|
||||
"resolutions": {
|
||||
"jquery": "~1.11"
|
||||
|
@ -8,17 +8,20 @@
|
||||
"name": "Hillel Coren",
|
||||
"email": "hillelcoren@gmail.com"
|
||||
}
|
||||
],
|
||||
],
|
||||
"require": {
|
||||
"laravel/framework": "4.1.25",
|
||||
"patricktalmadge/bootstrapper": "dev-develop",
|
||||
"patricktalmadge/bootstrapper": "4.1.x",
|
||||
"zizaco/confide": "3.1.x",
|
||||
"anahkiasen/former": "3.4.x",
|
||||
"barryvdh/laravel-debugbar": "dev-master",
|
||||
"chumper/datatable": "2.x",
|
||||
"omnipay/omnipay": "~2.0",
|
||||
"intervention/image": "1.x",
|
||||
"webpatser/laravel-countries": "dev-master"
|
||||
"webpatser/laravel-countries": "dev-master",
|
||||
"barryvdh/laravel-ide-helper": "1.*",
|
||||
"doctrine/dbal": "~2.3",
|
||||
"jsanc623/phpbenchtime": "2.x"
|
||||
},
|
||||
"require-dev": {
|
||||
"codeception/codeception": "dev-master"
|
||||
@ -45,8 +48,9 @@
|
||||
],
|
||||
"post-update-cmd": [
|
||||
"php artisan clear-compiled",
|
||||
"php artisan optimize",
|
||||
"php artisan debugbar:publish"
|
||||
"php artisan ide-helper:generate",
|
||||
"php artisan debugbar:publish",
|
||||
"php artisan optimize"
|
||||
],
|
||||
"post-create-project-cmd": [
|
||||
"php artisan key:generate"
|
||||
|
810
composer.lock
generated
810
composer.lock
generated
File diff suppressed because it is too large
Load Diff
747
public/built.css
747
public/built.css
File diff suppressed because one or more lines are too long
15294
public/built.js
15294
public/built.js
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -6,7 +6,7 @@ body {
|
||||
color: #2e2b2b;
|
||||
}
|
||||
.center-block {
|
||||
margin: 0 auto!;
|
||||
margin: 0 auto !important;
|
||||
float: none;
|
||||
}
|
||||
.valgin {padding: 0; margin: 0;}
|
||||
@ -254,7 +254,7 @@ a .cta:hover span {
|
||||
.hero1.background {
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-attachment: static;
|
||||
background-attachment: fixed;
|
||||
background-size: cover;
|
||||
min-height: 500px;
|
||||
}
|
||||
@ -280,7 +280,7 @@ a .cta:hover span {
|
||||
padding-right: 15px;
|
||||
position: relative;
|
||||
padding: 10px 35px 20px 35px;
|
||||
xheight: 212px;
|
||||
height: 212px;
|
||||
border-left: 1px dotted #ccc;
|
||||
margin-top: 100px;
|
||||
text-align: left;
|
||||
@ -785,7 +785,7 @@ footer.footer form#mad_mimi_signup_form .form-group {
|
||||
position: relative;
|
||||
}
|
||||
footer.footer form#mad_mimi_signup_form input {
|
||||
padding: right: 60px;
|
||||
padding-right: 60px;
|
||||
background: #393636;
|
||||
border: none;
|
||||
color: #b7b7b6;
|
||||
|
@ -637,6 +637,7 @@ function displayAccount(doc, invoice, x, y, layout) {
|
||||
|
||||
var data1 = [
|
||||
account.name,
|
||||
account.vat_number,
|
||||
account.work_email,
|
||||
account.work_phone
|
||||
];
|
||||
@ -674,6 +675,7 @@ function displayClient(doc, invoice, x, y, layout) {
|
||||
}
|
||||
var data = [
|
||||
getClientDisplayName(client),
|
||||
client.vat_number,
|
||||
concatStrings(client.address1, client.address2),
|
||||
concatStrings(client.city, client.state, client.postal_code),
|
||||
client.country ? client.country.name : false,
|
||||
@ -1503,4 +1505,4 @@ function roundToTwo(num, toString) {
|
||||
|
||||
function truncate(str, length) {
|
||||
return (str && str.length > length) ? (str.substr(0, length-1) + '...') : str;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user