mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-08 12:12:48 +01:00
Add support for invoice attachments
This commit is contained in:
parent
5640c74f35
commit
88808d44bf
@ -95,6 +95,7 @@ module.exports = function(grunt) {
|
||||
'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.no.min.js',
|
||||
'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.es.min.js',
|
||||
'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.sv.min.js',
|
||||
'public/vendor/dropzone/dist/min/dropzone.min.js',
|
||||
'public/vendor/typeahead.js/dist/typeahead.jquery.min.js',
|
||||
'public/vendor/accounting/accounting.min.js',
|
||||
'public/vendor/spectrum/spectrum.js',
|
||||
@ -137,6 +138,7 @@ module.exports = function(grunt) {
|
||||
'public/vendor/datatables-bootstrap3/BS3/assets/css/datatables.css',
|
||||
'public/vendor/font-awesome/css/font-awesome.min.css',
|
||||
'public/vendor/bootstrap-datepicker/dist/css/bootstrap-datepicker3.css',
|
||||
'public/vendor/dropzone/dist/min/dropzone.min.css',
|
||||
'public/vendor/spectrum/spectrum.css',
|
||||
'public/css/bootstrap-combobox.css',
|
||||
'public/css/typeahead.js-bootstrap.css',
|
||||
|
60
app/Http/Controllers/DocumentController.php
Normal file
60
app/Http/Controllers/DocumentController.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php namespace App\Http\Controllers;
|
||||
|
||||
use Datatable;
|
||||
use Input;
|
||||
use Redirect;
|
||||
use Session;
|
||||
use URL;
|
||||
use Utils;
|
||||
use View;
|
||||
use Validator;
|
||||
use Response;
|
||||
use App\Models\Document;
|
||||
use App\Ninja\Repositories\DocumentRepository;
|
||||
|
||||
class DocumentController extends BaseController
|
||||
{
|
||||
protected $documentRepo;
|
||||
protected $model = 'App\Models\Document';
|
||||
|
||||
public function __construct(DocumentRepository $documentRepo)
|
||||
{
|
||||
// parent::__construct();
|
||||
|
||||
$this->documentRepo = $documentRepo;
|
||||
}
|
||||
|
||||
public function get($publicId)
|
||||
{
|
||||
$document = Document::scope($publicId)
|
||||
->withTrashed()
|
||||
->firstOrFail();
|
||||
|
||||
if(!$this->checkViewPermission($document, $response)){
|
||||
return $response;
|
||||
}
|
||||
|
||||
$public_url = $document->getPublicUrl();
|
||||
if($public_url){
|
||||
return redirect($public_url);
|
||||
}
|
||||
|
||||
|
||||
$response = Response::make($document->getRaw(), 200);
|
||||
$response->header('content-type', $document->type);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function postUpload()
|
||||
{
|
||||
if(!$this->checkCreatePermission($response)){
|
||||
return $response;
|
||||
}
|
||||
|
||||
$document = Input::all();
|
||||
|
||||
$response = $this->documentRepo->upload($document);
|
||||
return $response;
|
||||
}
|
||||
}
|
@ -23,6 +23,7 @@ use App\Models\Activity;
|
||||
use App\Ninja\Mailers\ContactMailer as Mailer;
|
||||
use App\Ninja\Repositories\InvoiceRepository;
|
||||
use App\Ninja\Repositories\ClientRepository;
|
||||
use App\Ninja\Repositories\DocumentRepository;
|
||||
use App\Services\InvoiceService;
|
||||
use App\Services\RecurringInvoiceService;
|
||||
use App\Http\Requests\SaveInvoiceWithClientRequest;
|
||||
@ -32,11 +33,12 @@ class InvoiceController extends BaseController
|
||||
protected $mailer;
|
||||
protected $invoiceRepo;
|
||||
protected $clientRepo;
|
||||
protected $documentRepo;
|
||||
protected $invoiceService;
|
||||
protected $recurringInvoiceService;
|
||||
protected $model = 'App\Models\Invoice';
|
||||
|
||||
public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, InvoiceService $invoiceService, RecurringInvoiceService $recurringInvoiceService)
|
||||
public function __construct(Mailer $mailer, InvoiceRepository $invoiceRepo, ClientRepository $clientRepo, InvoiceService $invoiceService, DocumentRepository $documentRepo, RecurringInvoiceService $recurringInvoiceService)
|
||||
{
|
||||
// parent::__construct();
|
||||
|
||||
@ -89,7 +91,7 @@ class InvoiceController extends BaseController
|
||||
{
|
||||
$account = Auth::user()->account;
|
||||
$invoice = Invoice::scope($publicId)
|
||||
->with('invitations', 'account.country', 'client.contacts', 'client.country', 'invoice_items', 'payments')
|
||||
->with('invitations', 'account.country', 'client.contacts', 'client.country', 'invoice_items', 'documents', 'payments')
|
||||
->withTrashed()
|
||||
->firstOrFail();
|
||||
|
||||
@ -229,7 +231,7 @@ class InvoiceController extends BaseController
|
||||
return $response;
|
||||
}
|
||||
|
||||
$account = Auth::user()->account;
|
||||
$account = Auth::user()->account;
|
||||
$entityType = $isRecurring ? ENTITY_RECURRING_INVOICE : ENTITY_INVOICE;
|
||||
$clientId = null;
|
||||
|
||||
|
@ -132,6 +132,9 @@ Route::group(['middleware' => 'auth:user'], function() {
|
||||
Route::post('invoices/bulk', 'InvoiceController@bulk');
|
||||
Route::post('recurring_invoices/bulk', 'InvoiceController@bulk');
|
||||
|
||||
Route::get('document/{public_id}/{filename?}', 'DocumentController@get');
|
||||
Route::post('document', 'DocumentController@postUpload');
|
||||
|
||||
Route::get('quotes/create/{client_id?}', 'QuoteController@create');
|
||||
Route::get('quotes/{public_id}/clone', 'InvoiceController@cloneInvoice');
|
||||
Route::get('quotes/{public_id}/edit', 'InvoiceController@edit');
|
||||
@ -425,6 +428,7 @@ if (!defined('CONTACT_EMAIL')) {
|
||||
define('MAX_IFRAME_URL_LENGTH', 250);
|
||||
define('MAX_LOGO_FILE_SIZE', 200); // KB
|
||||
define('MAX_FAILED_LOGINS', 10);
|
||||
define('DEFAULT_MAX_DOCUMENT_SIZE', 10000);// KB
|
||||
define('DEFAULT_FONT_SIZE', 9);
|
||||
define('DEFAULT_HEADER_FONT', 1);// Roboto
|
||||
define('DEFAULT_BODY_FONT', 1);// Roboto
|
||||
|
102
app/Models/Document.php
Normal file
102
app/Models/Document.php
Normal file
@ -0,0 +1,102 @@
|
||||
<?php namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class Document extends EntityModel
|
||||
{
|
||||
public static $extensions = array(
|
||||
'png' => 'image/png',
|
||||
'jpg' => 'image/jpeg',
|
||||
'jpeg' => 'image/jpeg',
|
||||
'pdf' => 'application/pdf',
|
||||
'gif' => 'image/gif'
|
||||
);
|
||||
|
||||
public static $types = array(
|
||||
'image/png' => array(
|
||||
'extension' => 'png',
|
||||
'image' => true,
|
||||
),
|
||||
'image/jpeg' => array(
|
||||
'extension' => 'jpeg',
|
||||
'image' => true,
|
||||
),
|
||||
'image/gif' => array(
|
||||
'extension' => 'gif',
|
||||
'image' => true,
|
||||
),
|
||||
'application/pdf' => array(
|
||||
'extension' => 'pdf',
|
||||
),
|
||||
);
|
||||
|
||||
// Expenses
|
||||
use SoftDeletes;
|
||||
|
||||
protected $dates = ['deleted_at'];
|
||||
|
||||
public function fill(array $attributes)
|
||||
{
|
||||
parent::fill($attributes);
|
||||
|
||||
if(empty($this->attributes['disk'])){
|
||||
$this->attributes['disk'] = env('LOGO_DISK', 'documents');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function account()
|
||||
{
|
||||
return $this->belongsTo('App\Models\Account');
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo('App\Models\User');
|
||||
}
|
||||
|
||||
public function expense()
|
||||
{
|
||||
return $this->belongsTo('App\Models\Expense')->withTrashed();
|
||||
}
|
||||
|
||||
public function invoice()
|
||||
{
|
||||
return $this->belongsTo('App\Models\Invoice')->withTrashed();
|
||||
}
|
||||
|
||||
public function getDisk(){
|
||||
return Storage::disk(!empty($this->disk)?$this->disk:env('LOGO_DISK', 'documents'));
|
||||
}
|
||||
|
||||
public function setDiskAttribute($value)
|
||||
{
|
||||
$this->attributes['disk'] = $value?$value:env('LOGO_DISK', 'documents');
|
||||
}
|
||||
|
||||
public function getPublicUrl(){
|
||||
$disk = $this->getDisk();
|
||||
$adapter = $disk->getAdapter();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getRaw(){
|
||||
$disk = $this->getDisk();
|
||||
|
||||
return $disk->get($this->path);
|
||||
}
|
||||
|
||||
public function getUrl(){
|
||||
return url('document/'.$this->public_id.'/'.$this->name);
|
||||
}
|
||||
|
||||
public function toArray()
|
||||
{
|
||||
$array = parent::toArray();
|
||||
$array['url'] = $this->getUrl();
|
||||
return $array;
|
||||
}
|
||||
}
|
@ -175,6 +175,11 @@ class Invoice extends EntityModel implements BalanceAffecting
|
||||
return $this->hasMany('App\Models\InvoiceItem')->orderBy('id');
|
||||
}
|
||||
|
||||
public function documents()
|
||||
{
|
||||
return $this->hasMany('App\Models\Document')->orderBy('id');
|
||||
}
|
||||
|
||||
public function invoice_status()
|
||||
{
|
||||
return $this->belongsTo('App\Models\InvoiceStatus');
|
||||
@ -385,6 +390,7 @@ class Invoice extends EntityModel implements BalanceAffecting
|
||||
'amount',
|
||||
'balance',
|
||||
'invoice_items',
|
||||
'documents',
|
||||
'client',
|
||||
'tax_name',
|
||||
'tax_rate',
|
||||
|
128
app/Ninja/Repositories/DocumentRepository.php
Normal file
128
app/Ninja/Repositories/DocumentRepository.php
Normal file
@ -0,0 +1,128 @@
|
||||
<?php namespace app\Ninja\Repositories;
|
||||
|
||||
use DB;
|
||||
use Utils;
|
||||
use Response;
|
||||
use App\Models\Document;
|
||||
use App\Ninja\Repositories\BaseRepository;
|
||||
use Intervention\Image\Facades\Image;
|
||||
use Session;
|
||||
|
||||
class DocumentRepository extends BaseRepository
|
||||
{
|
||||
// Expenses
|
||||
public function getClassName()
|
||||
{
|
||||
return 'App\Models\Document';
|
||||
}
|
||||
|
||||
public function all()
|
||||
{
|
||||
return Document::scope()
|
||||
->with('user')
|
||||
->withTrashed()
|
||||
->where('is_deleted', '=', false)
|
||||
->get();
|
||||
}
|
||||
|
||||
public function find()
|
||||
{
|
||||
$accountid = \Auth::user()->account_id;
|
||||
$query = DB::table('clients')
|
||||
->join('accounts', 'accounts.id', '=', 'clients.account_id')
|
||||
->leftjoin('clients', 'clients.id', '=', 'clients.client_id')
|
||||
/*->leftJoin('expenses', 'expenses.id', '=', 'clients.expense_id')
|
||||
->leftJoin('invoices', 'invoices.id', '=', 'clients.invoice_id')*/
|
||||
->where('documents.account_id', '=', $accountid)
|
||||
/*->where('vendors.deleted_at', '=', null)
|
||||
->where('clients.deleted_at', '=', null)*/
|
||||
->select(
|
||||
'documents.account_id',
|
||||
'documents.path',
|
||||
'documents.deleted_at',
|
||||
'documents.size',
|
||||
'documents.width',
|
||||
'documents.height',
|
||||
'documents.id',
|
||||
'documents.is_deleted',
|
||||
'documents.public_id',
|
||||
'documents.invoice_id',
|
||||
'documents.expense_id',
|
||||
'documents.user_id',
|
||||
'invoices.public_id as invoice_public_id',
|
||||
'invoices.user_id as invoice_user_id',
|
||||
'expenses.public_id as expense_public_id',
|
||||
'expenses.user_id as expense_user_id'
|
||||
);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function upload($input)
|
||||
{
|
||||
$uploaded = $input['file'];
|
||||
|
||||
$extension = strtolower($uploaded->extension());
|
||||
if(empty(Document::$extensions[$extension])){
|
||||
return Response::json([
|
||||
'error' => 'Unsupported extension',
|
||||
'code' => 400
|
||||
], 400);
|
||||
}
|
||||
|
||||
$documentType = Document::$extensions[$extension];
|
||||
$filePath = $uploaded->path();
|
||||
$fileContents = null;
|
||||
$name = $uploaded->getClientOriginalName();
|
||||
|
||||
if(filesize($filePath)/1000 > env('MAX_DOCUMENT_SIZE', DEFAULT_MAX_DOCUMENT_SIZE)){
|
||||
return Response::json([
|
||||
'error' => 'File too large',
|
||||
'code' => 400
|
||||
], 400);
|
||||
}
|
||||
|
||||
if($documentType == 'image/gif'){
|
||||
// Convert gif to png
|
||||
$img = Image::make($filePath);
|
||||
|
||||
$fileContents = (string) $img->encode('png');
|
||||
$documentType = 'image/png';
|
||||
$name = pathinfo($name)['filename'].'.png';
|
||||
}
|
||||
|
||||
$documentTypeData = Document::$types[$documentType];
|
||||
|
||||
|
||||
$hash = $fileContents?sha1($fileContents):sha1_file($filePath);
|
||||
$filename = \Auth::user()->account->account_key.'/'.$hash.'.'.$documentTypeData['extension'];
|
||||
|
||||
$document = Document::createNew();
|
||||
$disk = $document->getDisk();
|
||||
if(!$disk->exists($filename)){// Have we already stored the same file
|
||||
$disk->put($filename, $fileContents?$fileContents:file_get_contents($filePath));
|
||||
}
|
||||
|
||||
$document->path = $filename;
|
||||
$document->type = $documentType;
|
||||
$document->size = $fileContents?strlen($fileContents):filesize($filePath);
|
||||
$document->name = substr($name, -255);
|
||||
|
||||
if(!empty($documentTypeData['image'])){
|
||||
$imageSize = getimagesize($filePath);
|
||||
if($imageSize){
|
||||
$document->width = $imageSize[0];
|
||||
$document->height = $imageSize[1];
|
||||
}
|
||||
}
|
||||
|
||||
$document->save();
|
||||
|
||||
|
||||
return Response::json([
|
||||
'error' => false,
|
||||
'document' => $document,
|
||||
'code' => 200
|
||||
], 200);
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ use App\Models\InvoiceItem;
|
||||
use App\Models\Invitation;
|
||||
use App\Models\Product;
|
||||
use App\Models\Task;
|
||||
use App\Models\Document;
|
||||
use App\Models\Expense;
|
||||
use App\Services\PaymentService;
|
||||
use App\Ninja\Repositories\BaseRepository;
|
||||
@ -397,6 +398,23 @@ class InvoiceRepository extends BaseRepository
|
||||
if ($publicId) {
|
||||
$invoice->invoice_items()->forceDelete();
|
||||
}
|
||||
|
||||
foreach ($data['documents'] as $document_id){
|
||||
$document = Document::scope($document_id)->first();
|
||||
if($document && !$checkSubPermissions || $document->canEdit()){
|
||||
$document->invoice_id = $invoice->id;
|
||||
$document->save();
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($invoice->documents as $document){
|
||||
if(!in_array($document->id, $data['documents'])){
|
||||
// Removed
|
||||
if(!$checkSubPermissions || $document->canEdit()){
|
||||
$document->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($data['invoice_items'] as $item) {
|
||||
$item = (array) $item;
|
||||
|
@ -26,7 +26,8 @@
|
||||
"quill": "~0.20.0",
|
||||
"datetimepicker": "~2.4.5",
|
||||
"stacktrace-js": "~1.0.1",
|
||||
"fuse.js": "~2.0.2"
|
||||
"fuse.js": "~2.0.2",
|
||||
"dropzone": "~4.3.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"jquery": "~1.11"
|
||||
|
66
database/migrations/2016_03_22_168362_add_documents.php
Normal file
66
database/migrations/2016_03_22_168362_add_documents.php
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddDocuments extends Migration {
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
/*Schema::table('accounts', function($table) {
|
||||
$table->string('logo')->nullable()->default(null);
|
||||
$table->unsignedInteger('logo_width');
|
||||
$table->unsignedInteger('logo_height');
|
||||
$table->unsignedInteger('logo_size');
|
||||
});
|
||||
|
||||
DB::table('accounts')->update(array('logo' => ''));*/
|
||||
Schema::dropIfExists('documents');
|
||||
Schema::create('documents', function($t)
|
||||
{
|
||||
$t->increments('id');
|
||||
$t->unsignedInteger('public_id')->nullable();
|
||||
$t->unsignedInteger('account_id');
|
||||
$t->unsignedInteger('user_id');
|
||||
$t->unsignedInteger('invoice_id')->nullable();
|
||||
$t->unsignedInteger('expense_id')->nullable();
|
||||
$t->string('path');
|
||||
$t->string('name');
|
||||
$t->string('type');
|
||||
$t->string('disk');
|
||||
$t->unsignedInteger('size');
|
||||
$t->unsignedInteger('width')->nullable();
|
||||
$t->unsignedInteger('height')->nullable();
|
||||
|
||||
$t->timestamps();
|
||||
$t->softDeletes();
|
||||
|
||||
$t->foreign('account_id')->references('id')->on('accounts');
|
||||
$t->foreign('user_id')->references('id')->on('users');
|
||||
$t->foreign('invoice_id')->references('id')->on('invoices');
|
||||
$t->foreign('expense_id')->references('id')->on('expenses');
|
||||
|
||||
|
||||
$t->unique( array('account_id','public_id') );
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('accounts', function($table) {
|
||||
$table->dropColumn('logo');
|
||||
$table->dropColumn('logo_width');
|
||||
$table->dropColumn('logo_height');
|
||||
$table->dropColumn('logo_size');
|
||||
});
|
||||
|
||||
Schema::dropIfExists('documents');
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
<?php
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use DB;
|
||||
|
||||
class AddDocuments extends Migration {
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
/*Schema::table('accounts', function($table) {
|
||||
$table->string('logo')->nullable()->default(null);
|
||||
$table->unsignedInteger('logo_width');
|
||||
$table->unsignedInteger('logo_height');
|
||||
$table->unsignedInteger('logo_size');
|
||||
});*/
|
||||
|
||||
DB::table('accounts')->update(array('logo' => ''));
|
||||
}
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('accounts', function($table) {
|
||||
$table->dropColumn('logo');
|
||||
$table->dropColumn('logo_width');
|
||||
$table->dropColumn('logo_height');
|
||||
$table->dropColumn('logo_size');
|
||||
});
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
25
public/css/built.css
vendored
25
public/css/built.css
vendored
File diff suppressed because one or more lines are too long
23
public/css/style.css
vendored
23
public/css/style.css
vendored
@ -1061,4 +1061,27 @@ td.right {
|
||||
|
||||
div.panel-body div.panel-body {
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
/* Attached Documents */
|
||||
.dropzone {
|
||||
border:1px solid #ebe7e7;
|
||||
background:#f9f9f9 !important;
|
||||
border-radius:3px;
|
||||
}
|
||||
|
||||
.dropzone .dz-preview.dz-image-preview{
|
||||
background:none;
|
||||
}
|
||||
|
||||
.dropzone .dz-preview .dz-image{
|
||||
width:119px;
|
||||
height:119px;
|
||||
border-radius:5px!important;
|
||||
}
|
||||
|
||||
.dropzone .dz-preview.dz-image-preview .dz-image img{
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
@ -1097,6 +1097,10 @@ $LANG = array(
|
||||
'november' => 'November',
|
||||
'december' => 'December',
|
||||
|
||||
// Documents
|
||||
'invoice_documents' => 'Attached Documents',
|
||||
'document_upload_message' => 'Drop files here or click to upload.'
|
||||
|
||||
);
|
||||
|
||||
return $LANG;
|
||||
|
@ -270,6 +270,7 @@
|
||||
<li role="presentation" class="active"><a href="#notes" aria-controls="notes" role="tab" data-toggle="tab">{{ trans('texts.note_to_client') }}</a></li>
|
||||
<li role="presentation"><a href="#terms" aria-controls="terms" role="tab" data-toggle="tab">{{ trans("texts.{$entityType}_terms") }}</a></li>
|
||||
<li role="presentation"><a href="#footer" aria-controls="footer" role="tab" data-toggle="tab">{{ trans("texts.{$entityType}_footer") }}</a></li>
|
||||
<li role="presentation"><a href="#attached-documents" aria-controls="attached-documents" role="tab" data-toggle="tab">{{ trans("texts.{$entityType}_documents") }}</a></li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
@ -301,6 +302,16 @@
|
||||
</div>
|
||||
</div>') !!}
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="attached-documents" style="position:relative;z-index:9">
|
||||
<div id="document-upload" class="dropzone">
|
||||
<div class="fallback">
|
||||
<input name="file" type="file" multiple />
|
||||
</div>
|
||||
</div>
|
||||
<div data-bind="foreach: documents">
|
||||
<input type="hidden" name="documents[]" data-bind="value: public_id">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -675,10 +686,12 @@
|
||||
@include('invoices.knockout')
|
||||
|
||||
<script type="text/javascript">
|
||||
Dropzone.autoDiscover = false;
|
||||
|
||||
var products = {!! $products !!};
|
||||
var clients = {!! $clients !!};
|
||||
var account = {!! Auth::user()->account !!};
|
||||
var dropzone;
|
||||
|
||||
var clientMap = {};
|
||||
var $clientSelect = $('select#client');
|
||||
@ -905,6 +918,43 @@
|
||||
@endif
|
||||
|
||||
applyComboboxListeners();
|
||||
|
||||
// Initialize document upload
|
||||
dropzone = new Dropzone('#document-upload', {
|
||||
url:{!! json_encode(url('document')) !!},
|
||||
params:{
|
||||
_token:"{{ Session::getToken() }}"
|
||||
},
|
||||
acceptedFiles:{!! json_encode(implode(',',array_keys(\App\Models\Document::$types))) !!},
|
||||
addRemoveLinks:true,
|
||||
maxFileSize:{{floatval(env('MAX_DOCUMENT_SIZE', DEFAULT_MAX_DOCUMENT_SIZE)/1000)}},
|
||||
dictDefaultMessage:{!! json_encode(trans('texts.document_upload_message')) !!}
|
||||
});
|
||||
dropzone.on("addedfile",handleDocumentAdded);
|
||||
dropzone.on("removedfile",handleDocumentRemoved);
|
||||
dropzone.on("success",handleDocumentUploaded);
|
||||
|
||||
for (var i=0; i<model.invoice().documents().length; i++) {
|
||||
var document = model.invoice().documents()[i];
|
||||
var mockFile = {
|
||||
name:document.name(),
|
||||
size:document.size(),
|
||||
type:document.type(),
|
||||
public_id:document.public_id(),
|
||||
status:Dropzone.SUCCESS,
|
||||
accepted:true,
|
||||
url:document.url(),
|
||||
mock:true,
|
||||
index:i
|
||||
};
|
||||
|
||||
dropzone.emit('addedfile', mockFile);
|
||||
dropzone.emit('complete', mockFile);
|
||||
if(document.type().match(/image.*/)){
|
||||
dropzone.emit('thumbnail', mockFile, document.url());
|
||||
}
|
||||
dropzone.files.push(mockFile);
|
||||
}
|
||||
});
|
||||
|
||||
function onFrequencyChange(){
|
||||
@ -1262,6 +1312,21 @@
|
||||
number = number.replace('{$custom2}', client.custom_value2 ? client.custom_value1 : '');
|
||||
model.invoice().invoice_number(number);
|
||||
}
|
||||
|
||||
function handleDocumentAdded(file){
|
||||
if(file.mock)return;
|
||||
file.index = model.invoice().documents().length;
|
||||
model.invoice().addDocument({name:file.name, size:file.size, type:file.type});
|
||||
}
|
||||
|
||||
function handleDocumentRemoved(file){
|
||||
model.invoice().removeDocument(file.public_id);
|
||||
}
|
||||
|
||||
function handleDocumentUploaded(file, response){
|
||||
file.public_id = response.document.public_id
|
||||
model.invoice().documents()[file.index].update(response.document);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
@ -226,6 +226,7 @@ function InvoiceModel(data) {
|
||||
self.auto_bill = ko.observable();
|
||||
self.invoice_status_id = ko.observable(0);
|
||||
self.invoice_items = ko.observableArray();
|
||||
self.documents = ko.observableArray();
|
||||
self.amount = ko.observable(0);
|
||||
self.balance = ko.observable(0);
|
||||
self.invoice_design_id = ko.observable(1);
|
||||
@ -251,6 +252,11 @@ function InvoiceModel(data) {
|
||||
return new ItemModel(options.data);
|
||||
}
|
||||
},
|
||||
'documents': {
|
||||
create: function(options) {
|
||||
return new DocumentModel(options.data);
|
||||
}
|
||||
},
|
||||
'tax': {
|
||||
create: function(options) {
|
||||
return new TaxRateModel(options.data);
|
||||
@ -267,6 +273,18 @@ function InvoiceModel(data) {
|
||||
applyComboboxListeners();
|
||||
return itemModel;
|
||||
}
|
||||
|
||||
self.addDocument = function() {
|
||||
var documentModel = new DocumentModel();
|
||||
self.documents.push(documentModel);
|
||||
return documentModel;
|
||||
}
|
||||
|
||||
self.removeDocument = function(public_id) {
|
||||
self.documents.remove(function(document) {
|
||||
return document.public_id() == public_id;
|
||||
});
|
||||
}
|
||||
|
||||
if (data) {
|
||||
ko.mapping.fromJS(data, self.mapping, self);
|
||||
@ -810,6 +828,23 @@ function ItemModel(data) {
|
||||
|
||||
this.onSelect = function() {}
|
||||
}
|
||||
|
||||
function DocumentModel(data) {
|
||||
var self = this;
|
||||
self.public_id = ko.observable(0);
|
||||
self.size = ko.observable(0);
|
||||
self.name = ko.observable('');
|
||||
self.type = ko.observable('');
|
||||
self.url = ko.observable('');
|
||||
|
||||
self.update = function(data){
|
||||
ko.mapping.fromJS(data, {}, this);
|
||||
}
|
||||
|
||||
if (data) {
|
||||
self.update(data);
|
||||
}
|
||||
}
|
||||
|
||||
/* Custom binding for product key typeahead */
|
||||
ko.bindingHandlers.typeahead = {
|
||||
|
Loading…
Reference in New Issue
Block a user