* Refactor company properties to be presented from settings object instead of company properties

* Working on Email Tests

* Working on emails

* Working on email templats

* Include text version of email

* Refactor Email template builder into trait'

* Fix for custom_value4

* Refactor payment_date -> date && payment_type_id -> type_id

* expose paymentables to API

* expose paymentables to API

* Implement a next_send_date field in invoice/quote tables to allow control over reminder scheduling

* Add custom_values to users,documents and company_gateways tables
This commit is contained in:
David Bomba 2019-12-16 22:34:38 +11:00 committed by GitHub
parent f8551d6119
commit c6e1658ffe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 589 additions and 223 deletions

View File

@ -321,11 +321,11 @@ class CreateTestData extends Command
if(rand(0, 1)) {
$payment = PaymentFactory::create($client->company->id, $client->user->id);
$payment->payment_date = now();
$payment->date = now();
$payment->client_id = $client->id;
$payment->amount = $invoice->balance;
$payment->transaction_reference = rand(0,500);
$payment->payment_type_id = PaymentType::CREDIT_CARD_OTHER;
$payment->type_id = PaymentType::CREDIT_CARD_OTHER;
$payment->status_id = Payment::STATUS_COMPLETED;
$payment->save();

View File

@ -179,6 +179,8 @@ class CompanySettings extends BaseSettings
public $schedule_reminder2 = ''; // (enum: after_invoice_date, before_due_date, after_due_date)
public $schedule_reminder3 = ''; // (enum: after_invoice_date, before_due_date, after_due_date)
public $reminder_send_time = 32400; //number of seconds from UTC +0 to send reminders
public $late_fee_amount1 = 0;
public $late_fee_amount2 = 0;
public $late_fee_amount3 = 0;
@ -215,6 +217,7 @@ class CompanySettings extends BaseSettings
public static $casts = [
'reminder_send_time' => 'int',
'email_sending_method' => 'string',
'gmail_sending_user_id' => 'string',
'currency_id' => 'string',

View File

@ -17,7 +17,7 @@ class EmailTemplateDefaults
{
public static function emailInvoiceSubject()
{
return ctrans('invoice_subject', ['number'=>'$number', 'account'=>'$company']);
return ctrans('texts.invoice_subject', ['number'=>'$number', 'account'=>'$company.name']);
//return Parsedown::instance()->line(self::transformText('invoice_subject'));
}
@ -28,7 +28,7 @@ class EmailTemplateDefaults
public static function emailQuoteSubject()
{
return ctrans('quote_subject', ['number'=>'$number', 'account'=>'$company']);
return ctrans('texts.quote_subject', ['number'=>'$number', 'account'=>'$company.name']);
//return Parsedown::instance()->line(self::transformText('quote_subject'));
}
@ -51,9 +51,7 @@ class EmailTemplateDefaults
public static function emailReminder1Subject()
{
return ctrans('reminder_subject', ['invoice'=>'$number', 'account'=>'$company']);
// return Parsedown::instance()->line(self::transformText('reminder_subject'));
return ctrans('texts.reminder_subject', ['invoice'=>'$invoice.number', 'account'=>'$company.name']);
}
public static function emailReminder1Template()
@ -63,7 +61,7 @@ class EmailTemplateDefaults
public static function emailReminder2Subject()
{
return ctrans('reminder_subject', ['invoice'=>'$number', 'account'=>'$company']);
return ctrans('texts.reminder_subject', ['invoice'=>'$invoice.number', 'account'=>'$company.name']);
// return Parsedown::instance()->line(self::transformText('reminder_subject'));
}
@ -74,7 +72,7 @@ class EmailTemplateDefaults
public static function emailReminder3Subject()
{
return ctrans('reminder_subject', ['invoice'=>'$number', 'account'=>'$company']);
return ctrans('texts.reminder_subject', ['invoice'=>'$invoice.number', 'account'=>'$company.name']);
// return Parsedown::instance()->line(self::transformText('reminder_subject'));
}
@ -85,7 +83,7 @@ class EmailTemplateDefaults
public static function emailReminderEndlessSubject()
{
return ctrans('reminder_subject', ['invoice'=>'$number', 'account'=>'$company']);
return ctrans('texts.reminder_subject', ['invoice'=>'$invoice.number', 'account'=>'$company.name']);
// return Parsedown::instance()->line(self::transformText('reminder_subject'));
}

View File

@ -20,7 +20,7 @@ class PaymentTransaction
public $account_gateway_id;
public $payment_type_id;
public $type_id;
public $status; // prepayment|payment|response|completed

View File

@ -29,10 +29,10 @@ class PaymentFactory
$payment->client_contact_id = null;
$payment->invitation_id = null;
$payment->company_gateway_id = null;
$payment->payment_type_id = null;
$payment->type_id = null;
$payment->is_deleted = false;
$payment->amount = 0;
$payment->payment_date = Carbon::now()->format('Y-m-d');
$payment->date = Carbon::now()->format('Y-m-d');
$payment->transaction_reference = null;
$payment->payer_id = null;
$payment->status_id = Payment::STATUS_PENDING;

View File

@ -37,7 +37,7 @@ class PaymentFilters extends QueryFilters
return $this->builder->where(function ($query) use ($filter) {
$query->where('payments.amount', 'like', '%'.$filter.'%')
->orWhere('payments.payment_date', 'like', '%'.$filter.'%')
->orWhere('payments.date', 'like', '%'.$filter.'%')
->orWhere('payments.custom_value1', 'like', '%'.$filter.'%')
->orWhere('payments.custom_value2', 'like' , '%'.$filter.'%')
->orWhere('payments.custom_value3', 'like' , '%'.$filter.'%')

View File

@ -52,20 +52,20 @@ class PaymentController extends Controller
return DataTables::of($payments)->addColumn('action', function ($payment) {
return '<a href="/client/payments/'. $payment->hashed_id .'" class="btn btn-xs btn-primary"><i class="glyphicon glyphicon-edit"></i>'.ctrans('texts.view').'</a>';
})->editColumn('payment_type_id', function ($payment) {
})->editColumn('type_id', function ($payment) {
return $payment->type->name;
})
->editColumn('status_id', function ($payment){
return Payment::badgeForStatus($payment->status_id);
})
->editColumn('payment_date', function ($payment){
//return $payment->payment_date;
return $payment->formatDate($payment->payment_date, $payment->client->date_format());
->editColumn('date', function ($payment){
//return $payment->date;
return $payment->formatDate($payment->date, $payment->client->date_format());
})
->editColumn('amount', function ($payment) {
return Number::formatMoney($payment->amount, $payment->client);
})
->rawColumns(['action', 'status_id','payment_type_id'])
->rawColumns(['action', 'status_id','type_id'])
->make(true);
}

View File

@ -37,6 +37,7 @@
* @OA\Property(property="translations", type="object", example="", description="JSON payload of customized translations"),
* @OA\Property(property="task_number_pattern", type="string", example="{$year}-{$counter}", description="Allows customisation of the task number pattern"),
* @OA\Property(property="task_number_counter", type="integer", example="1", description="____________"),
* @OA\Property(property="reminder_send_time", type="integer", example="32400", description="Time from UTC +0 when the email will be sent to the client"),
* @OA\Property(property="expense_number_pattern", type="string", example="{$year}-{$counter}", description="Allows customisation of the expense number pattern"),
* @OA\Property(property="expense_number_counter", type="integer", example="1", description="____________"),
* @OA\Property(property="vendor_number_pattern", type="string", example="{$year}-{$counter}", description="Allows customisation of the vendor number pattern"),

View File

@ -35,6 +35,7 @@
* @OA\Property(property="is_deleted", type="boolean", example=true, description="_________"),
* @OA\Property(property="uses_inclusive_taxes", type="boolean", example=true, description="Defines the type of taxes used as either inclusive or exclusive"),
* @OA\Property(property="date", type="string", format="date", example="1994-07-30", description="The Invoice Date"),
* @OA\Property(property="next_send_date", type="string", format="date", example="1994-07-30", description="The Next date for a reminder to be sent"),
* @OA\Property(property="partial_due_date", type="string", format="date", example="1994-07-30", description="_________"),
* @OA\Property(property="due_date", type="string", format="date", example="1994-07-30", description="_________"),
* @OA\Property(property="settings",ref="#/components/schemas/CompanySettings"),

View File

@ -4,7 +4,20 @@
* schema="Payment",
* type="object",
* @OA\Property(property="id", type="string", example="Opnel5aKBz", description="______"),
* @OA\Property(property="client_id", type="string", example="Opnel5aKBz", description="______"),
* @OA\Property(property="invitation_id", type="string", example="Opnel5aKBz", description="______"),
* @OA\Property(property="client_contact_id", type="string", example="Opnel5aKBz", description="______"),
* @OA\Property(property="user_id", type="string", example="Opnel5aKBz", description="______"),
* @OA\Property(property="type_id", type="string", example="1", description="The Payment Type ID"),
* @OA\Property(property="date", type="string", example="1-1-2014", description="The Payment date"),
* @OA\Property(property="transaction_reference", type="string", example="xcsSxcs124asd", description="The transaction reference as defined by the payment gateway"),
* @OA\Property(property="assigned_user_id", type="string", example="Opnel5aKBz", description="______"),
* @OA\Property(property="is_manual", type="boolean", example=true, description="______"),
* @OA\Property(property="is_deleted", type="boolean", example=true, description="______"),
* @OA\Property(property="amount", type="number", example=10.00, description="The amount of this payment"),
* @OA\Property(property="refunded", type="number", example=10.00, description="The refunded amount of this payment"),
* @OA\Property(property="updated_at", type="number", format="integer", example="1434342123", description="Timestamp"),
* @OA\Property(property="archived_at", type="number", format="integer", example="1434342123", description="Timestamp"),
* @OA\Property(property="company_gateway_id", type="string", example="3", description="The company gateway id"),
* )
*/

View File

@ -5,5 +5,6 @@
* type="object",
* @OA\Property(property="id", type="string", example="Opnel5aKBz", description="______"),
* @OA\Property(property="total_taxes", type="number", format="float", example="10.00", description="The total taxes for the quote"),
* @OA\Property(property="next_send_date", type="string", format="date", example="1994-07-30", description="The Next date for a reminder to be sent"),
* )
*/

View File

@ -200,7 +200,7 @@ class PaymentController extends BaseController
* format="float",
* ),
* @OA\Property(
* property="payment_date",
* property="date",
* example="2019/12/1",
* description="The payment date",
* type="string",

View File

@ -28,7 +28,7 @@ class TemplateController extends BaseController
* @return \Illuminate\Http\Response
*
* @OA\Post(
* path="/api/v1/templates/{entity}/{entity_id}",
* path="/api/v1/templates",
* operationId="getShowTemplate",
* tags={"templates"},
* summary="Returns a entity template with the template variables replaced with the Entities",

View File

@ -75,7 +75,7 @@ class TokenAuth
'errors' => []
];
return response()->json(json_encode($error, JinvoicelspSON_PRETTY_PRINT) ,403);
return response()->json(json_encode($error, JSON_PRETTY_PRINT) ,403);
}
return $next($request);

View File

@ -63,7 +63,7 @@ class StorePaymentRequest extends Request
$rules = [
'amount' => 'numeric|required',
'payment_date' => 'required',
'date' => 'required',
'client_id' => 'required',
'invoices' => new ValidPayableInvoicesRule(),
];

View File

@ -35,9 +35,9 @@ class UpdatePaymentRequest extends Request
return [
'documents' => 'mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx',
'client_id' => 'integer|nullable',
'payment_type_id' => 'integer|nullable',
'type_id' => 'integer|nullable',
'amount' => 'numeric',
'payment_date' => 'required',
'date' => 'required',
];
}

View File

@ -18,11 +18,15 @@ class TemplateEmail extends Mailable
private $user; //the user the email will be sent from
public function __construct($message, $template, $user)
private $client;
public function __construct($message, $template, $user, $client)
{
$this->message = $message;
$this->template = $template;
$this->user = $user;
$this->user = $user; //this is inappropriate here, need to refactor 'user' in this context the 'user' could also be the 'system'
$this->client = $client;
}
/**
@ -37,12 +41,19 @@ class TemplateEmail extends Mailable
//if using a system level template
$template_name = 'email.template.'.$this->template;
$settings = $this->client->getMergedSettings();
$company = $this->client->company;
return $this->from($this->user->email, $this->user->present()->name()) //todo this needs to be fixed to handle the hosted version
->subject($this->message['subject'])
->text('email.template.plain', ['body' => $this->message['body'], 'footer' => $this->message['footer']])
->view($template_name, [
'body' => $this->message['body'],
'footer' => $this->message['footer'],
'title' => $this->message['title'],
'settings' => $settings,
'company' => $company
]);
}

View File

@ -75,7 +75,7 @@ class Client extends BaseModel
'custom_value1',
'custom_value2',
'custom_value3',
'custom_value4,',
'custom_value4',
'shipping_address1',
'shipping_address2',
'shipping_city',

View File

@ -22,6 +22,7 @@ use App\Models\Currency;
use App\Models\Filterable;
use App\Models\PaymentTerm;
use App\Utils\Number;
use App\Utils\Traits\InvoiceEmailBuilder;
use App\Utils\Traits\MakesDates;
use App\Utils\Traits\MakesInvoiceValues;
use App\Utils\Traits\NumberFormatter;
@ -41,7 +42,8 @@ class Invoice extends BaseModel
use MakesDates;
use PresentableTrait;
use MakesInvoiceValues;
use InvoiceEmailBuilder;
protected $presenter = 'App\Models\Presenters\InvoicePresenter';
protected $hidden = [
@ -461,35 +463,4 @@ class Invoice extends BaseModel
});
}
/**
* @deprecated
*
* we can use the trait -> makeValues()
*
*/
public function getVariables() :array
{
return [
'$number' => $this->number,
'$amount' => $this->amount,
'$date' => $this->date,
'$due_date' => $this->due_date,
'$balance' => $this->balance,
'$status' => $this->textStatus(),
'$invoice.number' => $this->number,
'$invoice.amount' => $this->amount,
'$invoice.date' => $this->date,
'$invoice.due_date' => $this->due_date,
'$invoice.balance' => $this->balance,
'$invoice.status' => $this->textStatus(),
];
}
public function getVariableByKey($key)
{
}
}

View File

@ -14,6 +14,7 @@ namespace App\Models;
use App\Models\BaseModel;
use App\Models\DateFormat;
use App\Models\Filterable;
use App\Models\Paymentable;
use App\Utils\Number;
use App\Utils\Traits\MakesDates;
use App\Utils\Traits\MakesHash;
@ -51,9 +52,9 @@ class Payment extends BaseModel
protected $fillable = [
'client_id',
'payment_type_id',
'type_id',
'amount',
'payment_date',
'date',
'transaction_reference'
];
@ -102,7 +103,12 @@ class Payment extends BaseModel
public function type()
{
return $this->hasOne(PaymentType::class,'id','payment_type_id');
return $this->hasOne(PaymentType::class,'id','type_id');
}
public function paymentables()
{
return $this->hasMany(Paymentable::class);
}
public function formattedAmount()
@ -112,12 +118,12 @@ class Payment extends BaseModel
public function clientPaymentDate()
{
if(!$this->payment_date)
if(!$this->date)
return '';
$date_format = DateFormat::find($this->client->getSetting('date_format_id'));
return $this->createClientDate($this->payment_date, $this->client->timezone()->name)->format($date_format->format);
return $this->createClientDate($this->date, $this->client->timezone()->name)->format($date_format->format);
}
public static function badgeForStatus(int $status)

View File

@ -19,5 +19,7 @@ class Paymentable extends Pivot
// public $incrementing = true;
protected $table = 'paymentables';
}

