1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-09-20 08:21:34 +02:00

Merge pull request #8939 from turbo124/v5-develop

v5.7.43
This commit is contained in:
David Bomba 2023-11-04 10:53:52 +11:00 committed by GitHub
commit 413eadd6fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 564 additions and 134 deletions

View File

@ -1 +1 @@
5.7.42
5.7.43

View File

@ -41,6 +41,9 @@ class ExpenseFilters extends QueryFilters
->orWhere('custom_value4', 'like', '%'.$filter.'%')
->orWhereHas('category', function ($q) use ($filter) {
$q->where('name', 'like', '%'.$filter.'%');
})
->orWhereHas('vendor', function ($q) use ($filter) {
$q->where('name', 'like', '%'.$filter.'%');
});
});
}

View File

@ -154,23 +154,15 @@ class UserFilters extends QueryFilters
$user = auth()->user();
if (in_array(self::STATUS_ACTIVE, $filters)) {
$query = $query->orWhereHas('company_users', function ($q) use($user){
$q->where('company_id', $user->company()->id)->whereNull('deleted_at');
});
$query->orWhereNull('deleted_at');
}
if (in_array(self::STATUS_ARCHIVED, $filters)) {
$query = $query->orWhereHas('company_users', function ($q) use($user){
$q->where('company_id', $user->company()->id)->whereNotNull('deleted_at')->where('is_deleted', 0);
});
$query->orWhereNotNull('deleted_at')->where('is_deleted', 0);
}
if (in_array(self::STATUS_DELETED, $filters)) {
$query = $query->orWhereHas('company_users', function ($q) use($user){
$q->where('company_id', $user->company()->id)->where('is_deleted', 1);
});
$query->orWhere('is_deleted', 1);
}
});
}

View File

