Fixes for invoice update and invoice designs (#3302)

* BaseController cleanup

* Working on invoice designs

* Working on invoice designs

* working on invoice designs

* working on invoice designs

* invoice designs

* Working on Invoice Designs

* Fixes for user settings object

* Working on invoice designs

* Fixes for encoded user settings

* Working on contact localized invoice pdfs

* working on invoice designs

* Fix for invoice update 500 error
This commit is contained in:
David Bomba 2020-02-10 20:53:02 +11:00 committed by GitHub
parent e8f19f9b63
commit 9a19f7fd4c
23 changed files with 1438 additions and 1204 deletions

View File

@ -483,7 +483,7 @@ class CreateTestData extends Command
UpdateInvoicePayment::dispatchNow($payment, $payment->company);
}
//@todo this slow things down, but gives us PDFs of the invoices for inspection whilst debugging.
//event(new InvoiceWasCreated($invoice, $invoice->company));
event(new InvoiceWasCreated($invoice, $invoice->company));
}
private function createCredit($client)

View File

@ -217,7 +217,7 @@ class CompanySettings extends BaseSettings
public $embed_documents = false;
public $all_pages_header = true;
public $all_pages_footer = true;
public $invoice_variables = [];
public static $casts = [
'auto_email_invoice' => 'bool',
@ -369,6 +369,7 @@ class CompanySettings extends BaseSettings
'counter_padding' => 'integer',
'design' => 'string',
'website' => 'string',
'invoice_variables' => 'object',
];
/**
@ -413,7 +414,8 @@ class CompanySettings extends BaseSettings
$data->date_format_id = (string)config('ninja.i18n.date_format_id');
$data->country_id = (string)config('ninja.i18n.country_id');
$data->translations = (object) [];
$data->invoice_variables = (array) self::getInvoiceVariableDefaults();
// $data->email_subject_invoice = EmailTemplateDefaults::emailInvoiceSubject();
// $data->email_template_invoice = EmailTemplateDefaults:: emailInvoiceTemplate();
// $data->email_subject_quote = EmailTemplateDefaults::emailQuoteSubject();
@ -453,4 +455,53 @@ class CompanySettings extends BaseSettings
return $settings;
}
private static function getInvoiceVariableDefaults()
{
$variables = [
'client_details' => [
'name',
'id_number',
'vat_number',
'address1',
'address2',
'city_state_postal',
'country',
'email',
],
'company_details' => [
'company_name',
'id_number',
'vat_number',
'website',
'email',
'phone',
],
'company_address' => [
'address1',
'address2',
'city_state_postal',
'country',
],
'invoice_details' => [
'invoice_number',
'po_number',
'date',
'due_date',
'balance_due',
'invoice_total',
],
'table_columns' => [
'product_key',
'notes',
'cost',
'quantity',
'discount',
'tax_name1',
'line_total'
],
];
return $variables;
}
}

View File

@ -34,7 +34,7 @@ class DefaultSettings extends BaseSettings
public static function userSettings() : \stdClass
{
return (object)[
class_basename(User::class) => self::userSettingsObject(),
// class_basename(User::class) => self::userSettingsObject(),
];
}
@ -44,7 +44,7 @@ class DefaultSettings extends BaseSettings
private static function userSettingsObject() : \stdClass
{
return (object)[
'per_page' => self::$per_page,
// 'per_page' => self::$per_page,
];
}
}

View File

@ -11,6 +11,7 @@
namespace App\Designs;
use App\Models\Company;
use App\Models\Invoice;
class Designer
@ -24,11 +25,34 @@ class Designer
protected $html;
public function __construct($design, array $input_variables)
private static $custom_fields = [
'invoice1',
'invoice2',
'invoice3',
'invoice4',
'surcharge1',
'surcharge2',
'surcharge3',
'surcharge4',
'client1',
'client2',
'client3',
'client4',
'contact1',
'contact2',
'contact3',
'contact4',
'company1',
'company2',
'company3',
'company4',
];
public function __construct($design, $input_variables)
{
$this->design = $design;
$this->input_variables = $input_variables;
$this->input_variables = (array)$input_variables;
}
/**
@ -39,7 +63,7 @@ class Designer
public function build(Invoice $invoice) :Designer
{
$this->exportVariables()
$this->exportVariables($invoice)
->setDesign($this->getSection('header'))
->setDesign($this->getSection('body'))
->setDesign($this->getTable($invoice))
@ -86,14 +110,15 @@ class Designer
return str_replace(array_keys($this->exported_variables), array_values($this->exported_variables), $this->design->{$section}());
}
private function exportVariables()
private function exportVariables($invoice)
{
$company = $invoice->company;
$this->exported_variables['$client_details'] = $this->processVariables($this->input_variables['client_details'], $this->clientDetails());
$this->exported_variables['$company_details'] = $this->processVariables($this->input_variables['company_details'], $this->companyDetails());
$this->exported_variables['$company_address'] = $this->processVariables($this->input_variables['company_address'], $this->companyAddress());
$this->exported_variables['$invoice_details_labels'] = $this->processLabels($this->input_variables['invoice_details'], $this->invoiceDetails());
$this->exported_variables['$invoice_details'] = $this->processVariables($this->input_variables['invoice_details'], $this->invoiceDetails());
$this->exported_variables['$client_details'] = $this->processVariables($this->processInputVariables($company, $this->input_variables['client_details']), $this->clientDetails($company));
$this->exported_variables['$company_details'] = $this->processVariables($this->processInputVariables($company, $this->input_variables['company_details']), $this->companyDetails($company));
$this->exported_variables['$company_address'] = $this->processVariables($this->processInputVariables($company, $this->input_variables['company_address']), $this->companyAddress($company));
$this->exported_variables['$invoice_details_labels'] = $this->processLabels($this->processInputVariables($company, $this->input_variables['invoice_details']), $this->invoiceDetails($company));
$this->exported_variables['$invoice_details'] = $this->processVariables($this->processInputVariables($company, $this->input_variables['invoice_details']), $this->invoiceDetails($company));
return $this;
}
@ -153,10 +178,10 @@ class Designer
// $footer = $this->design->footer();
// }
private function clientDetails()
private function clientDetails(Company $company)
{
return [
$data = [
'name' => '<p>$client.name</p>',
'id_number' => '<p>$client.id_number</p>',
'vat_number' => '<p>$client.vat_number</p>',
@ -166,51 +191,59 @@ class Designer
'postal_city_state' => '<p>$client.postal_city_state</p>',
'country' => '<p>$client.country</p>',
'email' => '<p>$client.email</p>',
'custom_value1' => '<p>$client.custom_value1</p>',
'custom_value2' => '<p>$client.custom_value2</p>',
'custom_value3' => '<p>$client.custom_value3</p>',
'custom_value4' => '<p>$client.custom_value4</p>',
'client1' => '<p>$client1</p>',
'client2' => '<p>$client2</p>',
'client3' => '<p>$client3</p>',
'client4' => '<p>$client4</p>',
'contact1' => '<p>$contact1</p>',
'contact2' => '<p>$contact2</p>',
'contact3' => '<p>$contact3</p>',
'contact4' => '<p>$contact4</p>',
];
return $this->processCustomFields($company, $data);
}
private function companyDetails()
private function companyDetails(Company $company)
{
return [
$data = [
'company_name' => '<span>$company.company_name</span>',
'id_number' => '<span>$company.id_number</span>',
'vat_number' => '<span>$company.vat_number</span>',
'website' => '<span>$company.website</span>',
'email' => '<span>$company.email</span>',
'phone' => '<span>$company.phone</span>',
'custom_value1' => '<span>$company.custom_value1</span>',
'custom_value2' => '<span>$company.custom_value2</span>',
'custom_value3' => '<span>$company.custom_value3</span>',
'custom_value4' => '<span>$company.custom_value4</span>',
'company1' => '<span>$company1</span>',
'company2' => '<span>$company2</span>',
'company3' => '<span>$company3</span>',
'company4' => '<span>$company4</span>',
];
return $this->processCustomFields($company, $data);
}
private function companyAddress()
private function companyAddress(Company $company)
{
return [
$data = [
'address1' => '<span>$company.address1</span>',
'address2' => '<span>$company.address1</span>',
'city_state_postal' => '<span>$company.city_state_postal</span>',
'postal_city_state' => '<span>$company.postal_city_state</span>',
'country' => '<span>$company.country</span>',
'custom_value1' => '<span>$company.custom_value1</span>',
'custom_value2' => '<span>$company.custom_value2</span>',
'custom_value3' => '<span>$company.custom_value3</span>',
'custom_value4' => '<span>$company.custom_value4</span>',
'company1' => '<span>$company1</span>',
'company2' => '<span>$company2</span>',
'company3' => '<span>$company3</span>',
'company4' => '<span>$company4</span>',
];
return $this->processCustomFields($company, $data);
}
private function invoiceDetails()
private function invoiceDetails(Company $company)
{
return [
$data = [
'invoice_number' => '<span>$invoice_number</span>',
'po_number' => '<span>$po_number</span>',
'date' => '<span>$date</span>',
@ -218,11 +251,62 @@ class Designer
'balance_due' => '<span>$balance_due</span>',
'invoice_total' => '<span>$invoice_total</span>',
'partial_due' => '<span>$partial_due</span>',
'custom_value1' => '<span>$invoice.custom_value1</span>',
'custom_value2' => '<span>$invoice.custom_value2</span>',
'custom_value3' => '<span>$invoice.custom_value3</span>',
'custom_value4' => '<span>$invoice.custom_value4</span>',
'invoice1' => '<span>$invoice1</span>',
'invoice2' => '<span>$invoice2</span>',
'invoice3' => '<span>$invoice3</span>',
'invoice4' => '<span>$invoice4</span>',
'surcharge1' =>'<span>$surcharge1</span>',
'surcharge2' =>'<span>$surcharge2</span>',
'surcharge3' =>'<span>$surcharge3</span>',
'surcharge4' =>'<span>$surcharge4</span>',
];
return $this->processCustomFields($company, $data);
}
private function processCustomFields(Company $company, $data)
{
$custom_fields = $company->custom_fields;
foreach(self::$custom_fields as $cf)
{
if(!property_exists($custom_fields, $cf) || (strlen($custom_fields->{$cf}) == 0))
unset($data[$cf]);
}
return $data;
}
private function processInputVariables($company, $variables)
{
$custom_fields = $company->custom_fields;
$matches = array_intersect(self::$custom_fields, $variables);
foreach($matches as $match)
{
if(!property_exists($custom_fields, $match) || (strlen($custom_fields->{$match}) == 0))
{
foreach($variables as $key => $value)
{
if($value == $match)
{
unset($variables[$key]);
}
}
}
}
return $variables;
}
}

File diff suppressed because one or more lines are too long

View File

@ -31,7 +31,8 @@ class CompanyFactory
$company->company_key = $this->createHash();
$company->settings = CompanySettings::defaults();
$company->db = config('database.default');
$company->custom_fields = (object) ['custom1' => '1', 'custom2' => '2', 'custom3'=>'3'];
//$company->custom_fields = (object) ['invoice1' => '1', 'invoice2' => '2', 'client1'=>'3'];
$company->custom_fields = (object) [];
$company->subdomain = '';
return $company;

View File

@ -56,32 +56,35 @@ class BaseController extends Controller
public function __construct()
{
$this->manager = new Manager();
$this->forced_includes = [];
$this->forced_index = 'data';
}
private function buildManager()
{
$include = '';
if(request()->has('first_load') && request()->input('first_load') == 'true') {
$include = $this->getRequestIncludes([]);
$include = array_merge($this->forced_includes, $include);
$include = implode("," , array_merge($this->forced_includes, $this->getRequestIncludes([])));
$include = implode(",", $include);
}
else if (request()->input('include') !== null) {
$request_include = explode(",", request()->input('include'));
$include = array_merge($this->forced_includes, $request_include);
$include = array_merge($this->forced_includes, explode(",", request()->input('include')));
$include = implode(",", $include);
} elseif (count($this->forced_includes) >= 1) {
$include = implode(",", $this->forced_includes);
}
$this->manager->parseIncludes($include);
@ -89,10 +92,15 @@ class BaseController extends Controller
$this->serializer = request()->input('serializer') ?: EntityTransformer::API_SERIALIZER_ARRAY;
if ($this->serializer === EntityTransformer::API_SERIALIZER_JSON) {
$this->manager->setSerializer(new JsonApiSerializer());
} else {
$this->manager->setSerializer(new ArraySerializer());
}
}
/**
@ -101,27 +109,36 @@ class BaseController extends Controller
*/
public function notFound()
{
return response()->json(['message' => '404 | Nothing to see here!'], 404)
->header('X-API-VERSION', config('ninja.api_version'))
->header('X-APP-VERSION', config('ninja.app_version'));
}
public function notFoundClient()
{
return abort(404);
}
protected function errorResponse($response, $httpErrorCode = 400)
{
$error['error'] = $response;
$error = json_encode($error, JSON_PRETTY_PRINT);
$headers = self::getApiHeaders();
return response()->make($error, $httpErrorCode, $headers);
}
protected function listResponse($query)
{
$this->buildManager();
$transformer = new $this->entity_transformer(Input::get('serializer'));
@ -145,32 +162,40 @@ class BaseController extends Controller
$data = $this->createCollection($query, $transformer, $this->entity_type);
return $this->response($data);
}
protected function createCollection($query, $transformer, $entity_type)
{
$this->buildManager();
if ($this->serializer && $this->serializer != EntityTransformer::API_SERIALIZER_JSON) {
if ($this->serializer && $this->serializer != EntityTransformer::API_SERIALIZER_JSON)
$entity_type = null;
}
if (is_a($query, "Illuminate\Database\Eloquent\Builder")) {
$limit = Input::get('per_page', 20);
$paginator = $query->paginate($limit);
$query = $paginator->getCollection();
$resource = new Collection($query, $transformer, $entity_type);
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
} else {
$resource = new Collection($query, $transformer, $entity_type);
}
return $this->manager->createData($resource)->toArray();
}
protected function response($response)
{
$index = request()->input('index') ?: $this->forced_index;
if ($index == 'none') {
@ -194,55 +219,50 @@ class BaseController extends Controller
ksort($response);
$response = json_encode($response, JSON_PRETTY_PRINT);
$headers = self::getApiHeaders();
return response()->make($response, 200, $headers);
}
protected function itemResponse($item)
{
$this->buildManager();
$transformer = new $this->entity_transformer(Input::get('serializer'));
$data = $this->createItem($item, $transformer, $this->entity_type);
if (request()->include_static) {
if (request()->include_static)
$data['static'] = Statics::company(auth()->user()->getCompany()->getLocale());
}
return $this->response($data);
}
protected function createItem($data, $transformer, $entity_type)
{
if ($this->serializer && $this->serializer != EntityTransformer::API_SERIALIZER_JSON) {
$entity_type = null;
}
if ($this->serializer && $this->serializer != EntityTransformer::API_SERIALIZER_JSON)
$entity_type = null;
$resource = new Item($data, $transformer, $entity_type);
return $this->manager->createData($resource)->toArray();
}
public static function getApiHeaders($count = 0)
{
return [
'Content-Type' => 'application/json',
//'Access-Control-Allow-Origin' => '*',
//'Access-Control-Allow-Methods' => 'GET',
//'Access-Control-Allow-Headers' => 'Origin, Content-Type, Accept, Authorization, X-Requested-With',
//'Access-Control-Allow-Credentials' => 'true',
//'X-Total-Count' => $count,
'X-Api-Version' => config('ninja.api_version'),
'X-App-Version' => config('ninja.app_version'),
//'X-Rate-Limit-Limit' - The number of allowed requests in the current period
//'X-Rate-Limit-Remaining' - The number of remaining requests in the current period
//'X-Rate-Limit-Reset' - The number of seconds left in the current period,
];
}
@ -279,7 +299,9 @@ class BaseController extends Controller
'company.groups',
];
/**
* Thresholds for displaying large account on first load
*/
if (request()->has('first_load') && request()->input('first_load') == 'true')
{
@ -315,4 +337,5 @@ class BaseController extends Controller
return $data;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -11,6 +11,9 @@
namespace App\Http\Controllers;
use Codedge\Updater\UpdaterManager;
use Illuminate\Foundation\Bus\DispatchesJobs;
class SelfUpdateController extends BaseController
{
use DispatchesJobs;
@ -20,8 +23,10 @@ class SelfUpdateController extends BaseController
}
public function update()
public function update(UpdaterManager $updater)
{
$updater->update();
}
}

