1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-09-20 08:21:34 +02:00
* Version bump

* Refactors for refunds / credits

* Working on Company Ledger

* Company Ledger OpenAPI Documentation

* Version Bump

* Fixes for internal composer update
This commit is contained in:
David Bomba 2020-04-11 21:19:05 +10:00 committed by GitHub
parent 4c0bba7814
commit ba55cc32e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 310 additions and 48 deletions

View File

@ -1 +1 @@
0.0.3
5.0.4

View File

@ -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);
}
}

View File

@ -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.

View File

@ -0,0 +1,77 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Controllers;
use App\Http\Requests\CompanyLedger\ShowCompanyLedgerRequest;
use App\Models\CompanyLedger;
use App\Transformers\CompanyLedgerTransformer;
use Illuminate\Http\Request;
class CompanyLedgerController extends BaseController
{
protected $entity_type = CompanyLedger::class;
protected $entity_transformer = CompanyLedgerTransformer::class;
public function __construct()
{
parent::__construct();
}
/**
* Store a newly created resource in storage.
*
* @return \Illuminate\Http\Response
*
* @OA\Get(
* path="/api/v1/company_ledger",
* operationId="getCompanyLedger",
* tags={"company_ledger"},
* summary="Gets a list of company_ledger",
* description="Lists the company_ledger.",
* @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(ref="#/components/parameters/include"),
* @OA\Response(
* response=200,
* description="A list of company_ledger",
* @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\JsonContent(ref="#/components/schemas/CompanyLedger"),
* ),
* @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 index(ShowCompanyLedgerRequest $request)
{
$company_ledger = CompanyLedger::whereCompanyId(auth()->user()->company()->id)->orderBy('id', 'ASC');
return $this->listResponse($company_ledger);
}
}

View File

@ -0,0 +1,13 @@
<?php
/**
* @OA\Schema(
* schema="CompanyLedger",
* type="object",
* @OA\Property(property="entity_id", type="string", example="AS3df3A", description="This field will reference one of the following entity hashed ID payment_id, invoice_id or credit_id"),
* @OA\Property(property="notes", type="string", example="Credit note for invoice #3212", description="The notes which reference this entry of the ledger"),
* @OA\Property(property="balance", type="number", format="float", example="10.00", description="The client balance"),
* @OA\Property(property="adjustment", type="number", format="float", example="10.00", description="The amount the client balance is adjusted by"),
* @OA\Property(property="updated_at", type="number", format="integer", example="1434342123", description="Timestamp"),
* @OA\Property(property="created_at", type="number", format="integer", example="1434342123", description="Timestamp"),
* )
*/

View File

@ -0,0 +1,30 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Http\Requests\CompanyLedger;
use App\Http\Requests\Request;
class ShowCompanyLedgerRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->isAdmin();
}
}

View File

@ -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',

View File

@ -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) {

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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)

View File

@ -10,6 +10,9 @@ use App\Models\SystemLog;
class UpdateInvoicePayment
{
/**
* @deprecated This is bad logic, assumes too much.
*/
public $payment;
public function __construct($payment)

View File

@ -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
*

View File

@ -0,0 +1,43 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Transformers;
use App\Models\CompanyLedger;
use App\Utils\Traits\MakesHash;
/**
* Class CompanyLedgerTransformer.
*
*/
class CompanyLedgerTransformer extends EntityTransformer
{
use MakesHash;
/**
* @param ClientContact $company_ledger
*
* @return array
*
*/
public function transform(CompanyLedger $company_ledger)
{
$entity_name = lcfirst(class_basename($company_ledger->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,
];
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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'),

View File

@ -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',
],
];
});

View File

@ -6,7 +6,7 @@
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
processIsolation="true"
stopOnFailure="true">
<testsuites>
<testsuite name="Unit">

View File

@ -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