Fixes for tests (#3082)

* Update client paid to date job:

* Backup Invoice HTML when invoice is marked as sent and paid

* Store HTML of invoice when invoice was paid

* Fix foreign keys in db schema

* V2 Endpoints for Company Migrations

* Fixes for tests
This commit is contained in:
David Bomba 2019-11-20 16:41:49 +11:00 committed by GitHub
parent 6d225b7fe7
commit f59585dd62
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 641 additions and 230 deletions

1
.gitignore vendored
View File

@ -21,3 +21,4 @@ yarn-error.log
.env.dusk.local .env.dusk.local
/public/vendors/* /public/vendors/*
public/mix-manifest.json public/mix-manifest.json
*.log

View File

@ -119,8 +119,10 @@ class CreateTestData extends Command
private function createClient($company, $user) private function createClient($company, $user)
{ {
$client = ClientFactory::create($company->id, $user->id); $client = factory(\App\Models\Client::class)->create([
$client->save(); 'user_id' => $user->id,
'company_id' => $company->id
]);
factory(\App\Models\ClientContact::class,1)->create([ factory(\App\Models\ClientContact::class,1)->create([

View File

@ -0,0 +1,38 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2019. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Events\Invoice;
use App\Models\Invoice;
use Illuminate\Queue\SerializesModels;
/**
* Class InvoiceWasPaid.
*/
class InvoiceWasPaid
{
use SerializesModels;
/**
* @var Invoice
*/
public $invoice;
/**
* Create a new event instance.
*
* @param Invoice $invoice
*/
public function __construct(Invoice $invoice)
{
$this->invoice = $invoice;
}
}

View File

@ -0,0 +1,144 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2019. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Controllers;
use App\Http\Requests\Account\CreateAccountRequest;
use App\Jobs\Account\CreateAccount;
use App\Models\Account;
use App\Models\Company;
use App\Models\CompanyUser;
use App\Transformers\AccountTransformer;
use App\Transformers\CompanyUserTransformer;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class MigrationController extends BaseController
{
use DispatchesJobs;
public function __construct()
{
parent::__construct();
}
/**
*
* Purge Company
*
* @OA\Post(
* path="/api/v1/migration/purge/{company}",
* operationId="postPurgeCompany",
* tags={"migration"},
* summary="Attempts to purge a company record and all its child records",
* description="Attempts to purge a company record and all its child records",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(
* name="company",
* in="path",
* description="The Company Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Success",
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function purgeCompany(Company $company)
{
$company->delete();
return response()->json(['message'=>'Company purged'], 200);
}
/**
*
* Purge Company but save settings
*
* @OA\Post(
* path="/api/v1/migration/purge_save_settings/{company}",
* operationId="postPurgeCompanySaveSettings",
* tags={"migration"},
* summary="Attempts to purge a companies child records but save the company record and its settings",
* description="Attempts to purge a companies child records but save the company record and its settings",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(
* name="company",
* in="path",
* description="The Company Hashed ID",
* example="D2J234DFA",
* required=true,
* @OA\Schema(
* type="string",
* format="string",
* ),
* ),
* @OA\Response(
* response=200,
* description="Success",
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function purgeCompanySaveSettings(Company $company)
{
$company->client->delete()
$company->save()
return response()->json(['message'=>'Setting preserved'], 200);
}
}

View File

@ -0,0 +1,50 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2019. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Jobs\Client;
use App\Models\Client;
use Illuminate\Foundation\Bus\Dispatchable;
class UpdateClientPaidToDate
{
use Dispatchable;
protected $amount;
protected $client;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Client $client, $amount)
{
$this->amount = $amount;
$this->client = $client;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$this->client->paid_to_date += $this->amount;
$this->client->save();
}
}

View File

@ -11,6 +11,7 @@
namespace App\Jobs\Invoice; namespace App\Jobs\Invoice;
use App\Events\Invoice\InvoiceWasPaid;
use App\Jobs\Invoice\ApplyInvoiceNumber; use App\Jobs\Invoice\ApplyInvoiceNumber;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\Payment; use App\Models\Payment;
@ -95,8 +96,11 @@ class ApplyPaymentToInvoice implements ShouldQueue
$this->invoice->balance = $this->invoice->balance + $adjustment; $this->invoice->balance = $this->invoice->balance + $adjustment;
/* Update Invoice Status */ /* Update Invoice Status */
if($this->invoice->balance == 0) if($this->invoice->balance == 0){
$this->invoice->status_id = Invoice::STATUS_PAID; $this->invoice->status_id = Invoice::STATUS_PAID;
$this->invoice->save();
event(new InvoiceWasPaid($this->invoice));
}
elseif($this->payment->amount > 0 && $this->invoice->balance > 0) elseif($this->payment->amount > 0 && $this->invoice->balance > 0)
$this->invoice->status_id = Invoice::STATUS_PARTIAL; $this->invoice->status_id = Invoice::STATUS_PARTIAL;

View File

@ -16,13 +16,13 @@ use App\Models\Payment;
use App\Models\PaymentTerm; use App\Models\PaymentTerm;
use App\Repositories\InvoiceRepository; use App\Repositories\InvoiceRepository;
use App\Utils\Traits\NumberFormatter; use App\Utils\Traits\NumberFormatter;
use App\Utils\Traits\MakesInvoiceHtml;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Spatie\Browsershot\Browsershot; use Spatie\Browsershot\Browsershot;
@ -30,7 +30,7 @@ use Symfony\Component\Debug\Exception\FatalThrowableError;
class CreateInvoicePdf implements ShouldQueue class CreateInvoicePdf implements ShouldQueue
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, NumberFormatter; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, NumberFormatter, MakesInvoiceHtml;
public $invoice; public $invoice;
@ -89,67 +89,5 @@ class CreateInvoicePdf implements ShouldQueue
//->savePdf('test.pdf'); //->savePdf('test.pdf');
} }
/**
* Generate the HTML invoice parsing variables
* and generating the final invoice HTML
*
* @param string $design either the path to the design template, OR the full design template string
* @param Collection $invoice The invoice object
*
* @return string The invoice string in HTML format
*/
private function generateInvoiceHtml($design, $invoice) :string
{
$variables = array_merge($invoice->makeLabels(), $invoice->makeValues());
$design = str_replace(array_keys($variables), array_values($variables), $design);
$data['invoice'] = $invoice;
return $this->renderView($design, $data);
//return view($design, $data)->render();
}
/**
* Parses the blade file string and processes the template variables
*
* @param string $string The Blade file string
* @param array $data The array of template variables
* @return string The return HTML string
*
*/
private function renderView($string, $data) :string
{
if (!$data) {
$data = [];
}
$data['__env'] = app(\Illuminate\View\Factory::class);
$php = Blade::compileString($string);
$obLevel = ob_get_level();
ob_start();
extract($data, EXTR_SKIP);
try {
eval('?' . '>' . $php);
} catch (\Exception $e) {
while (ob_get_level() > $obLevel) {
ob_end_clean();
}
throw $e;
} catch (\Throwable $e) {
while (ob_get_level() > $obLevel) {
ob_end_clean();
}
throw new FatalThrowableError($e);
}
return ob_get_clean();
}
} }

