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); UpdateInvoicePayment::dispatchNow($payment, $payment->company);
} }
//@todo this slow things down, but gives us PDFs of the invoices for inspection whilst debugging. //@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) private function createCredit($client)

View File

@ -217,7 +217,7 @@ class CompanySettings extends BaseSettings
public $embed_documents = false; public $embed_documents = false;
public $all_pages_header = true; public $all_pages_header = true;
public $all_pages_footer = true; public $all_pages_footer = true;
public $invoice_variables = [];
public static $casts = [ public static $casts = [
'auto_email_invoice' => 'bool', 'auto_email_invoice' => 'bool',
@ -369,6 +369,7 @@ class CompanySettings extends BaseSettings
'counter_padding' => 'integer', 'counter_padding' => 'integer',
'design' => 'string', 'design' => 'string',
'website' => 'string', 'website' => 'string',
'invoice_variables' => 'object',
]; ];
/** /**
@ -413,6 +414,7 @@ class CompanySettings extends BaseSettings
$data->date_format_id = (string)config('ninja.i18n.date_format_id'); $data->date_format_id = (string)config('ninja.i18n.date_format_id');
$data->country_id = (string)config('ninja.i18n.country_id'); $data->country_id = (string)config('ninja.i18n.country_id');
$data->translations = (object) []; $data->translations = (object) [];
$data->invoice_variables = (array) self::getInvoiceVariableDefaults();
// $data->email_subject_invoice = EmailTemplateDefaults::emailInvoiceSubject(); // $data->email_subject_invoice = EmailTemplateDefaults::emailInvoiceSubject();
// $data->email_template_invoice = EmailTemplateDefaults:: emailInvoiceTemplate(); // $data->email_template_invoice = EmailTemplateDefaults:: emailInvoiceTemplate();
@ -453,4 +455,53 @@ class CompanySettings extends BaseSettings
return $settings; 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 public static function userSettings() : \stdClass
{ {
return (object)[ 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 private static function userSettingsObject() : \stdClass
{ {
return (object)[ return (object)[
'per_page' => self::$per_page, // 'per_page' => self::$per_page,
]; ];
} }
} }

View File

@ -11,6 +11,7 @@
namespace App\Designs; namespace App\Designs;
use App\Models\Company;
use App\Models\Invoice; use App\Models\Invoice;
class Designer class Designer
@ -24,11 +25,34 @@ class Designer
protected $html; 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->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 public function build(Invoice $invoice) :Designer
{ {
$this->exportVariables() $this->exportVariables($invoice)
->setDesign($this->getSection('header')) ->setDesign($this->getSection('header'))
->setDesign($this->getSection('body')) ->setDesign($this->getSection('body'))
->setDesign($this->getTable($invoice)) ->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}()); 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['$client_details'] = $this->processVariables($this->processInputVariables($company, $this->input_variables['client_details']), $this->clientDetails($company));
$this->exported_variables['$company_details'] = $this->processVariables($this->input_variables['company_details'], $this->companyDetails()); $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->input_variables['company_address'], $this->companyAddress()); $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->input_variables['invoice_details'], $this->invoiceDetails()); $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->input_variables['invoice_details'], $this->invoiceDetails()); $this->exported_variables['$invoice_details'] = $this->processVariables($this->processInputVariables($company, $this->input_variables['invoice_details']), $this->invoiceDetails($company));
return $this; return $this;
} }
@ -153,10 +178,10 @@ class Designer
// $footer = $this->design->footer(); // $footer = $this->design->footer();
// } // }
private function clientDetails() private function clientDetails(Company $company)
{ {
return [ $data = [
'name' => '<p>$client.name</p>', 'name' => '<p>$client.name</p>',
'id_number' => '<p>$client.id_number</p>', 'id_number' => '<p>$client.id_number</p>',
'vat_number' => '<p>$client.vat_number</p>', 'vat_number' => '<p>$client.vat_number</p>',
@ -166,51 +191,59 @@ class Designer
'postal_city_state' => '<p>$client.postal_city_state</p>', 'postal_city_state' => '<p>$client.postal_city_state</p>',
'country' => '<p>$client.country</p>', 'country' => '<p>$client.country</p>',
'email' => '<p>$client.email</p>', 'email' => '<p>$client.email</p>',
'custom_value1' => '<p>$client.custom_value1</p>', 'client1' => '<p>$client1</p>',
'custom_value2' => '<p>$client.custom_value2</p>', 'client2' => '<p>$client2</p>',
'custom_value3' => '<p>$client.custom_value3</p>', 'client3' => '<p>$client3</p>',
'custom_value4' => '<p>$client.custom_value4</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>', 'company_name' => '<span>$company.company_name</span>',
'id_number' => '<span>$company.id_number</span>', 'id_number' => '<span>$company.id_number</span>',
'vat_number' => '<span>$company.vat_number</span>', 'vat_number' => '<span>$company.vat_number</span>',
'website' => '<span>$company.website</span>', 'website' => '<span>$company.website</span>',
'email' => '<span>$company.email</span>', 'email' => '<span>$company.email</span>',
'phone' => '<span>$company.phone</span>', 'phone' => '<span>$company.phone</span>',
'custom_value1' => '<span>$company.custom_value1</span>', 'company1' => '<span>$company1</span>',
'custom_value2' => '<span>$company.custom_value2</span>', 'company2' => '<span>$company2</span>',
'custom_value3' => '<span>$company.custom_value3</span>', 'company3' => '<span>$company3</span>',
'custom_value4' => '<span>$company.custom_value4</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>', 'address1' => '<span>$company.address1</span>',
'address2' => '<span>$company.address1</span>', 'address2' => '<span>$company.address1</span>',
'city_state_postal' => '<span>$company.city_state_postal</span>', 'city_state_postal' => '<span>$company.city_state_postal</span>',
'postal_city_state' => '<span>$company.postal_city_state</span>', 'postal_city_state' => '<span>$company.postal_city_state</span>',
'country' => '<span>$company.country</span>', 'country' => '<span>$company.country</span>',
'custom_value1' => '<span>$company.custom_value1</span>', 'company1' => '<span>$company1</span>',
'custom_value2' => '<span>$company.custom_value2</span>', 'company2' => '<span>$company2</span>',
'custom_value3' => '<span>$company.custom_value3</span>', 'company3' => '<span>$company3</span>',
'custom_value4' => '<span>$company.custom_value4</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>', 'invoice_number' => '<span>$invoice_number</span>',
'po_number' => '<span>$po_number</span>', 'po_number' => '<span>$po_number</span>',
'date' => '<span>$date</span>', 'date' => '<span>$date</span>',
@ -218,11 +251,62 @@ class Designer
'balance_due' => '<span>$balance_due</span>', 'balance_due' => '<span>$balance_due</span>',
'invoice_total' => '<span>$invoice_total</span>', 'invoice_total' => '<span>$invoice_total</span>',
'partial_due' => '<span>$partial_due</span>', 'partial_due' => '<span>$partial_due</span>',
'custom_value1' => '<span>$invoice.custom_value1</span>', 'invoice1' => '<span>$invoice1</span>',
'custom_value2' => '<span>$invoice.custom_value2</span>', 'invoice2' => '<span>$invoice2</span>',
'custom_value3' => '<span>$invoice.custom_value3</span>', 'invoice3' => '<span>$invoice3</span>',
'custom_value4' => '<span>$invoice.custom_value4</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->company_key = $this->createHash();
$company->settings = CompanySettings::defaults(); $company->settings = CompanySettings::defaults();
$company->db = config('database.default'); $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 = ''; $company->subdomain = '';
return $company; return $company;

View File

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

View File

@ -24,25 +24,23 @@ use App\Http\Requests\Invoice\EditInvoiceRequest;
use App\Http\Requests\Invoice\ShowInvoiceRequest; use App\Http\Requests\Invoice\ShowInvoiceRequest;
use App\Http\Requests\Invoice\StoreInvoiceRequest; use App\Http\Requests\Invoice\StoreInvoiceRequest;
use App\Http\Requests\Invoice\UpdateInvoiceRequest; use App\Http\Requests\Invoice\UpdateInvoiceRequest;
use App\Jobs\Entity\ActionEntity;
use App\Jobs\Invoice\EmailInvoice; use App\Jobs\Invoice\EmailInvoice;
use App\Jobs\Invoice\MarkInvoicePaid;
use App\Jobs\Invoice\StoreInvoice; use App\Jobs\Invoice\StoreInvoice;
use App\Models\Invoice; use App\Models\Invoice;
use App\Repositories\BaseRepository;
use App\Repositories\InvoiceRepository; use App\Repositories\InvoiceRepository;
use App\Transformers\InvoiceTransformer; use App\Transformers\InvoiceTransformer;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
/** /**
* Class InvoiceController * Class InvoiceController
* @package App\Http\Controllers\InvoiceController * @package App\Http\Controllers\InvoiceController
*/ */
class InvoiceController extends BaseController class InvoiceController extends BaseController {
{
use MakesHash; use MakesHash;
protected $entity_type = Invoice::class ; protected $entity_type = Invoice::class ;
@ -59,8 +57,7 @@ class InvoiceController extends BaseController
* *
* @param \App\Repositories\InvoiceRepository $invoice_repo The invoice repo * @param \App\Repositories\InvoiceRepository $invoice_repo The invoice repo
*/ */
public function __construct(InvoiceRepository $invoice_repo) public function __construct(InvoiceRepository $invoice_repo) {
{
parent::__construct(); parent::__construct();
$this->invoice_repo = $invoice_repo; $this->invoice_repo = $invoice_repo;
@ -107,8 +104,7 @@ class InvoiceController extends BaseController
* ) * )
* *
*/ */
public function index(InvoiceFilters $filters) public function index(InvoiceFilters $filters) {
{
$invoices = Invoice::filter($filters); $invoices = Invoice::filter($filters);
return $this->listResponse($invoices); return $this->listResponse($invoices);
@ -154,14 +150,12 @@ class InvoiceController extends BaseController
* ) * )
* *
*/ */
public function create(CreateInvoiceRequest $request) public function create(CreateInvoiceRequest $request) {
{
$invoice = InvoiceFactory::create(auth()->user()->company()->id, auth()->user()->id); $invoice = InvoiceFactory::create(auth()->user()->company()->id, auth()->user()->id);
return $this->itemResponse($invoice); return $this->itemResponse($invoice);
} }
/** /**
* Store a newly created resource in storage. * Store a newly created resource in storage.
* *
@ -202,8 +196,7 @@ class InvoiceController extends BaseController
* ) * )
* *
*/ */
public function store(StoreInvoiceRequest $request) public function store(StoreInvoiceRequest $request) {
{
$invoice = $this->invoice_repo->save($request->all(), InvoiceFactory::create(auth()->user()->company()->id, auth()->user()->id)); $invoice = $this->invoice_repo->save($request->all(), InvoiceFactory::create(auth()->user()->company()->id, auth()->user()->id));
$invoice = StoreInvoice::dispatchNow($invoice, $request->all(), $invoice->company);//todo potentially this may return mixed ie PDF/$invoice... need to revisit when we implement UI $invoice = StoreInvoice::dispatchNow($invoice, $request->all(), $invoice->company);//todo potentially this may return mixed ie PDF/$invoice... need to revisit when we implement UI
@ -265,8 +258,7 @@ class InvoiceController extends BaseController
* ) * )
* *
*/ */
public function show(ShowInvoiceRequest $request, Invoice $invoice) public function show(ShowInvoiceRequest $request, Invoice $invoice) {
{
return $this->itemResponse($invoice); return $this->itemResponse($invoice);
} }
@ -321,8 +313,7 @@ class InvoiceController extends BaseController
* ) * )
* *
*/ */
public function edit(EditInvoiceRequest $request, Invoice $invoice) public function edit(EditInvoiceRequest $request, Invoice $invoice) {
{
return $this->itemResponse($invoice); return $this->itemResponse($invoice);
} }
@ -378,10 +369,10 @@ class InvoiceController extends BaseController
* ) * )
* *
*/ */
public function update(UpdateInvoiceRequest $request, Invoice $invoice) public function update(UpdateInvoiceRequest $request, Invoice $invoice) {
{ if ($request->entityIsDeleted($invoice)) {
if($request->entityIsDeleted($invoice))
return $request->disallowUpdate(); return $request->disallowUpdate();
}
$invoice = $this->invoice_repo->save($request->all(), $invoice); $invoice = $this->invoice_repo->save($request->all(), $invoice);
@ -440,8 +431,7 @@ class InvoiceController extends BaseController
* ) * )
* *
*/ */
public function destroy(DestroyInvoiceRequest $request, Invoice $invoice) public function destroy(DestroyInvoiceRequest $request, Invoice $invoice) {
{
$invoice->delete(); $invoice->delete();
return response()->json([], 200); return response()->json([], 200);
@ -499,8 +489,7 @@ class InvoiceController extends BaseController
* ) * )
* *
*/ */
public function bulk() public function bulk() {
{
$action = request()->input('action'); $action = request()->input('action');
$ids = request()->input('ids'); $ids = request()->input('ids');
@ -590,13 +579,11 @@ class InvoiceController extends BaseController
* ) * )
* *
*/ */
public function action(ActionInvoiceRequest $request, Invoice $invoice, $action) public function action(ActionInvoiceRequest $request, Invoice $invoice, $action) {
{
return $this->performAction($invoice, $action); return $this->performAction($invoice, $action);
} }
private function performAction(Invoice $invoice, $action, $bulk = false) private function performAction(Invoice $invoice, $action, $bulk = false) {
{
/*If we are using bulk actions, we don't want to return anything */ /*If we are using bulk actions, we don't want to return anything */
switch ($action) { switch ($action) {
case 'clone_to_invoice': case 'clone_to_invoice':
@ -660,4 +647,13 @@ class InvoiceController extends BaseController
break; break;
} }
} }
public function downloadPdf($invitation_key) {
$invitation = InvoiceInvitation::whereKey($invitation_key)->company()->first();
$contact = $invitation->contact;
$invoice = $invitation->invoice;
return response()->json($invitation_key);
}
} }

