mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-05-24 02:14:21 -04:00
Merge branch 'v5-develop' into bank_rules
This commit is contained in:
commit
0f2e19a873
@ -1 +1 @@
|
||||
5.5.41
|
||||
5.5.42
|
@ -22,6 +22,8 @@ use App\Jobs\Company\CreateCompanyTaskStatuses;
|
||||
use App\Jobs\Ninja\CompanySizeCheck;
|
||||
use App\Jobs\Util\VersionCheck;
|
||||
use App\Models\Account;
|
||||
use App\Models\BankIntegration;
|
||||
use App\Models\BankTransaction;
|
||||
use App\Models\Client;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\Company;
|
||||
@ -223,6 +225,18 @@ class DemoMode extends Command
|
||||
'company_id' => $company->id,
|
||||
]);
|
||||
|
||||
$bi = BankIntegration::factory()->create([
|
||||
'account_id' => $account->id,
|
||||
'company_id' => $company->id,
|
||||
'user_id' => $user->id,
|
||||
]);
|
||||
|
||||
BankTransaction::factory()->count(50)->create([
|
||||
'bank_integration_id' => $bi->id,
|
||||
'user_id' => $user->id,
|
||||
'company_id' => $company->id,
|
||||
]);
|
||||
|
||||
$this->info('Creating '.$this->count.' clients');
|
||||
|
||||
for ($x = 0; $x < $this->count; $x++) {
|
||||
|
@ -182,7 +182,7 @@ class Handler extends ExceptionHandler
|
||||
} elseif ($exception instanceof FatalThrowableError && $request->expectsJson()) {
|
||||
return response()->json(['message'=>'Fatal error'], 500);
|
||||
} elseif ($exception instanceof AuthorizationException) {
|
||||
return response()->json(['message'=>'You are not authorized to view or perform this action'], 401);
|
||||
return response()->json(['message'=> $exception->getMessage()], 401);
|
||||
} elseif ($exception instanceof TokenMismatchException) {
|
||||
return redirect()
|
||||
->back()
|
||||
|
@ -87,7 +87,8 @@ class AccountTransformer implements AccountTransformerInterface
|
||||
return [
|
||||
'id' => $account->id,
|
||||
'account_type' => $account->CONTAINER,
|
||||
'account_name' => $account->accountName,
|
||||
// 'account_name' => $account->accountName,
|
||||
'account_name' => property_exists($account, 'accountName') ? $account->accountName : $account->nickname,
|
||||
'account_status' => $account->accountStatus,
|
||||
'account_number' => property_exists($account, 'accountNumber') ? '**** ' . substr($account?->accountNumber, -7) : '',
|
||||
'provider_account_id' => $account->providerAccountId,
|
||||
|
@ -48,8 +48,15 @@ class EpcQrGenerator
|
||||
|
||||
$this->validateFields();
|
||||
|
||||
$qr = $writer->writeString($this->encodeMessage());
|
||||
|
||||
try {
|
||||
$qr = $writer->writeString($this->encodeMessage(), 'utf-8');
|
||||
}
|
||||
catch(\Throwable $e){
|
||||
return '';
|
||||
}
|
||||
catch(\Exception $e){
|
||||
return '';
|
||||
}
|
||||
return "<svg viewBox='0 0 200 200' width='200' height='200' x='0' y='0' xmlns='http://www.w3.org/2000/svg'>
|
||||
<rect x='0' y='0' width='100%'' height='100%' />{$qr}</svg>";
|
||||
|
||||
|
@ -87,10 +87,10 @@ class SwissQrGenerator
|
||||
$qrBill->setUltimateDebtor(
|
||||
QrBill\DataGroup\Element\StructuredAddress::createWithStreet(
|
||||
substr($this->client->present()->name(), 0 , 70),
|
||||
$this->client->address1 ? substr($this->client->address1, 0 , 70) : '',
|
||||
$this->client->address2 ? substr($this->client->address2, 0 , 16) : '',
|
||||
$this->client->postal_code ? substr($this->client->postal_code, 0, 16) : '',
|
||||
$this->client->city ? substr($this->client->city, 0, 35) : '',
|
||||
$this->client->address1 ? substr($this->client->address1, 0 , 70) : '_',
|
||||
$this->client->address2 ? substr($this->client->address2, 0 , 16) : '_',
|
||||
$this->client->postal_code ? substr($this->client->postal_code, 0, 16) : '_',
|
||||
$this->client->city ? substr($this->client->city, 0, 35) : '_',
|
||||
'CH'
|
||||
));
|
||||
|
||||
|
@ -164,7 +164,8 @@ class CompanyController extends BaseController
|
||||
*/
|
||||
public function create(CreateCompanyRequest $request)
|
||||
{
|
||||
$company = CompanyFactory::create(auth()->user()->company()->account->id);
|
||||
$cf = new \App\Factory\CompanyFactory;
|
||||
$company = $cf->create(auth()->user()->company()->account->id);
|
||||
|
||||
return $this->itemResponse($company);
|
||||
}
|
||||
|
@ -777,7 +777,7 @@ class InvoiceController extends BaseController
|
||||
case 'email':
|
||||
//check query parameter for email_type and set the template else use calculateTemplate
|
||||
|
||||
if (request()->has('email_type') && property_exists($invoice->company->settings, request()->input('email_type'))) {
|
||||
if (request()->has('email_type') && in_array(request()->input('email_type'), ['reminder1', 'reminder2', 'reminder3', 'reminder_endless', 'custom1', 'custom2', 'custom3'])) {
|
||||
$this->reminder_template = $invoice->client->getSetting(request()->input('email_type'));
|
||||
} else {
|
||||
$this->reminder_template = $invoice->calculateTemplate('invoice');
|
||||
|
@ -41,6 +41,7 @@ class ContactKeyLogin
|
||||
$request->session()->invalidate();
|
||||
}
|
||||
|
||||
//magic links survive for 1 hour
|
||||
if ($request->segment(2) && $request->segment(2) == 'magic_link' && $request->segment(3)) {
|
||||
$payload = Cache::get($request->segment(3));
|
||||
|
||||
@ -66,7 +67,11 @@ class ContactKeyLogin
|
||||
}
|
||||
} elseif ($request->segment(3) && config('ninja.db.multi_db_enabled')) {
|
||||
if (MultiDB::findAndSetDbByContactKey($request->segment(3))) {
|
||||
if ($client_contact = ClientContact::where('contact_key', $request->segment(3))->first()) {
|
||||
if ($client_contact = ClientContact::with('company')->where('contact_key', $request->segment(3))->first()) {
|
||||
|
||||
if($client_contact->company->settings->enable_client_portal_password)
|
||||
return redirect()->route('client.login', ['company_key' => $client_contact->company->company_key]);
|
||||
|
||||
if (empty($client_contact->email)) {
|
||||
$client_contact->email = Str::random(6).'@example.com';
|
||||
}
|
||||
@ -82,7 +87,11 @@ class ContactKeyLogin
|
||||
}
|
||||
}
|
||||
} elseif ($request->segment(2) && $request->segment(2) == 'key_login' && $request->segment(3)) {
|
||||
if ($client_contact = ClientContact::where('contact_key', $request->segment(3))->first()) {
|
||||
if ($client_contact = ClientContact::with('company')->where('contact_key', $request->segment(3))->first()) {
|
||||
|
||||
if($client_contact->company->settings->enable_client_portal_password)
|
||||
return redirect()->route('client.login', ['company_key' => $client_contact->company->company_key]);
|
||||
|
||||
if (empty($client_contact->email)) {
|
||||
$client_contact->email = Str::random(6).'@example.com';
|
||||
$client_contact->save();
|
||||
@ -125,7 +134,11 @@ class ContactKeyLogin
|
||||
return redirect($this->setRedirectPath());
|
||||
}
|
||||
} elseif ($request->segment(3)) {
|
||||
if ($client_contact = ClientContact::where('contact_key', $request->segment(3))->first()) {
|
||||
if ($client_contact = ClientContact::with('company')->where('contact_key', $request->segment(3))->first()) {
|
||||
|
||||
if($client_contact->company->settings->enable_client_portal_password)
|
||||
return redirect()->route('client.login', ['company_key' => $client_contact->company->company_key]);
|
||||
|
||||
if (empty($client_contact->email)) {
|
||||
$client_contact->email = Str::random(6).'@example.com';
|
||||
$client_contact->save();
|
||||
|
@ -44,7 +44,7 @@ class StoreBankTransactionRequest extends Request
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
if(array_key_exists('bank_integration_id', $input) && strlen($input['bank_integration_id']) > 1)
|
||||
if(array_key_exists('bank_integration_id', $input) && strlen($input['bank_integration_id']) > 1 && !is_numeric($input['bank_integration_id']))
|
||||
$input['bank_integration_id'] = $this->decodePrimaryKey($input['bank_integration_id']);
|
||||
|
||||
$this->replace($input);
|
||||
|
@ -108,6 +108,8 @@ class UpdateCompanyRequest extends Request
|
||||
}
|
||||
}
|
||||
|
||||
$settings['email_style_custom'] = str_replace("{{", "", $settings['email_style_custom']);
|
||||
|
||||
if (! $account->isFreeHostedClient()) {
|
||||
return $settings;
|
||||
}
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace App\Http\Requests\Report;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class GenericReportRequest extends Request
|
||||
{
|
||||
@ -27,11 +28,14 @@ class GenericReportRequest extends Request
|
||||
|
||||
public function rules()
|
||||
{
|
||||
nlog($this->date_range);
|
||||
|
||||
return [
|
||||
'start_date' => 'string|date',
|
||||
'end_date' => 'string|date',
|
||||
'date_key' => 'string',
|
||||
'date_range' => 'sometimes|string',
|
||||
'date_range' => 'bail|required|string',
|
||||
// 'start_date' => [Rule::requiredIf($this->date_range === 'custom')],
|
||||
// 'end_date' => [Rule::requiredIf($this->date_range === 'custom')],
|
||||
'end_date' => 'bail|required_if:date_range,custom|nullable|date',
|
||||
'start_date' => 'bail|required_if:date_range,custom|nullable|date',
|
||||
'report_keys' => 'present|array',
|
||||
'send_email' => 'required|bool',
|
||||
];
|
||||
|
@ -28,8 +28,8 @@ class ProfitLossRequest extends Request
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'start_date' => 'string|date',
|
||||
'end_date' => 'string|date',
|
||||
'start_date' => 'required_if:date_range,custom|string|date',
|
||||
'end_date' => 'required_if:date_range,custom|string|date',
|
||||
'is_income_billed' => 'required|bail|bool',
|
||||
'is_expense_billed' => 'bool',
|
||||
'include_tax' => 'required|bail|bool',
|
||||
|
@ -15,6 +15,7 @@ use App\Http\Requests\Request;
|
||||
use App\Models\Project;
|
||||
use App\Utils\Traits\ChecksEntityStatus;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateTaskRequest extends Request
|
||||
@ -29,6 +30,10 @@ class UpdateTaskRequest extends Request
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
//prevent locked tasks from updating
|
||||
if($this->task->invoice_lock && $this->task->invoice_id)
|
||||
return false;
|
||||
|
||||
return auth()->user()->can('edit', $this->task);
|
||||
}
|
||||
|
||||
@ -87,4 +92,11 @@ class UpdateTaskRequest extends Request
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
|
||||
|
||||
protected function failedAuthorization()
|
||||
{
|
||||
throw new AuthorizationException(ctrans('texts.task_update_authorization_error'));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -293,7 +293,7 @@ class CompanyExport implements ShouldQueue
|
||||
$this->export_data['payments'] = $this->company->payments()->orderBy('number', 'DESC')->cursor()->map(function ($payment){
|
||||
|
||||
$payment = $this->transformBasicEntities($payment);
|
||||
$payment = $this->transformArrayOfKeys($payment, ['client_id','project_id', 'vendor_id', 'client_contact_id', 'invitation_id', 'company_gateway_id']);
|
||||
$payment = $this->transformArrayOfKeys($payment, ['client_id','project_id', 'vendor_id', 'client_contact_id', 'invitation_id', 'company_gateway_id', 'transaction_id']);
|
||||
|
||||
$payment->paymentables = $this->transformPaymentable($payment);
|
||||
|
||||
@ -456,7 +456,6 @@ class CompanyExport implements ShouldQueue
|
||||
|
||||
})->all();
|
||||
|
||||
|
||||
$this->export_data['purchase_order_invitations'] = PurchaseOrderInvitation::where('company_id', $this->company->id)->withTrashed()->cursor()->map(function ($purchase_order){
|
||||
|
||||
$purchase_order = $this->transformArrayOfKeys($purchase_order, ['company_id', 'user_id', 'vendor_contact_id', 'purchase_order_id']);
|
||||
@ -466,6 +465,21 @@ class CompanyExport implements ShouldQueue
|
||||
})->all();
|
||||
|
||||
|
||||
$this->export_data['bank_integrations'] = $this->company->bank_integrations()->orderBy('id', 'ASC')->cursor()->map(function ($bank_integration){
|
||||
|
||||
$bank_integration = $this->transformArrayOfKeys($bank_integration, ['account_id','company_id', 'user_id']);
|
||||
|
||||
return $bank_integration->makeVisible(['id','user_id','company_id','account_id']);
|
||||
|
||||
})->all();
|
||||
|
||||
$this->export_data['bank_transactions'] = $this->company->bank_transactions()->orderBy('id', 'ASC')->cursor()->map(function ($bank_transaction){
|
||||
|
||||
$bank_transaction = $this->transformArrayOfKeys($bank_transaction, ['company_id', 'user_id','bank_integration_id','expense_id','category_id','ninja_category_id','vendor_id']);
|
||||
|
||||
return $bank_transaction->makeVisible(['id','user_id','company_id']);
|
||||
|
||||
})->all();
|
||||
|
||||
//write to tmp and email to owner();
|
||||
|
||||
@ -516,9 +530,6 @@ class CompanyExport implements ShouldQueue
|
||||
$file_name = date('Y-m-d').'_'.str_replace([" ", "/"],["_",""], $this->company->present()->name() . '_' . $this->company->company_key .'.zip');
|
||||
|
||||
$path = 'backups';
|
||||
|
||||
// if(!Storage::disk(config('filesystems.default'))->exists($path))
|
||||
// Storage::disk(config('filesystems.default'))->makeDirectory($path, 0775);
|
||||
|
||||
$zip_path = public_path('storage/backups/'.$file_name);
|
||||
$zip = new \ZipArchive();
|
||||
|
@ -24,6 +24,8 @@ use App\Mail\Import\CompanyImportFailure;
|
||||
use App\Mail\Import\ImportCompleted;
|
||||
use App\Models\Activity;
|
||||
use App\Models\Backup;
|
||||
use App\Models\BankIntegration;
|
||||
use App\Models\BankTransaction;
|
||||
use App\Models\Client;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\ClientGatewayToken;
|
||||
@ -142,15 +144,16 @@ class CompanyImport implements ShouldQueue
|
||||
'expenses',
|
||||
'tasks',
|
||||
'payments',
|
||||
// 'activities',
|
||||
// 'backups',
|
||||
'company_ledger',
|
||||
'designs',
|
||||
'documents',
|
||||
'webhooks',
|
||||
'system_logs',
|
||||
'purchase_orders',
|
||||
'purchase_order_invitations'
|
||||
'purchase_order_invitations',
|
||||
'bank_integrations',
|
||||
'bank_transactions',
|
||||
'payments',
|
||||
];
|
||||
|
||||
private $company_properties = [
|
||||
@ -527,6 +530,37 @@ class CompanyImport implements ShouldQueue
|
||||
|
||||
}
|
||||
|
||||
private function import_bank_integrations()
|
||||
{
|
||||
$this->genericImport(BankIntegration::class,
|
||||
['assigned_user_id','account_id', 'company_id', 'id', 'hashed_id'],
|
||||
[
|
||||
['users' => 'user_id'],
|
||||
],
|
||||
'bank_integrations',
|
||||
'description');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function import_bank_transactions()
|
||||
{
|
||||
$this->genericImport(BankTransaction::class,
|
||||
['assigned_user_id','company_id', 'id', 'hashed_id', 'user_id'],
|
||||
[
|
||||
['users' => 'user_id'],
|
||||
['expenses' => 'expense_id'],
|
||||
['vendors' => 'vendor_id'],
|
||||
['expense_categories' => 'ninja_category_id'],
|
||||
['expense_categories' => 'category_id'],
|
||||
['bank_integrations' => 'bank_integration_id']
|
||||
],
|
||||
'bank_transactions',
|
||||
null);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function import_recurring_expenses()
|
||||
{
|
||||
//unset / transforms / object_property / match_key
|
||||
@ -979,6 +1013,7 @@ class CompanyImport implements ShouldQueue
|
||||
['vendors' => 'vendor_id'],
|
||||
['invoice_invitations' => 'invitation_id'],
|
||||
['company_gateways' => 'company_gateway_id'],
|
||||
['bank_transactions' => 'transaction_id'],
|
||||
],
|
||||
'payments',
|
||||
'number');
|
||||
@ -1569,6 +1604,28 @@ class CompanyImport implements ShouldQueue
|
||||
$obj_array,
|
||||
);
|
||||
}
|
||||
elseif($class == 'App\Models\BankIntegration'){
|
||||
$new_obj = new BankIntegration();
|
||||
$new_obj->company_id = $this->company->id;
|
||||
$new_obj->account_id = $this->account->id;
|
||||
$new_obj->fill($obj_array);
|
||||
$new_obj->save(['timestamps' => false]);
|
||||
}
|
||||
elseif($class == 'App\Models\BankTransaction'){
|
||||
|
||||
$new_obj = new BankTransaction();
|
||||
$new_obj->company_id = $this->company->id;
|
||||
|
||||
$obj_array['invoice_ids'] = collect(explode(",",$obj_array['invoice_ids']))->map(function ($id) {
|
||||
return $this->transformId('invoices', $id);
|
||||
})->map(function ($encodeable){
|
||||
return $this->encodePrimaryKey($encodeable);
|
||||
})->implode(",");
|
||||
|
||||
|
||||
$new_obj->fill($obj_array);
|
||||
$new_obj->save(['timestamps' => false]);
|
||||
}
|
||||
else{
|
||||
$new_obj = $class::withTrashed()->firstOrNew(
|
||||
[$match_key => $obj->{$match_key}, 'company_id' => $this->company->id],
|
||||
|
@ -129,11 +129,8 @@ class NinjaMailerJob implements ShouldQueue
|
||||
LightLogs::create(new EmailSuccess($this->nmo->company->company_key))
|
||||
->send();
|
||||
|
||||
// nlog('Using ' . ((int) (memory_get_usage(true) / (1024 * 1024))) . 'MB ');
|
||||
|
||||
$this->nmo = null;
|
||||
$this->company = null;
|
||||
app('queue.worker')->shouldQuit = 1;
|
||||
|
||||
} catch (\Exception | \RuntimeException | \Google\Service\Exception $e) {
|
||||
|
||||
|
@ -19,6 +19,9 @@ use stdClass;
|
||||
|
||||
class InvoiceEmailFailedActivity implements ShouldQueue
|
||||
{
|
||||
|
||||
// public $delay = 10;
|
||||
|
||||
protected $activity_repo;
|
||||
|
||||
/**
|
||||
|
@ -18,13 +18,18 @@ use App\Libraries\MultiDB;
|
||||
use App\Mail\Admin\EntityFailedSendObject;
|
||||
use App\Notifications\Admin\EntitySentNotification;
|
||||
use App\Utils\Traits\Notifications\UserNotifies;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class InvoiceFailedEmailNotification
|
||||
{
|
||||
use UserNotifies;
|
||||
|
||||
public $delay = 5;
|
||||
use UserNotifies, Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $delay = 10;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
|
@ -124,6 +124,7 @@ class Company extends BaseModel
|
||||
'enabled_expense_tax_rates',
|
||||
'invoice_task_project',
|
||||
'report_include_deleted',
|
||||
'invoice_task_lock',
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
|
@ -40,6 +40,7 @@ class Task extends BaseModel
|
||||
'number',
|
||||
'is_date_based',
|
||||
'status_order',
|
||||
'invoice_lock'
|
||||
];
|
||||
|
||||
protected $touches = [];
|
||||
|
@ -111,7 +111,10 @@ class PayPal
|
||||
'paymentMethodNonce' => $gateway_response->nonce,
|
||||
]);
|
||||
|
||||
return $payment_method->paymentMethod->token;
|
||||
if($payment_method->success)
|
||||
return $payment_method->paymentMethod->token;
|
||||
else
|
||||
throw new PaymentFailed(property_exists($payment_method, 'message') ? $payment_method->message : 'Undefined error storing payment token.', 0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -306,10 +306,53 @@ class CheckoutComPaymentDriver extends BaseDriver
|
||||
|
||||
try {
|
||||
$response = $this->gateway->getCustomersClient()->create($request);
|
||||
} catch (\Exception $e) {
|
||||
// API error
|
||||
throw new PaymentFailed($e->getMessage(), $e->getCode());
|
||||
}
|
||||
catch (CheckoutApiException $e) {
|
||||
// API error
|
||||
$request_id = $e->request_id;
|
||||
$http_status_code = $e->http_status_code;
|
||||
$error_details = $e->error_details;
|
||||
|
||||
if(is_array($error_details)) {
|
||||
$error_details = end($e->error_details['error_codes']);
|
||||
}
|
||||
|
||||
$human_exception = $error_details ? new \Exception($error_details, 400) : $e;
|
||||
|
||||
|
||||
throw new PaymentFailed($human_exception);
|
||||
} catch (CheckoutArgumentException $e) {
|
||||
// Bad arguments
|
||||
|
||||
$error_details = $e->error_details;
|
||||
|
||||
if(is_array($error_details)) {
|
||||
$error_details = end($e->error_details['error_codes']);
|
||||
}
|
||||
|
||||
$human_exception = $error_details ? new \Exception($error_details, 400) : $e;
|
||||
|
||||
throw new PaymentFailed($human_exception);
|
||||
} catch (CheckoutAuthorizationException $e) {
|
||||
// Bad Invalid authorization
|
||||
|
||||
$error_details = $e->error_details;
|
||||
|
||||
if(is_array($error_details)) {
|
||||
$error_details = end($e->error_details['error_codes']);
|
||||
}
|
||||
|
||||
$human_exception = $error_details ? new \Exception($error_details, 400) : $e;
|
||||
|
||||
throw new PaymentFailed($human_exception);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// catch (\Exception $e) {
|
||||
// // API error
|
||||
// throw new PaymentFailed($e->getMessage(), $e->getCode());
|
||||
// }
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
@ -63,7 +63,11 @@ class ImportCustomers
|
||||
$this->addCustomer($customer);
|
||||
}
|
||||
|
||||
$starting_after = end($customers->data)['id'];
|
||||
//handle
|
||||
if(is_array($customers->data) && end($customers->data) && array_key_exists('id', end($customers->data)))
|
||||
$starting_after = end($customers->data)['id'];
|
||||
else
|
||||
break;
|
||||
|
||||
} while ($customers->has_more);
|
||||
}
|
||||
|
@ -93,7 +93,7 @@ class PaymentIntentWebhook implements ShouldQueue
|
||||
return;
|
||||
|
||||
|
||||
if(optional($this->stripe_request['object']['charges']['data'][0])['id']){
|
||||
if(isset($this->stripe_request['object']['charges']) && optional($this->stripe_request['object']['charges']['data'][0])['id']){
|
||||
|
||||
$company = Company::where('company_key', $this->company_key)->first();
|
||||
|
||||
|
@ -451,7 +451,7 @@ class EventServiceProvider extends ServiceProvider
|
||||
PaymentEmailedActivity::class,
|
||||
],
|
||||
PaymentWasEmailedAndFailed::class => [
|
||||
PaymentEmailFailureActivity::class,
|
||||
// PaymentEmailFailureActivity::class,
|
||||
],
|
||||
PurchaseOrderWasArchived::class => [
|
||||
PurchaseOrderArchivedActivity::class,
|
||||
|
@ -60,7 +60,7 @@ class ActivityRepository extends BaseRepository
|
||||
$activity->save();
|
||||
|
||||
//rate limiter
|
||||
// $this->createBackup($entity, $activity);
|
||||
$this->createBackup($entity, $activity);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -104,7 +104,6 @@ class DeletePayment
|
||||
|
||||
$client = $this->payment
|
||||
->client
|
||||
->fresh()
|
||||
->service()
|
||||
->updateBalance($net_deletable)
|
||||
->save();
|
||||
@ -136,9 +135,8 @@ class DeletePayment
|
||||
});
|
||||
}
|
||||
|
||||
$client = $this->payment->client->fresh();
|
||||
|
||||
$client
|
||||
$this->payment
|
||||
->client
|
||||
->service()
|
||||
->updatePaidToDate(($this->payment->amount - $this->payment->refunded) * -1)
|
||||
->save();
|
||||
@ -146,7 +144,7 @@ class DeletePayment
|
||||
$transaction = [
|
||||
'invoice' => [],
|
||||
'payment' => [],
|
||||
'client' => $client->transaction_event(),
|
||||
'client' => $this->payment->client->transaction_event(),
|
||||
'credit' => [],
|
||||
'metadata' => [],
|
||||
];
|
||||
|
@ -62,12 +62,14 @@ class UpdateInvoicePayment
|
||||
$paid_amount = $paid_invoice->amount;
|
||||
}
|
||||
|
||||
$client->service()->updateBalanceAndPaidToDate($paid_amount*-1, $paid_amount);
|
||||
$client->service()->updatePaidToDate($paid_amount); //always use the payment->amount
|
||||
|
||||
/* Need to determine here is we have an OVER payment - if YES only apply the max invoice amount */
|
||||
if($paid_amount > $invoice->partial && $paid_amount > $invoice->balance)
|
||||
$paid_amount = $invoice->balance;
|
||||
|
||||
$client->service()->updateBalance($paid_amount*-1); //only ever use the amount applied to the invoice
|
||||
|
||||
/*Improve performance here - 26-01-2022 - also change the order of events for invoice first*/
|
||||
//caution what if we amount paid was less than partial - we wipe it!
|
||||
$invoice->balance -= $paid_amount;
|
||||
|
@ -40,12 +40,8 @@ use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class TaskSchedulerService
|
||||
{
|
||||
public Scheduler $scheduler;
|
||||
|
||||
public function __construct(Scheduler $scheduler)
|
||||
{
|
||||
$this->scheduler = $scheduler;
|
||||
}
|
||||
|
||||
public function __construct(public Scheduler $scheduler) {}
|
||||
|
||||
public function store(Scheduler $scheduler, CreateScheduledTaskRequest $request)
|
||||
{
|
||||
|
@ -188,6 +188,7 @@ class CompanyTransformer extends EntityTransformer
|
||||
'enabled_expense_tax_rates' => (int) $company->enabled_expense_tax_rates,
|
||||
'invoice_task_project' => (bool) $company->invoice_task_project,
|
||||
'report_include_deleted' => (bool) $company->report_include_deleted,
|
||||
'invoice_task_lock' => (bool) $company->invoice_task_lock,
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -72,7 +72,6 @@ class TaskTransformer extends EntityTransformer
|
||||
'user_id' => (string) $this->encodePrimaryKey($task->user_id),
|
||||
'assigned_user_id' => (string) $this->encodePrimaryKey($task->assigned_user_id),
|
||||
'number' => (string) $task->number ?: '',
|
||||
// 'start_time' => (int) $task->start_time,
|
||||
'description' => (string) $task->description ?: '',
|
||||
'duration' => (int) $task->duration ?: 0,
|
||||
'rate' => (float) $task->rate ?: 0,
|
||||
@ -93,6 +92,7 @@ class TaskTransformer extends EntityTransformer
|
||||
'status_sort_order' => (int) $task->status_sort_order, //deprecated 5.0.34
|
||||
'is_date_based' => (bool) $task->is_date_based,
|
||||
'status_order' => is_null($task->status_order) ? null : (int) $task->status_order,
|
||||
'invoice_lock' => (bool) $task->invoice_lock,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -134,6 +134,30 @@ class Helpers
|
||||
|
||||
$replacements = [
|
||||
'literal' => [
|
||||
':MONTH_BEFORE' => \sprintf(
|
||||
'%s %s %s',
|
||||
Carbon::now()->subMonth(1)->translatedFormat($entity->date_format()),
|
||||
ctrans('texts.to'),
|
||||
Carbon::now()->subDay(1)->translatedFormat($entity->date_format()),
|
||||
),
|
||||
':YEAR_BEFORE' => \sprintf(
|
||||
'%s %s %s',
|
||||
Carbon::now()->subYear(1)->translatedFormat($entity->date_format()),
|
||||
ctrans('texts.to'),
|
||||
Carbon::now()->subDay(1)->translatedFormat($entity->date_format()),
|
||||
),
|
||||
':MONTH_AFTER' => \sprintf(
|
||||
'%s %s %s',
|
||||
Carbon::now()->translatedFormat($entity->date_format()),
|
||||
ctrans('texts.to'),
|
||||
Carbon::now()->addMonth(1)->subDay(1)->translatedFormat($entity->date_format()),
|
||||
),
|
||||
':YEAR_AFTER' => \sprintf(
|
||||
'%s %s %s',
|
||||
Carbon::now()->translatedFormat($entity->date_format()),
|
||||
ctrans('texts.to'),
|
||||
Carbon::now()->addYear(1)->subDay(1)->translatedFormat($entity->date_format()),
|
||||
),
|
||||
':MONTHYEAR' => \sprintf(
|
||||
'%s %s',
|
||||
Carbon::createFromDate(now()->month)->translatedFormat('F'),
|
||||
@ -150,15 +174,15 @@ class Helpers
|
||||
),
|
||||
':WEEK_AHEAD' => \sprintf(
|
||||
'%s %s %s',
|
||||
Carbon::now()->addDays(6)->translatedFormat($entity->date_format()),
|
||||
Carbon::now()->addDays(7)->translatedFormat($entity->date_format()),
|
||||
ctrans('texts.to'),
|
||||
Carbon::now()->addDays(13)->translatedFormat($entity->date_format())
|
||||
),
|
||||
':WEEK' => \sprintf(
|
||||
'%s %s %s',
|
||||
Carbon::now()->subDays(7)->translatedFormat($entity->date_format()),
|
||||
Carbon::now()->translatedFormat($entity->date_format()),
|
||||
ctrans('texts.to'),
|
||||
Carbon::now()->addDays(13)->translatedFormat($entity->date_format())
|
||||
Carbon::now()->addDays(6)->translatedFormat($entity->date_format())
|
||||
),
|
||||
],
|
||||
'raw' => [
|
||||
|
@ -95,10 +95,14 @@ class SystemHealth
|
||||
|
||||
if(strlen(config('ninja.currency_converter_api_key')) == 0){
|
||||
|
||||
try{
|
||||
$cs = DB::table('clients')
|
||||
->select('settings->currency_id as id')
|
||||
->get();
|
||||
|
||||
}
|
||||
catch(\Exception $e){
|
||||
return true; //fresh installs, there may be no DB connection, nor migrations could have run yet.
|
||||
}
|
||||
|
||||
$currency_count = $cs->unique('id')->filter(function ($value){
|
||||
return !is_null($value->id);
|
||||
|
@ -14,8 +14,8 @@ return [
|
||||
'require_https' => env('REQUIRE_HTTPS', true),
|
||||
'app_url' => rtrim(env('APP_URL', ''), '/'),
|
||||
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
|
||||
'app_version' => '5.5.41',
|
||||
'app_tag' => '5.5.41',
|
||||
'app_version' => '5.5.42',
|
||||
'app_tag' => '5.5.42',
|
||||
'minimum_client_version' => '5.0.16',
|
||||
'terms_version' => '1.0.1',
|
||||
'api_secret' => env('API_SECRET', ''),
|
||||
|
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('tasks', function (Blueprint $table)
|
||||
{
|
||||
$table->boolean('invoice_lock')->default(false);
|
||||
});
|
||||
|
||||
Schema::table('companies', function (Blueprint $table)
|
||||
{
|
||||
$table->boolean('invoice_task_lock')->default(false);
|
||||
});
|
||||
|
||||
Schema::table('bank_transactions', function (Blueprint $table)
|
||||
{
|
||||
$table->bigInteger('bank_rule_id')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
};
|
@ -4843,6 +4843,11 @@ $LANG = array(
|
||||
'refresh_accounts' => 'Refresh Accounts',
|
||||
'upgrade_to_connect_bank_account' => 'Upgrade to Enterprise to connect your bank account',
|
||||
'click_here_to_connect_bank_account' => 'Click here to connect your bank account',
|
||||
'task_update_authorization_error' => 'Insufficient permissions, or task may be locked',
|
||||
'cash_vs_accrual' => 'Accrual accounting',
|
||||
'cash_vs_accrual_help' => 'Turn on for accrual reporting, turn off for cash basis reporting.',
|
||||
'expense_paid_report' => 'Expensed reporting',
|
||||
'expense_paid_report_help' => 'Turn on for reporting all expenses, turn off for reporting only paid expenses',
|
||||
);
|
||||
|
||||
return $LANG;
|
||||
|
4
public/flutter_service_worker.js
vendored
4
public/flutter_service_worker.js
vendored
@ -11,9 +11,9 @@ const RESOURCES = {
|
||||
"favicon.png": "dca91c54388f52eded692718d5a98b8b",
|
||||
"favicon.ico": "51636d3a390451561744c42188ccd628",
|
||||
"flutter.js": "f85e6fb278b0fd20c349186fb46ae36d",
|
||||
"/": "112f22769207bffb3936c08dec3ffa4d",
|
||||
"/": "9d48b2826c07eb42f0817289b6a7b7ca",
|
||||
"manifest.json": "ef43d90e57aa7682d7e2cfba2f484a40",
|
||||
"main.dart.js": "bddfba2a7d482fece1e7a9ff84429256",
|
||||
"main.dart.js": "3568e02ff28e4dae78f695088a6e21c5",
|
||||
"assets/AssetManifest.json": "759f9ef9973f7e26c2a51450b55bb9fa",
|
||||
"assets/FontManifest.json": "087fb858dc3cbfbf6baf6a30004922f1",
|
||||
"assets/NOTICES": "1a34e70168d56fad075adfb4bdbb20eb",
|
||||
|
131504
public/main.dart.js
vendored
131504
public/main.dart.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
129170
public/main.foss.dart.js
vendored
129170
public/main.foss.dart.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
8301
public/main.profile.dart.js
vendored
8301
public/main.profile.dart.js
vendored
File diff suppressed because one or more lines are too long
@ -257,7 +257,7 @@
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.stamp {
|
||||
.stamp {
|
||||
transform: rotate(12deg);
|
||||
color: #555;
|
||||
font-size: 3rem;
|
||||
|
@ -249,6 +249,16 @@
|
||||
opacity: 0.2;
|
||||
z-index:200 !important;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.project-header {
|
||||
font-size: 1.5em;
|
||||
margin-top: 0.1em;
|
||||
margin-bottom: 0.1em;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
font-weight: bold;
|
||||
color: #505050;
|
||||
}
|
||||
/** Useful snippets, uncomment to enable. **/
|
||||
|
||||
|
@ -31,7 +31,7 @@
|
||||
{{ ctrans('texts.type') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
{{ $payment_method->meta?->brand }}
|
||||
{{ property_exists($payment_method->meta, 'brand') ? $payment_method->meta?->brand : ''}}
|
||||
{{ property_exists($payment_method->meta, 'scheme') ? $payment_method->meta?->scheme : '' }}
|
||||
</dd>
|
||||
</div>
|
||||
|
@ -186,7 +186,7 @@ Route::group(['middleware' => ['throttle:300,1', 'api_db', 'token_auth', 'locale
|
||||
Route::put('expenses/{expense}/upload', [ExpenseController::class, 'upload']);
|
||||
Route::post('expenses/bulk', [ExpenseController::class, 'bulk'])->name('expenses.bulk');
|
||||
|
||||
Route::post('export', [ExportController::class, 'index'])->name('export.index');
|
||||
Route::post('export', [ExportController::class, 'index'])->middleware('throttle:2,1')->name('export.index');
|
||||
|
||||
Route::resource('expense_categories', ExpenseCategoryController::class); // name = (expense_categories. index / create / show / update / destroy / edit
|
||||
Route::post('expense_categories/bulk', [ExpenseCategoryController::class, 'bulk'])->name('expense_categories.bulk');
|
||||
@ -196,7 +196,7 @@ Route::group(['middleware' => ['throttle:300,1', 'api_db', 'token_auth', 'locale
|
||||
Route::put('group_settings/{group_setting}/upload', [GroupSettingController::class, 'upload'])->name('group_settings.upload');
|
||||
|
||||
Route::post('import', [ImportController::class, 'import'])->name('import.import');
|
||||
Route::post('import_json', [ImportJsonController::class, 'import'])->name('import.import_json');
|
||||
Route::post('import_json', [ImportJsonController::class, 'import'])->middleware('throttle:2,1')->name('import.import_json');
|
||||
Route::post('preimport', [ImportController::class, 'preimport'])->name('import.preimport');
|
||||
|
||||
Route::resource('invoices', InvoiceController::class); // name = (invoices. index / create / show / update / destroy / edit
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Task;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
@ -42,6 +43,90 @@ class TaskApiTest extends TestCase
|
||||
Model::reguard();
|
||||
}
|
||||
|
||||
|
||||
public function testTaskLockingGate()
|
||||
{
|
||||
$data = [
|
||||
'timelog' => [[1,2],[3,4]],
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/tasks', $data);
|
||||
|
||||
$arr = $response->json();
|
||||
$response->assertStatus(200);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->putJson('/api/v1/tasks/' . $arr['data']['id'], $data);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$task = Task::find($this->decodePrimaryKey($arr['data']['id']));
|
||||
$task->invoice_id = $this->invoice->id;
|
||||
$task->save();
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->putJson('/api/v1/tasks/' . $arr['data']['id'], $data);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$task = Task::find($this->decodePrimaryKey($arr['data']['id']));
|
||||
$task->invoice_lock =true;
|
||||
$task->invoice_id = $this->invoice->id;
|
||||
$task->save();
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->putJson('/api/v1/tasks/' . $arr['data']['id'], $data);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$response->assertStatus(401);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function testTaskLocking()
|
||||
{
|
||||
$data = [
|
||||
'timelog' => [[1,2],[3,4]],
|
||||
'invoice_lock' => true
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/tasks', $data);
|
||||
|
||||
$arr = $response->json();
|
||||
$response->assertStatus(200);
|
||||
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->putJson('/api/v1/tasks/' . $arr['data']['id'], $data);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public function testTimeLogValidation()
|
||||
{
|
||||
$data = [
|
||||
@ -75,9 +160,10 @@ class TaskApiTest extends TestCase
|
||||
$arr = $response->json();
|
||||
$response->assertStatus(200);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function testTimeLogValidation2()
|
||||
{
|
||||
$data = [
|
||||
|
Loading…
x
Reference in New Issue
Block a user