View File

@ -72,8 +72,8 @@ class MarkInvoicePaid implements ShouldQueue
event(new PaymentWasCreated($payment)); event(new PaymentWasCreated($payment));
UpdateCompanyLedgerWithPayment::dispatchNow($payment, ($payment->amount*-1)); UpdateCompanyLedgerWithPayment::dispatchNow($payment, ($payment->amount*-1));
UpdateClientBalance::dispatchNow($payment->client, $this->payment->amount*-1); UpdateClientBalance::dispatchNow($payment->client, $payment->amount*-1);
UpdateClientPaidToDate::dispatchNow($payment->client, $this->payment->amount); UpdateClientPaidToDate::dispatchNow($payment->client, $payment->amount);
return $this->invoice; return $this->invoice;
} }

View File

@ -0,0 +1,50 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2019. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Listeners\Invoice;
use App\Models\Activity;
use App\Repositories\ActivityRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class CreateInvoiceHtmlBackup implements ShouldQueue
{
protected $activity_repo;
/**
* Create the event listener.
*
* @return void
*/
public function __construct(ActivityRepository $activity_repo)
{
$this->activity_repo = $activity_repo;
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
$fields = new \stdClass;
$fields->invoice_id = $event->invoice->id;
$fields->user_id = $event->invoice->user_id;
$fields->company_id = $event->invoice->company_id;
$fields->activity_type_id = Activity::MARK_SENT_INVOICE;
$this->activity_repo->save($fields, $event->invoice);
}
}

View File

@ -45,8 +45,6 @@ class UpdateInvoiceInvitations implements ShouldQueue
*/ */
$invoices->each(function ($invoice) use($payment) { $invoices->each(function ($invoice) use($payment) {
$invoice->status_id = Invoice::STATUS_PAID;
$invoice->save();
$invoice->invitations()->update(['transaction_reference' => $payment->transaction_reference]); $invoice->invitations()->update(['transaction_reference' => $payment->transaction_reference]);
}); });

View File