View File

@ -103,8 +103,8 @@ class TemplateController extends BaseController
$entity_obj = $class::whereId(request()->input('entity_id'))->company()->first();
}
$subject = request()->input('subject');
$body = request()->input('body');
$subject = request()->input('subject') ?: '';
$body = request()->input('body') ?: '';
$converter = new CommonMarkConverter([
'html_input' => 'strip',

View File

@ -14,11 +14,10 @@ namespace App\Jobs\Invoice;
use App\Designs\Designer;
use App\Designs\Modern;
use App\Libraries\MultiDB;
use App\Models\ClientContact;
use App\Models\Company;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\PaymentTerm;
use App\Repositories\InvoiceRepository;
use App\Utils\Traits\MakesInvoiceHtml;
use App\Utils\Traits\NumberFormatter;
use Illuminate\Bus\Queueable;
@ -26,142 +25,78 @@ use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Storage;
use Spatie\Browsershot\Browsershot;
use Symfony\Component\Debug\Exception\FatalThrowableError;
class CreateInvoicePdf implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, NumberFormatter, MakesInvoiceHtml;
class CreateInvoicePdf implements ShouldQueue {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, NumberFormatter, MakesInvoiceHtml;
public $invoice;
public $invoice;
public $company;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Invoice $invoice, Company $company)
{
$this->invoice = $invoice;
public $company;
$this->company = $company;
}
public $contact;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Invoice $invoice, Company $company, ClientContact $contact) {
$this->invoice = $invoice;
public function handle()
{
MultiDB::setDB($this->company->db);
$this->company = $company;
$this->contact = $contact;
}
$input_variables = [
'client_details' => [
'name',
'id_number',
'vat_number',
'address1',
'address2',
'city_state_postal',
'postal_city_state',
'country',
'email',
'custom_value1',
'custom_value2',
'custom_value3',
'custom_value4',
],
'company_details' => [
'company_name',
'id_number',
'vat_number',
'website',
'email',
'phone',
'custom_value1',
'custom_value2',
'custom_value3',
'custom_value4',
],
'company_address' => [
'address1',
'address2',
'city_state_postal',
'postal_city_state',
'country',
'custom_value1',
'custom_value2',
'custom_value3',
'custom_value4',
],
'invoice_details' => [
'invoice_number',
'po_number',
'date',
'due_date',
'balance_due',
'invoice_total',
'partial_due',
'custom_value1',
'custom_value2',
'custom_value3',
'custom_value4',
],
'table_columns' => [
'product_key',
'notes',
'cost',
'quantity',
'discount',
'tax_name1',
'line_total'
],
];
public function handle() {
MultiDB::setDB($this->company->db);
App::setLocale($this->contact->preferredLocale());
$this->invoice->load('client');
$path = 'public/'.$this->invoice->client->client_hash.'/invoices/';
$file_path = $path.$this->invoice->number.'.pdf';
$modern = new Modern();
$designer = new Designer($modern, $this->invoice->client->getSetting('invoice_variables'));
//get invoice design
$html = $this->generateInvoiceHtml($designer->build($this->invoice)->getHtml(), $this->invoice, $this->contact);
$this->invoice->load('client');
$path = 'public/' . $this->invoice->client->client_hash . '/invoices/';
$file_path = $path . $this->invoice->number . '.pdf';
//todo - move this to the client creation stage so we don't keep hitting this unnecessarily
Storage::makeDirectory($path, 0755);
$modern = new Modern();
$designer = new Designer($modern, $input_variables);
//\Log::error($html);
//create pdf
$pdf = $this->makePdf(null, null, $html);
//get invoice design
$html = $this->generateInvoiceHtml($designer->build($this->invoice)->getHtml(), $this->invoice);
$path = Storage::put($file_path, $pdf);
//todo - move this to the client creation stage so we don't keep hitting this unnecessarily
Storage::makeDirectory($path, 0755);
return $path;
}
\Log::error($html);
//create pdf
$pdf = $this->makePdf(null, null, $html);
$path = Storage::put($file_path, $pdf);
}
/**
* Returns a PDF stream
*
* @param string $header Header to be included in PDF
* @param string $footer Footer to be included in PDF
* @param string $html The HTML object to be converted into PDF
*
* @return string The PDF string
*/
private function makePdf($header, $footer, $html)
{
return Browsershot::html($html)
//->showBrowserHeaderAndFooter()
//->headerHtml($header)
//->footerHtml($footer)
->deviceScaleFactor(1)
->showBackground()
->waitUntilNetworkIdle(false)->pdf();
//->margins(10,10,10,10)
//->savePdf('test.pdf');
}
/**
* Returns a PDF stream
*
* @param string $header Header to be included in PDF
* @param string $footer Footer to be included in PDF
* @param string $html The HTML object to be converted into PDF
*
* @return string The PDF string
*/
private function makePdf($header, $footer, $html) {
return Browsershot::html($html)
//->showBrowserHeaderAndFooter()
//->headerHtml($header)
//->footerHtml($footer)
->deviceScaleFactor(1)
->showBackground()
->waitUntilNetworkIdle(true) ->pdf();
//->margins(10,10,10,10)
//->savePdf('test.pdf');
}
}