View File

@ -11,6 +11,8 @@
namespace App\Models\Presenters;
use App\Models\Country;
/**
* Class CompanyPresenter
* @package App\Models\Presenters
@ -26,47 +28,56 @@ class CompanyPresenter extends EntityPresenter
return $this->entity->name ?: ctrans('texts.untitled_account');
}
public function logo()
public function logo($settings = null)
{
return strlen($this->entity->getLogo() > 0) ? $this->entity->getLogo() : 'https://www.invoiceninja.com/wp-content/uploads/2019/01/InvoiceNinja-Logo-Round-300x300.png';
if(!$settings)
$settings = $this->entity->settings;
return strlen($settings->company_logo > 0) ? $settings->company_logo : 'https://www.invoiceninja.com/wp-content/uploads/2019/01/InvoiceNinja-Logo-Round-300x300.png';
}
public function address()
public function address($settings = null)
{
$str = '';
$company = $this->entity;
if ($address1 = $company->settings->address1) {
if(!$settings)
$settings = $this->entity->settings;
if ($address1 = $settings->address1) {
$str .= e($address1) . '<br/>';
}
if ($address2 = $company->settings->address2) {
if ($address2 = $settings->address2) {
$str .= e($address2) . '<br/>';
}
if ($cityState = $this->getCompanyCityState()) {
if ($cityState = $this->getCompanyCityState($settings)) {
$str .= e($cityState) . '<br/>';
}
if ($country = $company->country()) {
if ($country = Country::find($settings->country_id)->first()) {
$str .= e($country->name) . '<br/>';
}
if ($company->settings->phone) {
$str .= ctrans('texts.work_phone') . ": ". e($company->settings->phone) .'<br/>';
if ($settings->phone) {
$str .= ctrans('texts.work_phone') . ": ". e($settings->phone) .'<br/>';
}
if ($company->settings->email) {
$str .= ctrans('texts.work_email') . ": ". e($company->settings->email) .'<br/>';
if ($settings->email) {
$str .= ctrans('texts.work_email') . ": ". e($settings->email) .'<br/>';
}
return $str;
}
public function getCompanyCityState()
public function getCompanyCityState($settings = null)
{
$company = $this->entity;
if(!$settings)
$settings = $this->entity->settings;
$swap = $company->country() && $company->country()->swap_postal_code;
$country = Country::find($settings->country_id)->first();
$city = e($company->settings->city);
$state = e($company->settings->state);
$postalCode = e($company->settings->postal_code);
$swap = $country && $country->swap_postal_code;
$city = e($settings->city);
$state = e($settings->state);
$postalCode = e($settings->postal_code);
if ($city || $state || $postalCode) {
return $this->cityStateZip($city, $state, $postalCode, $swap);

View File

@ -225,7 +225,7 @@ class BasePaymentDriver
$payment->client_id = $this->client->id;
$payment->company_gateway_id = $this->company_gateway->id;
$payment->status_id = Payment::STATUS_COMPLETED;
$payment->payment_date = Carbon::now();
$payment->date = Carbon::now();
return $payment;

View File

@ -276,7 +276,7 @@ class PayPalExpressPaymentDriver extends BasePaymentDriver
$client_contact_id = $client_contact ? $client_contact->id : null;
$payment->amount = $data['PAYMENTINFO_0_AMT'];
$payment->payment_type_id = PaymentType::PAYPAL;
$payment->type_id = PaymentType::PAYPAL;
$payment->transaction_reference = $data['PAYMENTINFO_0_TRANSACTIONID'];
$payment->client_contact_id = $client_contact_id;
$payment->save();

View File

@ -408,7 +408,7 @@ class StripePaymentDriver extends BasePaymentDriver
$client_contact_id = $client_contact ? $client_contact->id : null;
$payment->amount = $this->convertFromStripeAmount($data['amount'], $this->client->currency()->precision);
$payment->payment_type_id = $data['payment_type'];
$payment->type_id = $data['payment_type'];
$payment->transaction_reference = $data['payment_method'];
$payment->client_contact_id = $client_contact_id;
$payment->save();

View File

@ -0,0 +1,56 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2019. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Transformers;
use App\Models\Document;
use App\Utils\Traits\MakesHash;
class DocumentTransformer extends EntityTransformer
{
use MakesHash;
protected $serializer;
protected $defaultIncludes = [];
protected $availableIncludes = [];
public function __construct($serializer = null)
{
$this->serializer = $serializer;
}
public function transform(Document $document)
{
return [
'id' => $this->encodePrimaryKey($document->id),
'user_id' => $this->encodePrimaryKey($document->user_id),
'assigned_user_id' => $this->encodePrimaryKey($document->assigned_user_id),
'project_id' => $this->encodePrimaryKey($document->project_id),
'vendor_id' => $this->encodePrimaryKey($document->vendor_id),
'path' => (string) $document->path ?: '',
'preview' => (string) $document->preview ?: '',
'name' => (string) $document->name,
'type' => (string) $document->type,
'disk' => (string) $document->disk,
'hash' => (string) $document->hash,
'size' => (int) $document->size,
'width' => (int) $document->width,
'height' => (int) $document->height,
'is_default' => (bool) $document->is_default,
'updated_at' => (int) $document->updated_at,
'archived_at' => (int) $document->archived_at
];
}
}

View File

@ -96,6 +96,7 @@ class InvoiceTransformer extends EntityTransformer
'discount' => (float) $invoice->discount,
'po_number' => $invoice->po_number ?: '',
'date' => $invoice->date ?: '',
'next_send_date' => $invoice->date ?: '',
'due_date' => $invoice->due_date ?: '',
'terms' => $invoice->terms ?: '',
'public_notes' => $invoice->public_notes ?: '',

View File

@ -15,6 +15,8 @@ use App\Models\Account;
use App\Models\Client;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\Paymentable;
use App\Transformers\PaymentableTransformer;
use App\Utils\Traits\MakesHash;
class PaymentTransformer extends EntityTransformer
@ -28,6 +30,7 @@ class PaymentTransformer extends EntityTransformer
protected $availableIncludes = [
'client',
'invoices',
'paymentables'
];
public function __construct($serializer = null)
@ -50,7 +53,15 @@ class PaymentTransformer extends EntityTransformer
return $this->includeItem($payment->client, $transformer, Client::class);
}
//todo incomplete
public function includePaymentables(Payment $payment)
{
$transformer = new PaymentableTransformer($this->serializer);
return $this->includeCollection($payment->paymentables, $transformer, Paymentable::class);
}
public function transform(Payment $payment)
{
return [
@ -58,14 +69,22 @@ class PaymentTransformer extends EntityTransformer
'user_id' => $this->encodePrimaryKey($payment->user_id),
'assigned_user_id' => $this->encodePrimaryKey($payment->assigned_user_id),
'amount' => (float) $payment->amount,
'refunded' => (float) $payment->refunded,
'transaction_reference' => $payment->transaction_reference ?: '',
'payment_date' => $payment->payment_date ?: '',
'date' => $payment->date ?: '',
'is_manual' => (bool) $payment->is_manual,
'updated_at' => $payment->updated_at,
'archived_at' => $payment->deleted_at,
'is_deleted' => (bool) $payment->is_deleted,
'payment_type_id' => (string) $payment->payment_type_id ?: '',
'type_id' => (string) $payment->payment_type_id ?: '',
'invitation_id' => (string) $payment->invitation_id ?: '',
'client_id' => (string) $this->encodePrimaryKey($payment->client_id),
'client_contact_id' => (string) $this->encodePrimaryKey($payment->client_contact_id),
'company_gateway_id' => (string) $this->encodePrimaryKey($payment->company_gateway_id),
'status_id'=> (string) $payment->status_id,
'type_id'=> (string) $payment->type_id,
'project_id' => (string) $this->encodePrimaryKey($payment->project_id),
'vendor_id' => (string) $this->encodePrimaryKey($payment->vendor_id),
/*
'private_notes' => $payment->private_notes ?: '',
'exchange_rate' => (float) $payment->exchange_rate,

View File

@ -0,0 +1,43 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2019. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Transformers;
use App\Models\Payment;
use App\Models\Paymentable;
use App\Utils\Traits\MakesHash;
class PaymentableTransformer extends EntityTransformer
{
use MakesHash;
protected $serializer;
protected $defaultIncludes = [];
protected $availableIncludes = [];
public function __construct($serializer = null)
{
$this->serializer = $serializer;
}
public function transform(Paymentable $paymentable)
{
return [
'id' => $this->encodePrimaryKey($paymentable->id),
'invoice_id' => $this->encodePrimaryKey($paymentable->paymentable_id),
'amount' => $paymentable->amount,
];
}
}

View File

@ -93,6 +93,7 @@ class QuoteTransformer extends EntityTransformer
'discount' => (float) $quote->discount ?: '',
'po_number' => $quote->po_number ?: '',
'quote_date' => $quote->quote_date ?: '',
'next_send_date' => $quote->date ?: '',
'valid_until' => $quote->valid_until ?: '',
'terms' => $quote->terms ?: '',
'public_notes' => $quote->public_notes ?: '',

View File

@ -0,0 +1,120 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2019. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Utils\Traits;
use App\Models\Invoice;
use Illuminate\Support\Carbon;
use Parsedown;
/**
* Class InvoiceEmailBuilder
* @package App\Utils\Traits
*/
trait InvoiceEmailBuilder
{
/**
* Builds the correct template to send
* @param string $reminder_template The template name ie reminder1
* @return array
*/
public function getEmailData($reminder_template = null) :array
{
//client
$client = $this->client;
if(!$reminder_template)
$reminder_template = $this->calculateTemplate();
//Need to determine which email template we are producing
$email_data = $this->generateTemplateData($reminder_template);
return $email_data;
}
private function generateTemplateData(string $reminder_template) :array
{
$data = [];
$client = $this->client;
$body_template = $client->getSetting('email_template_'.$reminder_template);
$subject_template = $client->getSetting('email_subject_'.$reminder_template);
$data['body'] = $this->parseTemplate($body_template, false);
$data['subject'] = $this->parseTemplate($subject_template, true);
return $data;
}
private function parseTemplate(string $template_data, bool $is_markdown = true) :string
{
$invoice_variables = $this->makeValues();
//process variables
$data = str_replace(array_keys($invoice_variables), array_values($invoice_variables), $template_data);
//process markdown
if($is_markdown)
$data = Parsedown::instance()->line($data);
return $data;
}
private function calculateTemplate() :string
{
//if invoice is currently a draft, or being marked as sent, this will be the initial email
$client = $this->client;
//if the invoice
if($this->status_id == Invoice::STATUS_DRAFT || Carbon::parse($this->due_date) > now())
{
return 'invoice';
}
else if($client->getSetting('enable_reminder1') !== false && $this->inReminderWindow($client->getSetting('schedule_reminder1'), $client->getSetting('num_days_reminder1')))
{
return 'template1';
}
else if($client->getSetting('enable_reminder2') !== false && $this->inReminderWindow($client->getSetting('schedule_reminder2'), $client->getSetting('num_days_reminder2')))
{
return 'template2';
}
else if($client->getSetting('enable_reminder3') !== false && $this->inReminderWindow($client->getSetting('schedule_reminder3'), $client->getSetting('num_days_reminder3')))
{
return 'template3';
}
//also implement endless reminders here
//
}
private function inReminderWindow($schedule_reminder, $num_days_reminder)
{
switch ($schedule_reminder) {
case 'after_invoice_date':
return Carbon::parse($this->date)->addDays($num_days_reminder)->startOfDay()->eq(Carbon::now()->startOfDay());
break;
case 'before_due_date':
return Carbon::parse($this->due_date)->subDays($num_days_reminder)->startOfDay()->eq(Carbon::now()->startOfDay());
break;
case 'after_due_date':
return Carbon::parse($this->due_date)->addDays($num_days_reminder)->startOfDay()->eq(Carbon::now()->startOfDay());
break;
default:
# code...
break;
}
}
}