@ -1123,10 +1123,10 @@ class BaseController extends Controller
$data['white_label'] = Ninja::isSelfHost() ? $account->isPaid() : false;
//pass referral code to front end
$data['rc'] = request()->has('rc') ? request()->input('rc') : '';
$data['build'] = request()->has('build') ? request()->input('build') : '';
$data['login'] = request()->has('login') ? request()->input('login') : 'false';
$data['signup'] = request()->has('signup') ? request()->input('signup') : 'false';
$data['rc'] = request()->has('rc') && is_string(request()->input('rc')) ? request()->input('rc') : '';
$data['build'] = request()->has('build') && is_string(request()->input('build')) ? request()->input('build') : '';
$data['login'] = request()->has('login') && is_string(request()->input('input')) ? request()->input('login') : 'false';
$data['signup'] = request()->has('signup') && is_string(request()->input('signup')) ? request()->input('signup') : 'false';
$data['canvas_path'] = $canvas_path;
if (request()->session()->has('login')) {

View File

@ -181,6 +181,16 @@ class InvoiceController extends Controller
->with('message', ctrans('texts.no_payable_invoices_selected'));
}
//ensure all stale fees are removed.
$invoices->each(function ($invoice) {
$invoice->service()
->markSent()
->removeUnpaidGatewayFees()
->save();
});
$invoices = $invoices->fresh();
//iterate and sum the payable amounts either partial or balance
$total = 0;
foreach ($invoices as $invoice) {

View File

@ -11,27 +11,28 @@
namespace App\Http\Controllers;
use App\Events\Expense\ExpenseWasCreated;
use App\Events\Expense\ExpenseWasUpdated;
use App\Utils\Ninja;
use App\Models\Account;
use App\Models\Expense;
use Illuminate\Http\Response;
use App\Factory\ExpenseFactory;
use App\Filters\ExpenseFilters;
use App\Http\Requests\Expense\CreateExpenseRequest;
use App\Http\Requests\Expense\DestroyExpenseRequest;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\Uploadable;
use App\Utils\Traits\BulkOptions;
use App\Utils\Traits\SavesDocuments;
use App\Repositories\ExpenseRepository;
use App\Transformers\ExpenseTransformer;
use App\Events\Expense\ExpenseWasCreated;
use App\Events\Expense\ExpenseWasUpdated;
use App\Http\Requests\Expense\BulkExpenseRequest;
use App\Http\Requests\Expense\EditExpenseRequest;
use App\Http\Requests\Expense\ShowExpenseRequest;
use App\Http\Requests\Expense\StoreExpenseRequest;
use App\Http\Requests\Expense\CreateExpenseRequest;
use App\Http\Requests\Expense\UpdateExpenseRequest;
use App\Http\Requests\Expense\UploadExpenseRequest;
use App\Models\Account;
use App\Models\Expense;
use App\Repositories\ExpenseRepository;
use App\Transformers\ExpenseTransformer;
use App\Utils\Ninja;
use App\Utils\Traits\BulkOptions;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\SavesDocuments;
use App\Utils\Traits\Uploadable;
use Illuminate\Http\Response;
use App\Http\Requests\Expense\DestroyExpenseRequest;
/**
* Class ExpenseController.
@ -321,7 +322,10 @@ class ExpenseController extends BaseController
*/
public function create(CreateExpenseRequest $request)
{
$expense = ExpenseFactory::create(auth()->user()->company()->id, auth()->user()->id);
/** @var \App\Models\User $user */
$user = auth()->user();
$expense = ExpenseFactory::create($user->company()->id, $user->id);
return $this->itemResponse($expense);
}
@ -366,9 +370,12 @@ class ExpenseController extends BaseController
*/
public function store(StoreExpenseRequest $request)
{
$expense = $this->expense_repo->save($request->all(), ExpenseFactory::create(auth()->user()->company()->id, auth()->user()->id));
/** @var \App\Models\User $user */
$user = auth()->user();
event(new ExpenseWasCreated($expense, $expense->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
$expense = $this->expense_repo->save($request->all(), ExpenseFactory::create($user->company()->id, $user->id));
event(new ExpenseWasCreated($expense, $expense->company, Ninja::eventVars($user ? $user->id : null)));
event('eloquent.created: App\Models\Expense', $expense);
@ -481,20 +488,25 @@ class ExpenseController extends BaseController
* ),
* )
*/
public function bulk()
public function bulk(BulkExpenseRequest $request)
{
$action = request()->input('action');
/** @var \App\Models\User $user */
$user = auth()->user();
$ids = request()->input('ids');
$expenses = Expense::withTrashed()->find($this->transformKeys($ids));
$expenses = Expense::withTrashed()->find($request->ids);
$expenses->each(function ($expense, $key) use ($action) {
if (auth()->user()->can('edit', $expense)) {
$this->expense_repo->{$action}($expense);
if($request->action == 'bulk_categorize' && $user->can('edit', $expenses->first())) {
$this->expense_repo->categorize($expenses, $request->category_id);
$expenses = collect([]);
}
$expenses->each(function ($expense) use ($request, $user) {
if ($user->can('edit', $expense)) {
$this->expense_repo->{$request->action}($expense);
}
});
return $this->listResponse(Expense::withTrashed()->whereIn('id', $this->transformKeys($ids)));
return $this->listResponse(Expense::withTrashed()->whereIn('id', $request->ids));
}
/**

View File

@ -55,6 +55,9 @@ class ImportJsonController extends BaseController
*/
public function import(ImportJsonRequest $request)
{
/** @var \App\Models\User $user */
$user = auth()->user();
$file_location = $request->file('files')
->storeAs(
'migrations',
@ -63,9 +66,9 @@ class ImportJsonController extends BaseController
);
if (Ninja::isHosted()) {
CompanyImport::dispatch(auth()->user()->getCompany(), auth()->user(), $file_location, $request->except('files'))->onQueue('migration');
CompanyImport::dispatch($user->company(), $user, $file_location, $request->except('files'))->onQueue('migration');
} else {
CompanyImport::dispatch(auth()->user()->getCompany(), auth()->user(), $file_location, $request->except('files'));
CompanyImport::dispatch($user->company(), $user, $file_location, $request->except('files'));
}
return response()->json(['message' => 'Processing'], 200);

View File

@ -27,7 +27,7 @@ class SubdomainController extends BaseController
public function index()
{
if (!MultiDB::checkDomainAvailable(request()->input('subdomain'))) {
return response()->json(['message' => 'Domain not available'], 401);
return response()->json(['message' => ctrans('texts.subdomain_is_not_available')], 401);
}
return response()->json(['message' => 'Domain available'], 200);

View File

@ -12,13 +12,10 @@
namespace App\Http\Requests\Expense;
use App\Http\Requests\Request;
use App\Models\Expense;
use App\Utils\Traits\BulkOptions;
use Illuminate\Validation\Rule;
class BulkExpenseRequest extends Request
{
use BulkOptions;
/**
* Determine if the user is authorized to make this request.
*
@ -26,15 +23,7 @@ class BulkExpenseRequest extends Request
*/
public function authorize()
{
if (! $this->has('action')) {
return false;
}
if (! in_array($this->action, $this->getBulkOptions(), true)) {
return false;
}
return auth()->user()->can(auth()->user()->isAdmin(), Expense::class);
return true;
}
/**
@ -44,13 +33,30 @@ class BulkExpenseRequest extends Request
*/
public function rules()
{
$rules = $this->getGlobalRules();
/** @var \App\Models\User $user */
$user = auth()->user();
/* We don't require IDs on bulk storing. */
if ($this->action !== self::$STORE_METHOD) {
$rules['ids'] = ['required'];
return [
'ids' => ['required','bail','array', Rule::exists('expenses', 'id')->where('company_id', $user->company()->id)],
'category_id' => ['sometimes', 'bail', Rule::exists('expense_categories', 'id')->where('company_id', $user->company()->id)],
'action' => 'in:archive,restore,delete,bulk_categorize',
];
}
public function prepareForValidation()
{
$input = $this->all();
if (isset($input['ids'])) {
$input['ids'] = $this->transformKeys($input['ids']);
}
return $rules;
if (isset($input['category_id'])) {
$input['category_id'] = $this->transformKeys($input['category_id']);
}
$this->replace($input);
}
}

View File

@ -82,6 +82,8 @@ class StoreRecurringInvoiceRequest extends Request
public function prepareForValidation()
{
$input = $this->all();
$input['amount'] = 0;
$input['balance'] = 0;
if (array_key_exists('due_date_days', $input) && is_null($input['due_date_days'])) {
$input['due_date_days'] = 'terms';

View File

@ -25,7 +25,10 @@ class StoreSchedulerRequest extends Request
*/
public function authorize(): bool
{
return auth()->user()->isAdmin();
/** @var \App\Models\User $user */
$user = auth()->user();
return $user->isAdmin();
}
public function rules()

View File

@ -22,7 +22,10 @@ class UpdateSchedulerRequest extends Request
*/
public function authorize(): bool
{
return auth()->user()->isAdmin() && $this->task_scheduler->company_id == auth()->user()->company()->id;
/** @var \App\Models\User $user */
$user = auth()->user();
return $user->isAdmin() && $this->task_scheduler->company_id == $user->company()->id;
}
public function rules(): array

View File

@ -28,7 +28,10 @@ class ValidClientIds implements Rule
*/
public function passes($attribute, $value)
{
return Client::where('company_id', auth()->user()->company()->id)->whereIn('id', $this->transformKeys($value))->count() == count($value);
/** @var \App\Models\User $user */
$user = auth()->user();
return Client::where('company_id', $user->company()->id)->whereIn('id', $this->transformKeys($value))->count() == count($value);
}
/**

View File

@ -201,7 +201,7 @@ class CompanyExport implements ShouldQueue
return $document->makeVisible(['id']);
})->all();
$this->export_data['expense_categories'] = $this->company->expense_categories->map(function ($expense_category) {
$this->export_data['expense_categories'] = $this->company->expense_categories()->cursor()->map(function ($expense_category) {
$expense_category = $this->transformArrayOfKeys($expense_category, ['user_id', 'company_id']);
return $expense_category->makeVisible(['id']);
@ -288,7 +288,7 @@ class CompanyExport implements ShouldQueue
$this->export_data['recurring_expenses'] = $this->company->recurring_expenses()->orderBy('number', 'DESC')->cursor()->map(function ($expense) {
$expense = $this->transformBasicEntities($expense);
$expense = $this->transformArrayOfKeys($expense, ['vendor_id', 'invoice_id', 'client_id', 'category_id', 'project_id']);
return $expense->makeVisible(['id']);
})->all();
@ -399,6 +399,12 @@ class CompanyExport implements ShouldQueue
return $bank_transaction->makeVisible(['id','user_id','company_id']);
})->all();
$this->export_data['schedulers'] = $this->company->schedulers()->orderBy('id', 'ASC')->cursor()->map(function ($scheduler) {
$scheduler = $this->transformArrayOfKeys($scheduler, ['company_id', 'user_id']);
return $scheduler->makeVisible(['id','user_id','company_id']);
})->all();
//write to tmp and email to owner();
$this->zipAndSend();
@ -440,7 +446,7 @@ class CompanyExport implements ShouldQueue
$path = 'backups';
Storage::makeDirectory(storage_path('backups/'));
// Storage::makeDirectory(storage_path('backups/'));
try {
mkdir(storage_path('backups/'));

View File

@ -16,6 +16,7 @@ use App\Exceptions\NonExistingMigrationFile;
use App\Factory\ClientContactFactory;
use App\Jobs\Mail\NinjaMailerJob;
use App\Jobs\Mail\NinjaMailerObject;
use App\Jobs\Ninja\TaskScheduler;
use App\Libraries\MultiDB;
use App\Mail\Import\CompanyImportFailure;
use App\Mail\Import\ImportCompleted;
@ -111,6 +112,8 @@ class CompanyImport implements ShouldQueue
private $file_path;
private string $import_version = '';
private $importables = [
// 'company',
'users',
@ -151,6 +154,7 @@ class CompanyImport implements ShouldQueue
'bank_integrations',
'bank_transactions',
'payments',
'schedulers',
];
private $company_properties = [
@ -210,6 +214,22 @@ class CompanyImport implements ShouldQueue
"convert_rate_to_client",
];
private array $version_keys = [
'baseline' => [],
'5.7.35' => [
Payment::class => [
'refund_meta',
'category_id',
],
User::class => [
'user_logged_in_notification',
],
Design::class => [
'is_template',
]
],
];
/**
* Create a new job instance.
*
@ -407,9 +427,7 @@ class CompanyImport implements ShouldQueue
$data = (object)$this->getObject('app_version', true);
if ($this->current_app_version != $data->app_version) {
//perform some magic here
}
$this->import_version = $data->app_version;
if ($this->pre_flight_checks_pass === false) {
$this->sendImportMail($this->message);
@ -439,10 +457,12 @@ class CompanyImport implements ShouldQueue
$settings->project_number_counter = 1;
$settings->purchase_order_number_counter = 1;
$this->company->settings = $co->settings;
// $this->company->settings = $this->backup_file->company->settings;
$this->company->saveSettings($co->settings, $this->company);
$this->company->save();
return $this;
return $this;
}
private function purgeCompanyData()
@ -456,6 +476,10 @@ class CompanyImport implements ShouldQueue
$this->company->expenses()->forceDelete();
$this->company->subscriptions()->forceDelete();
$this->company->purchase_orders()->forceDelete();
$this->company->bank_integrations()->forceDelete();
$this->company->bank_transactions()->forceDelete();
$this->company->schedulers()->forceDelete();
$this->company->system_log_relation()->forceDelete();
$this->company->save();
@ -507,6 +531,20 @@ class CompanyImport implements ShouldQueue
return $this;
}
private function import_schedulers()
{
$this->genericNewClassImport(
\App\Models\Scheduler::class,
['company_id', 'id', 'hashed_id'],
[
['users' => 'user_id'],
],
'schedulers'
);
return $this;
}
private function import_bank_integrations()
{
$this->genericImport(
@ -1131,7 +1169,6 @@ class CompanyImport implements ShouldQueue
$new_document->documentable_type = $document->documentable_type;
$new_document->save(['timestamps' => false]);
$storage_url = (object)$this->getObject('storage_url', true);
@ -1327,7 +1364,36 @@ class CompanyImport implements ShouldQueue
}
}
private function filterVersionProps($class, array $obj_array): array
{
if($this->current_app_version == $this->import_version)
return $obj_array;
$version_index = 0;
$index = 0;
$filters = collect($this->version_keys)
->map(function ($value, $key) use (&$version_index, &$index) {
if($this->import_version == $key) {
$version_index = $index;
}
$index++;
return $value;
})
->when($version_index == 0, function ($collection) {
return collect([]);
})
->when($version_index > 0, function ($collection) use (&$version_index, $class) {
return $collection->slice($version_index)->pluck($class)->filter();
});
return collect($obj_array)->diffKeys($filters->flatten()->flip())->toArray();
}
private function genericNewClassImport($class, $unset, $transforms, $object_property)
{
$class::unguard();
@ -1348,6 +1414,10 @@ class CompanyImport implements ShouldQueue
if (Ninja::isSelfHost() && $obj_array['gateway_key'] == 'd14dd26a47cecc30fdd65700bfb67b34') {
$obj_array['gateway_key'] = 'd14dd26a37cecc30fdd65700bfb55b23';
}
if(!isset($obj_array['fees_and_limits'])){
$obj_array['fees_and_limits'] = \json_encode([]);
}
}
if (array_key_exists('deleted_at', $obj_array) && $obj_array['deleted_at'] > 1) {
@ -1385,8 +1455,29 @@ class CompanyImport implements ShouldQueue
$obj_array['config'] = encrypt($obj_array['config']);
}
if($class == 'App\Models\Scheduler') {
$parameters = $obj_array['parameters'];
if(isset($parameters->clients)){
$parameters->clients =
collect($parameters->clients)->map(function ($client_hash){
return $this->encodePrimaryKey($this->transformId('clients', $client_hash));
})->toArray();
}
if(isset($parameters->entity_id)){
$parameters->entity_id = $this->encodePrimaryKey($this->transformId($parameters->entity."s", $parameters->entity_id));
}
$obj_array['parameters'] = $parameters;
}
$new_obj = new $class();
$new_obj->company_id = $this->company->id;
$obj_array = $this->filterVersionProps($class,$obj_array);
$new_obj->fill($obj_array);
$new_obj->save(['timestamps' => false]);
@ -1430,6 +1521,8 @@ class CompanyImport implements ShouldQueue
$obj_array['recurring_product_ids'] = $this->recordProductIds($obj_array['recurring_product_ids']);
$obj_array['webhook_configuration'] = \json_encode($obj_array['webhook_configuration']);
}
$obj_array = $this->filterVersionProps($class, $obj_array);
$new_obj = $class::firstOrNew(
[$match_key => $obj->{$match_key}],
@ -1481,6 +1574,8 @@ class CompanyImport implements ShouldQueue
$obj_array['recurring_product_ids'] = '';
$obj_array['product_ids'] = '';
}
$obj_array = $this->filterVersionProps($class, $obj_array);
/* Expenses that don't have a number will not be inserted - so need to override here*/
if ($class == 'App\Models\Expense' && is_null($obj->{$match_key})) {

View File

@ -743,12 +743,26 @@ class BaseDriver extends AbstractPaymentDriver
}
$invoices_string = str_replace(["*","<",">","'",'"'], "-", $invoices_string);
// $invoices_string = "I-".$invoices_string;
// $invoices_string = substr($invoices_string, 0, 22);
$invoices_string = "I-".$invoices_string;
// 2023-11-02 - improve the statement descriptor for string
$company_name = $this->client->company->present()->name();
$invoices_string = substr($invoices_string, 0, 22);
$invoices_string = str_pad($invoices_string, 5, ctrans('texts.invoice'), STR_PAD_LEFT);
if(ctype_digit(substr($company_name, 0, 1)))
$company_name = "X" . $company_name;
$suffix = strlen($invoices_string) + 1;
$length = 22 - $suffix;
$company_name = substr($company_name, 0, $length);
$descriptor = "{$company_name} {$invoices_string}";
$invoices_string = str_pad($descriptor, 5, ctrans('texts.invoice'), STR_PAD_RIGHT);
// $invoices_string = str_pad($invoices_string, 5, ctrans('texts.invoice'), STR_PAD_LEFT);
return $invoices_string;

View File

@ -51,34 +51,6 @@ class DirectDebit implements MethodInterface
public function authorizeView(array $data)
{
return $this->billingRequestFlows($data);
// $session_token = \Illuminate\Support\Str::uuid()->toString();
// try {
// $redirect = $this->go_cardless->gateway->redirectFlows()->create([
// 'params' => [
// 'session_token' => $session_token,
// 'success_redirect_url' => route('client.payment_methods.confirm', [
// 'method' => GatewayType::DIRECT_DEBIT,
// 'session_token' => $session_token,
// ]),
// 'prefilled_customer' => [
// 'given_name' => auth()->guard('contact')->user()->first_name ?: '',
// 'family_name' => auth()->guard('contact')->user()->last_name ?: '',
// 'email' => auth()->guard('contact')->user()->email ?: '',
// 'address_line1' => auth()->guard('contact')->user()->client->address1 ?: '',
// 'city' => auth()->guard('contact')->user()->client->city ?: '',
// 'postal_code' => auth()->guard('contact')->user()->client->postal_code ?: '',
// 'country_code' => auth()->guard('contact')->user()->client->country->iso_3166_2,
// ],
// ],
// ]);
// return redirect(
// $redirect->redirect_url
// );
// } catch (\Exception $exception) {
// return $this->processUnsuccessfulAuthorization($exception);
// }
}
/**
@ -109,7 +81,7 @@ class DirectDebit implements MethodInterface
"params" => [
"mandate_request" => [
"currency" => auth()->guard('contact')->user()->client->currency()->code,
"verify" => "recommended"
"verify" => "when_available"
]
]
]);

View File

@ -404,7 +404,9 @@ class GoCardlessPaymentDriver extends BaseDriver
$this->init();
$mandate = $this->gateway->mandates()->get($token);
if ($mandate->status !== 'active') {
if(!in_array($mandate->status, ['pending_submission', 'submitted', 'active','pending_customer_approval'])) {
// if ($mandate->status !== 'active') {
throw new \Exception(ctrans('texts.gocardless_mandate_not_ready'));
}
} catch (\Exception $exception) {

View File

@ -199,7 +199,7 @@ class BaseRepository
});
}
}
nlog($model->toArray());
$model->saveQuietly();
/* Model now persisted, now lets do some child tasks */

View File

@ -12,13 +12,15 @@
namespace App\Repositories;
use App\Factory\ExpenseFactory;
use App\Libraries\Currency\Conversion\CurrencyApi;
use App\Models\Expense;
use App\Utils\Traits\GeneratesCounter;
use Carbon\Exceptions\InvalidFormatException;
use Illuminate\Database\QueryException;
use Illuminate\Support\Carbon;
use App\Factory\ExpenseFactory;
use App\Models\ExpenseCategory;
use App\Utils\Traits\GeneratesCounter;
use Illuminate\Database\QueryException;
use Carbon\Exceptions\InvalidFormatException;
use App\Libraries\Currency\Conversion\CurrencyApi;
use Illuminate\Database\Eloquent\Collection;
/**
* ExpenseRepository.
@ -158,4 +160,25 @@ class ExpenseRepository extends BaseRepository
return $expense;
}
/**
* Categorize Expenses in bulk
*
* @param Collection $expenses
* @param int $category_id
* @return void
*/
public function categorize(Collection $expenses, int $category_id): void
{
$ec = ExpenseCategory::withTrashed()->find($category_id);
$expenses->when($ec)
->each(function ($expense) use($ec){
$expense->category_id = $ec->id;
$expense->save();
});
}
}

View File

@ -175,16 +175,14 @@ class FacturaEInvoice extends AbstractService
->setBillingPeriod()
->signDocument();
$disk = config('filesystems.default');
// $disk = config('filesystems.default');
if (!Storage::disk($disk)->exists($this->invoice->client->e_invoice_filepath($this->invoice->invitations->first()))) {
Storage::makeDirectory($this->invoice->client->e_invoice_filepath($this->invoice->invitations->first()));
}
// if (!Storage::disk($disk)->exists($this->invoice->client->e_invoice_filepath($this->invoice->invitations->first()))) {
// Storage::makeDirectory($this->invoice->client->e_invoice_filepath($this->invoice->invitations->first()));
// }
// $this->fac->export(Storage::disk($disk)->path($this->invoice->client->e_invoice_filepath($this->invoice->invitations->first()) . $this->invoice->getFileName("xsig")));
return $this->fac->export();
// return $this->invoice->client->e_invoice_filepath($this->invoice->invitations->first()) . $this->invoice->getFileName("xsig");
}
/** Check if this is a public administration body */
@ -546,8 +544,8 @@ class FacturaEInvoice extends AbstractService
"isLegalEntity" => $this->invoice->client->classification === 'individual' ? false : true,
"taxNumber" => $this->invoice->client->vat_number,
"name" => substr($this->invoice->client->present()->name(), 0, 40),
"firstSurname" => substr($this->invoice->client->present()->first_name(), 0, 40),
"lastSurname" => substr($this->invoice->client->present()->last_name(), 0, 40),
// "firstSurname" => substr($this->invoice->client->present()->last_name(), 0, 40),
// "lastSurname" => substr($this->invoice->client->present()->last_name(), 0, 40),
"address" => substr($this->invoice->client->address1, 0, 80),
"postCode" => substr($this->invoice->client->postal_code, 0, 5),
"town" => substr($this->invoice->client->city, 0, 50),
@ -563,6 +561,11 @@ class FacturaEInvoice extends AbstractService
// "ineTownCode" => "280796" // Cód. de municipio del INE
]);
if($this->invoice->client->classification === 'individual') {
$buyer['name'] = $this->invoice->client->present()->first_name();
$buyer['firstSurname'] = $this->invoice->client->present()->last_name();
}
$this->fac->setBuyer($buyer);
return $this;

View File

@ -442,7 +442,6 @@ class InvoiceService
$this->invoice->line_items = array_values($items);
$this->invoice = $this->invoice->calc()->getInvoice();
$this->deleteEInvoice(); //@deprecated
/* 24-03-2022 */
$new_balance = $this->invoice->balance;

View File

@ -522,7 +522,7 @@ class TemplateService
'balance_raw' => ($payment->amount - $payment->refunded - $payment->applied),
'date' => $this->translateDate($payment->date, $payment->client->date_format(), $payment->client->locale()),
'method' => $payment->translatedType(),
'currency' => $payment->currency->code,
'currency' => $payment->currency->code ?? $payment->company->currency()->code,
'exchange_rate' => $payment->exchange_rate,
'transaction_reference' => $payment->transaction_reference,
'is_manual' => $payment->is_manual,

View File

@ -15,11 +15,11 @@ return [
'require_https' => env('REQUIRE_HTTPS', true),
'app_url' => rtrim(env('APP_URL', ''), '/'),
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
'app_version' => env('APP_VERSION','5.7.42'),
'app_tag' => env('APP_TAG','5.7.42'),
'app_version' => env('APP_VERSION','5.7.43'),
'app_tag' => env('APP_TAG','5.7.43'),
'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', ''),
'api_secret' => env('API_SECRET', false),
'google_maps_api_key' => env('GOOGLE_MAPS_API_KEY'),
'google_analytics_url' => env('GOOGLE_ANALYTICS_URL', 'https://www.google-analytics.com/collect'),
'key_length' => 32,

View File

@ -27,7 +27,7 @@ class BankTransactionFactory extends Factory
'amount' => $this->faker->randomFloat(2, 10, 10000) ,
'currency_id' => '1',
'account_type' => 'creditCard',
'category_id' => 1,
'category_id' => null,
'category_type' => 'Random' ,
'date' => $this->faker->date('Y-m-d') ,
'bank_account_id' => 1 ,

View File

@ -11,11 +11,12 @@
namespace Tests\Feature;
use App\Models\BankIntegration;
use App\Models\BankTransaction;
use Tests\TestCase;
use App\Models\Expense;
use Tests\MockAccountData;
use App\Models\BankIntegration;
use App\Models\BankTransaction;
use App\Models\ExpenseCategory;
use App\Utils\Traits\MakesHash;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Session;
@ -232,6 +233,42 @@ class ExpenseApiTest extends TestCase
$this->assertTrue($arr['data'][0]['is_deleted']);
}
public function testExpenseBulkCategorize()
{
$e = Expense::factory()->create([
'company_id' => $this->company->id,
'user_id' => $this->user->id,
]);
$ec = ExpenseCategory::factory()->create([
'company_id' => $this->company->id,
'user_id' => $this->user->id,
'name' => 'Test Category',
]);
nlog("expense category id = {$ec->hashed_id}");
$data = [
'category_id' => $ec->hashed_id,
'action' => 'bulk_categorize',
'ids' => [$this->encodePrimaryKey($e->id)],
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/expenses/bulk', $data);
$arr = $response->json();
nlog($arr);
$this->assertEquals($ec->hashed_id, $arr['data'][0]['category_id']);
}
public function testAddingExpense()
{
$data = [

View File

@ -195,7 +195,7 @@ class UserTest extends TestCase
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $company_token->token,
'X-API-PASSWORD' => 'ALongAndBriliantPassword',
])->get("/api/v1/users?without={$company_token->user->hashed_id}&status=active");
])->get("/api/v1/users?status=active&without={$company_token->user->hashed_id}");
$response->assertStatus(200);
$this->assertCount(0, $response->json()['data']);
@ -204,7 +204,7 @@ class UserTest extends TestCase
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $company_token->token,
'X-API-PASSWORD' => 'ALongAndBriliantPassword',
])->get("/api/v1/users?without={$company_token->user->hashed_id}&status=archived");
])->get("/api/v1/users?status=archived&without={$company_token->user->hashed_id}");
$response->assertStatus(200);
$this->assertCount(1, $response->json()['data']);
@ -213,7 +213,7 @@ class UserTest extends TestCase
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $company_token->token,
'X-API-PASSWORD' => 'ALongAndBriliantPassword',
])->get("/api/v1/users?without={$company_token->user->hashed_id}&status=deleted");
])->get("/api/v1/users?status=deleted&without={$company_token->user->hashed_id}");
$response->assertStatus(200);
$this->assertCount(0, $response->json()['data']);

