mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
Refactors (#3148)
* 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:
parent
f8551d6119
commit
c6e1658ffe
@ -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();
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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'));
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ class PaymentTransaction
|
||||
|
||||
public $account_gateway_id;
|
||||
|
||||
public $payment_type_id;
|
||||
public $type_id;
|
||||
|
||||
public $status; // prepayment|payment|response|completed
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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.'%')
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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"),
|
||||
|
@ -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"),
|
||||
|
@ -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"),
|
||||
* )
|
||||
*/
|
@ -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"),
|
||||
* )
|
||||
*/
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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);
|
||||
|
@ -63,7 +63,7 @@ class StorePaymentRequest extends Request
|
||||
|
||||
$rules = [
|
||||
'amount' => 'numeric|required',
|
||||
'payment_date' => 'required',
|
||||
'date' => 'required',
|
||||
'client_id' => 'required',
|
||||
'invoices' => new ValidPayableInvoicesRule(),
|
||||
];
|
||||
|
@ -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',
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
]);
|
||||
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ class Client extends BaseModel
|
||||
'custom_value1',
|
||||
'custom_value2',
|
||||
'custom_value3',
|
||||
'custom_value4,',
|
||||
'custom_value4',
|
||||
'shipping_address1',
|
||||
'shipping_address2',
|
||||
'shipping_city',
|
||||
|
@ -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)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -19,5 +19,7 @@ class Paymentable extends Pivot
|
||||
|
||||
// public $incrementing = true;
|
||||
|
||||
protected $table = 'paymentables';
|
||||
|
||||
|
||||
}
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
56
app/Transformers/DocumentTransformer.php
Normal file
56
app/Transformers/DocumentTransformer.php
Normal 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
|
||||
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -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 ?: '',
|
||||
|
@ -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,
|
||||
|
43
app/Transformers/PaymentableTransformer.php
Normal file
43
app/Transformers/PaymentableTransformer.php
Normal 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,
|
||||
];
|
||||
}
|
||||
}
|
@ -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 ?: '',
|
||||
|
120
app/Utils/Traits/InvoiceEmailBuilder.php
Normal file
120
app/Utils/Traits/InvoiceEmailBuilder.php
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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'] = ;
|
||||
/*
|
||||
|
@ -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
|
||||
];
|
||||
|
||||
|
@ -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']);
|
||||
|
@ -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,
|
||||
]);
|
||||
|
||||
|
11
resources/views/email/partials/company_logo.blade.php
Normal file
11
resources/views/email/partials/company_logo.blade.php
Normal 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
|
@ -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> </td>
|
||||
<td class="container">
|
||||
|
@ -1,4 +1,4 @@
|
||||
{{ $body }}
|
||||
<br>
|
||||
<br>
|
||||
|
||||
|
||||
{{ $footer }}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@ -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([
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user