View File

@ -35,6 +35,6 @@ class CreateInvoicePdf implements ShouldQueue
*/
public function handle($event)
{
PdfCreator::dispatch($event->invoice, $event->company);
PdfCreator::dispatch($event->invoice, $event->company, $event->invoice->client->primary_contact()->first());
}
}

View File

@ -32,13 +32,14 @@ use App\Utils\Traits\GeneratesCounter;
use App\Utils\Traits\MakesDates;
use App\Utils\Traits\MakesHash;
use Hashids\Hashids;
use Illuminate\Contracts\Translation\HasLocalePreference;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\URL;
use Laracasts\Presenter\PresentableTrait;
class Client extends BaseModel
class Client extends BaseModel implements HasLocalePreference
{
use PresentableTrait;
use MakesHash;
@ -424,4 +425,18 @@ class Client extends BaseModel
return $payment_urls;
}
public function preferredLocale()
{
$languages = Cache::get('languages');
return $languages->filter(function ($item) {
return $item->id == $this->client->getSetting('language_id');
})->first()->locale;
//$lang = Language::find($this->client->getSetting('language_id'));
//return $lang->locale;
}
}

View File

@ -33,7 +33,6 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Laracasts\Presenter\PresentableTrait;
@ -349,7 +348,7 @@ class Invoice extends BaseModel
if (!Storage::exists($storage_path)) {
event(new InvoiceWasUpdated($this, $this->company));
CreateInvoicePdf::dispatch($this, $this->company);
CreateInvoicePdf::dispatch($this, $this->company, $this->client->primary_contact()->first());
}
return $public_path;
@ -360,7 +359,7 @@ class Invoice extends BaseModel
$storage_path = 'storage/' . $this->client->client_hash . '/invoices/'. $this->number . '.pdf';
if (!Storage::exists($storage_path)) {
CreateInvoicePdf::dispatchNow($this, $this->company);
CreateInvoicePdf::dispatchNow($this, $this->company, $this->client->primary_contact()->first());
}
return $storage_path;

View File