View File

@ -11,6 +11,7 @@
namespace App\Utils\Traits;
use App\Models\Country;
use App\Utils\Number;
/**
@ -158,7 +159,9 @@ trait MakesInvoiceValues
throw new Exception(debug_backtrace()[1]['function'], 1);
exit;
}
$settings = $this->client->getMergedSettings();
$data = [];
$data['$date'] = $this->date;
@ -166,9 +169,13 @@ trait MakesInvoiceValues
$data['$due_date'] = $this->due_date;
$data['$invoice.due_date'] = &$data['$due_date'];
$data['$number'] = $this->number;
$data['$invoice.number'] = &$data['$number'];
$data['$po_number'] = $this->po_number;
$data['$invoice.po_number'] = &$data['$po_number'];
$data['$line_taxes'] = $this->makeLineTaxes();
$data['$invoice.line_taxes'] = &$data['$line_taxes'];
$data['$total_taxes'] = $this->makeTotalTaxes();
$data['$invoice.total_taxes'] = &$data['$total_taxes'];
// $data['$tax'] = ;
// $data['$item'] = ;
// $data['$description'] = ;
@ -179,12 +186,22 @@ trait MakesInvoiceValues
$data['$discount'] = Number::formatMoney($this->calc()->getTotalDiscount(), $this->client);
$data['$invoice.discount'] = &$data['$discount'];
$data['$subtotal'] = Number::formatMoney($this->calc()->getSubTotal(), $this->client);
$data['$invoice.subtotal'] = &$data['$subtotal'];
$data['$balance_due'] = Number::formatMoney($this->balance, $this->client);
$data['$invoice.balance_due'] = &$data['$balance_due'];
$data['$partial_due'] = Number::formatMoney($this->partial, $this->client);
$data['$invoice.partial_due'] = &$data['$partial_due'];
$data['$total'] = Number::formatMoney($this->calc()->getTotal(), $this->client);
$data['$invoice.total'] = &$data['$total'];
$data['$amount'] = &$data['$total'];
$data['$invoice.amount'] = &$data['$total'];
$data['$balance'] = Number::formatMoney($this->calc()->getBalance(), $this->client);
$data['$invoice.balance'] = &$data['$balance'];
$data['$taxes'] = Number::formatMoney($this->calc()->getItemTotalTaxes(), $this->client);
$data['$invoice.taxes'] = &$data['$taxes'];
$data['$terms'] = $this->terms;
$data['$invoice.terms'] = &$data['$terms'];
// $data['$your_invoice'] = ;
// $data['$quote'] = ;
// $data['$your_quote'] = ;
@ -200,34 +217,48 @@ trait MakesInvoiceValues
// $data['$quote_to'] = ;
// $data['$details'] = ;
$data['$invoice_no'] = $this->number;
$data['$invoice.invoice_no'] = &$data['$invoice_no'];
// $data['$quote_no'] = ;
// $data['$valid_until'] = ;
$data['$client_name'] = $this->present()->clientName();
$data['$client.name'] = &$data['$client_name'];
$data['$client_address'] = $this->present()->address();
$data['$client.address'] = &$data['$client_address'];
$data['$address1'] = $this->client->address1;
$data['$client.address1'] = &$data['$address1'];
$data['$address2'] = $this->client->address2;
$data['$client.address2'] = &$data['$address2'];
$data['$id_number'] = $this->client->id_number;
$data['$client.id_number'] = &$data['$id_number'];
$data['$vat_number'] = $this->client->vat_number;
$data['$client.vat_number'] = &$data['$vat_number'];
$data['$website'] = $this->client->present()->website();
$data['$client.website'] = &$data['$website'];
$data['$phone'] = $this->client->present()->phone();
$data['$client.phone'] = &$data['$phone'];
$data['$city_state_postal'] = $this->present()->cityStateZip($this->client->city, $this->client->state, $this->client->postal_code, FALSE);
$data['$client.city_state_postal'] = &$data['$city_state_postal'];
$data['$postal_city_state'] = $this->present()->cityStateZip($this->client->city, $this->client->state, $this->client->postal_code, TRUE);
$data['$client.postal_city_state'] = &$data['$postal_city_state'];
$data['$country'] = isset($this->client->country->name) ?: 'No Country Set';
$data['$client.country'] = &$data['$country'];
$data['$email'] = isset($this->client->primary_contact()->first()->email) ?: 'no contact email on record';
$data['$client.email'] = &$data['$email'];
$data['$contact_name'] = $this->client->present()->primary_contact_name();
$data['$company_name'] = $this->company->present()->name();
$data['$company_address1'] = $this->company->address1;
$data['$company_address2'] = $this->company->address2;
$data['$company_city'] = $this->company->city;
$data['$company_state'] = $this->company->state;
$data['$company_postal_code'] = $this->company->postal_code;
$data['$company_country'] = $this->company->country() ? $this->company->country()->name : '';
$data['$company_phone'] = $this->company->phone;
$data['$company_email'] = $this->company->email;
$data['$company_vat_number'] = $this->company->vat_number;
$data['$company_id_number'] = $this->company->id_number;
$data['$company_address'] = $this->company->present()->address();
$data['$company_logo'] = $this->company->present()->logo();
$data['$contact.name'] = &$data['$contact_name'];
$data['$company.name'] = $this->company->present()->name();
$data['$company.address1'] = $settings->address1;
$data['$company.address2'] = $settings->address2;
$data['$company.city'] = $settings->city;
$data['$company.state'] = $settings->state;
$data['$company.postal_code'] = $settings->postal_code;
$data['$company.country'] = Country::find($settings->country_id)->first()->name;
$data['$company.phone'] = $settings->phone;
$data['$company.email'] = $settings->email;
$data['$company.vat_number'] = $settings->vat_number;
$data['$company.id_number'] = $settings->id_number;
$data['$company.address'] = $this->company->present()->address($settings);
$data['$company.logo'] = $this->company->present()->logo($settings);
//$data['$blank'] = ;
//$data['$surcharge'] = ;
/*

View File

@ -10,9 +10,9 @@ $factory->define(App\Models\Payment::class, function (Faker $faker) {
return [
'is_deleted' => false,
'amount' => $faker->numberBetween(1,10),
'payment_date' => $faker->date(),
'date' => $faker->date(),
'transaction_reference' => $faker->text(10),
'payment_type_id' => Payment::TYPE_CREDIT_CARD,
'type_id' => Payment::TYPE_CREDIT_CARD,
'status_id' => Payment::STATUS_COMPLETED
];

View File

@ -211,7 +211,10 @@ class CreateUsersTable extends Migration
Schema::create('documents', function (Blueprint $table){
$table->increments('id');
$table->unsignedInteger('user_id');
$table->unsignedInteger('assigned_user_id');
$table->unsignedInteger('company_id')->index();
$table->unsignedInteger('project_id')->nullable();
$table->unsignedInteger('vendor_id')->nullable();
$table->string('path')->nullable();
$table->string('preview')->nullable();
$table->string('name')->nullable();
@ -222,6 +225,10 @@ class CreateUsersTable extends Migration
$table->unsignedInteger('width')->nullable();
$table->unsignedInteger('height')->nullable();
$table->boolean('is_default')->default(0);
$table->string('custom_value1')->nullable();
$table->string('custom_value2')->nullable();
$table->string('custom_value3')->nullable();
$table->string('custom_value4')->nullable();
$table->unsignedInteger('documentable_id');
$table->string('documentable_type');
@ -258,7 +265,11 @@ class CreateUsersTable extends Migration
$table->mediumText('signature')->nullable();
$table->string('password');
$table->rememberToken();
$table->string('custom_value1')->nullable();
$table->string('custom_value2')->nullable();
$table->string('custom_value3')->nullable();
$table->string('custom_value4')->nullable();
$table->timestamps(6);
$table->softDeletes('deleted_at', 6);
@ -378,6 +389,22 @@ class CreateUsersTable extends Migration
});
Schema::create('projects', function ($t) {
$t->increments('id');
$t->unsignedInteger('user_id');
$t->unsignedInteger('assigned_user_id');
$t->unsignedInteger('company_id')->index();
$t->unsignedInteger('client_id')->nullable();
$t->string('name');
$t->string('description');
$t->timestamps();
$t->softDeletes();
$t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$t->foreign('company_id')->references('id')->on('companies');
});
Schema::create('company_gateways', function($table)
{
$table->increments('id');
@ -391,7 +418,11 @@ class CreateUsersTable extends Migration
$table->boolean('update_details')->default(false)->nullable();
$table->mediumText('config');
$table->text('fees_and_limits');
$table->string('custom_value1')->nullable();
$table->string('custom_value2')->nullable();
$table->string('custom_value3')->nullable();
$table->string('custom_value4')->nullable();
$table->timestamps(6);
$table->softDeletes('deleted_at', 6);
@ -410,7 +441,8 @@ class CreateUsersTable extends Migration
$t->unsignedInteger('assigned_user_id')->nullable();
$t->unsignedInteger('company_id')->index();
$t->unsignedInteger('status_id');
$t->unsignedInteger('project_id')->nullable();
$t->unsignedInteger('vendor_id')->nullable();
$t->unsignedInteger('recurring_id')->nullable();
$t->unsignedInteger('design_id')->nullable();
@ -449,6 +481,7 @@ class CreateUsersTable extends Migration
$t->string('custom_value2')->nullable();
$t->string('custom_value3')->nullable();
$t->string('custom_value4')->nullable();
$t->datetime('next_send_date')->nullable();
$t->string('custom_surcharge1')->nullable();
$t->string('custom_surcharge2')->nullable();
@ -482,6 +515,8 @@ class CreateUsersTable extends Migration
$t->unsignedInteger('user_id');
$t->unsignedInteger('assigned_user_id')->nullable();
$t->unsignedInteger('company_id')->index();
$t->unsignedInteger('project_id')->nullable();
$t->unsignedInteger('vendor_id')->nullable();
$t->unsignedInteger('status_id')->index();
$t->text('number')->nullable();
@ -548,7 +583,8 @@ class CreateUsersTable extends Migration
$t->unsignedInteger('user_id');
$t->unsignedInteger('assigned_user_id')->nullable();
$t->unsignedInteger('company_id')->index();
$t->unsignedInteger('project_id')->nullable();
$t->unsignedInteger('vendor_id')->nullable();
$t->unsignedInteger('status_id')->index();
$t->float('discount')->default(0);
@ -613,7 +649,8 @@ class CreateUsersTable extends Migration
$t->unsignedInteger('assigned_user_id')->nullable();
$t->unsignedInteger('company_id')->index();
$t->unsignedInteger('status_id');
$t->unsignedInteger('project_id')->nullable();
$t->unsignedInteger('vendor_id')->nullable();
$t->unsignedInteger('recurring_id')->nullable();
$t->unsignedInteger('design_id')->nullable();
@ -624,6 +661,7 @@ class CreateUsersTable extends Migration
$t->string('po_number')->nullable();
$t->date('date')->nullable();
$t->datetime('due_date')->nullable();
$t->datetime('next_send_date')->nullable();
$t->boolean('is_deleted')->default(false);
@ -731,7 +769,8 @@ class CreateUsersTable extends Migration
$t->unsignedInteger('company_id')->index();
$t->unsignedInteger('user_id');
$t->unsignedInteger('assigned_user_id')->nullable();
$t->unsignedInteger('project_id')->nullable();
$t->unsignedInteger('vendor_id')->nullable();
$t->string('custom_value1')->nullable();
$t->string('custom_value2')->nullable();
$t->string('custom_value3')->nullable();
@ -765,16 +804,18 @@ class CreateUsersTable extends Migration
$t->increments('id');
$t->unsignedInteger('company_id')->index();
$t->unsignedInteger('client_id')->index();
$t->unsignedInteger('project_id')->nullable();
$t->unsignedInteger('vendor_id')->nullable();
$t->unsignedInteger('user_id')->nullable();
$t->unsignedInteger('assigned_user_id')->nullable();
$t->unsignedInteger('client_contact_id')->nullable();
$t->unsignedInteger('invitation_id')->nullable();
$t->unsignedInteger('company_gateway_id')->nullable();
$t->unsignedInteger('payment_type_id')->nullable();
$t->unsignedInteger('type_id')->nullable();
$t->unsignedInteger('status_id')->index();
$t->decimal('amount', 16, 4)->default(0);
$t->decimal('refunded', 16, 4)->default(0);
$t->datetime('payment_date')->nullable();
$t->date('date')->nullable();
$t->string('transaction_reference')->nullable();
$t->string('payer_id')->nullable();
$t->timestamps(6);
@ -788,7 +829,7 @@ class CreateUsersTable extends Migration
$t->foreign('company_gateway_id')->references('id')->on('company_gateways')->onDelete('cascade');
$t->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$t->foreign('payment_type_id')->references('id')->on('payment_types');
$t->foreign('type_id')->references('id')->on('payment_types');
});
@ -819,6 +860,9 @@ class CreateUsersTable extends Migration
$table->unsignedInteger('company_id')->index();
$table->unsignedInteger('client_id')->nullable();
$table->unsignedInteger('invoice_id')->nullable();
$table->unsignedInteger('project_id')->nullable();
$table->unsignedInteger('vendor_id')->nullable();
$table->timestamps(6);
$table->softDeletes('deleted_at', 6);
@ -902,6 +946,8 @@ class CreateUsersTable extends Migration
$table->unsignedInteger('client_id')->nullable();
$table->unsignedInteger('client_contact_id')->nullable();
$table->unsignedInteger('account_id')->nullable();
$table->unsignedInteger('project_id')->nullable();
$table->unsignedInteger('vendor_id')->nullable();
$table->unsignedInteger('payment_id')->nullable();
$table->unsignedInteger('invoice_id')->nullable();
$table->unsignedInteger('invitation_id')->nullable();
@ -914,6 +960,8 @@ class CreateUsersTable extends Migration
$table->text('notes');
$table->timestamps(6);
$table->index(['vendor_id', 'company_id']);
$table->index(['project_id', 'company_id']);
$table->index(['user_id', 'company_id']);
$table->index(['client_id', 'company_id']);
$table->index(['payment_id', 'company_id']);

View File

@ -168,13 +168,13 @@ class RandomDataSeeder extends Seeder
if(rand(0, 1)) {
$payment = App\Models\Payment::create([
'payment_date' => now(),
'date' => now(),
'user_id' => $user->id,
'company_id' => $company->id,
'client_id' => $client->id,
'amount' => $invoice->balance,
'transaction_reference' => rand(0,500),
'payment_type_id' => PaymentType::CREDIT_CARD_OTHER,
'type_id' => PaymentType::CREDIT_CARD_OTHER,
'status_id' => Payment::STATUS_COMPLETED,
]);

View File

@ -0,0 +1,11 @@
@if ($company->present()->logo($settings))
@if ($settings->website)
<a href="{{ $settings->website }}" style="color: #19BB40; text-decoration: underline;">
@endif
<img src="{{ $company->present()->logo() }}" height="50" style="height:50px; max-width:140px; margin-left: 33px; padding-top: 2px" alt=""/>
@if ($settings->website)
</a>
@endif
@endif

View File

@ -304,6 +304,9 @@
<body class="">
<span class="preheader">This is preheader text. Some clients will show this text as a preview.</span>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="body">
<tr>
<td>@include('email.partials.company_logo')</td>
</tr>
<tr>
<td>&nbsp;</td>
<td class="container">

View File

@ -1,4 +1,4 @@
{{ $body }}
<br>
<br>
{{ $footer }}

View File

@ -2,10 +2,19 @@
namespace Feature;
use App\Mail\TemplateEmail;
use App\Models\ClientContact;
use App\Models\Invoice;
use App\Models\InvoiceInvitation;
use App\Utils\Traits\GeneratesCounter;
use App\Utils\Traits\InvoiceEmailBuilder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Testing\Concerns\InteractsWithDatabase;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Session;
use Parsedown;
use Tests\MockAccountData;
use Tests\TestCase;
@ -16,6 +25,7 @@ class InvoiceEmailTest extends TestCase
{
use MockAccountData;
use DatabaseTransactions;
use GeneratesCounter;
public function setUp() :void
{
@ -33,119 +43,62 @@ class InvoiceEmailTest extends TestCase
public function test_initial_email_sends()
{
\Log::error($this->invoice->makeValues());
}
// \Log::error($this->invoice->makeValues());
$this->invoice->date = now();
$this->invoice->due_date = now()->addDays(7);
$this->invoice->number = $this->getNextInvoiceNumber($this->client);
$this->invoice->client = $this->client;
$message_array = $this->invoice->getEmailData();
$message_array['title'] = &$message_array['subject'];
$message_array['footer'] = 'The Footer';
$template_style = $this->client->getSetting('email_style');
$template_style = 'light';
//iterate through the senders list and send from here
$invitations = InvoiceInvitation::whereInvoiceId($this->invoice->id)->get();
$invitations->each(function($invitation) use($message_array, $template_style) {
$contact = ClientContact::find($invitation->client_contact_id)->first();
if($contact->send_invoice && $contact->email)
{
//there may be template variables left over for the specific contact? need to reparse here
//change the runtime config of the mail provider here:
//send message
Mail::to($contact->email)
->send(new TemplateEmail($message_array, $template_style, $this->user, $this->client));
//fire any events
sleep(5);
}
//TDD
/**
* Builds the correct template to send
* @param App\Models\Invoice $invoice The Invoice Model
* @param string $reminder_template The template name ie reminder1
* @return void
*/
private function invoiceEmailWorkFlow($invoice, $reminder_template = null)
{
//client
$client = $invoice->client;
$template_style = $client->getSetting('email_style');
if(!$reminder_template)
$reminder_template = $this->calculateTemplate($invoice);
//Need to determine which email template we are producing
$email_data = $this->generateTemplateData($invoice, $reminder_template);
}
private function generateTemplateData(Invoice $invoice, string $reminder_template) :array
{
$data = [];
$client = $invoice->client;
$body_template = $client->getSetting('email_template_'.$reminder_template);
$subject_template = $client->getSetting('email_subject_'.$reminder_template);
$data['message'] = $this->parseTemplate($invoice, $body_template);
$data['subject'] = $this->parseTemplate($invoice, $subject_template);
return $data;
}
private function parseTemplate($invoice, $template_data) :string
{
//process variables
});
//process markdown
}
private function calculateTemplate(Invoice $invoice) :string
{
//if invoice is currently a draft, or being marked as sent, this will be the initial email
$client = $invoice->client;
//if the invoice
if($invoice->status_id == Invoice::STATUS_DRAFT || Carbon::parse($invoice->due_date) > now())
{
return 'invoice';
}
else if($client->getSetting('enable_reminder1') !== false && $this->inReminderWindow($invoice, $client->getSetting('schedule_reminder1'), $client->getSetting('num_days_reminder1')))
{
return 'template1';
}
else if($client->getSetting('enable_reminder2') !== false && $this->inReminderWindow($invoice, $client->getSetting('schedule_reminder2'), $client->getSetting('num_days_reminder2')))
{
return 'template2';
}
else if($client->getSetting('enable_reminder3') !== false && $this->inReminderWindow($invoice, $client->getSetting('schedule_reminder3'), $client->getSetting('num_days_reminder3')))
{
return 'template3';
}
//also implement endless reminders here
//
}
private function inReminderWindow($invoice, $schedule_reminder, $num_days_reminder)
{
switch ($schedule_reminder) {
case 'after_invoice_date':
return Carbon::parse($invoice->date)->addDays($num_days_reminder)->startOfDay()->eq(Carbon::now()->startOfDay());
break;
case 'before_due_date':
return Carbon::parse($invoice->due_date)->subDays($num_days_reminder)->startOfDay()->eq(Carbon::now()->startOfDay());
break;
case 'after_due_date':
return Carbon::parse($invoice->due_date)->addDays($num_days_reminder)->startOfDay()->eq(Carbon::now()->startOfDay());
break;
default:
# code...
break;
}
}
}

