mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-10 21:22:58 +01:00
commit
bbffc6528f
@ -83,6 +83,8 @@ class CheckData extends Command
|
||||
$this->checkInvoiceBalances();
|
||||
$this->checkClientBalances();
|
||||
$this->checkContacts();
|
||||
$this->checkCompanyData();
|
||||
|
||||
//$this->checkLogoFiles();
|
||||
|
||||
if (! $this->option('client_id')) {
|
||||
@ -388,4 +390,67 @@ class CheckData extends Command
|
||||
['database', null, InputOption::VALUE_OPTIONAL, 'Database', null],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
private function checkCompanyData()
|
||||
{
|
||||
$tables = [
|
||||
'activities' => [
|
||||
'invoice',
|
||||
'client',
|
||||
'client_contact',
|
||||
'payment',
|
||||
],
|
||||
'invoices' => [
|
||||
'client',
|
||||
],
|
||||
'payments' => [
|
||||
'client',
|
||||
],
|
||||
'products' => [
|
||||
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($tables as $table => $entityTypes) {
|
||||
foreach ($entityTypes as $entityType) {
|
||||
$tableName = $this->pluralizeEntityType($entityType);
|
||||
$field = $entityType;
|
||||
if ($table == 'companies') {
|
||||
$company_id = 'id';
|
||||
} else {
|
||||
$company_id = 'company_id';
|
||||
}
|
||||
$records = \DB::table($table)
|
||||
->join($tableName, "{$tableName}.id", '=', "{$table}.{$field}_id")
|
||||
->where("{$table}.{$company_id}", '!=', \DB::raw("{$tableName}.company_id"))
|
||||
->get(["{$table}.id"]);
|
||||
|
||||
if ($records->count()) {
|
||||
$this->isValid = false;
|
||||
$this->logMessage($records->count() . " {$table} records with incorrect {$entityType} company id");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// foreach(User::cursor() as $user) {
|
||||
|
||||
// $records = Company::where('account_id',)
|
||||
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
public function pluralizeEntityType($type)
|
||||
{
|
||||
|
||||
if ($type === 'company') {
|
||||
return 'companies';
|
||||
}
|
||||
|
||||
return $type . 's';
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -232,12 +232,12 @@ class CompanySettings extends BaseSettings
|
||||
public $portal_custom_js = '';
|
||||
|
||||
public $client_can_register = false;
|
||||
public $client_signup_terms = '';
|
||||
public $client_signup_privacy_policy = '';
|
||||
public $client_portal_terms = '';
|
||||
public $client_portal_privacy_policy = '';
|
||||
|
||||
public static $casts = [
|
||||
'client_signup_terms' => 'string',
|
||||
'client_signup_privacy_policy' => 'string',
|
||||
'client_portal_terms' => 'string',
|
||||
'client_portal_privacy_policy' => 'string',
|
||||
'client_can_register' => 'bool',
|
||||
'portal_design_id' => 'string',
|
||||
'late_fee_endless_percent' => 'float',
|
||||
|
@ -37,7 +37,7 @@ class ContactRegister
|
||||
|
||||
$company = Company::where('company_key', $request->company_key)->firstOrFail();
|
||||
|
||||
abort_unless($company->getSetting('client_can_register'), 404);
|
||||
abort_unless($company->client_can_register, 404);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ class StoreUserRequest extends Request
|
||||
*/
|
||||
|
||||
public function authorize() : bool
|
||||
{
|
||||
{
|
||||
return auth()->user()->isAdmin();
|
||||
}
|
||||
|
||||
@ -43,8 +43,9 @@ class StoreUserRequest extends Request
|
||||
$rules['email'] = new ValidUserForCompany();
|
||||
}
|
||||
|
||||
if(auth()->user()->company()->account->isFreeHostedClient())
|
||||
if(auth()->user()->company()->account->isFreeHostedClient()){
|
||||
$rules['hosted_users'] = new CanAddUserRule(auth()->user()->company()->account);
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ class Credit extends BaseModel
|
||||
'client_id',
|
||||
'footer',
|
||||
'design_id',
|
||||
|
||||
'exchange_rate',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
|
@ -100,6 +100,7 @@ class Invoice extends BaseModel
|
||||
'custom_surcharge_tax4',
|
||||
'design_id',
|
||||
'assigned_user_id',
|
||||
'exchange_rate'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
|
@ -261,6 +261,12 @@ class Payment extends BaseModel
|
||||
return $this->status_id == self::STATUS_REFUNDED;
|
||||
}
|
||||
|
||||
public function setStatus($status)
|
||||
{
|
||||
$this->status_id = $status;
|
||||
$this->save();
|
||||
}
|
||||
|
||||
public function markVoided()
|
||||
{
|
||||
if ($this->isVoided() || $this->isPartiallyRefunded() || $this->isRefunded()) {
|
||||
|
@ -70,6 +70,7 @@ class Quote extends BaseModel
|
||||
'client_id',
|
||||
'footer',
|
||||
'design_id',
|
||||
'exchange_rate'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
|
@ -196,9 +196,21 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
$this->id = auth()->user()->id;
|
||||
}
|
||||
|
||||
return $this->hasOneThrough(CompanyUser::class, CompanyToken::class, 'user_id', 'company_id', 'id', 'company_id')
|
||||
->where('company_user.user_id', $this->id)
|
||||
->withTrashed();
|
||||
return $this->hasOneThrough(CompanyUser::class, CompanyToken::class, 'user_id', 'company_id', 'id', 'company_id')
|
||||
->where('company_user.user_id', $this->id)
|
||||
->withTrashed();
|
||||
|
||||
// if(request()->header('X-API-TOKEN')){
|
||||
// return $this->hasOneThrough(CompanyUser::class, CompanyToken::class, 'user_id', 'company_id', 'id', 'company_id')
|
||||
// ->where('company_tokens.token', request()->header('X-API-TOKEN'))
|
||||
// ->withTrashed();
|
||||
// }
|
||||
// else {
|
||||
|
||||
// return $this->hasOneThrough(CompanyUser::class, CompanyToken::class, 'user_id', 'company_id', 'id', 'company_id')
|
||||
// ->where('company_user.user_id', $this->id)
|
||||
// ->withTrashed();
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -120,7 +120,7 @@ class AuthorizeCreditCard
|
||||
$payment->client_id = $this->authorize->client->id;
|
||||
$payment->company_gateway_id = $this->authorize->company_gateway->id;
|
||||
$payment->status_id = Payment::STATUS_COMPLETED;
|
||||
$payment->gateway_type_id = $this->authorize->payment_method;
|
||||
$payment->gateway_type_id = GatewayType::CREDIT_CARD;
|
||||
$payment->type_id = PaymentType::CREDIT_CARD_OTHER;
|
||||
$payment->currency_id = $this->authorize->client->getSetting('currency_id');
|
||||
$payment->date = Carbon::now();
|
||||
|
@ -193,4 +193,25 @@ class PaymentRepository extends BaseRepository
|
||||
|
||||
return $payment;
|
||||
}
|
||||
|
||||
public function delete($payment)
|
||||
{
|
||||
//cannot double delete a payment
|
||||
if($payment->is_deleted)
|
||||
return;
|
||||
|
||||
$payment->service()->deletePayment();
|
||||
|
||||
return parent::delete($payment);
|
||||
|
||||
}
|
||||
|
||||
public function restore($payment)
|
||||
{
|
||||
//we cannot restore a deleted payment.
|
||||
if($payment->is_deleted)
|
||||
return;
|
||||
|
||||
return parent::restore($payment);
|
||||
}
|
||||
}
|
||||
|
118
app/Services/Payment/DeletePayment.php
Normal file
118
app/Services/Payment/DeletePayment.php
Normal file
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Payment;
|
||||
|
||||
use App\Exceptions\PaymentRefundFailed;
|
||||
use App\Factory\CreditFactory;
|
||||
use App\Factory\InvoiceItemFactory;
|
||||
use App\Models\Activity;
|
||||
use App\Models\CompanyGateway;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Repositories\ActivityRepository;
|
||||
|
||||
class DeletePayment
|
||||
{
|
||||
public $payment;
|
||||
|
||||
private $activity_repository;
|
||||
|
||||
public function __construct($payment)
|
||||
{
|
||||
$this->payment = $payment;
|
||||
|
||||
$this->activity_repository = new ActivityRepository();
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
|
||||
return $this->setStatus(Payment::STATUS_VOIDED) //sets status of payment
|
||||
->updateCreditables() //return the credits first
|
||||
->adjustInvoices()
|
||||
->updateClient()
|
||||
->save();
|
||||
}
|
||||
|
||||
|
||||
|
||||
//reverse paymentables->invoices
|
||||
|
||||
//reverse paymentables->credits
|
||||
|
||||
//set refunded to amount
|
||||
|
||||
//set applied amount to 0
|
||||
|
||||
private function updateClient()
|
||||
{
|
||||
$this->payment->client->service()->updatePaidToDate(-1*$this->payment->amount)->save();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function adjustInvoices()
|
||||
{
|
||||
if ($this->payment->invoices()->exists())
|
||||
{
|
||||
|
||||
$this->payment->invoices()->each(function ($paymentable_invoice){
|
||||
|
||||
$paymentable_invoice->service()->updateBalance($paymentable_invoice->pivot->amount)->save();
|
||||
$paymentable_invoice->ledger()->updateInvoiceBalance($paymentable_invoice->pivot->amount)->save();
|
||||
|
||||
if(floatval($paymentable_invoice->balance) == 0)
|
||||
$paymentable_invoice->service()->setStatus(Invoice::STATUS_SENT)->save();
|
||||
else
|
||||
$paymentable_invoice->service()->setStatus(Invoice::STATUS_PARTIAL)->save();
|
||||
|
||||
//fire event for this credit
|
||||
//
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function updateCreditables()
|
||||
{
|
||||
if ($this->payment->credits()->exists())
|
||||
{
|
||||
|
||||
$this->payment->credits()->each(function ($paymentable_credit){
|
||||
|
||||
$paymentable_credit->balance += $paymentable_credit->pivot->amount;
|
||||
$paymentable_credit->setStatus(Credit::STATUS_SENT);
|
||||
//fire event for this credit
|
||||
//
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function setStatus($status)
|
||||
{
|
||||
$this->payment->status_id = Payment::STATUS_VOIDED;
|
||||
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* Saves the payment
|
||||
*
|
||||
* @return Payment $payment
|
||||
*/
|
||||
private function save()
|
||||
{
|
||||
$this->payment->save();
|
||||
|
||||
return $this->payment;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -14,6 +14,7 @@ namespace App\Services\Payment;
|
||||
use App\Factory\PaymentFactory;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Services\Payment\DeletePayment;
|
||||
use App\Services\Payment\RefundPayment;
|
||||
use App\Services\Payment\UpdateInvoicePayment;
|
||||
|
||||
@ -77,6 +78,11 @@ class PaymentService
|
||||
return ((new RefundPayment($this->payment, $data)))->run();
|
||||
}
|
||||
|
||||
public function deletePayment() :?Payment
|
||||
{
|
||||
return (new DeletePayment($this->payment))->run();
|
||||
}
|
||||
|
||||
public function updateInvoicePayment() :?Payment
|
||||
{
|
||||
return ((new UpdateInvoicePayment($this->payment)))->run();
|
||||
|
@ -248,12 +248,10 @@ class RefundPayment
|
||||
// $ledger_string = "Refund for Invoice {$invoice->number} for amount " . $refunded_invoice['amount']; //todo
|
||||
|
||||
// $this->credit_note->ledger()->updateCreditBalance($adjustment_amount, $ledger_string);
|
||||
|
||||
|
||||
|
||||
|
||||
$client = $this->payment->client->fresh();
|
||||
$client->paid_to_date -= $this->total_refund;
|
||||
$client->save();
|
||||
$client->service()->updatePaidToDate(-1*$this->total_refund)->save();
|
||||
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
@ -149,6 +149,7 @@ class CreateUsersTable extends Migration
|
||||
$table->boolean('fill_products')->default(true);
|
||||
$table->boolean('update_products')->default(true);
|
||||
$table->boolean('show_product_details')->default(true);
|
||||
$table->boolean('client_can_register')->default(false);
|
||||
|
||||
$table->boolean('custom_surcharge_taxes1')->default(false);
|
||||
$table->boolean('custom_surcharge_taxes2')->default(false);
|
||||
@ -489,6 +490,7 @@ class CreateUsersTable extends Migration
|
||||
$t->boolean('custom_surcharge_tax3')->default(false);
|
||||
$t->boolean('custom_surcharge_tax4')->default(false);
|
||||
|
||||
$t->decimal('exchange_rate', 16, 4);
|
||||
$t->decimal('amount', 16, 4);
|
||||
$t->decimal('balance', 16, 4);
|
||||
$t->decimal('partial', 16, 4)->nullable();
|
||||
@ -566,6 +568,7 @@ class CreateUsersTable extends Migration
|
||||
$t->boolean('custom_surcharge_tax3')->default(false);
|
||||
$t->boolean('custom_surcharge_tax4')->default(false);
|
||||
|
||||
$t->decimal('exchange_rate', 16, 4);
|
||||
$t->decimal('amount', 16, 4);
|
||||
$t->decimal('balance', 16, 4);
|
||||
$t->decimal('partial', 16, 4)->nullable();
|
||||
@ -809,6 +812,7 @@ class CreateUsersTable extends Migration
|
||||
$t->boolean('custom_surcharge_tax3')->default(false);
|
||||
$t->boolean('custom_surcharge_tax4')->default(false);
|
||||
|
||||
$t->decimal('exchange_rate', 16, 4);
|
||||
$t->decimal('amount', 16, 4);
|
||||
$t->decimal('balance', 16, 4);
|
||||
$t->decimal('partial', 16, 4)->nullable();
|
||||
|
@ -24,15 +24,15 @@
|
||||
|
||||
<div class="flex justify-between items-center mt-8">
|
||||
<span class="inline-flex items-center" x-data="{ terms_of_service: false, privacy_policy: false }">
|
||||
@if(!empty($company->settings->client_signup_terms) || !empty($company->settings->client_signup_privacy_policy))
|
||||
@if(!empty($company->settings->client_portal_terms) || !empty($company->settings->client_portal_privacy_policy))
|
||||
<input type="checkbox" name="terms" class="form-checkbox mr-2 cursor-pointer" checked>
|
||||
<span class="text-sm text-gray-800">
|
||||
|
||||
{{ ctrans('texts.i_agree') }}
|
||||
@endif
|
||||
|
||||
@includeWhen(!empty($company->settings->client_signup_terms), 'portal.ninja2020.auth.includes.register.popup', ['property' => 'terms_of_service', 'title' => ctrans('texts.terms_of_service'), 'content' => $company->settings->client_signup_terms])
|
||||
@includeWhen(!empty($company->settings->client_signup_privacy_policy), 'portal.ninja2020.auth.includes.register.popup', ['property' => 'privacy_policy', 'title' => ctrans('texts.privacy_policy'), 'content' => $company->settings->client_signup_privacy_policy])
|
||||
@includeWhen(!empty($company->settings->client_portal_terms), 'portal.ninja2020.auth.includes.register.popup', ['property' => 'terms_of_service', 'title' => ctrans('texts.terms_of_service'), 'content' => $company->settings->client_portal_terms])
|
||||
@includeWhen(!empty($company->settings->client_portal_privacy_policy), 'portal.ninja2020.auth.includes.register.popup', ['property' => 'privacy_policy', 'title' => ctrans('texts.privacy_policy'), 'content' => $company->settings->client_portal_privacy_policy])
|
||||
</span>
|
||||
</span>
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
<footer class="bg-white px-4 py-5 shadow px-4 sm:px-6 md:px-8 flex justify-center border border-gray-200 justify-between items-center">
|
||||
<span class="text-sm text-gray-700">{{ ctrans('texts.footer_label', ['year' => date('Y')]) }}</span>
|
||||
@if(!auth()->user()->user->account->isPaid())
|
||||
@if(auth()->user()->user && !auth()->user()->user->account->isPaid())
|
||||
<a href="#">
|
||||
<img class="h-8" src="{{ asset('images/invoiceninja-black-logo-2.png') }}" alt="Invoice Ninja Logo">
|
||||
</a>
|
||||
|
@ -4,6 +4,7 @@ namespace Feature;
|
||||
|
||||
use App\Factory\CompanyUserFactory;
|
||||
use App\Factory\UserFactory;
|
||||
use App\Http\Middleware\PasswordProtection;
|
||||
use App\Models\Account;
|
||||
use App\Models\Activity;
|
||||
use App\Models\Company;
|
||||
@ -35,17 +36,18 @@ class UserTest extends TestCase
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->withoutMiddleware(
|
||||
ThrottleRequests::class
|
||||
);
|
||||
|
||||
Session::start();
|
||||
|
||||
$this->faker = \Faker\Factory::create();
|
||||
|
||||
$this->makeTestData();
|
||||
|
||||
Model::reguard();
|
||||
|
||||
$this->makeTestData();
|
||||
$this->withoutMiddleware(
|
||||
ThrottleRequests::class,
|
||||
PasswordProtection::class
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
@ -62,6 +64,8 @@ class UserTest extends TestCase
|
||||
|
||||
public function testUserStore()
|
||||
{
|
||||
$this->withoutMiddleware(PasswordProtection::class);
|
||||
|
||||
$data = [
|
||||
'first_name' => 'hey',
|
||||
'last_name' => 'you',
|
||||
@ -88,6 +92,8 @@ class UserTest extends TestCase
|
||||
|
||||
public function testUserAttachAndDetach()
|
||||
{
|
||||
$this->withoutMiddleware(PasswordProtection::class);
|
||||
|
||||
$user = UserFactory::create($this->account->id);
|
||||
$user->first_name = 'Test';
|
||||
$user->last_name = 'Palloni';
|
||||
@ -96,7 +102,7 @@ class UserTest extends TestCase
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
'X-API-PASSWORD' => 'ALongAndBriliantPassword',
|
||||
'X-API-PASSWORD' => 'ALongAndBriliantPassword',
|
||||
])->post('/api/v1/users/'.$this->encodePrimaryKey($user->id).'/attach_to_company?include=company_user');
|
||||
|
||||
$response->assertStatus(200);
|
||||
@ -124,6 +130,7 @@ class UserTest extends TestCase
|
||||
|
||||
public function testAttachUserToMultipleCompanies()
|
||||
{
|
||||
$this->withoutMiddleware(PasswordProtection::class);
|
||||
|
||||
/* Create New Company */
|
||||
$company2 = factory(\App\Models\Company::class)->create([
|
||||
|
@ -120,11 +120,12 @@ trait MockAccountData
|
||||
if (!$this->user) {
|
||||
$this->user = factory(\App\Models\User::class)->create([
|
||||
'account_id' => $this->account->id,
|
||||
'password' => Hash::make('ALongAndBriliantPassword'),
|
||||
'confirmation_code' => $this->createDbHash(config('database.default'))
|
||||
]);
|
||||
}
|
||||
|
||||
$this->user->password = Hash::make('ALongAndBriliantPassword');
|
||||
|
||||
$cu = CompanyUserFactory::create($this->user->id, $this->company->id, $this->account->id);
|
||||
$cu->is_owner = true;
|
||||
$cu->is_admin = true;
|
||||
|
21
tests/Unit/SentryTest.php
Normal file
21
tests/Unit/SentryTest.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Foundation\Testing\WithFaker;
|
||||
use Tests\TestCase;
|
||||
|
||||
class SentryTest extends TestCase
|
||||
{
|
||||
|
||||
public function testSentryFiresAppropriately()
|
||||
{
|
||||
|
||||
$e = new \Exception("Test Fire");
|
||||
app('sentry')->captureException($e);
|
||||
|
||||
$this->assertTrue(true);
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user