mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
commit
9c174984cf
2
.github/workflows/phpunit.yml
vendored
2
.github/workflows/phpunit.yml
vendored
@ -105,4 +105,4 @@ jobs:
|
||||
|
||||
- name: Run php-cs-fixer
|
||||
run: |
|
||||
vendor/bin/php-cs-fixer fix
|
||||
vendor/bin/php-cs-fixer fix
|
||||
|
@ -1 +1 @@
|
||||
5.1.8
|
||||
5.1.9
|
@ -136,7 +136,7 @@ class CompanySettings extends BaseSettings
|
||||
public $tax_name3 = ''; //@TODO where do we use this?
|
||||
public $tax_rate3 = 0; //@TODO where do we use this?
|
||||
public $payment_type_id = '0'; //@TODO where do we use this?
|
||||
public $invoice_fields = ''; //@TODO is this redundant, we store this in the custom_fields on the company?
|
||||
// public $invoice_fields = ''; //@TODO is this redundant, we store this in the custom_fields on the company?
|
||||
|
||||
public $show_accept_invoice_terms = false; //@TODO ben to confirm
|
||||
public $show_accept_quote_terms = false; //@TODO ben to confirm
|
||||
@ -392,7 +392,7 @@ class CompanySettings extends BaseSettings
|
||||
'invoice_number_pattern' => 'string',
|
||||
'invoice_number_counter' => 'integer',
|
||||
'invoice_design_id' => 'string',
|
||||
'invoice_fields' => 'string',
|
||||
// 'invoice_fields' => 'string',
|
||||
'invoice_taxes' => 'int',
|
||||
//'enabled_item_tax_rates' => 'int',
|
||||
'invoice_footer' => 'string',
|
||||
|
@ -99,21 +99,21 @@ class Handler extends ExceptionHandler
|
||||
|
||||
private function validException($exception)
|
||||
{
|
||||
if (strpos($exception->getMessage(), 'file_put_contents') !== false) {
|
||||
if (strpos($exception->getMessage(), 'file_put_contents') !== false)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strpos($exception->getMessage(), 'Permission denied') !== false) {
|
||||
if (strpos($exception->getMessage(), 'Permission denied') !== false)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strpos($exception->getMessage(), 'flock()') !== false) {
|
||||
if (strpos($exception->getMessage(), 'flock()') !== false)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strpos($exception->getMessage(), 'expects parameter 1 to be resource') !== false) {
|
||||
if (strpos($exception->getMessage(), 'expects parameter 1 to be resource') !== false)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strpos($exception->getMessage(), 'fwrite()') !== false)
|
||||
return false;
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -27,7 +27,8 @@ trait CustomValuer
|
||||
|
||||
public function valuerTax($custom_value, $has_custom_invoice_taxes)
|
||||
{
|
||||
if (isset($custom_value) && is_numeric($custom_value) && $has_custom_invoice_taxes === true) {
|
||||
|
||||
if (isset($custom_value) && is_numeric($custom_value) && $has_custom_invoice_taxes) {
|
||||
return round($custom_value * ($this->invoice->tax_rate1 / 100), 2) + round($custom_value * ($this->invoice->tax_rate2 / 100), 2) + round($custom_value * ($this->invoice->tax_rate3 / 100), 2);
|
||||
}
|
||||
|
||||
|
@ -57,8 +57,8 @@ class InvoiceSum
|
||||
{
|
||||
$this->calculateLineItems()
|
||||
->calculateDiscount()
|
||||
->calculateCustomValues()
|
||||
->calculateInvoiceTaxes()
|
||||
->calculateCustomValues()
|
||||
->setTaxMap()
|
||||
->calculateTotals()
|
||||
->calculateBalance()
|
||||
@ -89,16 +89,17 @@ class InvoiceSum
|
||||
|
||||
private function calculateCustomValues()
|
||||
{
|
||||
$this->total_taxes += $this->valuerTax($this->invoice->custom_surcharge1, $this->invoice->custom_surcharge_taxes1);
|
||||
|
||||
$this->total_taxes += $this->valuerTax($this->invoice->custom_surcharge1, $this->invoice->custom_surcharge_tax1);
|
||||
$this->total_custom_values += $this->valuer($this->invoice->custom_surcharge1);
|
||||
|
||||
$this->total_taxes += $this->valuerTax($this->invoice->custom_surcharge2, $this->invoice->custom_surcharge_taxes2);
|
||||
$this->total_taxes += $this->valuerTax($this->invoice->custom_surcharge2, $this->invoice->custom_surcharge_tax2);
|
||||
$this->total_custom_values += $this->valuer($this->invoice->custom_surcharge2);
|
||||
|
||||
$this->total_taxes += $this->valuerTax($this->invoice->custom_surcharge3, $this->invoice->custom_surcharge_taxes3);
|
||||
$this->total_taxes += $this->valuerTax($this->invoice->custom_surcharge3, $this->invoice->custom_surcharge_tax3);
|
||||
$this->total_custom_values += $this->valuer($this->invoice->custom_surcharge3);
|
||||
|
||||
$this->total_taxes += $this->valuerTax($this->invoice->custom_surcharge4, $this->invoice->custom_surcharge_taxes4);
|
||||
$this->total_taxes += $this->valuerTax($this->invoice->custom_surcharge4, $this->invoice->custom_surcharge_tax4);
|
||||
$this->total_custom_values += $this->valuer($this->invoice->custom_surcharge4);
|
||||
|
||||
$this->total += $this->total_custom_values;
|
||||
@ -108,24 +109,25 @@ class InvoiceSum
|
||||
|
||||
private function calculateInvoiceTaxes()
|
||||
{
|
||||
if ($this->invoice->tax_rate1 > 0) {
|
||||
|
||||
if (strlen($this->invoice->tax_name1) > 1) {
|
||||
$tax = $this->taxer($this->total, $this->invoice->tax_rate1);
|
||||
$this->total_taxes += $tax;
|
||||
$this->total_tax_map[] = ['name' => $this->invoice->tax_name1.' '.floatval($this->invoice->tax_rate1).'%', 'total' => $tax];
|
||||
}
|
||||
|
||||
if ($this->invoice->tax_rate2 > 0) {
|
||||
if (strlen($this->invoice->tax_name2) > 1) {
|
||||
$tax = $this->taxer($this->total, $this->invoice->tax_rate2);
|
||||
$this->total_taxes += $tax;
|
||||
$this->total_tax_map[] = ['name' => $this->invoice->tax_name2.' '.floatval($this->invoice->tax_rate2).'%', 'total' => $tax];
|
||||
}
|
||||
|
||||
if ($this->invoice->tax_rate3 > 0) {
|
||||
if (strlen($this->invoice->tax_name3) > 1) {
|
||||
$tax = $this->taxer($this->total, $this->invoice->tax_rate3);
|
||||
$this->total_taxes += $tax;
|
||||
$this->total_tax_map[] = ['name' => $this->invoice->tax_name3.' '.floatval($this->invoice->tax_rate3).'%', 'total' => $tax];
|
||||
}
|
||||
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -299,7 +301,7 @@ class InvoiceSum
|
||||
}
|
||||
|
||||
public function getTaxMap()
|
||||
{
|
||||
{
|
||||
return $this->tax_map;
|
||||
}
|
||||
|
||||
|
@ -89,16 +89,16 @@ class InvoiceSumInclusive
|
||||
|
||||
private function calculateCustomValues()
|
||||
{
|
||||
$this->total_taxes += $this->valuerTax($this->invoice->custom_surcharge1, $this->invoice->custom_surcharge_taxes1);
|
||||
$this->total_taxes += $this->valuerTax($this->invoice->custom_surcharge1, $this->invoice->custom_surcharge_tax1);
|
||||
$this->total_custom_values += $this->valuer($this->invoice->custom_surcharge1);
|
||||
|
||||
$this->total_taxes += $this->valuerTax($this->invoice->custom_surcharge2, $this->invoice->custom_surcharge_taxes2);
|
||||
$this->total_taxes += $this->valuerTax($this->invoice->custom_surcharge2, $this->invoice->custom_surcharge_tax2);
|
||||
$this->total_custom_values += $this->valuer($this->invoice->custom_surcharge2);
|
||||
|
||||
$this->total_taxes += $this->valuerTax($this->invoice->custom_surcharge3, $this->invoice->custom_surcharge_taxes3);
|
||||
$this->total_taxes += $this->valuerTax($this->invoice->custom_surcharge3, $this->invoice->custom_surcharge_tax3);
|
||||
$this->total_custom_values += $this->valuer($this->invoice->custom_surcharge3);
|
||||
|
||||
$this->total_taxes += $this->valuerTax($this->invoice->custom_surcharge4, $this->invoice->custom_surcharge_taxes4);
|
||||
$this->total_taxes += $this->valuerTax($this->invoice->custom_surcharge4, $this->invoice->custom_surcharge_tax4);
|
||||
$this->total_custom_values += $this->valuer($this->invoice->custom_surcharge4);
|
||||
|
||||
$this->total += $this->total_custom_values;
|
||||
|
@ -54,11 +54,11 @@ class InvoiceController extends Controller
|
||||
'invoice' => $invoice,
|
||||
];
|
||||
|
||||
if ($request->query('mode') === 'portal') {
|
||||
return $this->render('invoices.show', $data);
|
||||
if ($request->query('mode') === 'fullscreen') {
|
||||
return response()->file($invoice->pdf_file_path(null, 'path'));
|
||||
}
|
||||
|
||||
return $this->render('invoices.show.fullscreen', $data);
|
||||
return $this->render('invoices.show', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -35,7 +35,7 @@ class QuoteController extends Controller
|
||||
*
|
||||
* @param ShowQuoteRequest $request
|
||||
* @param Quote $quote
|
||||
* @return Factory|View
|
||||
* @return Factory|View|\Symfony\Component\HttpFoundation\BinaryFileResponse
|
||||
*/
|
||||
public function show(ShowQuoteRequest $request, Quote $quote)
|
||||
{
|
||||
@ -43,11 +43,11 @@ class QuoteController extends Controller
|
||||
'quote' => $quote,
|
||||
];
|
||||
|
||||
if ($request->query('mode') === 'portal') {
|
||||
return $this->render('quotes.show', $data);
|
||||
if ($request->query('mode') === 'fullscreen') {
|
||||
return response()->file($quote->pdf_file_path(null, 'path'));
|
||||
}
|
||||
|
||||
return $this->render('quotes.show.fullscreen', $data);
|
||||
return $this->render('quotes.show', $data);
|
||||
}
|
||||
|
||||
public function bulk(ProcessQuotesInBulkRequest $request)
|
||||
|
139
app/Http/Controllers/ConnectedAccountController.php
Normal file
139
app/Http/Controllers/ConnectedAccountController.php
Normal file
@ -0,0 +1,139 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Libraries\OAuth\Providers\Google;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ConnectedAccountController extends BaseController
|
||||
{
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect an OAuth account to a regular email/password combination account
|
||||
*
|
||||
* @param Request $request
|
||||
* @return User Refresh Feed.
|
||||
*
|
||||
*
|
||||
* @OA\Post(
|
||||
* path="/api/v1/connected_account",
|
||||
* operationId="connected_account",
|
||||
* tags={"connected_account"},
|
||||
* summary="Connect an oauth user to an existing user",
|
||||
* description="Refreshes the dataset",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/include"),
|
||||
* @OA\Parameter(ref="#/components/parameters/include_static"),
|
||||
* @OA\Parameter(ref="#/components/parameters/clear_cache"),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="The Company User response",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* @OA\JsonContent(ref="#/components/schemas/User"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
if ($request->input('provider') == 'google') {
|
||||
return $this->handleGoogleOauth();
|
||||
}
|
||||
|
||||
return response()
|
||||
->json(['message' => 'Provider not supported'], 400)
|
||||
->header('X-App-Version', config('ninja.app_version'))
|
||||
->header('X-Api-Version', config('ninja.minimum_client_version'));
|
||||
}
|
||||
|
||||
private function handleGoogleOauth()
|
||||
{
|
||||
$user = false;
|
||||
|
||||
$google = new Google();
|
||||
|
||||
$user = $google->getTokenResponse(request()->input('id_token'));
|
||||
|
||||
if (is_array($user)) {
|
||||
|
||||
$query = [
|
||||
'oauth_user_id' => $google->harvestSubField($user),
|
||||
'oauth_provider_id'=> 'google',
|
||||
];
|
||||
|
||||
/* Cannot allow duplicates! */
|
||||
if ($existing_user = MultiDB::hasUser($query)) {
|
||||
return response()
|
||||
->json(['message' => 'User already exists in system.'], 401)
|
||||
->header('X-App-Version', config('ninja.app_version'))
|
||||
->header('X-Api-Version', config('ninja.minimum_client_version'));
|
||||
}
|
||||
}
|
||||
|
||||
if ($user) {
|
||||
$client = new Google_Client();
|
||||
$client->setClientId(config('ninja.auth.google.client_id'));
|
||||
$client->setClientSecret(config('ninja.auth.google.client_secret'));
|
||||
$client->setRedirectUri(config('ninja.app_url'));
|
||||
$token = $client->authenticate(request()->input('server_auth_code'));
|
||||
|
||||
$refresh_token = '';
|
||||
|
||||
if (array_key_exists('refresh_token', $token)) {
|
||||
$refresh_token = $token['refresh_token'];
|
||||
}
|
||||
|
||||
|
||||
$connected_account = [
|
||||
'password' => '',
|
||||
'email' => $google->harvestEmail($user),
|
||||
'oauth_user_id' => $google->harvestSubField($user),
|
||||
'oauth_user_token' => $token,
|
||||
'oauth_user_refresh_token' => $refresh_token,
|
||||
'oauth_provider_id' => 'google',
|
||||
'email_verified_at' =>now()
|
||||
];
|
||||
|
||||
auth()->user()->update($connected_account);
|
||||
auth()->user()->email_verified_at = now();
|
||||
auth()->user()->save();
|
||||
|
||||
//$ct = CompanyUser::whereUserId(auth()->user()->id);
|
||||
|
||||
return $this->listResponse(auth()->user());
|
||||
}
|
||||
|
||||
return response()
|
||||
->json(['message' => ctrans('texts.invalid_credentials')], 401)
|
||||
->header('X-App-Version', config('ninja.app_version'))
|
||||
->header('X-Api-Version', config('ninja.minimum_client_version'));
|
||||
}
|
||||
}
|
@ -1,4 +1,13 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
|
@ -78,7 +78,6 @@
|
||||
* @OA\Property(property="tax_name3", type="string", example="GST", description="The tax name"),
|
||||
* @OA\Property(property="payment_type_id", type="string", example="1", description="The default payment type id"),
|
||||
* @OA\Property(property="custom_fields", type="string", example="{}", description="JSON string of custom fields"),
|
||||
* @OA\Property(property="invoice_fields", type="string", example="{}", description="JSON string of invoice fields"),
|
||||
* @OA\Property(property="email_footer", type="string", example="A default email footer", description="The default email footer"),
|
||||
* @OA\Property(property="email_sending_method", type="string", example="default", description="The email driver to use to send email, options include default, gmail"),
|
||||
* @OA\Property(property="gmail_sending_user_id", type="string", example="F76sd34D", description="The hashed_id of the user account to send email from"),
|
||||
|
@ -49,6 +49,9 @@
|
||||
* @OA\Property(property="custom_surcharge2", type="number", format="float", example="10.00", description="Second Custom Surcharge"),
|
||||
* @OA\Property(property="custom_surcharge3", type="number", format="float", example="10.00", description="Third Custom Surcharge"),
|
||||
* @OA\Property(property="custom_surcharge4", type="number", format="float", example="10.00", description="Fourth Custom Surcharge"),
|
||||
* @OA\Property(property="custom_surcharge_taxes", type="boolean", example=true, description="Toggles charging taxes on custom surcharge amounts"),
|
||||
* @OA\Property(property="custom_surcharge_tax1", type="boolean", example=true, description="Toggles charging taxes on custom surcharge amounts"),
|
||||
* @OA\Property(property="custom_surcharge_tax2", type="boolean", example=true, description="Toggles charging taxes on custom surcharge amounts"),
|
||||
* @OA\Property(property="custom_surcharge_tax3", type="boolean", example=true, description="Toggles charging taxes on custom surcharge amounts"),
|
||||
* @OA\Property(property="custom_surcharge_tax4", type="boolean", example=true, description="Toggles charging taxes on custom surcharge amounts"),
|
||||
* )
|
||||
*/
|
||||
|
@ -48,6 +48,9 @@
|
||||
* @OA\Property(property="custom_surcharge2", type="number", format="float", example="10.00", description="Second Custom Surcharge"),
|
||||
* @OA\Property(property="custom_surcharge3", type="number", format="float", example="10.00", description="Third Custom Surcharge"),
|
||||
* @OA\Property(property="custom_surcharge4", type="number", format="float", example="10.00", description="Fourth Custom Surcharge"),
|
||||
* @OA\Property(property="custom_surcharge_taxes", type="boolean", example=true, description="Toggles charging taxes on custom surcharge amounts"),
|
||||
* @OA\Property(property="custom_surcharge_tax1", type="boolean", example=true, description="Toggles charging taxes on custom surcharge amounts"),
|
||||
* @OA\Property(property="custom_surcharge_tax2", type="boolean", example=true, description="Toggles charging taxes on custom surcharge amounts"),
|
||||
* @OA\Property(property="custom_surcharge_tax3", type="boolean", example=true, description="Toggles charging taxes on custom surcharge amounts"),
|
||||
* @OA\Property(property="custom_surcharge_tax4", type="boolean", example=true, description="Toggles charging taxes on custom surcharge amounts"),
|
||||
* )
|
||||
*/
|
||||
|
@ -48,6 +48,9 @@
|
||||
* @OA\Property(property="custom_surcharge2", type="number", format="float", example="10.00", description="Second Custom Surcharge"),
|
||||
* @OA\Property(property="custom_surcharge3", type="number", format="float", example="10.00", description="Third Custom Surcharge"),
|
||||
* @OA\Property(property="custom_surcharge4", type="number", format="float", example="10.00", description="Fourth Custom Surcharge"),
|
||||
* @OA\Property(property="custom_surcharge_taxes", type="boolean", example=true, description="Toggles charging taxes on custom surcharge amounts"),
|
||||
* @OA\Property(property="custom_surcharge_tax1", type="boolean", example=true, description="Toggles charging taxes on custom surcharge amounts"),
|
||||
* @OA\Property(property="custom_surcharge_tax2", type="boolean", example=true, description="Toggles charging taxes on custom surcharge amounts"),
|
||||
* @OA\Property(property="custom_surcharge_tax3", type="boolean", example=true, description="Toggles charging taxes on custom surcharge amounts"),
|
||||
* @OA\Property(property="custom_surcharge_tax4", type="boolean", example=true, description="Toggles charging taxes on custom surcharge amounts"),
|
||||
* )
|
||||
*/
|
||||
|
156
app/Http/Controllers/PostMarkController.php
Normal file
156
app/Http/Controllers/PostMarkController.php
Normal file
@ -0,0 +1,156 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
* Class PostMarkController.
|
||||
*/
|
||||
class PostMarkController extends BaseController
|
||||
{
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Process Postmark Webhook.
|
||||
*
|
||||
*
|
||||
* @OA\Post(
|
||||
* path="/api/v1/postmark_webhook",
|
||||
* operationId="postmarkWebhook",
|
||||
* tags={"postmark"},
|
||||
* summary="Processing webhooks from PostMark",
|
||||
* description="Adds an credit to the system",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/include"),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Returns the saved credit object",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* @OA\JsonContent(ref="#/components/schemas/Credit"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
*
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function webhook(Request $request)
|
||||
{
|
||||
|
||||
if($request->header('X-API-SECURITY') && $request->header('X-API-SECURITY') == config('postmark.secret'))
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// {
|
||||
// "RecordType": "Delivery",
|
||||
// "ServerID": 23,
|
||||
// "MessageStream": "outbound",
|
||||
// "MessageID": "00000000-0000-0000-0000-000000000000",
|
||||
// "Recipient": "john@example.com",
|
||||
// "Tag": "welcome-email",
|
||||
// "DeliveredAt": "2021-02-21T16:34:52Z",
|
||||
// "Details": "Test delivery webhook details",
|
||||
// "Metadata": {
|
||||
// "example": "value",
|
||||
// "example_2": "value"
|
||||
// }
|
||||
// }
|
||||
private function processDelivery($request)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// {
|
||||
// "Metadata": {
|
||||
// "example": "value",
|
||||
// "example_2": "value"
|
||||
// },
|
||||
// "RecordType": "Bounce",
|
||||
// "ID": 42,
|
||||
// "Type": "HardBounce",
|
||||
// "TypeCode": 1,
|
||||
// "Name": "Hard bounce",
|
||||
// "Tag": "Test",
|
||||
// "MessageID": "00000000-0000-0000-0000-000000000000",
|
||||
// "ServerID": 1234,
|
||||
// "MessageStream": "outbound",
|
||||
// "Description": "The server was unable to deliver your message (ex: unknown user, mailbox not found).",
|
||||
// "Details": "Test bounce details",
|
||||
// "Email": "john@example.com",
|
||||
// "From": "sender@example.com",
|
||||
// "BouncedAt": "2021-02-21T16:34:52Z",
|
||||
// "DumpAvailable": true,
|
||||
// "Inactive": true,
|
||||
// "CanActivate": true,
|
||||
// "Subject": "Test subject",
|
||||
// "Content": "Test content"
|
||||
// }
|
||||
|
||||
private function processBounce($request)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// {
|
||||
// "Metadata": {
|
||||
// "example": "value",
|
||||
// "example_2": "value"
|
||||
// },
|
||||
// "RecordType": "SpamComplaint",
|
||||
// "ID": 42,
|
||||
// "Type": "SpamComplaint",
|
||||
// "TypeCode": 100001,
|
||||
// "Name": "Spam complaint",
|
||||
// "Tag": "Test",
|
||||
// "MessageID": "00000000-0000-0000-0000-000000000000",
|
||||
// "ServerID": 1234,
|
||||
// "MessageStream": "outbound",
|
||||
// "Description": "The subscriber explicitly marked this message as spam.",
|
||||
// "Details": "Test spam complaint details",
|
||||
// "Email": "john@example.com",
|
||||
// "From": "sender@example.com",
|
||||
// "BouncedAt": "2021-02-21T16:34:52Z",
|
||||
// "DumpAvailable": true,
|
||||
// "Inactive": true,
|
||||
// "CanActivate": false,
|
||||
// "Subject": "Test subject",
|
||||
// "Content": "Test content"
|
||||
// }
|
||||
private function processSpamComplaint($request)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -406,9 +406,9 @@ class ProductController extends BaseController
|
||||
*/
|
||||
public function destroy(DestroyProductRequest $request, Product $product)
|
||||
{
|
||||
$product->delete();
|
||||
$this->product_repo->delete($product);
|
||||
|
||||
return $this->itemResponse($product);
|
||||
return $this->itemResponse($product->fresh());
|
||||
}
|
||||
|
||||
/**
|
||||
|
63
app/Http/Controllers/TwoFactorController.php
Normal file
63
app/Http/Controllers/TwoFactorController.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use PragmaRX\Google2FA\Google2FA;
|
||||
use Crypt;
|
||||
|
||||
class TwoFactorController extends BaseController
|
||||
{
|
||||
public function setupTwoFactor()
|
||||
{
|
||||
$user = auth()->user();
|
||||
|
||||
if ($user->google_2fa_secret)
|
||||
return response()->json(['message' => '2FA already enabled'], 400);
|
||||
elseif(! $user->phone)
|
||||
return response()->json(['message' => ctrans('texts.set_phone_for_two_factor')], 400);
|
||||
elseif(! $user->confirmed)
|
||||
return response()->json(['message' => 'Please confirm your account first'], 400);
|
||||
|
||||
$google2fa = new Google2FA();
|
||||
$secret = $google2fa->generateSecretKey();
|
||||
|
||||
$qr_code = $google2fa->getQRCodeGoogleUrl(
|
||||
config('ninja.app_name')
|
||||
$user->email,
|
||||
$secret
|
||||
);
|
||||
|
||||
$data = [
|
||||
'secret' => $secret,
|
||||
'qrCode' => $qrCode,
|
||||
];
|
||||
|
||||
return response()->json(['data' => $data], 200);
|
||||
|
||||
}
|
||||
|
||||
public function enableTwoFactor()
|
||||
{
|
||||
$user = auth()->user();
|
||||
$secret = request()->input('secret');
|
||||
$oneTimePassword = request()->input('one_time_password');
|
||||
|
||||
if (! $secret || ! \Google2FA::verifyKey($secret, $oneTimePassword)) {
|
||||
return response()->json('message' > ctrans('texts.invalid_one_time_password'));
|
||||
} elseif (! $user->google_2fa_secret && $user->phone && $user->confirmed) {
|
||||
$user->google_2fa_secret = encrypt($secret);
|
||||
$user->save();
|
||||
}
|
||||
|
||||
return response()->json(['message' => ctrans('texts.enabled_two_factor')], 200);
|
||||
}
|
||||
}
|
@ -23,11 +23,16 @@ use App\Http\Requests\User\CreateUserRequest;
|
||||
use App\Http\Requests\User\DestroyUserRequest;
|
||||
use App\Http\Requests\User\DetachCompanyUserRequest;
|
||||
use App\Http\Requests\User\EditUserRequest;
|
||||
use App\Http\Requests\User\ReconfirmUserRequest;
|
||||
use App\Http\Requests\User\ShowUserRequest;
|
||||
use App\Http\Requests\User\StoreUserRequest;
|
||||
use App\Http\Requests\User\UpdateUserRequest;
|
||||
use App\Jobs\Company\CreateCompanyToken;
|
||||
use App\Jobs\Mail\NinjaMailer;
|
||||
use App\Jobs\Mail\NinjaMailerJob;
|
||||
use App\Jobs\Mail\NinjaMailerObject;
|
||||
use App\Jobs\User\UserEmailChanged;
|
||||
use App\Mail\Admin\VerifyUserObject;
|
||||
use App\Models\CompanyUser;
|
||||
use App\Models\User;
|
||||
use App\Repositories\UserRepository;
|
||||
@ -378,11 +383,12 @@ class UserController extends BaseController
|
||||
$new_user = $this->user_repo->save($request->all(), $user);
|
||||
$new_user = $user->fresh();
|
||||
|
||||
|
||||
nlog($old_user);
|
||||
|
||||
if ($old_user_email != $new_email)
|
||||
/* When changing email address we store the former email in case we need to rollback */
|
||||
if ($old_user_email != $new_email) {
|
||||
$user->last_confirmed_email_address = $old_user_email;
|
||||
$user->save();
|
||||
UserEmailChanged::dispatch($new_user, json_decode($old_user), auth()->user()->company());
|
||||
}
|
||||
|
||||
|
||||
if(
|
||||
@ -684,4 +690,70 @@ class UserController extends BaseController
|
||||
|
||||
return response()->json(['message' => ctrans('texts.user_detached')], 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detach an existing user to a company.
|
||||
*
|
||||
* @OA\Post(
|
||||
* path="/api/v1/users/{user}/reconfirm",
|
||||
* operationId="reconfirmUser",
|
||||
* tags={"users"},
|
||||
* summary="Reconfirm an existing user to a company",
|
||||
* description="Reconfirm an existing user from a company",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/include"),
|
||||
* @OA\Parameter(
|
||||
* name="user",
|
||||
* in="path",
|
||||
* description="The user hashed_id",
|
||||
* example="FD767dfd7",
|
||||
* required=true,
|
||||
* @OA\Schema(
|
||||
* type="string",
|
||||
* format="string",
|
||||
* ),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Success response",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
*
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
* @param ReconfirmUserRequest $request
|
||||
* @param User $user
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function reconfirm(ReconfirmUserRequest $request, User $user)
|
||||
{
|
||||
$user->confirmation_code = $this->createDbHash($user->company()->db);
|
||||
$user->save();
|
||||
|
||||
$nmo = new NinjaMailerObject;
|
||||
$nmo->mailable = new NinjaMailer((new VerifyUserObject($user, $user->company()))->build());
|
||||
$nmo->company = $user->company();
|
||||
$nmo->to_user = $user;
|
||||
$nmo->settings = $user->company->settings;
|
||||
|
||||
NinjaMailerJob::dispatch($nmo);
|
||||
|
||||
return response()->json(['message' => ctrans('texts.confirmation_resent')], 200);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
34
app/Http/Livewire/PayNowDropdown.php
Normal file
34
app/Http/Livewire/PayNowDropdown.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Http\Livewire;
|
||||
|
||||
use Livewire\Component;
|
||||
|
||||
class PayNowDropdown extends Component
|
||||
{
|
||||
public $total;
|
||||
|
||||
public $methods;
|
||||
|
||||
public function mount(int $total)
|
||||
{
|
||||
$this->total = $total;
|
||||
|
||||
$this->methods = auth()->user()->client->service()->getPaymentMethods($total);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return render('components.livewire.pay-now-dropdown');
|
||||
}
|
||||
}
|
28
app/Http/Requests/User/ReconfirmUserRequest.php
Normal file
28
app/Http/Requests/User/ReconfirmUserRequest.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\User;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use App\Models\User;
|
||||
|
||||
class ReconfirmUserRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->user()->Admin();
|
||||
}
|
||||
}
|
@ -105,7 +105,7 @@ class EmailEntity implements ShouldQueue
|
||||
MultiDB::setDB($this->company->db);
|
||||
|
||||
$nmo = new NinjaMailerObject;
|
||||
$nmo->mailable = new TemplateEmail($this->email_entity_builder,$this->invitation->contact);
|
||||
$nmo->mailable = new TemplateEmail($this->email_entity_builder,$this->invitation->contact, $this->invitation);
|
||||
$nmo->company = $this->company;
|
||||
$nmo->settings = $this->settings;
|
||||
$nmo->to_user = $this->invitation->contact;
|
||||
|
@ -54,6 +54,7 @@ use Symfony\Component\HttpFoundation\ParameterBag;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class CSVImport implements ShouldQueue {
|
||||
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, CleanLineItems;
|
||||
|
||||
public $invoice;
|
||||
@ -79,7 +80,9 @@ class CSVImport implements ShouldQueue {
|
||||
$this->hash = $request['hash'];
|
||||
$this->import_type = $request['import_type'];
|
||||
$this->skip_header = $request['skip_header'] ?? null;
|
||||
$this->column_map = $request['column_map'] ?? null;
|
||||
$this->column_map =
|
||||
! empty( $request['column_map'] ) ?
|
||||
array_combine( array_keys( $request['column_map'] ), array_column( $request['column_map'], 'mapping' ) ) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -142,7 +142,7 @@ class NinjaMailerJob implements ShouldQueue
|
||||
|
||||
$user = User::find($this->decodePrimaryKey($sending_user));
|
||||
|
||||
nlog("Sending via {$user->present()->name()}");
|
||||
nlog("Sending via {$user->name()}");
|
||||
|
||||
$google = (new Google())->init();
|
||||
$google->getClient()->setAccessToken(json_encode($user->oauth_user_token));
|
||||
@ -164,7 +164,7 @@ class NinjaMailerJob implements ShouldQueue
|
||||
|
||||
$this->nmo
|
||||
->mailable
|
||||
->from($user->email, $user->present()->name())
|
||||
->from($user->email, $user->name())
|
||||
->withSwiftMessage(function ($message) use($token) {
|
||||
$message->getHeaders()->addTextHeader('GmailToken', $token);
|
||||
});
|
||||
|
@ -295,7 +295,7 @@ class SendReminders implements ShouldQueue
|
||||
$invoice_item = new InvoiceItem;
|
||||
$invoice_item->type_id = '5';
|
||||
$invoice_item->product_key = trans('texts.fee');
|
||||
$invoice_item->notes = ctrans('texts.late_fee_added', ['date' => $this->formatDate(now()->startOfDay(), $invoice->client->date_format())]);
|
||||
$invoice_item->notes = ctrans('texts.late_fee_added', ['date' => $this->translateDate(now()->startOfDay(), $invoice->client->date_format(), $invoice->client->locale())]);
|
||||
$invoice_item->quantity = 1;
|
||||
$invoice_item->cost = $fee;
|
||||
|
||||
|
58
app/Listeners/Mail/MailSentListener.php
Normal file
58
app/Listeners/Mail/MailSentListener.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Listeners\Mail;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use Illuminate\Mail\Events\MessageSent;
|
||||
|
||||
class MailSentListener implements ShouldQueue
|
||||
{
|
||||
|
||||
/**
|
||||
* Create the event listener.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param object $event
|
||||
* @return void
|
||||
*/
|
||||
public function handle(MessageSent $event)
|
||||
{
|
||||
|
||||
if(property_exists($event->message, 'invitation')){
|
||||
|
||||
MultiDB::setDb($event->message->invitation->company->db);
|
||||
|
||||
if($event->message->getHeaders()->get('x-pm-message-id')){
|
||||
|
||||
$postmark_id = $event->message->getHeaders()->get('x-pm-message-id')->getValue();
|
||||
|
||||
nlog($postmark_id);
|
||||
$invitation = $event->message->invitation;
|
||||
$invitation->message_id = $postmark_id;
|
||||
$invitation->save();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -50,21 +50,5 @@ class BouncedEmail extends Mailable
|
||||
->text()
|
||||
->subject($subject);
|
||||
|
||||
//todo
|
||||
/*
|
||||
|
||||
|
||||
//todo determine WHO is notified!! In this instance the _user_ is notified
|
||||
|
||||
Mail::to($invitation->user->email)
|
||||
//->cc('')
|
||||
//->bcc('')
|
||||
->queue(new BouncedEmail($invitation));
|
||||
|
||||
return $this->from('x@gmail.com') //todo
|
||||
->subject(ctrans('texts.confirmation_subject'))
|
||||
->markdown('email.auth.verify', ['user' => $this->user])
|
||||
->text('email.auth.verify_text');
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,8 @@ class BaseEmailEngine implements EngineInterface
|
||||
|
||||
public $text;
|
||||
|
||||
public $invitation;
|
||||
|
||||
public function setFooter($footer)
|
||||
{
|
||||
$this->footer = $footer;
|
||||
@ -141,4 +143,15 @@ class BaseEmailEngine implements EngineInterface
|
||||
public function build()
|
||||
{
|
||||
}
|
||||
|
||||
public function setInvitation($invitation)
|
||||
{
|
||||
$this->invitation = $invitation;
|
||||
}
|
||||
|
||||
public function getInvitation()
|
||||
{
|
||||
return $this->invitation;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,7 +85,8 @@ class CreditEmailEngine extends BaseEmailEngine
|
||||
->setBody($body_template)
|
||||
->setFooter("<a href='{$this->invitation->getLink()}'>".ctrans('texts.view_credit').'</a>')
|
||||
->setViewLink($this->invitation->getLink())
|
||||
->setViewText(ctrans('texts.view_credit'));
|
||||
->setViewText(ctrans('texts.view_credit'))
|
||||
->setInvitation($this->invitation);
|
||||
|
||||
if ($this->client->getSetting('pdf_email_attachment') !== false) {
|
||||
$this->setAttachments(['path' => $this->credit->pdf_file_path(), 'name' => basename($this->credit->pdf_file_path())]);
|
||||
|
@ -94,7 +94,8 @@ class InvoiceEmailEngine extends BaseEmailEngine
|
||||
->setBody($body_template)
|
||||
->setFooter("<a href='{$this->invitation->getLink()}'>".ctrans('texts.view_invoice').'</a>')
|
||||
->setViewLink($this->invitation->getLink())
|
||||
->setViewText(ctrans('texts.view_invoice'));
|
||||
->setViewText(ctrans('texts.view_invoice'))
|
||||
->setInvitation($this->invitation);
|
||||
|
||||
if ($this->client->getSetting('pdf_email_attachment') !== false) {
|
||||
$this->setAttachments([$this->invoice->pdf_file_path()]);
|
||||
|
@ -87,7 +87,7 @@ class PaymentEmailEngine extends BaseEmailEngine
|
||||
$data['$entity'] = ['value' => '', 'label' => ctrans('texts.payment')];
|
||||
$data['$payment.amount'] = ['value' => Number::formatMoney($this->payment->amount, $this->client) ?: ' ', 'label' => ctrans('texts.amount')];
|
||||
$data['$amount'] = &$data['$payment.amount'];
|
||||
$data['$payment.date'] = ['value' => $this->formatDate($this->payment->date, $this->client->date_format()), 'label' => ctrans('texts.payment_date')];
|
||||
$data['$payment.date'] = ['value' => $this->translateDate($this->payment->date, $this->client->date_format(), $this->client->locale()), 'label' => ctrans('texts.payment_date')];
|
||||
$data['$transaction_reference'] = ['value' => $this->payment->transaction_reference, 'label' => ctrans('texts.transaction_reference')];
|
||||
$data['$public_notes'] = ['value' => $this->payment->public_notes, 'label' => ctrans('texts.notes')];
|
||||
|
||||
|
@ -85,7 +85,9 @@ class QuoteEmailEngine extends BaseEmailEngine
|
||||
->setBody($body_template)
|
||||
->setFooter("<a href='{$this->invitation->getLink()}'>".ctrans('texts.view_quote').'</a>')
|
||||
->setViewLink($this->invitation->getLink())
|
||||
->setViewText(ctrans('texts.view_quote'));
|
||||
->setViewText(ctrans('texts.view_quote'))
|
||||
->setInvitation($this->invitation);
|
||||
|
||||
|
||||
if ($this->client->getSetting('pdf_email_attachment') !== false) {
|
||||
// $this->setAttachments([$this->quote->pdf_file_path()]);
|
||||
|
@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mail\Invoices;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class InvoiceWasPaid extends Mailable
|
||||
{
|
||||
// use Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the message.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
return $this->from(config('mail.from.address'), config('mail.from.name'))->view('email.invoices.paid');
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mail\Quote;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class QuoteWasApproved extends Mailable
|
||||
{
|
||||
// use Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the message.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
return $this->from(config('mail.from.address'), config('mail.from.name'))->view('email.quotes.approved');
|
||||
}
|
||||
}
|
@ -27,13 +27,21 @@ class TemplateEmail extends Mailable
|
||||
|
||||
private $contact;
|
||||
|
||||
public function __construct($build_email, ClientContact $contact)
|
||||
private $company;
|
||||
|
||||
private $invitation;
|
||||
|
||||
public function __construct($build_email, ClientContact $contact, $invitation = null)
|
||||
{
|
||||
$this->build_email = $build_email;
|
||||
|
||||
$this->contact = $contact;
|
||||
|
||||
$this->client = $contact->client;
|
||||
|
||||
$this->company = $contact->company;
|
||||
|
||||
$this->invitation = $invitation;
|
||||
}
|
||||
|
||||
public function build()
|
||||
@ -44,15 +52,13 @@ class TemplateEmail extends Mailable
|
||||
|
||||
$company = $this->client->company;
|
||||
|
||||
$this->from(config('mail.from.address'), config('mail.from.name'));
|
||||
$this->from(config('mail.from.address'), $this->company->present()->name());
|
||||
|
||||
if (strlen($settings->reply_to_email) > 1) {
|
||||
if (strlen($settings->reply_to_email) > 1)
|
||||
$this->replyTo($settings->reply_to_email, $settings->reply_to_email);
|
||||
}
|
||||
|
||||
if (strlen($settings->bcc_email) > 1) {
|
||||
if (strlen($settings->bcc_email) > 1)
|
||||
$this->bcc($settings->bcc_email, $settings->bcc_email);
|
||||
}
|
||||
|
||||
$this->subject($this->build_email->getSubject())
|
||||
->text('email.template.plain', [
|
||||
@ -75,6 +81,7 @@ class TemplateEmail extends Mailable
|
||||
])
|
||||
->withSwiftMessage(function ($message) use($company){
|
||||
$message->getHeaders()->addTextHeader('Tag', $company->company_key);
|
||||
$message->invitation = $this->invitation;
|
||||
});
|
||||
|
||||
//conditionally attach files
|
||||
|
@ -41,14 +41,11 @@ class Credit extends BaseModel
|
||||
protected $presenter = CreditPresenter::class;
|
||||
|
||||
protected $fillable = [
|
||||
'assigned_user_id',
|
||||
'project_id',
|
||||
'number',
|
||||
'discount',
|
||||
'po_number',
|
||||
'date',
|
||||
'due_date',
|
||||
'partial_due_date',
|
||||
'terms',
|
||||
'public_notes',
|
||||
'private_notes',
|
||||
@ -59,8 +56,9 @@ class Credit extends BaseModel
|
||||
'tax_name3',
|
||||
'tax_rate3',
|
||||
'is_amount_discount',
|
||||
'footer',
|
||||
'partial',
|
||||
'partial_due_date',
|
||||
'project_id',
|
||||
'custom_value1',
|
||||
'custom_value2',
|
||||
'custom_value3',
|
||||
@ -68,7 +66,16 @@ class Credit extends BaseModel
|
||||
'line_items',
|
||||
'client_id',
|
||||
'footer',
|
||||
'custom_surcharge1',
|
||||
'custom_surcharge2',
|
||||
'custom_surcharge3',
|
||||
'custom_surcharge4',
|
||||
'custom_surcharge_tax1',
|
||||
'custom_surcharge_tax2',
|
||||
'custom_surcharge_tax3',
|
||||
'custom_surcharge_tax4',
|
||||
'design_id',
|
||||
'assigned_user_id',
|
||||
'exchange_rate',
|
||||
];
|
||||
|
||||
|
@ -7,6 +7,9 @@ namespace App\Models;
|
||||
*/
|
||||
class DateFormat extends StaticModel
|
||||
{
|
||||
|
||||
protected $fillable = ['translated_format'];
|
||||
|
||||
public static $days_of_the_week = [
|
||||
0 => 'sunday',
|
||||
1 => 'monday',
|
||||
|
@ -102,6 +102,10 @@ class Invoice extends BaseModel
|
||||
'updated_at' => 'timestamp',
|
||||
'created_at' => 'timestamp',
|
||||
'deleted_at' => 'timestamp',
|
||||
'custom_surcharge_tax1' => 'bool',
|
||||
'custom_surcharge_tax2' => 'bool',
|
||||
'custom_surcharge_tax3' => 'bool',
|
||||
'custom_surcharge_tax4' => 'bool',
|
||||
];
|
||||
|
||||
protected $with = [];
|
||||
@ -146,6 +150,16 @@ class Invoice extends BaseModel
|
||||
return $this->belongsTo(Company::class);
|
||||
}
|
||||
|
||||
public function project()
|
||||
{
|
||||
return $this->belongsTo(Project::class);
|
||||
}
|
||||
|
||||
public function design()
|
||||
{
|
||||
return $this->belongsTo(Design::class);
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class)->withTrashed();
|
||||
@ -367,13 +381,13 @@ class Invoice extends BaseModel
|
||||
return $invoice_calc->build();
|
||||
}
|
||||
|
||||
public function pdf_file_path($invitation = null)
|
||||
public function pdf_file_path($invitation = null, string $type = 'url')
|
||||
{
|
||||
if (! $invitation) {
|
||||
$invitation = $this->invitations->first();
|
||||
}
|
||||
|
||||
$storage_path = Storage::url($this->client->invoice_filepath().$this->number.'.pdf');
|
||||
$storage_path = Storage::$type($this->client->invoice_filepath().$this->number.'.pdf');
|
||||
|
||||
if (! Storage::exists($this->client->invoice_filepath().$this->number.'.pdf')) {
|
||||
event(new InvoiceWasUpdated($this, $this->company, Ninja::eventVars()));
|
||||
|
@ -42,7 +42,6 @@ class Quote extends BaseModel
|
||||
protected $touches = [];
|
||||
|
||||
protected $fillable = [
|
||||
'assigned_user_id',
|
||||
'number',
|
||||
'discount',
|
||||
'po_number',
|
||||
@ -51,7 +50,6 @@ class Quote extends BaseModel
|
||||
'terms',
|
||||
'public_notes',
|
||||
'private_notes',
|
||||
'project_id',
|
||||
'tax_name1',
|
||||
'tax_rate1',
|
||||
'tax_name2',
|
||||
@ -61,6 +59,7 @@ class Quote extends BaseModel
|
||||
'is_amount_discount',
|
||||
'partial',
|
||||
'partial_due_date',
|
||||
'project_id',
|
||||
'custom_value1',
|
||||
'custom_value2',
|
||||
'custom_value3',
|
||||
@ -68,7 +67,16 @@ class Quote extends BaseModel
|
||||
'line_items',
|
||||
'client_id',
|
||||
'footer',
|
||||
'custom_surcharge1',
|
||||
'custom_surcharge2',
|
||||
'custom_surcharge3',
|
||||
'custom_surcharge4',
|
||||
'custom_surcharge_tax1',
|
||||
'custom_surcharge_tax2',
|
||||
'custom_surcharge_tax3',
|
||||
'custom_surcharge_tax4',
|
||||
'design_id',
|
||||
'assigned_user_id',
|
||||
'exchange_rate',
|
||||
];
|
||||
|
||||
@ -187,13 +195,13 @@ class Quote extends BaseModel
|
||||
return new QuoteService($this);
|
||||
}
|
||||
|
||||
public function pdf_file_path($invitation = null)
|
||||
public function pdf_file_path($invitation = null, string $type = 'url')
|
||||
{
|
||||
if (! $invitation) {
|
||||
$invitation = $this->invitations->where('client_contact_id', $this->client->primary_contact()->first()->id)->first();
|
||||
}
|
||||
|
||||
$storage_path = Storage::url($this->client->quote_filepath().$this->number.'.pdf');
|
||||
$storage_path = Storage::$type($this->client->quote_filepath().$this->number.'.pdf');
|
||||
|
||||
if (Storage::exists($this->client->quote_filepath().$this->number.'.pdf')) {
|
||||
return $storage_path;
|
||||
|
@ -74,7 +74,6 @@ class RecurringInvoice extends BaseModel
|
||||
'due_date',
|
||||
'due_date_days',
|
||||
'line_items',
|
||||
'settings',
|
||||
'footer',
|
||||
'public_notes',
|
||||
'private_notes',
|
||||
@ -97,6 +96,17 @@ class RecurringInvoice extends BaseModel
|
||||
'auto_bill',
|
||||
'auto_bill_enabled',
|
||||
'design_id',
|
||||
'custom_surcharge1',
|
||||
'custom_surcharge2',
|
||||
'custom_surcharge3',
|
||||
'custom_surcharge4',
|
||||
'custom_surcharge_tax1',
|
||||
'custom_surcharge_tax2',
|
||||
'custom_surcharge_tax3',
|
||||
'custom_surcharge_tax4',
|
||||
'design_id',
|
||||
'assigned_user_id',
|
||||
'exchange_rate',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
|
@ -103,6 +103,12 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
'deleted_at' => 'timestamp',
|
||||
];
|
||||
|
||||
|
||||
public function name()
|
||||
{
|
||||
return $this->first_name . ' ' . $this->last_name;
|
||||
}
|
||||
|
||||
public function getEntityType()
|
||||
{
|
||||
return self::class;
|
||||
|
@ -136,6 +136,7 @@ use App\Listeners\Invoice\InvoiceRestoredActivity;
|
||||
use App\Listeners\Invoice\InvoiceReversedActivity;
|
||||
use App\Listeners\Invoice\InvoiceViewedActivity;
|
||||
use App\Listeners\Invoice\UpdateInvoiceActivity;
|
||||
use App\Listeners\Mail\MailSentListener;
|
||||
use App\Listeners\Misc\InvitationViewedListener;
|
||||
use App\Listeners\Payment\PaymentEmailFailureActivity;
|
||||
use App\Listeners\Payment\PaymentEmailedActivity;
|
||||
@ -157,6 +158,8 @@ use App\Listeners\User\RestoredUserActivity;
|
||||
use App\Listeners\User\UpdateUserLastLogin;
|
||||
use App\Listeners\User\UpdatedUserActivity;
|
||||
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
||||
use Illuminate\Mail\Events\MessageSending;
|
||||
use Illuminate\Mail\Events\MessageSent;
|
||||
|
||||
class EventServiceProvider extends ServiceProvider
|
||||
{
|
||||
@ -166,6 +169,11 @@ class EventServiceProvider extends ServiceProvider
|
||||
* @var array
|
||||
*/
|
||||
protected $listen = [
|
||||
MessageSending::class =>[
|
||||
],
|
||||
MessageSent::class => [
|
||||
MailSentListener::class,
|
||||
],
|
||||
UserWasCreated::class => [
|
||||
CreatedUserActivity::class,
|
||||
SendVerificationNotification::class,
|
||||
|
@ -3,8 +3,10 @@
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Helpers\Mail\GmailTransportManager;
|
||||
use Coconuts\Mail\PostmarkTransport;
|
||||
use Illuminate\Mail\MailServiceProvider as MailProvider;
|
||||
use Illuminate\Mail\TransportManager;
|
||||
use GuzzleHttp\Client as HttpClient;
|
||||
|
||||
class MailServiceProvider extends MailProvider
|
||||
{
|
||||
@ -24,7 +26,18 @@ class MailServiceProvider extends MailProvider
|
||||
$this->app->bind('mailer', function ($app) {
|
||||
return $app->make('mail.manager')->mailer();
|
||||
});
|
||||
}
|
||||
|
||||
$this->app['mail.manager']->extend('postmark', function () {
|
||||
return new PostmarkTransport(
|
||||
$this->guzzle(config('postmark.guzzle', [])),
|
||||
config('postmark.secret', config('services.postmark.secret'))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
protected function guzzle(array $config): HttpClient
|
||||
{
|
||||
return new HttpClient($config);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -199,6 +199,12 @@ class BaseRepository
|
||||
unset($tmp_data['client_contacts']);
|
||||
|
||||
$model->fill($tmp_data);
|
||||
|
||||
$model->custom_surcharge_tax1 = $client->company->custom_surcharge_taxes1;
|
||||
$model->custom_surcharge_tax2 = $client->company->custom_surcharge_taxes2;
|
||||
$model->custom_surcharge_tax3 = $client->company->custom_surcharge_taxes3;
|
||||
$model->custom_surcharge_tax4 = $client->company->custom_surcharge_taxes4;
|
||||
|
||||
$model->save();
|
||||
|
||||
/* Model now persisted, now lets do some child tasks */
|
||||
@ -286,7 +292,7 @@ class BaseRepository
|
||||
$model = $model->service()->applyNumber()->save();
|
||||
|
||||
/* Update product details if necessary */
|
||||
if ($model->company->update_products !== false)
|
||||
if ($model->company->update_products)
|
||||
UpdateOrCreateProduct::dispatch($model->line_items, $model, $model->company);
|
||||
|
||||
/* Perform model specific tasks */
|
||||
|
@ -58,6 +58,7 @@ class UserTransformer extends EntityTransformer
|
||||
'custom_value3' => $user->custom_value3 ?: '',
|
||||
'custom_value4' => $user->custom_value4 ?: '',
|
||||
'oauth_provider_id' => (string) $user->oauth_provider_id,
|
||||
'last_confirmed_email_address' => (string) $user->last_confirmed_email_address ?: '',
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,7 @@ class Helpers
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
public function formatCustomFieldValue($custom_fields = null, $field, $value, Client $client = null): ?string
|
||||
public function formatCustomFieldValue($custom_fields, $field, $value, Client $client = null): ?string
|
||||
{
|
||||
$custom_field = '';
|
||||
|
||||
@ -64,7 +64,7 @@ class Helpers
|
||||
|
||||
switch ($custom_field) {
|
||||
case 'date':
|
||||
return is_null($client) ? $value : $this->formatDate($value, $client->date_format());
|
||||
return is_null($client) ? $value : $this->translateDate($value, $client->date_format(), $client->locale());
|
||||
break;
|
||||
|
||||
case 'switch':
|
||||
@ -84,7 +84,7 @@ class Helpers
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function makeCustomField($custom_fields = null, $field): string
|
||||
public function makeCustomField($custom_fields, $field): string
|
||||
{
|
||||
if ($custom_fields && property_exists($custom_fields, $field)) {
|
||||
$custom_field = $custom_fields->{$field};
|
||||
|
@ -105,15 +105,15 @@ class HtmlEngine
|
||||
$data['$total_tax_values'] = ['value' => $this->totalTaxValues(), 'label' => ctrans('texts.taxes')];
|
||||
$data['$line_tax_labels'] = ['value' => $this->lineTaxLabels(), 'label' => ctrans('texts.taxes')];
|
||||
$data['$line_tax_values'] = ['value' => $this->lineTaxValues(), 'label' => ctrans('texts.taxes')];
|
||||
$data['$date'] = ['value' => $this->formatDate($this->entity->date, $this->entity->client->date_format()) ?: ' ', 'label' => ctrans('texts.date')];
|
||||
$data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->entity->client->date_format(), $this->entity->client->locale()) ?: ' ', 'label' => ctrans('texts.date')];
|
||||
|
||||
$data['$invoice.date'] = &$data['$date'];
|
||||
$data['$due_date'] = ['value' => $this->formatDate($this->entity->due_date, $this->entity->client->date_format()) ?: ' ', 'label' => ctrans('texts.'.$this->entity_string.'_due_date')];
|
||||
$data['$payment_due'] = ['value' => $this->formatDate($this->entity->due_date, $this->entity->client->date_format()) ?: ' ', 'label' => ctrans('texts.payment_due')];
|
||||
$data['$due_date'] = ['value' => $this->translateDate($this->entity->due_date, $this->entity->client->date_format(), $this->entity->client->locale()) ?: ' ', 'label' => ctrans('texts.'.$this->entity_string.'_due_date')];
|
||||
$data['$payment_due'] = ['value' => $this->translateDate($this->entity->due_date, $this->entity->client->date_format(), $this->entity->client->locale()) ?: ' ', 'label' => ctrans('texts.payment_due')];
|
||||
$data['$invoice.due_date'] = &$data['$due_date'];
|
||||
$data['$invoice.number'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.invoice_number')];
|
||||
$data['$invoice.po_number'] = ['value' => $this->entity->po_number ?: ' ', 'label' => ctrans('texts.po_number')];
|
||||
$data['$entity.datetime'] = ['value' => $this->formatDatetime($this->entity->created_at, $this->entity->client->date_format()), 'label' => ctrans('texts.date')];
|
||||
$data['$entity.datetime'] = ['value' => $this->formatDatetime($this->entity->created_at, $this->entity->client->date_format(), $this->entity->client->locale()), 'label' => ctrans('texts.date')];
|
||||
$data['$invoice.datetime'] = &$data['$entity.datetime'];
|
||||
$data['$quote.datetime'] = &$data['$entity.datetime'];
|
||||
$data['$credit.datetime'] = &$data['$entity.datetime'];
|
||||
@ -125,6 +125,9 @@ class HtmlEngine
|
||||
$data['$terms'] = &$data['$entity.terms'];
|
||||
$data['$view_link'] = ['value' => '<a class="button" href="'.$this->invitation->getLink().'">'.ctrans('texts.view_invoice').'</a>', 'label' => ctrans('texts.view_invoice')];
|
||||
$data['$view_url'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_invoice')];
|
||||
|
||||
if($this->entity->project()->exists())
|
||||
$data['$project.name'] = ['value' => $this->entity->project->name, 'label' => ctrans('texts.project_name')];
|
||||
}
|
||||
|
||||
if ($this->entity_string == 'quote') {
|
||||
@ -154,8 +157,11 @@ class HtmlEngine
|
||||
|
||||
if ($this->entity->partial > 0) {
|
||||
$data['$balance_due'] = ['value' => Number::formatMoney($this->entity->partial, $this->client) ?: ' ', 'label' => ctrans('texts.partial_due')];
|
||||
$data['$balance_due_raw'] = ['value' => $this->entity->partial, 'label' => ctrans('texts.partial_due')];
|
||||
} else {
|
||||
$data['$balance_due'] = ['value' => Number::formatMoney($this->entity->balance, $this->client) ?: ' ', 'label' => ctrans('texts.balance_due')];
|
||||
$data['$balance_due_raw'] = ['value' => $this->entity->balance, 'label' => ctrans('texts.balance_due')];
|
||||
|
||||
}
|
||||
|
||||
$data['$quote.balance_due'] = $data['$balance_due'];
|
||||
@ -174,7 +180,7 @@ class HtmlEngine
|
||||
$data['$credit.number'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.credit_number')];
|
||||
$data['$credit.total'] = &$data['$credit.total'];
|
||||
$data['$credit.po_number'] = &$data['$invoice.po_number'];
|
||||
$data['$credit.date'] = ['value' => $this->formatDate($this->entity->date, $this->entity->client->date_format()), 'label' => ctrans('texts.credit_date')];
|
||||
$data['$credit.date'] = ['value' => $this->translateDate($this->entity->date, $this->entity->client->date_format(), $this->entity->client->locale()), 'label' => ctrans('texts.credit_date')];
|
||||
$data['$balance'] = ['value' => Number::formatMoney($this->entity_calc->getBalance(), $this->client) ?: ' ', 'label' => ctrans('texts.balance')];
|
||||
$data['$credit.balance'] = &$data['$balance'];
|
||||
|
||||
@ -186,20 +192,20 @@ class HtmlEngine
|
||||
$data['$invoice.custom2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice2', $this->entity->custom_value2, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice2')];
|
||||
$data['$invoice.custom3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice3', $this->entity->custom_value3, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice3')];
|
||||
$data['$invoice.custom4'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice4', $this->entity->custom_value4, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice4')];
|
||||
$data['$invoice.public_notes'] = ['value' => nl2br($this->entity->public_notes) ?: ' ', 'label' => ctrans('texts.public_notes')];
|
||||
$data['$invoice.public_notes'] = ['value' => nl2br($this->entity->public_notes) ?: '', 'label' => ctrans('texts.public_notes')];
|
||||
$data['$entity.public_notes'] = &$data['$invoice.public_notes'];
|
||||
$data['$public_notes'] = &$data['$invoice.public_notes'];
|
||||
|
||||
$data['$entity_issued_to'] = ['value' => '', 'label' => ctrans("texts.{$this->entity_string}_issued_to")];
|
||||
$data['$your_entity'] = ['value' => '', 'label' => ctrans("texts.your_{$this->entity_string}")];
|
||||
|
||||
$data['$quote.date'] = ['value' => $this->formatDate($this->entity->date, $this->entity->client->date_format()) ?: ' ', 'label' => ctrans('texts.quote_date')];
|
||||
$data['$quote.date'] = ['value' => $this->translateDate($this->entity->date, $this->entity->client->date_format(), $this->entity->client->locale()) ?: ' ', 'label' => ctrans('texts.quote_date')];
|
||||
$data['$quote.number'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.quote_number')];
|
||||
$data['$quote.po_number'] = &$data['$invoice.po_number'];
|
||||
$data['$quote.quote_number'] = &$data['$quote.number'];
|
||||
$data['$quote_no'] = &$data['$quote.number'];
|
||||
$data['$quote.quote_no'] = &$data['$quote.number'];
|
||||
$data['$quote.valid_until'] = ['value' => $this->formatDate($this->entity->due_date, $this->client->date_format()), 'label' => ctrans('texts.valid_until')];
|
||||
$data['$quote.valid_until'] = ['value' => $this->translateDate($this->entity->due_date, $this->client->date_format(), $this->entity->client->locale()), 'label' => ctrans('texts.valid_until')];
|
||||
$data['$credit_amount'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->client) ?: ' ', 'label' => ctrans('texts.credit_amount')];
|
||||
$data['$credit_balance'] = ['value' => Number::formatMoney($this->entity->balance, $this->client) ?: ' ', 'label' => ctrans('texts.credit_balance')];
|
||||
|
||||
@ -287,10 +293,10 @@ class HtmlEngine
|
||||
$data['$company3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'company3', $this->settings->custom_value3, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'company3')];
|
||||
$data['$company4'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'company4', $this->settings->custom_value4, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'company4')];
|
||||
|
||||
$data['$custom_surcharge1'] = ['value' => $this->entity->custom_surcharge1 ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'custom_surcharge1')];
|
||||
$data['$custom_surcharge2'] = ['value' => $this->entity->custom_surcharge2 ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'custom_surcharge2')];
|
||||
$data['$custom_surcharge3'] = ['value' => $this->entity->custom_surcharge3 ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'custom_surcharge3')];
|
||||
$data['$custom_surcharge4'] = ['value' => $this->entity->custom_surcharge4 ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'custom_surcharge4')];
|
||||
$data['$custom_surcharge1'] = ['value' => Number::formatMoney($this->entity->custom_surcharge1, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'surcharge1')];
|
||||
$data['$custom_surcharge2'] = ['value' => Number::formatMoney($this->entity->custom_surcharge2, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'surcharge2')];
|
||||
$data['$custom_surcharge3'] = ['value' => Number::formatMoney($this->entity->custom_surcharge3, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'surcharge3')];
|
||||
$data['$custom_surcharge4'] = ['value' => Number::formatMoney($this->entity->custom_surcharge4, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'surcharge4')];
|
||||
|
||||
$data['$product.item'] = ['value' => '', 'label' => ctrans('texts.item')];
|
||||
$data['$product.date'] = ['value' => '', 'label' => ctrans('texts.date')];
|
||||
@ -355,8 +361,6 @@ class HtmlEngine
|
||||
$arrKeysLength = array_map('strlen', array_keys($data));
|
||||
array_multisort($arrKeysLength, SORT_DESC, $data);
|
||||
|
||||
//info(print_r($data,1));
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
@ -169,6 +169,9 @@ trait CompanySettingsSaver
|
||||
if (substr($key, -3) == '_id' || substr($key, -14) == 'number_counter') {
|
||||
$value = 'integer';
|
||||
|
||||
if($key == 'gmail_sending_user_id')
|
||||
$value = 'string';
|
||||
|
||||
if (! property_exists($settings, $key)) {
|
||||
continue;
|
||||
} elseif ($this->checkAttribute($value, $settings->{$key})) {
|
||||
@ -218,12 +221,14 @@ trait CompanySettingsSaver
|
||||
case 'int':
|
||||
case 'integer':
|
||||
return ctype_digit(strval(abs($value)));
|
||||
// return is_int($value) || ctype_digit(strval(abs($value)));
|
||||
case 'real':
|
||||
case 'float':
|
||||
case 'double':
|
||||
return is_float($value) || is_numeric(strval($value));
|
||||
case 'string':
|
||||
return method_exists($value, '__toString') || is_null($value) || is_string($value);
|
||||
//return is_null($value) || is_string($value);
|
||||
case 'bool':
|
||||
case 'boolean':
|
||||
return is_bool($value) || (int) filter_var($value, FILTER_VALIDATE_BOOLEAN);
|
||||
|
@ -60,9 +60,6 @@ trait MakesDates
|
||||
if (!isset($date)) {
|
||||
return '';
|
||||
}
|
||||
// if (!$date || strlen($date) < 1) {
|
||||
// return '';
|
||||
// }
|
||||
|
||||
if (is_string($date)) {
|
||||
$date = $this->convertToDateObject($date);
|
||||
@ -99,4 +96,13 @@ trait MakesDates
|
||||
$dt->setTimezone(new DateTimeZone('UTC'));
|
||||
return $dt;
|
||||
}
|
||||
}
|
||||
|
||||
public function translateDate($date, $format, $locale)
|
||||
{
|
||||
|
||||
Carbon::setLocale($locale);
|
||||
return Carbon::parse($date)->translatedFormat($format);
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -48,6 +48,10 @@ trait SettingsSaver
|
||||
/*Separate loop if it is a _id field which is an integer cast as a string*/
|
||||
elseif (substr($key, -3) == '_id' || substr($key, -14) == 'number_counter') {
|
||||
$value = 'integer';
|
||||
|
||||
if($key == 'gmail_sending_user_id')
|
||||
$value = 'string';
|
||||
|
||||
if (! property_exists($settings, $key)) {
|
||||
continue;
|
||||
} elseif (! $this->checkAttribute($value, $settings->{$key})) {
|
||||
|
@ -30,19 +30,21 @@
|
||||
"ext-dom": "*",
|
||||
"ext-json": "*",
|
||||
"ext-libxml": "*",
|
||||
"asgrim/ofxparser": "^1.2",
|
||||
"authorizenet/authorizenet": "^2.0",
|
||||
"bacon/bacon-qr-code": "^2.0",
|
||||
"beganovich/snappdf": "^1.0",
|
||||
"checkout/checkout-sdk-php": "^1.0",
|
||||
"cleverit/ubl_invoice": "^1.3",
|
||||
"coconutcraig/laravel-postmark": "^2.10",
|
||||
"composer/composer": "^2",
|
||||
"czproject/git-php": "^3.17",
|
||||
"dacastro4/laravel-gmail": "dev-master",
|
||||
"doctrine/dbal": "^2.10",
|
||||
"fideloper/proxy": "^4.2",
|
||||
"fzaninotto/faker": "^1.4",
|
||||
"google/apiclient": "^2.7",
|
||||
"guzzlehttp/guzzle": "^7.0.1",
|
||||
"hashids/hashids": "^3.0",
|
||||
"hashids/hashids": "^4.0",
|
||||
"intervention/image": "^2.5",
|
||||
"laracasts/presenter": "^0.2.1",
|
||||
"laravel/framework": "^8.0",
|
||||
@ -59,11 +61,11 @@
|
||||
"maennchen/zipstream-php": "^1.2",
|
||||
"nwidart/laravel-modules": "^8.0",
|
||||
"omnipay/paypal": "^3.0",
|
||||
"pragmarx/google2fa": "^8.0",
|
||||
"predis/predis": "^1.1",
|
||||
"sentry/sentry-laravel": "^2",
|
||||
"stripe/stripe-php": "^7.50",
|
||||
"turbo124/beacon": "^1",
|
||||
"turbo124/laravel-gmail": "^5.0",
|
||||
"webpatser/laravel-countries": "dev-master#75992ad",
|
||||
"wildbit/swiftmailer-postmark": "^3.3"
|
||||
},
|
||||
@ -89,7 +91,6 @@
|
||||
"Database\\Seeders\\": "database/seeders/"
|
||||
},
|
||||
"files": [
|
||||
"app/Libraries/OFX.php"
|
||||
]
|
||||
},
|
||||
"autoload-dev": {
|
||||
|
1102
composer.lock
generated
1102
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -13,7 +13,7 @@ return [
|
||||
'require_https' => env('REQUIRE_HTTPS', true),
|
||||
'app_url' => rtrim(env('APP_URL', ''), '/'),
|
||||
'app_domain' => env('APP_DOMAIN', ''),
|
||||
'app_version' => '5.1.8',
|
||||
'app_version' => '5.1.9',
|
||||
'minimum_client_version' => '5.0.16',
|
||||
'terms_version' => '1.0.1',
|
||||
'api_secret' => env('API_SECRET', false),
|
||||
|
30
config/postmark.php
Normal file
30
config/postmark.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Postmark credentials
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may provide your Postmark server API token.
|
||||
|
|
||||
*/
|
||||
|
||||
'secret' => env('POSTMARK_SECRET'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Guzzle options
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Under the hood we use Guzzle to make API calls to Postmark.
|
||||
| Here you may provide any request options for Guzzle.
|
||||
|
|
||||
*/
|
||||
|
||||
'guzzle' => [
|
||||
'timeout' => 10,
|
||||
'connect_timeout' => 10,
|
||||
],
|
||||
];
|
@ -36,9 +36,6 @@ return [
|
||||
'gmail' => [
|
||||
'token' => '',
|
||||
],
|
||||
'postmark' => [
|
||||
'token' => env('POSTMARK_API_TOKEN', ''),
|
||||
],
|
||||
'stripe' => [
|
||||
'model' => App\Models\User::class,
|
||||
'key' => env('STRIPE_KEY'),
|
||||
|
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class EmailLastConfirmedEmailAddressUsersTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('users', function(Blueprint $table){
|
||||
$table->string('last_confirmed_email_address')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
@ -80,4 +80,4 @@ class DateFormatsSeeder extends Seeder
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
2
public/css/app.css
vendored
2
public/css/app.css
vendored
File diff suppressed because one or more lines are too long
4
public/flutter_service_worker.js
vendored
4
public/flutter_service_worker.js
vendored
@ -3,7 +3,7 @@ const MANIFEST = 'flutter-app-manifest';
|
||||
const TEMP = 'flutter-temp-cache';
|
||||
const CACHE_NAME = 'flutter-app-cache';
|
||||
const RESOURCES = {
|
||||
"main.dart.js": "5e63c564cc944e8930bd80a70ea4e156",
|
||||
"main.dart.js": "3720742fd85b1fbea07bcf1bd87689c0",
|
||||
"icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed",
|
||||
"icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35",
|
||||
"favicon.ico": "51636d3a390451561744c42188ccd628",
|
||||
@ -29,7 +29,7 @@ const RESOURCES = {
|
||||
"assets/assets/images/google-icon.png": "0f118259ce403274f407f5e982e681c3",
|
||||
"assets/assets/images/logo.png": "090f69e23311a4b6d851b3880ae52541",
|
||||
"assets/packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf": "3e722fd57a6db80ee119f0e2c230ccff",
|
||||
"version.json": "1cca74946fc6f0a80171ac6667e8719a",
|
||||
"version.json": "c71c432fdc63e809b2f63fcc64edd8cd",
|
||||
"manifest.json": "77215c1737c7639764e64a192be2f7b8",
|
||||
"favicon.png": "dca91c54388f52eded692718d5a98b8b",
|
||||
"/": "23224b5e03519aaa87594403d54412cf"
|
||||
|
219926
public/main.dart.js
vendored
219926
public/main.dart.js
vendored
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
||||
{
|
||||
"/js/app.js": "/js/app.js?id=696e8203d5e8e7cf5ff5",
|
||||
"/css/app.css": "/css/app.css?id=1ea5400f7ae7b45f050c",
|
||||
"/css/app.css": "/css/app.css?id=58736e43b16ddde82ba9",
|
||||
"/js/clients/invoices/action-selectors.js": "/js/clients/invoices/action-selectors.js?id=a09bb529b8e1826f13b4",
|
||||
"/js/clients/invoices/payment.js": "/js/clients/invoices/payment.js?id=8ce8955ba775ea5f47d1",
|
||||
"/js/clients/payment_methods/authorize-authorize-card.js": "/js/clients/payment_methods/authorize-authorize-card.js?id=206d7de4ac97612980ff",
|
||||
|
@ -1 +1 @@
|
||||
{"app_name":"invoiceninja_flutter","version":"5.0.42","build_number":"42"}
|
||||
{"app_name":"invoiceninja_flutter","version":"5.0.43","build_number":"43"}
|
@ -8,7 +8,7 @@
|
||||
<p>{{ $greeting }}</p>
|
||||
@endif
|
||||
|
||||
<p>{{ $title }}</p>
|
||||
<h2>{{ $title }}</h2>
|
||||
|
||||
<p>{{ $message }}</p>
|
||||
|
||||
|
@ -19,7 +19,7 @@
|
||||
}
|
||||
|
||||
.primary-color-bg {
|
||||
background-color: var(--primary-color);
|
||||
background-color: {{ isset($settings) ? $settings->primary_color : '#4caf50' }};
|
||||
}
|
||||
|
||||
#email-content h1, h2, h3, h4 {
|
||||
@ -33,11 +33,11 @@
|
||||
display: block;
|
||||
color: {{ $design == 'light' ? 'black' : 'white' }};
|
||||
padding-bottom: 20px;
|
||||
padding-top: 20px;
|
||||
/*padding-top: 20px;*/
|
||||
}
|
||||
|
||||
.button {
|
||||
background-color: var(--primary-color);
|
||||
background-color: {{ isset($settings) ? $settings->primary_color : '#4caf50' }};
|
||||
color: white;
|
||||
padding: 10px 16px;
|
||||
text-decoration: none;
|
||||
|
@ -0,0 +1,45 @@
|
||||
<div>
|
||||
@unless(count($methods) == 0)
|
||||
<div x-data="{ open: false }" @keydown.window.escape="open = false" @click.away="open = false"
|
||||
class="relative inline-block text-left" data-cy="payment-methods-dropdown">
|
||||
<div>
|
||||
<div class="rounded-md shadow-sm">
|
||||
<button data-cy="pay-now-dropdown" @click="open = !open" type="button"
|
||||
class="inline-flex justify-center w-full px-4 py-2 text-sm font-medium leading-5 text-gray-700 transition duration-150 ease-in-out bg-white border border-gray-300 rounded-md hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800">
|
||||
{{ ctrans('texts.pay_now') }}
|
||||
<svg class="w-5 h-5 ml-2 -mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd"
|
||||
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
||||
clip-rule="evenodd"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div x-show="open" class="absolute right-0 w-56 mt-2 origin-top-right rounded-md shadow-lg">
|
||||
<div class="bg-white rounded-md shadow-xs">
|
||||
<div class="py-1">
|
||||
@foreach($methods as $index => $method)
|
||||
@if($method['label'] == 'Custom')
|
||||
<a href="#" @click="{ open = false }" data-cy="pay-with-custom"
|
||||
data-company-gateway-id="{{ $method['company_gateway_id'] }}"
|
||||
data-gateway-type-id="{{ $method['gateway_type_id'] }}"
|
||||
class="block px-4 py-2 text-sm leading-5 text-gray-700 dropdown-gateway-button hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900"
|
||||
data-cy="payment-method">
|
||||
{{ \App\Models\CompanyGateway::find($method['company_gateway_id'])->firstOrFail()->getConfigField('name') }}
|
||||
</a>
|
||||
@elseif($total > 0)
|
||||
<a href="#" @click="{ open = false }" data-cy="pay-with-{{ $index }}"
|
||||
data-company-gateway-id="{{ $method['company_gateway_id'] }}"
|
||||
data-gateway-type-id="{{ $method['gateway_type_id'] }}"
|
||||
class="block px-4 py-2 text-sm leading-5 text-gray-700 dropdown-gateway-button hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900"
|
||||
data-cy="payment-method">
|
||||
{{ $method['label'] }}
|
||||
</a>
|
||||
@endif
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endunless
|
||||
</div>
|
@ -19,38 +19,7 @@
|
||||
<div class="col-span-6 md:col-start-2 md:col-span-4">
|
||||
<div class="flex justify-end">
|
||||
<div class="flex justify-end mb-2">
|
||||
<!-- Pay now button -->
|
||||
@if(count($payment_methods) > 0)
|
||||
<div x-data="{ open: false }" @keydown.window.escape="open = false" @click.away="open = false" class="relative inline-block text-left" data-cy="payment-methods-dropdown">
|
||||
<div>
|
||||
<div class="rounded-md shadow-sm">
|
||||
<button data-cy="pay-now-dropdown" @click="open = !open" type="button" class="inline-flex justify-center w-full px-4 py-2 text-sm font-medium leading-5 text-gray-700 transition duration-150 ease-in-out bg-white border border-gray-300 rounded-md hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800">
|
||||
{{ ctrans('texts.pay_now') }}
|
||||
<svg class="w-5 h-5 ml-2 -mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div x-show="open" class="absolute right-0 w-56 mt-2 origin-top-right rounded-md shadow-lg">
|
||||
<div class="bg-white rounded-md shadow-xs">
|
||||
<div class="py-1">
|
||||
@foreach($payment_methods as $index => $payment_method)
|
||||
@if($payment_method['label'] == 'Custom')
|
||||
<a href="#" @click="{ open = false }" data-cy="pay-with-custom" data-company-gateway-id="{{ $payment_method['company_gateway_id'] }}" data-gateway-type-id="{{ $payment_method['gateway_type_id'] }}" class="block px-4 py-2 text-sm leading-5 text-gray-700 dropdown-gateway-button hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" data-cy="payment-method">
|
||||
{{ \App\Models\CompanyGateway::find($payment_method['company_gateway_id'])->firstOrFail()->getConfigField('name') }}
|
||||
</a>
|
||||
@elseif($total > 0)
|
||||
<a href="#" @click="{ open = false }" data-cy="pay-with-{{ $index }}" data-company-gateway-id="{{ $payment_method['company_gateway_id'] }}" data-gateway-type-id="{{ $payment_method['gateway_type_id'] }}" class="block px-4 py-2 text-sm leading-5 text-gray-700 dropdown-gateway-button hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" data-cy="payment-method">
|
||||
{{ $payment_method['label'] }}
|
||||
</a>
|
||||
@endif
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@livewire('pay-now-dropdown', ['total' => $total])
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -3,7 +3,10 @@
|
||||
|
||||
@push('head')
|
||||
<meta name="pdf-url" content="{{ $invoice->pdf_file_path() }}">
|
||||
<meta name="show-invoice-terms" content="{{ $settings->show_accept_invoice_terms ? true : false }}">
|
||||
<meta name="require-invoice-signature" content="{{ $settings->require_invoice_signature ? true : false }}">
|
||||
<script src="{{ asset('js/vendor/pdf.js/pdf.min.js') }}"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/signature_pad@2.3.2/dist/signature_pad.min.js"></script>
|
||||
@endpush
|
||||
|
||||
@section('body')
|
||||
@ -15,19 +18,30 @@
|
||||
@endif
|
||||
|
||||
@if($invoice->isPayable())
|
||||
<form action="{{ route('client.invoices.bulk') }}" method="post">
|
||||
<form action="{{ ($settings->client_portal_allow_under_payment || $settings->client_portal_allow_over_payment) ? route('client.invoices.bulk') : route('client.payments.process') }}" method="post" id="payment-form">
|
||||
@csrf
|
||||
<input type="hidden" name="invoices[]" value="{{ $invoice->hashed_id }}">
|
||||
<input type="hidden" name="action" value="payment">
|
||||
|
||||
<input type="hidden" name="company_gateway_id" id="company_gateway_id">
|
||||
<input type="hidden" name="payment_method_id" id="payment_method_id">
|
||||
<input type="hidden" name="signature">
|
||||
|
||||
<input type="hidden" name="payable_invoices[0][amount]" value="{{ $invoice->partial > 0 ? \App\Utils\Number::formatValue($invoice->partial, $invoice->client->currency()) : \App\Utils\Number::formatValue($invoice->balance, $invoice->client->currency()) }}">
|
||||
<input type="hidden" name="payable_invoices[0][invoice_id]" value="{{ $invoice->hashed_id }}">
|
||||
|
||||
<div class="bg-white shadow sm:rounded-lg mb-4" translate>
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<div class="sm:flex sm:items-start sm:justify-between">
|
||||
<div>
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||
{{ ctrans('texts.invoice_number_placeholder', ['invoice' => $invoice->number])}} - {{ ctrans('texts.unpaid') }}
|
||||
{{ ctrans('texts.invoice_number_placeholder', ['invoice' => $invoice->number])}}
|
||||
- {{ ctrans('texts.unpaid') }}
|
||||
</h3>
|
||||
<div class="mt-2 max-w-xl text-sm leading-5 text-gray-500">
|
||||
<p translate>
|
||||
{{ ctrans('texts.invoice_still_unpaid') }}
|
||||
<!-- This invoice is still not paid. Click the button to complete the payment. -->
|
||||
{{ ctrans('texts.invoice_still_unpaid') }}
|
||||
<!-- This invoice is still not paid. Click the button to complete the payment. -->
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -35,7 +49,12 @@
|
||||
<div class="inline-flex rounded-md shadow-sm">
|
||||
<input type="hidden" name="invoices[]" value="{{ $invoice->hashed_id }}">
|
||||
<input type="hidden" name="action" value="payment">
|
||||
<button class="button button-primary bg-primary">{{ ctrans('texts.pay_now') }}</button>
|
||||
|
||||
@if($settings->client_portal_allow_under_payment || $settings->client_portal_allow_over_payment)
|
||||
<button class="button button-primary bg-primary">{{ ctrans('texts.pay_now') }}</button>
|
||||
@else
|
||||
@livewire('pay-now-dropdown', ['total' => $invoice->partial > 0 ? $invoice->partial : $invoice->balance])
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -43,18 +62,18 @@
|
||||
</div>
|
||||
</form>
|
||||
@else
|
||||
<div class="bg-white shadow sm:rounded-lg mb-4">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<div class="sm:flex sm:items-start sm:justify-between">
|
||||
<div>
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||
{{ ctrans('texts.invoice_number_placeholder', ['invoice' => $invoice->number])}}
|
||||
- {{ ctrans('texts.paid') }}
|
||||
</h3>
|
||||
<div class="bg-white shadow sm:rounded-lg mb-4">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<div class="sm:flex sm:items-start sm:justify-between">
|
||||
<div>
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||
{{ ctrans('texts.invoice_number_placeholder', ['invoice' => $invoice->number])}}
|
||||
- {{ ctrans('texts.paid') }}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if($invoice->documents->count() > 0)
|
||||
@ -90,57 +109,88 @@
|
||||
<div class="flex items-center justify-between mt-4">
|
||||
<section class="flex items-center">
|
||||
<div class="items-center" style="display: none" id="pagination-button-container">
|
||||
<button class="input-label focus:outline-none hover:text-blue-600 transition ease-in-out duration-300" id="previous-page-button" title="Previous page">
|
||||
<button class="input-label focus:outline-none hover:text-blue-600 transition ease-in-out duration-300"
|
||||
id="previous-page-button" title="Previous page">
|
||||
<svg class="w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="input-label focus:outline-none hover:text-blue-600 transition ease-in-out duration-300" id="next-page-button" title="Next page">
|
||||
<button class="input-label focus:outline-none hover:text-blue-600 transition ease-in-out duration-300"
|
||||
id="next-page-button" title="Next page">
|
||||
<svg class="w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<span class="text-sm text-gray-700 ml-2">{{ ctrans('texts.page') }}:
|
||||
<span class="text-sm text-gray-700 ml-2 lg:hidden">{{ ctrans('texts.page') }}:
|
||||
<span id="current-page-container"></span>
|
||||
<span>{{ strtolower(ctrans('texts.of')) }}</span>
|
||||
<span id="total-page-container"></span>
|
||||
</span>
|
||||
</section>
|
||||
<section class="flex items-center space-x-1">
|
||||
<div class="flex items-center mr-4 space-x-1">
|
||||
<div class="flex items-center mr-4 space-x-1 lg:hidden">
|
||||
<span class="text-gray-600 mr-2" id="zoom-level">100%</span>
|
||||
<a href="#" id="zoom-in">
|
||||
<svg class="text-gray-400 hover:text-gray-600 focus:outline-none focus:text-gray-600 cursor-pointer" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line><line x1="11" y1="8" x2="11" y2="14"></line><line x1="8" y1="11" x2="14" y2="11"></line></svg>
|
||||
<svg class="text-gray-400 hover:text-gray-600 focus:outline-none focus:text-gray-600 cursor-pointer"
|
||||
xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="11" cy="11" r="8"></circle>
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
||||
<line x1="11" y1="8" x2="11" y2="14"></line>
|
||||
<line x1="8" y1="11" x2="14" y2="11"></line>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="#" id="zoom-out">
|
||||
<svg class="text-gray-400 hover:text-gray-600 focus:outline-none focus:text-gray-600 cursor-pointer" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line><line x1="8" y1="11" x2="14" y2="11"></line></svg>
|
||||
<svg class="text-gray-400 hover:text-gray-600 focus:outline-none focus:text-gray-600 cursor-pointer"
|
||||
xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="11" cy="11" r="8"></circle>
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
||||
<line x1="8" y1="11" x2="14" y2="11"></line>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<div x-data="{ open: false }" @keydown.escape="open = false" @click.away="open = false" class="relative inline-block text-left">
|
||||
<div x-data="{ open: false }" @keydown.escape="open = false" @click.away="open = false"
|
||||
class="relative inline-block text-left">
|
||||
<div>
|
||||
<button @click="open = !open" class="flex items-center text-gray-400 hover:text-gray-600 focus:outline-none focus:text-gray-600">
|
||||
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z" />
|
||||
</svg>
|
||||
<button @click="open = !open"
|
||||
class="flex items-center text-gray-400 hover:text-gray-600 focus:outline-none focus:text-gray-600">
|
||||
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div x-show="open" x-transition:enter="transition ease-out duration-100" x-transition:enter-start="transform opacity-0 scale-95" x-transition:enter-end="transform opacity-100 scale-100" x-transition:leave="transition ease-in duration-75" x-transition:leave-start="transform opacity-100 scale-100" x-transition:leave-end="transform opacity-0 scale-95" class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg">
|
||||
<div x-show="open" x-transition:enter="transition ease-out duration-100"
|
||||
x-transition:enter-start="transform opacity-0 scale-95"
|
||||
x-transition:enter-end="transform opacity-100 scale-100"
|
||||
x-transition:leave="transition ease-in duration-75"
|
||||
x-transition:leave-start="transform opacity-100 scale-100"
|
||||
x-transition:leave-end="transform opacity-0 scale-95"
|
||||
class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg">
|
||||
<div class="rounded-md bg-white shadow-xs">
|
||||
<div class="py-1">
|
||||
<a target="_blank" href="?mode=fullscreen" class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900">{{ ctrans('texts.open_in_new_tab') }}</a>
|
||||
</div>
|
||||
<div class="py-1">
|
||||
<a target="_blank" href="?mode=fullscreen"
|
||||
class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900">{{ ctrans('texts.open_in_new_tab') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<iframe src="{{ $invoice->pdf_file_path() }}" class="h-screen w-full border-0 sm:hidden lg:block mt-4"></iframe>
|
||||
|
||||
<div class="flex justify-center">
|
||||
<canvas id="pdf-placeholder" class="shadow rounded-lg bg-white mt-4 p-4"></canvas>
|
||||
<canvas id="pdf-placeholder" class="shadow rounded-lg bg-white lg:hidden mt-4 p-4"></canvas>
|
||||
</div>
|
||||
|
||||
@include('portal.ninja2020.invoices.includes.terms', ['entities' => [$invoice], 'entity_type' => ctrans('texts.invoice')])
|
||||
@include('portal.ninja2020.invoices.includes.signature')
|
||||
@endsection
|
||||
|
||||
@section('footer')
|
||||
<script src="{{ asset('js/clients/shared/pdf.js') }}"></script>
|
||||
<script src="{{ asset('js/clients/invoices/payment.js') }}"></script>
|
||||
@endsection
|
||||
|
@ -1,57 +0,0 @@
|
||||
@extends('portal.ninja2020.layout.clean', ['custom_body_class' => 'overflow-y-hidden'])
|
||||
@section('meta_title', ctrans('texts.view_invoice'))
|
||||
|
||||
@section('body')
|
||||
@if($invoice->isPayable())
|
||||
<form action="{{ route('client.invoices.bulk') }}" method="post">
|
||||
@csrf
|
||||
<div class="bg-white shadow sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<div class="sm:flex sm:items-start sm:justify-between">
|
||||
<div>
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||
{{ ctrans('texts.invoice_number_placeholder', ['invoice' => $invoice->number])}}
|
||||
- {{ ctrans('texts.unpaid') }}
|
||||
</h3>
|
||||
<div class="mt-2 max-w-xl text-sm leading-5 text-gray-500">
|
||||
<p translate>
|
||||
{{ ctrans('texts.invoice_still_unpaid') }}
|
||||
<!-- This invoice is still not paid. Click the button to complete the payment. -->
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 sm:mt-0 sm:ml-6 sm:flex-shrink-0 sm:flex sm:items-center">
|
||||
<a href="{{ route('client.invoice.show', $invoice->hashed_id) }}?mode=portal"
|
||||
class="mr-4 text-primary">
|
||||
← {{ ctrans('texts.client_portal') }}
|
||||
</a>
|
||||
|
||||
<div class="inline-flex rounded-md shadow-sm">
|
||||
<input type="hidden" name="invoices[]" value="{{ $invoice->hashed_id }}">
|
||||
<input type="hidden" name="action" value="payment">
|
||||
<button class="button button-primary bg-primary">{{ ctrans('texts.pay_now') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@else
|
||||
<div class="bg-white shadow sm:rounded-lg mb-4">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<div class="sm:flex sm:items-start sm:justify-between">
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||
{{ ctrans('texts.invoice_number_placeholder', ['invoice' => $invoice->number])}}
|
||||
- {{ ctrans('texts.paid') }}
|
||||
</h3>
|
||||
<a href="{{ route('client.invoice.show', $invoice->hashed_id) }}?mode=portal"
|
||||
class="mr-4 text-primary">
|
||||
← {{ ctrans('texts.client_portal') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<iframe src="{{ $invoice->pdf_file_path() }}" class="h-screen w-full border-0"></iframe>
|
||||
@endsection
|
@ -20,73 +20,48 @@
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center justify-between mt-4">
|
||||
<section class="flex items-center">
|
||||
<div class="items-center" style="display: none" id="pagination-button-container">
|
||||
<button class="input-label focus:outline-none hover:text-blue-600 transition ease-in-out duration-300"
|
||||
id="previous-page-button" title="Previous page">
|
||||
<button class="input-label focus:outline-none hover:text-blue-600 transition ease-in-out duration-300" id="previous-page-button" title="Previous page">
|
||||
<svg class="w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="input-label focus:outline-none hover:text-blue-600 transition ease-in-out duration-300"
|
||||
id="next-page-button" title="Next page">
|
||||
<button class="input-label focus:outline-none hover:text-blue-600 transition ease-in-out duration-300" id="next-page-button" title="Next page">
|
||||
<svg class="w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<span class="text-sm text-gray-700 ml-2">{{ ctrans('texts.page') }}:
|
||||
<span class="text-sm text-gray-700 ml-2 lg:hidden">{{ ctrans('texts.page') }}:
|
||||
<span id="current-page-container"></span>
|
||||
<span>{{ strtolower(ctrans('texts.of')) }}</span>
|
||||
<span id="total-page-container"></span>
|
||||
</span>
|
||||
</section>
|
||||
<section class="flex items-center space-x-1">
|
||||
<div class="flex items-center mr-4 space-x-1">
|
||||
<span class="text-gray-600 mr-2" id="zoom-level">175%</span>
|
||||
<div class="flex items-center mr-4 space-x-1 lg:hidden">
|
||||
<span class="text-gray-600 mr-2" id="zoom-level">100%</span>
|
||||
<a href="#" id="zoom-in">
|
||||
<svg class="text-gray-400 hover:text-gray-600 focus:outline-none focus:text-gray-600 cursor-pointer"
|
||||
xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="11" cy="11" r="8"></circle>
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
||||
<line x1="11" y1="8" x2="11" y2="14"></line>
|
||||
<line x1="8" y1="11" x2="14" y2="11"></line>
|
||||
</svg>
|
||||
<svg class="text-gray-400 hover:text-gray-600 focus:outline-none focus:text-gray-600 cursor-pointer" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line><line x1="11" y1="8" x2="11" y2="14"></line><line x1="8" y1="11" x2="14" y2="11"></line></svg>
|
||||
</a>
|
||||
<a href="#" id="zoom-out">
|
||||
<svg class="text-gray-400 hover:text-gray-600 focus:outline-none focus:text-gray-600 cursor-pointer"
|
||||
xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="11" cy="11" r="8"></circle>
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
||||
<line x1="8" y1="11" x2="14" y2="11"></line>
|
||||
</svg>
|
||||
<svg class="text-gray-400 hover:text-gray-600 focus:outline-none focus:text-gray-600 cursor-pointer" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line><line x1="8" y1="11" x2="14" y2="11"></line></svg>
|
||||
</a>
|
||||
</div>
|
||||
<div x-data="{ open: false }" @keydown.escape="open = false" @click.away="open = false"
|
||||
class="relative inline-block text-left">
|
||||
<div x-data="{ open: false }" @keydown.escape="open = false" @click.away="open = false" class="relative inline-block text-left">
|
||||
<div>
|
||||
<button @click="open = !open"
|
||||
class="flex items-center text-gray-400 hover:text-gray-600 focus:outline-none focus:text-gray-600">
|
||||
<button @click="open = !open" class="flex items-center text-gray-400 hover:text-gray-600 focus:outline-none focus:text-gray-600">
|
||||
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z"/>
|
||||
<path d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div x-show="open" x-transition:enter="transition ease-out duration-100"
|
||||
x-transition:enter-start="transform opacity-0 scale-95"
|
||||
x-transition:enter-end="transform opacity-100 scale-100"
|
||||
x-transition:leave="transition ease-in duration-75"
|
||||
x-transition:leave-start="transform opacity-100 scale-100"
|
||||
x-transition:leave-end="transform opacity-0 scale-95"
|
||||
class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg">
|
||||
<div x-show="open" x-transition:enter="transition ease-out duration-100" x-transition:enter-start="transform opacity-0 scale-95" x-transition:enter-end="transform opacity-100 scale-100" x-transition:leave="transition ease-in duration-75" x-transition:leave-start="transform opacity-100 scale-100" x-transition:leave-end="transform opacity-0 scale-95" class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg">
|
||||
<div class="rounded-md bg-white shadow-xs">
|
||||
<div class="py-1">
|
||||
<a target="_blank" href="?mode=fullscreen"
|
||||
class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900">{{ ctrans('texts.open_in_new_tab') }}</a>
|
||||
<a target="_blank" href="?mode=fullscreen" class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900">{{ ctrans('texts.open_in_new_tab') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -94,9 +69,11 @@
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center">
|
||||
<div class="flex justify-center lg:hidden">
|
||||
<canvas id="pdf-placeholder" class="shadow rounded-lg bg-white mt-4 p-4"></canvas>
|
||||
</div>
|
||||
|
||||
<iframe src="{{ $quote->pdf_file_path() }}" class="h-screen w-full border-0 mt-4"></iframe>
|
||||
@endsection
|
||||
|
||||
@section('footer')
|
||||
|
@ -1,16 +0,0 @@
|
||||
@extends('portal.ninja2020.layout.clean')
|
||||
@section('meta_title', ctrans('texts.view_quote'))
|
||||
|
||||
@section('body')
|
||||
@if(!$quote->isApproved())
|
||||
@component('portal.ninja2020.quotes.includes.actions', ['quote' => $quote])
|
||||
@section('quote-not-approved-right-side')
|
||||
<a href="{{ route('client.quote.show', $quote->hashed_id) }}?mode=portal" class="mr-4 text-primary">
|
||||
← {{ ctrans('texts.client_portal') }}
|
||||
</a>
|
||||
@endsection
|
||||
@endcomponent
|
||||
@endif
|
||||
|
||||
<iframe src="{{ $quote->pdf_file_path() }}" class="h-screen w-full border-0"></iframe>
|
||||
@endsection
|
@ -36,6 +36,8 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a
|
||||
Route::put('clients/{client}/upload', 'ClientController@upload')->name('clients.upload');
|
||||
Route::post('clients/bulk', 'ClientController@bulk')->name('clients.bulk');
|
||||
|
||||
Route::post('connected_account', 'ConnectedAccountController@index');
|
||||
|
||||
Route::resource('client_statement', 'ClientStatementController@statement'); // name = (client_statement. index / create / show / update / destroy / edit
|
||||
|
||||
Route::post('companies/purge/{company}', 'MigrationController@purgeCompany')->middleware('password_protected');
|
||||
@ -146,6 +148,9 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a
|
||||
Route::resource('tokens', 'TokenController')->middleware('password_protected'); // name = (tokens. index / create / show / update / destroy / edit
|
||||
Route::post('tokens/bulk', 'TokenController@bulk')->name('tokens.bulk')->middleware('password_protected');
|
||||
|
||||
Route::get('settings/enable_two_factor', 'TwoFactorController@setupTwoFactor');
|
||||
Route::post('settings/enable_two_factor', 'TwoFactorController@enableTwoFactor');
|
||||
|
||||
Route::resource('vendors', 'VendorController'); // name = (vendors. index / create / show / update / destroy / edit
|
||||
Route::post('vendors/bulk', 'VendorController@bulk')->name('vendors.bulk');
|
||||
Route::put('vendors/{vendor}/upload', 'VendorController@upload');
|
||||
@ -156,6 +161,7 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a
|
||||
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('/user/{user}/reconfirm', 'UserController@reconfirm')->middleware('password_protected');
|
||||
|
||||
Route::resource('webhooks', 'WebhookController');
|
||||
Route::post('webhooks/bulk', 'WebhookController@bulk')->name('webhooks.bulk');
|
||||
@ -171,4 +177,6 @@ Route::match(['get', 'post'], 'payment_webhook/{company_key}/{company_gateway_id
|
||||
->middleware(['guest', 'api_db'])
|
||||
->name('payment_webhook');
|
||||
|
||||
Route::post('postmark_webhook', 'PostMarkController@webhook');
|
||||
|
||||
Route::fallback('BaseController@notFound');
|
||||
|
@ -74,7 +74,7 @@ class ImportCsvTest extends TestCase
|
||||
|
||||
$data = [
|
||||
'hash' => $hash,
|
||||
'column_map' => [ 'client' => $column_map ],
|
||||
'column_map' => [ 'client' => [ 'mapping' => $column_map ] ],
|
||||
'skip_header' => true,
|
||||
'import_type' => 'csv',
|
||||
];
|
||||
@ -106,7 +106,7 @@ class ImportCsvTest extends TestCase
|
||||
|
||||
$data = [
|
||||
'hash' => $hash,
|
||||
'column_map' => [ 'client' => $column_map ],
|
||||
'column_map' => [ 'client' => [ 'mapping' => $column_map ] ],
|
||||
'skip_header' => true,
|
||||
'import_type' => 'csv',
|
||||
];
|
||||
@ -139,7 +139,7 @@ class ImportCsvTest extends TestCase
|
||||
|
||||
$data = [
|
||||
'hash' => $hash,
|
||||
'column_map' => [ 'invoice' => $column_map ],
|
||||
'column_map' => [ 'invoice' => [ 'mapping' => $column_map ] ],
|
||||
'skip_header' => true,
|
||||
'import_type' => 'csv',
|
||||
];
|
||||
@ -167,7 +167,7 @@ class ImportCsvTest extends TestCase
|
||||
|
||||
$data = [
|
||||
'hash' => $hash,
|
||||
'column_map' => [ 'vendor' => $column_map ],
|
||||
'column_map' => [ 'vendor' => [ 'mapping' => $column_map ] ],
|
||||
'skip_header' => true,
|
||||
'import_type' => 'csv',
|
||||
];
|
||||
@ -192,7 +192,7 @@ class ImportCsvTest extends TestCase
|
||||
|
||||
$data = [
|
||||
'hash' => $hash,
|
||||
'column_map' => [ 'product' => $column_map ],
|
||||
'column_map' => [ 'product' => [ 'mapping' => $column_map ] ],
|
||||
'skip_header' => true,
|
||||
'import_type' => 'csv',
|
||||
];
|
||||
@ -217,7 +217,7 @@ class ImportCsvTest extends TestCase
|
||||
|
||||
$data = [
|
||||
'hash' => $hash,
|
||||
'column_map' => [ 'expense' => $column_map ],
|
||||
'column_map' => [ 'expense' => [ 'mapping' => $column_map ] ],
|
||||
'skip_header' => true,
|
||||
'import_type' => 'csv',
|
||||
];
|
||||
@ -249,7 +249,7 @@ class ImportCsvTest extends TestCase
|
||||
|
||||
$data = [
|
||||
'hash' => $hash,
|
||||
'column_map' => [ 'client' => $column_map ],
|
||||
'column_map' => [ 'client' => [ 'mapping' => $column_map ] ],
|
||||
'skip_header' => true,
|
||||
'import_type' => 'csv',
|
||||
];
|
||||
@ -282,7 +282,7 @@ class ImportCsvTest extends TestCase
|
||||
|
||||
$data = [
|
||||
'hash' => $hash,
|
||||
'column_map' => [ 'invoice' => $column_map ],
|
||||
'column_map' => [ 'invoice' => [ 'mapping' => $column_map ] ],
|
||||
'skip_header' => true,
|
||||
'import_type' => 'csv',
|
||||
];
|
||||
@ -308,7 +308,7 @@ class ImportCsvTest extends TestCase
|
||||
|
||||
$data = [
|
||||
'hash' => $hash,
|
||||
'column_map' => [ 'payment' => $column_map ],
|
||||
'column_map' => [ 'payment' => [ 'mapping' => $column_map ] ],
|
||||
'skip_header' => true,
|
||||
'import_type' => 'csv',
|
||||
];
|
||||
@ -348,4 +348,4 @@ class ImportCsvTest extends TestCase
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user