@ -67,7 +67,9 @@ class Activity extends StaticModel
const ARCHIVE_USER=50; const ARCHIVE_USER=50;
const DELETE_USER=51; const DELETE_USER=51;
const RESTORE_USER=52; const RESTORE_USER=52;
const MARK_SENT_INVOICE=53;
const PAID_INVOICE=54;
protected $casts = [ protected $casts = [
'is_system' => 'boolean', 'is_system' => 'boolean',
'updated_at' => 'timestamp', 'updated_at' => 'timestamp',

View File

@ -23,7 +23,6 @@ class CompanyUser extends Pivot
* @var array * @var array
*/ */
protected $casts = [ protected $casts = [
'settings' => 'object',
'permissions' => 'object', 'permissions' => 'object',
'updated_at' => 'timestamp', 'updated_at' => 'timestamp',
'created_at' => 'timestamp', 'created_at' => 'timestamp',

View File

@ -11,6 +11,8 @@
namespace App\Models; namespace App\Models;
use App\Events\Invoice\InvoiceWasMarkedSent;
use App\Events\Invoice\InvoiceWasPaid;
use App\Events\Invoice\InvoiceWasUpdated; use App\Events\Invoice\InvoiceWasUpdated;
use App\Helpers\Invoice\InvoiceSum; use App\Helpers\Invoice\InvoiceSum;
use App\Helpers\Invoice\InvoiceSumInclusive; use App\Helpers\Invoice\InvoiceSumInclusive;
@ -370,8 +372,13 @@ class Invoice extends BaseModel
$this->balance = $this->balance + $balance_adjustment; $this->balance = $this->balance + $balance_adjustment;
if($this->balance == 0) if($this->balance == 0) {
$this->status_id = self::STATUS_PAID; $this->status_id = self::STATUS_PAID;
$this->save();
event(new InvoiceWasPaid($this));
return;
}
$this->save(); $this->save();
} }
@ -399,6 +406,8 @@ class Invoice extends BaseModel
$this->markInvitationsSent(); $this->markInvitationsSent();
event(new InvoiceWasMarkedSent($this));
UpdateClientBalance::dispatchNow($this->client, $this->balance); UpdateClientBalance::dispatchNow($this->client, $this->balance);
$this->save(); $this->save();

View File

@ -327,7 +327,11 @@ class User extends Authenticatable implements MustVerifyEmail
public function hasPermission($permission) : bool public function hasPermission($permission) : bool
{ {
return $this->permissionsFlat()->contains($permission);
return (stripos($this->user_company()->permissions, $permission) !== false);
// return $this->permissionsFlat()->contains($permission);
} }

View File

@ -15,6 +15,7 @@ use App\Events\Client\ClientWasCreated;
use App\Events\Contact\ContactLoggedIn; use App\Events\Contact\ContactLoggedIn;
use App\Events\Invoice\InvoiceWasCreated; use App\Events\Invoice\InvoiceWasCreated;
use App\Events\Invoice\InvoiceWasMarkedSent; use App\Events\Invoice\InvoiceWasMarkedSent;
use App\Events\Invoice\InvoiceWasPaid;
use App\Events\Invoice\InvoiceWasUpdated; use App\Events\Invoice\InvoiceWasUpdated;
use App\Events\Payment\PaymentWasCreated; use App\Events\Payment\PaymentWasCreated;
use App\Events\Payment\PaymentWasDeleted; use App\Events\Payment\PaymentWasDeleted;
@ -25,6 +26,7 @@ use App\Listeners\Activity\PaymentCreatedActivity;
use App\Listeners\Activity\PaymentDeletedActivity; use App\Listeners\Activity\PaymentDeletedActivity;
use App\Listeners\Contact\UpdateContactLastLogin; use App\Listeners\Contact\UpdateContactLastLogin;
use App\Listeners\Invoice\CreateInvoiceActivity; use App\Listeners\Invoice\CreateInvoiceActivity;
use App\Listeners\Invoice\CreateInvoiceHtmlBackup;
use App\Listeners\Invoice\CreateInvoiceInvitation; use App\Listeners\Invoice\CreateInvoiceInvitation;
use App\Listeners\Invoice\CreateInvoicePdf; use App\Listeners\Invoice\CreateInvoicePdf;
use App\Listeners\Invoice\UpdateInvoiceActivity; use App\Listeners\Invoice\UpdateInvoiceActivity;
@ -59,10 +61,10 @@ class EventServiceProvider extends ServiceProvider
PaymentWasCreated::class => [ PaymentWasCreated::class => [
PaymentCreatedActivity::class, PaymentCreatedActivity::class,
//UpdateInvoicePayment::class, //UpdateInvoicePayment::class,
UpdateInvoiceInvitations::class, //UpdateInvoiceInvitations::class,
], ],
PaymentWasDeleted::class => [ PaymentWasDeleted::class => [
PaymentDeletedActivity::class PaymentDeletedActivity::class,
], ],
'App\Events\ClientWasArchived' => [ 'App\Events\ClientWasArchived' => [
'App\Listeners\ActivityListener@archivedClient', 'App\Listeners\ActivityListener@archivedClient',
@ -82,7 +84,7 @@ class EventServiceProvider extends ServiceProvider
//Invoices //Invoices
InvoiceWasMarkedSent::class => [ InvoiceWasMarkedSent::class => [
CreateInvoiceInvitation::class, CreateInvoiceHtmlBackup::class,
], ],
InvoiceWasUpdated::class => [ InvoiceWasUpdated::class => [
UpdateInvoiceActivity::class, UpdateInvoiceActivity::class,
@ -92,6 +94,9 @@ class EventServiceProvider extends ServiceProvider
CreateInvoiceActivity::class, CreateInvoiceActivity::class,
CreateInvoicePdf::class, CreateInvoicePdf::class,
], ],
InvoiceWasPaid::class => [
CreateInvoiceHtmlBackup::class,
]
]; ];
/** /**

View File

@ -14,6 +14,8 @@ namespace App\Repositories;
use App\Models\Activity; use App\Models\Activity;
use App\Models\Backup; use App\Models\Backup;
use App\Models\Client; use App\Models\Client;
use App\Models\Invoice;
use App\Utils\Traits\MakesInvoiceHtml;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
/** /**
@ -21,7 +23,7 @@ use Illuminate\Support\Facades\Log;
*/ */
class ActivityRepository extends BaseRepository class ActivityRepository extends BaseRepository
{ {
use MakesInvoiceHtml;
/** /**
* Save the Activity * Save the Activity
* *
@ -66,6 +68,11 @@ class ActivityRepository extends BaseRepository
else else
$entity->load('company','client'); $entity->load('company','client');
if(get_class($entity) == Invoice::class && ($activity->activity_type_id == Activity::MARK_SENT_INVOICE || $activity->activity_type_id == Activity::PAID_INVOICE))
$backup->html_backup = $this->generateInvoiceHtml($entity->design(), $entity);
$backup->activity_id = $activity->id; $backup->activity_id = $activity->id;
$backup->json_backup = $entity->toJson(); $backup->json_backup = $entity->toJson();
$backup->save(); $backup->save();

View File

@ -18,7 +18,6 @@ use App\Models\Quote;
use App\Models\RecurringInvoice; use App\Models\RecurringInvoice;
use App\Models\Timezone; use App\Models\Timezone;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Log;
/** /**
* Class GeneratesCounter * Class GeneratesCounter
@ -43,7 +42,6 @@ trait GeneratesCounter
//todo handle if we have specific client patterns in the future //todo handle if we have specific client patterns in the future
$pattern = $client->getSetting('invoice_number_pattern'); $pattern = $client->getSetting('invoice_number_pattern');
//Determine if we are using client_counters //Determine if we are using client_counters
if(strpos($pattern, 'clientCounter')) if(strpos($pattern, 'clientCounter'))
{ {
@ -65,10 +63,12 @@ trait GeneratesCounter
$pattern = $client->getSetting('invoice_number_pattern'); $pattern = $client->getSetting('invoice_number_pattern');
$prefix = $client->getSetting('invoice_number_prefix'); $prefix = $client->getSetting('invoice_number_prefix');
$padding = $client->getSetting('counter_padding'); $padding = $client->getSetting('counter_padding');
$invoice_number = $this->checkEntityNumber(Invoice::class, $client, $counter, $padding, $prefix, $pattern); $invoice_number = $this->checkEntityNumber(Invoice::class, $client, $counter, $padding, $prefix, $pattern);
$this->incrementCounter($counter_entity, 'invoice_number_counter'); $this->incrementCounter($counter_entity, 'invoice_number_counter');
return $invoice_number; return $invoice_number;
} }
@ -173,7 +173,7 @@ trait GeneratesCounter
public function hasSharedCounter(Client $client) : bool public function hasSharedCounter(Client $client) : bool
{ {
return $client->getSettingsByKey('shared_invoice_quote_counter') === TRUE; return $client->getSetting('shared_invoice_quote_counter') === TRUE;
} }
@ -213,6 +213,7 @@ trait GeneratesCounter
} while ($check); } while ($check);
return $number; return $number;
} }
@ -235,6 +236,7 @@ trait GeneratesCounter
private function prefixCounter($counter, $prefix) : string private function prefixCounter($counter, $prefix) : string
{ {
if(strlen($prefix) == 0) if(strlen($prefix) == 0)
return $counter; return $counter;
@ -330,6 +332,7 @@ trait GeneratesCounter
*/ */
private function applyNumberPattern(Client $client, string $counter, $pattern) :string private function applyNumberPattern(Client $client, string $counter, $pattern) :string
{ {
if(!$pattern) if(!$pattern)
return $counter; return $counter;

View File

@ -0,0 +1,88 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2019. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Utils\Traits;
use Illuminate\Support\Facades\Blade;
use Symfony\Component\Debug\Exception\FatalThrowableError;
/**
* Class MakesInvoiceHtml.
*/
trait MakesInvoiceHtml
{
/**
* Generate the HTML invoice parsing variables
* and generating the final invoice HTML
*
* @param string $design either the path to the design template, OR the full design template string
* @param Collection $invoice The invoice object
*
* @return string The invoice string in HTML format
*/
public function generateInvoiceHtml($design, $invoice) :string
{
$variables = array_merge($invoice->makeLabels(), $invoice->makeValues());
$design = str_replace(array_keys($variables), array_values($variables), $design);
$data['invoice'] = $invoice;
return $this->renderView($design, $data);
//return view($design, $data)->render();
}
/**
* Parses the blade file string and processes the template variables
*
* @param string $string The Blade file string
* @param array $data The array of template variables
* @return string The return HTML string
*
*/
public function renderView($string, $data) :string
{
if (!$data) {
$data = [];
}
$data['__env'] = app(\Illuminate\View\Factory::class);
$php = Blade::compileString($string);
$obLevel = ob_get_level();
ob_start();
extract($data, EXTR_SKIP);
try {
eval('?' . '>' . $php);
} catch (\Exception $e) {
while (ob_get_level() > $obLevel) {
ob_end_clean();
}
throw $e;
} catch (\Throwable $e) {
while (ob_get_level() > $obLevel) {
ob_end_clean();
}
throw new FatalThrowableError($e);
}
return ob_get_clean();
}
}

View File

@ -41,11 +41,11 @@ return [
'username' => env('DB_USERNAME1', 'forge'), 'username' => env('DB_USERNAME1', 'forge'),
'password' => env('DB_PASSWORD1', ''), 'password' => env('DB_PASSWORD1', ''),
'port' => env('DB_PORT1', '3306'), 'port' => env('DB_PORT1', '3306'),
'charset' => 'utf8', 'charset' => 'utf8mb4',
'collation' => 'utf8_unicode_ci', 'collation' => 'utf8mb4_unicode_ci',
'prefix' => '', 'prefix' => '',
'strict' => env('DB_STRICT', false), 'strict' => env('DB_STRICT', false),
'engine' => 'InnoDB', 'engine' => 'InnoDB ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8',
], ],
'sqlite' => [ 'sqlite' => [
@ -92,7 +92,7 @@ return [
'prefix' => '', 'prefix' => '',
'prefix_indexes' => true, 'prefix_indexes' => true,
'strict' => env('DB_STRICT', false), 'strict' => env('DB_STRICT', false),
'engine' => 'InnoDB', 'engine' => 'InnoDB ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8',
], ],
'db-ninja-02' => [ 'db-ninja-02' => [
@ -107,7 +107,7 @@ return [
'prefix' => '', 'prefix' => '',
'prefix_indexes' => true, 'prefix_indexes' => true,
'strict' => env('DB_STRICT', false), 'strict' => env('DB_STRICT', false),
'engine' => 'InnoDB', 'engine' => 'InnoDB ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8',
], ],
], ],

View File

@ -15,6 +15,9 @@ class CreateUsersTable extends Migration
public function up() public function up()
{ {
DB::raw("SET GLOBAL innodb_file_per_table=1;");
DB::raw("SET GLOBAL innodb_file_format=Barracuda;");
Schema::create('languages', function ($table) { Schema::create('languages', function ($table) {
$table->increments('id'); $table->increments('id');
$table->string('name'); $table->string('name');
@ -179,7 +182,7 @@ class CreateUsersTable extends Migration
}); });
DB::statement('ALTER table companies key_block_size=8 row_format=compressed'); //DB::statement('ALTER table companies key_block_size=8 row_format=compressed');
Schema::create('company_user', function (Blueprint $table) { Schema::create('company_user', function (Blueprint $table) {
@ -195,6 +198,7 @@ class CreateUsersTable extends Migration
$table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade'); $table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade');
$table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); $table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->index(['account_id', 'company_id']); $table->index(['account_id', 'company_id']);
@ -255,8 +259,7 @@ class CreateUsersTable extends Migration
$table->unique(['oauth_user_id', 'oauth_provider_id']); $table->unique(['oauth_user_id', 'oauth_provider_id']);
$table->foreign('user_id')->references('user_id')->on('company_users')->onDelete('cascade');
// $table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
}); });
@ -364,7 +367,6 @@ class CreateUsersTable extends Migration
$table->timestamps(6); $table->timestamps(6);
$table->softDeletes('deleted_at', 6); $table->softDeletes('deleted_at', 6);
$table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade');
$table->foreign('client_id')->references('id')->on('clients')->onDelete('cascade'); $table->foreign('client_id')->references('id')->on('clients')->onDelete('cascade');
//$table->unique(['company_id', 'email']); //$table->unique(['company_id', 'email']);
}); });
@ -755,7 +757,7 @@ class CreateUsersTable extends Migration
$t->foreign('client_contact_id')->references('id')->on('client_contacts')->onDelete('cascade'); $t->foreign('client_contact_id')->references('id')->on('client_contacts')->onDelete('cascade');
$t->foreign('company_gateway_id')->references('id')->on('company_gateways')->onDelete('cascade'); $t->foreign('company_gateway_id')->references('id')->on('company_gateways')->onDelete('cascade');
$t->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); $t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
;
$t->foreign('payment_type_id')->references('id')->on('payment_types'); $t->foreign('payment_type_id')->references('id')->on('payment_types');
}); });
@ -766,6 +768,9 @@ class CreateUsersTable extends Migration
$table->unsignedInteger('paymentable_id'); $table->unsignedInteger('paymentable_id');
$table->decimal('amount', 16, 4)->default(0); $table->decimal('amount', 16, 4)->default(0);
$table->string('paymentable_type'); $table->string('paymentable_type');
$table->foreign('payment_id')->references('id')->on('payments')->onDelete('cascade');
}); });
Schema::create('payment_libraries', function ($t) { Schema::create('payment_libraries', function ($t) {
@ -896,7 +901,8 @@ class CreateUsersTable extends Migration
Schema::create('backups', function ($table) { Schema::create('backups', function ($table) {
$table->increments('id'); $table->increments('id');
$table->unsignedInteger('activity_id'); $table->unsignedInteger('activity_id');
$table->mediumText('json_backup')->nullable(); $table->longText('json_backup')->nullable();
$table->longText('html_backup')->nullable();
$table->timestamps(6); $table->timestamps(6);
$table->foreign('activity_id')->references('id')->on('activities')->onDelete('cascade'); $table->foreign('activity_id')->references('id')->on('activities')->onDelete('cascade');

View File

@ -73,6 +73,9 @@ Route::group(['middleware' => ['api_db','api_secret_check','token_auth'], 'prefi
Route::post('users/bulk', 'UserController@bulk')->name('users.bulk')->middleware('password_protected'); Route::post('users/bulk', 'UserController@bulk')->name('users.bulk')->middleware('password_protected');
Route::post('migration/purge/{company}', 'MigrationController@purgeCompany');
Route::post('migration/purge_save_settings/{company}', 'MigrationController@purgeCompanySaveSettings');
Route::resource('companies', 'CompanyController'); // name = (companies. index / create / show / update / destroy / edit Route::resource('companies', 'CompanyController'); // name = (companies. index / create / show / update / destroy / edit
Route::resource('company_gateways', 'CompanyGatewayController'); Route::resource('company_gateways', 'CompanyGatewayController');

View File

@ -0,0 +1,96 @@
<?php
namespace Feature;
use App\Jobs\Account\CreateAccount;
use App\Models\Account;
use App\Models\Client;
use App\Models\Company;
use App\Models\Invoice;
use App\Models\User;
use App\Utils\Traits\UserSessionAttributes;
use Faker\Factory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Session;
use Tests\MockAccountData;
use Tests\TestCase;
/**
* @test
* @covers App\Http\Controllers\MigrationController
*/
class MigrationTest extends TestCase
{
use DatabaseTransactions;
use MockAccountData;
public function setUp() :void
{
parent::setUp();
Session::start();
$this->faker = \Faker\Factory::create();
Model::reguard();
$this->makeTestData();
}
public function testCompanyExists()
{
$co = Company::find($this->company->id);
// $this->assertNull($this->company);
$this->assertNotNull($co);
}
public function testThatCompanyDeletesCompletely()
{
$company_id = $this->company->id;
$this->company->delete();
$this->company->fresh();
$co = Company::find($company_id);
// $this->assertNull($this->company);
$this->assertNull($co);
}
public function testCompanyChildDeletes()
{
$this->makeTestData();
$this->assertNotNull($this->company);
$co = Client::whereCompanyId($this->company->id)->get();
$inv = Invoice::whereCompanyId($this->company->id)->get();
$this->assertEquals($co->count(),1);
$this->assertEquals($inv->count(),1);
DB::statement( 'DELETE FROM `clients` WHERE `company_id`=:company_id', array('company_id' => $this->company->id) );
$co = Client::whereCompanyId($this->company->id)->get();
$inv = Invoice::whereCompanyId($this->company->id)->get();
$this->assertEquals($co->count(),0);
$this->assertEquals($inv->count(),0);
$this->assertNotNull($this->company);
$this->assertNotNull($this->company->settings);
$this->assertNotNull($this->company->settings->timezone_id);
}
}

View File

@ -28,6 +28,7 @@ use App\Models\GroupSetting;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\Quote; use App\Models\Quote;
use App\Models\RecurringInvoice; use App\Models\RecurringInvoice;
use App\Models\User;
use App\Utils\Traits\GeneratesCounter; use App\Utils\Traits\GeneratesCounter;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
@ -93,11 +94,15 @@ trait MockAccountData
$this->account->default_company_id = $this->company->id; $this->account->default_company_id = $this->company->id;
$this->account->save(); $this->account->save();
$this->user = factory(\App\Models\User::class)->create([ $this->user = User::whereEmail('user@example.com')->first();
// 'account_id' => $account->id,
'confirmation_code' => $this->createDbHash(config('database.default'))
]);
if(!$this->user){
$this->user = factory(\App\Models\User::class)->create([
// 'account_id' => $account->id,
'confirmation_code' => $this->createDbHash(config('database.default'))
]);
}
$this->token = \Illuminate\Support\Str::random(64); $this->token = \Illuminate\Support\Str::random(64);
$company_token = CompanyToken::create([ $company_token = CompanyToken::create([

View File

@ -9,10 +9,12 @@ use App\Factory\InvoiceFactory;
use App\Factory\ProductFactory; use App\Factory\ProductFactory;
use App\Factory\UserFactory; use App\Factory\UserFactory;
use App\Models\Client; use App\Models\Client;
use App\Models\User;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Session; use Illuminate\Support\Facades\Session;
use Tests\MockAccountData;
use Tests\TestCase; use Tests\TestCase;
/** /**
@ -22,6 +24,7 @@ class FactoryCreationTest extends TestCase
{ {
use MakesHash; use MakesHash;
use DatabaseTransactions; use DatabaseTransactions;
use MockAccountData;
public function setUp() :void public function setUp() :void
{ {
@ -34,21 +37,8 @@ class FactoryCreationTest extends TestCase
Model::reguard(); Model::reguard();
$this->makeTestData();
$this->account = factory(\App\Models\Account::class)->create();
$this->company = factory(\App\Models\Company::class)->create([
'account_id' => $this->account->id,
'domain' => 'ninja.test',
]);
$this->account->default_company_id = $this->company->id;
$this->account->save();
$this->user = factory(\App\Models\User::class)->create([
// 'account_id' => $account->id,
'confirmation_code' => $this->createDbHash(config('database.default'))
]);
} }
/** /**
@ -120,13 +110,13 @@ class FactoryCreationTest extends TestCase
*/ */
public function testClientCreate() public function testClientCreate()
{ {
$client = ClientFactory::create($this->company->id, $this->user->id); $cliz = ClientFactory::create($this->company->id, $this->user->id);
$client->save(); $cliz->save();
$this->assertNotNull($client); $this->assertNotNull($cliz);
$this->assertInternalType("int", $client->id); $this->assertInternalType("int", $cliz->id);
} }
/** /**
@ -136,33 +126,13 @@ class FactoryCreationTest extends TestCase
public function testClientContactCreate() public function testClientContactCreate()
{ {
factory(\App\Models\Client::class)->create(['user_id' => $this->user->id, 'company_id' => $this->company->id])->each(function ($c){ $cliz = ClientFactory::create($this->company->id, $this->user->id);
factory(\App\Models\ClientContact::class,1)->create([ $cliz->save();
'user_id' => $this->user->id,
'client_id' => $c->id,
'company_id' => $this->company->id,
'is_primary' => 1
]);
factory(\App\Models\ClientContact::class,2)->create([ $this->assertNotNull($cliz->contacts);
'user_id' => $this->user->id, $this->assertEquals(1, $cliz->contacts->count());
'client_id' => $c->id, $this->assertInternalType("int", $cliz->contacts->first()->id);
'company_id' => $this->company->id
]);
});
$client = Client::whereUserId($this->user->id)->whereCompanyId($this->company->id)->first();
$contact = ClientContactFactory::create($this->company->id, $this->user->id);
$contact->client_id = $client->id;
$contact->save();
$this->assertNotNull($contact);
$this->assertInternalType("int", $contact->id);
} }

View File

@ -2,12 +2,15 @@
namespace Tests\Unit; namespace Tests\Unit;
use App\DataMapper\ClientSettings;
use App\DataMapper\DefaultSettings; use App\DataMapper\DefaultSettings;
use App\Factory\ClientFactory;
use App\Models\Client; use App\Models\Client;
use App\Models\Company; use App\Models\Company;
use App\Models\Credit; use App\Models\Credit;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\RecurringInvoice; use App\Models\RecurringInvoice;
use App\Models\User;
use App\Utils\Traits\GeneratesCounter; use App\Utils\Traits\GeneratesCounter;
use App\Utils\Traits\GeneratesNumberCounter; use App\Utils\Traits\GeneratesNumberCounter;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
@ -15,6 +18,7 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Session; use Illuminate\Support\Facades\Session;
use Tests\MockAccountData;
use Tests\TestCase; use Tests\TestCase;
/** /**
@ -26,7 +30,7 @@ class GeneratesCounterTest extends TestCase
use GeneratesCounter; use GeneratesCounter;
use DatabaseTransactions; use DatabaseTransactions;
use MakesHash; use MakesHash;
//use MockAccountData; use MockAccountData;
public function setUp() :void public function setUp() :void
{ {
@ -36,49 +40,8 @@ class GeneratesCounterTest extends TestCase
Session::start(); Session::start();
$this->faker = \Faker\Factory::create(); $this->faker = \Faker\Factory::create();
Model::reguard(); Model::reguard();
$account = factory(\App\Models\Account::class)->create();
$company = factory(\App\Models\Company::class)->create([
'account_id' => $account->id,
'domain' => 'ninja.test',
]); $this->makeTestData();
$account->default_company_id = $company->id;
$account->save();
$user = factory(\App\Models\User::class)->create([
// 'account_id' => $account->id,
'confirmation_code' => $this->createDbHash(config('database.default'))
]);
$userPermissions = collect([
'view_invoice',
'view_client',
'edit_client',
'edit_invoice',
'create_invoice',
'create_client'
]);
$userSettings = DefaultSettings::userSettings();
$user->companies()->attach($company->id, [
'account_id' => $account->id,
'is_owner' => 1,
'is_admin' => 1,
'permissions' => $userPermissions->toJson(),
'settings' => json_encode($userSettings),
'is_locked' => 0,
]);
factory(\App\Models\Client::class)->create(['user_id' => $user->id, 'company_id' => $company->id])->each(function ($c) use ($user, $company){
factory(\App\Models\ClientContact::class,1)->create([
'user_id' => $user->id,
'client_id' => $c->id,
'company_id' => $company->id,
'is_primary' => 1
]);
factory(\App\Models\ClientContact::class,2)->create([
'user_id' => $user->id,
'client_id' => $c->id,
'company_id' => $company->id
]);
});
$this->client = Client::whereUserId($user->id)->whereCompanyId($company->id)->first();
} }
@ -92,11 +55,11 @@ class GeneratesCounterTest extends TestCase
$invoice_number = $this->getNextInvoiceNumber($this->client); $invoice_number = $this->getNextInvoiceNumber($this->client);
$this->assertEquals($invoice_number, 1); $this->assertEquals($invoice_number, 0007);
$invoice_number = $this->getNextInvoiceNumber($this->client); $invoice_number = $this->getNextInvoiceNumber($this->client);
$this->assertEquals($invoice_number, 2); $this->assertEquals($invoice_number, '0008');
} }
@ -110,6 +73,10 @@ class GeneratesCounterTest extends TestCase
$this->client->company->settings = $settings; $this->client->company->settings = $settings;
$this->client->company->save(); $this->client->company->save();
$this->client->settings = $settings;
$this->client->save();
$this->client->fresh();
$invoice_number = $this->getNextInvoiceNumber($this->client); $invoice_number = $this->getNextInvoiceNumber($this->client);
$invoice_number2 = $this->getNextInvoiceNumber($this->client); $invoice_number2 = $this->getNextInvoiceNumber($this->client);
@ -121,70 +88,86 @@ class GeneratesCounterTest extends TestCase
public function testInvoiceClientNumberPattern() public function testInvoiceClientNumberPattern()
{ {
$settings = $this->client->company->settings; $settings = $this->company->settings;
$settings->client_number_prefix = '';
$settings->client_number_pattern = '{$year}-{$clientCounter}';
$settings->client_number_counter = 10;
$settings->invoice_number_prefix = ''; $this->company->settings = $settings;
$settings->invoice_number_pattern = '{$year}-{$clientCounter}'; $this->company->save();
$this->client->company->settings = $settings;
$this->client->company->save();
$settings = $this->client->settings; $settings = $this->client->settings;
$settings->invoice_number_counter = 10; $settings->client_number_pattern = '{$year}-{$clientCounter}';
$settings->client_number_counter = 10;
$this->client->settings = $settings; $this->client->settings = $settings;
$this->client->save(); $this->client->save();
$this->client->fresh();
$this->assertEquals($this->client->settings->invoice_number_counter,10); $this->assertEquals($this->client->settings->client_number_counter,10);
$this->assertEquals($this->client->getSetting('client_number_pattern'), '{$year}-{$clientCounter}');
$invoice_number = $this->getNextInvoiceNumber($this->client); $invoice_number = $this->getNextClientNumber($this->client);
$this->assertEquals($invoice_number, '2019-0010'); $this->assertEquals($invoice_number, '2019-0001');
$invoice_number = $this->getNextInvoiceNumber($this->client); $invoice_number = $this->getNextClientNumber($this->client);
$this->assertEquals($invoice_number, '2019-0011'); $this->assertEquals($invoice_number, '2019-0002');
} }
public function testInvoicePadding() public function testInvoicePadding()
{ {
$settings = $this->client->company->settings; $settings = $this->company->settings;
$settings->counter_padding = 5; $settings->counter_padding = 5;
$this->client->company->settings = $settings; $settings->invoice_number_counter = 7;
$this->client->push(); //$this->client->settings = $settings;
$this->company->settings = $settings;
$this->company->save();
$invoice_number = $this->getNextInvoiceNumber($this->client); $cliz = ClientFactory::create($this->company->id, $this->user->id);
$cliz->settings = ClientSettings::defaults();
$cliz->save();
$invoice_number = $this->getNextInvoiceNumber($cliz);
$this->assertEquals($this->client->company->settings->counter_padding, 5); $this->assertEquals($cliz->getSetting('counter_padding'), 5);
$this->assertEquals($invoice_number, '00007');
$this->assertEquals(strlen($invoice_number), 5); $this->assertEquals(strlen($invoice_number), 5);
$this->assertEquals($invoice_number, '00001');
$settings = $this->company->settings;
$settings = $this->client->company->settings;
$settings->counter_padding = 10; $settings->counter_padding = 10;
$this->client->company->settings = $settings; $this->company->settings = $settings;
$this->client->push(); $this->company->save();
$cliz = ClientFactory::create($this->company->id, $this->user->id);
$cliz->settings = ClientSettings::defaults();
$cliz->save();
$invoice_number = $this->getNextInvoiceNumber($this->client); $invoice_number = $this->getNextInvoiceNumber($cliz);
$this->assertEquals($this->client->company->settings->counter_padding, 10); $this->assertEquals($cliz->getSetting('counter_padding'), 10);
$this->assertEquals(strlen($invoice_number), 10); $this->assertEquals(strlen($invoice_number), 10);
$this->assertEquals($invoice_number, '0000000002'); $this->assertEquals($invoice_number, '0000000007');
} }
public function testInvoicePrefix() public function testInvoicePrefix()
{ {
$settings = $this->client->company->settings; $settings = $this->company->settings;
$settings->invoice_number_prefix = 'X'; $settings->invoice_number_prefix = 'X';
$this->client->company->settings = $settings; $this->company->settings = $settings;
$this->client->company->save(); $this->company->save();
$invoice_number = $this->getNextInvoiceNumber($this->client); $cliz = ClientFactory::create($this->company->id, $this->user->id);
$cliz->settings = ClientSettings::defaults();
$cliz->save();
$invoice_number = $this->getNextInvoiceNumber($cliz);
$this->assertEquals($invoice_number, 'X0001'); $this->assertEquals($invoice_number, 'X0001');
$invoice_number = $this->getNextInvoiceNumber($this->client); $invoice_number = $this->getNextInvoiceNumber($cliz);
$this->assertEquals($invoice_number, 'X0002'); $this->assertEquals($invoice_number, 'X0002');
@ -206,16 +189,20 @@ class GeneratesCounterTest extends TestCase
public function testClientNumberPrefix() public function testClientNumberPrefix()
{ {
$settings = $this->client->company->settings; $settings = $this->company->settings;
$settings->client_number_prefix = 'C'; $settings->client_number_prefix = 'C';
$this->client->company->settings = $settings; $this->company->settings = $settings;
$this->client->company->save(); $this->company->save();
$client_number = $this->getNextClientNumber($this->client); $cliz = ClientFactory::create($this->company->id, $this->user->id);
$cliz->settings = ClientSettings::defaults();
$cliz->save();
$client_number = $this->getNextClientNumber($cliz);
$this->assertEquals($client_number, 'C0001'); $this->assertEquals($client_number, 'C0001');
$client_number = $this->getNextClientNumber($this->client); $client_number = $this->getNextClientNumber($cliz);
$this->assertEquals($client_number, 'C0002'); $this->assertEquals($client_number, 'C0002');
@ -224,21 +211,23 @@ class GeneratesCounterTest extends TestCase
public function testClientNumberPattern() public function testClientNumberPattern()
{ {
$settings = $this->client->company->settings; $settings = $this->company->settings;
$settings->client_number_prefix = ''; $settings->client_number_prefix = '';
$settings->client_number_pattern = '{$year}-{$user_id}-{$counter}'; $settings->client_number_pattern = '{$year}-{$user_id}-{$counter}';
$this->client->company->settings = $settings; $this->company->settings = $settings;
$this->client->company->save(); $this->company->save();
$this->client->save();
$this->client->fresh();
$client_number = $this->getNextClientNumber($this->client); $cliz = ClientFactory::create($this->company->id, $this->user->id);
$cliz->settings = ClientSettings::defaults();
$this->assertEquals($client_number, date('Y') . '-' . $this->client->user_id . '-0001'); $cliz->save();
$client_number = $this->getNextClientNumber($this->client); $client_number = $this->getNextClientNumber($cliz);
$this->assertEquals($client_number, date('Y') . '-' . $this->client->user_id . '-0002'); $this->assertEquals($client_number, date('Y') . '-' . str_pad($this->client->user_id, 2, '0', STR_PAD_LEFT) . '-0001');
$client_number = $this->getNextClientNumber($cliz);
$this->assertEquals($client_number, date('Y') . '-' . str_pad($this->client->user_id, 2, '0', STR_PAD_LEFT) . '-0002');
} }
/* /*