bug fixes

This commit is contained in:
David Bomba 2016-06-01 20:03:19 +10:00
commit f239cfdfb4
17 changed files with 285 additions and 124 deletions

View File

@ -34,9 +34,7 @@ class DocumentAPIController extends BaseAPIController
public function store(CreateDocumentRequest $request) public function store(CreateDocumentRequest $request)
{ {
Log::info($request); $document = $this->documentRepo->upload($request->all());
$document = $this->documentRepo->upload($request->file);
return $this->itemResponse($document); return $this->itemResponse($document);
} }

View File

@ -102,7 +102,7 @@ class DocumentController extends BaseController
public function postUpload(CreateDocumentRequest $request) public function postUpload(CreateDocumentRequest $request)
{ {
$result = $this->documentRepo->upload($request->file, $doc_array); $result = $this->documentRepo->upload($request->all(), $doc_array);
if(is_string($result)){ if(is_string($result)){
return Response::json([ return Response::json([

View File

@ -1,7 +1,15 @@
<?php namespace App\Http\Requests; <?php namespace App\Http\Requests;
use App\Models\Invoice;
use App\Models\Expense;
class CreateDocumentRequest extends DocumentRequest class CreateDocumentRequest extends DocumentRequest
{ {
protected $autoload = [
ENTITY_INVOICE,
ENTITY_EXPENSE,
];
/** /**
* Determine if the user is authorized to make this request. * Determine if the user is authorized to make this request.
* *
@ -9,7 +17,19 @@ class CreateDocumentRequest extends DocumentRequest
*/ */
public function authorize() public function authorize()
{ {
return $this->user()->can('create', ENTITY_DOCUMENT) && $this->user()->hasFeature(FEATURE_DOCUMENTS); if ( ! $this->user()->hasFeature(FEATURE_DOCUMENTS)) {
return false;
}
if ($this->invoice && $this->user()->cannot('edit', $this->invoice)) {
return false;
}
if ($this->expense && $this->user()->cannot('edit', $this->expense)) {
return false;
}
return $this->user()->can('create', ENTITY_DOCUMENT);
} }
/** /**
@ -23,4 +43,5 @@ class CreateDocumentRequest extends DocumentRequest
//'file' => 'mimes:jpg' //'file' => 'mimes:jpg'
]; ];
} }
} }

View File

@ -2,8 +2,50 @@
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
// https://laracasts.com/discuss/channels/general-discussion/laravel-5-modify-input-before-validation/replies/34366
abstract class Request extends FormRequest { abstract class Request extends FormRequest {
// // populate in subclass to auto load record
protected $autoload = [];
/**
* Validate the input.
*
* @param \Illuminate\Validation\Factory $factory
* @return \Illuminate\Validation\Validator
*/
public function validator($factory)
{
return $factory->make(
$this->sanitizeInput(), $this->container->call([$this, 'rules']), $this->messages()
);
}
/**
* Sanitize the input.
*
* @return array
*/
protected function sanitizeInput()
{
if (method_exists($this, 'sanitize')) {
$input = $this->container->call([$this, 'sanitize']);
} else {
$input = $this->all();
}
// autoload referenced entities
foreach ($this->autoload as $entityType) {
if ($id = $this->input("{$entityType}_public_id") ?: $this->input("{$entityType}_id")) {
$class = "App\\Models\\" . ucwords($entityType);
$entity = $class::scope($id)->firstOrFail();
$input[$entityType] = $entity;
$input[$entityType . '_id'] = $entity->id;
}
}
$this->replace($input);
return $this->all();
}
} }

View File

@ -652,30 +652,34 @@ class Account extends Eloquent
public function getNextInvoiceNumber($invoice) public function getNextInvoiceNumber($invoice)
{ {
if ($this->hasNumberPattern($invoice->invoice_type_id)) { if ($this->hasNumberPattern($invoice->invoice_type_id)) {
return $this->getNumberPattern($invoice); $number = $this->getNumberPattern($invoice);
} else {
$counter = $this->getCounter($invoice->invoice_type_id);
$prefix = $this->getNumberPrefix($invoice->invoice_type_id);
$counterOffset = 0;
// confirm the invoice number isn't already taken
do {
$number = $prefix . str_pad($counter, $this->invoice_number_padding, '0', STR_PAD_LEFT);
$check = Invoice::scope(false, $this->id)->whereInvoiceNumber($number)->withTrashed()->first();
$counter++;
$counterOffset++;
} while ($check);
// update the invoice counter to be caught up
if ($counterOffset > 1) {
if ($invoice->isType(INVOICE_TYPE_QUOTE) && !$this->share_counter) {
$this->quote_number_counter += $counterOffset - 1;
} else {
$this->invoice_number_counter += $counterOffset - 1;
}
$this->save();
}
} }
$counter = $this->getCounter($invoice->invoice_type_id); if ($invoice->recurring_invoice_id) {
$prefix = $this->getNumberPrefix($invoice->invoice_type_id); $number = $this->recurring_invoice_number_prefix . $number;
$counterOffset = 0;
// confirm the invoice number isn't already taken
do {
$number = $prefix . str_pad($counter, $this->invoice_number_padding, '0', STR_PAD_LEFT);
$check = Invoice::scope(false, $this->id)->whereInvoiceNumber($number)->withTrashed()->first();
$counter++;
$counterOffset++;
} while ($check);
// update the invoice counter to be caught up
if ($counterOffset > 1) {
if ($invoice->isType(INVOICE_TYPE_QUOTE) && !$this->share_counter) {
$this->quote_number_counter += $counterOffset - 1;
} else {
$this->invoice_number_counter += $counterOffset - 1;
}
$this->save();
} }
return $number; return $number;
@ -683,17 +687,15 @@ class Account extends Eloquent
public function incrementCounter($invoice) public function incrementCounter($invoice)
{ {
// if they didn't use the counter don't increment it
if ($invoice->invoice_number != $this->getNextInvoiceNumber($invoice)) {
return;
}
if ($invoice->isType(INVOICE_TYPE_QUOTE) && !$this->share_counter) { if ($invoice->isType(INVOICE_TYPE_QUOTE) && !$this->share_counter) {
$this->quote_number_counter += 1; $this->quote_number_counter += 1;
} else { } else {
$default = $this->invoice_number_counter; $this->invoice_number_counter += 1;
$actual = Utils::parseInt($invoice->invoice_number);
if ( ! $this->hasFeature(FEATURE_INVOICE_SETTINGS) && $default != $actual) {
$this->invoice_number_counter = $actual + 1;
} else {
$this->invoice_number_counter += 1;
}
} }
$this->save(); $this->save();

View File

@ -6,20 +6,25 @@ use Auth;
class Document extends EntityModel class Document extends EntityModel
{ {
protected $fillable = [
'invoice_id',
'expense_id',
];
public static $extraExtensions = array( public static $extraExtensions = array(
'jpg' => 'jpeg', 'jpg' => 'jpeg',
'tif' => 'tiff', 'tif' => 'tiff',
); );
public static $allowedMimes = array(// Used by Dropzone.js; does not affect what the server accepts public static $allowedMimes = array(// Used by Dropzone.js; does not affect what the server accepts
'image/png', 'image/jpeg', 'image/tiff', 'application/pdf', 'image/gif', 'image/vnd.adobe.photoshop', 'text/plain', 'image/png', 'image/jpeg', 'image/tiff', 'application/pdf', 'image/gif', 'image/vnd.adobe.photoshop', 'text/plain',
'application/zip', 'application/msword', 'application/zip', 'application/msword',
'application/excel', 'application/vnd.ms-excel', 'application/x-excel', 'application/x-msexcel', 'application/excel', 'application/vnd.ms-excel', 'application/x-excel', 'application/x-msexcel',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet','application/postscript', 'image/svg+xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet','application/postscript', 'image/svg+xml',
'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/vnd.ms-powerpoint',
); );
public static $types = array( public static $types = array(
'png' => array( 'png' => array(
'mime' => 'image/png', 'mime' => 'image/png',
@ -70,18 +75,18 @@ class Document extends EntityModel
'mime' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'mime' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
), ),
); );
public function fill(array $attributes) public function fill(array $attributes)
{ {
parent::fill($attributes); parent::fill($attributes);
if(empty($this->attributes['disk'])){ if(empty($this->attributes['disk'])){
$this->attributes['disk'] = env('DOCUMENT_FILESYSTEM', 'documents'); $this->attributes['disk'] = env('DOCUMENT_FILESYSTEM', 'documents');
} }
return $this; return $this;
} }
public function account() public function account()
{ {
return $this->belongsTo('App\Models\Account'); return $this->belongsTo('App\Models\Account');
@ -101,7 +106,7 @@ class Document extends EntityModel
{ {
return $this->belongsTo('App\Models\Invoice')->withTrashed(); return $this->belongsTo('App\Models\Invoice')->withTrashed();
} }
public function getDisk(){ public function getDisk(){
return Storage::disk(!empty($this->disk)?$this->disk:env('DOCUMENT_FILESYSTEM', 'documents')); return Storage::disk(!empty($this->disk)?$this->disk:env('DOCUMENT_FILESYSTEM', 'documents'));
} }
@ -110,19 +115,19 @@ class Document extends EntityModel
{ {
$this->attributes['disk'] = $value?$value:env('DOCUMENT_FILESYSTEM', 'documents'); $this->attributes['disk'] = $value?$value:env('DOCUMENT_FILESYSTEM', 'documents');
} }
public function getDirectUrl(){ public function getDirectUrl(){
return static::getDirectFileUrl($this->path, $this->getDisk()); return static::getDirectFileUrl($this->path, $this->getDisk());
} }
public function getDirectPreviewUrl(){ public function getDirectPreviewUrl(){
return $this->preview?static::getDirectFileUrl($this->preview, $this->getDisk(), true):null; return $this->preview?static::getDirectFileUrl($this->preview, $this->getDisk(), true):null;
} }
public static function getDirectFileUrl($path, $disk, $prioritizeSpeed = false){ public static function getDirectFileUrl($path, $disk, $prioritizeSpeed = false){
$adapter = $disk->getAdapter(); $adapter = $disk->getAdapter();
$fullPath = $adapter->applyPathPrefix($path); $fullPath = $adapter->applyPathPrefix($path);
if($adapter instanceof \League\Flysystem\AwsS3v3\AwsS3Adapter) { if($adapter instanceof \League\Flysystem\AwsS3v3\AwsS3Adapter) {
$client = $adapter->getClient(); $client = $adapter->getClient();
$command = $client->getCommand('GetObject', [ $command = $client->getCommand('GetObject', [
@ -136,12 +141,12 @@ class Document extends EntityModel
$secret = env('RACKSPACE_TEMP_URL_SECRET'); $secret = env('RACKSPACE_TEMP_URL_SECRET');
if($secret){ if($secret){
$object = $adapter->getContainer()->getObject($fullPath); $object = $adapter->getContainer()->getObject($fullPath);
if(env('RACKSPACE_TEMP_URL_SECRET_SET')){ if(env('RACKSPACE_TEMP_URL_SECRET_SET')){
// Go ahead and set the secret too // Go ahead and set the secret too
$object->getService()->getAccount()->setTempUrlSecret($secret); $object->getService()->getAccount()->setTempUrlSecret($secret);
} }
$url = $object->getUrl(); $url = $object->getUrl();
$expiry = strtotime('+10 minutes'); $expiry = strtotime('+10 minutes');
$urlPath = urldecode($url->getPath()); $urlPath = urldecode($url->getPath());
@ -150,64 +155,64 @@ class Document extends EntityModel
return sprintf('%s?temp_url_sig=%s&temp_url_expires=%d', $url, $hash, $expiry); return sprintf('%s?temp_url_sig=%s&temp_url_expires=%d', $url, $hash, $expiry);
} }
} }
return null; return null;
} }
public function getRaw(){ public function getRaw(){
$disk = $this->getDisk(); $disk = $this->getDisk();
return $disk->get($this->path); return $disk->get($this->path);
} }
public function getStream(){ public function getStream(){
$disk = $this->getDisk(); $disk = $this->getDisk();
return $disk->readStream($this->path); return $disk->readStream($this->path);
} }
public function getRawPreview(){ public function getRawPreview(){
$disk = $this->getDisk(); $disk = $this->getDisk();
return $disk->get($this->preview); return $disk->get($this->preview);
} }
public function getUrl(){ public function getUrl(){
return url('documents/'.$this->public_id.'/'.$this->name); return url('documents/'.$this->public_id.'/'.$this->name);
} }
public function getClientUrl($invitation){ public function getClientUrl($invitation){
return url('client/documents/'.$invitation->invitation_key.'/'.$this->public_id.'/'.$this->name); return url('client/documents/'.$invitation->invitation_key.'/'.$this->public_id.'/'.$this->name);
} }
public function isPDFEmbeddable(){ public function isPDFEmbeddable(){
return $this->type == 'jpeg' || $this->type == 'png' || $this->preview; return $this->type == 'jpeg' || $this->type == 'png' || $this->preview;
} }
public function getVFSJSUrl(){ public function getVFSJSUrl(){
if(!$this->isPDFEmbeddable())return null; if(!$this->isPDFEmbeddable())return null;
return url('documents/js/'.$this->public_id.'/'.$this->name.'.js'); return url('documents/js/'.$this->public_id.'/'.$this->name.'.js');
} }
public function getClientVFSJSUrl(){ public function getClientVFSJSUrl(){
if(!$this->isPDFEmbeddable())return null; if(!$this->isPDFEmbeddable())return null;
return url('client/documents/js/'.$this->public_id.'/'.$this->name.'.js'); return url('client/documents/js/'.$this->public_id.'/'.$this->name.'.js');
} }
public function getPreviewUrl(){ public function getPreviewUrl(){
return $this->preview?url('documents/preview/'.$this->public_id.'/'.$this->name.'.'.pathinfo($this->preview, PATHINFO_EXTENSION)):null; return $this->preview?url('documents/preview/'.$this->public_id.'/'.$this->name.'.'.pathinfo($this->preview, PATHINFO_EXTENSION)):null;
} }
public function toArray() public function toArray()
{ {
$array = parent::toArray(); $array = parent::toArray();
if(empty($this->visible) || in_array('url', $this->visible))$array['url'] = $this->getUrl(); if(empty($this->visible) || in_array('url', $this->visible))$array['url'] = $this->getUrl();
if(empty($this->visible) || in_array('preview_url', $this->visible))$array['preview_url'] = $this->getPreviewUrl(); if(empty($this->visible) || in_array('preview_url', $this->visible))$array['preview_url'] = $this->getPreviewUrl();
return $array; return $array;
} }
public function cloneDocument(){ public function cloneDocument(){
$document = Document::createNew($this); $document = Document::createNew($this);
$document->path = $this->path; $document->path = $this->path;
@ -219,7 +224,7 @@ class Document extends EntityModel
$document->size = $this->size; $document->size = $this->size;
$document->width = $this->width; $document->width = $this->width;
$document->height = $this->height; $document->height = $this->height;
return $document; return $document;
} }
} }
@ -230,11 +235,11 @@ Document::deleted(function ($document) {
->where('documents.path', '=', $document->path) ->where('documents.path', '=', $document->path)
->where('documents.disk', '=', $document->disk) ->where('documents.disk', '=', $document->disk)
->count(); ->count();
if(!$same_path_count){ if(!$same_path_count){
$document->getDisk()->delete($document->path); $document->getDisk()->delete($document->path);
} }
if($document->preview){ if($document->preview){
$same_preview_count = DB::table('documents') $same_preview_count = DB::table('documents')
->where('documents.account_id', '=', $document->account_id) ->where('documents.account_id', '=', $document->account_id)
@ -245,5 +250,5 @@ Document::deleted(function ($document) {
$document->getDisk()->delete($document->preview); $document->getDisk()->delete($document->preview);
} }
} }
}); });

View File

@ -16,6 +16,24 @@ class Product extends EntityModel
'default_tax_rate_id', 'default_tax_rate_id',
]; ];
public static function getImportColumns()
{
return [
'product_key',
'notes',
'cost',
];
}
public static function getImportMap()
{
return [
'product|item' => 'product_key',
'notes|description|details' => 'notes',
'cost|amount|price' => 'cost',
];
}
public function getEntityType() public function getEntityType()
{ {
return ENTITY_PRODUCT; return ENTITY_PRODUCT;

View File

@ -15,21 +15,38 @@ class BaseTransformer extends TransformerAbstract
protected function hasClient($name) protected function hasClient($name)
{ {
$name = strtolower($name); $name = trim(strtolower($name));
return isset($this->maps[ENTITY_CLIENT][$name]); return isset($this->maps[ENTITY_CLIENT][$name]);
} }
protected function hasProduct($key)
{
$key = trim(strtolower($key));
return isset($this->maps[ENTITY_PRODUCT][$key]);
}
protected function getString($data, $field) protected function getString($data, $field)
{ {
return (isset($data->$field) && $data->$field) ? $data->$field : ''; return (isset($data->$field) && $data->$field) ? $data->$field : '';
} }
protected function getNumber($data, $field)
{
return (isset($data->$field) && $data->$field) ? $data->$field : 0;
}
protected function getClientId($name) protected function getClientId($name)
{ {
$name = strtolower($name); $name = strtolower($name);
return isset($this->maps[ENTITY_CLIENT][$name]) ? $this->maps[ENTITY_CLIENT][$name] : null; return isset($this->maps[ENTITY_CLIENT][$name]) ? $this->maps[ENTITY_CLIENT][$name] : null;
} }
protected function getProductId($name)
{
$name = strtolower($name);
return isset($this->maps[ENTITY_PRODUCT][$name]) ? $this->maps[ENTITY_PRODUCT][$name] : null;
}
protected function getCountryId($name) protected function getCountryId($name)
{ {
$name = strtolower($name); $name = strtolower($name);
@ -53,7 +70,7 @@ class BaseTransformer extends TransformerAbstract
if ( ! $date instanceof DateTime) { if ( ! $date instanceof DateTime) {
$date = DateTime::createFromFormat($format, $date); $date = DateTime::createFromFormat($format, $date);
} }
return $date ? $date->format('Y-m-d') : null; return $date ? $date->format('Y-m-d') : null;
} }
@ -87,11 +104,11 @@ class BaseTransformer extends TransformerAbstract
return isset($this->maps[ENTITY_INVOICE.'_'.ENTITY_CLIENT][$invoiceNumber])? $this->maps[ENTITY_INVOICE.'_'.ENTITY_CLIENT][$invoiceNumber] : null; return isset($this->maps[ENTITY_INVOICE.'_'.ENTITY_CLIENT][$invoiceNumber])? $this->maps[ENTITY_INVOICE.'_'.ENTITY_CLIENT][$invoiceNumber] : null;
} }
protected function getVendorId($name) protected function getVendorId($name)
{ {
$name = strtolower($name); $name = strtolower($name);
return isset($this->maps[ENTITY_VENDOR][$name]) ? $this->maps[ENTITY_VENDOR][$name] : null; return isset($this->maps[ENTITY_VENDOR][$name]) ? $this->maps[ENTITY_VENDOR][$name] : null;
} }
} }

View File

@ -0,0 +1,22 @@
<?php namespace App\Ninja\Import\CSV;
use App\Ninja\Import\BaseTransformer;
use League\Fractal\Resource\Item;
class ProductTransformer extends BaseTransformer
{
public function transform($data)
{
if (empty($data->product_key) || $this->hasProduct($data->product_key)) {
return false;
}
return new Item($data, function ($data) {
return [
'product_key' => $this->getString($data, 'product_key'),
'notes' => $this->getString($data, 'notes'),
'cost' => $this->getNumber($data, 'cost'),
];
});
}
}

View File

@ -57,8 +57,9 @@ class DocumentRepository extends BaseRepository
return $query; return $query;
} }
public function upload($uploaded, &$doc_array=null) public function upload($data, &$doc_array=null)
{ {
$uploaded = $data['file'];
$extension = strtolower($uploaded->getClientOriginalExtension()); $extension = strtolower($uploaded->getClientOriginalExtension());
if(empty(Document::$types[$extension]) && !empty(Document::$extraExtensions[$extension])){ if(empty(Document::$types[$extension]) && !empty(Document::$extraExtensions[$extension])){
$documentType = Document::$extraExtensions[$extension]; $documentType = Document::$extraExtensions[$extension];
@ -81,12 +82,17 @@ class DocumentRepository extends BaseRepository
return 'File too large'; return 'File too large';
} }
// don't allow a document to be linked to both an invoice and an expense
if (array_get($data, 'invoice_id') && array_get($data, 'expense_id')) {
unset($data['expense_id']);
}
$hash = sha1_file($filePath); $hash = sha1_file($filePath);
$filename = \Auth::user()->account->account_key.'/'.$hash.'.'.$documentType; $filename = \Auth::user()->account->account_key.'/'.$hash.'.'.$documentType;
$document = Document::createNew(); $document = Document::createNew();
$document->fill($data);
$disk = $document->getDisk(); $disk = $document->getDisk();
if(!$disk->exists($filename)){// Have we already stored the same file if(!$disk->exists($filename)){// Have we already stored the same file
$stream = fopen($filePath, 'r'); $stream = fopen($filePath, 'r');

View File

@ -741,7 +741,7 @@ class InvoiceRepository extends BaseRepository
$invoice = Invoice::createNew($recurInvoice); $invoice = Invoice::createNew($recurInvoice);
$invoice->client_id = $recurInvoice->client_id; $invoice->client_id = $recurInvoice->client_id;
$invoice->recurring_invoice_id = $recurInvoice->id; $invoice->recurring_invoice_id = $recurInvoice->id;
$invoice->invoice_number = $recurInvoice->account->recurring_invoice_number_prefix . $recurInvoice->account->getNextInvoiceNumber($recurInvoice); $invoice->invoice_number = $recurInvoice->account->getNextInvoiceNumber($invoice);
$invoice->amount = $recurInvoice->amount; $invoice->amount = $recurInvoice->amount;
$invoice->balance = $recurInvoice->amount; $invoice->balance = $recurInvoice->amount;
$invoice->invoice_date = date_create()->format('Y-m-d'); $invoice->invoice_date = date_create()->format('Y-m-d');

View File

@ -11,6 +11,13 @@ class ProductRepository extends BaseRepository
return 'App\Models\Product'; return 'App\Models\Product';
} }
public function all()
{
return Product::scope()
->withTrashed()
->get();
}
public function find($accountId) public function find($accountId)
{ {
return DB::table('products') return DB::table('products')
@ -30,11 +37,11 @@ class ProductRepository extends BaseRepository
'products.deleted_at' 'products.deleted_at'
); );
} }
public function save($data, $product = null) public function save($data, $product = null)
{ {
$publicId = isset($data['public_id']) ? $data['public_id'] : false; $publicId = isset($data['public_id']) ? $data['public_id'] : false;
if ($product) { if ($product) {
// do nothing // do nothing
} elseif ($publicId) { } elseif ($publicId) {
@ -50,4 +57,4 @@ class ProductRepository extends BaseRepository
return $product; return $product;
} }
} }

View File

@ -29,8 +29,8 @@ class AppServiceProvider extends ServiceProvider {
else{ else{
$contents = $image; $contents = $image;
} }
return 'data:image/jpeg;base64,' . base64_encode($contents); return 'data:image/jpeg;base64,' . base64_encode($contents);
}); });
Form::macro('nav_link', function($url, $text, $url2 = '', $extra = '') { Form::macro('nav_link', function($url, $text, $url2 = '', $extra = '') {
@ -58,11 +58,11 @@ class AppServiceProvider extends ServiceProvider {
$str = '<li class="dropdown '.$class.'"> $str = '<li class="dropdown '.$class.'">
<a href="'.URL::to($types).'" class="dropdown-toggle">'.trans("texts.$types").'</a>'; <a href="'.URL::to($types).'" class="dropdown-toggle">'.trans("texts.$types").'</a>';
$items = []; $items = [];
if($user->can('create', $type))$items[] = '<li><a href="'.URL::to($types.'/create').'">'.trans("texts.new_$type").'</a></li>'; if($user->can('create', $type))$items[] = '<li><a href="'.URL::to($types.'/create').'">'.trans("texts.new_$type").'</a></li>';
if ($type == ENTITY_INVOICE) { if ($type == ENTITY_INVOICE) {
if(!empty($items))$items[] = '<li class="divider"></li>'; if(!empty($items))$items[] = '<li class="divider"></li>';
$items[] = '<li><a href="'.URL::to('recurring_invoices').'">'.trans("texts.recurring_invoices").'</a></li>'; $items[] = '<li><a href="'.URL::to('recurring_invoices').'">'.trans("texts.recurring_invoices").'</a></li>';
@ -81,7 +81,7 @@ class AppServiceProvider extends ServiceProvider {
$items[] = '<li><a href="'.URL::to('vendors').'">'.trans("texts.vendors").'</a></li>'; $items[] = '<li><a href="'.URL::to('vendors').'">'.trans("texts.vendors").'</a></li>';
if($user->can('create', ENTITY_VENDOR))$items[] = '<li><a href="'.URL::to('vendors/create').'">'.trans("texts.new_vendor").'</a></li>'; if($user->can('create', ENTITY_VENDOR))$items[] = '<li><a href="'.URL::to('vendors/create').'">'.trans("texts.new_vendor").'</a></li>';
} }
if(!empty($items)){ if(!empty($items)){
$str.= '<ul class="dropdown-menu" id="menu1">'.implode($items).'</ul>'; $str.= '<ul class="dropdown-menu" id="menu1">'.implode($items).'</ul>';
} }
@ -157,14 +157,14 @@ class AppServiceProvider extends ServiceProvider {
return $str . '</ol>'; return $str . '</ol>';
}); });
Form::macro('human_filesize', function($bytes, $decimals = 1) { Form::macro('human_filesize', function($bytes, $decimals = 1) {
$size = array('B','kB','MB','GB','TB','PB','EB','ZB','YB'); $size = array('B','kB','MB','GB','TB','PB','EB','ZB','YB');
$factor = floor((strlen($bytes) - 1) / 3); $factor = floor((strlen($bytes) - 1) / 3);
if($factor == 0)$decimals=0;// There aren't fractional bytes if($factor == 0)$decimals=0;// There aren't fractional bytes
return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . ' ' . @$size[$factor]; return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . ' ' . @$size[$factor];
}); });
Validator::extend('positive', function($attribute, $value, $parameters) { Validator::extend('positive', function($attribute, $value, $parameters) {
return Utils::parseFloat($value) >= 0; return Utils::parseFloat($value) >= 0;
}); });

View File

@ -13,6 +13,7 @@ use App\Ninja\Repositories\ContactRepository;
use App\Ninja\Repositories\ClientRepository; use App\Ninja\Repositories\ClientRepository;
use App\Ninja\Repositories\InvoiceRepository; use App\Ninja\Repositories\InvoiceRepository;
use App\Ninja\Repositories\PaymentRepository; use App\Ninja\Repositories\PaymentRepository;
use App\Ninja\Repositories\ProductRepository;
use App\Ninja\Serializers\ArraySerializer; use App\Ninja\Serializers\ArraySerializer;
use App\Models\Client; use App\Models\Client;
use App\Models\Invoice; use App\Models\Invoice;
@ -23,6 +24,7 @@ class ImportService
protected $invoiceRepo; protected $invoiceRepo;
protected $clientRepo; protected $clientRepo;
protected $contactRepo; protected $contactRepo;
protected $productRepo;
protected $processedRows = array(); protected $processedRows = array();
public static $entityTypes = [ public static $entityTypes = [
@ -31,6 +33,8 @@ class ImportService
ENTITY_INVOICE, ENTITY_INVOICE,
ENTITY_PAYMENT, ENTITY_PAYMENT,
ENTITY_TASK, ENTITY_TASK,
ENTITY_PRODUCT,
ENTITY_EXPENSE,
]; ];
public static $sources = [ public static $sources = [
@ -45,7 +49,14 @@ class ImportService
IMPORT_ZOHO, IMPORT_ZOHO,
]; ];
public function __construct(Manager $manager, ClientRepository $clientRepo, InvoiceRepository $invoiceRepo, PaymentRepository $paymentRepo, ContactRepository $contactRepo) public function __construct(
Manager $manager,
ClientRepository $clientRepo,
InvoiceRepository $invoiceRepo,
PaymentRepository $paymentRepo,
ContactRepository $contactRepo,
ProductRepository $productRepo
)
{ {
$this->fractal = $manager; $this->fractal = $manager;
$this->fractal->setSerializer(new ArraySerializer()); $this->fractal->setSerializer(new ArraySerializer());
@ -54,6 +65,7 @@ class ImportService
$this->invoiceRepo = $invoiceRepo; $this->invoiceRepo = $invoiceRepo;
$this->paymentRepo = $paymentRepo; $this->paymentRepo = $paymentRepo;
$this->contactRepo = $contactRepo; $this->contactRepo = $contactRepo;
$this->productRepo = $productRepo;
} }
public function import($source, $files) public function import($source, $files)
@ -216,8 +228,11 @@ class ImportService
'invoice_number' => 'required|unique:invoices,invoice_number,,id,account_id,'.Auth::user()->account_id, 'invoice_number' => 'required|unique:invoices,invoice_number,,id,account_id,'.Auth::user()->account_id,
'discount' => 'positive', 'discount' => 'positive',
]; ];
} else { }
return true; if ($entityType === ENTITY_PRODUCT) {
$rules = [
'product_key' => 'required',
];
} }
$validator = Validator::make($data, $rules); $validator = Validator::make($data, $rules);
@ -251,6 +266,14 @@ class ImportService
} }
} }
$productMap = [];
$products = $this->productRepo->all();
foreach ($products as $product) {
if ($key = strtolower(trim($product->product_key))) {
$productMap[$key] = $product->id;
}
}
$countryMap = []; $countryMap = [];
$countryMap2 = []; $countryMap2 = [];
$countries = Cache::get('countries'); $countries = Cache::get('countries');
@ -269,6 +292,7 @@ class ImportService
ENTITY_CLIENT => $clientMap, ENTITY_CLIENT => $clientMap,
ENTITY_INVOICE => $invoiceMap, ENTITY_INVOICE => $invoiceMap,
ENTITY_INVOICE.'_'.ENTITY_CLIENT => $invoiceClientMap, ENTITY_INVOICE.'_'.ENTITY_CLIENT => $invoiceClientMap,
ENTITY_PRODUCT => $productMap,
'countries' => $countryMap, 'countries' => $countryMap,
'countries2' => $countryMap2, 'countries2' => $countryMap2,
'currencies' => $currencyMap, 'currencies' => $currencyMap,
@ -280,13 +304,9 @@ class ImportService
$data = []; $data = [];
foreach ($files as $entityType => $filename) { foreach ($files as $entityType => $filename) {
if ($entityType === ENTITY_CLIENT) { $class = "App\\Models\\" . ucwords($entityType);
$columns = Client::getImportColumns(); $columns = $class::getImportColumns();
$map = Client::getImportMap(); $map = $class::getImportMap();
} else {
$columns = Invoice::getImportColumns();
$map = Invoice::getImportMap();
}
// Lookup field translations // Lookup field translations
foreach ($columns as $key => $value) { foreach ($columns as $key => $value) {
@ -452,12 +472,8 @@ class ImportService
private function convertToObject($entityType, $data, $map) private function convertToObject($entityType, $data, $map)
{ {
$obj = new stdClass(); $obj = new stdClass();
$class = "App\\Models\\" . ucwords($entityType);
if ($entityType === ENTITY_CLIENT) { $columns = $class::getImportColumns();
$columns = Client::getImportColumns();
} else {
$columns = Invoice::getImportColumns();
}
foreach ($columns as $column) { foreach ($columns as $column) {
$obj->$column = false; $obj->$column = false;

View File

@ -1314,6 +1314,13 @@ $LANG = array(
'wait_for_upload' => 'Please wait for the document upload to complete.', 'wait_for_upload' => 'Please wait for the document upload to complete.',
'upgrade_for_permissions' => 'Upgrade to our Enterprise plan to enable permissions.', 'upgrade_for_permissions' => 'Upgrade to our Enterprise plan to enable permissions.',
'enable_second_tax_rate' => 'Enable specifying a <b>second tax rate</b>', 'enable_second_tax_rate' => 'Enable specifying a <b>second tax rate</b>',
'payment_file' => 'Payment File',
'expense_file' => 'Expense File',
'product_file' => 'Product File',
'import_products' => 'Import Products',
'products_will_create' => 'products will be created.',
'product_key' => 'Product',
'created_products' => 'Successfully created :count product(s)',
); );

View File

@ -4,9 +4,7 @@
@parent @parent
<style type="text/css"> <style type="text/css">
.contact-file, .import-file {
.task-file,
.payment-file {
display: none; display: none;
} }
</style> </style>
@ -34,7 +32,7 @@
@foreach (\App\Services\ImportService::$entityTypes as $entityType) @foreach (\App\Services\ImportService::$entityTypes as $entityType)
{!! Former::file("{$entityType}_file") {!! Former::file("{$entityType}_file")
->addGroupClass("{$entityType}-file") !!} ->addGroupClass("import-file {$entityType}-file") !!}
@endforeach @endforeach
{!! Former::actions( Button::info(trans('texts.upload'))->submit()->large()->appendIcon(Icon::create('open'))) !!} {!! Former::actions( Button::info(trans('texts.upload'))->submit()->large()->appendIcon(Icon::create('open'))) !!}
@ -67,13 +65,17 @@
trans('texts.payments') => array('name' => ENTITY_PAYMENT, 'value' => 1), trans('texts.payments') => array('name' => ENTITY_PAYMENT, 'value' => 1),
])->check(ENTITY_CLIENT)->check(ENTITY_TASK)->check(ENTITY_INVOICE)->check(ENTITY_PAYMENT) !!} ])->check(ENTITY_CLIENT)->check(ENTITY_TASK)->check(ENTITY_INVOICE)->check(ENTITY_PAYMENT) !!}
{!! Former::actions( Button::primary(trans('texts.download'))->submit()->large()->appendIcon(Icon::create('download-alt'))) !!} {!! Former::actions( Button::primary(trans('texts.download'))->submit()->large()->appendIcon(Icon::create('download-alt'))) !!}
</div> </div>
</div> </div>
{!! Former::close() !!} {!! Former::close() !!}
<script type="text/javascript"> <script type="text/javascript">
$(function() {
setFileTypesVisible();
});
function setEntityTypesVisible() { function setEntityTypesVisible() {
var selector = '.entity-types input[type=checkbox]'; var selector = '.entity-types input[type=checkbox]';
if ($('#format').val() === 'JSON') { if ($('#format').val() === 'JSON') {
@ -103,4 +105,4 @@
</script> </script>
@stop @stop

View File

@ -7,18 +7,16 @@
{!! Former::open('/import_csv')->addClass('warn-on-exit') !!} {!! Former::open('/import_csv')->addClass('warn-on-exit') !!}
@if (isset($data[ENTITY_CLIENT])) @foreach (App\Services\ImportService::$entityTypes as $entityType)
@include('accounts.partials.map', $data[ENTITY_CLIENT]) @if (isset($data[$entityType]))
@endif @include('accounts.partials.map', $data[$entityType])
@endif
@endforeach
@if (isset($data[ENTITY_INVOICE])) {!! Former::actions(
@include('accounts.partials.map', $data[ENTITY_INVOICE])
@endif
{!! Former::actions(
Button::normal(trans('texts.cancel'))->large()->asLinkTo(URL::to('/settings/import_export'))->appendIcon(Icon::create('remove-circle')), Button::normal(trans('texts.cancel'))->large()->asLinkTo(URL::to('/settings/import_export'))->appendIcon(Icon::create('remove-circle')),
Button::success(trans('texts.import'))->submit()->large()->appendIcon(Icon::create('floppy-disk'))) !!} Button::success(trans('texts.import'))->submit()->large()->appendIcon(Icon::create('floppy-disk'))) !!}
{!! Former::close() !!} {!! Former::close() !!}
@stop @stop