View File

@ -11,6 +11,9 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Codedge\Updater\UpdaterManager;
use Illuminate\Foundation\Bus\DispatchesJobs;
class SelfUpdateController extends BaseController class SelfUpdateController extends BaseController
{ {
use DispatchesJobs; 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(); $entity_obj = $class::whereId(request()->input('entity_id'))->company()->first();
} }
$subject = request()->input('subject'); $subject = request()->input('subject') ?: '';
$body = request()->input('body'); $body = request()->input('body') ?: '';
$converter = new CommonMarkConverter([ $converter = new CommonMarkConverter([
'html_input' => 'strip', 'html_input' => 'strip',

View File

@ -14,11 +14,10 @@ namespace App\Jobs\Invoice;
use App\Designs\Designer; use App\Designs\Designer;
use App\Designs\Modern; use App\Designs\Modern;
use App\Libraries\MultiDB; use App\Libraries\MultiDB;
use App\Models\ClientContact;
use App\Models\Company; use App\Models\Company;
use App\Models\Invoice; 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\MakesInvoiceHtml;
use App\Utils\Traits\NumberFormatter; use App\Utils\Traits\NumberFormatter;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
@ -26,121 +25,58 @@ use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Spatie\Browsershot\Browsershot; use Spatie\Browsershot\Browsershot;
use Symfony\Component\Debug\Exception\FatalThrowableError;
class CreateInvoicePdf implements ShouldQueue class CreateInvoicePdf implements ShouldQueue {
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, NumberFormatter, MakesInvoiceHtml; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, NumberFormatter, MakesInvoiceHtml;
public $invoice; public $invoice;
public $company; public $company;
public $contact;
/** /**
* Create a new job instance. * Create a new job instance.
* *
* @return void * @return void
*/ */
public function __construct(Invoice $invoice, Company $company) public function __construct(Invoice $invoice, Company $company, ClientContact $contact) {
{
$this->invoice = $invoice; $this->invoice = $invoice;
$this->company = $company; $this->company = $company;
$this->contact = $contact;
} }
public function handle() public function handle() {
{
MultiDB::setDB($this->company->db); MultiDB::setDB($this->company->db);
App::setLocale($this->contact->preferredLocale());
$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'
],
];
$this->invoice->load('client'); $this->invoice->load('client');
$path = 'public/'.$this->invoice->client->client_hash.'/invoices/'; $path = 'public/'.$this->invoice->client->client_hash.'/invoices/';
$file_path = $path.$this->invoice->number.'.pdf'; $file_path = $path.$this->invoice->number.'.pdf';
$modern = new Modern(); $modern = new Modern();
$designer = new Designer($modern, $input_variables); $designer = new Designer($modern, $this->invoice->client->getSetting('invoice_variables'));
//get invoice design //get invoice design
$html = $this->generateInvoiceHtml($designer->build($this->invoice)->getHtml(), $this->invoice); $html = $this->generateInvoiceHtml($designer->build($this->invoice)->getHtml(), $this->invoice, $this->contact);
//todo - move this to the client creation stage so we don't keep hitting this unnecessarily //todo - move this to the client creation stage so we don't keep hitting this unnecessarily
Storage::makeDirectory($path, 0755); Storage::makeDirectory($path, 0755);
\Log::error($html); //\Log::error($html);
//create pdf //create pdf
$pdf = $this->makePdf(null, null, $html); $pdf = $this->makePdf(null, null, $html);
$path = Storage::put($file_path, $pdf); $path = Storage::put($file_path, $pdf);
return $path;
} }
/** /**
@ -152,15 +88,14 @@ class CreateInvoicePdf implements ShouldQueue
* *
* @return string The PDF string * @return string The PDF string
*/ */
private function makePdf($header, $footer, $html) private function makePdf($header, $footer, $html) {
{
return Browsershot::html($html) return Browsershot::html($html)
//->showBrowserHeaderAndFooter() //->showBrowserHeaderAndFooter()
//->headerHtml($header) //->headerHtml($header)
//->footerHtml($footer) //->footerHtml($footer)
->deviceScaleFactor(1) ->deviceScaleFactor(1)
->showBackground() ->showBackground()
->waitUntilNetworkIdle(false)->pdf(); ->waitUntilNetworkIdle(true) ->pdf();
//->margins(10,10,10,10) //->margins(10,10,10,10)
//->savePdf('test.pdf'); //->savePdf('test.pdf');
} }

View File

@ -35,6 +35,6 @@ class CreateInvoicePdf implements ShouldQueue
*/ */
public function handle($event) 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\MakesDates;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use Hashids\Hashids; use Hashids\Hashids;
use Illuminate\Contracts\Translation\HasLocalePreference;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\URL; use Illuminate\Support\Facades\URL;
use Laracasts\Presenter\PresentableTrait; use Laracasts\Presenter\PresentableTrait;
class Client extends BaseModel class Client extends BaseModel implements HasLocalePreference
{ {
use PresentableTrait; use PresentableTrait;
use MakesHash; use MakesHash;
@ -424,4 +425,18 @@ class Client extends BaseModel
return $payment_urls; 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\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Laracasts\Presenter\PresentableTrait; use Laracasts\Presenter\PresentableTrait;
@ -349,7 +348,7 @@ class Invoice extends BaseModel
if (!Storage::exists($storage_path)) { if (!Storage::exists($storage_path)) {
event(new InvoiceWasUpdated($this, $this->company)); event(new InvoiceWasUpdated($this, $this->company));
CreateInvoicePdf::dispatch($this, $this->company); CreateInvoicePdf::dispatch($this, $this->company, $this->client->primary_contact()->first());
} }
return $public_path; return $public_path;
@ -360,7 +359,7 @@ class Invoice extends BaseModel
$storage_path = 'storage/' . $this->client->client_hash . '/invoices/'. $this->number . '.pdf'; $storage_path = 'storage/' . $this->client->client_hash . '/invoices/'. $this->number . '.pdf';
if (!Storage::exists($storage_path)) { if (!Storage::exists($storage_path)) {
CreateInvoicePdf::dispatchNow($this, $this->company); CreateInvoicePdf::dispatchNow($this, $this->company, $this->client->primary_contact()->first());
} }
return $storage_path; return $storage_path;

View File

@ -14,64 +14,57 @@ namespace App\Models;
use App\Models\Invoice; use App\Models\Invoice;
use App\Utils\Traits\Inviteable; use App\Utils\Traits\Inviteable;
use App\Utils\Traits\MakesDates; use App\Utils\Traits\MakesDates;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
class InvoiceInvitation extends BaseModel class InvoiceInvitation extends BaseModel {
{
use MakesDates; use MakesDates;
use SoftDeletes; use SoftDeletes;
use Inviteable; use Inviteable;
protected $fillable = [ protected $fillable = [
'id', //'id',
'client_contact_id', //'client_contact_id',
]; ];
protected $with = [ protected $with = [
// 'company', // 'company',
]; ];
public function entityType() public function entityType() {
{
return Invoice::class ; return Invoice::class ;
} }
/** /**
* @return mixed * @return mixed
*/ */
public function invoice() public function invoice() {
{
return $this->belongsTo(Invoice::class )->withTrashed(); return $this->belongsTo(Invoice::class )->withTrashed();
} }
/** /**
* @return mixed * @return mixed
*/ */
public function contact() public function contact() {
{
return $this->belongsTo(ClientContact::class , 'client_contact_id', 'id')->withTrashed(); return $this->belongsTo(ClientContact::class , 'client_contact_id', 'id')->withTrashed();
} }
/** /**
* @return mixed * @return mixed
*/ */
public function user() public function user() {
{
return $this->belongsTo(User::class )->withTrashed(); return $this->belongsTo(User::class )->withTrashed();
} }
/** /**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/ */
public function company() public function company() {
{
return $this->belongsTo(Company::class ); return $this->belongsTo(Company::class );
} }
public function signatureDiv() public function signatureDiv() {
{
if (!$this->signature_base64) { if (!$this->signature_base64) {
return false; return false;
} }
@ -79,13 +72,11 @@ class InvoiceInvitation extends BaseModel
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() public function getName() {
{
return $this->key; return $this->key;
} }
public function markViewed() public function markViewed() {
{
$this->viewed_date = Carbon::now(); $this->viewed_date = Carbon::now();
$this->save(); $this->save();
} }

View File

@ -11,25 +11,21 @@
namespace App\Repositories; namespace App\Repositories;
use App\Events\Invoice\InvoiceWasCreated;
use App\Events\Invoice\InvoiceWasUpdated;
use App\Factory\InvoiceInvitationFactory; use App\Factory\InvoiceInvitationFactory;
use App\Helpers\Invoice\InvoiceSum;
use App\Jobs\Company\UpdateCompanyLedgerWithInvoice; use App\Jobs\Company\UpdateCompanyLedgerWithInvoice;
use App\Jobs\Product\UpdateOrCreateProduct; use App\Jobs\Product\UpdateOrCreateProduct;
use App\Listeners\Invoice\CreateInvoiceInvitation;
use App\Models\ClientContact; use App\Models\ClientContact;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\InvoiceInvitation; use App\Models\InvoiceInvitation;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
/** /**
* InvoiceRepository * InvoiceRepository
*/ */
class InvoiceRepository extends BaseRepository
{ class InvoiceRepository extends BaseRepository {
use MakesHash; use MakesHash;
/** /**
@ -37,8 +33,7 @@ class InvoiceRepository extends BaseRepository
* *
* @return string The class name. * @return string The class name.
*/ */
public function getClassName() public function getClassName() {
{
return Invoice::class ; return Invoice::class ;
} }
@ -50,8 +45,7 @@ class InvoiceRepository extends BaseRepository
* *
* @return Invoice|InvoiceSum|\App\Models\Invoice|null Returns the invoice object * @return Invoice|InvoiceSum|\App\Models\Invoice|null Returns the invoice object
*/ */
public function save($data, Invoice $invoice) : ?Invoice public function save($data, Invoice $invoice):?Invoice {
{
/* Always carry forward the initial invoice amount this is important for tracking client balance changes later......*/ /* Always carry forward the initial invoice amount this is important for tracking client balance changes later......*/
$starting_amount = $invoice->amount; $starting_amount = $invoice->amount;
@ -129,8 +123,7 @@ class InvoiceRepository extends BaseRepository
* *
* @return Invoice|\App\Models\Invoice|null Return the invoice object * @return Invoice|\App\Models\Invoice|null Return the invoice object
*/ */
public function markSent(Invoice $invoice) : ?Invoice public function markSent(Invoice $invoice):?Invoice {
{
return $invoice->service()->markSent()->save(); return $invoice->service()->markSent()->save();
} }
} }

View File

@ -11,6 +11,7 @@
namespace App\Utils\Traits; namespace App\Utils\Traits;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Blade; use Illuminate\Support\Facades\Blade;
use Symfony\Component\Debug\Exception\FatalThrowableError; use Symfony\Component\Debug\Exception\FatalThrowableError;
@ -29,11 +30,20 @@ trait MakesInvoiceHtml
* *
* @return string The invoice string in HTML format * @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; $data['invoice'] = $invoice;

View File

@ -119,10 +119,10 @@ trait MakesInvoiceValues
'service', 'service',
'product_key', 'product_key',
'unit_cost', 'unit_cost',
'custom_value1', // 'custom_value1',
'custom_value2', // 'custom_value2',
'custom_value3', // 'custom_value3',
'custom_value4', // 'custom_value4',
'delivery_note', 'delivery_note',
'date', 'date',
'method', 'method',
@ -130,6 +130,49 @@ trait MakesInvoiceValues
'reference', 'reference',
'amount', 'amount',
'amount_paid', '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); $data['$'.$label . '_label'] = ctrans('texts.'.$label);
} }
if($custom_fields && property_exists($custom_fields,'invoice_text1')) if($custom_fields)
$data['$invoice_text1'] = $custom_fields->invoice_text1; {
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; return $data;
} }
@ -179,18 +263,18 @@ trait MakesInvoiceValues
$data['$line_tax_labels'] = $this->lineTaxLabels(); $data['$line_tax_labels'] = $this->lineTaxLabels();
$data['$line_tax_values'] = $this->lineTaxValues(); $data['$line_tax_values'] = $this->lineTaxValues();
$data['$date'] = $this->date; $data['$date'] = $this->date ?: '&nbsp;';
$data['$invoice.date'] = &$data['$date']; $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['$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['$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['$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['$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['$invoice.total_taxes'] = &$data['$total_taxes'];
// $data['$tax'] = ; // $data['$tax'] = ;
// $data['$item'] = ; // $data['$item'] = ;
@ -199,31 +283,31 @@ trait MakesInvoiceValues
// $data['$quantity'] = ; // $data['$quantity'] = ;
// $data['$line_total'] = ; // $data['$line_total'] = ;
// $data['$paid_to_date'] = ; // $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['$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['$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['$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['$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['$invoice.total'] = &$data['$total'];
$data['$amount'] = &$data['$total']; $data['$amount'] = &$data['$total'];
$data['$invoice_total'] = &$data['$total']; $data['$invoice_total'] = &$data['$total'];
$data['$invoice.amount'] = &$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['$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['$invoice.taxes'] = &$data['$taxes'];
$data['$terms'] = $this->terms; $data['$terms'] = $this->terms ?: '&nbsp;';
$data['$invoice.terms'] = &$data['$terms']; $data['$invoice.terms'] = &$data['$terms'];
$data['$invoice.custom_value1'] = $this->custom_value1; $data['$invoice1'] = $this->custom_value1 ?: '&nbsp;';
$data['$invoice.custom_value2'] = $this->custom_value2; $data['$invoice2'] = $this->custom_value2 ?: '&nbsp;';
$data['$invoice.custom_value3'] = $this->custom_value3; $data['$invoice3'] = $this->custom_value3 ?: '&nbsp;';
$data['$invoice.custom_value4'] = $this->custom_value4; $data['$invoice4'] = $this->custom_value4 ?: '&nbsp;';
$data['$invoice.public_notes'] = $this->public_notes; $data['$invoice.public_notes'] = $this->public_notes ?: '&nbsp;';
// $data['$your_invoice'] = ; // $data['$your_invoice'] = ;
// $data['$quote'] = ; // $data['$quote'] = ;
// $data['$your_quote'] = ; // $data['$your_quote'] = ;
@ -238,74 +322,74 @@ trait MakesInvoiceValues
// $data['$invoice_to'] = ; // $data['$invoice_to'] = ;
// $data['$quote_to'] = ; // $data['$quote_to'] = ;
// $data['$details'] = ; // $data['$details'] = ;
$data['$invoice_no'] = $this->number; $data['$invoice_no'] = $this->number ?: '&nbsp;';
$data['$invoice.invoice_no'] = &$data['$invoice_no']; $data['$invoice.invoice_no'] = &$data['$invoice_no'];
// $data['$quote_no'] = ; // $data['$quote_no'] = ;
// $data['$valid_until'] = ; // $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.name'] = &$data['$client_name'];
$data['$client_address'] = $this->present()->address(); $data['$address1'] = $this->client->address1 ?: '&nbsp;';
$data['$client.address'] = &$data['$client_address']; $data['$address2'] = $this->client->address2 ?: '&nbsp;';
$data['$address1'] = $this->client->address1;
$data['$client.address1'] = &$data['$address1'];
$data['$address2'] = $this->client->address2;
$data['$client.address2'] = &$data['$address2']; $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['$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['$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['$client.website'] = &$data['$website'];
$data['$phone'] = $this->client->present()->phone(); $data['$phone'] = $this->client->present()->phone() ?: '&nbsp;';
$data['$client.phone'] = &$data['$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['$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['$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['$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['$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.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) if(!$contact)
$contact = $this->client->primary_contact()->first(); $contact = $this->client->primary_contact()->first();
$data['$contact_name'] = isset($contact) ? $contact->present()->name() : 'no contact name on record'; $data['$contact_name'] = isset($contact) ? $contact->present()->name() : 'no contact name on record';
$data['$contact.name'] = &$data['$contact_name']; $data['$contact.name'] = &$data['$contact_name'];
$data['$contact.custom_value1'] = isset($contact) ? $contact->custom_value1 : ''; $data['$contact1'] = isset($contact) ? $contact->custom_value1 : '&nbsp;';
$data['$contact.custom_value2'] = isset($contact) ? $contact->custom_value2 : ''; $data['$contact2'] = isset($contact) ? $contact->custom_value2 : '&nbsp;';
$data['$contact.custom_value3'] = isset($contact) ? $contact->custom_value3 : ''; $data['$contact3'] = isset($contact) ? $contact->custom_value3 : '&nbsp;';
$data['$contact.custom_value4'] = isset($contact) ? $contact->custom_value4 : ''; $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.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); $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(); $data['$company.name'] = $this->company->present()->name() ?: '&nbsp;';
$data['$company.company_name'] = &$data['$company.name']; $data['$company.company_name'] = &$data['$company.name'];
$data['$company.address1'] = $settings->address1; $data['$company.address1'] = $settings->address1 ?: '&nbsp;';
$data['$company.address2'] = $settings->address2; $data['$company.address2'] = $settings->address2 ?: '&nbsp;';
$data['$company.city'] = $settings->city; $data['$company.city'] = $settings->city ?: '&nbsp;';
$data['$company.state'] = $settings->state; $data['$company.state'] = $settings->state ?: '&nbsp;';
$data['$company.postal_code'] = $settings->postal_code; $data['$company.postal_code'] = $settings->postal_code ?: '&nbsp;';
$data['$company.country'] = Country::find($settings->country_id)->first()->name; $data['$company.country'] = Country::find($settings->country_id)->first()->name ?: '&nbsp;';
$data['$company.phone'] = $settings->phone; $data['$company.phone'] = $settings->phone ?: '&nbsp;';
$data['$company.email'] = $settings->email; $data['$company.email'] = $settings->email ?: '&nbsp;';
$data['$company.vat_number'] = $settings->vat_number; $data['$company.vat_number'] = $settings->vat_number ?: '&nbsp;';
$data['$company.id_number'] = $settings->id_number; $data['$company.id_number'] = $settings->id_number ?: '&nbsp;';
$data['$company.website'] = $settings->website; $data['$company.website'] = $settings->website ?: '&nbsp;';
$data['$company.address'] = $this->company->present()->address($settings); $data['$company.address'] = $this->company->present()->address($settings) ?: '&nbsp;';
$logo = $this->company->present()->logo($settings); $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_logo'] = &$data['$company.logo'];
$data['$company.custom_value1'] = $this->company->custom_value1; $data['$company1'] = $settings->custom_value1 ?: '&nbsp;';
$data['$company.custom_value2'] = $this->company->custom_value2; $data['$company2'] = $settings->custom_value2 ?: '&nbsp;';
$data['$company.custom_value3'] = $this->company->custom_value3; $data['$company3'] = $settings->custom_value3 ?: '&nbsp;';
$data['$company.custom_value4'] = $this->company->custom_value4; $data['$company4'] = $settings->custom_value4 ?: '&nbsp;';
//$data['$blank'] = ; //$data['$blank'] = ;
//$data['$surcharge'] = ; //$data['$surcharge'] = ;
/* /*
@ -340,6 +424,12 @@ trait MakesInvoiceValues
$data['$amount'] = ; $data['$amount'] = ;
$data['$amount_paid'] =; $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; return $data;
} }
@ -499,6 +589,26 @@ trait MakesInvoiceValues
$item->discount = $item->discount . '%'; $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

@ -44,6 +44,8 @@ return [
'repository_url' => '', 'repository_url' => '',
'download_path' => env('SELF_UPDATER_DOWNLOAD_PATH', '/tmp'), 'download_path' => env('SELF_UPDATER_DOWNLOAD_PATH', '/tmp'),
'private_access_token' => env('SELF_UPDATER_GITHUB_PRIVATE_ACCESS_TOKEN', ''), 'private_access_token' => env('SELF_UPDATER_GITHUB_PRIVATE_ACCESS_TOKEN', ''),
'use_branch' => env('SELF_UPDATER_BRANCH_NAME', 'v2'),
], ],
'http' => [ 'http' => [
'type' => 'http', 'type' => 'http',

View File

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

View File

@ -1,7 +1,5 @@
<?php <?php
use Illuminate\Http\Request;
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| API Routes | API Routes
@ -18,7 +16,8 @@ Route::middleware('auth:api')->get('/user', function (Request $request) {
}); });
*/ */
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/signup', 'AccountController@store')->name('signup.submit');
Route::post('api/v1/oauth_login', 'Auth\LoginController@oauthApiLogin'); Route::post('api/v1/oauth_login', 'Auth\LoginController@oauthApiLogin');
@ -44,6 +43,8 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a
Route::get('invoices/{invoice}/{action}', 'InvoiceController@action')->name('invoices.action'); Route::get('invoices/{invoice}/{action}', 'InvoiceController@action')->name('invoices.action');
Route::get('invoice/{invitation_key}/download', 'InvoiceController@downloadPdf')->name('invoices.downloadPdf');
Route::post('invoices/bulk', 'InvoiceController@bulk')->name('invoices.bulk'); Route::post('invoices/bulk', 'InvoiceController@bulk')->name('invoices.bulk');
Route::resource('credits', 'CreditController');// name = (credits. index / create / show / update / destroy / edit Route::resource('credits', 'CreditController');// name = (credits. index / create / show / update / destroy / edit
@ -93,7 +94,6 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a
Route::post('users/{user}/attach_to_company', 'UserController@attach')->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::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('users/bulk', 'UserController@bulk')->name('users.bulk')->middleware('password_protected');
Route::post('migration/purge/{company}', 'MigrationController@purgeCompany')->middleware('password_protected'); Route::post('migration/purge/{company}', 'MigrationController@purgeCompany')->middleware('password_protected');
@ -112,6 +112,8 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a
Route::post('templates', 'TemplateController@show')->name('templates.show'); 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::resource('tasks', 'TaskController'); // name = (tasks. index / create / show / update / destroy / edit

View File

@ -281,6 +281,14 @@ class PaymentTest extends TestCase
$client = ClientFactory::create($this->company->id, $this->user->id); $client = ClientFactory::create($this->company->id, $this->user->id);
$client->save(); $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 = InvoiceFactory::create($this->company->id,$this->user->id);//stub the company and user_id
$this->invoice->client_id = $client->id; $this->invoice->client_id = $client->id;

View File

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