@ -14,79 +14,70 @@ namespace App\Models;
use App\Models\Invoice;
use App\Utils\Traits\Inviteable;
use App\Utils\Traits\MakesDates;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Carbon;
class InvoiceInvitation extends BaseModel
{
use MakesDates;
use SoftDeletes;
use Inviteable;
class InvoiceInvitation extends BaseModel {
use MakesDates;
use SoftDeletes;
use Inviteable;
protected $fillable = [
'id',
'client_contact_id',
];
protected $fillable = [
//'id',
//'client_contact_id',
];
protected $with = [
// 'company',
];
protected $with = [
// 'company',
];
public function entityType()
{
return Invoice::class;
}
public function entityType() {
return Invoice::class ;
}
/**
* @return mixed
*/
public function invoice()
{
return $this->belongsTo(Invoice::class)->withTrashed();
}
/**
* @return mixed
*/
public function invoice() {
return $this->belongsTo(Invoice::class )->withTrashed();
}
/**
* @return mixed
*/
public function contact()
{
return $this->belongsTo(ClientContact::class, 'client_contact_id', 'id')->withTrashed();
}
/**
* @return mixed
*/
public function contact() {
return $this->belongsTo(ClientContact::class , 'client_contact_id', 'id')->withTrashed();
}
/**
* @return mixed
*/
public function user()
{
return $this->belongsTo(User::class)->withTrashed();
}
/**
* @return mixed
*/
public function user() {
return $this->belongsTo(User::class )->withTrashed();
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function company()
{
return $this->belongsTo(Company::class);
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function company() {
return $this->belongsTo(Company::class );
}
public function signatureDiv()
{
if (! $this->signature_base64) {
return false;
}
public function signatureDiv() {
if (!$this->signature_base64) {
return false;
}
return sprintf('<img src="data:image/svg+xml;base64,%s"></img><p/>%s: %s', $this->signature_base64, ctrans('texts.signed'), $this->createClientDate($this->signature_date, $this->contact->client->timezone()->name));
}
return sprintf('<img src="data:image/svg+xml;base64,%s"></img><p/>%s: %s', $this->signature_base64, ctrans('texts.signed'), $this->createClientDate($this->signature_date, $this->contact->client->timezone()->name));
}
public function getName()
{
return $this->key;
}
public function getName() {
return $this->key;
}
public function markViewed()
{
$this->viewed_date = Carbon::now();
$this->save();
}
public function markViewed() {
$this->viewed_date = Carbon::now();
$this->save();
}
}

View File

@ -11,126 +11,119 @@
namespace App\Repositories;
use App\Events\Invoice\InvoiceWasCreated;
use App\Events\Invoice\InvoiceWasUpdated;
use App\Factory\InvoiceInvitationFactory;
use App\Helpers\Invoice\InvoiceSum;
use App\Jobs\Company\UpdateCompanyLedgerWithInvoice;
use App\Jobs\Product\UpdateOrCreateProduct;
use App\Listeners\Invoice\CreateInvoiceInvitation;
use App\Models\ClientContact;
use App\Models\Invoice;
use App\Models\InvoiceInvitation;
use App\Utils\Traits\MakesHash;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
/**
* InvoiceRepository
*/
class InvoiceRepository extends BaseRepository
{
use MakesHash;
/**
* Gets the class name.
*
* @return string The class name.
*/
public function getClassName()
{
return Invoice::class;
}
/**
* Saves the invoices
*
* @param array. $data The invoice data
* @param InvoiceSum|\App\Models\Invoice $invoice The invoice
*
* @return Invoice|InvoiceSum|\App\Models\Invoice|null Returns the invoice object
*/
public function save($data, Invoice $invoice) : ?Invoice
{
/* Always carry forward the initial invoice amount this is important for tracking client balance changes later......*/
$starting_amount = $invoice->amount;
class InvoiceRepository extends BaseRepository {
use MakesHash;
$invoice->fill($data);
/**
* Gets the class name.
*
* @return string The class name.
*/
public function getClassName() {
return Invoice::class ;
}
$invoice->save();
/**
* Saves the invoices
*
* @param array. $data The invoice data
* @param InvoiceSum|\App\Models\Invoice $invoice The invoice
*
* @return Invoice|InvoiceSum|\App\Models\Invoice|null Returns the invoice object
*/
public function save($data, Invoice $invoice):?Invoice {
if (isset($data['client_contacts'])) {
foreach ($data['client_contacts'] as $contact) {
if ($contact['send_invoice'] == 1) {
$client_contact = ClientContact::find($this->decodePrimaryKey($contact['id']));
$client_contact->send_invoice = true;
$client_contact->save();
}
}
}
/* Always carry forward the initial invoice amount this is important for tracking client balance changes later......*/
$starting_amount = $invoice->amount;
if (isset($data['invitations'])) {
$invitations = collect($data['invitations']);
$invoice->fill($data);
/* Get array of Keyss which have been removed from the invitations array and soft delete each invitation */
collect($invoice->invitations->pluck('key'))->diff($invitations->pluck('key'))->each(function ($invitation) {
InvoiceInvitation::destroy($invitation);
});
$invoice->save();
foreach ($data['invitations'] as $invitation) {
$inv = false;
if (isset($data['client_contacts'])) {
foreach ($data['client_contacts'] as $contact) {
if ($contact['send_invoice'] == 1) {
$client_contact = ClientContact::find($this->decodePrimaryKey($contact['id']));
$client_contact->send_invoice = true;
$client_contact->save();
}
}
}
if (array_key_exists('key', $invitation)) {
$inv = InvoiceInvitation::whereKey($invitation['key'])->first();
}
if (isset($data['invitations'])) {
$invitations = collect($data['invitations']);
if (!$inv) {
/* Get array of Keyss which have been removed from the invitations array and soft delete each invitation */
collect($invoice->invitations->pluck('key'))->diff($invitations->pluck('key'))->each(function ($invitation) {
InvoiceInvitation::destroy($invitation);
});
$new_invitation = InvoiceInvitationFactory::create($invoice->company_id, $invoice->user_id);
$new_invitation->fill($invitation);
$new_invitation->invoice_id = $invoice->id;
$new_invitation->client_contact_id = $this->decodePrimaryKey($invitation['client_contact_id']);
$new_invitation->save();
}
}
}
foreach ($data['invitations'] as $invitation) {
$inv = false;
/* If no invitations have been created, this is our fail safe to maintain state*/
if ($invoice->invitations->count() == 0) {
$invoice->service()->createInvitations();
}
if (array_key_exists('key', $invitation)) {
$inv = InvoiceInvitation::whereKey($invitation['key'])->first();
}
$invoice = $invoice->calc()->getInvoice();
$invoice->save();
if (!$inv) {
$finished_amount = $invoice->amount;
$new_invitation = InvoiceInvitationFactory::create($invoice->company_id, $invoice->user_id);
$new_invitation->fill($invitation);
$new_invitation->invoice_id = $invoice->id;
$new_invitation->client_contact_id = $this->decodePrimaryKey($invitation['client_contact_id']);
$new_invitation->save();
/**/
if (($finished_amount != $starting_amount) && ($invoice->status_id != Invoice::STATUS_DRAFT)) {
UpdateCompanyLedgerWithInvoice::dispatchNow($invoice, ($finished_amount - $starting_amount), $invoice->company);
}
}
}
}
$invoice = $invoice->service()->applyNumber()->save();
/* If no invitations have been created, this is our fail safe to maintain state*/
if ($invoice->invitations->count() == 0) {
$invoice->service()->createInvitations();
}
if ($invoice->company->update_products !== false) {
UpdateOrCreateProduct::dispatch($invoice->line_items, $invoice, $invoice->company);
}
$invoice = $invoice->calc()->getInvoice();
return $invoice->fresh();
}
$invoice->save();
/**
* Mark the invoice as sent.
*
* @param \App\Models\Invoice $invoice The invoice
*
* @return Invoice|\App\Models\Invoice|null Return the invoice object
*/
public function markSent(Invoice $invoice) : ?Invoice
{
return $invoice->service()->markSent()->save();
}
$finished_amount = $invoice->amount;
/**/
if (($finished_amount != $starting_amount) && ($invoice->status_id != Invoice::STATUS_DRAFT)) {
UpdateCompanyLedgerWithInvoice::dispatchNow($invoice, ($finished_amount-$starting_amount), $invoice->company);
}
$invoice = $invoice->service()->applyNumber()->save();
if ($invoice->company->update_products !== false) {
UpdateOrCreateProduct::dispatch($invoice->line_items, $invoice, $invoice->company);
}
return $invoice->fresh();
}
/**
* Mark the invoice as sent.
*
* @param \App\Models\Invoice $invoice The invoice
*
* @return Invoice|\App\Models\Invoice|null Return the invoice object
*/
public function markSent(Invoice $invoice):?Invoice {
return $invoice->service()->markSent()->save();
}
}

View File

@ -11,6 +11,7 @@
namespace App\Utils\Traits;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Blade;
use Symfony\Component\Debug\Exception\FatalThrowableError;
@ -29,11 +30,20 @@ trait MakesInvoiceHtml
*
* @return string The invoice string in HTML format
*/
public function generateInvoiceHtml($design, $invoice) :string
public function generateInvoiceHtml($design, $invoice, $contact = null) :string
{
$variables = array_merge($invoice->makeLabels(), $invoice->makeValues());
//$variables = array_merge($invoice->makeLabels(), $invoice->makeValues());
//$design = str_replace(array_keys($variables), array_values($variables), $design);
if(!$contact)
$contact = $invoice->client->primary_contact()->first();
$design = str_replace(array_keys($variables), array_values($variables), $design);
App::setLocale($contact->preferredLocale());
$labels = $invoice->makeLabels();
$values = $invoice->makeValues($contact);
$design = str_replace(array_keys($labels), array_values($labels), $design);
$design = str_replace(array_keys($values), array_values($values), $design);
$data['invoice'] = $invoice;

View File

@ -119,10 +119,10 @@ trait MakesInvoiceValues
'service',
'product_key',
'unit_cost',
'custom_value1',
'custom_value2',
'custom_value3',
'custom_value4',
// 'custom_value1',
// 'custom_value2',
// 'custom_value3',
// 'custom_value4',
'delivery_note',
'date',
'method',
@ -130,6 +130,49 @@ trait MakesInvoiceValues
'reference',
'amount',
'amount_paid',
'invoice1',
'invoice2',
'invoice3',
'invoice4',
'surcharge1',
'surcharge2',
'surcharge3',
'surcharge4',
'client1',
'client2',
'client3',
'client4',
'contact1',
'contact2',
'contact3',
'contact4',
'company1',
'company2',
'company3',
'company4',
];
private static $custom_label_fields = [
'invoice1',
'invoice2',
'invoice3',
'invoice4',
'surcharge1',
'surcharge2',
'surcharge3',
'surcharge4',
'client1',
'client2',
'client3',
'client4',
'contact1',
'contact2',
'contact3',
'contact4',
'company1',
'company2',
'company3',
'company4',
];
/**
@ -150,8 +193,49 @@ trait MakesInvoiceValues
$data['$'.$label . '_label'] = ctrans('texts.'.$label);
}
if($custom_fields && property_exists($custom_fields,'invoice_text1'))
$data['$invoice_text1'] = $custom_fields->invoice_text1;
if($custom_fields)
{
foreach($custom_fields as $key => $value)
{
if(strpos($value, '|') !== false)
{
$value = explode("|", $value);
$value = $value[0];
}
$data['$'.$key.'_label'] = $value;
}
}
/*
Don't forget pipe | strings for dropdowns needs to be filtered
*/
/*
invoice1
invoice2
invoice3
invoice4
surcharge1
surcharge2
surcharge3
surcharge4
client1
client2
client3
client4
contact1
contact2
contact3
contact4
*/
$arrKeysLength = array_map('strlen', array_keys($data));
array_multisort($arrKeysLength, SORT_DESC, $data);
return $data;
}
@ -179,18 +263,18 @@ trait MakesInvoiceValues
$data['$line_tax_labels'] = $this->lineTaxLabels();
$data['$line_tax_values'] = $this->lineTaxValues();
$data['$date'] = $this->date;
$data['$date'] = $this->date ?: '&nbsp;';
$data['$invoice.date'] = &$data['$date'];
$data['$due_date'] = $this->due_date;
$data['$due_date'] = $this->due_date ?: '&nbsp;';
$data['$invoice.due_date'] = &$data['$due_date'];
$data['$number'] = $this->number;
$data['$number'] = $this->number ?: '&nbsp;';
$data['$invoice.number'] = &$data['$number'];
$data['$invoice_number'] = &$data['$number'];
$data['$po_number'] = $this->po_number;
$data['$po_number'] = $this->po_number ?: '&nbsp;';
$data['$invoice.po_number'] = &$data['$po_number'];
$data['$line_taxes'] = $this->makeLineTaxes();
$data['$line_taxes'] = $this->makeLineTaxes() ?: '&nbsp;';
$data['$invoice.line_taxes'] = &$data['$line_taxes'];
$data['$total_taxes'] = $this->makeTotalTaxes();
$data['$total_taxes'] = $this->makeTotalTaxes() ?: '&nbsp;';
$data['$invoice.total_taxes'] = &$data['$total_taxes'];
// $data['$tax'] = ;
// $data['$item'] = ;
@ -199,31 +283,31 @@ trait MakesInvoiceValues
// $data['$quantity'] = ;
// $data['$line_total'] = ;
// $data['$paid_to_date'] = ;
$data['$discount'] = Number::formatMoney($this->calc()->getTotalDiscount(), $this->client);
$data['$discount'] = Number::formatMoney($this->calc()->getTotalDiscount(), $this->client) ?: '&nbsp;';
$data['$invoice.discount'] = &$data['$discount'];
$data['$subtotal'] = Number::formatMoney($this->calc()->getSubTotal(), $this->client);
$data['$subtotal'] = Number::formatMoney($this->calc()->getSubTotal(), $this->client) ?: '&nbsp;';
$data['$invoice.subtotal'] = &$data['$subtotal'];
$data['$balance_due'] = Number::formatMoney($this->balance, $this->client);
$data['$balance_due'] = Number::formatMoney($this->balance, $this->client) ?: '&nbsp;';
$data['$invoice.balance_due'] = &$data['$balance_due'];
$data['$partial_due'] = Number::formatMoney($this->partial, $this->client);
$data['$partial_due'] = Number::formatMoney($this->partial, $this->client) ?: '&nbsp;';
$data['$invoice.partial_due'] = &$data['$partial_due'];
$data['$total'] = Number::formatMoney($this->calc()->getTotal(), $this->client);
$data['$total'] = Number::formatMoney($this->calc()->getTotal(), $this->client) ?: '&nbsp;';
$data['$invoice.total'] = &$data['$total'];
$data['$amount'] = &$data['$total'];
$data['$invoice_total'] = &$data['$total'];
$data['$invoice.amount'] = &$data['$total'];
$data['$balance'] = Number::formatMoney($this->calc()->getBalance(), $this->client);
$data['$balance'] = Number::formatMoney($this->calc()->getBalance(), $this->client) ?: '&nbsp;';
$data['$invoice.balance'] = &$data['$balance'];
$data['$taxes'] = Number::formatMoney($this->calc()->getItemTotalTaxes(), $this->client);
$data['$taxes'] = Number::formatMoney($this->calc()->getItemTotalTaxes(), $this->client) ?: '&nbsp;';
$data['$invoice.taxes'] = &$data['$taxes'];
$data['$terms'] = $this->terms;
$data['$terms'] = $this->terms ?: '&nbsp;';
$data['$invoice.terms'] = &$data['$terms'];
$data['$invoice.custom_value1'] = $this->custom_value1;
$data['$invoice.custom_value2'] = $this->custom_value2;
$data['$invoice.custom_value3'] = $this->custom_value3;
$data['$invoice.custom_value4'] = $this->custom_value4;
$data['$invoice.public_notes'] = $this->public_notes;
$data['$invoice1'] = $this->custom_value1 ?: '&nbsp;';
$data['$invoice2'] = $this->custom_value2 ?: '&nbsp;';
$data['$invoice3'] = $this->custom_value3 ?: '&nbsp;';
$data['$invoice4'] = $this->custom_value4 ?: '&nbsp;';
$data['$invoice.public_notes'] = $this->public_notes ?: '&nbsp;';
// $data['$your_invoice'] = ;
// $data['$quote'] = ;
// $data['$your_quote'] = ;
@ -238,74 +322,74 @@ trait MakesInvoiceValues
// $data['$invoice_to'] = ;
// $data['$quote_to'] = ;
// $data['$details'] = ;
$data['$invoice_no'] = $this->number;
$data['$invoice_no'] = $this->number ?: '&nbsp;';
$data['$invoice.invoice_no'] = &$data['$invoice_no'];
// $data['$quote_no'] = ;
// $data['$valid_until'] = ;
$data['$client_name'] = $this->present()->clientName();
$data['$client1'] = $this->client->custom_value1 ?: '&nbsp;';
$data['$client2'] = $this->client->custom_value2 ?: '&nbsp;';
$data['$client3'] = $this->client->custom_value3 ?: '&nbsp;';
$data['$client4'] = $this->client->custom_value4 ?: '&nbsp;';
$data['$client_name'] = $this->present()->clientName() ?: '&nbsp;';
$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['$address1'] = $this->client->address1 ?: '&nbsp;';
$data['$address2'] = $this->client->address2 ?: '&nbsp;';
$data['$client.address2'] = &$data['$address2'];
$data['$id_number'] = $this->client->id_number;
$data['$client.address1'] = &$data['$address1'];
$data['$client.address'] = &$data['$client_address'];
$data['$client_address'] = $this->present()->address() ?: '&nbsp;';
$data['$id_number'] = $this->client->id_number ?: '&nbsp;';
$data['$client.id_number'] = &$data['$id_number'];
$data['$vat_number'] = $this->client->vat_number;
$data['$vat_number'] = $this->client->vat_number ?: '&nbsp;';
$data['$client.vat_number'] = &$data['$vat_number'];
$data['$website'] = $this->client->present()->website();
$data['$website'] = $this->client->present()->website() ?: '&nbsp;';
$data['$client.website'] = &$data['$website'];
$data['$phone'] = $this->client->present()->phone();
$data['$phone'] = $this->client->present()->phone() ?: '&nbsp;';
$data['$client.phone'] = &$data['$phone'];
$data['$city_state_postal'] = $this->present()->cityStateZip($this->client->city, $this->client->state, $this->client->postal_code, false);
$data['$city_state_postal'] = $this->present()->cityStateZip($this->client->city, $this->client->state, $this->client->postal_code, false) ?: '&nbsp;';
$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['$postal_city_state'] = $this->present()->cityStateZip($this->client->city, $this->client->state, $this->client->postal_code, true) ?: '&nbsp;';
$data['$client.postal_city_state'] = &$data['$postal_city_state'];
$data['$country'] = isset($this->client->country->name) ?: 'No Country Set';
$data['$country'] = isset($this->client->country->name) ? $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['$email'] = isset($this->client->primary_contact()->first()->email) ? $this->client->primary_contact()->first()->email : 'no contact email on record';
$data['$client.email'] = &$data['$email'];
$data['$client.custom_value1'] = $this->client->custom_value1;
$data['$client.custom_value2'] = $this->client->custom_value2;
$data['$client.custom_value3'] = $this->client->custom_value3;
$data['$client.custom_value4'] = $this->client->custom_value4;
if(!$contact)
$contact = $this->client->primary_contact()->first();
$data['$contact_name'] = isset($contact) ? $contact->present()->name() : 'no contact name on record';
$data['$contact.name'] = &$data['$contact_name'];
$data['$contact.custom_value1'] = isset($contact) ? $contact->custom_value1 : '';
$data['$contact.custom_value2'] = isset($contact) ? $contact->custom_value2 : '';
$data['$contact.custom_value3'] = isset($contact) ? $contact->custom_value3 : '';
$data['$contact.custom_value4'] = isset($contact) ? $contact->custom_value4 : '';
$data['$contact1'] = isset($contact) ? $contact->custom_value1 : '&nbsp;';
$data['$contact2'] = isset($contact) ? $contact->custom_value2 : '&nbsp;';
$data['$contact3'] = isset($contact) ? $contact->custom_value3 : '&nbsp;';
$data['$contact4'] = isset($contact) ? $contact->custom_value4 : '&nbsp;';
$data['$company.city_state_postal'] = $this->company->present()->cityStateZip($settings->city, $settings->state, $settings->postal_code, false);
$data['$company.postal_city_state'] = $this->company->present()->cityStateZip($settings->city, $settings->state, $settings->postal_code, true);
$data['$company.name'] = $this->company->present()->name();
$data['$company.city_state_postal'] = $this->company->present()->cityStateZip($settings->city, $settings->state, $settings->postal_code, false) ?: '&nbsp;';
$data['$company.postal_city_state'] = $this->company->present()->cityStateZip($settings->city, $settings->state, $settings->postal_code, true) ?: '&nbsp;';
$data['$company.name'] = $this->company->present()->name() ?: '&nbsp;';
$data['$company.company_name'] = &$data['$company.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.website'] = $settings->website;
$data['$company.address'] = $this->company->present()->address($settings);
$data['$company.address1'] = $settings->address1 ?: '&nbsp;';
$data['$company.address2'] = $settings->address2 ?: '&nbsp;';
$data['$company.city'] = $settings->city ?: '&nbsp;';
$data['$company.state'] = $settings->state ?: '&nbsp;';
$data['$company.postal_code'] = $settings->postal_code ?: '&nbsp;';
$data['$company.country'] = Country::find($settings->country_id)->first()->name ?: '&nbsp;';
$data['$company.phone'] = $settings->phone ?: '&nbsp;';
$data['$company.email'] = $settings->email ?: '&nbsp;';
$data['$company.vat_number'] = $settings->vat_number ?: '&nbsp;';
$data['$company.id_number'] = $settings->id_number ?: '&nbsp;';
$data['$company.website'] = $settings->website ?: '&nbsp;';
$data['$company.address'] = $this->company->present()->address($settings) ?: '&nbsp;';
$logo = $this->company->present()->logo($settings);
$data['$company.logo'] = "<img src='{$logo}' class='w-48' alt='logo'>";
$data['$company.logo'] = "<img src='{$logo}' class='w-48' alt='logo'>" ?: '&nbsp;';
$data['$company_logo'] = &$data['$company.logo'];
$data['$company.custom_value1'] = $this->company->custom_value1;
$data['$company.custom_value2'] = $this->company->custom_value2;
$data['$company.custom_value3'] = $this->company->custom_value3;
$data['$company.custom_value4'] = $this->company->custom_value4;
$data['$company1'] = $settings->custom_value1 ?: '&nbsp;';
$data['$company2'] = $settings->custom_value2 ?: '&nbsp;';
$data['$company3'] = $settings->custom_value3 ?: '&nbsp;';
$data['$company4'] = $settings->custom_value4 ?: '&nbsp;';
//$data['$blank'] = ;
//$data['$surcharge'] = ;
/*
@ -340,6 +424,12 @@ trait MakesInvoiceValues
$data['$amount'] = ;
$data['$amount_paid'] =;
*/
$arrKeysLength = array_map('strlen', array_keys($data));
array_multisort($arrKeysLength, SORT_DESC, $data);
// \Log::error('woop');
//\Log::error(print_r($data,1));
return $data;
}
@ -499,6 +589,26 @@ trait MakesInvoiceValues
$item->discount = $item->discount . '%';
}
}
else
$item->discount = '';
if(isset($item->tax_rate1) && $item->tax_rate1 > 0)
$item->tax_rate1 = $item->tax_rate1."%";
if(isset($item->tax_rate2) && $item->tax_rate2 > 0)
$item->tax_rate2 = $item->tax_rate2."%";
if(isset($item->tax_rate2) && $item->tax_rate2 > 0)
$item->tax_rate2 = $item->tax_rate2."%";
if(isset($item->tax_rate1) && $item->tax_rate1 == 0)
$item->tax_rate1 = '';
if(isset($item->tax_rate2) && $item->tax_rate2 == 0)
$item->tax_rate2 = '';
if(isset($item->tax_rate2) && $item->tax_rate2 == 0)
$item->tax_rate2 = '';
}

