1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-10 21:22:58 +01:00

Merge pull request #6829 from turbo124/v5-develop

Minor fixes for Statements
This commit is contained in:
David Bomba 2021-10-13 14:16:47 +11:00 committed by GitHub
commit 5118a01e7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 497 additions and 92 deletions

View File

@ -496,6 +496,7 @@ class DesignController extends BaseController
$company = auth()->user()->getCompany();
$design = Design::where('company_id', $company->id)
->orWhereNull('company_id')
->where('id', $design_id)
->exists();

View File

@ -204,6 +204,7 @@ class PreviewController extends BaseController
if($request->has('entity_id')){
$entity_obj = $class::on(config('database.default'))
->with('client.company')
->where('id', $this->decodePrimaryKey($request->input('entity_id')))
->where('company_id', $company->id)
->withTrashed()
@ -216,11 +217,11 @@ class PreviewController extends BaseController
if(!$request->has('entity_id'))
$entity_obj->service()->fillDefaults()->save();
$entity_obj->load('client.contacts','company');
// $entity_obj->load('client.contacts','client.company');
App::forgetInstance('translator');
$t = app('translator');
App::setLocale($entity_obj->client->contacts()->first()->preferredLocale());
App::setLocale($entity_obj->client->locale());
$t->replace(Ninja::transformTranslations($entity_obj->client->getMergedSettings()));
$html = new HtmlEngine($entity_obj->invitations()->first());

View File

@ -268,7 +268,7 @@ class BillingPortalPurchase extends Component
$client = $client_repo->save($data, ClientFactory::create($company->id, $user->id));
return $client->contacts->first();
return $client->fresh()->contacts->first();
}
/**

View File

@ -39,8 +39,6 @@ class StorePaymentRequest extends Request
{
$input = $this->all();
// nlog(print_r($input,1));
$invoices_total = 0;
$credits_total = 0;

View File

@ -51,6 +51,6 @@ class CreateStatementRequest extends Request
public function client(): ?Client
{
return Client::where('id', $this->client_id)->first();
return Client::with('company')->where('id', $this->client_id)->withTrashed()->first();
}
}

View File

@ -336,6 +336,14 @@ class CompanyExport implements ShouldQueue
})->all();
$this->export_data['recurring_expenses'] = $this->company->recurring_expenses->map(function ($expense){
$expense = $this->transformBasicEntities($expense);
$expense = $this->transformArrayOfKeys($expense, ['vendor_id', 'invoice_id', 'client_id', 'category_id', 'project_id']);
return $expense->makeVisible(['id']);
})->all();
$this->export_data['recurring_invoices'] = $this->company->recurring_invoices->makeVisible(['id'])->map(function ($ri){

View File

@ -47,6 +47,7 @@ use App\Models\Product;
use App\Models\Project;
use App\Models\Quote;
use App\Models\QuoteInvitation;
use App\Models\RecurringExpense;
use App\Models\RecurringInvoice;
use App\Models\RecurringInvoiceInvitation;
use App\Models\Subscription;
@ -76,6 +77,8 @@ use ZipArchive;
use ZipStream\Option\Archive;
use ZipStream\ZipStream;
use function GuzzleHttp\json_encode;
class CompanyImport implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, MakesHash, GeneratesCounter;
@ -135,6 +138,7 @@ class CompanyImport implements ShouldQueue
'quote_invitations',
'credits',
'credit_invitations',
'recurring_expenses',
'expenses',
'tasks',
'payments',
@ -452,6 +456,26 @@ class CompanyImport implements ShouldQueue
}
private function import_recurring_expenses()
{
//unset / transforms / object_property / match_key
$this->genericImport(RecurringExpense::class,
['assigned_user_id', 'user_id', 'client_id', 'company_id', 'id', 'hashed_id', 'project_id', 'vendor_id'],
[
['users' => 'user_id'],
['users' => 'assigned_user_id'],
['clients' => 'client_id'],
['projects' => 'project_id'],
['vendors' => 'vendor_id'],
['invoices' => 'invoice_id'],
['expense_categories' => 'category_id'],
],
'expenses',
'number');
return $this;
}
private function import_payment_terms()
{
@ -795,6 +819,8 @@ class CompanyImport implements ShouldQueue
['projects' => 'project_id'],
['vendors' => 'vendor_id'],
['invoices' => 'invoice_id'],
['recurring_expenses' => 'recurring_expense_id'],
['expense_categories' => 'category_id'],
],
'expenses',
'number');
@ -1248,6 +1274,7 @@ class CompanyImport implements ShouldQueue
if($class == 'App\Models\Subscription'){
$obj_array['product_ids'] = $this->recordProductIds($obj_array['product_ids']);
$obj_array['recurring_product_ids'] = $this->recordProductIds($obj_array['recurring_product_ids']);
$obj_array['webhook_configuration'] = json_encode($obj_array['webhook_configuration']);
}
$new_obj = $class::firstOrNew(
@ -1297,7 +1324,8 @@ class CompanyImport implements ShouldQueue
if($class == 'App\Models\Subscription'){
//$obj_array['product_ids'] = $this->recordProductIds($obj_array['product_ids']);
//$obj_array['recurring_product_ids'] = $this->recordProductIds($obj_array['recurring_product_ids']);
//
// $obj_array['webhook_configuration'] = json_encode($obj_array['webhook_configuration']);
$obj_array['webhook_configuration'] = '';
$obj_array['recurring_product_ids'] = '';
$obj_array['product_ids'] = '';
}

View File

@ -46,7 +46,7 @@ class PaymentEmailEngine extends BaseEmailEngine
$this->payment = $payment;
$this->company = $payment->company;
$this->client = $payment->client;
$this->contact = $contact ?: $this->client->primary_contact()->first();
$this->contact = $contact ?: $this->client->contacts()->first();
$this->contact->load('client.company');
$this->settings = $this->client->getMergedSettings();
$this->template_data = $template_data;

View File

@ -61,7 +61,7 @@ class Gateway extends StaticModel
$link = 'https://bitpay.com/dashboard/signup';
} elseif ($this->id == 18) {
$link = 'https://applications.sagepay.com/apply/2C02C252-0F8A-1B84-E10D-CF933EFCAA99';
} elseif ($this->id == 20) {
} elseif ($this->id == 20 || $this->id == 56) {
$link = 'https://dashboard.stripe.com/account/apikeys';
}
@ -89,17 +89,20 @@ class Gateway extends StaticModel
break;
case 7:
return [
GatewayType::CREDIT_CARD => ['refund' => false, 'token_billing' => true], // Mollie
GatewayType::BANK_TRANSFER => ['refund' => false, 'token_billing' => true],
GatewayType::KBC => ['refund' => false, 'token_billing' => false],
GatewayType::BANCONTACT => ['refund' => false, 'token_billing' => false],
GatewayType::IDEAL => ['refund' => false, 'token_billing' => false],
GatewayType::CREDIT_CARD => ['refund' => false, 'token_billing' => true, 'webhooks' => [' ']], // Mollie
GatewayType::BANK_TRANSFER => ['refund' => false, 'token_billing' => true, 'webhooks' => [' ']],
GatewayType::KBC => ['refund' => false, 'token_billing' => false, 'webhooks' => [' ']],
GatewayType::BANCONTACT => ['refund' => false, 'token_billing' => false, 'webhooks' => [' ']],
GatewayType::IDEAL => ['refund' => false, 'token_billing' => false, 'webhooks' => [' ']],
];
case 15:
return [GatewayType::PAYPAL => ['refund' => true, 'token_billing' => false]]; //Paypal
return [
GatewayType::PAYPAL => ['refund' => true, 'token_billing' => false]
]; //Paypal
break;
case 20:
return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true],
return [
GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true],
GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable','charge.succeeded']],
GatewayType::ALIPAY => ['refund' => false, 'token_billing' => false],
GatewayType::APPLE_PAY => ['refund' => false, 'token_billing' => false],
@ -109,30 +112,41 @@ class Gateway extends StaticModel
GatewayType::GIROPAY => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']],
GatewayType::EPS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']],
GatewayType::BANCONTACT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']],
GatewayType::IDEAL => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']]];
GatewayType::IDEAL => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']],
];
case 39:
return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true]]; //Checkout
return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true, 'webhooks' => [' ']]]; //Checkout
break;
case 46:
return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true]]; //Paytrace
case 49:
return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true],
GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true]]; //WePay
return [
GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true],
GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true, 'webhooks' => [' ']]
]; //WePay
break;
case 50:
return [
GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true], //Braintree
GatewayType::PAYPAL => ['refund' => true, 'token_billing' => true],
GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true],
GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true, 'webhooks' => [' ']],
];
break;
case 56:
return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true],
return [
GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true],
GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable','charge.succeeded']],
GatewayType::ALIPAY => ['refund' => false, 'token_billing' => false],
GatewayType::APPLE_PAY => ['refund' => false, 'token_billing' => false],
GatewayType::SOFORT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']]]; //Stripe
GatewayType::SOFORT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']], //Stripe
GatewayType::SEPA => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']],
GatewayType::PRZELEWY24 => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']],
GatewayType::GIROPAY => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']],
GatewayType::EPS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']],
GatewayType::BANCONTACT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']],
GatewayType::IDEAL => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']],
];
break;
case 57:
return [
@ -141,12 +155,12 @@ class Gateway extends StaticModel
break;
case 52:
return [
GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true] // GoCardless
GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true, 'webhooks' => [' ']] // GoCardless
];
break;
case 58:
return [
GatewayType::HOSTED_PAGE => ['refund' => false, 'token_billing' => false] // Razorpay
GatewayType::HOSTED_PAGE => ['refund' => false, 'token_billing' => false, 'webhooks' => [' ']] // Razorpay
];
break;
default:

View File

@ -403,10 +403,8 @@ class BaseDriver extends AbstractPaymentDriver
});
}
SystemLogger::dispatch(
$gateway->payment_hash,
SystemLog::CATEGORY_GATEWAY_RESPONSE,
@ -620,21 +618,27 @@ class BaseDriver extends AbstractPaymentDriver
{
$types = [];
// if($type == GatewayType::BANK_TRANSFER && $this->company_gateway->fees_and_limits->{GatewayType::BANK_TRANSFER}->is_enabled)
// {
// $types[] = $type;
// }
// elseif($type == GatewayType::CREDIT_CARD && $this->company_gateway->fees_and_limits->{GatewayType::CREDIT_CARD}->is_enabled)
// {
// $types[] = $type;
// }
$types[] = GatewayType::CREDIT_CARD;
$types[] = GatewayType::BANK_TRANSFER;
return $types;
}
/**
* Generic description handler
*/
public function getDescription(bool $abbreviated = true)
{
if(!$this->payment_hash)
return "";
if($abbreviated)
return \implode(', ', collect($this->payment_hash->invoices())->pluck('invoice_number')->toArray());
return sprintf('%s: %s', ctrans('texts.invoices'), \implode(', ', collect($this->payment_hash->invoices())->pluck('invoice_number')->toArray()));
}
public function disconnect()
{
return true;

View File

@ -88,7 +88,6 @@ class CreditCard
'raw_value' => $request->raw_value,
'currency' => $request->currency,
'payment_hash' => $request->payment_hash,
'reference' => $request->payment_hash,
'client_id' => $this->checkout->client->id,
];
@ -134,9 +133,10 @@ class CreditCard
private function completePayment($method, PaymentResponseRequest $request)
{
$payment = new Payment($method, $this->checkout->payment_hash->data->currency);
$payment->amount = $this->checkout->payment_hash->data->value;
$payment->reference = $this->checkout->payment_hash->data->reference;
$payment->reference = $this->checkout->getDescription();
$this->checkout->payment_hash->data = array_merge((array)$this->checkout->payment_hash->data, ['checkout_payment_ref' => $payment]);
$this->checkout->payment_hash->save();

View File

@ -243,27 +243,18 @@ class CheckoutComPaymentDriver extends BaseDriver
$amount = array_sum(array_column($payment_hash->invoices(), 'amount')) + $payment_hash->fee_total;
$invoice = Invoice::whereIn('id', $this->transformKeys(array_column($payment_hash->invoices(), 'invoice_id')))->withTrashed()->first();
if ($invoice) {
$description = "Invoice {$invoice->number} for {$amount} for client {$this->client->present()->name()}";
} else {
$description = "Payment with no invoice for amount {$amount} for client {$this->client->present()->name()}";
}
$this->init();
$method = new IdSource($cgt->token);
$payment = new \Checkout\Models\Payments\Payment($method, $this->client->getCurrencyCode());
$payment->amount = $this->convertToCheckoutAmount($amount, $this->client->getCurrencyCode());
//$payment->reference = $cgt->meta->last4 . '-' . now();
$payment->reference = $invoice->number . '-' . now();
$request = new PaymentResponseRequest();
$request->setMethod('POST');
$request->request->add(['payment_hash' => $payment_hash->hash]);
//$this->setPaymentHash($payment_hash);
try {
$response = $this->gateway->payments()->request($payment);

View File

@ -162,7 +162,7 @@ class ImportCustomers
if(strlen($this->stripe->company_gateway->getConfigField('account_id')) < 1)
throw new StripeConnectFailure('Stripe Connect has not been configured');
$customer = Customer::retrieve($customer_id, $this->stripe_connect_auth);
$customer = Customer::retrieve($customer_id, $this->stripe->stripe_connect_auth);
if(!$customer)
return;

View File

@ -89,11 +89,11 @@ class PaymentRepository extends BaseRepository {
if (array_key_exists('credits', $data) && is_array($data['credits']) && count($data['credits']) > 0) {
$_credit_totals = array_sum(array_column($data['credits'], 'amount'));
if ($data['amount'] == $_credit_totals) {
$data['amount'] = 0;
} else {
// if ($data['amount'] == $_credit_totals) {
// $data['amount'] = 0;
// } else {
$client->service()->updatePaidToDate($_credit_totals)->save();
}
// }
}
}
@ -170,11 +170,6 @@ class PaymentRepository extends BaseRepository {
event( new PaymentWasCreated( $payment, $payment->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null) ) );
}
// nlog("payment amount = {$payment->amount}");
// nlog("payment applied = {$payment->applied}");
// nlog("invoice totals = {$invoice_totals}");
// nlog("credit totals = {$credit_totals}");
$payment->applied += ($invoice_totals - $credit_totals); //wont work because - check tests
// $payment->applied += $invoice_totals; //wont work because - check tests

View File

@ -73,7 +73,7 @@ class Statement
$state = [
'template' => $template->elements([
'client' => $this->entity->client,
'client' => $this->client,
'entity' => $this->entity,
'pdf_variables' => (array)$this->entity->company->settings->pdf_variables,
'$product' => $this->getDesign()->design->product,
@ -219,7 +219,7 @@ class Statement
*/
protected function getInvoices(): Collection
{
return Invoice::where('company_id', $this->client->company->id)
return Invoice::where('company_id', $this->client->company_id)
->where('client_id', $this->client->id)
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL, Invoice::STATUS_PAID])
->whereBetween('date', [$this->options['start_date'], $this->options['end_date']])
@ -234,7 +234,8 @@ class Statement
*/
protected function getPayments(): Collection
{
return Payment::where('company_id', $this->client->company->id)
return Payment::with('client.country','invoices')
->where('company_id', $this->client->company_id)
->where('client_id', $this->client->id)
->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])
->whereBetween('date', [$this->options['start_date'], $this->options['end_date']])
@ -285,18 +286,15 @@ class Statement
$from = $ranges[0];
$to = $ranges[1];
$client = Client::where('id', $this->client->id)->first();
$amount = Invoice::where('company_id', $this->client->company->id)
->where('client_id', $client->id)
$amount = Invoice::where('client_id', $this->client->id)
->where('company_id', $this->client->company_id)
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
->where('balance', '>', 0)
->where('is_deleted', 0)
->whereBetween('date', [$to, $from])
->whereBetween('due_date', [$to, $from])
->sum('balance');
return Number::formatMoney($amount, $client);
return Number::formatMoney($amount, $this->client);
}
/**
@ -328,7 +326,7 @@ class Statement
return $ranges;
case '120+':
$ranges[0] = now()->startOfDay()->subDays(120);
$ranges[1] = now()->startOfDay()->subYears(40);
$ranges[1] = now()->startOfDay()->subYears(20);
return $ranges;
default:
$ranges[0] = now()->startOfDay()->subDays(0);

View File

@ -449,6 +449,8 @@ class InvoiceService
public function fillDefaults()
{
$this->invoice->load('client.company');
$settings = $this->invoice->client->getMergedSettings();
if (! $this->invoice->design_id)

View File

@ -58,11 +58,17 @@ class MarkPaid extends AbstractService
$payment->transaction_reference = ctrans('texts.manual_entry');
$payment->currency_id = $this->invoice->client->getSetting('currency_id');
$payment->is_manual = true;
/* Create a payment relationship to the invoice entity */
$payment_type_id = $this->invoice->client->getSetting('payment_type_id');
if((int)$payment_type_id > 0)
$payment->type_id = (int)$payment_type_id;
$payment->save();
$this->setExchangeRate($payment);
/* Create a payment relationship to the invoice entity */
$payment->invoices()->attach($this->invoice->id, [
'amount' => $payment->amount,
]);

View File

@ -195,15 +195,15 @@ class Design extends BaseDesign
if ($this->type == self::DELIVERY_NOTE) {
$elements = [
['element' => 'p', 'content' => ctrans('texts.delivery_note'), 'properties' => ['data-ref' => 'delivery_note-label', 'style' => 'font-weight: bold; text-transform: uppercase']],
['element' => 'p', 'content' => $this->entity->client->name, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-client.name']],
['element' => 'p', 'content' => $this->entity->client->shipping_address1, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-client.shipping_address1']],
['element' => 'p', 'content' => $this->entity->client->shipping_address2, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-client.shipping_address2']],
['element' => 'p', 'content' => $this->client->name, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-client.name']],
['element' => 'p', 'content' => $this->client->shipping_address1, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-client.shipping_address1']],
['element' => 'p', 'content' => $this->client->shipping_address2, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-client.shipping_address2']],
['element' => 'p', 'show_empty' => false, 'elements' => [
['element' => 'span', 'content' => "{$this->entity->client->shipping_city} ", 'properties' => ['ref' => 'delivery_note-client.shipping_city']],
['element' => 'span', 'content' => "{$this->entity->client->shipping_state} ", 'properties' => ['ref' => 'delivery_note-client.shipping_state']],
['element' => 'span', 'content' => "{$this->entity->client->shipping_postal_code} ", 'properties' => ['ref' => 'delivery_note-client.shipping_postal_code']],
['element' => 'span', 'content' => "{$this->client->shipping_city} ", 'properties' => ['ref' => 'delivery_note-client.shipping_city']],
['element' => 'span', 'content' => "{$this->client->shipping_state} ", 'properties' => ['ref' => 'delivery_note-client.shipping_state']],
['element' => 'span', 'content' => "{$this->client->shipping_postal_code} ", 'properties' => ['ref' => 'delivery_note-client.shipping_postal_code']],
]],
['element' => 'p', 'content' => optional($this->entity->client->shipping_country)->name, 'show_empty' => false],
['element' => 'p', 'content' => optional($this->client->shipping_country)->name, 'show_empty' => false],
];
if (!is_null($this->context['contact'])) {
@ -232,7 +232,7 @@ class Design extends BaseDesign
]],
['element' => 'tr', 'properties' => [], 'elements' => [
['element' => 'th', 'properties' => [], 'content' => '$balance_due_label'],
['element' => 'th', 'properties' => [], 'content' => Number::formatMoney($this->invoices->sum('balance'), $this->entity->client)],
['element' => 'th', 'properties' => [], 'content' => Number::formatMoney($this->invoices->sum('balance'), $this->client)],
]],
];
}
@ -363,10 +363,10 @@ class Design extends BaseDesign
$element = ['element' => 'tr', 'elements' => []];
$element['elements'][] = ['element' => 'td', 'content' => $invoice->number];
$element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($invoice->date, $invoice->client->date_format(), $invoice->client->locale()) ?: '&nbsp;'];
$element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($invoice->due_date, $invoice->client->date_format(), $invoice->client->locale()) ?: '&nbsp;'];
$element['elements'][] = ['element' => 'td', 'content' => Number::formatMoney($invoice->amount, $invoice->client) ?: '&nbsp;'];
$element['elements'][] = ['element' => 'td', 'content' => Number::formatMoney($invoice->balance, $invoice->client) ?: '&nbsp;'];
$element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($invoice->date, $this->client->date_format(), $this->client->locale()) ?: '&nbsp;'];
$element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($invoice->due_date, $this->client->date_format(), $this->client->locale()) ?: '&nbsp;'];
$element['elements'][] = ['element' => 'td', 'content' => Number::formatMoney($invoice->amount, $this->client) ?: '&nbsp;'];
$element['elements'][] = ['element' => 'td', 'content' => Number::formatMoney($invoice->balance, $this->client) ?: '&nbsp;'];
$tbody[] = $element;
}
@ -386,7 +386,7 @@ class Design extends BaseDesign
$outstanding = $this->invoices->sum('balance');
return [
['element' => 'p', 'content' => '$outstanding_label: ' . Number::formatMoney($outstanding, $this->entity->client)],
['element' => 'p', 'content' => '$outstanding_label: ' . Number::formatMoney($outstanding, $this->client)],
];
}
@ -412,9 +412,9 @@ class Design extends BaseDesign
$element = ['element' => 'tr', 'elements' => []];
$element['elements'][] = ['element' => 'td', 'content' => $invoice->number];
$element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($payment->date, $payment->client->date_format(), $payment->client->locale()) ?: '&nbsp;'];
$element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($payment->date, $this->client->date_format(), $this->client->locale()) ?: '&nbsp;'];
$element['elements'][] = ['element' => 'td', 'content' => $payment->type ? $payment->type->name : ctrans('texts.manual_entry')];
$element['elements'][] = ['element' => 'td', 'content' => Number::formatMoney($payment->amount, $payment->client) ?: '&nbsp;'];
$element['elements'][] = ['element' => 'td', 'content' => Number::formatMoney($payment->amount, $this->client) ?: '&nbsp;'];
$tbody[] = $element;
}
@ -439,7 +439,7 @@ class Design extends BaseDesign
$payment = $this->payments->first();
return [
['element' => 'p', 'content' => \sprintf('%s: %s', ctrans('texts.amount_paid'), Number::formatMoney($this->payments->sum('amount'), $payment->client))],
['element' => 'p', 'content' => \sprintf('%s: %s', ctrans('texts.amount_paid'), Number::formatMoney($this->payments->sum('amount'), $this->client))],
];
}
@ -626,7 +626,7 @@ class Design extends BaseDesign
['element' => 'p', 'content' => strtr($_variables['values']['$entity.public_notes'], $_variables), 'properties' => ['data-ref' => 'total_table-public_notes', 'style' => 'text-align: left;']],
['element' => 'p', 'content' => '', 'properties' => ['style' => 'text-align: left; display: flex; flex-direction: column;'], 'elements' => [
['element' => 'span', 'content' => '$entity.terms_label: ', 'properties' => ['hidden' => $this->entityVariableCheck('$entity.terms'), 'data-ref' => 'total_table-terms-label', 'style' => 'font-weight: bold; text-align: left; margin-top: 1rem;']],
['element' => 'span', 'content' => strtr($_variables['values']['$entity.terms'], $_variables), 'properties' => ['data-ref' => 'total_table-terms', 'style' => 'text-align: left;']],
['element' => 'span', 'content' => strtr($_variables['values']['$entity.terms'], $_variables['labels']), 'properties' => ['data-ref' => 'total_table-terms', 'style' => 'text-align: left;']],
]],
['element' => 'img', 'properties' => ['style' => 'max-width: 50%; height: auto;', 'src' => '$contact.signature', 'id' => 'contact-signature']],
['element' => 'div', 'properties' => ['style' => 'margin-top: 1.5rem; display: flex; align-items: flex-start;'], 'elements' => [

View File

@ -693,6 +693,8 @@ class SubscriptionService
public function convertInvoiceToRecurring($client_id) :RecurringInvoice
{
$client = Client::find($client_id);
$subscription_repo = new SubscriptionRepository();
$recurring_invoice = RecurringInvoiceFactory::create($this->subscription->company_id, $this->subscription->user_id);
@ -702,10 +704,23 @@ class SubscriptionService
$recurring_invoice->frequency_id = $this->subscription->frequency_id ?: RecurringInvoice::FREQUENCY_MONTHLY;
$recurring_invoice->date = now();
$recurring_invoice->remaining_cycles = -1;
$recurring_invoice->auto_bill = $client->getSetting('auto_bill');
$recurring_invoice->auto_bill_enabled = $this->setAutoBillFlag($recurring_invoice->auto_bill);
$recurring_invoice->due_date_days = 'terms';
return $recurring_invoice;
}
private function setAutoBillFlag($auto_bill)
{
if ($auto_bill == 'always' || $auto_bill == 'optout') {
return true;
}
return false;
}
/**
* Hit a 3rd party API if defined in the subscription
*

View File

@ -0,0 +1,290 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace Tests\Feature;
use App\DataMapper\ClientSettings;
use App\Factory\ClientFactory;
use App\Factory\CreditFactory;
use App\Factory\InvoiceFactory;
use App\Factory\InvoiceItemFactory;
use App\Factory\PaymentFactory;
use App\Helpers\Invoice\InvoiceSum;
use App\Models\Client;
use App\Models\ClientContact;
use App\Models\Credit;
use App\Models\Invoice;
use App\Models\Payment;
use App\Utils\Traits\MakesHash;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Foundation\Testing\WithoutEvents;
use Illuminate\Routing\Middleware\ThrottleRequests;
use Illuminate\Support\Facades\Session;
use Illuminate\Validation\ValidationException;
use Tests\MockAccountData;
use Tests\MockUnitData;
use Tests\TestCase;
/**
* @test
*/
class CreditPaymentTest extends TestCase
{
use MakesHash;
use DatabaseTransactions;
use MockUnitData;
use WithoutEvents;
public function setUp() :void
{
parent::setUp();
Session::start();
$this->faker = \Faker\Factory::create();
Model::reguard();
$this->makeTestData();
$this->withoutExceptionHandling();
$this->withoutMiddleware(
ThrottleRequests::class
);
}
public function testRegularPayment()
{
$invoice = Invoice::factory()->create(['user_id' => $this->user->id, 'company_id' => $this->company->id, 'client_id' => $this->client->id]);
$invoice->line_items = $this->buildLineItems();
$invoice->uses_inclusive_taxes = false;
$invoice->discount = 0;
$invoice->tax_rate1 = 0;
$invoice->tax_name1 = '';
$invoice->tax_rate2 = 0;
$invoice->tax_name2 = '';
$invoice_calc = new InvoiceSum($invoice);
$invoice_calc->build();
$invoice = $invoice_calc->getInvoice();
$invoice->setRelation('client', $this->client);
$invoice->setRelation('company', $this->company);
$invoice->service()->markSent()->save();
$data = [
'amount' => 0,
'client_id' => $this->client->hashed_id,
'invoices' => [
[
'invoice_id' => $invoice->hashed_id,
'amount' => 10,
],
],
'date' => '2019/12/12',
];
$response = false;
try {
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/payments/', $data);
} catch (ValidationException $e) {
$message = json_decode($e->validator->getMessageBag(), 1);
nlog($e->validator->getMessageBag());
}
$response->assertStatus(200);
$arr = $response->json();
$payment_id = $arr['data']['id'];
$payment = Payment::whereId($this->decodePrimaryKey($payment_id))->first();
$this->assertEquals($payment->amount, 10);
$this->assertEquals($payment->applied, 10);
}
public function testCreditPayments()
{
$invoice = Invoice::factory()->create(['user_id' => $this->user->id, 'company_id' => $this->company->id, 'client_id' => $this->client->id]);
$invoice->line_items = $this->buildLineItems();
$invoice->uses_inclusive_taxes = false;
$invoice->discount = 0;
$invoice->tax_rate1 = 0;
$invoice->tax_name1 = '';
$invoice->tax_rate2 = 0;
$invoice->tax_name2 = '';
$invoice_calc = new InvoiceSum($invoice);
$invoice_calc->build();
$invoice = $invoice_calc->getInvoice();
$invoice->setRelation('client', $this->client);
$invoice->setRelation('company', $this->company);
$invoice->service()->markSent()->save();
$credit = Credit::factory()->create(['user_id' => $this->user->id, 'company_id' => $this->company->id, 'client_id' => $this->client->id]);
$credit->line_items = $this->buildLineItems();
$credit->uses_inclusive_taxes = false;
$credit->discount = 0;
$credit->tax_rate1 = 0;
$credit->tax_name1 = '';
$credit->tax_rate2 = 0;
$credit->tax_name2 = '';
// $invoice->save();
$invoice_calc = new InvoiceSum($credit);
$invoice_calc->build();
$credit = $invoice_calc->getCredit();
$credit->setRelation('client', $this->client);
$credit->setRelation('company', $this->company);
$credit->service()->markSent()->save();
$data = [
'amount' => 0,
'client_id' => $this->client->hashed_id,
'invoices' => [
[
'invoice_id' => $invoice->hashed_id,
'amount' => 10,
],
],
'credits' => [
[
'credit_id' => $credit->hashed_id,
'amount' => 5
]
],
'date' => '2019/12/12',
];
$response = false;
try {
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/payments/', $data);
} catch (ValidationException $e) {
$message = json_decode($e->validator->getMessageBag(), 1);
nlog($e->validator->getMessageBag());
}
$response->assertStatus(200);
$arr = $response->json();
$payment_id = $arr['data']['id'];
$payment = Payment::whereId($this->decodePrimaryKey($payment_id))->first();
$this->assertEquals($payment->amount, 5);
$this->assertEquals($payment->applied, 5);
}
/*
public function testDoublePaymentTestWithInvalidAmounts()
{
$data = [
'amount' => 15.0,
'client_id' => $this->encodePrimaryKey($client->id),
'invoices' => [
[
'invoice_id' => $this->encodePrimaryKey($this->invoice->id),
'amount' => 10,
],
],
'date' => '2019/12/12',
];
$response = false;
try {
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/payments/', $data);
} catch (ValidationException $e) {
$message = json_decode($e->validator->getMessageBag(), 1);
\Log::error(print_r($e->validator->getMessageBag(), 1));
}
$response->assertStatus(200);
$arr = $response->json();
$payment_id = $arr['data']['id'];
$payment = Payment::whereId($this->decodePrimaryKey($payment_id))->first();
$this->assertEquals($payment->amount, 15);
$this->assertEquals($payment->applied, 10);
$this->invoice = null;
$this->invoice = InvoiceFactory::create($this->company->id, $this->user->id); //stub the company and user_id
$this->invoice->client_id = $client->id;
$this->invoice->line_items = $this->buildLineItems();
$this->invoice->uses_inclusive_taxes = false;
$this->invoice->save();
$this->invoice_calc = new InvoiceSum($this->invoice);
$this->invoice_calc->build();
$this->invoice = $this->invoice_calc->getInvoice();
$this->invoice->save();
$this->invoice->service()->markSent()->save();
$data = [
'amount' => 15.0,
'client_id' => $this->encodePrimaryKey($client->id),
'invoices' => [
[
'invoice_id' => $this->encodePrimaryKey($this->invoice->id),
'amount' => 10,
],
],
'date' => '2019/12/12',
];
$response = false;
try {
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->put('/api/v1/payments/'.$this->encodePrimaryKey($payment->id), $data);
} catch (ValidationException $e) {
$message = json_decode($e->validator->getMessageBag(), 1);
$this->assertTrue(array_key_exists('invoices', $message));
}
}
*/
}

View File

@ -185,7 +185,7 @@ class RecurringExpenseApiTest extends TestCase
])->post('/api/v1/recurring_expenses/bulk?action=start', $data);
$arr = $response->json();
nlog($arr);
$this->assertEquals(RecurringInvoice::STATUS_ACTIVE, $arr['data'][0]['status_id']);
}
@ -207,7 +207,7 @@ nlog($arr);
])->post('/api/v1/recurring_expenses/bulk?action=stop', $data);
$arr = $response->json();
nlog($arr);
$this->assertEquals(RecurringInvoice::STATUS_PAUSED, $arr['data'][0]['status_id']);
}

