diff --git a/VERSION.txt b/VERSION.txt index 6812f8122ef3..553fe87a05ea 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -0.0.3 \ No newline at end of file +5.0.4 \ No newline at end of file diff --git a/app/Console/Commands/ArtisanUpgrade.php b/app/Console/Commands/ArtisanUpgrade.php index 7a9a2ffa0e3b..4f7c59a43570 100644 --- a/app/Console/Commands/ArtisanUpgrade.php +++ b/app/Console/Commands/ArtisanUpgrade.php @@ -2,9 +2,12 @@ namespace App\Console\Commands; +use Composer\Composer; +use Composer\Factory; +use Composer\IO\NullIO; +use Composer\Installer; use Illuminate\Console\Command; use Illuminate\Support\Facades\Artisan; -use Composer\Console\Application; use Symfony\Component\Console\Input\ArrayInput; class ArtisanUpgrade extends Command @@ -54,10 +57,21 @@ class ArtisanUpgrade extends Command \Log::error("I wasn't able to optimize."); } - putenv('COMPOSER_HOME=' . __DIR__ . '/vendor/bin/composer'); - $input = new ArrayInput(array('command' => 'update')); - $application = new Application(); - $application->setAutoExit(true); // prevent `$application->run` method from exitting the script - $application->run($input); + $composer = Factory::create(new NullIO(), base_path('composer.json'), false); + + $output = Installer::create(new NullIO, $composer) + ->setVerbose() + ->setUpdate(true) + ->run(); + + \Log::error(print_r($output,1)); + + + + // putenv('COMPOSER_HOME=' . __DIR__ . '/vendor/bin/composer'); + // $input = new ArrayInput(array('command' => 'update')); + // $application = new Application(); + // $application->setAutoExit(true); // prevent `$application->run` method from exitting the script + // $application->run($input); } } diff --git a/app/Console/Commands/CreateTestData.php b/app/Console/Commands/CreateTestData.php index 1ade12baee00..400c3cddc8ce 100644 --- a/app/Console/Commands/CreateTestData.php +++ b/app/Console/Commands/CreateTestData.php @@ -473,22 +473,24 @@ class CreateTestData extends Command $invoice->service()->createInvitations(); if (rand(0, 1)) { - $payment = PaymentFactory::create($client->company->id, $client->user->id); - $payment->date = $dateable; - $payment->client_id = $client->id; - $payment->amount = $invoice->balance; - $payment->transaction_reference = rand(0, 500); - $payment->type_id = PaymentType::CREDIT_CARD_OTHER; - $payment->status_id = Payment::STATUS_COMPLETED; - $payment->number = $client->getNextPaymentNumber($client); - $payment->currency_id = 1; - $payment->save(); + // $payment = PaymentFactory::create($client->company->id, $client->user->id); + // $payment->date = $dateable; + // $payment->client_id = $client->id; + // $payment->amount = $invoice->balance; + // $payment->transaction_reference = rand(0, 500); + // $payment->type_id = PaymentType::CREDIT_CARD_OTHER; + // $payment->status_id = Payment::STATUS_COMPLETED; + // $payment->number = $client->getNextPaymentNumber($client); + // $payment->currency_id = 1; + // $payment->save(); - $payment->invoices()->save($invoice); + // $payment->invoices()->save($invoice); - event(new PaymentWasCreated($payment, $payment->company)); + $invoice = $invoice->service()->markPaid()->save(); - $payment->service()->updateInvoicePayment(); + //$payment = $invoice->payments->first(); + + //$payment->service()->updateInvoicePayment(); //UpdateInvoicePayment::dispatchNow($payment, $payment->company); } //@todo this slow things down, but gives us PDFs of the invoices for inspection whilst debugging. diff --git a/app/Http/Controllers/CompanyLedgerController.php b/app/Http/Controllers/CompanyLedgerController.php new file mode 100644 index 000000000000..40188d3e5612 --- /dev/null +++ b/app/Http/Controllers/CompanyLedgerController.php @@ -0,0 +1,77 @@ +user()->company()->id)->orderBy('id', 'ASC'); + + return $this->listResponse($company_ledger); + } + + +} diff --git a/app/Http/Controllers/OpenAPI/CompanyLedgerSchema.php b/app/Http/Controllers/OpenAPI/CompanyLedgerSchema.php new file mode 100644 index 000000000000..59989843fc86 --- /dev/null +++ b/app/Http/Controllers/OpenAPI/CompanyLedgerSchema.php @@ -0,0 +1,13 @@ +user()->isAdmin(); + } + +} diff --git a/app/Http/Requests/Payment/RefundPaymentRequest.php b/app/Http/Requests/Payment/RefundPaymentRequest.php index 6f56730ef122..7a586e2f0efe 100644 --- a/app/Http/Requests/Payment/RefundPaymentRequest.php +++ b/app/Http/Requests/Payment/RefundPaymentRequest.php @@ -67,7 +67,7 @@ class RefundPaymentRequest extends Request $input = $this->all(); $rules = [ - 'id' => 'required', + 'id' => 'bail|required', 'id' => new ValidRefundableRequest($input), 'amount' => 'numeric', 'date' => 'required', diff --git a/app/Http/ValidationRules/Payment/ValidRefundableRequest.php b/app/Http/ValidationRules/Payment/ValidRefundableRequest.php index 7fcb39b0f46a..568cbb84cae5 100644 --- a/app/Http/ValidationRules/Payment/ValidRefundableRequest.php +++ b/app/Http/ValidationRules/Payment/ValidRefundableRequest.php @@ -44,6 +44,11 @@ class ValidRefundableRequest implements Rule public function passes($attribute, $value) { + if(!array_key_exists('id', $this->input)){ + $this->error_msg = "Payment `id` required."; + return false; + } + $payment = Payment::whereId($this->input['id'])->first(); if (!$payment) { diff --git a/app/Http/ValidationRules/ValidRefundableInvoices.php b/app/Http/ValidationRules/ValidRefundableInvoices.php index e7e6becc9266..8c2bdbac5603 100644 --- a/app/Http/ValidationRules/ValidRefundableInvoices.php +++ b/app/Http/ValidationRules/ValidRefundableInvoices.php @@ -43,6 +43,12 @@ class ValidRefundableInvoices implements Rule public function passes($attribute, $value) { + + if(!array_key_exists('id', $this->input)){ + $this->error_msg = "Payment `id` required."; + return false; + } + $payment = Payment::whereId($this->input['id'])->first(); if (!$payment) { @@ -50,10 +56,11 @@ class ValidRefundableInvoices implements Rule return false; } - if (request()->has('amount') && (request()->input('amount') > ($payment->amount - $payment->refunded))) { - $this->error_msg = "Attempting to refunded more than payment amount, enter a value equal to or lower than the payment amount of ". $payment->amount; - return false; - } + /*We are not sending the Refunded amount in the 'amount field, this is the Payment->amount, need to skip this check. */ + // if (request()->has('amount') && (request()->input('amount') > ($payment->amount - $payment->refunded))) { + // $this->error_msg = "Attempting to refund more than payment amount, enter a value equal to or lower than the payment amount of ". $payment->amount; + // return false; + // } /*If no invoices has been sent, then we apply the payment to the client account*/ $invoices = []; @@ -65,6 +72,7 @@ class ValidRefundableInvoices implements Rule } foreach ($invoices as $invoice) { + if (! $invoice->isRefundable()) { $this->error_msg = "Invoice id ".$invoice->hashed_id ." cannot be refunded"; return false; diff --git a/app/Jobs/Credit/CreateCreditPdf.php b/app/Jobs/Credit/CreateCreditPdf.php index bce5a33d9681..8a0fef63e84f 100644 --- a/app/Jobs/Credit/CreateCreditPdf.php +++ b/app/Jobs/Credit/CreateCreditPdf.php @@ -87,7 +87,7 @@ class CreateCreditPdf implements ShouldQueue //todo - move this to the client creation stage so we don't keep hitting this unnecessarily Storage::makeDirectory($path, 0755); - $pdf = $this->makePdf(null, null, $html); + $pdf = $this->makePdf(null, null, $html); $instance = Storage::disk($this->disk)->put($file_path, $pdf); diff --git a/app/Jobs/Quote/CreateQuotePdf.php b/app/Jobs/Quote/CreateQuotePdf.php index abb8fd0512c1..9d9d37f07ace 100644 --- a/app/Jobs/Quote/CreateQuotePdf.php +++ b/app/Jobs/Quote/CreateQuotePdf.php @@ -83,16 +83,18 @@ class CreateQuotePdf implements ShouldQueue //todo - move this to the client creation stage so we don't keep hitting this unnecessarily Storage::makeDirectory($path, 0755); - $all_pages_header = $settings->all_pages_header; - $all_pages_footer = $settings->all_pages_footer; - $quote_number = $this->quote->number; $design_body = $designer->build()->getHtml(); $html = $this->generateEntityHtml($designer, $this->quote, $this->contact); - $pdf = $this->makePdf($all_pages_header, $all_pages_footer, $html); +//$start = microtime(true); + + $pdf = $this->makePdf(null, null, $html); + +//\Log::error("PDF Build time = ". (microtime(true) - $start)); + $file_path = $path . $quote_number . '.pdf'; $instance = Storage::disk($this->disk)->put($file_path, $pdf); diff --git a/app/Models/Client.php b/app/Models/Client.php index 93c87c222cc4..633c8e4601c3 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -114,6 +114,11 @@ class Client extends BaseModel implements HasLocalePreference 'deleted_at' => 'timestamp', ]; + public function ledger() + { + return $this->hasMany(CompanyLedger::class); + } + public function gateway_tokens() { return $this->hasMany(ClientGatewayToken::class); diff --git a/app/Models/Company.php b/app/Models/Company.php index 35862f5e9611..217c2ec1e405 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -133,6 +133,11 @@ class Company extends BaseModel self::ENTITY_RECURRING_QUOTE => 2048, ]; + public function ledger() + { + return $this->hasMany(CompanyLedger::class); + } + public function getCompanyIdAttribute() { return $this->encodePrimaryKey($this->id); diff --git a/app/Services/Ledger/LedgerService.php b/app/Services/Ledger/LedgerService.php index e81892de3cd6..caad1d9d6cd5 100644 --- a/app/Services/Ledger/LedgerService.php +++ b/app/Services/Ledger/LedgerService.php @@ -56,7 +56,6 @@ class LedgerService $balance = $company_ledger->balance; } - $company_ledger = CompanyLedgerFactory::create($this->entity->company_id, $this->entity->user_id); $company_ledger->client_id = $this->entity->client_id; $company_ledger->adjustment = $adjustment; @@ -66,6 +65,29 @@ class LedgerService $this->entity->company_ledger()->save($company_ledger); } + public function updateCreditBalance($adjustment, $notes = '') + { + $balance = 0; + + $company_ledger = $this->ledger(); + + if ($company_ledger) { + $balance = $company_ledger->balance; + } + + $company_ledger = CompanyLedgerFactory::create($this->entity->company_id, $this->entity->user_id); + $company_ledger->client_id = $this->entity->client_id; + $company_ledger->adjustment = $adjustment; + $company_ledger->notes = $notes; + $company_ledger->balance = $balance + $adjustment; + $company_ledger->save(); + + $this->entity->company_ledger()->save($company_ledger); + + return $this; + + } + private function ledger() :?CompanyLedger { return CompanyLedger::whereClientId($this->entity->client_id) diff --git a/app/Services/Payment/UpdateInvoicePayment.php b/app/Services/Payment/UpdateInvoicePayment.php index b21eff970f92..ea875c0029de 100644 --- a/app/Services/Payment/UpdateInvoicePayment.php +++ b/app/Services/Payment/UpdateInvoicePayment.php @@ -10,6 +10,9 @@ use App\Models\SystemLog; class UpdateInvoicePayment { + /** + * @deprecated This is bad logic, assumes too much. + */ public $payment; public function __construct($payment) diff --git a/app/Transformers/ClientTransformer.php b/app/Transformers/ClientTransformer.php index f916410b64fd..87354f602528 100644 --- a/app/Transformers/ClientTransformer.php +++ b/app/Transformers/ClientTransformer.php @@ -15,8 +15,10 @@ use App\Models\Activity; use App\Models\Client; use App\Models\ClientContact; use App\Models\ClientGatewayToken; +use App\Models\CompanyLedger; use App\Transformers\ActivityTransformer; use App\Transformers\ClientGatewayTokenTransformer; +use App\Transformers\CompanyLedgerTransformer; use App\Utils\Traits\MakesHash; /** @@ -36,6 +38,7 @@ class ClientTransformer extends EntityTransformer protected $availableIncludes = [ 'gateway_tokens', 'activities', + 'ledger', ]; @@ -69,6 +72,13 @@ class ClientTransformer extends EntityTransformer return $this->includeCollection($client->gateway_tokens, $transformer, ClientGatewayToken::class); } + + public function includeLedger(Client $client) + { + $transformer = new CompanyLedgerTransformer($this->serializer); + + return $this->includeCollection($client->ledger, $transformer, CompanyLedger::class); + } /** * @param Client $client * diff --git a/app/Transformers/CompanyLedgerTransformer.php b/app/Transformers/CompanyLedgerTransformer.php new file mode 100644 index 000000000000..9b10f5421259 --- /dev/null +++ b/app/Transformers/CompanyLedgerTransformer.php @@ -0,0 +1,43 @@ +company_ledgerable_type)) . '_id'; + return [ + $entity_name => (string)$this->encodePrimaryKey($company_ledger->company_ledgerable_id), + 'notes' => (string)$company_ledger->notes ?: '', + 'balance' => (float) $company_ledger->balance, + 'adjustment' => (float) $company_ledger->adjustment, + 'created_at' => (int)$company_ledger->created_at, + 'updated_at' => (int)$company_ledger->updated_at, + 'archived_at' => (int)$company_ledger->deleted_at, + ]; + } +} diff --git a/app/Transformers/CompanyTransformer.php b/app/Transformers/CompanyTransformer.php index 44caad9a28c5..b356d4e91216 100644 --- a/app/Transformers/CompanyTransformer.php +++ b/app/Transformers/CompanyTransformer.php @@ -16,6 +16,7 @@ use App\Models\Activity; use App\Models\Client; use App\Models\Company; use App\Models\CompanyGateway; +use App\Models\CompanyLedger; use App\Models\CompanyUser; use App\Models\Design; use App\Models\Expense; @@ -27,6 +28,7 @@ use App\Models\Quote; use App\Models\Task; use App\Models\TaxRate; use App\Models\User; +use App\Transformers\CompanyLedgerTransformer; use App\Transformers\TaskTransformer; use App\Utils\Traits\MakesHash; @@ -68,6 +70,7 @@ class CompanyTransformer extends EntityTransformer 'quotes', 'projects', 'tasks', + 'ledger', ]; @@ -233,4 +236,11 @@ class CompanyTransformer extends EntityTransformer return $this->includeCollection($company->designs()->get(), $transformer, Design::class); } + + public function includeLedger(Company $company) + { + $transformer = new CompanyLedgerTransformer($this->serializer); + + return $this->includeCollection($company->ledger, $transformer, CompanyLedger::class); + } } diff --git a/app/Utils/Traits/Payment/Refundable.php b/app/Utils/Traits/Payment/Refundable.php index 52917fcd9b24..48c98c788e6b 100644 --- a/app/Utils/Traits/Payment/Refundable.php +++ b/app/Utils/Traits/Payment/Refundable.php @@ -100,6 +100,8 @@ trait Refundable $line_items = []; + $ledger_string = ''; + foreach ($data['invoices'] as $invoice) { $inv = Invoice::find($invoice['invoice_id']); @@ -111,6 +113,8 @@ trait Refundable $credit_line_item->line_total = $invoice['amount']; $credit_line_item->date = $data['date']; + $ledger_string .= $credit_line_item->notes . ' '; + $line_items[] = $credit_line_item; } @@ -171,7 +175,9 @@ trait Refundable $this->save(); - $this->adjustInvoices($data); + $client_balance_adjustment = $this->adjustInvoices($data); + + $credit_note->ledger()->updateCreditBalance($client_balance_adjustment, $ledger_string); $this->client->paid_to_date -= $data['amount']; $this->client->save(); @@ -216,8 +222,10 @@ trait Refundable return $credit_note; } - private function adjustInvoices(array $data) :void + private function adjustInvoices(array $data) { + $adjustment_amount = 0; + foreach ($data['invoices'] as $refunded_invoice) { $invoice = Invoice::find($refunded_invoice['invoice_id']); @@ -231,12 +239,14 @@ trait Refundable $client = $invoice->client; + $adjustment_amount += $refunded_invoice['amount']; $client->balance += $refunded_invoice['amount']; - ///$client->paid_to_date -= $refunded_invoice['amount']; $client->save(); //todo adjust ledger balance here? or after and reference the credit and its total } + + return $adjustment_amount; } } diff --git a/config/ninja.php b/config/ninja.php index 94afdf5bb73d..f3ec465a9ed5 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -11,8 +11,8 @@ return [ 'app_env' => env('APP_ENV', 'local'), 'app_url' => env('APP_URL', ''), 'app_domain' => env('APP_DOMAIN', ''), - 'app_version' => '0.0.3', - 'api_version' => '0.0.3', + 'app_version' => '5.0.4', + 'api_version' => '5.0.4', 'terms_version' => '1.0.1', 'api_secret' => env('API_SECRET', ''), 'google_maps_api_key' => env('GOOGLE_MAPS_API_KEY'), diff --git a/database/factories/CompanyFactory.php b/database/factories/CompanyFactory.php index 6678eeb68e2b..4c1fe5ab87fe 100644 --- a/database/factories/CompanyFactory.php +++ b/database/factories/CompanyFactory.php @@ -12,17 +12,17 @@ $factory->define(App\Models\Company::class, function (Faker $faker) { 'settings' => CompanySettings::defaults(), 'custom_fields' => (object) [ 'invoice1' => '1|date', - 'invoice2' => '2|switch', - 'invoice3' => '3|', - 'invoice4' => '4', - 'client1'=>'1', - 'client2'=>'2', - 'client3'=>'3|date', - 'client4'=>'4|switch', - 'company1'=>'1|date', - 'company2'=>'2|switch', - 'company3'=>'3', - 'company4'=>'4', + // 'invoice2' => '2|switch', + // 'invoice3' => '3|', + // 'invoice4' => '4', + // 'client1'=>'1', + // 'client2'=>'2', + // 'client3'=>'3|date', + // 'client4'=>'4|switch', + // 'company1'=>'1|date', + // 'company2'=>'2|switch', + // 'company3'=>'3', + // 'company4'=>'4', ], ]; }); diff --git a/phpunit.xml b/phpunit.xml index 8c949a4ab873..a83e2f358c30 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -6,7 +6,7 @@ convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" - processIsolation="false" + processIsolation="true" stopOnFailure="true"> diff --git a/routes/api.php b/routes/api.php index b09daa25c385..c84d766e9adc 100644 --- a/routes/api.php +++ b/routes/api.php @@ -124,6 +124,9 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a Route::resource('subscriptions', 'SubscriptionController'); Route::post('subscriptions/bulk', 'SubscriptionController@bulk')->name('subscriptions.bulk'); + /*Company Ledger */ + Route::get('company_ledger', 'CompanyLedgerController@index')->name('company_ledger.index'); + /* Route::resource('tasks', 'TaskController'); // name = (tasks. index / create / show / update / destroy / edit