View File

@ -2,124 +2,126 @@
return [
/*
|--------------------------------------------------------------------------
| Default source repository type
|--------------------------------------------------------------------------
|
| The default source repository type you want to pull your updates from.
|
*/
/*
|--------------------------------------------------------------------------
| Default source repository type
|--------------------------------------------------------------------------
|
| The default source repository type you want to pull your updates from.
|
*/
'default' => env('SELF_UPDATER_SOURCE', 'github'),
'default' => env('SELF_UPDATER_SOURCE', 'github'),
/*
|--------------------------------------------------------------------------
| Version installed
|--------------------------------------------------------------------------
|
| Set this to the version of your software installed on your system.
|
*/
/*
|--------------------------------------------------------------------------
| Version installed
|--------------------------------------------------------------------------
|
| Set this to the version of your software installed on your system.
|
*/
'version_installed' => env('SELF_UPDATER_VERSION_INSTALLED', ''),
'version_installed' => env('SELF_UPDATER_VERSION_INSTALLED', ''),
/*
|--------------------------------------------------------------------------
| Repository types
|--------------------------------------------------------------------------
|
| A repository can be of different types, which can be specified here.
| Current options:
| - github
| - http
|
*/
/*
|--------------------------------------------------------------------------
| Repository types
|--------------------------------------------------------------------------
|
| A repository can be of different types, which can be specified here.
| Current options:
| - github
| - http
|
*/
'repository_types' => [
'github' => [
'type' => 'github',
'repository_vendor' => env('SELF_UPDATER_REPO_VENDOR', ''),
'repository_name' => env('SELF_UPDATER_REPO_NAME', ''),
'repository_url' => '',
'download_path' => env('SELF_UPDATER_DOWNLOAD_PATH', '/tmp'),
'private_access_token' => env('SELF_UPDATER_GITHUB_PRIVATE_ACCESS_TOKEN', ''),
],
'http' => [
'type' => 'http',
'repository_url' => env('SELF_UPDATER_REPO_URL', ''),
'pkg_filename_format' => env('SELF_UPDATER_PKG_FILENAME_FORMAT', 'v_VERSION_'),
'download_path' => env('SELF_UPDATER_DOWNLOAD_PATH', '/tmp'),
'private_access_token' => env('SELF_UPDATER_HTTP_PRIVATE_ACCESS_TOKEN', ''),
],
],
'repository_types' => [
'github' => [
'type' => 'github',
'repository_vendor' => env('SELF_UPDATER_REPO_VENDOR', ''),
'repository_name' => env('SELF_UPDATER_REPO_NAME', ''),
'repository_url' => '',
'download_path' => env('SELF_UPDATER_DOWNLOAD_PATH', '/tmp'),
'private_access_token' => env('SELF_UPDATER_GITHUB_PRIVATE_ACCESS_TOKEN', ''),
'use_branch' => env('SELF_UPDATER_BRANCH_NAME', 'v2'),
/*
|--------------------------------------------------------------------------
| Exclude folders from update
|--------------------------------------------------------------------------
|
| Specifiy folders which should not be updated and will be skipped during the
| update process.
|
| Here's already a list of good examples to skip. You may want to keep those.
|
*/
],
'http' => [
'type' => 'http',
'repository_url' => env('SELF_UPDATER_REPO_URL', ''),
'pkg_filename_format' => env('SELF_UPDATER_PKG_FILENAME_FORMAT', 'v_VERSION_'),
'download_path' => env('SELF_UPDATER_DOWNLOAD_PATH', '/tmp'),
'private_access_token' => env('SELF_UPDATER_HTTP_PRIVATE_ACCESS_TOKEN', ''),
],
],
'exclude_folders' => [
'node_modules',
'bootstrap/cache',
'bower',
'storage/app',
'storage/framework',
'storage/logs',
'storage/self-update',
'vendor',
],
/*
|--------------------------------------------------------------------------
| Exclude folders from update
|--------------------------------------------------------------------------
|
| Specifiy folders which should not be updated and will be skipped during the
| update process.
|
| Here's already a list of good examples to skip. You may want to keep those.
|
*/
/*
|--------------------------------------------------------------------------
| Event Logging
|--------------------------------------------------------------------------
|
| Configure if fired events should be logged
|
*/
'exclude_folders' => [
'node_modules',
'bootstrap/cache',
'bower',
'storage/app',
'storage/framework',
'storage/logs',
'storage/self-update',
'vendor',
],
'log_events' => env('SELF_UPDATER_LOG_EVENTS', false),
/*
|--------------------------------------------------------------------------
| Event Logging
|--------------------------------------------------------------------------
|
| Configure if fired events should be logged
|
*/
/*
|--------------------------------------------------------------------------
| Mail To Settings
|--------------------------------------------------------------------------
|
| Configure if fired events should be logged
|
*/
'log_events' => env('SELF_UPDATER_LOG_EVENTS', false),
'mail_to' => [
'address' => env('SELF_UPDATER_MAILTO_ADDRESS', ''),
'name' => env('SELF_UPDATER_MAILTO_NAME', ''),
'subject_update_available' => env('SELF_UPDATER_MAILTO_UPDATE_AVAILABLE_SUBJECT', 'Update available'),
'subject_update_succeeded' => env('SELF_UPDATER_MAILTO_UPDATE_SUCCEEDED_SUBJECT', 'Update succeeded'),
],
/*
|--------------------------------------------------------------------------
| Mail To Settings
|--------------------------------------------------------------------------
|
| Configure if fired events should be logged
|
*/
/*
|---------------------------------------------------------------------------
| Register custom artisan commands
|---------------------------------------------------------------------------
*/
'mail_to' => [
'address' => env('SELF_UPDATER_MAILTO_ADDRESS', ''),
'name' => env('SELF_UPDATER_MAILTO_NAME', ''),
'subject_update_available' => env('SELF_UPDATER_MAILTO_UPDATE_AVAILABLE_SUBJECT', 'Update available'),
'subject_update_succeeded' => env('SELF_UPDATER_MAILTO_UPDATE_SUCCEEDED_SUBJECT', 'Update succeeded'),
],
'artisan_commands' => [
'pre_update' => [
//'command:signature' => [
// 'class' => Command class
// 'params' => []
//]
],
'post_update' => [
/*
|---------------------------------------------------------------------------
| Register custom artisan commands
|---------------------------------------------------------------------------
*/
],
],
'artisan_commands' => [
'pre_update' => [
//'command:signature' => [
// 'class' => Command class
// 'params' => []
//]
],
'post_update' => [
],
],
];