View File

@ -311,9 +311,9 @@ class AuthorizeTest extends TestCase
$response = $controller->executeWithApiResponse(\net\authorize\api\constants\ANetEnvironment::SANDBOX);
// nlog($response);
nlog($response->getTransactionResponse()->getMessages() !== null);
nlog($response->getTransactionResponse()->getMessages());
nlog($response->getTransactionResponse()->getMessages()[0]);
// nlog($response->getTransactionResponse()->getMessages() !== null);
// nlog($response->getTransactionResponse()->getMessages());
// nlog($response->getTransactionResponse()->getMessages()[0]);
//nlog($response->getTransactionResponse()->getMessages()[0]->getCode());
$code = '';

View File

@ -11,10 +11,14 @@
namespace Tests;
use App\DataMapper\CompanySettings;
use App\DataMapper\DefaultSettings;
use App\Factory\InvoiceItemFactory;
use App\Models\Account;
use App\Models\Client;
use App\Models\ClientContact;
use App\Models\Company;
use App\Models\CompanyToken;
use App\Models\User;
/**
* Class MockUnitData.
@ -33,6 +37,8 @@ trait MockUnitData
public $primary_contact;
public $token;
public function makeTestData()
{
@ -49,6 +55,39 @@ trait MockUnitData
'account_id' => $this->account->id
]);
$userPermissions = collect([
'view_invoice',
'view_client',
'edit_client',
'edit_invoice',
'create_invoice',
'create_client',
]);
$userSettings = DefaultSettings::userSettings();
$this->user->companies()->attach($this->company->id, [
'account_id' => $this->account->id,
'is_owner' => 1,
'is_admin' => 1,
'notifications' => CompanySettings::notificationDefaults(),
'permissions' => $userPermissions->toJson(),
'settings' => json_encode($userSettings),
'is_locked' => 0,
]);
$this->token = \Illuminate\Support\Str::random(64);
$company_token = new CompanyToken;
$company_token->user_id = $this->user->id;
$company_token->company_id = $this->company->id;
$company_token->account_id = $this->account->id;
$company_token->name = 'test token';
$company_token->token = $this->token;
$company_token->is_system = true;
$company_token->save();
$this->client = Client::factory()->create([
'user_id' => $this->user->id,
'company_id' => $this->company->id
@ -68,4 +107,17 @@ trait MockUnitData
]);
}
public function buildLineItems()
{
$line_items = [];
$item = InvoiceItemFactory::create();
$item->quantity = 1;
$item->cost = 10;
$line_items[] = $item;
return $line_items;
}
}

View File

@ -275,7 +275,9 @@ class InvoiceInclusiveTest extends TestCase
$this->assertEquals($this->invoice_calc->getSubTotal(), 19);
$this->assertEquals($this->invoice_calc->getTotalDiscount(), 0.95);
$this->assertEquals($this->invoice_calc->getTotalTaxes(), 4.92);
nlog($this->invoice_calc->getTaxMap());
// nlog($this->invoice_calc->getTaxMap());
$this->assertEquals(count($this->invoice_calc->getTaxMap()), 1);
$this->assertEquals($this->invoice_calc->getTotal(), 18.05);
$this->assertEquals($this->invoice_calc->getBalance(), 18.05);