mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
commit
99a0edbc57
@ -1 +1 @@
|
|||||||
5.1.29
|
5.1.30
|
@ -101,6 +101,7 @@ class CreateSingleAccount extends Command
|
|||||||
$company = Company::factory()->create([
|
$company = Company::factory()->create([
|
||||||
'account_id' => $account->id,
|
'account_id' => $account->id,
|
||||||
'slack_webhook_url' => config('ninja.notification.slack'),
|
'slack_webhook_url' => config('ninja.notification.slack'),
|
||||||
|
'default_password_timeout' => 30*60000,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$account->default_company_id = $company->id;
|
$account->default_company_id = $company->id;
|
||||||
|
@ -35,12 +35,18 @@ class WebhookConfiguration
|
|||||||
*/
|
*/
|
||||||
public $post_purchase_body = '';
|
public $post_purchase_body = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $post_purchase_rest_method = 'POST';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
public static $casts = [
|
public static $casts = [
|
||||||
'return_url' => 'string',
|
'return_url' => 'string',
|
||||||
'post_purchase_url' => 'string',
|
'post_purchase_url' => 'string',
|
||||||
|
'post_purchase_rest_method' => 'string',
|
||||||
'post_purchase_headers' => 'array',
|
'post_purchase_headers' => 'array',
|
||||||
'post_purchase_body' => 'object',
|
'post_purchase_body' => 'object',
|
||||||
];
|
];
|
||||||
|
@ -35,7 +35,8 @@ class CompanyFactory
|
|||||||
$company->custom_fields = (object) [];
|
$company->custom_fields = (object) [];
|
||||||
$company->subdomain = '';
|
$company->subdomain = '';
|
||||||
$company->enabled_modules = config('ninja.enabled_modules'); //32767;//8191; //4095
|
$company->enabled_modules = config('ninja.enabled_modules'); //32767;//8191; //4095
|
||||||
$company->default_password_timeout = 30 * 60000;
|
$company->default_password_timeout = 1800000;
|
||||||
|
|
||||||
|
|
||||||
return $company;
|
return $company;
|
||||||
}
|
}
|
||||||
|
@ -350,6 +350,7 @@ class LoginController extends BaseController
|
|||||||
// $refresh_token = $token['refresh_token'];
|
// $refresh_token = $token['refresh_token'];
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
// $refresh_token = '';
|
||||||
|
|
||||||
$name = OAuth::splitName($google->harvestName($user));
|
$name = OAuth::splitName($google->harvestName($user));
|
||||||
|
|
||||||
@ -359,8 +360,8 @@ class LoginController extends BaseController
|
|||||||
'password' => '',
|
'password' => '',
|
||||||
'email' => $google->harvestEmail($user),
|
'email' => $google->harvestEmail($user),
|
||||||
'oauth_user_id' => $google->harvestSubField($user),
|
'oauth_user_id' => $google->harvestSubField($user),
|
||||||
'oauth_user_token' => $token,
|
// 'oauth_user_token' => $token,
|
||||||
'oauth_user_refresh_token' => $refresh_token,
|
// 'oauth_user_refresh_token' => $refresh_token,
|
||||||
'oauth_provider_id' => 'google',
|
'oauth_provider_id' => 'google',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -14,7 +14,9 @@ namespace App\Http\Controllers;
|
|||||||
use App\Libraries\MultiDB;
|
use App\Libraries\MultiDB;
|
||||||
use App\Libraries\OAuth\Providers\Google;
|
use App\Libraries\OAuth\Providers\Google;
|
||||||
use App\Models\CompanyUser;
|
use App\Models\CompanyUser;
|
||||||
|
use App\Models\User;
|
||||||
use App\Transformers\CompanyUserTransformer;
|
use App\Transformers\CompanyUserTransformer;
|
||||||
|
use App\Transformers\UserTransformer;
|
||||||
use Google_Client;
|
use Google_Client;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
@ -95,7 +97,46 @@ class ConnectedAccountController extends BaseController
|
|||||||
$client->setClientId(config('ninja.auth.google.client_id'));
|
$client->setClientId(config('ninja.auth.google.client_id'));
|
||||||
$client->setClientSecret(config('ninja.auth.google.client_secret'));
|
$client->setClientSecret(config('ninja.auth.google.client_secret'));
|
||||||
$client->setRedirectUri(config('ninja.app_url'));
|
$client->setRedirectUri(config('ninja.app_url'));
|
||||||
$token = $client->authenticate(request()->input('server_auth_code'));
|
$refresh_token = '';
|
||||||
|
$token = '';
|
||||||
|
|
||||||
|
$connected_account = [
|
||||||
|
'email' => $google->harvestEmail($user),
|
||||||
|
'oauth_user_id' => $google->harvestSubField($user),
|
||||||
|
'oauth_provider_id' => 'google',
|
||||||
|
'email_verified_at' =>now()
|
||||||
|
];
|
||||||
|
|
||||||
|
auth()->user()->update($connected_account);
|
||||||
|
auth()->user()->email_verified_at = now();
|
||||||
|
auth()->user()->save();
|
||||||
|
|
||||||
|
return $this->itemResponse(auth()->user());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()
|
||||||
|
->json(['message' => ctrans('texts.invalid_credentials')], 401)
|
||||||
|
->header('X-App-Version', config('ninja.app_version'))
|
||||||
|
->header('X-Api-Version', config('ninja.minimum_client_version'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleGmailOauth(Request $request)
|
||||||
|
{
|
||||||
|
|
||||||
|
$user = false;
|
||||||
|
|
||||||
|
$google = new Google();
|
||||||
|
|
||||||
|
$user = $google->getTokenResponse($request->input('id_token'));
|
||||||
|
|
||||||
|
if ($user) {
|
||||||
|
|
||||||
|
$client = new Google_Client();
|
||||||
|
$client->setClientId(config('ninja.auth.google.client_id'));
|
||||||
|
$client->setClientSecret(config('ninja.auth.google.client_secret'));
|
||||||
|
$client->setRedirectUri(config('ninja.app_url'));
|
||||||
|
$token = $client->authenticate($request->input('server_auth_code'));
|
||||||
|
|
||||||
$refresh_token = '';
|
$refresh_token = '';
|
||||||
|
|
||||||
@ -104,7 +145,6 @@ class ConnectedAccountController extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
$connected_account = [
|
$connected_account = [
|
||||||
'password' => '',
|
|
||||||
'email' => $google->harvestEmail($user),
|
'email' => $google->harvestEmail($user),
|
||||||
'oauth_user_id' => $google->harvestSubField($user),
|
'oauth_user_id' => $google->harvestSubField($user),
|
||||||
'oauth_user_token' => $token,
|
'oauth_user_token' => $token,
|
||||||
@ -116,17 +156,15 @@ class ConnectedAccountController extends BaseController
|
|||||||
auth()->user()->update($connected_account);
|
auth()->user()->update($connected_account);
|
||||||
auth()->user()->email_verified_at = now();
|
auth()->user()->email_verified_at = now();
|
||||||
auth()->user()->save();
|
auth()->user()->save();
|
||||||
|
|
||||||
//$ct = CompanyUser::whereUserId(auth()->user()->id);
|
|
||||||
//return $this->listResponse($ct);
|
|
||||||
|
|
||||||
return $this->itemResponse(auth()->user());
|
return $this->itemResponse(auth()->user());
|
||||||
// return $this->listResponse(auth()->user());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()
|
return response()
|
||||||
->json(['message' => ctrans('texts.invalid_credentials')], 401)
|
->json(['message' => ctrans('texts.invalid_credentials')], 401)
|
||||||
->header('X-App-Version', config('ninja.app_version'))
|
->header('X-App-Version', config('ninja.app_version'))
|
||||||
->header('X-Api-Version', config('ninja.minimum_client_version'));
|
->header('X-Api-Version', config('ninja.minimum_client_version'));
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,6 +47,10 @@ class StoreClientRequest extends Request
|
|||||||
$rules['documents'] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
|
$rules['documents'] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($this->number)) {
|
||||||
|
$rules['number'] = Rule::unique('clients')->where('company_id', auth()->user()->company()->id);
|
||||||
|
}
|
||||||
|
|
||||||
/* Ensure we have a client name, and that all emails are unique*/
|
/* Ensure we have a client name, and that all emails are unique*/
|
||||||
//$rules['name'] = 'required|min:1';
|
//$rules['name'] = 'required|min:1';
|
||||||
$rules['settings'] = new ValidClientGroupSettingsRule();
|
$rules['settings'] = new ValidClientGroupSettingsRule();
|
||||||
|
@ -11,6 +11,6 @@ class CreditPolicy extends EntityPolicy
|
|||||||
|
|
||||||
public function create(User $user) : bool
|
public function create(User $user) : bool
|
||||||
{
|
{
|
||||||
return $user->isAdmin() || $user->hasPermission('create_quote') || $user->hasPermission('create_all');
|
return $user->isAdmin() || $user->hasPermission('create_credit') || $user->hasPermission('create_all');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,6 @@ class TaskPolicy extends EntityPolicy
|
|||||||
{
|
{
|
||||||
public function create(User $user) : bool
|
public function create(User $user) : bool
|
||||||
{
|
{
|
||||||
return $user->isAdmin();
|
return $user->isAdmin() || $user->hasPermission('create_task') || $user->hasPermission('create_all');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,17 +13,25 @@ namespace App\Services\BillingSubscription;
|
|||||||
|
|
||||||
use App\DataMapper\InvoiceItem;
|
use App\DataMapper\InvoiceItem;
|
||||||
use App\Factory\InvoiceFactory;
|
use App\Factory\InvoiceFactory;
|
||||||
|
use App\Jobs\Util\SystemLogger;
|
||||||
use App\Models\BillingSubscription;
|
use App\Models\BillingSubscription;
|
||||||
use App\Models\ClientSubscription;
|
use App\Models\ClientSubscription;
|
||||||
use App\Models\PaymentHash;
|
use App\Models\PaymentHash;
|
||||||
use App\Models\Product;
|
use App\Models\Product;
|
||||||
|
use App\Models\SystemLog;
|
||||||
use App\Repositories\InvoiceRepository;
|
use App\Repositories\InvoiceRepository;
|
||||||
|
use App\Utils\Traits\MakesHash;
|
||||||
|
use GuzzleHttp\RequestOptions;
|
||||||
|
|
||||||
class BillingSubscriptionService
|
class BillingSubscriptionService
|
||||||
{
|
{
|
||||||
|
use MakesHash;
|
||||||
|
|
||||||
/** @var BillingSubscription */
|
/** @var BillingSubscription */
|
||||||
private $billing_subscription;
|
private $billing_subscription;
|
||||||
|
|
||||||
|
private $client_subscription;
|
||||||
|
|
||||||
public function __construct(BillingSubscription $billing_subscription)
|
public function __construct(BillingSubscription $billing_subscription)
|
||||||
{
|
{
|
||||||
$this->billing_subscription = $billing_subscription;
|
$this->billing_subscription = $billing_subscription;
|
||||||
@ -74,6 +82,10 @@ class BillingSubscriptionService
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the required line items for the invoice
|
||||||
|
* for the billing subscription.
|
||||||
|
*/
|
||||||
private function createLineItems($data): array
|
private function createLineItems($data): array
|
||||||
{
|
{
|
||||||
$line_items = [];
|
$line_items = [];
|
||||||
@ -108,11 +120,14 @@ class BillingSubscriptionService
|
|||||||
return $line_items;
|
return $line_items;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If a coupon is entered (and is valid)
|
||||||
|
* then we apply the coupon discount with a line item.
|
||||||
|
*/
|
||||||
private function createPromoLine($data)
|
private function createPromoLine($data)
|
||||||
{
|
{
|
||||||
|
|
||||||
$product = $this->billing_subscription->product;
|
$product = $this->billing_subscription->product;
|
||||||
|
|
||||||
$discounted_amount = 0;
|
$discounted_amount = 0;
|
||||||
$discount = 0;
|
$discount = 0;
|
||||||
$amount = $data['quantity'] * $product->cost;
|
$amount = $data['quantity'] * $product->cost;
|
||||||
@ -142,27 +157,79 @@ class BillingSubscriptionService
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function convertInvoiceToRecurring()
|
private function convertInvoiceToRecurring($payment_hash)
|
||||||
{
|
{
|
||||||
//The first invoice is a plain invoice - the second is fired on the recurring schedule.
|
//The first invoice is a plain invoice - the second is fired on the recurring schedule.
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createClientSubscription($payment_hash, $recurring_invoice_id = null)
|
public function createClientSubscription($payment_hash)
|
||||||
{
|
{
|
||||||
//create the client sub record
|
//create the client subscription record
|
||||||
|
|
||||||
|
//are we in a trial phase?
|
||||||
|
//has money been paid?
|
||||||
|
//is this a recurring or one off subscription.
|
||||||
|
|
||||||
//?trial enabled?
|
|
||||||
$cs = new ClientSubscription();
|
$cs = new ClientSubscription();
|
||||||
$cs->subscription_id = $this->billing_subscription->id;
|
$cs->subscription_id = $this->billing_subscription->id;
|
||||||
$cs->company_id = $this->billing_subscription->company_id;
|
$cs->company_id = $this->billing_subscription->company_id;
|
||||||
|
|
||||||
// client_id
|
//if is_trial
|
||||||
|
//$cs->trial_started = time();
|
||||||
|
//$cs->trial_duration = time() + duration period in seconds
|
||||||
|
|
||||||
|
//trials will not have any monies paid.
|
||||||
|
|
||||||
|
//if a payment has been made
|
||||||
|
//$cs->invoice_id = xx
|
||||||
|
|
||||||
|
//if is_recurring
|
||||||
|
//create recurring invoice from invoice
|
||||||
|
$recurring_invoice = $this->convertInvoiceToRecurring($payment_hash);
|
||||||
|
$recurring_invoice->frequency_id = $this->billing_subscription->frequency_id;
|
||||||
|
$recurring_invoice->next_send_date = $recurring_invoice->nextDateByFrequency(now()->format('Y-m-d'));
|
||||||
|
//$cs->recurring_invoice_id = $recurring_invoice->id;
|
||||||
|
|
||||||
|
//?set the recurring invoice as active - set the date here also based on the frequency?
|
||||||
|
|
||||||
|
//$cs->quantity = xx
|
||||||
|
|
||||||
|
// client_id
|
||||||
|
//$cs->client_id = xx
|
||||||
|
|
||||||
$cs->save();
|
$cs->save();
|
||||||
|
|
||||||
|
$this->client_subscription = $cs;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function triggerWebhook($payment_hash)
|
public function triggerWebhook($payment_hash)
|
||||||
{
|
{
|
||||||
//hit the webhook to after a successful onboarding
|
//hit the webhook to after a successful onboarding
|
||||||
|
//$client = xxxxxxx
|
||||||
|
//todo webhook
|
||||||
|
|
||||||
|
$body = [
|
||||||
|
'billing_subscription' => $this->billing_subscription,
|
||||||
|
'client_subscription' => $this->client_subscription,
|
||||||
|
// 'client' => $client->toArray(),
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
$client = new \GuzzleHttp\Client(['headers' => $this->billing_subscription->webhook_configuration->post_purchase_headers]);
|
||||||
|
|
||||||
|
$response = $client->{$this->billing_subscription->webhook_configuration->post_purchase_rest_method}($this->billing_subscription->post_purchase_url,[
|
||||||
|
RequestOptions::JSON => ['body' => $body]
|
||||||
|
]);
|
||||||
|
|
||||||
|
SystemLogger::dispatch(
|
||||||
|
$body,
|
||||||
|
SystemLog::CATEGORY_WEBHOOK,
|
||||||
|
SystemLog::EVENT_WEBHOOK_RESPONSE,
|
||||||
|
SystemLog::TYPE_WEBHOOK_RESPONSE,
|
||||||
|
//$client,
|
||||||
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function fireNotifications()
|
public function fireNotifications()
|
||||||
|
@ -53,7 +53,7 @@ return [
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
'url' => env('APP_URL', 'http://localhost'),
|
'url' => env('APP_URL', 'http://localhost'),
|
||||||
|
'mix_url' => env('APP_URL', 'http://localhost'),
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Application Timezone
|
| Application Timezone
|
||||||
|
@ -13,7 +13,7 @@ return [
|
|||||||
'require_https' => env('REQUIRE_HTTPS', true),
|
'require_https' => env('REQUIRE_HTTPS', true),
|
||||||
'app_url' => rtrim(env('APP_URL', ''), '/'),
|
'app_url' => rtrim(env('APP_URL', ''), '/'),
|
||||||
'app_domain' => env('APP_DOMAIN', ''),
|
'app_domain' => env('APP_DOMAIN', ''),
|
||||||
'app_version' => '5.1.29',
|
'app_version' => '5.1.30',
|
||||||
'minimum_client_version' => '5.0.16',
|
'minimum_client_version' => '5.0.16',
|
||||||
'terms_version' => '1.0.1',
|
'terms_version' => '1.0.1',
|
||||||
'api_secret' => env('API_SECRET', false),
|
'api_secret' => env('API_SECRET', false),
|
||||||
|
@ -37,6 +37,7 @@ class CompanyFactory extends Factory
|
|||||||
'db' => config('database.default'),
|
'db' => config('database.default'),
|
||||||
'settings' => CompanySettings::defaults(),
|
'settings' => CompanySettings::defaults(),
|
||||||
'is_large' => false,
|
'is_large' => false,
|
||||||
|
'default_password_timeout' => 30*60000,
|
||||||
'enabled_modules' => config('ninja.enabled_modules'),
|
'enabled_modules' => config('ninja.enabled_modules'),
|
||||||
'custom_fields' => (object) [
|
'custom_fields' => (object) [
|
||||||
//'invoice1' => 'Custom Date|date',
|
//'invoice1' => 'Custom Date|date',
|
||||||
|
@ -37,10 +37,6 @@ class AddUniqueConstraintsOnAllEntities extends Migration
|
|||||||
$table->unique(['company_id', 'number']);
|
$table->unique(['company_id', 'number']);
|
||||||
});
|
});
|
||||||
|
|
||||||
Schema::table('payment_hashes', function (Blueprint $table) {
|
|
||||||
$table->unique(['hash']);
|
|
||||||
});
|
|
||||||
|
|
||||||
Schema::table('recurring_invoices', function (Blueprint $table) {
|
Schema::table('recurring_invoices', function (Blueprint $table) {
|
||||||
$table->string('number')->change();
|
$table->string('number')->change();
|
||||||
$table->unique(['company_id', 'number']);
|
$table->unique(['company_id', 'number']);
|
||||||
|
4
public/flutter_service_worker.js
vendored
4
public/flutter_service_worker.js
vendored
@ -4,7 +4,7 @@ const TEMP = 'flutter-temp-cache';
|
|||||||
const CACHE_NAME = 'flutter-app-cache';
|
const CACHE_NAME = 'flutter-app-cache';
|
||||||
const RESOURCES = {
|
const RESOURCES = {
|
||||||
"favicon.ico": "51636d3a390451561744c42188ccd628",
|
"favicon.ico": "51636d3a390451561744c42188ccd628",
|
||||||
"manifest.json": "77215c1737c7639764e64a192be2f7b8",
|
"manifest.json": "ce1b79950eb917ea619a0a30da27c6a3",
|
||||||
"icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed",
|
"icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed",
|
||||||
"icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35",
|
"icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35",
|
||||||
"assets/NOTICES": "e80e999afd09f0f14597c78d582d9c7c",
|
"assets/NOTICES": "e80e999afd09f0f14597c78d582d9c7c",
|
||||||
@ -30,7 +30,7 @@ const RESOURCES = {
|
|||||||
"assets/packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf": "3e722fd57a6db80ee119f0e2c230ccff",
|
"assets/packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf": "3e722fd57a6db80ee119f0e2c230ccff",
|
||||||
"assets/FontManifest.json": "cf3c681641169319e61b61bd0277378f",
|
"assets/FontManifest.json": "cf3c681641169319e61b61bd0277378f",
|
||||||
"/": "23224b5e03519aaa87594403d54412cf",
|
"/": "23224b5e03519aaa87594403d54412cf",
|
||||||
"main.dart.js": "114d8affe0f4b7576170753cf9fb4c0a",
|
"main.dart.js": "1e6ec7d853ad458f61ecd490624944bc",
|
||||||
"version.json": "b7c8971e1ab5b627fd2a4317c52b843e",
|
"version.json": "b7c8971e1ab5b627fd2a4317c52b843e",
|
||||||
"favicon.png": "dca91c54388f52eded692718d5a98b8b"
|
"favicon.png": "dca91c54388f52eded692718d5a98b8b"
|
||||||
};
|
};
|
||||||
|
151088
public/main.dart.js
vendored
151088
public/main.dart.js
vendored
File diff suppressed because one or more lines are too long
@ -11,7 +11,6 @@
|
|||||||
"related_applications": [
|
"related_applications": [
|
||||||
{
|
{
|
||||||
"platform": "play",
|
"platform": "play",
|
||||||
"url": "https://play.google.com/store/apps/details?id=com.invoiceninja.app",
|
|
||||||
"id": "com.invoiceninja.app"
|
"id": "com.invoiceninja.app"
|
||||||
}, {
|
}, {
|
||||||
"platform": "itunes",
|
"platform": "itunes",
|
||||||
|
@ -37,6 +37,7 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a
|
|||||||
Route::post('clients/bulk', 'ClientController@bulk')->name('clients.bulk');
|
Route::post('clients/bulk', 'ClientController@bulk')->name('clients.bulk');
|
||||||
|
|
||||||
Route::post('connected_account', 'ConnectedAccountController@index');
|
Route::post('connected_account', 'ConnectedAccountController@index');
|
||||||
|
Route::post('connected_account/gmail', 'ConnectedAccountController@handleGmailOauth');
|
||||||
|
|
||||||
Route::resource('client_statement', 'ClientStatementController@statement'); // name = (client_statement. index / create / show / update / destroy / edit
|
Route::resource('client_statement', 'ClientStatementController@statement'); // name = (client_statement. index / create / show / update / destroy / edit
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user