mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-07 15:34:39 -04:00
Allow documents to be attached to expenses
This commit is contained in:
parent
bff6782026
commit
5e62d7d296
@ -99,7 +99,7 @@ class ExpenseController extends BaseController
|
|||||||
|
|
||||||
public function edit($publicId)
|
public function edit($publicId)
|
||||||
{
|
{
|
||||||
$expense = Expense::scope($publicId)->firstOrFail();
|
$expense = Expense::scope($publicId)->with('documents')->firstOrFail();
|
||||||
|
|
||||||
if(!$this->checkEditPermission($expense, $response)){
|
if(!$this->checkEditPermission($expense, $response)){
|
||||||
return $response;
|
return $response;
|
||||||
@ -163,7 +163,14 @@ class ExpenseController extends BaseController
|
|||||||
*/
|
*/
|
||||||
public function update(UpdateExpenseRequest $request)
|
public function update(UpdateExpenseRequest $request)
|
||||||
{
|
{
|
||||||
$expense = $this->expenseService->save($request->input());
|
$data = $request->input();
|
||||||
|
$data['documents'] = $request->file('documents');
|
||||||
|
|
||||||
|
if(!$this->checkUpdatePermission($data, $response)){
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
$expense = $this->expenseService->save($data, true);
|
||||||
|
|
||||||
Session::flash('message', trans('texts.updated_expense'));
|
Session::flash('message', trans('texts.updated_expense'));
|
||||||
|
|
||||||
@ -195,7 +202,6 @@ class ExpenseController extends BaseController
|
|||||||
$expenses = Expense::scope($ids)->with('client')->get();
|
$expenses = Expense::scope($ids)->with('client')->get();
|
||||||
$clientPublicId = null;
|
$clientPublicId = null;
|
||||||
$currencyId = null;
|
$currencyId = null;
|
||||||
$data = [];
|
|
||||||
|
|
||||||
// Validate that either all expenses do not have a client or if there is a client, it is the same client
|
// Validate that either all expenses do not have a client or if there is a client, it is the same client
|
||||||
foreach ($expenses as $expense)
|
foreach ($expenses as $expense)
|
||||||
@ -220,19 +226,11 @@ class ExpenseController extends BaseController
|
|||||||
Session::flash('error', trans('texts.expense_error_invoiced'));
|
Session::flash('error', trans('texts.expense_error_invoiced'));
|
||||||
return Redirect::to('expenses');
|
return Redirect::to('expenses');
|
||||||
}
|
}
|
||||||
|
|
||||||
$account = Auth::user()->account;
|
|
||||||
$data[] = [
|
|
||||||
'publicId' => $expense->public_id,
|
|
||||||
'description' => $expense->public_notes,
|
|
||||||
'qty' => 1,
|
|
||||||
'cost' => $expense->present()->converted_amount,
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Redirect::to("invoices/create/{$clientPublicId}")
|
return Redirect::to("invoices/create/{$clientPublicId}")
|
||||||
->with('expenseCurrencyId', $currencyId)
|
->with('expenseCurrencyId', $currencyId)
|
||||||
->with('expenses', $data);
|
->with('expenses', $ids);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -17,6 +17,7 @@ use App\Models\Invoice;
|
|||||||
use App\Models\Client;
|
use App\Models\Client;
|
||||||
use App\Models\Account;
|
use App\Models\Account;
|
||||||
use App\Models\Product;
|
use App\Models\Product;
|
||||||
|
use App\Models\Expense;
|
||||||
use App\Models\TaxRate;
|
use App\Models\TaxRate;
|
||||||
use App\Models\InvoiceDesign;
|
use App\Models\InvoiceDesign;
|
||||||
use App\Models\Activity;
|
use App\Models\Activity;
|
||||||
@ -91,7 +92,7 @@ class InvoiceController extends BaseController
|
|||||||
{
|
{
|
||||||
$account = Auth::user()->account;
|
$account = Auth::user()->account;
|
||||||
$invoice = Invoice::scope($publicId)
|
$invoice = Invoice::scope($publicId)
|
||||||
->with('invitations', 'account.country', 'client.contacts', 'client.country', 'invoice_items', 'documents', 'payments')
|
->with('invitations', 'account.country', 'client.contacts', 'client.country', 'invoice_items', 'documents', 'expenses', 'expenses.documents', 'payments')
|
||||||
->withTrashed()
|
->withTrashed()
|
||||||
->firstOrFail();
|
->firstOrFail();
|
||||||
|
|
||||||
@ -242,6 +243,12 @@ class InvoiceController extends BaseController
|
|||||||
$invoice = $account->createInvoice($entityType, $clientId);
|
$invoice = $account->createInvoice($entityType, $clientId);
|
||||||
$invoice->public_id = 0;
|
$invoice->public_id = 0;
|
||||||
|
|
||||||
|
$invoice->expenses = Expense::scope([2])->with('documents')->get();
|
||||||
|
if(Session::get('expenses')){
|
||||||
|
$invoice->expenses = Expense::scope(Session::get('expenses'))->with('documents')->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
$clients = Client::scope()->with('contacts', 'country')->orderBy('name');
|
$clients = Client::scope()->with('contacts', 'country')->orderBy('name');
|
||||||
if(!Auth::user()->hasPermission('view_all')){
|
if(!Auth::user()->hasPermission('view_all')){
|
||||||
$clients = $clients->where('clients.user_id', '=', Auth::user()->id);
|
$clients = $clients->where('clients.user_id', '=', Auth::user()->id);
|
||||||
@ -352,7 +359,6 @@ class InvoiceController extends BaseController
|
|||||||
'recurringDueDateHelp' => $recurringDueDateHelp,
|
'recurringDueDateHelp' => $recurringDueDateHelp,
|
||||||
'invoiceLabels' => Auth::user()->account->getInvoiceLabels(),
|
'invoiceLabels' => Auth::user()->account->getInvoiceLabels(),
|
||||||
'tasks' => Session::get('tasks') ? json_encode(Session::get('tasks')) : null,
|
'tasks' => Session::get('tasks') ? json_encode(Session::get('tasks')) : null,
|
||||||
'expenses' => Session::get('expenses') ? json_encode(Session::get('expenses')) : null,
|
|
||||||
'expenseCurrencyId' => Session::get('expenseCurrencyId') ?: null,
|
'expenseCurrencyId' => Session::get('expenseCurrencyId') ?: null,
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -537,7 +543,7 @@ class InvoiceController extends BaseController
|
|||||||
public function invoiceHistory($publicId)
|
public function invoiceHistory($publicId)
|
||||||
{
|
{
|
||||||
$invoice = Invoice::withTrashed()->scope($publicId)->firstOrFail();
|
$invoice = Invoice::withTrashed()->scope($publicId)->firstOrFail();
|
||||||
$invoice->load('user', 'invoice_items', 'documents', 'account.country', 'client.contacts', 'client.country');
|
$invoice->load('user', 'invoice_items', 'documents', 'expenses', 'expenses.documents', 'account.country', 'client.contacts', 'client.country');
|
||||||
$invoice->invoice_date = Utils::fromSqlDate($invoice->invoice_date);
|
$invoice->invoice_date = Utils::fromSqlDate($invoice->invoice_date);
|
||||||
$invoice->due_date = Utils::fromSqlDate($invoice->due_date);
|
$invoice->due_date = Utils::fromSqlDate($invoice->due_date);
|
||||||
$invoice->is_pro = Auth::user()->isPro();
|
$invoice->is_pro = Auth::user()->isPro();
|
||||||
|
@ -180,12 +180,6 @@ class PublicClientController extends BaseController
|
|||||||
return $paymentTypes;
|
return $paymentTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function humanFilesize($bytes, $decimals = 2) {
|
|
||||||
$size = array('B','kB','MB','GB','TB','PB','EB','ZB','YB');
|
|
||||||
$factor = floor((strlen($bytes) - 1) / 3);
|
|
||||||
return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @$size[$factor];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function download($invitationKey)
|
public function download($invitationKey)
|
||||||
{
|
{
|
||||||
if (!$invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey)) {
|
if (!$invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey)) {
|
||||||
@ -473,7 +467,13 @@ class PublicClientController extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected function getInvoiceZipDocuments($invoice, &$size=0){
|
protected function getInvoiceZipDocuments($invoice, &$size=0){
|
||||||
$documents = $invoice->documents->sortBy('size');
|
$documents = $invoice->documents;
|
||||||
|
|
||||||
|
foreach($invoice->expenses as $expense){
|
||||||
|
$documents = $documents->merge($expense->documents);
|
||||||
|
}
|
||||||
|
|
||||||
|
$documents = $documents->sortBy('size');
|
||||||
|
|
||||||
$size = 0;
|
$size = 0;
|
||||||
$maxSize = MAX_ZIP_DOCUMENTS_SIZE * 1000;
|
$maxSize = MAX_ZIP_DOCUMENTS_SIZE * 1000;
|
||||||
@ -520,10 +520,6 @@ class PublicClientController extends BaseController
|
|||||||
|
|
||||||
$invoice = $invitation->invoice;
|
$invoice = $invitation->invoice;
|
||||||
|
|
||||||
if(!count($invoice->documents)){
|
|
||||||
return Response::view('error', array('error'=>'No documents'), 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
$toZip = $this->getInvoiceZipDocuments($invoice);
|
$toZip = $this->getInvoiceZipDocuments($invoice);
|
||||||
|
|
||||||
if(!count($toZip)){
|
if(!count($toZip)){
|
||||||
|
@ -200,8 +200,10 @@ class Document extends EntityModel
|
|||||||
public function toArray()
|
public function toArray()
|
||||||
{
|
{
|
||||||
$array = parent::toArray();
|
$array = parent::toArray();
|
||||||
$array['url'] = $this->getUrl();
|
|
||||||
$array['preview_url'] = $this->getPreviewUrl();
|
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();
|
||||||
|
|
||||||
return $array;
|
return $array;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,6 +53,11 @@ class Expense extends EntityModel
|
|||||||
return $this->belongsTo('App\Models\Invoice')->withTrashed();
|
return $this->belongsTo('App\Models\Invoice')->withTrashed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function documents()
|
||||||
|
{
|
||||||
|
return $this->hasMany('App\Models\Document')->orderBy('id');
|
||||||
|
}
|
||||||
|
|
||||||
public function getName()
|
public function getName()
|
||||||
{
|
{
|
||||||
if($this->expense_number)
|
if($this->expense_number)
|
||||||
@ -80,6 +85,20 @@ class Expense extends EntityModel
|
|||||||
{
|
{
|
||||||
return $this->invoice_currency_id != $this->expense_currency_id;
|
return $this->invoice_currency_id != $this->expense_currency_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function convertedAmount()
|
||||||
|
{
|
||||||
|
return round($this->amount * $this->exchange_rate, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toArray()
|
||||||
|
{
|
||||||
|
$array = parent::toArray();
|
||||||
|
|
||||||
|
if(empty($this->visible) || in_array('converted_amount', $this->visible))$array['previewconverted_amount_url'] = $this->convertedAmount();
|
||||||
|
|
||||||
|
return $array;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Expense::creating(function ($expense) {
|
Expense::creating(function ($expense) {
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
use Utils;
|
use Utils;
|
||||||
use DateTime;
|
use DateTime;
|
||||||
|
use URL;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Laracasts\Presenter\PresentableTrait;
|
use Laracasts\Presenter\PresentableTrait;
|
||||||
use App\Models\BalanceAffecting;
|
use App\Models\BalanceAffecting;
|
||||||
@ -391,6 +392,7 @@ class Invoice extends EntityModel implements BalanceAffecting
|
|||||||
'balance',
|
'balance',
|
||||||
'invoice_items',
|
'invoice_items',
|
||||||
'documents',
|
'documents',
|
||||||
|
'expenses',
|
||||||
'client',
|
'client',
|
||||||
'tax_name',
|
'tax_name',
|
||||||
'tax_rate',
|
'tax_rate',
|
||||||
@ -463,6 +465,7 @@ class Invoice extends EntityModel implements BalanceAffecting
|
|||||||
'custom_invoice_text_label2',
|
'custom_invoice_text_label2',
|
||||||
'custom_invoice_item_label1',
|
'custom_invoice_item_label1',
|
||||||
'custom_invoice_item_label2',
|
'custom_invoice_item_label2',
|
||||||
|
'invoice_embed_documents'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
foreach ($this->invoice_items as $invoiceItem) {
|
foreach ($this->invoice_items as $invoiceItem) {
|
||||||
@ -487,6 +490,26 @@ class Invoice extends EntityModel implements BalanceAffecting
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach ($this->documents as $document) {
|
||||||
|
$document->setVisible([
|
||||||
|
'public_id',
|
||||||
|
'name',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->expenses as $expense) {
|
||||||
|
$expense->setVisible([
|
||||||
|
'documents',
|
||||||
|
]);
|
||||||
|
|
||||||
|
foreach ($expense->documents as $document) {
|
||||||
|
$document->setVisible([
|
||||||
|
'public_id',
|
||||||
|
'name',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -867,6 +890,18 @@ class Invoice extends EntityModel implements BalanceAffecting
|
|||||||
|
|
||||||
return $taxes;
|
return $taxes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function hasDocuments(){
|
||||||
|
if(count($this->documents))return true;
|
||||||
|
return $this->hasExpenseDocuments();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasExpenseDocuments(){
|
||||||
|
foreach($this->expenses as $expense){
|
||||||
|
if(count($expense->documents))return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Invoice::creating(function ($invoice) {
|
Invoice::creating(function ($invoice) {
|
||||||
|
@ -63,8 +63,14 @@ class ContactMailer extends Mailer
|
|||||||
}
|
}
|
||||||
|
|
||||||
$documentStrings = array();
|
$documentStrings = array();
|
||||||
if ($account->document_email_attachment && !empty($invoice->documents)) {
|
if ($account->document_email_attachment && $invoice->hasDocuments()) {
|
||||||
$documents = $invoice->documents->sortBy('size');
|
$documents = $invoice->documents;
|
||||||
|
|
||||||
|
foreach($invoice->expenses as $expense){
|
||||||
|
$documents = $documents->merge($expense->documents);
|
||||||
|
}
|
||||||
|
|
||||||
|
$documents = $documents->sortBy('size');
|
||||||
|
|
||||||
$size = 0;
|
$size = 0;
|
||||||
$maxSize = MAX_EMAIL_DOCUMENTS_SIZE * 1000;
|
$maxSize = MAX_EMAIL_DOCUMENTS_SIZE * 1000;
|
||||||
@ -285,11 +291,16 @@ class ContactMailer extends Mailer
|
|||||||
$passwordHTML = isset($data['password'])?'<p>'.trans('texts.password').': '.$data['password'].'<p>':false;
|
$passwordHTML = isset($data['password'])?'<p>'.trans('texts.password').': '.$data['password'].'<p>':false;
|
||||||
$documentsHTML = '';
|
$documentsHTML = '';
|
||||||
|
|
||||||
if($account->isPro() && count($invoice->documents)){
|
if($account->isPro() && $invoice->hasDocuments()){
|
||||||
$documentsHTML .= trans('texts.email_documents_header').'<ul>';
|
$documentsHTML .= trans('texts.email_documents_header').'<ul>';
|
||||||
foreach($invoice->documents as $document){
|
foreach($invoice->documents as $document){
|
||||||
$documentsHTML .= '<li><a href="'.HTML::entities($document->getClientUrl($invitation)).'">'.HTML::entities($document->name).'</a></li>';
|
$documentsHTML .= '<li><a href="'.HTML::entities($document->getClientUrl($invitation)).'">'.HTML::entities($document->name).'</a></li>';
|
||||||
}
|
}
|
||||||
|
foreach($invoice->expenses as $expense){
|
||||||
|
foreach($expense->documents as $document){
|
||||||
|
$documentsHTML .= '<li><a href="'.HTML::entities($document->getClientUrl($invitation)).'">'.HTML::entities($document->name).'</a></li>';
|
||||||
|
}
|
||||||
|
}
|
||||||
$documentsHTML .= '</ul>';
|
$documentsHTML .= '</ul>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,14 +16,9 @@ class ExpensePresenter extends Presenter {
|
|||||||
return Utils::fromSqlDate($this->entity->expense_date);
|
return Utils::fromSqlDate($this->entity->expense_date);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function converted_amount()
|
|
||||||
{
|
|
||||||
return round($this->entity->amount * $this->entity->exchange_rate, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function invoiced_amount()
|
public function invoiced_amount()
|
||||||
{
|
{
|
||||||
return $this->entity->invoice_id ? $this->converted_amount() : 0;
|
return $this->entity->invoice_id ? $this->entity->convertedAmount() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function link()
|
public function link()
|
||||||
|
@ -59,7 +59,7 @@ class DocumentRepository extends BaseRepository
|
|||||||
|
|
||||||
public function upload($uploaded, &$doc_array=null)
|
public function upload($uploaded, &$doc_array=null)
|
||||||
{
|
{
|
||||||
$extension = strtolower($uploaded->extension());
|
$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];
|
||||||
}
|
}
|
||||||
@ -68,7 +68,7 @@ class DocumentRepository extends BaseRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(empty(Document::$types[$documentType])){
|
if(empty(Document::$types[$documentType])){
|
||||||
return 'Unsupported extension';
|
return 'Unsupported file type';
|
||||||
}
|
}
|
||||||
|
|
||||||
$documentTypeData = Document::$types[$documentType];
|
$documentTypeData = Document::$types[$documentType];
|
||||||
@ -180,10 +180,14 @@ class DocumentRepository extends BaseRepository
|
|||||||
|
|
||||||
public function getClientDatatable($contactId, $entityType, $search)
|
public function getClientDatatable($contactId, $entityType, $search)
|
||||||
{
|
{
|
||||||
|
|
||||||
$query = DB::table('invitations')
|
$query = DB::table('invitations')
|
||||||
->join('accounts', 'accounts.id', '=', 'invitations.account_id')
|
->join('accounts', 'accounts.id', '=', 'invitations.account_id')
|
||||||
->join('invoices', 'invoices.id', '=', 'invitations.invoice_id')
|
->join('invoices', 'invoices.id', '=', 'invitations.invoice_id')
|
||||||
->join('documents', 'documents.invoice_id', '=', 'invitations.invoice_id')
|
->join('expenses', 'expenses.invoice_id', '=', 'invitations.invoice_id')
|
||||||
|
->join('documents', function($join){
|
||||||
|
$join->on('documents.invoice_id', '=', 'invitations.invoice_id')->orOn('documents.expense_id', '=', 'expenses.id');
|
||||||
|
})
|
||||||
->join('clients', 'clients.id', '=', 'invoices.client_id')
|
->join('clients', 'clients.id', '=', 'invoices.client_id')
|
||||||
->where('invitations.contact_id', '=', $contactId)
|
->where('invitations.contact_id', '=', $contactId)
|
||||||
->where('invitations.deleted_at', '=', null)
|
->where('invitations.deleted_at', '=', null)
|
||||||
|
@ -4,17 +4,25 @@ use DB;
|
|||||||
use Utils;
|
use Utils;
|
||||||
use App\Models\Expense;
|
use App\Models\Expense;
|
||||||
use App\Models\Vendor;
|
use App\Models\Vendor;
|
||||||
|
use App\Models\Document;
|
||||||
use App\Ninja\Repositories\BaseRepository;
|
use App\Ninja\Repositories\BaseRepository;
|
||||||
use Session;
|
use Session;
|
||||||
|
|
||||||
class ExpenseRepository extends BaseRepository
|
class ExpenseRepository extends BaseRepository
|
||||||
{
|
{
|
||||||
|
protected $documentRepo;
|
||||||
|
|
||||||
// Expenses
|
// Expenses
|
||||||
public function getClassName()
|
public function getClassName()
|
||||||
{
|
{
|
||||||
return 'App\Models\Expense';
|
return 'App\Models\Expense';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function __construct(DocumentRepository $documentRepo)
|
||||||
|
{
|
||||||
|
$this->documentRepo = $documentRepo;
|
||||||
|
}
|
||||||
|
|
||||||
public function all()
|
public function all()
|
||||||
{
|
{
|
||||||
return Expense::scope()
|
return Expense::scope()
|
||||||
@ -113,7 +121,7 @@ class ExpenseRepository extends BaseRepository
|
|||||||
return $query;
|
return $query;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function save($input)
|
public function save($input, $checkSubPermissions=false)
|
||||||
{
|
{
|
||||||
$publicId = isset($input['public_id']) ? $input['public_id'] : false;
|
$publicId = isset($input['public_id']) ? $input['public_id'] : false;
|
||||||
|
|
||||||
@ -145,6 +153,45 @@ class ExpenseRepository extends BaseRepository
|
|||||||
$expense->exchange_rate = round($rate, 4);
|
$expense->exchange_rate = round($rate, 4);
|
||||||
$expense->amount = round(Utils::parseFloat($input['amount']), 2);
|
$expense->amount = round(Utils::parseFloat($input['amount']), 2);
|
||||||
|
|
||||||
|
// Documents
|
||||||
|
$document_ids = !empty($input['document_ids'])?array_map('intval', $input['document_ids']):array();;
|
||||||
|
foreach ($document_ids as $document_id){
|
||||||
|
$document = Document::scope($document_id)->first();
|
||||||
|
if($document && !$checkSubPermissions || $document->canEdit()){
|
||||||
|
$document->invoice_id = null;
|
||||||
|
$document->expense_id = $expense->id;
|
||||||
|
$document->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!empty($input['documents']) && Document::canCreate()){
|
||||||
|
// Fallback upload
|
||||||
|
$doc_errors = array();
|
||||||
|
foreach($input['documents'] as $upload){
|
||||||
|
$result = $this->documentRepo->upload($upload);
|
||||||
|
if(is_string($result)){
|
||||||
|
$doc_errors[] = $result;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$result->expense_id = $expense->id;
|
||||||
|
$result->save();
|
||||||
|
$document_ids[] = $result->public_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!empty($doc_errors)){
|
||||||
|
Session::flash('error', implode('<br>',array_map('htmlentities',$doc_errors)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($expense->documents as $document){
|
||||||
|
if(!in_array($document->public_id, $document_ids)){
|
||||||
|
// Removed
|
||||||
|
if(!$checkSubPermissions || $document->canEdit()){
|
||||||
|
$document->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$expense->save();
|
$expense->save();
|
||||||
|
|
||||||
return $expense;
|
return $expense;
|
||||||
|
@ -415,6 +415,7 @@ class InvoiceRepository extends BaseRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
$document->invoice_id = $invoice->id;
|
$document->invoice_id = $invoice->id;
|
||||||
|
$document->expense_id = null;
|
||||||
$document->save();
|
$document->save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ class ExpenseService extends BaseService
|
|||||||
return $this->expenseRepo;
|
return $this->expenseRepo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function save($data)
|
public function save($data, $checkSubPermissions=false)
|
||||||
{
|
{
|
||||||
if (isset($data['client_id']) && $data['client_id']) {
|
if (isset($data['client_id']) && $data['client_id']) {
|
||||||
$data['client_id'] = Client::getPrivateId($data['client_id']);
|
$data['client_id'] = Client::getPrivateId($data['client_id']);
|
||||||
@ -38,7 +38,7 @@ class ExpenseService extends BaseService
|
|||||||
$data['vendor_id'] = Vendor::getPrivateId($data['vendor_id']);
|
$data['vendor_id'] = Vendor::getPrivateId($data['vendor_id']);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->expenseRepo->save($data);
|
return $this->expenseRepo->save($data, $checkSubPermissions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDatatable($search)
|
public function getDatatable($search)
|
||||||
|
@ -31396,13 +31396,19 @@ NINJA.invoiceLines = function(invoice) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
NINJA.invoiceDocuments = function(invoice) {
|
NINJA.invoiceDocuments = function(invoice) {
|
||||||
if(!invoice.documents || !invoice.account.invoice_embed_documents)return[];
|
if(!invoice.account.invoice_embed_documents)return[];
|
||||||
var stack = [];
|
var stack = [];
|
||||||
var stackItem = null;
|
var stackItem = null;
|
||||||
|
|
||||||
var j = 0;
|
var j = 0;
|
||||||
for (var i = 0; i < invoice.documents.length; i++) {
|
for (var i = 0; i < invoice.documents.length; i++)addDoc(invoice.documents[i]);
|
||||||
var document = invoice.documents[i];
|
|
||||||
|
for (var i = 0; i < invoice.expenses.length; i++) {
|
||||||
|
var expense = invoice.expenses[i];
|
||||||
|
for (var i = 0; i < expense.documents.length; i++)addDoc(expense.documents[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addDoc(document){
|
||||||
var path = document.base64;
|
var path = document.base64;
|
||||||
|
|
||||||
if(!path)path = 'docs/'+document.public_id+'/'+document.name;
|
if(!path)path = 'docs/'+document.public_id+'/'+document.name;
|
||||||
@ -31417,7 +31423,7 @@ NINJA.invoiceDocuments = function(invoice) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {stack:stack};
|
return stack.length?{stack:stack}:[];
|
||||||
}
|
}
|
||||||
|
|
||||||
NINJA.subtotals = function(invoice, hideBalance)
|
NINJA.subtotals = function(invoice, hideBalance)
|
||||||
|
15
public/css/built.css
vendored
15
public/css/built.css
vendored
@ -3193,10 +3193,21 @@ div.panel-body div.panel-body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Attached Documents */
|
/* Attached Documents */
|
||||||
.dropzone {
|
#document-upload {
|
||||||
border:1px solid #ebe7e7;
|
border:1px solid #ebe7e7;
|
||||||
background:#f9f9f9 !important;
|
background:#f9f9f9 !important;
|
||||||
border-radius:3px;
|
border-radius:3px;
|
||||||
|
padding:20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invoice-table #document-upload{
|
||||||
|
max-width:560px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#document-upload .dropzone{
|
||||||
|
background:none;
|
||||||
|
border:none;
|
||||||
|
padding:0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropzone .dz-preview.dz-image-preview{
|
.dropzone .dz-preview.dz-image-preview{
|
||||||
@ -3204,8 +3215,6 @@ div.panel-body div.panel-body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dropzone .dz-preview .dz-image{
|
.dropzone .dz-preview .dz-image{
|
||||||
width:119px;
|
|
||||||
height:119px;
|
|
||||||
border-radius:5px!important;
|
border-radius:5px!important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
15
public/css/style.css
vendored
15
public/css/style.css
vendored
@ -1064,10 +1064,21 @@ div.panel-body div.panel-body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Attached Documents */
|
/* Attached Documents */
|
||||||
.dropzone {
|
#document-upload {
|
||||||
border:1px solid #ebe7e7;
|
border:1px solid #ebe7e7;
|
||||||
background:#f9f9f9 !important;
|
background:#f9f9f9 !important;
|
||||||
border-radius:3px;
|
border-radius:3px;
|
||||||
|
padding:20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invoice-table #document-upload{
|
||||||
|
max-width:560px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#document-upload .dropzone{
|
||||||
|
background:none;
|
||||||
|
border:none;
|
||||||
|
padding:0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropzone .dz-preview.dz-image-preview{
|
.dropzone .dz-preview.dz-image-preview{
|
||||||
@ -1075,8 +1086,6 @@ div.panel-body div.panel-body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dropzone .dz-preview .dz-image{
|
.dropzone .dz-preview .dz-image{
|
||||||
width:119px;
|
|
||||||
height:119px;
|
|
||||||
border-radius:5px!important;
|
border-radius:5px!important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -404,13 +404,19 @@ NINJA.invoiceLines = function(invoice) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
NINJA.invoiceDocuments = function(invoice) {
|
NINJA.invoiceDocuments = function(invoice) {
|
||||||
if(!invoice.documents || !invoice.account.invoice_embed_documents)return[];
|
if(!invoice.account.invoice_embed_documents)return[];
|
||||||
var stack = [];
|
var stack = [];
|
||||||
var stackItem = null;
|
var stackItem = null;
|
||||||
|
|
||||||
var j = 0;
|
var j = 0;
|
||||||
for (var i = 0; i < invoice.documents.length; i++) {
|
for (var i = 0; i < invoice.documents.length; i++)addDoc(invoice.documents[i]);
|
||||||
var document = invoice.documents[i];
|
|
||||||
|
for (var i = 0; i < invoice.expenses.length; i++) {
|
||||||
|
var expense = invoice.expenses[i];
|
||||||
|
for (var i = 0; i < expense.documents.length; i++)addDoc(expense.documents[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addDoc(document){
|
||||||
var path = document.base64;
|
var path = document.base64;
|
||||||
|
|
||||||
if(!path)path = 'docs/'+document.public_id+'/'+document.name;
|
if(!path)path = 'docs/'+document.public_id+'/'+document.name;
|
||||||
@ -425,7 +431,7 @@ NINJA.invoiceDocuments = function(invoice) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {stack:stack};
|
return stack.length?{stack:stack}:[];
|
||||||
}
|
}
|
||||||
|
|
||||||
NINJA.subtotals = function(invoice, hideBalance)
|
NINJA.subtotals = function(invoice, hideBalance)
|
||||||
|
@ -1103,11 +1103,13 @@ $LANG = array(
|
|||||||
'email_documents_example_1' => 'Widgets Receipt.pdf',
|
'email_documents_example_1' => 'Widgets Receipt.pdf',
|
||||||
'email_documents_example_2' => 'Final Deliverable.zip',
|
'email_documents_example_2' => 'Final Deliverable.zip',
|
||||||
'invoice_documents' => 'Attached Documents',
|
'invoice_documents' => 'Attached Documents',
|
||||||
|
'expense_documents' => 'Attached Documents',
|
||||||
'document_upload_message' => 'Drop files here or click to upload.',
|
'document_upload_message' => 'Drop files here or click to upload.',
|
||||||
'invoice_embed_documents' => 'Embed Documents',
|
'invoice_embed_documents' => 'Embed Documents',
|
||||||
'invoice_embed_documents_help' => 'Include attached images in the invoice.',
|
'invoice_embed_documents_help' => 'Include attached images in the invoice.',
|
||||||
'document_email_attachment' => 'Attach Documents',
|
'document_email_attachment' => 'Attach Documents',
|
||||||
'download_documents' => 'Download Documents (:size)',
|
'download_documents' => 'Download Documents (:size)',
|
||||||
|
'documents_from_expenses' => 'From Expenses:',
|
||||||
'documents' => 'Documents',
|
'documents' => 'Documents',
|
||||||
'document_date' => 'Document Date',
|
'document_date' => 'Document Date',
|
||||||
'document_size' => 'Size',
|
'document_size' => 'Size',
|
||||||
|
@ -105,6 +105,27 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@if ($account->isPro())
|
||||||
|
<div clas="row">
|
||||||
|
<div class="col-md-2 col-sm-4"><div class="control-label" style="margin-bottom:10px;">{{trans('texts.expense_documents')}}</div></div>
|
||||||
|
<div class="col-md-12 col-sm-8">
|
||||||
|
<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="documents[]" type="file" multiple />
|
||||||
|
</div>
|
||||||
|
<div data-bind="foreach: documents">
|
||||||
|
<div class="fallback-doc">
|
||||||
|
<a href="#" class="fallback-doc-remove" data-bind="click: $parent.removeDocument"><i class="fa fa-close"></i></a>
|
||||||
|
<span data-bind="text:name"></span>
|
||||||
|
<input type="hidden" name="document_ids[]" data-bind="value: public_id"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -122,6 +143,7 @@
|
|||||||
{!! Former::close() !!}
|
{!! Former::close() !!}
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
Dropzone.autoDiscover = false;
|
||||||
|
|
||||||
var vendors = {!! $vendors !!};
|
var vendors = {!! $vendors !!};
|
||||||
var clients = {!! $clients !!};
|
var clients = {!! $clients !!};
|
||||||
@ -194,6 +216,54 @@
|
|||||||
@else
|
@else
|
||||||
$('#amount').focus();
|
$('#amount').focus();
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
@if (Auth::user()->account->isPro())
|
||||||
|
$('.main-form').submit(function(){
|
||||||
|
if($('#document-upload .fallback input').val())$(this).attr('enctype', 'multipart/form-data')
|
||||||
|
else $(this).removeAttr('enctype')
|
||||||
|
})
|
||||||
|
|
||||||
|
// Initialize document upload
|
||||||
|
dropzone = new Dropzone('#document-upload', {
|
||||||
|
url:{!! json_encode(url('document')) !!},
|
||||||
|
params:{
|
||||||
|
_token:"{{ Session::getToken() }}"
|
||||||
|
},
|
||||||
|
acceptedFiles:{!! json_encode(implode(',',\App\Models\Document::$allowedMimes)) !!},
|
||||||
|
addRemoveLinks:true,
|
||||||
|
maxFileSize:{{floatval(MAX_DOCUMENT_SIZE/1000)}},
|
||||||
|
dictDefaultMessage:{!! json_encode(trans('texts.document_upload_message')) !!}
|
||||||
|
});
|
||||||
|
if(dropzone instanceof Dropzone){
|
||||||
|
dropzone.on("addedfile",handleDocumentAdded);
|
||||||
|
dropzone.on("removedfile",handleDocumentRemoved);
|
||||||
|
dropzone.on("success",handleDocumentUploaded);
|
||||||
|
for (var i=0; i<model.documents().length; i++) {
|
||||||
|
var document = model.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.preview_url()||document.url(),
|
||||||
|
mock:true,
|
||||||
|
index:i
|
||||||
|
};
|
||||||
|
|
||||||
|
dropzone.emit('addedfile', mockFile);
|
||||||
|
dropzone.emit('complete', mockFile);
|
||||||
|
if(document.preview_url()){
|
||||||
|
dropzone.emit('thumbnail', mockFile, document.preview_url()||document.url());
|
||||||
|
}
|
||||||
|
else if(document.type()=='jpeg' || document.type()=='png' || document.type()=='svg'){
|
||||||
|
dropzone.emit('thumbnail', mockFile, document.url());
|
||||||
|
}
|
||||||
|
dropzone.files.push(mockFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@endif
|
||||||
});
|
});
|
||||||
|
|
||||||
var ViewModel = function(data) {
|
var ViewModel = function(data) {
|
||||||
@ -206,8 +276,16 @@
|
|||||||
self.should_be_invoiced = ko.observable();
|
self.should_be_invoiced = ko.observable();
|
||||||
self.convert_currency = ko.observable(false);
|
self.convert_currency = ko.observable(false);
|
||||||
|
|
||||||
|
self.mapping = {
|
||||||
|
'documents': {
|
||||||
|
create: function(options) {
|
||||||
|
return new DocumentModel(options.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
ko.mapping.fromJS(data, {}, this);
|
ko.mapping.fromJS(data, self.mapping, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.account_currency_id = ko.observable({{ $account->getCurrencyId() }});
|
self.account_currency_id = ko.observable({{ $account->getCurrencyId() }});
|
||||||
@ -250,8 +328,57 @@
|
|||||||
|| invoiceCurrencyId != self.account_currency_id()
|
|| invoiceCurrencyId != self.account_currency_id()
|
||||||
|| expenseCurrencyId != self.account_currency_id();
|
|| expenseCurrencyId != self.account_currency_id();
|
||||||
})
|
})
|
||||||
};
|
|
||||||
|
|
||||||
|
self.addDocument = function() {
|
||||||
|
var documentModel = new DocumentModel();
|
||||||
|
self.documents.push(documentModel);
|
||||||
|
return documentModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.removeDocument = function(doc) {
|
||||||
|
var public_id = doc.public_id?doc.public_id():doc;
|
||||||
|
self.documents.remove(function(document) {
|
||||||
|
return document.public_id() == public_id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (Auth::user()->account->isPro())
|
||||||
|
function handleDocumentAdded(file){
|
||||||
|
if(file.mock)return;
|
||||||
|
file.index = model.documents().length;
|
||||||
|
model.addDocument({name:file.name, size:file.size, type:file.type});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDocumentRemoved(file){
|
||||||
|
model.removeDocument(file.public_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDocumentUploaded(file, response){
|
||||||
|
file.public_id = response.document.public_id
|
||||||
|
model.documents()[file.index].update(response.document);
|
||||||
|
|
||||||
|
if(response.document.preview_url){
|
||||||
|
dropzone.emit('thumbnail', file, response.document.preview_url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@endif
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@stop
|
@stop
|
@ -38,9 +38,8 @@
|
|||||||
|
|
||||||
{!! Former::open($url)
|
{!! Former::open($url)
|
||||||
->method($method)
|
->method($method)
|
||||||
->addClass('warn-on-exit')
|
->addClass('warn-on-exit main-form')
|
||||||
->autocomplete('off')
|
->autocomplete('off')
|
||||||
->attributes(array('enctype'=>'multipart/form-data'))
|
|
||||||
->onsubmit('return onFormSubmit(event)')
|
->onsubmit('return onFormSubmit(event)')
|
||||||
->rules(array(
|
->rules(array(
|
||||||
'client' => 'required',
|
'client' => 'required',
|
||||||
@ -307,7 +306,8 @@
|
|||||||
</div>
|
</div>
|
||||||
@if ($account->isPro())
|
@if ($account->isPro())
|
||||||
<div role="tabpanel" class="tab-pane" id="attached-documents" style="position:relative;z-index:9">
|
<div role="tabpanel" class="tab-pane" id="attached-documents" style="position:relative;z-index:9">
|
||||||
<div id="document-upload" class="dropzone">
|
<div id="document-upload">
|
||||||
|
<div class="dropzone">
|
||||||
<div class="fallback">
|
<div class="fallback">
|
||||||
<input name="documents[]" type="file" multiple />
|
<input name="documents[]" type="file" multiple />
|
||||||
</div>
|
</div>
|
||||||
@ -319,6 +319,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@if ($invoice->hasExpenseDocuments())
|
||||||
|
<h4>{{trans('texts.documents_from_expenses')}}</h4>
|
||||||
|
@foreach($invoice->expenses as $expense)
|
||||||
|
@foreach($expense->documents as $document)
|
||||||
|
<div>{{$document->name}}</div>
|
||||||
|
@endforeach
|
||||||
|
@endforeach
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@ -778,24 +787,24 @@
|
|||||||
model.invoice().has_tasks(true);
|
model.invoice().has_tasks(true);
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@if (isset($expenses) && $expenses)
|
if(model.invoice().expenses() && !model.invoice().public_id()){
|
||||||
model.expense_currency_id({{ $expenseCurrencyId }});
|
model.expense_currency_id({{ $expenseCurrencyId }});
|
||||||
|
|
||||||
// move the blank invoice line item to the end
|
// move the blank invoice line item to the end
|
||||||
var blank = model.invoice().invoice_items.pop();
|
var blank = model.invoice().invoice_items.pop();
|
||||||
var expenses = {!! $expenses !!};
|
var expenses = model.invoice().expenses();
|
||||||
|
|
||||||
for (var i=0; i<expenses.length; i++) {
|
for (var i=0; i<expenses.length; i++) {
|
||||||
var expense = expenses[i];
|
var expense = expenses[i];
|
||||||
var item = model.invoice().addItem();
|
var item = model.invoice().addItem();
|
||||||
item.notes(expense.description);
|
item.notes(expense.public_notes());
|
||||||
item.qty(expense.qty);
|
item.qty(1);
|
||||||
item.expense_public_id(expense.publicId);
|
item.expense_public_id(expense.public_id());
|
||||||
item.cost(expense.cost);
|
item.cost(expense.converted_amount());
|
||||||
}
|
}
|
||||||
model.invoice().invoice_items.push(blank);
|
model.invoice().invoice_items.push(blank);
|
||||||
model.invoice().has_expenses(true);
|
model.invoice().has_expenses(true);
|
||||||
@endif
|
}
|
||||||
|
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@ -929,8 +938,13 @@
|
|||||||
applyComboboxListeners();
|
applyComboboxListeners();
|
||||||
|
|
||||||
@if (Auth::user()->account->isPro())
|
@if (Auth::user()->account->isPro())
|
||||||
|
$('.main-form').submit(function(){
|
||||||
|
if($('#document-upload .dropzone .fallback input').val())$(this).attr('enctype', 'multipart/form-data')
|
||||||
|
else $(this).removeAttr('enctype')
|
||||||
|
})
|
||||||
|
|
||||||
// Initialize document upload
|
// Initialize document upload
|
||||||
dropzone = new Dropzone('#document-upload', {
|
dropzone = new Dropzone('#document-upload .dropzone', {
|
||||||
url:{!! json_encode(url('document')) !!},
|
url:{!! json_encode(url('document')) !!},
|
||||||
params:{
|
params:{
|
||||||
_token:"{{ Session::getToken() }}"
|
_token:"{{ Session::getToken() }}"
|
||||||
@ -1360,6 +1374,13 @@
|
|||||||
<script src="{{ $document->getVFSJSUrl() }}" type="text/javascript" async></script>
|
<script src="{{ $document->getVFSJSUrl() }}" type="text/javascript" async></script>
|
||||||
@endif
|
@endif
|
||||||
@endforeach
|
@endforeach
|
||||||
|
@foreach ($invoice->expenses as $expense)
|
||||||
|
@foreach ($expense->documents as $document)
|
||||||
|
@if($document->isPDFEmbeddable())
|
||||||
|
<script src="{{ $document->getVFSJSUrl() }}" type="text/javascript" async></script>
|
||||||
|
@endif
|
||||||
|
@endforeach
|
||||||
|
@endforeach
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@stop
|
@stop
|
||||||
|
@ -57,11 +57,18 @@
|
|||||||
|
|
||||||
@include('invoices.pdf', ['account' => Auth::user()->account, 'pdfHeight' => 800])
|
@include('invoices.pdf', ['account' => Auth::user()->account, 'pdfHeight' => 800])
|
||||||
|
|
||||||
@if (Auth::user()->account->isPro() && Auth::user()->account->invoice_embed_documents)
|
@if (Utils::isPro() && $invoice->account->invoice_embed_documents)
|
||||||
@foreach ($invoice->documents as $document)
|
@foreach ($invoice->documents as $document)
|
||||||
@if($document->isPDFEmbeddable())
|
@if($document->isPDFEmbeddable())
|
||||||
<script src="{{ $document->getVFSJSUrl() }}" type="text/javascript" async></script>
|
<script src="{{ $document->getVFSJSUrl() }}" type="text/javascript" async></script>
|
||||||
@endif
|
@endif
|
||||||
@endforeach
|
@endforeach
|
||||||
|
@foreach ($invoice->expenses as $expense)
|
||||||
|
@foreach ($expense->documents as $document)
|
||||||
|
@if($document->isPDFEmbeddable())
|
||||||
|
<script src="{{ $document->getVFSJSUrl() }}" type="text/javascript" async></script>
|
||||||
|
@endif
|
||||||
|
@endforeach
|
||||||
|
@endforeach
|
||||||
@endif
|
@endif
|
||||||
@stop
|
@stop
|
@ -227,6 +227,7 @@ function InvoiceModel(data) {
|
|||||||
self.invoice_status_id = ko.observable(0);
|
self.invoice_status_id = ko.observable(0);
|
||||||
self.invoice_items = ko.observableArray();
|
self.invoice_items = ko.observableArray();
|
||||||
self.documents = ko.observableArray();
|
self.documents = ko.observableArray();
|
||||||
|
self.expenses = ko.observableArray();
|
||||||
self.amount = ko.observable(0);
|
self.amount = ko.observable(0);
|
||||||
self.balance = ko.observable(0);
|
self.balance = ko.observable(0);
|
||||||
self.invoice_design_id = ko.observable(1);
|
self.invoice_design_id = ko.observable(1);
|
||||||
@ -257,6 +258,11 @@ function InvoiceModel(data) {
|
|||||||
return new DocumentModel(options.data);
|
return new DocumentModel(options.data);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
'expenses': {
|
||||||
|
create: function(options) {
|
||||||
|
return new ExpenseModel(options.data);
|
||||||
|
}
|
||||||
|
},
|
||||||
'tax': {
|
'tax': {
|
||||||
create: function(options) {
|
create: function(options) {
|
||||||
return new TaxRateModel(options.data);
|
return new TaxRateModel(options.data);
|
||||||
@ -847,6 +853,28 @@ function DocumentModel(data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ExpenseModel = function(data) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self.mapping = {
|
||||||
|
'documents': {
|
||||||
|
create: function(options) {
|
||||||
|
return new DocumentModel(options.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.description = ko.observable('');
|
||||||
|
self.qty = ko.observable(0);
|
||||||
|
self.public_id = ko.observable(0);
|
||||||
|
self.amount = ko.observable();
|
||||||
|
self.converted_amount = ko.observable();
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
ko.mapping.fromJS(data, self.mapping, this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/* Custom binding for product key typeahead */
|
/* Custom binding for product key typeahead */
|
||||||
ko.bindingHandlers.typeahead = {
|
ko.bindingHandlers.typeahead = {
|
||||||
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
|
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
|
||||||
|
@ -49,13 +49,18 @@
|
|||||||
@endif
|
@endif
|
||||||
|
|
||||||
<div class="clearfix"></div><p> </p>
|
<div class="clearfix"></div><p> </p>
|
||||||
@if ($account->isPro() && count($invoice->documents))
|
@if ($account->isPro() && $invoice->hasDocuments())
|
||||||
<div class="invoice-documents">
|
<div class="invoice-documents">
|
||||||
<h3>{{ trans('texts.documents_header') }}</h3>
|
<h3>{{ trans('texts.documents_header') }}</h3>
|
||||||
<ul>
|
<ul>
|
||||||
@foreach ($invoice->documents as $document)
|
@foreach ($invoice->documents as $document)
|
||||||
<li><a target="_blank" href="{{ $document->getClientUrl($invitation) }}">{{$document->name}} ({{Form::human_filesize($document->size)}})</a></li>
|
<li><a target="_blank" href="{{ $document->getClientUrl($invitation) }}">{{$document->name}} ({{Form::human_filesize($document->size)}})</a></li>
|
||||||
@endforeach
|
@endforeach
|
||||||
|
@foreach ($invoice->expenses as $expense)
|
||||||
|
@foreach ($expense->documents as $document)
|
||||||
|
<li><a target="_blank" href="{{ $document->getClientUrl($invitation) }}">{{$document->name}} ({{Form::human_filesize($document->size)}})</a></li>
|
||||||
|
@endforeach
|
||||||
|
@endforeach
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
@ -63,9 +68,16 @@
|
|||||||
@if ($account->isPro() && $account->invoice_embed_documents)
|
@if ($account->isPro() && $account->invoice_embed_documents)
|
||||||
@foreach ($invoice->documents as $document)
|
@foreach ($invoice->documents as $document)
|
||||||
@if($document->isPDFEmbeddable())
|
@if($document->isPDFEmbeddable())
|
||||||
<script src="{{ $document->getClientVFSJSUrl() }}" type="text/javascript" {{ Input::has('phantomjs')?'':'async' }}></script>
|
<script src="{{ $document->getClientVFSJSUrl() }}" type="text/javascript" async></script>
|
||||||
@endif
|
@endif
|
||||||
@endforeach
|
@endforeach
|
||||||
|
@foreach ($invoice->expenses as $expense)
|
||||||
|
@foreach ($expense->documents as $document)
|
||||||
|
@if($document->isPDFEmbeddable())
|
||||||
|
<script src="{{ $document->getClientVFSJSUrl() }}" type="text/javascript" async></script>
|
||||||
|
@endif
|
||||||
|
@endforeach
|
||||||
|
@endforeach
|
||||||
@endif
|
@endif
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user