mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
bug fixes
This commit is contained in:
commit
f239cfdfb4
@ -34,9 +34,7 @@ class DocumentAPIController extends BaseAPIController
|
||||
|
||||
public function store(CreateDocumentRequest $request)
|
||||
{
|
||||
Log::info($request);
|
||||
|
||||
$document = $this->documentRepo->upload($request->file);
|
||||
$document = $this->documentRepo->upload($request->all());
|
||||
|
||||
return $this->itemResponse($document);
|
||||
}
|
||||
|
@ -102,7 +102,7 @@ class DocumentController extends BaseController
|
||||
|
||||
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)){
|
||||
return Response::json([
|
||||
|
@ -1,7 +1,15 @@
|
||||
<?php namespace App\Http\Requests;
|
||||
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Expense;
|
||||
|
||||
class CreateDocumentRequest extends DocumentRequest
|
||||
{
|
||||
protected $autoload = [
|
||||
ENTITY_INVOICE,
|
||||
ENTITY_EXPENSE,
|
||||
];
|
||||
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
@ -9,7 +17,19 @@ class CreateDocumentRequest extends DocumentRequest
|
||||
*/
|
||||
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'
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,8 +2,50 @@
|
||||
|
||||
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 {
|
||||
|
||||
//
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
@ -652,30 +652,34 @@ class Account extends Eloquent
|
||||
public function getNextInvoiceNumber($invoice)
|
||||
{
|
||||
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);
|
||||
$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();
|
||||
if ($invoice->recurring_invoice_id) {
|
||||
$number = $this->recurring_invoice_number_prefix . $number;
|
||||
}
|
||||
|
||||
return $number;
|
||||
@ -683,17 +687,15 @@ class Account extends Eloquent
|
||||
|
||||
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) {
|
||||
$this->quote_number_counter += 1;
|
||||
} else {
|
||||
$default = $this->invoice_number_counter;
|
||||
$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->invoice_number_counter += 1;
|
||||
}
|
||||
|
||||
$this->save();
|
||||
|
@ -6,6 +6,11 @@ use Auth;
|
||||
|
||||
class Document extends EntityModel
|
||||
{
|
||||
protected $fillable = [
|
||||
'invoice_id',
|
||||
'expense_id',
|
||||
];
|
||||
|
||||
public static $extraExtensions = array(
|
||||
'jpg' => 'jpeg',
|
||||
'tif' => 'tiff',
|
||||
|
@ -16,6 +16,24 @@ class Product extends EntityModel
|
||||
'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()
|
||||
{
|
||||
return ENTITY_PRODUCT;
|
||||
|
@ -15,21 +15,38 @@ class BaseTransformer extends TransformerAbstract
|
||||
|
||||
protected function hasClient($name)
|
||||
{
|
||||
$name = strtolower($name);
|
||||
$name = trim(strtolower($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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
$name = strtolower($name);
|
||||
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)
|
||||
{
|
||||
$name = strtolower($name);
|
||||
|
22
app/Ninja/Import/CSV/ProductTransformer.php
Normal file
22
app/Ninja/Import/CSV/ProductTransformer.php
Normal 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'),
|
||||
];
|
||||
});
|
||||
}
|
||||
}
|
@ -57,8 +57,9 @@ class DocumentRepository extends BaseRepository
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function upload($uploaded, &$doc_array=null)
|
||||
public function upload($data, &$doc_array=null)
|
||||
{
|
||||
$uploaded = $data['file'];
|
||||
$extension = strtolower($uploaded->getClientOriginalExtension());
|
||||
if(empty(Document::$types[$extension]) && !empty(Document::$extraExtensions[$extension])){
|
||||
$documentType = Document::$extraExtensions[$extension];
|
||||
@ -81,12 +82,17 @@ class DocumentRepository extends BaseRepository
|
||||
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);
|
||||
$filename = \Auth::user()->account->account_key.'/'.$hash.'.'.$documentType;
|
||||
|
||||
$document = Document::createNew();
|
||||
$document->fill($data);
|
||||
|
||||
$disk = $document->getDisk();
|
||||
if(!$disk->exists($filename)){// Have we already stored the same file
|
||||
$stream = fopen($filePath, 'r');
|
||||
|
@ -741,7 +741,7 @@ class InvoiceRepository extends BaseRepository
|
||||
$invoice = Invoice::createNew($recurInvoice);
|
||||
$invoice->client_id = $recurInvoice->client_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->balance = $recurInvoice->amount;
|
||||
$invoice->invoice_date = date_create()->format('Y-m-d');
|
||||
|
@ -11,6 +11,13 @@ class ProductRepository extends BaseRepository
|
||||
return 'App\Models\Product';
|
||||
}
|
||||
|
||||
public function all()
|
||||
{
|
||||
return Product::scope()
|
||||
->withTrashed()
|
||||
->get();
|
||||
}
|
||||
|
||||
public function find($accountId)
|
||||
{
|
||||
return DB::table('products')
|
||||
|
@ -13,6 +13,7 @@ use App\Ninja\Repositories\ContactRepository;
|
||||
use App\Ninja\Repositories\ClientRepository;
|
||||
use App\Ninja\Repositories\InvoiceRepository;
|
||||
use App\Ninja\Repositories\PaymentRepository;
|
||||
use App\Ninja\Repositories\ProductRepository;
|
||||
use App\Ninja\Serializers\ArraySerializer;
|
||||
use App\Models\Client;
|
||||
use App\Models\Invoice;
|
||||
@ -23,6 +24,7 @@ class ImportService
|
||||
protected $invoiceRepo;
|
||||
protected $clientRepo;
|
||||
protected $contactRepo;
|
||||
protected $productRepo;
|
||||
protected $processedRows = array();
|
||||
|
||||
public static $entityTypes = [
|
||||
@ -31,6 +33,8 @@ class ImportService
|
||||
ENTITY_INVOICE,
|
||||
ENTITY_PAYMENT,
|
||||
ENTITY_TASK,
|
||||
ENTITY_PRODUCT,
|
||||
ENTITY_EXPENSE,
|
||||
];
|
||||
|
||||
public static $sources = [
|
||||
@ -45,7 +49,14 @@ class ImportService
|
||||
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->setSerializer(new ArraySerializer());
|
||||
@ -54,6 +65,7 @@ class ImportService
|
||||
$this->invoiceRepo = $invoiceRepo;
|
||||
$this->paymentRepo = $paymentRepo;
|
||||
$this->contactRepo = $contactRepo;
|
||||
$this->productRepo = $productRepo;
|
||||
}
|
||||
|
||||
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,
|
||||
'discount' => 'positive',
|
||||
];
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
if ($entityType === ENTITY_PRODUCT) {
|
||||
$rules = [
|
||||
'product_key' => 'required',
|
||||
];
|
||||
}
|
||||
|
||||
$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 = [];
|
||||
$countryMap2 = [];
|
||||
$countries = Cache::get('countries');
|
||||
@ -269,6 +292,7 @@ class ImportService
|
||||
ENTITY_CLIENT => $clientMap,
|
||||
ENTITY_INVOICE => $invoiceMap,
|
||||
ENTITY_INVOICE.'_'.ENTITY_CLIENT => $invoiceClientMap,
|
||||
ENTITY_PRODUCT => $productMap,
|
||||
'countries' => $countryMap,
|
||||
'countries2' => $countryMap2,
|
||||
'currencies' => $currencyMap,
|
||||
@ -280,13 +304,9 @@ class ImportService
|
||||
$data = [];
|
||||
|
||||
foreach ($files as $entityType => $filename) {
|
||||
if ($entityType === ENTITY_CLIENT) {
|
||||
$columns = Client::getImportColumns();
|
||||
$map = Client::getImportMap();
|
||||
} else {
|
||||
$columns = Invoice::getImportColumns();
|
||||
$map = Invoice::getImportMap();
|
||||
}
|
||||
$class = "App\\Models\\" . ucwords($entityType);
|
||||
$columns = $class::getImportColumns();
|
||||
$map = $class::getImportMap();
|
||||
|
||||
// Lookup field translations
|
||||
foreach ($columns as $key => $value) {
|
||||
@ -452,12 +472,8 @@ class ImportService
|
||||
private function convertToObject($entityType, $data, $map)
|
||||
{
|
||||
$obj = new stdClass();
|
||||
|
||||
if ($entityType === ENTITY_CLIENT) {
|
||||
$columns = Client::getImportColumns();
|
||||
} else {
|
||||
$columns = Invoice::getImportColumns();
|
||||
}
|
||||
$class = "App\\Models\\" . ucwords($entityType);
|
||||
$columns = $class::getImportColumns();
|
||||
|
||||
foreach ($columns as $column) {
|
||||
$obj->$column = false;
|
||||
|
@ -1314,6 +1314,13 @@ $LANG = array(
|
||||
'wait_for_upload' => 'Please wait for the document upload to complete.',
|
||||
'upgrade_for_permissions' => 'Upgrade to our Enterprise plan to enable permissions.',
|
||||
'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)',
|
||||
|
||||
);
|
||||
|
||||
|
@ -4,9 +4,7 @@
|
||||
@parent
|
||||
|
||||
<style type="text/css">
|
||||
.contact-file,
|
||||
.task-file,
|
||||
.payment-file {
|
||||
.import-file {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
@ -34,7 +32,7 @@
|
||||
|
||||
@foreach (\App\Services\ImportService::$entityTypes as $entityType)
|
||||
{!! Former::file("{$entityType}_file")
|
||||
->addGroupClass("{$entityType}-file") !!}
|
||||
->addGroupClass("import-file {$entityType}-file") !!}
|
||||
@endforeach
|
||||
|
||||
{!! Former::actions( Button::info(trans('texts.upload'))->submit()->large()->appendIcon(Icon::create('open'))) !!}
|
||||
@ -74,6 +72,10 @@
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
$(function() {
|
||||
setFileTypesVisible();
|
||||
});
|
||||
|
||||
function setEntityTypesVisible() {
|
||||
var selector = '.entity-types input[type=checkbox]';
|
||||
if ($('#format').val() === 'JSON') {
|
||||
|
@ -7,13 +7,11 @@
|
||||
|
||||
{!! Former::open('/import_csv')->addClass('warn-on-exit') !!}
|
||||
|
||||
@if (isset($data[ENTITY_CLIENT]))
|
||||
@include('accounts.partials.map', $data[ENTITY_CLIENT])
|
||||
@endif
|
||||
|
||||
@if (isset($data[ENTITY_INVOICE]))
|
||||
@include('accounts.partials.map', $data[ENTITY_INVOICE])
|
||||
@endif
|
||||
@foreach (App\Services\ImportService::$entityTypes as $entityType)
|
||||
@if (isset($data[$entityType]))
|
||||
@include('accounts.partials.map', $data[$entityType])
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
{!! Former::actions(
|
||||
Button::normal(trans('texts.cancel'))->large()->asLinkTo(URL::to('/settings/import_export'))->appendIcon(Icon::create('remove-circle')),
|
||||
|
Loading…
x
Reference in New Issue
Block a user