mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-05-24 02:14:21 -04:00
Merge branch 'post_l10_release' into v5-develop
This commit is contained in:
commit
4c7550f01f
@ -11,28 +11,29 @@
|
||||
|
||||
namespace App\Console;
|
||||
|
||||
use App\Jobs\Cron\AutoBillCron;
|
||||
use App\Jobs\Cron\RecurringExpensesCron;
|
||||
use App\Jobs\Cron\RecurringInvoicesCron;
|
||||
use App\Jobs\Cron\SubscriptionCron;
|
||||
use App\Jobs\Cron\UpdateCalculatedFields;
|
||||
use App\Jobs\Invoice\InvoiceCheckLateWebhook;
|
||||
use App\Jobs\Ninja\AdjustEmailQuota;
|
||||
use App\Jobs\Ninja\BankTransactionSync;
|
||||
use App\Jobs\Ninja\CompanySizeCheck;
|
||||
use App\Utils\Ninja;
|
||||
use App\Models\Account;
|
||||
use App\Jobs\Ninja\QueueSize;
|
||||
use App\Jobs\Ninja\SystemMaintenance;
|
||||
use App\Jobs\Ninja\TaskScheduler;
|
||||
use App\Jobs\Quote\QuoteCheckExpired;
|
||||
use App\Jobs\Subscription\CleanStaleInvoiceOrder;
|
||||
use App\Jobs\Util\DiskCleanup;
|
||||
use App\Jobs\Util\ReminderJob;
|
||||
use App\Jobs\Util\SchedulerCheck;
|
||||
use App\Jobs\Util\UpdateExchangeRates;
|
||||
use App\Jobs\Cron\AutoBillCron;
|
||||
use App\Jobs\Util\VersionCheck;
|
||||
use App\Models\Account;
|
||||
use App\Utils\Ninja;
|
||||
use App\Jobs\Ninja\TaskScheduler;
|
||||
use App\Jobs\Util\SchedulerCheck;
|
||||
use App\Jobs\Ninja\CheckACHStatus;
|
||||
use App\Jobs\Cron\SubscriptionCron;
|
||||
use App\Jobs\Ninja\AdjustEmailQuota;
|
||||
use App\Jobs\Ninja\CompanySizeCheck;
|
||||
use App\Jobs\Ninja\SystemMaintenance;
|
||||
use App\Jobs\Quote\QuoteCheckExpired;
|
||||
use App\Jobs\Util\UpdateExchangeRates;
|
||||
use App\Jobs\Ninja\BankTransactionSync;
|
||||
use App\Jobs\Cron\RecurringExpensesCron;
|
||||
use App\Jobs\Cron\RecurringInvoicesCron;
|
||||
use App\Jobs\Cron\UpdateCalculatedFields;
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use App\Jobs\Invoice\InvoiceCheckLateWebhook;
|
||||
use App\Jobs\Subscription\CleanStaleInvoiceOrder;
|
||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||
|
||||
class Kernel extends ConsoleKernel
|
||||
@ -109,6 +110,9 @@ class Kernel extends ConsoleKernel
|
||||
/* Pulls in bank transactions from third party services */
|
||||
$schedule->job(new BankTransactionSync)->everyFourHours()->withoutOverlapping()->name('bank-trans-sync-job')->onOneServer();
|
||||
|
||||
/* Checks ACH verification status and updates state to authorize when verified */
|
||||
$schedule->job(new CheckACHStatus)->everySixHours()->withoutOverlapping()->name('ach-status-job')->onOneServer();
|
||||
|
||||
$schedule->command('ninja:check-data --database=db-ninja-01')->dailyAt('02:10')->withoutOverlapping()->name('check-data-db-1-job')->onOneServer();
|
||||
|
||||
$schedule->command('ninja:check-data --database=db-ninja-02')->dailyAt('02:20')->withoutOverlapping()->name('check-data-db-2-job')->onOneServer();
|
||||
|
@ -481,8 +481,11 @@ class CompanySettings extends BaseSettings
|
||||
|
||||
public $enable_e_invoice = false;
|
||||
|
||||
public $classification = ''; // individual, company, partnership, trust, charity, government, other
|
||||
|
||||
public static $casts = [
|
||||
'enable_e_invoice' => 'bool',
|
||||
'classification' => 'string',
|
||||
'default_expense_payment_type_id' => 'string',
|
||||
'e_invoice_type' => 'string',
|
||||
'mailgun_endpoint' => 'string',
|
||||
|
@ -32,7 +32,8 @@ class ClientFactory
|
||||
$client->is_deleted = 0;
|
||||
$client->client_hash = Str::random(40);
|
||||
$client->settings = ClientSettings::defaults();
|
||||
|
||||
$client->classification = '';
|
||||
|
||||
return $client;
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ class VendorFactory
|
||||
$vendor->country_id = 4;
|
||||
$vendor->is_deleted = 0;
|
||||
$vendor->vendor_hash = Str::random(40);
|
||||
$vendor->classification = '';
|
||||
|
||||
return $vendor;
|
||||
}
|
||||
|
@ -697,4 +697,19 @@ class CompanyController extends BaseController
|
||||
|
||||
return $this->itemResponse($company->fresh());
|
||||
}
|
||||
|
||||
public function logo()
|
||||
{
|
||||
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
$company = $user->company();
|
||||
$logo = strlen($company->settings->company_logo) > 5 ? $company->settings->company_logo : 'https://pdf.invoicing.co/favicon-v2.png';
|
||||
$headers = ['Content-Disposition' => 'inline'];
|
||||
|
||||
return response()->streamDownload(function () use ($logo){
|
||||
echo @file_get_contents($logo);
|
||||
}, 'logo.png', $headers);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -93,7 +93,8 @@ class StoreClientRequest extends Request
|
||||
|
||||
$rules['number'] = ['bail', 'nullable', Rule::unique('clients')->where('company_id', $user->company()->id)];
|
||||
$rules['id_number'] = ['bail', 'nullable', Rule::unique('clients')->where('company_id', $user->company()->id)];
|
||||
|
||||
$rules['classification'] = 'bail|sometimes|nullable|in:individual,company,partnership,trust,charity,government,other';
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
|
@ -60,6 +60,7 @@ class UpdateClientRequest extends Request
|
||||
$rules['size_id'] = 'integer|nullable';
|
||||
$rules['country_id'] = 'integer|nullable';
|
||||
$rules['shipping_country_id'] = 'integer|nullable';
|
||||
$rules['classification'] = 'bail|sometimes|nullable|in:individual,company,partnership,trust,charity,government,other';
|
||||
|
||||
if ($this->id_number) {
|
||||
$rules['id_number'] = Rule::unique('clients')->where('company_id', $user->company()->id)->ignore($this->client->id);
|
||||
|
@ -60,6 +60,7 @@ class StoreVendorRequest extends Request
|
||||
}
|
||||
|
||||
$rules['language_id'] = 'bail|nullable|sometimes|exists:languages,id';
|
||||
$rules['classification'] = 'bail|sometimes|nullable|in:individual,company,partnership,trust,charity,government,other';
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
@ -61,6 +61,7 @@ class UpdateVendorRequest extends Request
|
||||
}
|
||||
|
||||
$rules['language_id'] = 'bail|nullable|sometimes|exists:languages,id';
|
||||
$rules['classification'] = 'bail|sometimes|nullable|in:individual,company,partnership,trust,charity,government,other';
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
79
app/Jobs/Ninja/CheckACHStatus.php
Normal file
79
app/Jobs/Ninja/CheckACHStatus.php
Normal file
@ -0,0 +1,79 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Jobs\Ninja;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use App\Models\ClientGatewayToken;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
|
||||
class CheckACHStatus implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
//multiDB environment, need to
|
||||
foreach (MultiDB::$dbs as $db) {
|
||||
MultiDB::setDB($db);
|
||||
|
||||
nlog("Checking ACH status");
|
||||
|
||||
ClientGatewayToken::query()
|
||||
->where('created_at', '>', now()->subMonths(2))
|
||||
->where('gateway_type_id', 2)
|
||||
->whereHas('gateway', function ($q) {
|
||||
$q->whereIn('gateway_key', ['d14dd26a37cecc30fdd65700bfb55b23','d14dd26a47cecc30fdd65700bfb67b34']);
|
||||
})
|
||||
->whereJsonContains('meta', ['state' => 'unauthorized'])
|
||||
->cursor()
|
||||
->each(function ($token) {
|
||||
|
||||
try {
|
||||
$stripe = $token->gateway->driver($token->client)->init();
|
||||
$pm = $stripe->getStripePaymentMethod($token->token);
|
||||
|
||||
if($pm) {
|
||||
|
||||
$meta = $token->meta;
|
||||
$meta->state = 'authorized';
|
||||
$token->meta = $meta;
|
||||
$token->save();
|
||||
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -11,22 +11,24 @@
|
||||
|
||||
namespace App\Jobs\RecurringInvoice;
|
||||
|
||||
use App\DataMapper\Analytics\SendRecurringFailure;
|
||||
use App\Factory\InvoiceInvitationFactory;
|
||||
use App\Factory\RecurringInvoiceToInvoiceFactory;
|
||||
use App\Jobs\Cron\AutoBill;
|
||||
use App\Jobs\Entity\EmailEntity;
|
||||
use Carbon\Carbon;
|
||||
use App\Utils\Ninja;
|
||||
use App\Models\Invoice;
|
||||
use App\Jobs\Cron\AutoBill;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Jobs\Entity\EmailEntity;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Utils\Traits\GeneratesCounter;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Turbo124\Beacon\Facades\LightLogs;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use App\Events\Invoice\InvoiceWasCreated;
|
||||
use App\Factory\InvoiceInvitationFactory;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use App\Factory\RecurringInvoiceToInvoiceFactory;
|
||||
use App\DataMapper\Analytics\SendRecurringFailure;
|
||||
|
||||
class SendRecurring implements ShouldQueue
|
||||
{
|
||||
@ -105,6 +107,7 @@ class SendRecurring implements ShouldQueue
|
||||
$this->recurring_invoice->save();
|
||||
|
||||
event('eloquent.created: App\Models\Invoice', $invoice);
|
||||
event(new InvoiceWasCreated($invoice, $invoice->company, Ninja::eventVars()));
|
||||
|
||||
//auto bill, BUT NOT DRAFTS!!
|
||||
if ($invoice->auto_bill_enabled && $invoice->client->getSetting('auto_bill_date') == 'on_send_date' && $invoice->client->getSetting('auto_email_invoice')) {
|
||||
|
@ -52,7 +52,8 @@ class CreateInvoiceActivity implements ShouldQueue
|
||||
$fields->client_id = $event->invoice->client_id;
|
||||
$fields->company_id = $event->invoice->company_id;
|
||||
$fields->activity_type_id = Activity::CREATE_INVOICE;
|
||||
|
||||
$fields->recurring_invoice_id = $event->invoice->recurring_id;
|
||||
|
||||
$this->activity_repo->save($fields, $event->invoice, $event->event_vars);
|
||||
}
|
||||
}
|
||||
|
@ -416,6 +416,9 @@ class Activity extends StaticModel
|
||||
if($this->vendor)
|
||||
$replacements['vendor'] = ['label' => $this?->vendor?->present()->name() ?? '', 'hashed_id' => $this->vendor->hashed_id ?? ''];
|
||||
|
||||
if($this->activity_type_id == 4 && $this->recurring_invoice)
|
||||
$replacements['recurring_invoice'] = ['label' => $this?->recurring_invoice?->number ?? '', 'hashed_id' => $this->recurring_invoice->hashed_id ?? ''];
|
||||
|
||||
$replacements['activity_type_id'] = $this->activity_type_id;
|
||||
$replacements['id'] = $this->id;
|
||||
$replacements['hashed_id'] = $this->hashed_id;
|
||||
|
@ -169,6 +169,7 @@ class Client extends BaseModel implements HasLocalePreference
|
||||
'routing_id',
|
||||
'is_tax_exempt',
|
||||
'has_valid_vat_number',
|
||||
'classification',
|
||||
];
|
||||
|
||||
protected $with = [
|
||||
|
@ -361,6 +361,24 @@ class Payment extends BaseModel
|
||||
return new PaymentService($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* $data = [
|
||||
'id' => $payment->id,
|
||||
'amount' => 10,
|
||||
'invoices' => [
|
||||
[
|
||||
'invoice_id' => $invoice->id,
|
||||
'amount' => 10,
|
||||
],
|
||||
],
|
||||
'date' => '2020/12/12',
|
||||
'gateway_refund' => false,
|
||||
'email_receipt' => false,
|
||||
];
|
||||
*
|
||||
* @param array $data
|
||||
* @return self
|
||||
*/
|
||||
public function refund(array $data) :self
|
||||
{
|
||||
return $this->service()->refundPayment($data);
|
||||
|
@ -113,6 +113,7 @@ class Vendor extends BaseModel
|
||||
'custom_value4',
|
||||
'number',
|
||||
'language_id',
|
||||
'classification',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
|
147
app/PaymentDrivers/Stripe/Jobs/ChargeRefunded.php
Normal file
147
app/PaymentDrivers/Stripe/Jobs/ChargeRefunded.php
Normal file
@ -0,0 +1,147 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\PaymentDrivers\Stripe\Jobs;
|
||||
|
||||
use App\Models\Company;
|
||||
use App\Models\Payment;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\PaymentHash;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use App\Models\CompanyGateway;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use App\PaymentDrivers\Stripe\Utilities;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
|
||||
class ChargeRefunded implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, Utilities;
|
||||
|
||||
public $tries = 1; //number of retries
|
||||
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
public $stripe_request;
|
||||
|
||||
public $company_key;
|
||||
|
||||
private $company_gateway_id;
|
||||
|
||||
public $payment_completed = false;
|
||||
|
||||
public function __construct($stripe_request, $company_key, $company_gateway_id)
|
||||
{
|
||||
$this->stripe_request = $stripe_request;
|
||||
$this->company_key = $company_key;
|
||||
$this->company_gateway_id = $company_gateway_id;
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
MultiDB::findAndSetDbByCompanyKey($this->company_key);
|
||||
nlog($this->stripe_request);
|
||||
|
||||
$company = Company::query()->where('company_key', $this->company_key)->first();
|
||||
|
||||
$source = $this->stripe_request['object'];
|
||||
$charge_id = $source['id'];
|
||||
$amount_refunded = $source['amount_refunded'] ?? 0;
|
||||
|
||||
$payment_hash_key = $source['metadata']['payment_hash'] ?? null;
|
||||
|
||||
$company_gateway = CompanyGateway::query()->find($this->company_gateway_id);
|
||||
$payment_hash = PaymentHash::query()->where('hash', $payment_hash_key)->first();
|
||||
|
||||
$stripe_driver = $company_gateway->driver()->init();
|
||||
|
||||
$stripe_driver->payment_hash = $payment_hash;
|
||||
|
||||
/** @var \App\Models\Payment $payment **/
|
||||
$payment = Payment::query()
|
||||
->withTrashed()
|
||||
->where('company_id', $company->id)
|
||||
->where('transaction_reference', $charge_id)
|
||||
->first();
|
||||
|
||||
//don't touch if already refunded
|
||||
if(!$payment || in_array($payment->status_id, [Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$stripe_driver->client = $payment->client;
|
||||
|
||||
$amount_refunded = $stripe_driver->convertFromStripeAmount($amount_refunded, $payment->client->currency()->precision, $payment->client->currency());
|
||||
|
||||
if ($payment->status_id == Payment::STATUS_PENDING) {
|
||||
$payment->service()->deletePayment();
|
||||
$payment->status_id = Payment::STATUS_FAILED;
|
||||
$payment->save();
|
||||
return;
|
||||
}
|
||||
|
||||
if($payment->status_id == Payment::STATUS_COMPLETED) {
|
||||
|
||||
$invoice_collection = $payment->paymentables
|
||||
->where('paymentable_type','invoices')
|
||||
->map(function ($pivot){
|
||||
return [
|
||||
'invoice_id' => $pivot->paymentable_id,
|
||||
'amount' => $pivot->amount - $pivot->refunded
|
||||
];
|
||||
});
|
||||
|
||||
if($invoice_collection->count() == 1 && $invoice_collection->first()['amount'] >= $amount_refunded) {
|
||||
//If there is only one invoice- and we are refunding _less_ than the amount of the invoice, we can just refund the payment
|
||||
|
||||
$invoice_collection = $payment->paymentables
|
||||
->where('paymentable_type', 'invoices')
|
||||
->map(function ($pivot) use ($amount_refunded){
|
||||
return [
|
||||
'invoice_id' => $pivot->paymentable_id,
|
||||
'amount' => $amount_refunded
|
||||
];
|
||||
});
|
||||
|
||||
}
|
||||
elseif($invoice_collection->sum('amount') != $amount_refunded) {
|
||||
//too many edges cases at this point, return early
|
||||
return;
|
||||
}
|
||||
|
||||
$invoices = $invoice_collection->toArray();
|
||||
|
||||
$data = [
|
||||
'id' => $payment->id,
|
||||
'amount' => $amount_refunded,
|
||||
'invoices' => $invoices,
|
||||
'date' => now()->format('Y-m-d'),
|
||||
'gateway_refund' => false,
|
||||
'email_receipt' => false,
|
||||
];
|
||||
|
||||
nlog($data);
|
||||
|
||||
$payment->refund($data);
|
||||
|
||||
$payment->private_notes .= 'Refunded via Stripe';
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function middleware()
|
||||
{
|
||||
return [new WithoutOverlapping($this->company_gateway_id)];
|
||||
}
|
||||
}
|
@ -39,6 +39,7 @@ use App\PaymentDrivers\Stripe\FPX;
|
||||
use App\PaymentDrivers\Stripe\GIROPAY;
|
||||
use App\PaymentDrivers\Stripe\iDeal;
|
||||
use App\PaymentDrivers\Stripe\ImportCustomers;
|
||||
use App\PaymentDrivers\Stripe\Jobs\ChargeRefunded;
|
||||
use App\PaymentDrivers\Stripe\Jobs\PaymentIntentFailureWebhook;
|
||||
use App\PaymentDrivers\Stripe\Jobs\PaymentIntentPartiallyFundedWebhook;
|
||||
use App\PaymentDrivers\Stripe\Jobs\PaymentIntentProcessingWebhook;
|
||||
@ -790,6 +791,12 @@ class StripePaymentDriver extends BaseDriver
|
||||
} elseif ($request->data['object']['status'] == "pending") {
|
||||
return response()->json([], 200);
|
||||
}
|
||||
} elseif ($request->type === "charge.refunded") {
|
||||
|
||||
ChargeRefunded::dispatch($request->data, $request->company_key, $this->company_gateway->id)->delay(now()->addSeconds(rand(5, 10)));
|
||||
|
||||
return response()->json([], 200);
|
||||
|
||||
}
|
||||
|
||||
return response()->json([], 200);
|
||||
|
@ -151,6 +151,7 @@ class ClientTransformer extends EntityTransformer
|
||||
'is_tax_exempt' => (bool) $client->is_tax_exempt,
|
||||
'routing_id' => (string) $client->routing_id,
|
||||
'tax_info' => $client->tax_data ?: new \stdClass,
|
||||
'classification' => $client->classification ?: '',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -104,6 +104,7 @@ class VendorTransformer extends EntityTransformer
|
||||
'created_at' => (int) $vendor->created_at,
|
||||
'number' => (string) $vendor->number ?: '',
|
||||
'language_id' => (string) $vendor->language_id ?: '',
|
||||
'classification' => (string) $vendor->classification ?: '',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('clients', function (Blueprint $table) {
|
||||
$table->string('classification')->nullable();
|
||||
});
|
||||
|
||||
Schema::table('vendors', function (Blueprint $table) {
|
||||
$table->string('classification')->nullable();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
};
|
@ -5158,6 +5158,12 @@ $LANG = array(
|
||||
'click_or_drop_files_here' => 'Click or drop files here',
|
||||
'set_public' => 'Set public',
|
||||
'set_private' => 'Set private',
|
||||
'individual' => 'Individual',
|
||||
'business' => 'Business',
|
||||
'partnership' => 'partnership',
|
||||
'trust' => 'Trust',
|
||||
'charity' => 'Charity',
|
||||
'government' => 'Government',
|
||||
'in_stock_quantity' => 'Stock quantity',
|
||||
);
|
||||
|
||||
|
@ -177,6 +177,7 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale']
|
||||
Route::post('companies/purge_save_settings/{company}', [MigrationController::class, 'purgeCompanySaveSettings'])->middleware('password_protected');
|
||||
Route::resource('companies', CompanyController::class); // name = (companies. index / create / show / update / destroy / edit
|
||||
|
||||
Route::get('companies/{company}/logo', [CompanyController::class, 'logo']);
|
||||
Route::put('companies/{company}/upload', [CompanyController::class, 'upload']);
|
||||
Route::post('companies/{company}/default', [CompanyController::class, 'default']);
|
||||
Route::post('companies/updateOriginTaxData/{company}', [CompanyController::class, 'updateOriginTaxData'])->middleware('throttle:3,1');
|
||||
|
220
tests/Feature/ClassificationTest.php
Normal file
220
tests/Feature/ClassificationTest.php
Normal file
@ -0,0 +1,220 @@
|
||||
<?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\Feature;
|
||||
|
||||
use Tests\TestCase;
|
||||
use Tests\MockUnitData;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
class ClassificationTest extends TestCase
|
||||
{
|
||||
use MakesHash;
|
||||
use DatabaseTransactions;
|
||||
use MockUnitData;
|
||||
|
||||
protected function setUp() :void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->faker = \Faker\Factory::create();
|
||||
|
||||
$this->makeTestData();
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function testClientClassification()
|
||||
{
|
||||
$data = [
|
||||
'name' => 'Personal Company',
|
||||
'classification' => 'individual'
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->postJson('/api/v1/clients', $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$this->assertEquals('individual', $arr['data']['classification']);
|
||||
}
|
||||
|
||||
public function testValidationClassification()
|
||||
{
|
||||
$data = [
|
||||
'name' => 'Personal Company',
|
||||
'classification' => 'this_is_not_validated'
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->postJson('/api/v1/clients', $data);
|
||||
|
||||
$response->assertStatus(422);
|
||||
|
||||
}
|
||||
|
||||
public function testValidation2Classification()
|
||||
{
|
||||
$this->client->classification = 'company';
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->putJson('/api/v1/clients/'.$this->client->hashed_id, $this->client->toArray());
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$this->assertEquals('company', $arr['data']['classification']);
|
||||
}
|
||||
|
||||
public function testValidation3Classification()
|
||||
{
|
||||
$this->client->classification = 'this_is_not_validated';
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->putJson('/api/v1/clients/'.$this->client->hashed_id, $this->client->toArray());
|
||||
|
||||
$response->assertStatus(422);
|
||||
|
||||
}
|
||||
|
||||
public function testVendorClassification()
|
||||
{
|
||||
$data = [
|
||||
'name' => 'Personal Company',
|
||||
'classification' => 'individual'
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->postJson('/api/v1/vendors', $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$this->assertEquals('individual', $arr['data']['classification']);
|
||||
}
|
||||
|
||||
public function testVendorValidationClassification()
|
||||
{
|
||||
$data = [
|
||||
'name' => 'Personal Company',
|
||||
'classification' => 'this_is_not_validated'
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->postJson('/api/v1/vendors', $data);
|
||||
|
||||
$response->assertStatus(422);
|
||||
|
||||
}
|
||||
|
||||
public function testVendorValidation2Classification()
|
||||
{
|
||||
$this->vendor->classification = 'company';
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->putJson('/api/v1/vendors/'.$this->vendor->hashed_id, $this->vendor->toArray());
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$this->assertEquals('company', $arr['data']['classification']);
|
||||
}
|
||||
|
||||
public function testVendorValidation3Classification()
|
||||
{
|
||||
$this->vendor->classification = 'this_is_not_validated';
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->putJson('/api/v1/vendors/'.$this->vendor->hashed_id, $this->vendor->toArray());
|
||||
|
||||
$response->assertStatus(422);
|
||||
|
||||
}
|
||||
|
||||
public function testCompanyClassification()
|
||||
{
|
||||
$settings = $this->company->settings;
|
||||
$settings->classification = 'company';
|
||||
|
||||
$this->company->settings = $settings;
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->putJson('/api/v1/companies/'.$this->company->hashed_id, $this->company->toArray());
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$this->assertEquals('company', $arr['data']['settings']['classification']);
|
||||
}
|
||||
|
||||
public function testCompanyValidationClassification()
|
||||
{
|
||||
$settings = $this->company->settings;
|
||||
$settings->classification = 545454;
|
||||
|
||||
$this->company->settings = $settings;
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->putJson('/api/v1/companies/'.$this->company->hashed_id, $this->company->toArray());
|
||||
|
||||
$response->assertStatus(422);
|
||||
|
||||
}
|
||||
|
||||
public function testCompanyValidation2Classification()
|
||||
{
|
||||
$settings = $this->company->settings;
|
||||
$settings->classification = null;
|
||||
|
||||
$this->company->settings = $settings;
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->putJson('/api/v1/companies/'.$this->company->hashed_id, $this->company->toArray());
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$this->assertEquals('', $arr['data']['settings']['classification']);
|
||||
}
|
||||
}
|
@ -34,6 +34,8 @@ class CompanyTest extends TestCase
|
||||
use MockAccountData;
|
||||
use DatabaseTransactions;
|
||||
|
||||
public $faker;
|
||||
|
||||
protected function setUp() :void
|
||||
{
|
||||
parent::setUp();
|
||||
@ -47,6 +49,19 @@ class CompanyTest extends TestCase
|
||||
$this->makeTestData();
|
||||
}
|
||||
|
||||
|
||||
public function testCompanyLogoInline()
|
||||
{
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->postJson("/api/v1/companies/{$this->company->hashed_id}/logo");
|
||||
|
||||
$response->assertStatus(200);
|
||||
$response->streamedContent();
|
||||
|
||||
}
|
||||
|
||||
public function testUpdateCompanyPropertyInvoiceTaskHours()
|
||||
{
|
||||
$company_update = [
|
||||
@ -56,9 +71,9 @@ class CompanyTest extends TestCase
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->putJson('/api/v1/companies/'.$this->encodePrimaryKey($this->company->id), $company_update)
|
||||
->assertStatus(200);
|
||||
])->putJson('/api/v1/companies/'.$this->encodePrimaryKey($this->company->id), $company_update);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
|
@ -11,15 +11,16 @@
|
||||
|
||||
namespace Tests;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\Client;
|
||||
use App\Models\Vendor;
|
||||
use App\Models\Account;
|
||||
use App\Models\Company;
|
||||
use App\Models\CompanyToken;
|
||||
use App\Models\ClientContact;
|
||||
use App\DataMapper\CompanySettings;
|
||||
use App\DataMapper\DefaultSettings;
|
||||
use App\Factory\InvoiceItemFactory;
|
||||
use App\Models\Account;
|
||||
use App\Models\Client;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\Company;
|
||||
use App\Models\CompanyToken;
|
||||
use App\Models\User;
|
||||
|
||||
/**
|
||||
* Class MockUnitData.
|
||||
@ -34,6 +35,8 @@ trait MockUnitData
|
||||
|
||||
public $client;
|
||||
|
||||
public $vendor;
|
||||
|
||||
public $faker;
|
||||
|
||||
public $primary_contact;
|
||||
@ -92,6 +95,11 @@ trait MockUnitData
|
||||
'company_id' => $this->company->id,
|
||||
]);
|
||||
|
||||
$this->vendor = Vendor::factory()->create([
|
||||
'user_id' => $this->user->id,
|
||||
'company_id' => $this->company->id,
|
||||
]);
|
||||
|
||||
$this->primary_contact = ClientContact::factory()->create([
|
||||
'user_id' => $this->user->id,
|
||||
'client_id' => $this->client->id,
|
||||
|
Loading…
x
Reference in New Issue
Block a user