View File

@ -0,0 +1,242 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace Tests\Unit;
use Tests\TestCase;
use App\Models\User;
use App\Models\Design;
use App\Models\Payment;
/**
* @test
*/
class ArrayFiltersTest extends TestCase
{
private string $import_version = '';
private array $version_keys = [
'baseline' =>[],
'5.7.34' => [
Payment::class => [
'is_deleted',
'amount',
]
],
'5.7.35' => [
Payment::class => [
'date',
'transaction_reference',
],
User::class => [
'user_logged_in_notification',
'first_name',
'last_name',
],
Design::class => [
'is_template',
]
],
'5.7.36' => [
Payment::class => [
'type_id',
'status_id',
],
],
'5.7.37' => [
Payment::class => [
'currency_id',
'hashed_id',
],
],
];
protected function setUp() :void
{
parent::setUp();
}
public function testPaymentFilterFactory()
{
$p = Payment::factory()->make()->toArray();
$this->assertIsArray($p);
}
public function testPaymentUnsetProps()
{
$p = Payment::factory()->make()->toArray();
$version = '5.7.36';
$current_version = config('ninja.app_version');
$this->assertNotEquals($current_version, $version);
$index = 0;
$version_index = 0;
foreach($this->version_keys as $key => $value)
{
if($version == $key)
{
$version_index = $index;
}
$index++;
}
$this->assertEquals(3, $version_index);
$filters = collect($this->version_keys)->slice($version_index);
$this->assertEquals(2, $filters->count());
$x = collect($p)->diffKeys($filters->flatten()->flip());
$this->assertEquals(4, $x->count());
}
public function testPaymentUnsetPropsScenario2()
{
$p = Payment::factory()->make()->toArray();
$version = '5.7.35';
$current_version = config('ninja.app_version');
$this->assertNotEquals($current_version, $version);
$index = 0;
$version_index = 0;
foreach($this->version_keys as $key => $value)
{
if($version == $key)
{
$version_index = $index;
}
$index++;
}
$this->assertEquals(2, $version_index);
$index = 0;
$version_index = 0;
$filters = collect($this->version_keys)
->map(function ($value, $key) use ($version, &$version_index, &$index) {
if($version == $key)
$version_index = $index;
$index++;
return $value;
})
->slice($version_index)
->pluck(Payment::class);
$this->assertEquals(3, $filters->count());
$x = collect($p)->diffKeys($filters->flatten()->flip());
$this->assertEquals(2, $x->count());
}
public function testWhenScenario()
{
$p = Payment::factory()->make()->toArray();
$version = '5.7.35';
$current_version = '5.7.35';
$filters = collect($this->version_keys)
->map(function ($value, $key) use ($version, &$version_index, &$index) {
if($version == $key)
$version_index = $index;
$index++;
return $value;
})
->slice($version_index)
->pluck(Payment::class);
$this->assertEquals(3, $filters->count());
}
public function testWhenScenario2()
{
$p = Payment::factory()->make()->toArray();
$version = '5.7.33';
$current_version = '5.7.35';
$filters = collect($this->version_keys)
->map(function ($value, $key) use ($version, &$version_index, &$index) {
if($version == $key) {
$version_index = $index;
nlog("version = {$version_index}");
}
$index++;
return $value;
})
->slice($version_index ?? 0)
->pluck(Payment::class);
$x = collect($p)->diffKeys($filters->filter()->flatten()->flip());
$this->assertEquals(5, $filters->count());
}
private function filterArray($class, array $obj_array)
{
$index = 0;
$version_index = 0;
$filters = collect($this->version_keys)
->map(function ($value, $key) use (&$version_index, &$index) {
if($this->import_version == $key) {
$version_index = $index;
}
$index++;
return $value;
})
->when($version_index == 0, function ($collection){
return collect([]);
})
->when($version_index > 0, function ($collection) use (&$version_index, $class) {
return $collection->slice($version_index)->pluck($class)->filter();
});
return collect($obj_array)->diffKeys($filters->flatten()->flip())->toArray();
// return $filters->count() > 0 ? collect($obj_array)->diffKeys($filters->flatten()->flip())->toArray() : $obj_array;
}
public function testFilterArrayOne()
{
$u = User::factory()->make()->toArray();
$prop_count = count($u);
$this->import_version = '5.7.42';
$filtered_u = $this->filterArray(User::class, $u);
$this->assertCount($prop_count, $filtered_u);
}
}