$payment_methods
* @mixin \Eloquent
*/
class GatewayType extends StaticModel
@@ -94,6 +83,14 @@ class GatewayType extends StaticModel
const BACS = 24;
+ const VENMO = 25;
+
+ const MERCADOPAGO = 26;
+
+ const MYBANK = 27;
+
+ const PAYLATER = 28;
+
public function gateway()
{
return $this->belongsTo(Gateway::class);
@@ -153,9 +150,17 @@ class GatewayType extends StaticModel
return ctrans('texts.fpx');
case self::KLARNA:
return ctrans('texts.klarna');
+ case self::VENMO:
+ return ctrans('texts.payment_type_Venmo');
+ case self::MERCADOPAGO:
+ return ctrans('texts.mercado_pago');
+ case self::MYBANK:
+ return ctrans('texts.mybank');
+ case self::PAYLATER:
+ return ctrans('texts.paypal_paylater');
default:
return ' ';
- break;
}
}
}
+
diff --git a/app/Models/PaymentTerm.php b/app/Models/PaymentTerm.php
index d7edf3fa4a27..8c107801c5a3 100644
--- a/app/Models/PaymentTerm.php
+++ b/app/Models/PaymentTerm.php
@@ -57,9 +57,6 @@ class PaymentTerm extends BaseModel
*/
public $timestamps = true;
- /**
- * @var array
- */
protected $fillable = ['num_days'];
public function getNumDays()
diff --git a/app/Models/Presenters/CompanyPresenter.php b/app/Models/Presenters/CompanyPresenter.php
index 395adfc4a5da..c836091dc421 100644
--- a/app/Models/Presenters/CompanyPresenter.php
+++ b/app/Models/Presenters/CompanyPresenter.php
@@ -25,8 +25,6 @@ class CompanyPresenter extends EntityPresenter
*/
public function name()
{
- $settings = $this->entity->settings;
-
return $this->settings->name ?: ctrans('texts.untitled_account');
}
diff --git a/app/Models/Presenters/InvoicePresenter.php b/app/Models/Presenters/InvoicePresenter.php
index eaa34ad9e563..a7ce9222e30f 100644
--- a/app/Models/Presenters/InvoicePresenter.php
+++ b/app/Models/Presenters/InvoicePresenter.php
@@ -53,7 +53,7 @@ class InvoicePresenter extends EntityPresenter
$properties->itemized_receipt[] = $this->itemRbits($item);
}
- $data = new stdClass();
+ $data = new \stdClass();
$data->receive_time = time();
$data->type = 'transaction_details';
$data->source = 'user';
@@ -64,7 +64,7 @@ class InvoicePresenter extends EntityPresenter
public function itemRbits($item)
{
- $data = new stdClass();
+ $data = new \stdClass();
$data->description = $item->notes;
$data->item_price = floatval($item->cost);
$data->quantity = floatval($item->quantity);
diff --git a/app/Models/Project.php b/app/Models/Project.php
index 7e88c884072e..03f33215041b 100644
--- a/app/Models/Project.php
+++ b/app/Models/Project.php
@@ -106,12 +106,6 @@ class Project extends BaseModel
use PresentableTrait;
use Filterable;
- /**
- * @var array
- */
- /**
- * @var array
- */
protected $fillable = [
'name',
'client_id',
diff --git a/app/Models/StaticModel.php b/app/Models/StaticModel.php
index bc3cf3ea0c61..f5be46c52558 100644
--- a/app/Models/StaticModel.php
+++ b/app/Models/StaticModel.php
@@ -22,13 +22,13 @@ use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundExceptio
* @property-read mixed $id
* @method static \Illuminate\Database\Eloquent\Builder|StaticModel company()
* @method static \Illuminate\Database\Eloquent\Builder|StaticModel exclude($columns)
- * @method static \Illuminate\Database\Eloquent\Builder|StaticModel newModelQuery()
- * @method static \Illuminate\Database\Eloquent\Builder|StaticModel newQuery()
- * @method static \Illuminate\Database\Eloquent\Builder|StaticModel query()
- * @method static \Illuminate\Database\Eloquent\Builder|StaticModel find()
- * @method static \Illuminate\Database\Eloquent\Builder|StaticModel with()
- * @method static \Illuminate\Database\Eloquent\Builder|StaticModel withTrashed()
- * @method static \Illuminate\Database\Eloquent\Builder|StaticModel findOrFail()
+ * @method static \Illuminate\Database\Eloquent\Builder|StaticModel newModelQuery($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|StaticModel newQuery($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|StaticModel query($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|StaticModel find($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|StaticModel with($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|StaticModel withTrashed($value)
+ * @method static \Illuminate\Database\Eloquent\Builder|StaticModel findOrFail($value)
* @mixin \Eloquent
*/
class StaticModel extends Model
diff --git a/app/Models/User.php b/app/Models/User.php
index da37482dbb94..9ceb5f9ad2f3 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -98,6 +98,7 @@ use Illuminate\Foundation\Auth\User as Authenticatable;
* @method static \Illuminate\Database\Eloquent\Builder|User newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|User onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|User query()
+ * @method static \Illuminate\Database\Eloquent\Builder|User where($column, $value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereAcceptedTermsVersion($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereAccountId($value)
* @method static \Illuminate\Database\Eloquent\Builder|User whereAvatar($value)
@@ -336,13 +337,6 @@ class User extends Authenticatable implements MustVerifyEmail
return $this->getCompany();
}
- // private function setCompanyByGuard()
- // {
- // if (Auth::guard('contact')->check()) {
- // $this->setCompany(auth()->user()->client->company);
- // }
- // }
-
public function company_users()
{
return $this->hasMany(CompanyUser::class)->withTrashed();
diff --git a/app/Observers/ClientObserver.php b/app/Observers/ClientObserver.php
index 0b06b2cf3dee..e33316e1833a 100644
--- a/app/Observers/ClientObserver.php
+++ b/app/Observers/ClientObserver.php
@@ -88,7 +88,7 @@ class ClientObserver
{
/** Monitor postal code changes for US based clients for tax calculations */
- if($client->getOriginal('postal_code') != $client->postal_code && $client->country_id == 840 && $client->company->calculate_taxes && !$client->company->account->isFreeHostedClient()) {
+ if(($client->getOriginal('shipping_postal_code') != $client->shipping_postal_code || $client->getOriginal('postal_code') != $client->postal_code) && $client->country_id == 840 && $client->company->calculate_taxes && !$client->company->account->isFreeHostedClient()) {
UpdateTaxData::dispatch($client, $client->company);
}
diff --git a/app/Observers/SubscriptionObserver.php b/app/Observers/SubscriptionObserver.php
index 895ea59dfcc9..dece57bf8858 100644
--- a/app/Observers/SubscriptionObserver.php
+++ b/app/Observers/SubscriptionObserver.php
@@ -16,9 +16,9 @@ use App\Models\Subscription;
class SubscriptionObserver
{
/**
- * Handle the billing_subscription "created" event.
+ * Handle the subscription "created" event.
*
- * @param Subscription $billing_subscription
+ * @param Subscription $subscription
* @return void
*/
public function created(Subscription $subscription)
@@ -27,9 +27,9 @@ class SubscriptionObserver
}
/**
- * Handle the billing_subscription "updated" event.
+ * Handle the subscription "updated" event.
*
- * @param Subscription $billing_subscription
+ * @param Subscription $subscription
* @return void
*/
public function updated(Subscription $subscription)
@@ -38,9 +38,9 @@ class SubscriptionObserver
}
/**
- * Handle the billing_subscription "deleted" event.
+ * Handle the subscription "deleted" event.
*
- * @param Subscription $billing_subscription
+ * @param Subscription $subscription
* @return void
*/
public function deleted(Subscription $subscription)
@@ -49,9 +49,9 @@ class SubscriptionObserver
}
/**
- * Handle the billing_subscription "restored" event.
+ * Handle the subscription "restored" event.
*
- * @param Subscription $billing_subscription
+ * @param Subscription $subscription
* @return void
*/
public function restored(Subscription $subscription)
@@ -60,9 +60,9 @@ class SubscriptionObserver
}
/**
- * Handle the billing_subscription "force deleted" event.
+ * Handle the subscription "force deleted" event.
*
- * @param Subscription $billing_subscription
+ * @param Subscription $subscription
* @return void
*/
public function forceDeleted(Subscription $subscription)
diff --git a/app/PaymentDrivers/PayPalRestPaymentDriver.php b/app/PaymentDrivers/PayPalRestPaymentDriver.php
new file mode 100644
index 000000000000..6e257c8e715b
--- /dev/null
+++ b/app/PaymentDrivers/PayPalRestPaymentDriver.php
@@ -0,0 +1,432 @@
+ 'paypal',
+ 1 => 'card',
+ 25 => 'venmo',
+ 9 => 'sepa',
+ 12 => 'bancontact',
+ 17 => 'eps',
+ 15 => 'giropay',
+ 13 => 'ideal',
+ 26 => 'mercadopago',
+ 27 => 'mybank',
+ 28 => 'paylater',
+ 16 => 'p24',
+ 7 => 'sofort'
+ ];
+
+
+ public function gatewayTypes()
+ {
+
+ $funding_options = [];
+
+ foreach ($this->company_gateway->fees_and_limits as $key => $value) {
+ if ($value->is_enabled) {
+ $funding_options[] = $key;
+ }
+ }
+
+ return $funding_options;
+
+ }
+
+ public function init()
+ {
+ $this->omnipay_gateway = Omnipay::create(
+ $this->company_gateway->gateway->provider
+ );
+
+ $this->omnipay_gateway->initialize((array) $this->company_gateway->getConfig());
+
+ $this->api_endpoint_url = $this->company_gateway->getConfigField('testMode') ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com';
+
+ return $this;
+ }
+
+ public function setPaymentMethod($payment_method_id)
+ {
+ if(!$payment_method_id)
+ return $this;
+
+ $this->paypal_payment_method = $this->funding_options[$payment_method_id];
+
+ return $this;
+ }
+
+ public function authorizeView($payment_method)
+ {
+ // PayPal doesn't support direct authorization.
+
+ return $this;
+ }
+
+ public function authorizeResponse($request)
+ {
+ // PayPal doesn't support direct authorization.
+
+ return $this;
+ }
+
+ public function processPaymentView($data)
+ {
+ $this->init();
+
+ $data['gateway'] = $this;
+
+ $this->payment_hash->data = array_merge((array) $this->payment_hash->data, ['amount' => $data['total']['amount_with_fee']]);
+ $this->payment_hash->save();
+
+ $data['client_id'] = $this->company_gateway->getConfigField('clientId');
+ $data['token'] = $this->getClientToken();
+ $data['order_id'] = $this->createOrder($data);
+ $data['funding_options'] = $this->paypal_payment_method;
+
+ return render('gateways.paypal.pay', $data);
+
+ }
+
+ private function getFundingOptions():string
+ {
+
+ $enums = [
+ 3 => 'paypal',
+ 1 => 'card',
+ 25 => 'venmo',
+ // 9 => 'sepa',
+ // 12 => 'bancontact',
+ // 17 => 'eps',
+ // 15 => 'giropay',
+ // 13 => 'ideal',
+ // 26 => 'mercadopago',
+ // 27 => 'mybank',
+ // 28 => 'paylater',
+ // 16 => 'p24',
+ // 7 => 'sofort'
+ ];
+
+ $funding_options = '';
+
+ foreach($this->company_gateway->fees_and_limits as $key => $value) {
+
+ if($value->is_enabled) {
+
+ $funding_options .=$enums[$key].',';
+
+ }
+
+ }
+
+ return rtrim($funding_options, ',');
+
+ }
+
+ public function processPaymentResponse($request)
+ {
+
+ $response = json_decode($request['gateway_response'], true);
+
+ if($response['status'] == 'COMPLETED' && isset($response['purchase_units'])){
+
+ $data = [
+ 'payment_type' => PaymentType::PAYPAL,
+ 'amount' => $response['purchase_units'][0]['amount']['value'],
+ 'transaction_reference' => $response['purchase_units'][0]['payments']['captures'][0]['id'],
+ 'gateway_type_id' => GatewayType::PAYPAL,
+ ];
+
+ $payment = $this->createPayment($data, \App\Models\Payment::STATUS_COMPLETED);
+
+ SystemLogger::dispatch(
+ ['response' => $response, 'data' => $data],
+ SystemLog::CATEGORY_GATEWAY_RESPONSE,
+ SystemLog::EVENT_GATEWAY_SUCCESS,
+ SystemLog::TYPE_PAYPAL,
+ $this->client,
+ $this->client->company,
+ );
+
+ return redirect()->route('client.payments.show', ['payment' => $this->encodePrimaryKey($payment->id)]);
+
+ }
+ else {
+
+ SystemLogger::dispatch(
+ ['response' => $response],
+ SystemLog::CATEGORY_GATEWAY_RESPONSE,
+ SystemLog::EVENT_GATEWAY_FAILURE,
+ SystemLog::TYPE_PAYPAL,
+ $this->client,
+ $this->client->company,
+ );
+
+
+ throw new PaymentFailed('Payment failed. Please try again.', 401);
+ }
+ }
+
+ private function getClientToken(): string
+ {
+
+ $r = $this->gatewayRequest('/v1/identity/generate-token', 'post', ['body' => '']);
+
+ if($r->successful())
+ return $r->json()['client_token'];
+
+ throw new PaymentFailed('Unable to gain client token from Paypal. Check your configuration', 401);
+
+ }
+
+ private function createOrder(array $data): string
+ {
+
+ $_invoice = collect($this->payment_hash->data->invoices)->first();
+
+ $invoice = Invoice::withTrashed()->find($this->decodePrimaryKey($_invoice->invoice_id));
+
+ $order = [
+ "intent" => "CAPTURE",
+ "payer" => [
+ "name" => [
+ "given_name" => $this->client->present()->first_name(),
+ "surname" => $this->client->present()->last_name(),
+ ],
+ "email_address" => $this->client->present()->email(),
+ "address" => [
+ "address_line_1" => $this->client->address1,
+ "address_line_2" => $this->client->address2,
+ "admin_area_1" => $this->client->city,
+ "admin_area_2" => $this->client->state,
+ "postal_code" => $this->client->postal_code,
+ "country_code" => $this->client->country->iso_3166_2,
+ ]
+ ],
+ "purchase_units" => [
+ [
+ "description" =>ctrans('texts.invoice_number').'# '.$invoice->number,
+ "invoice_id" => $invoice->number,
+ "amount" => [
+ "value" => (string)$data['amount_with_fee'],
+ "currency_code"=> $this->client->currency()->code,
+ "breakdown" => [
+ "item_total" => [
+ "currency_code" => $this->client->currency()->code,
+ "value" => (string)$data['amount_with_fee']
+ ]
+ ]
+ ],
+ "items"=> [
+ [
+ "name" => ctrans('texts.invoice_number').'# '.$invoice->number,
+ "quantity" => "1",
+ "unit_amount" => [
+ "currency_code" => $this->client->currency()->code,
+ "value" => (string)$data['amount_with_fee']
+ ],
+ ],
+ ],
+ ]
+ ]
+ ];
+
+ $r = $this->gatewayRequest('/v2/checkout/orders', 'post', $order);
+
+ return $r->json()['id'];
+
+ }
+
+ public function gatewayRequest(string $uri, string $verb, array $data, ?array $headers = [])
+ {
+ $r = Http::withToken($this->omnipay_gateway->getToken())
+ ->withHeaders($this->getHeaders($headers))
+ ->{$verb}("{$this->api_endpoint_url}{$uri}", $data);
+
+ if($r->successful())
+ return $r;
+
+ throw new PaymentFailed("Gateway failure - {$r->body()}", 401);
+
+ }
+
+ private function getHeaders(array $headers = []): array
+ {
+ return array_merge([
+ 'Accept' => 'application/json',
+ 'Content-type' => 'application/json',
+ 'Accept-Language' => 'en_US',
+ ], $headers);
+ }
+
+ /*
+ public function processPaymentResponse($request)
+ {
+ $this->initializeOmnipayGateway();
+
+ $response = $this->omnipay_gateway
+ ->completePurchase(['amount' => $this->payment_hash->data->amount, 'currency' => $this->client->getCurrencyCode()])
+ ->send();
+
+ if ($response->isCancelled() && $this->client->getSetting('enable_client_portal')) {
+ return redirect()->route('client.invoices.index')->with('warning', ctrans('texts.status_cancelled'));
+ } elseif ($response->isCancelled() && !$this->client->getSetting('enable_client_portal')) {
+ redirect()->route('client.invoices.show', ['invoice' => $this->payment_hash->fee_invoice])->with('warning', ctrans('texts.status_cancelled'));
+ }
+
+ if ($response->isSuccessful()) {
+ $data = [
+ 'payment_method' => $response->getData()['TOKEN'],
+ 'payment_type' => PaymentType::PAYPAL,
+ 'amount' => $this->payment_hash->data->amount,
+ 'transaction_reference' => $response->getTransactionReference(),
+ 'gateway_type_id' => GatewayType::PAYPAL,
+ ];
+
+ $payment = $this->createPayment($data, \App\Models\Payment::STATUS_COMPLETED);
+
+ SystemLogger::dispatch(
+ ['response' => (array) $response->getData(), 'data' => $data],
+ SystemLog::CATEGORY_GATEWAY_RESPONSE,
+ SystemLog::EVENT_GATEWAY_SUCCESS,
+ SystemLog::TYPE_PAYPAL,
+ $this->client,
+ $this->client->company,
+ );
+
+ return redirect()->route('client.payments.show', ['payment' => $this->encodePrimaryKey($payment->id)]);
+ }
+
+ if (! $response->isSuccessful()) {
+ $data = $response->getData();
+
+ $this->sendFailureMail($response->getMessage() ?: '');
+
+ $message = [
+ 'server_response' => $data['L_LONGMESSAGE0'],
+ 'data' => $this->payment_hash->data,
+ ];
+
+ SystemLogger::dispatch(
+ $message,
+ SystemLog::CATEGORY_GATEWAY_RESPONSE,
+ SystemLog::EVENT_GATEWAY_FAILURE,
+ SystemLog::TYPE_PAYPAL,
+ $this->client,
+ $this->client->company,
+ );
+
+ throw new PaymentFailed($response->getMessage(), $response->getCode());
+ }
+ }
+
+ public function generatePaymentDetails(array $data)
+ {
+ $_invoice = collect($this->payment_hash->data->invoices)->first();
+ $invoice = Invoice::withTrashed()->find($this->decodePrimaryKey($_invoice->invoice_id));
+
+ // $this->fee = $this->feeCalc($invoice, $data['total']['amount_with_fee']);
+
+ return [
+ 'currency' => $this->client->getCurrencyCode(),
+ 'transactionType' => 'Purchase',
+ 'clientIp' => request()->getClientIp(),
+ // 'amount' => round(($data['total']['amount_with_fee'] + $this->fee),2),
+ 'amount' => round($data['total']['amount_with_fee'], 2),
+ 'returnUrl' => route('client.payments.response', [
+ 'company_gateway_id' => $this->company_gateway->id,
+ 'payment_hash' => $this->payment_hash->hash,
+ 'payment_method_id' => GatewayType::PAYPAL,
+ ]),
+ 'cancelUrl' => $this->client->company->domain()."/client/invoices/{$invoice->hashed_id}",
+ 'description' => implode(',', collect($this->payment_hash->data->invoices)
+ ->map(function ($invoice) {
+ return sprintf('%s: %s', ctrans('texts.invoice_number'), $invoice->invoice_number);
+ })->toArray()),
+ 'transactionId' => $this->payment_hash->hash.'-'.time(),
+ 'ButtonSource' => 'InvoiceNinja_SP',
+ 'solutionType' => 'Sole',
+ ];
+ }
+
+ public function generatePaymentItems(array $data)
+ {
+ $_invoice = collect($this->payment_hash->data->invoices)->first();
+ $invoice = Invoice::withTrashed()->find($this->decodePrimaryKey($_invoice->invoice_id));
+
+ $items = [];
+
+ $items[] = new Item([
+ 'name' => ' ',
+ 'description' => ctrans('texts.invoice_number').'# '.$invoice->number,
+ 'price' => $data['total']['amount_with_fee'],
+ 'quantity' => 1,
+ ]);
+
+ return $items;
+ }
+
+ */
+
+ private function feeCalc($invoice, $invoice_total)
+ {
+ $invoice->service()->removeUnpaidGatewayFees();
+ $invoice = $invoice->fresh();
+
+ $balance = floatval($invoice->balance);
+
+ $_updated_invoice = $invoice->service()->addGatewayFee($this->company_gateway, GatewayType::PAYPAL, $invoice_total)->save();
+
+ if (floatval($_updated_invoice->balance) > $balance) {
+ $fee = floatval($_updated_invoice->balance) - $balance;
+
+ $this->payment_hash->fee_total = $fee;
+ $this->payment_hash->save();
+
+ return $fee;
+ }
+
+ return 0;
+ }
+
+
+}
diff --git a/app/Repositories/BaseRepository.php b/app/Repositories/BaseRepository.php
index 15c93b5fe556..13df4b4d8435 100644
--- a/app/Repositories/BaseRepository.php
+++ b/app/Repositories/BaseRepository.php
@@ -165,6 +165,7 @@ class BaseRepository
if (! $model->id) {
$company_defaults = $client->setCompanyDefaults($data, lcfirst($resource));
+ $data['exchange_rate'] = $company_defaults['exchange_rate'];
$model->uses_inclusive_taxes = $client->getSetting('inclusive_taxes');
$data = array_merge($company_defaults, $data);
}
diff --git a/app/Services/ClientPortal/InstantPayment.php b/app/Services/ClientPortal/InstantPayment.php
index 284e79d93a24..c9366ad83b46 100644
--- a/app/Services/ClientPortal/InstantPayment.php
+++ b/app/Services/ClientPortal/InstantPayment.php
@@ -175,8 +175,11 @@ class InstantPayment
}
if ($this->request->has('signature') && ! is_null($this->request->signature) && ! empty($this->request->signature)) {
- $invoices->each(function ($invoice) {
- InjectSignature::dispatch($invoice, $this->request->signature);
+
+ $contact_id = auth()->guard('contact')->user() ? auth()->guard('contact')->user()->id : null;
+
+ $invoices->each(function ($invoice) use($contact_id) {
+ InjectSignature::dispatch($invoice, $contact_id, $this->request->signature, request()->getClientIp());
});
}
diff --git a/app/Services/Invoice/InvoiceService.php b/app/Services/Invoice/InvoiceService.php
index d5a0715ee7d2..fa4fb4036187 100644
--- a/app/Services/Invoice/InvoiceService.php
+++ b/app/Services/Invoice/InvoiceService.php
@@ -78,9 +78,9 @@ class InvoiceService
* Sets the exchange rate on the invoice if the client currency
* is different to the company currency.
*/
- public function setExchangeRate()
+ public function setExchangeRate($force = false)
{
- if ($this->invoice->exchange_rate != 1) {
+ if ($this->invoice->exchange_rate != 1 || $force) {
return $this;
}
diff --git a/app/Services/Quote/MarkSent.php b/app/Services/Quote/MarkSent.php
index 9969cbfc3f41..c6a615efd9a5 100644
--- a/app/Services/Quote/MarkSent.php
+++ b/app/Services/Quote/MarkSent.php
@@ -11,22 +11,17 @@
namespace App\Services\Quote;
-use App\Events\Quote\QuoteWasMarkedSent;
-use App\Models\Quote;
-use App\Models\Webhook;
-use App\Utils\Ninja;
use Carbon\Carbon;
+use App\Utils\Ninja;
+use App\Models\Quote;
+use App\Models\Client;
+use App\Models\Webhook;
+use App\Events\Quote\QuoteWasMarkedSent;
class MarkSent
{
- private $client;
-
- private $quote;
-
- public function __construct($client, $quote)
+ public function __construct(private Client $client, private Quote $quote)
{
- $this->client = $client;
- $this->quote = $quote;
}
public function run()
@@ -38,9 +33,9 @@ class MarkSent
$this->quote->markInvitationsSent();
- if ($this->quote->due_date != '' || $this->quote->client->getSetting('valid_until') == '') {
+ if ($this->quote->due_date != '' || $this->client->getSetting('valid_until') == '') {
} else {
- $this->quote->due_date = Carbon::parse($this->quote->date)->addDays($this->quote->client->getSetting('valid_until'));
+ $this->quote->due_date = Carbon::parse($this->quote->date)->addDays($this->client->getSetting('valid_until'));
}
$this->quote
diff --git a/app/Services/Report/ARSummaryReport.php b/app/Services/Report/ARSummaryReport.php
index ebbb891cfee7..0fd0d5b83dfb 100644
--- a/app/Services/Report/ARSummaryReport.php
+++ b/app/Services/Report/ARSummaryReport.php
@@ -19,7 +19,6 @@ use App\Models\Invoice;
use App\Utils\Ninja;
use App\Utils\Number;
use App\Utils\Traits\MakesDates;
-use Carbon\Carbon;
use Illuminate\Support\Facades\App;
use League\Csv\Writer;
@@ -33,6 +32,8 @@ class ARSummaryReport extends BaseExport
public Client $client;
+ private float $total = 0;
+
public array $report_keys = [
'client_name',
'client_number',
@@ -101,7 +102,7 @@ class ARSummaryReport extends BaseExport
{
$this->client = $client;
- return [
+ $row = [
$this->client->present()->name(),
$this->client->number,
$this->client->id_number,
@@ -111,7 +112,12 @@ class ARSummaryReport extends BaseExport
$this->getAgingAmount('90'),
$this->getAgingAmount('120'),
$this->getAgingAmount('120+'),
+ Number::formatMoney($this->total, $this->client),
];
+
+ $this->total = 0;
+
+ return $row;
}
private function getCurrent(): string
@@ -128,6 +134,8 @@ class ARSummaryReport extends BaseExport
})
->sum('balance');
+ $this->total += $amount;
+
return Number::formatMoney($amount, $this->client);
}
@@ -153,6 +161,8 @@ class ARSummaryReport extends BaseExport
->whereBetween('due_date', [$to, $from])
->sum('balance');
+ $this->total += $amount;
+
return Number::formatMoney($amount, $this->client);
}
diff --git a/app/Services/Report/ProfitLoss.php b/app/Services/Report/ProfitLoss.php
index 26d4f188ab7f..d070b7636614 100644
--- a/app/Services/Report/ProfitLoss.php
+++ b/app/Services/Report/ProfitLoss.php
@@ -366,7 +366,7 @@ class ProfitLoss
$csv->insertOne(['--------------------']);
- $csv->insertOne([ctrans('texts.total_revenue'), Number::formatMoney($this->income, $this->company)]);
+ $csv->insertOne([ctrans('texts.total_revenue'), Number::formatMoney($this->income - $this->income_taxes, $this->company)]);
//total taxes
@@ -386,7 +386,7 @@ class ProfitLoss
$csv->insertOne([ctrans('texts.total_taxes'), Number::formatMoney(array_sum(array_column($this->expense_break_down, 'tax')), $this->company)]);
$csv->insertOne(['--------------------']);
- $csv->insertOne([ctrans('texts.total_profit'), Number::formatMoney($this->income - array_sum(array_column($this->expense_break_down, 'total')), $this->company)]);
+ $csv->insertOne([ctrans('texts.total_profit'), Number::formatMoney($this->income - $this->income_taxes - array_sum(array_column($this->expense_break_down, 'total'))- array_sum(array_column($this->expense_break_down, 'tax')), $this->company)]);
//net profit
diff --git a/app/Utils/Traits/Inviteable.php b/app/Utils/Traits/Inviteable.php
index ee1cffcca78b..ed7c7056845c 100644
--- a/app/Utils/Traits/Inviteable.php
+++ b/app/Utils/Traits/Inviteable.php
@@ -68,6 +68,8 @@ trait Inviteable
$qr = $writer->writeString($this->getPaymentLink(), 'utf-8');
+ return "$qr
";
+
return "";
}
diff --git a/app/Utils/Traits/MakesHash.php b/app/Utils/Traits/MakesHash.php
index 72b138795f87..63c151b93530 100644
--- a/app/Utils/Traits/MakesHash.php
+++ b/app/Utils/Traits/MakesHash.php
@@ -62,7 +62,7 @@ trait MakesHash
return $hashids->encode($value);
}
- public function decodePrimaryKey($value) : string
+ public function decodePrimaryKey($value)
{
try {
$hashids = new Hashids(config('ninja.hash_salt'), 10);
@@ -71,7 +71,6 @@ trait MakesHash
if (! is_array($decoded_array)) {
throw new \Exception('Invalid Primary Key');
- //response()->json(['error'=>'Invalid primary key'], 400);
}
return $decoded_array[0];
diff --git a/config/ninja.php b/config/ninja.php
index d092d5e8571b..086fae76a203 100644
--- a/config/ninja.php
+++ b/config/ninja.php
@@ -84,6 +84,7 @@ return [
'password' => 'password',
'stripe' => env('STRIPE_KEYS', ''),
'paypal' => env('PAYPAL_KEYS', ''),
+ 'paypal_rest' => env('PAYPAL_REST_KEYS', ''),
'authorize' => env('AUTHORIZE_KEYS', ''),
'checkout' => env('CHECKOUT_KEYS', ''),
'travis' => env('TRAVIS', false),
diff --git a/database/migrations/2023_06_20_123355_add_paypal_rest_payment_driver.php b/database/migrations/2023_06_20_123355_add_paypal_rest_payment_driver.php
new file mode 100644
index 000000000000..df40da51c578
--- /dev/null
+++ b/database/migrations/2023_06_20_123355_add_paypal_rest_payment_driver.php
@@ -0,0 +1,48 @@
+clientId = "";
+ $fields->secret = "";
+ $fields->testMode = false;
+
+ $paypal = new Gateway;
+ $paypal->id = 60;
+ $paypal->name = 'PayPal REST';
+ $paypal->key = '80af24a6a691230bbec33e930ab40665';
+ $paypal->provider = 'PayPal_Rest';
+ $paypal->is_offsite = false;
+ $paypal->fields = \json_encode($fields);
+ $paypal->visible = 1;
+ $paypal->site_url = 'https://www.paypal.com/';
+ $paypal->save();
+ }
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ //
+ }
+};
diff --git a/database/seeders/PaymentLibrariesSeeder.php b/database/seeders/PaymentLibrariesSeeder.php
index b7420fb8c4ee..4a096febef58 100644
--- a/database/seeders/PaymentLibrariesSeeder.php
+++ b/database/seeders/PaymentLibrariesSeeder.php
@@ -83,6 +83,7 @@ class PaymentLibrariesSeeder extends Seeder
['id' => 57, 'name' => 'Square', 'provider' => 'Square', 'is_offsite' => false, 'sort_order' => 21, 'key' => '65faab2ab6e3223dbe848b1686490baz', 'fields' => '{"accessToken":"","applicationId":"","locationId":"","testMode":false}'],
['id' => 58, 'name' => 'Razorpay', 'provider' => 'Razorpay', 'is_offsite' => false, 'sort_order' => 21, 'key' => 'hxd6gwg3ekb9tb3v9lptgx1mqyg69zu9', 'fields' => '{"apiKey":"","apiSecret":""}'],
['id' => 59, 'name' => 'Forte', 'provider' => 'Forte', 'is_offsite' => false, 'sort_order' => 21, 'key' => 'kivcvjexxvdiyqtj3mju5d6yhpeht2xs', 'fields' => '{"testMode":false,"apiLoginId":"","apiAccessId":"","secureKey":"","authOrganizationId":"","organizationId":"","locationId":""}'],
+ ['id' => 60, 'name' => 'PayPal REST', 'provider' => 'PayPal_Rest', 'key' => '80af24a6a691230bbec33e930ab40665', 'fields' => '{"clientId":"","secret":"","signature":"","testMode":false}'],
];
foreach ($gateways as $gateway) {
@@ -99,7 +100,7 @@ class PaymentLibrariesSeeder extends Seeder
Gateway::query()->update(['visible' => 0]);
- Gateway::whereIn('id', [1,3,7,11,15,20,39,46,55,50,57,52,58,59])->update(['visible' => 1]);
+ Gateway::whereIn('id', [1,3,7,11,15,20,39,46,55,50,57,52,58,59,60])->update(['visible' => 1]);
if (Ninja::isHosted()) {
Gateway::whereIn('id', [20])->update(['visible' => 0]);
diff --git a/lang/da/texts.php b/lang/da/texts.php
index 1407fc804bc0..14151b4137dd 100644
--- a/lang/da/texts.php
+++ b/lang/da/texts.php
@@ -882,7 +882,7 @@ $LANG = array(
'custom_invoice_charges_helps' => 'Add a text input to the invoice create/edit page and include the charge in the invoice subtotals.',
'token_expired' => 'Validation token was expired. Please try again.',
'invoice_link' => 'Invoice Link',
- 'button_confirmation_message' => 'Confirm your email.',
+ 'button_confirmation_message' => 'Klik for at bekræfte din e-mail.',
'confirm' => 'Confirm',
'email_preferences' => 'Email Preferences',
'created_invoices' => 'Successfully created :count invoice(s)',
@@ -1179,7 +1179,7 @@ $LANG = array(
'plan_started' => 'Plan Started',
'plan_expires' => 'Plan Expires',
- 'white_label_button' => 'Purchase White Label',
+ 'white_label_button' => 'Køb whitelabel licens',
'pro_plan_year_description' => 'One year enrollment in the Invoice Ninja Pro Plan.',
'pro_plan_month_description' => 'One month enrollment in the Invoice Ninja Pro Plan.',
@@ -1853,7 +1853,7 @@ $LANG = array(
'task' => 'Opgave',
'contact_name' => 'Kontakt navn',
'city_state_postal' => 'By/Postnummer',
- 'postal_city' => 'Postal/City',
+ 'postal_city' => 'Postnr og by',
'custom_field' => 'Brugerdefineret felt',
'account_fields' => 'Virksomheds felter',
'facebook_and_twitter' => 'Facebook og Twitter',
@@ -2060,8 +2060,8 @@ $LANG = array(
'group_when_sorted' => 'Group Sort',
'group_dates_by' => 'Gruppér datoer efter',
'year' => 'År',
- 'view_statement' => 'View Statement',
- 'statement' => 'Statement',
+ 'view_statement' => 'Se kontoudskrift',
+ 'statement' => 'Kontoudtog',
'statement_date' => 'Statement Date',
'mark_active' => 'Markér som aktiv',
'send_automatically' => 'Send automatisk',
@@ -4972,7 +4972,7 @@ $LANG = array(
'xinvoice_payable' => 'Payable within :payeddue days net until :paydate',
'xinvoice_no_buyers_reference' => "No buyer's reference given",
'xinvoice_online_payment' => 'The invoice needs to be paid online via the provided link',
- 'pre_payment' => 'Pre Payment',
+ 'pre_payment' => 'Forudbetaling',
'number_of_payments' => 'Number of payments',
'number_of_payments_helper' => 'The number of times this payment will be made',
'pre_payment_indefinitely' => 'Continue until cancelled',
@@ -5113,6 +5113,16 @@ $LANG = array(
'item_tax_rate2' => 'Item Tax Rate 2',
'item_tax_rate3' => 'Item Tax Rate 3',
'buy_price' => 'Buy Price',
+ 'country_Macedonia' => 'Macedonia',
+ 'admin_initiated_payments' => 'Admin Initiated Payments',
+ 'admin_initiated_payments_help' => 'Support entering a payment in the admin portal without an invoice',
+ 'paid_date' => 'Paid Date',
+ 'downloaded_entities' => 'An email will be sent with the PDFs',
+ 'lang_French - Swiss' => 'French - Swiss',
+ 'currency_swazi_lilangeni' => 'Swazi Lilangeni',
+ 'income' => 'Income',
+ 'amount_received_help' => 'Enter a value here if the total amount received was MORE than the invoice amount, or when recording a payment with no invoices. Otherwise this field should be left blank.',
+ 'vendor_phone' => 'Vendor Phone',
);
diff --git a/lang/en/texts.php b/lang/en/texts.php
index de8f17ea971e..a1f4316e3dd1 100644
--- a/lang/en/texts.php
+++ b/lang/en/texts.php
@@ -5124,6 +5124,17 @@ $LANG = array(
'income' => 'Income',
'amount_received_help' => 'Enter a value here if the total amount received was MORE than the invoice amount, or when recording a payment with no invoices. Otherwise this field should be left blank.',
'vendor_phone' => 'Vendor Phone',
+ 'mercado_pago' => 'Mercado Pago',
+ 'mybank' => 'MyBank',
+ 'paypal_paylater' => 'Pay in 4',
+ 'paid_date' => 'Paid Date',
+ 'district' => 'District',
+ 'region' => 'Region',
+ 'county' => 'County',
+ 'tax_details' => 'Tax Details',
+ 'activity_10_online' => ':contact entered payment :payment for invoice :invoice for :client',
+ 'activity_10_manual' => ':user entered payment :payment for invoice :invoice for :client',
+ 'default_payment_type' => 'Default Payment Type',
);
diff --git a/lang/fr_CA/texts.php b/lang/fr_CA/texts.php
index 94189ac73af5..609c4acafd9a 100644
--- a/lang/fr_CA/texts.php
+++ b/lang/fr_CA/texts.php
@@ -4947,8 +4947,8 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette
'click_to_variables' => 'Cliquez ici pour voir toutes les variables',
'ship_to' => 'Livrer à',
'stripe_direct_debit_details' => 'Veuillez transférer dans le compte bancaire indiqué ci-dessus.',
- 'branch_name' => 'Nom de branche',
- 'branch_code' => 'Code de branche',
+ 'branch_name' => 'Nom de succursale',
+ 'branch_code' => 'Code de succursale',
'bank_name' => 'Nom de l\'institution bancaire',
'bank_code' => 'Code de l\'institution bancaire',
'bic' => 'BIC',
@@ -5114,7 +5114,8 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette
'lang_French - Swiss' => 'Français - Suisse',
'currency_swazi_lilangeni' => 'Lilangeni Eswatinien',
'income' => 'Revenu',
-
+ 'amount_received_help' => 'Saisissez une valeur si le montant total reçu été PLUS élevé que le montant de la facture, ou lors de l\'enregistrement d\'un paiement sans factures. Sinon, ce champ devrait rester vide.',
+ 'vendor_phone' => 'Téléphone du fournisseur',
);
diff --git a/lang/pt_BR/texts.php b/lang/pt_BR/texts.php
index 670a7033b49d..4585f228c220 100644
--- a/lang/pt_BR/texts.php
+++ b/lang/pt_BR/texts.php
@@ -4569,7 +4569,7 @@ Quando tiver as quantias, volte a esta página de formas de pagamento e clique "
'activity_134' => ':user restored purchase order :purchase_order',
'activity_135' => ':user emailed purchase order :purchase_order',
'activity_136' => ':contact viewed purchase order :purchase_order',
- 'purchase_order_subject' => 'New Purchase Order :number from :account',
+ 'purchase_order_subject' => 'Novo pedido de compra',
'purchase_order_message' => 'To view your purchase order for :amount, click the link below.',
'view_purchase_order' => 'View Purchase Order',
'purchase_orders_backup_subject' => 'Your purchase orders are ready for download',
@@ -4639,7 +4639,7 @@ Quando tiver as quantias, volte a esta página de formas de pagamento e clique "
'purchase_order_footer' => 'Purchase Order Footer',
'require_purchase_order_signature' => 'Purchase Order Signature',
'require_purchase_order_signature_help' => 'Require vendor to provide their signature.',
- 'new_purchase_order' => 'New Purchase Order',
+ 'new_purchase_order' => 'Novo pedido de compra',
'edit_purchase_order' => 'Edit Purchase Order',
'created_purchase_order' => 'Successfully created purchase order',
'updated_purchase_order' => 'Successfully updated purchase order',
@@ -5108,6 +5108,16 @@ Quando tiver as quantias, volte a esta página de formas de pagamento e clique "
'item_tax_rate2' => 'Item Tax Rate 2',
'item_tax_rate3' => 'Item Tax Rate 3',
'buy_price' => 'Buy Price',
+ 'country_Macedonia' => 'Macedonia',
+ 'admin_initiated_payments' => 'Admin Initiated Payments',
+ 'admin_initiated_payments_help' => 'Support entering a payment in the admin portal without an invoice',
+ 'paid_date' => 'Paid Date',
+ 'downloaded_entities' => 'An email will be sent with the PDFs',
+ 'lang_French - Swiss' => 'French - Swiss',
+ 'currency_swazi_lilangeni' => 'Swazi Lilangeni',
+ 'income' => 'Income',
+ 'amount_received_help' => 'Enter a value here if the total amount received was MORE than the invoice amount, or when recording a payment with no invoices. Otherwise this field should be left blank.',
+ 'vendor_phone' => 'Vendor Phone',
);
diff --git a/phpstan.neon b/phpstan.neon
index 2372c3d259ae..cdfa3b25d911 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -1,19 +1,13 @@
includes:
- ./vendor/nunomaduro/larastan/extension.neon
- - phpstan-baseline.neon
parameters:
- treatPhpDocTypesAsCertain: false
- parallel:
- jobSize: 10
- maximumNumberOfProcesses: 1
- processTimeout: 60.0
- ignoreErrors:
- - '#Call to an undefined method .*badMethod\(\)#'
- - '#Call to an undefined method Illuminate\Database\Eloquent\Builder::exclude#'
level: 4
paths:
- 'app/'
excludePaths:
- 'vendor/'
+ - 'app/Jobs/Ninja/*'
+ - 'app/Console/Commands/*'
+ - 'app/DataMapper/Analytics/*'
universalObjectCratesClasses:
- App\DataMapper\Tax\RuleInterface
\ No newline at end of file
diff --git a/resources/views/portal/ninja2020/components/html-viewer.blade.php b/resources/views/portal/ninja2020/components/html-viewer.blade.php
index 0b1ccdafed00..2841d3a17487 100644
--- a/resources/views/portal/ninja2020/components/html-viewer.blade.php
+++ b/resources/views/portal/ninja2020/components/html-viewer.blade.php
@@ -136,10 +136,12 @@ span {
{{ ctrans('texts.total') }} |
{{ $amount }} |
+ @if(!$is_quote)
{{ ctrans('texts.balance') }} |
{{ $balance }} |
+ @endif
diff --git a/resources/views/portal/ninja2020/gateways/paypal/pay.blade.php b/resources/views/portal/ninja2020/gateways/paypal/pay.blade.php
new file mode 100644
index 000000000000..c94550d3f331
--- /dev/null
+++ b/resources/views/portal/ninja2020/gateways/paypal/pay.blade.php
@@ -0,0 +1,68 @@
+@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.payment_type_credit_card'), 'card_title' => 'PayPal'])
+
+@section('gateway_head')
+
+
+@endsection
+
+@section('gateway_content')
+
+
+
+
+
+
+@endsection
+
+@section('gateway_footer')
+@endsection
+
+@push('footer')
+
+
+
+@endpush
\ No newline at end of file
diff --git a/tests/Feature/Export/ProfitAndLossReportTest.php b/tests/Feature/Export/ProfitAndLossReportTest.php
index 52ba1b37342d..56d634e1beb4 100644
--- a/tests/Feature/Export/ProfitAndLossReportTest.php
+++ b/tests/Feature/Export/ProfitAndLossReportTest.php
@@ -120,6 +120,108 @@ class ProfitAndLossReportTest extends TestCase
$this->account->delete();
}
+ public function testExpenseResolution()
+ {
+ $this->buildData();
+
+ Expense::factory()->create([
+ 'company_id' => $this->company->id,
+ 'user_id' => $this->user->id,
+ 'amount' => 121,
+ 'date' => now()->format('Y-m-d'),
+ 'uses_inclusive_taxes' => true,
+ 'tax_rate1' => 21,
+ 'tax_name1' => 'VAT',
+ 'calculate_tax_by_amount' => false,
+ 'exchange_rate' => 1,
+ ]);
+
+ $pl = new ProfitLoss($this->company, $this->payload);
+ $pl->build();
+
+ $expense_breakdown = $pl->getExpenseBreakDown();
+
+ $this->assertEquals(100, array_sum(array_column($expense_breakdown, 'total')));
+ $this->assertEquals(21, array_sum(array_column($expense_breakdown, 'tax')));
+
+ $this->account->delete();
+
+ }
+
+ public function testMultiCurrencyInvoiceIncome()
+ {
+ $this->buildData();
+
+ $settings = ClientSettings::defaults();
+ $settings->currency_id = 2;
+
+ $client = Client::factory()->create([
+ 'user_id' => $this->user->id,
+ 'company_id' => $this->company->id,
+ 'is_deleted' => 0,
+ 'settings' => $settings
+ ]);
+
+
+ $client2 = Client::factory()->create([
+ 'user_id' => $this->user->id,
+ 'company_id' => $this->company->id,
+ 'is_deleted' => 0,
+ ]);
+
+ Invoice::factory()->create([
+ 'client_id' => $client->id,
+ 'user_id' => $this->user->id,
+ 'company_id' => $this->company->id,
+ 'amount' => 10,
+ 'balance' => 10,
+ 'status_id' => 2,
+ 'total_taxes' => 1,
+ 'date' => now()->format('Y-m-d'),
+ 'terms' => 'nada',
+ 'discount' => 0,
+ 'tax_rate1' => 0,
+ 'tax_rate2' => 0,
+ 'tax_rate3' => 0,
+ 'tax_name1' => '',
+ 'tax_name2' => '',
+ 'tax_name3' => '',
+ 'uses_inclusive_taxes' => false,
+ 'exchange_rate' => 2
+ ]);
+
+ Invoice::factory()->create([
+ 'client_id' => $client2->id,
+ 'user_id' => $this->user->id,
+ 'company_id' => $this->company->id,
+ 'amount' => 10,
+ 'balance' => 10,
+ 'status_id' => 2,
+ 'total_taxes' => 1,
+ 'date' => now()->format('Y-m-d'),
+ 'terms' => 'nada',
+ 'discount' => 0,
+ 'tax_rate1' => 0,
+ 'tax_rate2' => 0,
+ 'tax_rate3' => 0,
+ 'tax_name1' => '',
+ 'tax_name2' => '',
+ 'tax_name3' => '',
+ 'uses_inclusive_taxes' => false,
+ 'exchange_rate' => 1
+ ]);
+
+
+ $pl = new ProfitLoss($this->company, $this->payload);
+ $pl->build();
+
+ $this->assertEquals(13.5, $pl->getIncome());
+ $this->assertEquals(1.5, $pl->getIncomeTaxes());
+
+ $this->account->delete();
+
+ }
+
public function testSimpleInvoiceIncome()
{
$this->buildData();
diff --git a/tests/Feature/Export/ReportCsvGenerationTest.php b/tests/Feature/Export/ReportCsvGenerationTest.php
index e11adfb447f4..53a9c3924255 100644
--- a/tests/Feature/Export/ReportCsvGenerationTest.php
+++ b/tests/Feature/Export/ReportCsvGenerationTest.php
@@ -70,6 +70,60 @@ class ReportCsvGenerationTest extends TestCase
public $cu;
+ private $all_client_report_keys = ["client.name","client.user","client.assigned_user","client.balance","client.paid_to_date","client.currency_id","client.website","client.private_notes","client.industry_id","client.size_id","client.address1","client.address2","client.city","client.state","client.postal_code","client.country_id","contact.custom_value4","client.shipping_address1","client.shipping_address2","client.shipping_city","client.shipping_state","client.shipping_postal_code","client.shipping_country_id","client.payment_terms","client.vat_number","client.id_number","client.public_notes","client.phone","contact.first_name","contact.last_name","contact.email","contact.phone"];
+
+ private $all_payment_report_keys = [
+ 'payment.date',
+ 'payment.amount',
+ 'payment.refunded',
+ 'payment.applied',
+ 'payment.transaction_reference',
+ 'payment.currency',
+ 'payment.exchange_rate',
+ 'payment.number',
+ 'payment.method',
+ 'payment.status',
+ 'payment.private_notes',
+ 'payment.custom_value1',
+ 'payment.custom_value2',
+ 'payment.custom_value3',
+ 'payment.custom_value4',
+ 'payment.user_id',
+ 'payment.assigned_user_id'
+ ];
+
+ private $all_invoice_report_keys = [
+ 'invoice.number',
+ 'invoice.amount',
+ 'invoice.balance',
+ 'invoice.paid_to_date',
+ 'invoice.discount',
+ 'invoice.po_number',
+ 'invoice.date',
+ 'invoice.due_date',
+ 'invoice.terms',
+ 'invoice.footer',
+ 'invoice.status',
+ 'invoice.public_notes',
+ 'invoice.private_notes',
+ 'invoice.uses_inclusive_taxes',
+ 'invoice.is_amount_discount',
+ 'invoice.partial',
+ 'invoice.partial_due_date',
+ 'invoice.custom_value1',
+ 'invoice.custom_value2',
+ 'invoice.custom_value3',
+ 'invoice.custom_value4',
+ 'invoice.custom_surcharge1',
+ 'invoice.custom_surcharge2',
+ 'invoice.custom_surcharge3',
+ 'invoice.custom_surcharge4',
+ 'invoice.exchange_rate',
+ 'invoice.total_taxes',
+ 'invoice.assigned_user_id',
+ 'invoice.user_id',
+ ];
+
/**
* start_date - Y-m-d
end_date - Y-m-d
@@ -329,6 +383,32 @@ class ReportCsvGenerationTest extends TestCase
$this->assertEquals('123456', $this->getFirstValueByColumn($csv, 'Invoice Invoice Number'));
$this->assertEquals(1000, $this->getFirstValueByColumn($csv, 'Invoice Amount'));
+
+ $data = [
+ 'date_range' => 'all',
+ 'report_keys' => $this->all_client_report_keys,
+ 'send_email' => false,
+ ];
+
+
+ $response = $this->withHeaders([
+ 'X-API-SECRET' => config('ninja.api_secret'),
+ 'X-API-TOKEN' => $this->token,
+ ])->post('/api/v1/reports/tasks', $data)->assertStatus(200);
+
+
+ $data = [
+ 'date_range' => 'all',
+ 'report_keys' => array_merge(["task.date","task.number"], $this->all_invoice_report_keys),
+ 'send_email' => false,
+ ];
+
+ $response = $this->withHeaders([
+ 'X-API-SECRET' => config('ninja.api_secret'),
+ 'X-API-TOKEN' => $this->token,
+ ])->post('/api/v1/reports/tasks', $data);
+
+
}
@@ -474,6 +554,33 @@ class ReportCsvGenerationTest extends TestCase
$this->assertEquals(0, $this->getFirstValueByColumn($csv, 'Client Balance'));
$this->assertEquals(100, $this->getFirstValueByColumn($csv, 'Client Paid to Date'));
+
+
+ $data = [
+ 'date_range' => 'all',
+ 'report_keys' => $this->all_client_report_keys,
+ 'send_email' => false,
+ ];
+
+
+ $response = $this->withHeaders([
+ 'X-API-SECRET' => config('ninja.api_secret'),
+ 'X-API-TOKEN' => $this->token,
+ ])->post('/api/v1/reports/payments', $data)->assertStatus(200);
+
+
+
+ $data = [
+ 'date_range' => 'all',
+ 'report_keys' => array_merge(["payment.amount","payment.date"],$this->all_invoice_report_keys),
+ 'send_email' => false,
+ ];
+
+ $response = $this->withHeaders([
+ 'X-API-SECRET' => config('ninja.api_secret'),
+ 'X-API-TOKEN' => $this->token,
+ ])->post('/api/v1/reports/payments', $data);
+
}
@@ -603,6 +710,17 @@ class ReportCsvGenerationTest extends TestCase
$this->assertEquals('Unpaid', $this->getFirstValueByColumn($csv, 'Payment Amount'));
$this->assertEquals('', $this->getFirstValueByColumn($csv, 'Payment Date'));
+ $data = [
+ 'date_range' => 'all',
+ 'report_keys' => $this->all_payment_report_keys,
+ 'send_email' => false,
+ ];
+
+ $response = $this->withHeaders([
+ 'X-API-SECRET' => config('ninja.api_secret'),
+ 'X-API-TOKEN' => $this->token,
+ ])->post('/api/v1/reports/credits', $data)->assertStatus(200);
+
}
public function testInvoiceCustomColumnsCsvGeneration()
@@ -625,7 +743,7 @@ class ReportCsvGenerationTest extends TestCase
$data = [
'date_range' => 'all',
- 'report_keys' => ["client.name","invoice.number","invoice.amount","payment.date", "payment.amount"],
+ 'report_keys' => ["client.name","invoice.number","invoice.amount","payment.date", "payment.amount","invoice.user"],
'send_email' => false,
];
@@ -641,6 +759,33 @@ class ReportCsvGenerationTest extends TestCase
$this->assertEquals('Unpaid', $this->getFirstValueByColumn($csv, 'Payment Amount'));
$this->assertEquals('', $this->getFirstValueByColumn($csv, 'Payment Date'));
+
+ $data = [
+ 'date_range' => 'all',
+ 'report_keys' => $this->all_client_report_keys,
+ 'send_email' => false,
+ ];
+
+
+ $response = $this->withHeaders([
+ 'X-API-SECRET' => config('ninja.api_secret'),
+ 'X-API-TOKEN' => $this->token,
+ ])->post('/api/v1/reports/invoices', $data)->assertStatus(200);
+
+
+
+ $data = [
+ 'date_range' => 'all',
+ 'report_keys' => $this->all_payment_report_keys,
+ 'send_email' => false,
+ ];
+
+ $response = $this->withHeaders([
+ 'X-API-SECRET' => config('ninja.api_secret'),
+ 'X-API-TOKEN' => $this->token,
+ ])->post('/api/v1/reports/invoices', $data)->assertStatus(200);
+
+
}
public function testRecurringInvoiceCustomColumnsCsvGeneration()
@@ -679,6 +824,20 @@ class ReportCsvGenerationTest extends TestCase
$this->assertEquals('1234', $this->getFirstValueByColumn($csv, 'Recurring Invoice Invoice Number'));
$this->assertEquals('Daily', $this->getFirstValueByColumn($csv, 'Recurring Invoice How Often'));
+
+ $data = [
+ 'date_range' => 'all',
+ 'report_keys' => $this->all_client_report_keys,
+ 'send_email' => false,
+ ];
+
+
+ $response = $this->withHeaders([
+ 'X-API-SECRET' => config('ninja.api_secret'),
+ 'X-API-TOKEN' => $this->token,
+ ])->post('/api/v1/reports/recurring_invoices', $data)->assertStatus(200);
+
+
}
@@ -745,6 +904,33 @@ class ReportCsvGenerationTest extends TestCase
$this->assertEquals('GST', $this->getFirstValueByColumn($csv, 'Tax Name 1'));
$this->assertEquals('10', $this->getFirstValueByColumn($csv, 'Tax Rate 1'));
+
+ $data = [
+ 'date_range' => 'all',
+ 'report_keys' => $this->all_client_report_keys,
+ 'send_email' => false,
+ ];
+
+
+ $response = $this->withHeaders([
+ 'X-API-SECRET' => config('ninja.api_secret'),
+ 'X-API-TOKEN' => $this->token,
+ ])->post('/api/v1/reports/invoice_items', $data)->assertStatus(200);
+
+
+ $data = [
+ 'date_range' => 'all',
+ 'report_keys' => $this->all_payment_report_keys,
+ 'send_email' => false,
+ ];
+
+ $response = $this->withHeaders([
+ 'X-API-SECRET' => config('ninja.api_secret'),
+ 'X-API-TOKEN' => $this->token,
+ ])->post('/api/v1/reports/invoice_items', $data)->assertStatus(200);
+
+
+
}
@@ -810,6 +996,20 @@ class ReportCsvGenerationTest extends TestCase
$this->assertEquals('GST', $this->getFirstValueByColumn($csv, 'Tax Name 1'));
$this->assertEquals('10', $this->getFirstValueByColumn($csv, 'Tax Rate 1'));
+
+ $data = [
+ 'date_range' => 'all',
+ 'report_keys' => $this->all_client_report_keys,
+ 'send_email' => false,
+ ];
+
+
+ $response = $this->withHeaders([
+ 'X-API-SECRET' => config('ninja.api_secret'),
+ 'X-API-TOKEN' => $this->token,
+ ])->post('/api/v1/reports/quote_items', $data)->assertStatus(200);
+
+
}
@@ -973,6 +1173,20 @@ class ReportCsvGenerationTest extends TestCase
$this->assertEquals('1234', $this->getFirstValueByColumn($csv, 'Quote Number'));
$this->assertEquals('100', $this->getFirstValueByColumn($csv, 'Quote Amount'));
+
+ $data = [
+ 'date_range' => 'all',
+ 'report_keys' => $this->all_client_report_keys,
+ 'send_email' => false,
+ ];
+
+
+ $response = $this->withHeaders([
+ 'X-API-SECRET' => config('ninja.api_secret'),
+ 'X-API-TOKEN' => $this->token,
+ ])->post('/api/v1/reports/quotes', $data)->assertStatus(200);
+
+
}
@@ -1104,6 +1318,33 @@ class ReportCsvGenerationTest extends TestCase
$this->assertEquals('Public', $this->getFirstValueByColumn($csv, 'Public Notes'));
$this->assertEquals('Private', $this->getFirstValueByColumn($csv, 'Private Notes'));
$this->assertEquals('Terms', $this->getFirstValueByColumn($csv, 'Terms'));
+
+
+ $data = [
+ 'date_range' => 'all',
+ 'report_keys' => $this->all_client_report_keys,
+ 'send_email' => false,
+ ];
+
+
+ $response = $this->withHeaders([
+ 'X-API-SECRET' => config('ninja.api_secret'),
+ 'X-API-TOKEN' => $this->token,
+ ])->post('/api/v1/reports/credits', $data)->assertStatus(200);
+
+
+
+ $data = [
+ 'date_range' => 'all',
+ 'report_keys' => $this->all_payment_report_keys,
+ 'send_email' => false,
+ ];
+
+ $response = $this->withHeaders([
+ 'X-API-SECRET' => config('ninja.api_secret'),
+ 'X-API-TOKEN' => $this->token,
+ ])->post('/api/v1/reports/credits', $data)->assertStatus(200);
+
}
public function testInvoiceCsvGeneration()
@@ -1364,6 +1605,21 @@ class ReportCsvGenerationTest extends TestCase
$this->assertEquals('Private', $this->getFirstValueByColumn($csv, 'Private Notes'));
$this->assertEquals($this->user->present()->name(), $this->getFirstValueByColumn($csv, 'User'));
+
+ $data = [
+ 'date_range' => 'all',
+ 'report_keys' => $this->all_client_report_keys,
+ 'send_email' => false,
+ ];
+
+
+ $response = $this->withHeaders([
+ 'X-API-SECRET' => config('ninja.api_secret'),
+ 'X-API-TOKEN' => $this->token,
+ ])->post('/api/v1/reports/expenses', $data)->assertStatus(200);
+
+
+
}
public function testExpenseCustomColumnsCsvGeneration()