View File

@ -10,7 +10,7 @@ $factory->define(App\Models\Company::class, function (Faker $faker) {
'ip' => $faker->ipv4,
'db' => config('database.default'),
'settings' => CompanySettings::defaults(),
'custom_fields' => (object) ['custom1' => '1', 'custom2' => '2', 'custom3'=>'3'],
'custom_fields' => (object) ['invoice1' => '1', 'invoice2' => '2', 'client1'=>'3'],
// 'address1' => $faker->secondaryAddress,
// 'address2' => $faker->address,

View File

@ -1,7 +1,5 @@
<?php
use Illuminate\Http\Request;
/*
|--------------------------------------------------------------------------
| API Routes
@ -14,120 +12,124 @@ use Illuminate\Http\Request;
Route::middleware('auth:api')->get('/user', function (Request $request) {
return $request->user();
return $request->user();
});
*/
*/
Route::group(['middleware' => ['api_secret_check']], function () {
Route::group(['middleware' => ['api_secret_check']],
function () {
Route::post('api/v1/signup', 'AccountController@store')->name('signup.submit');
Route::post('api/v1/oauth_login', 'Auth\LoginController@oauthApiLogin');
Route::post('api/v1/signup', 'AccountController@store')->name('signup.submit');
Route::post('api/v1/oauth_login', 'Auth\LoginController@oauthApiLogin');
});
});
Route::group(['api_secret_check', 'email_db'], function () {
Route::post('api/v1/login', 'Auth\LoginController@apiLogin')->name('login.submit');
Route::post('api/v1/reset_password', 'Auth\ForgotPasswordController@sendResetLinkEmail')->name('password.reset');
Route::post('api/v1/login', 'Auth\LoginController@apiLogin')->name('login.submit');
Route::post('api/v1/reset_password', 'Auth\ForgotPasswordController@sendResetLinkEmail')->name('password.reset');
});
});
Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'api/v1', 'as' => 'api.'], function () {
Route::resource('activities', 'ActivityController'); // name = (clients. index / create / show / update / destroy / edit
Route::resource('activities', 'ActivityController');// name = (clients. index / create / show / update / destroy / edit
Route::resource('clients', 'ClientController'); // name = (clients. index / create / show / update / destroy / edit
Route::resource('clients', 'ClientController');// name = (clients. index / create / show / update / destroy / edit
Route::post('clients/bulk', 'ClientController@bulk')->name('clients.bulk');
Route::post('clients/bulk', 'ClientController@bulk')->name('clients.bulk');
Route::resource('invoices', 'InvoiceController'); // name = (invoices. index / create / show / update / destroy / edit
Route::get('invoices/{invoice}/{action}', 'InvoiceController@action')->name('invoices.action');
Route::post('invoices/bulk', 'InvoiceController@bulk')->name('invoices.bulk');
Route::resource('invoices', 'InvoiceController');// name = (invoices. index / create / show / update / destroy / edit
Route::resource('credits', 'CreditController'); // name = (credits. index / create / show / update / destroy / edit
Route::get('invoices/{invoice}/{action}', 'InvoiceController@action')->name('invoices.action');
Route::get('credits/{credit}/{action}', 'CreditController@action')->name('credits.action');
Route::get('invoice/{invitation_key}/download', 'InvoiceController@downloadPdf')->name('invoices.downloadPdf');
Route::post('credits/bulk', 'CreditController@bulk')->name('credits.bulk');
Route::post('invoices/bulk', 'InvoiceController@bulk')->name('invoices.bulk');
Route::resource('products', 'ProductController'); // name = (products. index / create / show / update / destroy / edit
Route::resource('credits', 'CreditController');// name = (credits. index / create / show / update / destroy / edit
Route::post('products/bulk', 'ProductController@bulk')->name('products.bulk');
Route::get('credits/{credit}/{action}', 'CreditController@action')->name('credits.action');
Route::resource('quotes', 'QuoteController'); // name = (quotes. index / create / show / update / destroy / edit
Route::post('credits/bulk', 'CreditController@bulk')->name('credits.bulk');
Route::post('quotes/bulk', 'QuoteController@bulk')->name('quotes.bulk');
Route::resource('products', 'ProductController');// name = (products. index / create / show / update / destroy / edit
Route::resource('recurring_invoices', 'RecurringInvoiceController'); // name = (recurring_invoices. index / create / show / update / destroy / edit
Route::post('products/bulk', 'ProductController@bulk')->name('products.bulk');
Route::post('recurring_invoices/bulk', 'RecurringInvoiceController@bulk')->name('recurring_invoices.bulk');
Route::resource('quotes', 'QuoteController');// name = (quotes. index / create / show / update / destroy / edit
Route::resource('recurring_quotes', 'RecurringQuoteController'); // name = (recurring_invoices. index / create / show / update / destroy / edit
Route::post('quotes/bulk', 'QuoteController@bulk')->name('quotes.bulk');
Route::post('recurring_quotes/bulk', 'RecurringQuoteController@bulk')->name('recurring_quotes.bulk');
Route::resource('recurring_invoices', 'RecurringInvoiceController');// name = (recurring_invoices. index / create / show / update / destroy / edit
Route::resource('expenses', 'ExpenseController'); // name = (expenses. index / create / show / update / destroy / edit
Route::post('recurring_invoices/bulk', 'RecurringInvoiceController@bulk')->name('recurring_invoices.bulk');
Route::post('expenses/bulk', 'ExpenseController@bulk')->name('expenses.bulk');
Route::resource('recurring_quotes', 'RecurringQuoteController');// name = (recurring_invoices. index / create / show / update / destroy / edit
Route::resource('vendors', 'VendorController'); // name = (vendors. index / create / show / update / destroy / edit
Route::post('recurring_quotes/bulk', 'RecurringQuoteController@bulk')->name('recurring_quotes.bulk');
Route::post('vendors/bulk', 'VendorController@bulk')->name('vendors.bulk');
Route::resource('expenses', 'ExpenseController');// name = (expenses. index / create / show / update / destroy / edit
Route::resource('client_statement', 'ClientStatementController@statement'); // name = (client_statement. index / create / show / update / destroy / edit
Route::post('expenses/bulk', 'ExpenseController@bulk')->name('expenses.bulk');
Route::resource('payments', 'PaymentController'); // name = (payments. index / create / show / update / destroy / edit
Route::resource('vendors', 'VendorController');// name = (vendors. index / create / show / update / destroy / edit
Route::post('payments/refund', 'PaymentController@refund')->name('payments.refund');
Route::post('vendors/bulk', 'VendorController@bulk')->name('vendors.bulk');
Route::post('payments/bulk', 'PaymentController@bulk')->name('payments.bulk');
Route::resource('client_statement', 'ClientStatementController@statement');// name = (client_statement. index / create / show / update / destroy / edit
Route::post('migrate', 'Migration\MigrateController@index')->name('migrate.start');
Route::resource('payments', 'PaymentController');// name = (payments. index / create / show / update / destroy / edit
// Route::resource('users', 'UserController')->middleware('password_protected'); // name = (users. index / create / show / update / destroy / edit
Route::get('users', 'UserController@index');
Route::put('users/{user}', 'UserController@update')->middleware('password_protected');
Route::post('users', 'UserController@store')->middleware('password_protected');
Route::post('users/{user}/attach_to_company', 'UserController@attach')->middleware('password_protected');
Route::delete('users/{user}/detach_from_company', 'UserController@detach')->middleware('password_protected');
Route::post('payments/refund', 'PaymentController@refund')->name('payments.refund');
Route::post('payments/bulk', 'PaymentController@bulk')->name('payments.bulk');
Route::post('migrate', 'Migration\MigrateController@index')->name('migrate.start');
// Route::resource('users', 'UserController')->middleware('password_protected'); // name = (users. index / create / show / update / destroy / edit
Route::get('users', 'UserController@index');
Route::put('users/{user}', 'UserController@update')->middleware('password_protected');
Route::post('users', 'UserController@store')->middleware('password_protected');
Route::post('users/{user}/attach_to_company', 'UserController@attach')->middleware('password_protected');
Route::delete('users/{user}/detach_from_company', 'UserController@detach')->middleware('password_protected');
Route::post('users/bulk', 'UserController@bulk')->name('users.bulk')->middleware('password_protected');
Route::post('migration/purge/{company}', 'MigrationController@purgeCompany')->middleware('password_protected');
Route::post('migration/purge_save_settings/{company}', 'MigrationController@purgeCompanySaveSettings')->middleware('password_protected');
Route::post('migration/start', 'MigrationController@startMigration')->middleware('password_protected');
Route::resource('companies', 'CompanyController');// name = (companies. index / create / show / update / destroy / edit
Route::resource('company_gateways', 'CompanyGatewayController');
Route::resource('group_settings', 'GroupSettingController');
Route::resource('tax_rates', 'TaxRateController');// name = (tasks. index / create / show / update / destroy / edit
Route::post('refresh', 'Auth\LoginController@refresh');
Route::post('templates', 'TemplateController@show')->name('templates.show');
Route::post('self-update', 'SelfUpdateController@update');
/*
Route::resource('tasks', 'TaskController'); // name = (tasks. index / create / show / update / destroy / edit
Route::post('tasks/bulk', 'TaskController@bulk')->name('tasks.bulk');
Route::post('users/bulk', 'UserController@bulk')->name('users.bulk')->middleware('password_protected');
Route::resource('credits', 'CreditController'); // name = (credits. index / create / show / update / destroy / edit
Route::post('migration/purge/{company}', 'MigrationController@purgeCompany')->middleware('password_protected');
Route::post('migration/purge_save_settings/{company}', 'MigrationController@purgeCompanySaveSettings')->middleware('password_protected');
Route::post('migration/start', 'MigrationController@startMigration')->middleware('password_protected');
Route::resource('companies', 'CompanyController'); // name = (companies. index / create / show / update / destroy / edit
Route::resource('company_gateways', 'CompanyGatewayController');
Route::resource('group_settings', 'GroupSettingController');
Route::resource('tax_rates', 'TaxRateController'); // name = (tasks. index / create / show / update / destroy / edit
Route::post('refresh', 'Auth\LoginController@refresh');
Route::post('templates', 'TemplateController@show')->name('templates.show');
/*
Route::resource('tasks', 'TaskController'); // name = (tasks. index / create / show / update / destroy / edit
Route::post('tasks/bulk', 'TaskController@bulk')->name('tasks.bulk');
Route::resource('credits', 'CreditController'); // name = (credits. index / create / show / update / destroy / edit
Route::post('credits/bulk', 'CreditController@bulk')->name('credits.bulk');
Route::post('credits/bulk', 'CreditController@bulk')->name('credits.bulk');
Route::get('settings', 'SettingsController@index')->name('user.settings');
*/
Route::post('support/messages/send', 'Support\Messages\SendingController');
});
Route::get('settings', 'SettingsController@index')->name('user.settings');
*/
Route::post('support/messages/send', 'Support\Messages\SendingController');
});
Route::fallback('BaseController@notFound');