View File

@ -135,7 +135,7 @@ class PaymentTest extends TestCase
'amount' => $this->invoice->amount
],
],
'payment_date' => '2020/12/11',
'date' => '2020/12/11',
];
@ -185,7 +185,7 @@ class PaymentTest extends TestCase
'amount' => $this->invoice->amount
],
],
'payment_date' => '2020/12/12',
'date' => '2020/12/12',
];
@ -245,11 +245,13 @@ class PaymentTest extends TestCase
'amount' => $this->invoice->amount,
'client_id' => $client->hashed_id,
'invoices' => '',
'payment_date' => '2020/12/12',
'date' => '2020/12/12',
];
$response = false;
try {
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
@ -300,18 +302,31 @@ class PaymentTest extends TestCase
'amount' => 2.0
],
],
'payment_date' => '2019/12/12',
'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?include=invoices', $data);
$arr = $response->json();
}
catch(ValidationException $e) {
$message = json_decode($e->validator->getMessageBag(),1);
$this->assertNotNull($message);
\Log::error($message);
}
if($response) {
$response->assertStatus(200);
$arr = $response->json();
$payment_id = $arr['data']['id'];
$payment = Payment::find($this->decodePrimaryKey($payment_id))->first();
@ -325,6 +340,7 @@ class PaymentTest extends TestCase
$this->assertEquals($pivot_invoice->partial, 0);
$this->assertEquals($pivot_invoice->amount, 10.0000);
$this->assertEquals($pivot_invoice->balance, 8.0000);
}
}
@ -364,7 +380,7 @@ class PaymentTest extends TestCase
'amount' => 6.0
],
],
'payment_date' => '2019/12/12',
'date' => '2019/12/12',
];
$response = $this->withHeaders([
@ -425,7 +441,7 @@ class PaymentTest extends TestCase
'amount' => 2.0
],
],
'payment_date' => '2019/12/12',
'date' => '2019/12/12',
];
$response = $this->withHeaders([

View File

@ -17,16 +17,19 @@ use App\DataMapper\DefaultSettings;
use App\Factory\ClientFactory;
use App\Factory\CompanyUserFactory;
use App\Factory\InvoiceFactory;
use App\Factory\InvoiceInvitationFactory;
use App\Factory\InvoiceItemFactory;
use App\Factory\InvoiceToRecurringInvoiceFactory;
use App\Helpers\Invoice\InvoiceSum;
use App\Jobs\Company\UpdateCompanyLedgerWithInvoice;
use App\Jobs\Invoice\CreateInvoiceInvitations;
use App\Models\Client;
use App\Models\CompanyGateway;
use App\Models\CompanyToken;
use App\Models\Credit;
use App\Models\GroupSetting;
use App\Models\Invoice;
use App\Models\InvoiceInvitation;
use App\Models\Quote;
use App\Models\RecurringInvoice;
use App\Models\User;
@ -127,13 +130,33 @@ trait MockAccountData
// 'settings' => json_encode(DefaultSettings::userSettings()),
// ]);
$this->client = ClientFactory::create($this->company->id, $this->user->id);
$this->client->save();
$this->client = ClientFactory::create($this->company->id, $this->user->id);
$this->client->save();
factory(\App\Models\ClientContact::class,1)->create([
'user_id' => $this->user->id,
'client_id' => $this->client->id,
'company_id' => $this->company->id,
'is_primary' => 1,
'send_invoice' => true,
]);
factory(\App\Models\ClientContact::class,1)->create([
'user_id' => $this->user->id,
'client_id' => $this->client->id,
'company_id' => $this->company->id,
'send_invoice' => true
]);
$gs = new GroupSetting;
$gs->name = 'Test';
$gs->company_id = $this->client->company_id;
$gs->settings = ClientSettings::buildClientSettings($this->company->settings, $this->client->settings);
$gs_settings = $gs->settings;
$gs_settings->website = 'http://staging.invoicing.co';
$gs->settings = $gs_settings;
$gs->save();
$this->client->group_settings_id = $gs->id;
@ -154,6 +177,29 @@ trait MockAccountData
$this->invoice->save();
$this->invoice->markSent();
$contacts = $this->invoice->client->contacts;
$contacts->each(function ($contact) {
$invitation = InvoiceInvitation::whereCompanyId($this->invoice->company_id)
->whereClientContactId($contact->id)
->whereInvoiceId($this->invoice->id)
->first();
if(!$invitation && $contact->send_invoice) {
$ii = InvoiceInvitationFactory::create($this->invoice->company_id, $this->invoice->user_id);
$ii->invoice_id = $this->invoice->id;
$ii->client_contact_id = $contact->id;
$ii->save();
}
else if($invitation && !$contact->send_invoice) {
$invitation->delete();
}
});
UpdateCompanyLedgerWithInvoice::dispatchNow($this->invoice, $this->invoice->amount);
$recurring_invoice = InvoiceToRecurringInvoiceFactory::create($this->invoice);