View File

@ -281,6 +281,14 @@ class PaymentTest extends TestCase
$client = ClientFactory::create($this->company->id, $this->user->id);
$client->save();
factory(\App\Models\ClientContact::class)->create([
'user_id' => $this->user->id,
'client_id' => $client->id,
'company_id' =>$this->company->id,
'is_primary' => true,
]);
$this->invoice = InvoiceFactory::create($this->company->id,$this->user->id);//stub the company and user_id
$this->invoice->client_id = $client->id;

View File

@ -39,10 +39,14 @@ class InvoiceDesignTest extends TestCase
'postal_city_state',
'country',
'email',
'custom_value1',
'custom_value2',
'custom_value3',
'custom_value4',
'client1',
'client2',
'client3',
'client4',
'contact1',
'contact2',
'contact3',
'contact4',
],
'company_details' => [
'company_name',
@ -51,10 +55,10 @@ class InvoiceDesignTest extends TestCase
'website',
'email',
'phone',
'custom_value1',
'custom_value2',
'custom_value3',
'custom_value4',
'company1',
'company2',
'company3',
'company4',
],
'company_address' => [
'address1',
@ -62,10 +66,10 @@ class InvoiceDesignTest extends TestCase
'city_state_postal',
'postal_city_state',
'country',
'custom_value1',
'custom_value2',
'custom_value3',
'custom_value4',
'company1',
'company2',
'company3',
'company4',
],
'invoice_details' => [
'invoice_number',
@ -75,10 +79,14 @@ class InvoiceDesignTest extends TestCase
'balance_due',
'invoice_total',
'partial_due',
'custom_value1',
'custom_value2',
'custom_value3',
'custom_value4',
'invoice1',
'invoice2',
'invoice3',
'invoice4',
'surcharge1',
'surcharge2',
'surcharge3',
'surcharge4',
],
'table_columns' => [
'product_key',
@ -99,7 +107,7 @@ class InvoiceDesignTest extends TestCase
//\Log::error($html);
CreateInvoicePdf::dispatchNow($this->invoice, $this->invoice->company);
CreateInvoicePdf::dispatchNow($this->invoice, $this->invoice->company, $this->invoice->client->primary_contact()->first());
}