mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
commit
7d748c7881
@ -1 +1 @@
|
||||
5.6.30
|
||||
5.6.31
|
@ -35,6 +35,7 @@ use App\Models\BankTransaction;
|
||||
use App\Models\QuoteInvitation;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\CreditInvitation;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Models\InvoiceInvitation;
|
||||
use App\DataMapper\ClientSettings;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
@ -471,7 +472,7 @@ class CheckData extends Command
|
||||
$ii->saveQuietly();
|
||||
});
|
||||
|
||||
collect([Invoice::class, Quote::class, Credit::class, PurchaseOrder::class])->each(function ($entity) {
|
||||
collect([Invoice::class, Quote::class, Credit::class, PurchaseOrder::class, RecurringInvoice::class])->each(function ($entity) {
|
||||
if ($entity::doesntHave('invitations')->count() > 0) {
|
||||
$entity::doesntHave('invitations')->cursor()->each(function ($entity) {
|
||||
$client_vendor_key = 'client_id';
|
||||
@ -694,7 +695,7 @@ class CheckData extends Command
|
||||
{
|
||||
$this->wrong_balances = 0;
|
||||
|
||||
Client::cursor()->where('is_deleted', 0)->where('clients.updated_at', '>', now()->subDays(2))->each(function ($client) {
|
||||
Client::query()->cursor()->where('is_deleted', 0)->where('clients.updated_at', '>', now()->subDays(2))->each(function ($client) {
|
||||
$client->invoices->where('is_deleted', false)->whereIn('status_id', '!=', Invoice::STATUS_DRAFT)->each(function ($invoice) use ($client) {
|
||||
$total_paid = $invoice->payments()
|
||||
->where('is_deleted', false)->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment::STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])
|
||||
@ -876,7 +877,7 @@ class CheckData extends Command
|
||||
$this->wrong_balances = 0;
|
||||
$this->wrong_paid_to_dates = 0;
|
||||
|
||||
foreach (Client::where('is_deleted', 0)->where('clients.updated_at', '>', now()->subDays(2))->cursor() as $client) {
|
||||
foreach (Client::query()->where('is_deleted', 0)->where('clients.updated_at', '>', now()->subDays(2))->cursor() as $client) {
|
||||
$invoice_balance = $client->invoices()->where('is_deleted', false)->whereIn('status_id', [2,3])->sum('balance');
|
||||
$ledger = CompanyLedger::where('client_id', $client->id)->orderBy('id', 'DESC')->first();
|
||||
|
||||
|
101
app/DataMapper/Analytics/RevenueTrack.php
Normal file
101
app/DataMapper/Analytics/RevenueTrack.php
Normal file
@ -0,0 +1,101 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\DataMapper\Analytics;
|
||||
|
||||
use Turbo124\Beacon\ExampleMetric\GenericMixedMetric;
|
||||
|
||||
class RevenueTrack extends GenericMixedMetric
|
||||
{
|
||||
/**
|
||||
* The type of Sample.
|
||||
*
|
||||
* Monotonically incrementing counter
|
||||
*
|
||||
* - counter
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $type = 'mixed_metric';
|
||||
|
||||
/**
|
||||
* The name of the counter.
|
||||
* @var string
|
||||
*/
|
||||
public $name = 'app.revenue';
|
||||
|
||||
/**
|
||||
* The datetime of the counter measurement.
|
||||
*
|
||||
* date("Y-m-d H:i:s")
|
||||
*
|
||||
*/
|
||||
public $datetime;
|
||||
|
||||
/**
|
||||
* The Client email
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $string_metric5 = 'email';
|
||||
|
||||
/**
|
||||
* The AccountKey email
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $string_metric6 = 'key';
|
||||
|
||||
/**
|
||||
* Product Type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $string_metric7 = 'product';
|
||||
|
||||
/**
|
||||
* Gateway Reference
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $string_metric8 = 'gateway_reference';
|
||||
|
||||
public $string_metric9 = 'entity_reference';
|
||||
|
||||
public $string_metric10 = 'gateway_type';
|
||||
|
||||
/**
|
||||
* The counter
|
||||
* set to 1.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $int_metric1 = 1;
|
||||
|
||||
/**
|
||||
* Amount Received
|
||||
*
|
||||
* @var double
|
||||
*/
|
||||
public $double_metric2 = 0;
|
||||
|
||||
public function __construct($string_metric5, $string_metric6, $int_metric1, $double_metric2, $string_metric7, $string_metric8, $string_metric9, $string_metric10)
|
||||
{
|
||||
$this->int_metric1 = $int_metric1;
|
||||
$this->double_metric2 = $double_metric2;
|
||||
$this->string_metric5 = $string_metric5;
|
||||
$this->string_metric6 = $string_metric6;
|
||||
$this->string_metric7 = $string_metric7;
|
||||
$this->string_metric8 = $string_metric8;
|
||||
$this->string_metric9 = $string_metric9;
|
||||
$this->string_metric10 = $string_metric10;
|
||||
}
|
||||
}
|
@ -61,8 +61,13 @@ class InvoiceItem
|
||||
|
||||
public $tax_id = '';
|
||||
|
||||
public $task_id = '';
|
||||
|
||||
public $expense_id = '';
|
||||
|
||||
public static $casts = [
|
||||
'task_id' => 'string',
|
||||
'expense_id' => 'string',
|
||||
'tax_id' => 'string',
|
||||
'type_id' => 'string',
|
||||
'quantity' => 'float',
|
||||
|
36
app/Events/Account/StripeConnectFailure.php
Normal file
36
app/Events/Account/StripeConnectFailure.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Events\Account;
|
||||
|
||||
use App\Models\Company;
|
||||
use Illuminate\Broadcasting\Channel;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Broadcasting\PrivateChannel;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
|
||||
/**
|
||||
* Class StripeConnectFailure.
|
||||
*/
|
||||
class StripeConnectFailure
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
public function __construct(public Company $company, public string $db)
|
||||
{
|
||||
}
|
||||
|
||||
public function broadcastOn()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
@ -88,6 +88,11 @@ class CreditFilters extends QueryFilters
|
||||
->orWhere('credits.custom_value4', 'like', '%'.$filter.'%')
|
||||
->orWhereHas('client', function ($q) use ($filter) {
|
||||
$q->where('name', 'like', '%'.$filter.'%');
|
||||
})
|
||||
->orWhereHas('client.contacts', function ($q) use ($filter) {
|
||||
$q->where('first_name', 'like', '%'.$filter.'%')
|
||||
->orWhere('last_name', 'like', '%'.$filter.'%')
|
||||
->orWhere('email', 'like', '%'.$filter.'%');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -116,6 +116,11 @@ class InvoiceFilters extends QueryFilters
|
||||
->orWhere('custom_value4', 'like', '%'.$filter.'%')
|
||||
->orWhereHas('client', function ($q) use ($filter) {
|
||||
$q->where('name', 'like', '%'.$filter.'%');
|
||||
})
|
||||
->orWhereHas('client.contacts', function ($q) use ($filter) {
|
||||
$q->where('first_name', 'like', '%'.$filter.'%')
|
||||
->orWhere('last_name', 'like', '%'.$filter.'%')
|
||||
->orWhere('email', 'like', '%'.$filter.'%');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -44,7 +44,12 @@ class PaymentFilters extends QueryFilters
|
||||
->orWhere('custom_value4', 'like', '%'.$filter.'%')
|
||||
->orWhereHas('client', function ($q) use ($filter) {
|
||||
$q->where('name', 'like', '%'.$filter.'%');
|
||||
});
|
||||
})
|
||||
->orWhereHas('client.contacts', function ($q) use ($filter) {
|
||||
$q->where('first_name', 'like', '%'.$filter.'%')
|
||||
->orWhere('last_name', 'like', '%'.$filter.'%')
|
||||
->orWhere('email', 'like', '%'.$filter.'%');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -40,7 +40,12 @@ class QuoteFilters extends QueryFilters
|
||||
->orWhere('custom_value4', 'like', '%'.$filter.'%')
|
||||
->orWhereHas('client', function ($q) use ($filter) {
|
||||
$q->where('name', 'like', '%'.$filter.'%');
|
||||
});
|
||||
})
|
||||
->orWhereHas('client.contacts', function ($q) use ($filter) {
|
||||
$q->where('first_name', 'like', '%'.$filter.'%')
|
||||
->orWhere('last_name', 'like', '%'.$filter.'%')
|
||||
->orWhere('email', 'like', '%'.$filter.'%');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,12 @@ class RecurringInvoiceFilters extends QueryFilters
|
||||
->orWhere('custom_value4', 'like', '%'.$filter.'%')
|
||||
->orWhereHas('client', function ($q) use ($filter) {
|
||||
$q->where('name', 'like', '%'.$filter.'%');
|
||||
});
|
||||
})
|
||||
->orWhereHas('client.contacts', function ($q) use ($filter) {
|
||||
$q->where('first_name', 'like', '%'.$filter.'%')
|
||||
->orWhere('last_name', 'like', '%'.$filter.'%')
|
||||
->orWhere('email', 'like', '%'.$filter.'%');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,12 @@ class TaskFilters extends QueryFilters
|
||||
})
|
||||
->orWhereHas('client', function ($q) use ($filter) {
|
||||
$q->where('name', 'like', '%'.$filter.'%');
|
||||
});
|
||||
})
|
||||
->orWhereHas('client.contacts', function ($q) use ($filter) {
|
||||
$q->where('first_name', 'like', '%'.$filter.'%')
|
||||
->orWhere('last_name', 'like', '%'.$filter.'%')
|
||||
->orWhere('email', 'like', '%'.$filter.'%');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -127,8 +127,7 @@ class UserFilters extends QueryFilters
|
||||
$user_array = $this->transformKeys(explode(',', $user_id));
|
||||
|
||||
return $this->builder->where(function ($query) use ($user_array) {
|
||||
$query->whereNotIn('id', $user_array)
|
||||
->where('account_id', auth()->user()->account_id);
|
||||
$query->whereNotIn('id', $user_array);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -252,11 +252,20 @@ class InvoiceController extends Controller
|
||||
// create new archive
|
||||
$zipFile = new \PhpZip\ZipFile();
|
||||
try {
|
||||
|
||||
foreach ($invoices as $invoice) {
|
||||
//add it to the zip
|
||||
$zipFile->addFromString(basename($invoice->pdf_file_path()), file_get_contents($invoice->pdf_file_path(null, 'url', true)));
|
||||
|
||||
if ($invoice->client->getSetting('enable_e_invoice')) {
|
||||
$xml = $invoice->service()->getEInvoice();
|
||||
$zipFile->addFromString($invoice->getFileName("xml"), $xml);
|
||||
}
|
||||
|
||||
$file = $invoice->service()->getRawInvoicePdf();
|
||||
$zip_file_name = $invoice->getFileName();
|
||||
$zipFile->addFromString($zip_file_name, $file);
|
||||
}
|
||||
|
||||
|
||||
$filename = date('Y-m-d').'_'.str_replace(' ', '_', trans('texts.invoices')).'.zip';
|
||||
$filepath = sys_get_temp_dir().'/'.$filename;
|
||||
|
||||
|
@ -162,7 +162,9 @@ class InvoiceController extends BaseController
|
||||
*/
|
||||
public function create(CreateInvoiceRequest $request)
|
||||
{
|
||||
$invoice = InvoiceFactory::create(auth()->user()->company()->id, auth()->user()->id);
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
$invoice = InvoiceFactory::create($user->company()->id, $user->id);
|
||||
|
||||
return $this->itemResponse($invoice);
|
||||
}
|
||||
@ -211,7 +213,11 @@ class InvoiceController extends BaseController
|
||||
*/
|
||||
public function store(StoreInvoiceRequest $request)
|
||||
{
|
||||
$invoice = $this->invoice_repo->save($request->all(), InvoiceFactory::create(auth()->user()->company()->id, auth()->user()->id));
|
||||
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
$invoice = $this->invoice_repo->save($request->all(), InvoiceFactory::create($user->company()->id, $user->id));
|
||||
|
||||
$invoice = $invoice->service()
|
||||
->fillDefaults()
|
||||
@ -219,7 +225,7 @@ class InvoiceController extends BaseController
|
||||
->adjustInventory()
|
||||
->save();
|
||||
|
||||
event(new InvoiceWasCreated($invoice, $invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||
event(new InvoiceWasCreated($invoice, $invoice->company, Ninja::eventVars($user ? $user->id : null)));
|
||||
|
||||
$transaction = [
|
||||
'invoice' => $invoice->transaction_event(),
|
||||
@ -473,62 +479,17 @@ class InvoiceController extends BaseController
|
||||
return $this->itemResponse($invoice->fresh());
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform bulk actions on the list view.
|
||||
*
|
||||
* @return \Illuminate\Support\Collection
|
||||
*
|
||||
* @OA\Post(
|
||||
* path="/api/v1/invoices/bulk",
|
||||
* operationId="bulkInvoices",
|
||||
* tags={"invoices"},
|
||||
* summary="Performs bulk actions on an array of invoices",
|
||||
* description="",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/index"),
|
||||
* @OA\RequestBody(
|
||||
* description="User credentials",
|
||||
* required=true,
|
||||
* @OA\MediaType(
|
||||
* mediaType="application/json",
|
||||
* @OA\Schema(
|
||||
* type="array",
|
||||
* @OA\Items(
|
||||
* type="integer",
|
||||
* description="Array of hashed IDs to be bulk 'actioned",
|
||||
* example="[0,1,2,3]",
|
||||
* ),
|
||||
* )
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="The Bulk Action 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"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function bulk(BulkInvoiceRequest $request)
|
||||
{
|
||||
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
$action = $request->input('action');
|
||||
|
||||
$ids = $request->input('ids');
|
||||
|
||||
if (Ninja::isHosted() && (stripos($action, 'email') !== false) && !auth()->user()->company()->account->account_sms_verified) {
|
||||
if (Ninja::isHosted() && (stripos($action, 'email') !== false) && !$user->company()->account->account_sms_verified) {
|
||||
return response(['message' => 'Please verify your account to send emails.'], 400);
|
||||
}
|
||||
|
||||
@ -543,8 +504,8 @@ class InvoiceController extends BaseController
|
||||
*/
|
||||
|
||||
if ($action == 'bulk_download' && $invoices->count() > 1) {
|
||||
$invoices->each(function ($invoice) {
|
||||
if (auth()->user()->cannot('view', $invoice)) {
|
||||
$invoices->each(function ($invoice) use($user) {
|
||||
if ($user->cannot('view', $invoice)) {
|
||||
nlog('access denied');
|
||||
|
||||
return response()->json(['message' => ctrans('text.access_denied')]);
|
||||
@ -556,7 +517,7 @@ class InvoiceController extends BaseController
|
||||
return response()->json(['message' => ctrans('texts.sent_message')], 200);
|
||||
}
|
||||
|
||||
if ($action == 'download' && $invoices->count() >=1 && auth()->user()->can('view', $invoices->first())) {
|
||||
if ($action == 'download' && $invoices->count() >=1 && $user->can('view', $invoices->first())) {
|
||||
$file = $invoices->first()->service()->getInvoicePdf();
|
||||
|
||||
return response()->streamDownload(function () use ($file) {
|
||||
@ -564,7 +525,7 @@ class InvoiceController extends BaseController
|
||||
}, basename($file), ['Content-Type' => 'application/pdf']);
|
||||
}
|
||||
|
||||
if ($action == 'bulk_print' && auth()->user()->can('view', $invoices->first())) {
|
||||
if ($action == 'bulk_print' && $user->can('view', $invoices->first())) {
|
||||
$paths = $invoices->map(function ($invoice) {
|
||||
return $invoice->service()->getInvoicePdf();
|
||||
});
|
||||
@ -579,15 +540,15 @@ class InvoiceController extends BaseController
|
||||
/*
|
||||
* Send the other actions to the switch
|
||||
*/
|
||||
$invoices->each(function ($invoice, $key) use ($action) {
|
||||
if (auth()->user()->can('edit', $invoice)) {
|
||||
$invoices->each(function ($invoice, $key) use ($action, $user) {
|
||||
if ($user->can('edit', $invoice)) {
|
||||
$this->performAction($invoice, $action, true);
|
||||
}
|
||||
});
|
||||
|
||||
/* Need to understand which permission are required for the given bulk action ie. view / edit */
|
||||
|
||||
return $this->listResponse(Invoice::withTrashed()->whereIn('id', $this->transformKeys($ids))->company());
|
||||
return $this->listResponse(Invoice::query()->withTrashed()->whereIn('id', $this->transformKeys($ids))->company());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -889,6 +850,7 @@ class InvoiceController extends BaseController
|
||||
$invoice = $invitation->invoice;
|
||||
|
||||
$file = $invoice->service()->getEInvoice($contact);
|
||||
$file_name = $invoice->getFileName("xml");
|
||||
|
||||
$headers = ['Content-Type' => 'application/xml'];
|
||||
|
||||
@ -897,8 +859,8 @@ class InvoiceController extends BaseController
|
||||
}
|
||||
|
||||
return response()->streamDownload(function () use ($file) {
|
||||
echo Storage::get($file);
|
||||
}, basename($file), $headers);
|
||||
echo $file;
|
||||
}, $file_name, $headers);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1005,16 +967,17 @@ class InvoiceController extends BaseController
|
||||
*/
|
||||
public function upload(UploadInvoiceRequest $request, Invoice $invoice)
|
||||
{
|
||||
|
||||
if (! $this->checkFeature(Account::FEATURE_DOCUMENTS)) {
|
||||
return $this->featureFailure();
|
||||
}
|
||||
|
||||
if ($request->has('documents')) {
|
||||
$this->saveDocuments($request->file('documents'), $invoice);
|
||||
$this->saveDocuments($request->file('documents'), $invoice, $request->input('is_public', true));
|
||||
}
|
||||
|
||||
if ($request->has('file')) {
|
||||
$this->saveDocuments($request->file('documents'), $invoice);
|
||||
$this->saveDocuments($request->file('documents'), $invoice, $request->input('is_public', true));
|
||||
}
|
||||
|
||||
return $this->itemResponse($invoice->fresh());
|
||||
@ -1022,7 +985,10 @@ class InvoiceController extends BaseController
|
||||
|
||||
public function update_reminders(UpdateReminderRequest $request)
|
||||
{
|
||||
UpdateReminders::dispatch(auth()->user()->company());
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
UpdateReminders::dispatch($user->company());
|
||||
|
||||
return response()->json(['message' => 'Updating reminders'], 200);
|
||||
}
|
||||
|
@ -62,11 +62,11 @@ class BillingPortalPurchase extends Component
|
||||
public $password;
|
||||
|
||||
/**
|
||||
* Instance of subscription.
|
||||
* This arrives as an int and we resolve in the mount method
|
||||
*
|
||||
* @var \App\Models\Subscription $subscription
|
||||
* @var int|Subscription
|
||||
*/
|
||||
public Subscription $subscription;
|
||||
public $subscription;
|
||||
|
||||
/**
|
||||
* Instance of client contact.
|
||||
|
@ -21,6 +21,7 @@ use App\Models\QuoteInvitation;
|
||||
use App\Utils\VendorHtmlEngine;
|
||||
use App\Models\CreditInvitation;
|
||||
use App\Models\InvoiceInvitation;
|
||||
use App\Jobs\Invoice\CreateEInvoice;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use App\Models\PurchaseOrderInvitation;
|
||||
use App\Models\RecurringInvoiceInvitation;
|
||||
@ -38,7 +39,7 @@ class PdfSlot extends Component
|
||||
public $pdf;
|
||||
|
||||
public $url;
|
||||
|
||||
|
||||
private $settings;
|
||||
|
||||
private $html_variables;
|
||||
@ -61,7 +62,7 @@ class PdfSlot extends Component
|
||||
}
|
||||
|
||||
public function getPdf()
|
||||
{
|
||||
{
|
||||
// $this->pdf = $this->entity->fullscreenPdfViewer($this->invitation);
|
||||
|
||||
$blob = [
|
||||
@ -74,14 +75,14 @@ class PdfSlot extends Component
|
||||
$hash = Str::random(64);
|
||||
|
||||
Cache::put($hash, $blob, now()->addMinutes(2));
|
||||
|
||||
|
||||
$this->pdf = $hash;
|
||||
|
||||
}
|
||||
|
||||
public function downloadPdf()
|
||||
{
|
||||
|
||||
|
||||
$file_name = $this->entity->numberFormatter().'.pdf';
|
||||
|
||||
if($this->entity instanceof \App\Models\PurchaseOrder)
|
||||
@ -95,17 +96,32 @@ class PdfSlot extends Component
|
||||
echo $file;
|
||||
}, $file_name, $headers);
|
||||
|
||||
}
|
||||
public function downloadEInvoice()
|
||||
{
|
||||
|
||||
$file_name = $this->entity->numberFormatter().'.xml';
|
||||
|
||||
$file = (new CreateEInvoice($this->entity))->handle();
|
||||
|
||||
$headers = ['Content-Type' => 'application/xml'];
|
||||
|
||||
return response()->streamDownload(function () use ($file) {
|
||||
echo $file;
|
||||
}, $file_name, $headers);
|
||||
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
|
||||
|
||||
$this->entity_type = $this->resolveEntityType();
|
||||
|
||||
$this->settings = $this->entity->client ? $this->entity->client->getMergedSettings() : $this->entity->company->settings;
|
||||
|
||||
$this->show_cost = in_array('$product.unit_cost', $this->settings->pdf_variables->product_columns);
|
||||
$this->show_line_total = in_array('$product.line_total', $this->settings->pdf_variables->product_columns);
|
||||
$this->show_quantity = in_array('$product.quantity', $this->settings->pdf_variables->product_columns);
|
||||
|
||||
if($this->entity_type == 'quote' && !$this->settings->sync_invoice_quote_columns ){
|
||||
$this->show_cost = in_array('$product.unit_cost', $this->settings->pdf_variables->product_quote_columns);
|
||||
@ -146,7 +162,7 @@ class PdfSlot extends Component
|
||||
|
||||
private function getCompanyAddress()
|
||||
{
|
||||
|
||||
|
||||
$company_address = "";
|
||||
|
||||
foreach($this->settings->pdf_variables->company_address as $variable) {
|
||||
@ -166,7 +182,7 @@ class PdfSlot extends Component
|
||||
}
|
||||
|
||||
return $this->convertVariables($company_details);
|
||||
|
||||
|
||||
}
|
||||
|
||||
private function getEntityDetails()
|
||||
@ -174,9 +190,9 @@ class PdfSlot extends Component
|
||||
$entity_details = "";
|
||||
|
||||
if($this->entity_type == 'invoice' || $this->entity_type == 'recurring_invoice') {
|
||||
foreach($this->settings->pdf_variables->invoice_details as $variable)
|
||||
foreach($this->settings->pdf_variables->invoice_details as $variable)
|
||||
$entity_details .= "<div class='flex px-5 block'><p class= w-36 block'>{$variable}_label</p><p class='pl-5 w-36 block entity-field'>{$variable}</p></div>";
|
||||
|
||||
|
||||
}
|
||||
elseif($this->entity_type == 'quote'){
|
||||
foreach($this->settings->pdf_variables->quote_details as $variable)
|
||||
@ -190,7 +206,7 @@ class PdfSlot extends Component
|
||||
foreach($this->settings->pdf_variables->purchase_order_details as $variable)
|
||||
$entity_details .= "<div class='flex px-5 block'><p class= w-36 block'>{$variable}_label</p><p class='pl-5 w-36 block entity-field'>{$variable}</p></div>";
|
||||
}
|
||||
|
||||
|
||||
return $this->convertVariables($entity_details);
|
||||
|
||||
}
|
||||
@ -206,7 +222,7 @@ class PdfSlot extends Component
|
||||
|
||||
$name = $this->settings->pdf_variables->client_details[0];
|
||||
}
|
||||
|
||||
|
||||
return $this->convertVariables($name);
|
||||
|
||||
}
|
||||
@ -225,7 +241,7 @@ class PdfSlot extends Component
|
||||
$user_details .= "<p>{$variable}</p>";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $this->convertVariables($user_details);
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,9 @@ class UploadInvoiceRequest extends Request
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->user()->can('edit', $this->invoice);
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
return $user->can('edit', $this->invoice);
|
||||
}
|
||||
|
||||
public function rules()
|
||||
@ -42,6 +44,8 @@ class UploadInvoiceRequest extends Request
|
||||
$rules['file'] = $this->file_validation;
|
||||
}
|
||||
|
||||
$rules['is_public'] = 'sometimes|boolean';
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ class Request extends FormRequest
|
||||
use MakesHash;
|
||||
use RuntimeFormRequest;
|
||||
|
||||
protected $file_validation = 'sometimes|file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx,webp|max:20000';
|
||||
protected $file_validation = 'sometimes|file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx,webp,xml|max:20000';
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
|
@ -108,7 +108,7 @@ class StoreShopClientRequest extends Request
|
||||
$settings->currency_id = $this->getCurrencyCode($input['currency_code']);
|
||||
}
|
||||
|
||||
$input['settings'] = $settings;
|
||||
$input['settings'] = (array)$settings;
|
||||
|
||||
if (isset($input['contacts'])) {
|
||||
foreach ($input['contacts'] as $key => $contact) {
|
||||
|
@ -11,37 +11,38 @@
|
||||
|
||||
namespace App\Jobs\Entity;
|
||||
|
||||
use App\Exceptions\FilePermissionsFailure;
|
||||
use App\Jobs\Invoice\CreateEInvoice;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Utils\Ninja;
|
||||
use App\Models\Quote;
|
||||
use App\Models\Credit;
|
||||
use App\Models\CreditInvitation;
|
||||
use App\Models\Design;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\InvoiceInvitation;
|
||||
use App\Models\Quote;
|
||||
use App\Utils\HtmlEngine;
|
||||
use App\Libraries\MultiDB;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use App\Models\QuoteInvitation;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Models\CreditInvitation;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Utils\PhantomJS\Phantom;
|
||||
use App\Models\InvoiceInvitation;
|
||||
use App\Utils\HostedPDF\NinjaPdf;
|
||||
use App\Utils\Traits\Pdf\PdfMaker;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use App\Jobs\Invoice\CreateEInvoice;
|
||||
use App\Utils\Traits\NumberFormatter;
|
||||
use App\Utils\Traits\MakesInvoiceHtml;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use App\Utils\Traits\Pdf\PageNumbering;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use App\Exceptions\FilePermissionsFailure;
|
||||
use App\Models\RecurringInvoiceInvitation;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use horstoeko\zugferd\ZugferdDocumentPdfBuilder;
|
||||
use App\Services\PdfMaker\Design as PdfDesignModel;
|
||||
use App\Services\PdfMaker\Design as PdfMakerDesign;
|
||||
use App\Services\PdfMaker\PdfMaker as PdfMakerService;
|
||||
use App\Utils\HostedPDF\NinjaPdf;
|
||||
use App\Utils\HtmlEngine;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\PhantomJS\Phantom;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Utils\Traits\MakesInvoiceHtml;
|
||||
use App\Utils\Traits\NumberFormatter;
|
||||
use App\Utils\Traits\Pdf\PageNumbering;
|
||||
use App\Utils\Traits\Pdf\PdfMaker;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class CreateEntityPdf implements ShouldQueue
|
||||
{
|
||||
@ -116,7 +117,8 @@ class CreateEntityPdf implements ShouldQueue
|
||||
}
|
||||
|
||||
$entity_design_id = '';
|
||||
|
||||
$path = '';
|
||||
|
||||
if ($this->entity instanceof Invoice) {
|
||||
$path = $this->client->invoice_filepath($this->invitation);
|
||||
$entity_design_id = 'invoice_design_id';
|
||||
@ -206,6 +208,11 @@ class CreateEntityPdf implements ShouldQueue
|
||||
info($maker->getCompiledHTML());
|
||||
}
|
||||
|
||||
if($this->entity_string == "invoice" && $this->client->getSetting('enable_e_invoice'))
|
||||
{
|
||||
$pdf = $this->checkEInvoice($pdf);
|
||||
}
|
||||
|
||||
if ($pdf) {
|
||||
try {
|
||||
Storage::disk($this->disk)->put($file_path, $pdf);
|
||||
@ -213,9 +220,7 @@ class CreateEntityPdf implements ShouldQueue
|
||||
throw new FilePermissionsFailure($e->getMessage());
|
||||
}
|
||||
}
|
||||
if ($this->entity_string == "invoice" && $this->client->getSetting('enable_e_invoice')){
|
||||
(new CreateEInvoice($this->entity, true))->handle();
|
||||
}
|
||||
|
||||
$this->invitation = null;
|
||||
// $this->entity = null;
|
||||
$this->company = null;
|
||||
@ -224,10 +229,65 @@ class CreateEntityPdf implements ShouldQueue
|
||||
$maker = null;
|
||||
$state = null;
|
||||
|
||||
|
||||
return $file_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch to determine if we need to embed the xml into the PDF itself
|
||||
*
|
||||
* @param string $pdf
|
||||
* @return string
|
||||
*/
|
||||
private function checkEInvoice(string $pdf): string
|
||||
{
|
||||
if(!$this->entity instanceof Invoice)
|
||||
return $pdf;
|
||||
|
||||
$e_invoice_type = $this->entity->client->getSetting('e_invoice_type');
|
||||
|
||||
switch ($e_invoice_type) {
|
||||
case "EN16931":
|
||||
case "XInvoice_2_2":
|
||||
case "XInvoice_2_1":
|
||||
case "XInvoice_2_0":
|
||||
case "XInvoice_1_0":
|
||||
case "XInvoice-Extended":
|
||||
case "XInvoice-BasicWL":
|
||||
case "XInvoice-Basic":
|
||||
return $this->embedEInvoiceZuGFerD($pdf) ?? $pdf;
|
||||
//case "Facturae_3.2":
|
||||
//case "Facturae_3.2.1":
|
||||
//case "Facturae_3.2.2":
|
||||
//
|
||||
default:
|
||||
return $pdf;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Embed the .xml file into the PDF
|
||||
*
|
||||
* @param string $pdf
|
||||
* @return string
|
||||
*/
|
||||
private function embedEInvoiceZuGFerD(string $pdf): string
|
||||
{
|
||||
try {
|
||||
|
||||
$e_rechnung = (new CreateEInvoice($this->entity, true))->handle();
|
||||
$pdfBuilder = new ZugferdDocumentPdfBuilder($e_rechnung, $pdf);
|
||||
$pdfBuilder->generateDocument();
|
||||
return $pdfBuilder->downloadString(basename($this->entity->getFileName()));
|
||||
|
||||
} catch (\Exception $e) {
|
||||
nlog("E_Invoice Merge failed - " . $e->getMessage());
|
||||
}
|
||||
|
||||
return $pdf;
|
||||
}
|
||||
|
||||
|
||||
public function failed($e)
|
||||
{
|
||||
}
|
||||
|
@ -11,41 +11,43 @@
|
||||
|
||||
namespace App\Jobs\Entity;
|
||||
|
||||
use App\Exceptions\FilePermissionsFailure;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Utils\Ninja;
|
||||
use App\Models\Quote;
|
||||
use App\Models\Credit;
|
||||
use App\Models\CreditInvitation;
|
||||
use App\Models\Design;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\InvoiceInvitation;
|
||||
use App\Models\Quote;
|
||||
use App\Utils\HtmlEngine;
|
||||
use App\Libraries\MultiDB;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use App\Models\QuoteInvitation;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Models\CreditInvitation;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Utils\PhantomJS\Phantom;
|
||||
use App\Models\InvoiceInvitation;
|
||||
use App\Utils\HostedPDF\NinjaPdf;
|
||||
use App\Utils\Traits\Pdf\PdfMaker;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use App\Jobs\Invoice\CreateEInvoice;
|
||||
use App\Utils\Traits\NumberFormatter;
|
||||
use App\Utils\Traits\MakesInvoiceHtml;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use App\Utils\Traits\Pdf\PageNumbering;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use App\Exceptions\FilePermissionsFailure;
|
||||
use App\Models\RecurringInvoiceInvitation;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use horstoeko\zugferd\ZugferdDocumentPdfBuilder;
|
||||
use App\Services\PdfMaker\Design as PdfDesignModel;
|
||||
use App\Services\PdfMaker\Design as PdfMakerDesign;
|
||||
use App\Services\PdfMaker\PdfMaker as PdfMakerService;
|
||||
use App\Utils\HostedPDF\NinjaPdf;
|
||||
use App\Utils\HtmlEngine;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\PhantomJS\Phantom;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Utils\Traits\MakesInvoiceHtml;
|
||||
use App\Utils\Traits\NumberFormatter;
|
||||
use App\Utils\Traits\Pdf\PageNumbering;
|
||||
use App\Utils\Traits\Pdf\PdfMaker;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
class CreateRawPdf implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, NumberFormatter, MakesInvoiceHtml, PdfMaker, MakesHash, PageNumbering;
|
||||
|
||||
public $entity;
|
||||
public Invoice | Credit | Quote | RecurringInvoice $entity;
|
||||
|
||||
public $company;
|
||||
|
||||
@ -103,6 +105,7 @@ class CreateRawPdf implements ShouldQueue
|
||||
}
|
||||
|
||||
$entity_design_id = '';
|
||||
$path = '';
|
||||
|
||||
if ($this->entity instanceof Invoice) {
|
||||
$path = $this->entity->client->invoice_filepath($this->invitation);
|
||||
@ -202,11 +205,67 @@ class CreateRawPdf implements ShouldQueue
|
||||
if ($pdf) {
|
||||
$maker =null;
|
||||
$state = null;
|
||||
return $pdf;
|
||||
|
||||
return $this->checkEInvoice($pdf);
|
||||
}
|
||||
|
||||
throw new FilePermissionsFailure('Unable to generate the raw PDF');
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch to determine if we need to embed the xml into the PDF itself
|
||||
*
|
||||
* @param string $pdf
|
||||
* @return string
|
||||
*/
|
||||
private function checkEInvoice(string $pdf): string
|
||||
{
|
||||
if(!$this->entity instanceof Invoice)
|
||||
return $pdf;
|
||||
|
||||
$e_invoice_type = $this->entity->client->getSetting('e_invoice_type');
|
||||
|
||||
switch ($e_invoice_type) {
|
||||
case "EN16931":
|
||||
case "XInvoice_2_2":
|
||||
case "XInvoice_2_1":
|
||||
case "XInvoice_2_0":
|
||||
case "XInvoice_1_0":
|
||||
case "XInvoice-Extended":
|
||||
case "XInvoice-BasicWL":
|
||||
case "XInvoice-Basic":
|
||||
return $this->embedEInvoiceZuGFerD($pdf) ?? $pdf;
|
||||
//case "Facturae_3.2":
|
||||
//case "Facturae_3.2.1":
|
||||
//case "Facturae_3.2.2":
|
||||
//
|
||||
default:
|
||||
return $pdf;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Embed the .xml file into the PDF
|
||||
*
|
||||
* @param string $pdf
|
||||
* @return string
|
||||
*/
|
||||
private function embedEInvoiceZuGFerD(string $pdf): string
|
||||
{
|
||||
try {
|
||||
|
||||
$e_rechnung = (new CreateEInvoice($this->entity, true))->handle();
|
||||
$pdfBuilder = new ZugferdDocumentPdfBuilder($e_rechnung, $pdf);
|
||||
$pdfBuilder->generateDocument();
|
||||
return $pdfBuilder->downloadString(basename($this->entity->getFileName()));
|
||||
|
||||
} catch (\Exception $e) {
|
||||
nlog("E_Invoice Merge failed - " . $e->getMessage());
|
||||
}
|
||||
|
||||
return $pdf;
|
||||
}
|
||||
|
||||
public function failed($e)
|
||||
{
|
||||
|
@ -14,6 +14,7 @@ namespace App\Jobs\Invoice;
|
||||
|
||||
use App\Utils\Ninja;
|
||||
use App\Models\Invoice;
|
||||
use horstoeko\zugferd\ZugferdDocumentBuilder;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
@ -29,17 +30,16 @@ class CreateEInvoice implements ShouldQueue
|
||||
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
public function __construct(private Invoice $invoice, private bool $alterPDF, private string $custom_pdf_path = "")
|
||||
public function __construct(private Invoice $invoice, private bool $returnObject = false)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
*
|
||||
* @return string
|
||||
* @return string|ZugferdDocumentBuilder
|
||||
*/
|
||||
public function handle(): string
|
||||
public function handle(): string|ZugferdDocumentBuilder
|
||||
{
|
||||
/* Forget the singleton*/
|
||||
App::forgetInstance('translator');
|
||||
@ -63,13 +63,18 @@ class CreateEInvoice implements ShouldQueue
|
||||
case "XInvoice-Extended":
|
||||
case "XInvoice-BasicWL":
|
||||
case "XInvoice-Basic":
|
||||
return (new ZugferdEInvoice($this->invoice))->run();
|
||||
$zugferd = (new ZugferdEInvoice($this->invoice))->run();
|
||||
|
||||
return $this->returnObject ? $zugferd->xrechnung : $zugferd->getXml();
|
||||
case "Facturae_3.2":
|
||||
case "Facturae_3.2.1":
|
||||
case "Facturae_3.2.2":
|
||||
return (new FacturaEInvoice($this->invoice, str_replace("Facturae_", "", $e_invoice_type)))->run();
|
||||
default:
|
||||
return (new ZugferdEInvoice($this->invoice))->run();
|
||||
|
||||
$zugferd = (new ZugferdEInvoice($this->invoice))->run();
|
||||
|
||||
return $this->returnObject ? $zugferd : $zugferd->getXml();
|
||||
|
||||
}
|
||||
|
||||
|
@ -73,27 +73,19 @@ class ZipInvoices implements ShouldQueue
|
||||
$invitation = $this->invoices->first()->invitations->first();
|
||||
$path = $this->invoices->first()->client->invoice_filepath($invitation);
|
||||
|
||||
$this->invoices->each(function ($invoice) {
|
||||
(new CreateEntityPdf($invoice->invitations()->first()))->handle();
|
||||
if ($invoice->client->getSetting('enable_e_invoice')){
|
||||
(new CreateEInvoice($invoice, false))->handle();
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
|
||||
|
||||
foreach ($this->invoices as $invoice) {
|
||||
$file = $invoice->service()->getInvoicePdf();
|
||||
$zip_file_name = basename($file);
|
||||
$zipFile->addFromString($zip_file_name, Storage::get($file));
|
||||
|
||||
if($invoice->client->getSetting('enable_e_invoice')){
|
||||
|
||||
$xinvoice = $invoice->service()->getEInvoice();
|
||||
$xinvoice_zip_file_name = basename($xinvoice);
|
||||
$zipFile->addFromString($xinvoice_zip_file_name, Storage::get($xinvoice));
|
||||
|
||||
|
||||
if ($invoice->client->getSetting('enable_e_invoice')) {
|
||||
$xml = $invoice->service()->getEInvoice();
|
||||
$zipFile->addFromString($invoice->getFileName("xml"), $xml);
|
||||
}
|
||||
|
||||
$file = $invoice->service()->getRawInvoicePdf();
|
||||
$zip_file_name = $invoice->getFileName();
|
||||
$zipFile->addFromString($zip_file_name, $file);
|
||||
}
|
||||
|
||||
Storage::put($path.$file_name, $zipFile->outputAsString());
|
||||
|
@ -36,10 +36,10 @@ class UserEmailChanged implements ShouldQueue
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @param \App\Models\User $new_user
|
||||
* @param \App\Models\User $old_user
|
||||
* @param \stdClass $old_user
|
||||
* @param \App\Models\Company $company
|
||||
*/
|
||||
public function __construct(protected User $new_user, protected User $old_user, protected Company $company)
|
||||
public function __construct(protected User $new_user, protected \stdClass $old_user, protected Company $company)
|
||||
{
|
||||
$this->settings = $this->company->settings;
|
||||
}
|
||||
|
@ -1821,7 +1821,7 @@ class Import implements ShouldQueue
|
||||
|
||||
private function processActivities(array $data): void
|
||||
{
|
||||
Activity::where('company_id', $this->company->id)->cursor()->each(function ($a){
|
||||
Activity::query()->where('company_id', $this->company->id)->cursor()->each(function ($a){
|
||||
$a->forceDelete();
|
||||
nlog("deleting {$a->id}");
|
||||
});
|
||||
|
@ -53,7 +53,7 @@ class UploadFile implements ShouldQueue
|
||||
|
||||
public $disk;
|
||||
|
||||
public function __construct($file, $type, $user, $company, $entity, $disk = null, $is_public = false)
|
||||
public function __construct($file, $type, $user, $company, $entity, $disk = null, $is_public = true)
|
||||
{
|
||||
$this->file = $file;
|
||||
$this->type = $type;
|
||||
|
57
app/Listeners/Account/StripeConnectFailureListener.php
Normal file
57
app/Listeners/Account/StripeConnectFailureListener.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Listeners\Account;
|
||||
|
||||
use App\Utils\Ninja;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Jobs\Mail\NinjaMailerJob;
|
||||
use App\Jobs\Mail\NinjaMailerObject;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use App\Mail\Ninja\StripeConnectFailed;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
|
||||
class StripeConnectFailureListener implements ShouldQueue
|
||||
{
|
||||
/**
|
||||
* Create the event listener.
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param object $event
|
||||
* @return void
|
||||
*/
|
||||
public function handle($event)
|
||||
{
|
||||
MultiDB::setDb($event->db);
|
||||
|
||||
if (Ninja::isHosted() && is_null(Cache::get("stripe_connect_notification:{$event->company->company_key}")))
|
||||
{
|
||||
|
||||
$nmo = new NinjaMailerObject();
|
||||
$nmo->mailable = new StripeConnectFailed($event->company->owner(), $event->company);
|
||||
$nmo->company = $event->company;
|
||||
$nmo->settings = $event->company->settings;
|
||||
$nmo->to_user = $event->company->owner();
|
||||
|
||||
NinjaMailerJob::dispatch($nmo, true);
|
||||
|
||||
Cache::put("stripe_connect_notification:{$event->company->company_key}", true, 60 * 60 * 24);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -21,6 +21,7 @@ class PaymentCreatedActivity implements ShouldQueue
|
||||
{
|
||||
protected $activity_repo;
|
||||
|
||||
public $delay = 5;
|
||||
/**
|
||||
* Create the event listener.
|
||||
*
|
||||
|
@ -21,6 +21,8 @@ class InvoicePaidActivity implements ShouldQueue
|
||||
{
|
||||
protected $activity_repo;
|
||||
|
||||
public $delay = 10;
|
||||
|
||||
/**
|
||||
* Create the event listener.
|
||||
*
|
||||
|
@ -11,14 +11,16 @@
|
||||
|
||||
namespace App\Listeners\Payment;
|
||||
|
||||
use App\Utils\Ninja;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Jobs\Mail\NinjaMailer;
|
||||
use App\Jobs\Mail\NinjaMailerJob;
|
||||
use App\Jobs\Mail\NinjaMailerObject;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Mail\Admin\EntityPaidObject;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\Notifications\UserNotifies;
|
||||
use Turbo124\Beacon\Facades\LightLogs;
|
||||
use App\DataMapper\Analytics\RevenueTrack;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use App\Utils\Traits\Notifications\UserNotifies;
|
||||
|
||||
class PaymentNotification implements ShouldQueue
|
||||
{
|
||||
@ -158,6 +160,15 @@ class PaymentNotification implements ShouldQueue
|
||||
|
||||
$url = $base."&t=item&in={$item}&ip={$amount}&iq=1";
|
||||
$this->sendAnalytics($url);
|
||||
|
||||
$email = $client->present()->email();
|
||||
$account_key = $client->custom_value2 ?? 'unknown';
|
||||
$product = $item;
|
||||
$gateway_reference = $client->gateway_tokens()->count() >= 1 ? ($client->gateway_tokens()->first()->gateway_customer_reference ?? '') : '';
|
||||
|
||||
// LightLogs::create(new RevenueTrack($email, $account_key, 1, $amount, $product, $gateway_reference, $entity_number))
|
||||
// ->batch();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
114
app/Mail/Ninja/StripeConnectFailed.php
Normal file
114
app/Mail/Ninja/StripeConnectFailed.php
Normal file
@ -0,0 +1,114 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Mail\Ninja;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\Company;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Headers;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
|
||||
class StripeConnectFailed extends Mailable
|
||||
{
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(public User $user, public Company $company)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message envelope.
|
||||
*
|
||||
* @return \Illuminate\Mail\Mailables\Envelope
|
||||
*/
|
||||
public function envelope()
|
||||
{
|
||||
return new Envelope(
|
||||
subject: "Stripe Connect not configured, please login and connect.",
|
||||
from: "maildelivery@invoicing.co",
|
||||
to: $this->user->email,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message content definition.
|
||||
*
|
||||
* @return \Illuminate\Mail\Mailables\Content
|
||||
*/
|
||||
public function content()
|
||||
{
|
||||
|
||||
return new Content(
|
||||
view: 'email.admin.stripe_connect_failed',
|
||||
text: 'email.admin.stripe_connect_failed_text',
|
||||
with: [
|
||||
'text_body' => $this->textBody(), //@todo this is a bit hacky here.
|
||||
'body' => $this->htmlBody(),
|
||||
'title' => 'Connect your Stripe account',
|
||||
'settings' => $this->company->settings,
|
||||
'logo' => $this->company->present()->logo(),
|
||||
'signature' => '',
|
||||
'company' => $this->company,
|
||||
'greeting' => '',
|
||||
'links' => [],
|
||||
'url' => 'https://www.loom.com/share/a3dc3131cc924e14a34634d5d48065c8?sid=b1971aa2-9deb-4339-8ebd-53f9947ef633',
|
||||
'button' => "texts.view"
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
private function textBody()
|
||||
{
|
||||
return "
|
||||
We note you are yet to connect your Stripe account to Invoice Ninja. Please log in to Invoice Ninja and connect your Stripe account.\n\n
|
||||
Once logged in you can use the following resource to connect your Stripe account: \n\n
|
||||
";
|
||||
}
|
||||
|
||||
private function htmlBody()
|
||||
{
|
||||
return "
|
||||
We note you are yet to connect your Stripe account to Invoice Ninja. Please log in to Invoice Ninja and connect your Stripe account.<br><br>
|
||||
|
||||
Once logged in you can use the following resource to connect your Stripe account: <br><br>
|
||||
|
||||
";
|
||||
}
|
||||
/**
|
||||
* Get the attachments for the message.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function attachments()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message headers.
|
||||
*
|
||||
* @return \Illuminate\Mail\Mailables\Headers
|
||||
*/
|
||||
public function headers()
|
||||
{
|
||||
return new Headers(
|
||||
messageId: null,
|
||||
references: [],
|
||||
text:['' => ''],
|
||||
);
|
||||
}
|
||||
}
|
@ -18,7 +18,6 @@ use App\Services\PdfMaker\Designs\Utilities\DesignHelpers;
|
||||
use App\Utils\HtmlEngine;
|
||||
use App\Utils\Ninja;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class TemplateEmail extends Mailable
|
||||
{
|
||||
@ -111,7 +110,7 @@ class TemplateEmail extends Mailable
|
||||
if (Ninja::isHosted()) {
|
||||
$bccs = explode(',', str_replace(' ', '', $settings->bcc_email));
|
||||
$this->bcc(array_slice($bccs, 0, 2));
|
||||
//$this->bcc(reset($bccs)); //remove whitespace if any has been inserted.
|
||||
//$this->bcc(reset($bccs)); //remove whitespace if any has been inserted.
|
||||
} else {
|
||||
$this->bcc(explode(',', str_replace(' ', '', $settings->bcc_email)));
|
||||
}//remove whitespace if any has been inserted.
|
||||
@ -149,16 +148,21 @@ class TemplateEmail extends Mailable
|
||||
if ($this->invitation && $this->invitation->invoice && $settings->ubl_email_attachment && $this->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) {
|
||||
$ubl_string = (new CreateUbl($this->invitation->invoice))->handle();
|
||||
|
||||
nlog("template {$ubl_string}");
|
||||
|
||||
if ($ubl_string) {
|
||||
$this->attachData($ubl_string, $this->invitation->invoice->getFileName('xml'));
|
||||
}
|
||||
|
||||
}
|
||||
if ($this->invitation && $this->invitation->invoice && $this->invitation->invoice->client->getSetting('enable_e_invoice') && $this->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) {
|
||||
|
||||
$xinvoice_filepath = $this->invitation->invoice->service()->getEInvoice($this->invitation->contact);
|
||||
$xml_string = $this->invitation->invoice->service()->getEInvoice($this->invitation->contact);
|
||||
|
||||
if(Storage::disk(config('filesystems.default'))->exists($xinvoice_filepath))
|
||||
$this->attach(Storage::disk(config('filesystems.default'))->path($xinvoice_filepath), ['as' => $this->invitation->invoice->getFileName("xml"), 'mime' => null]);
|
||||
nlog("template {$xml_string}");
|
||||
|
||||
if($xml_string) {
|
||||
$this->attachData($xml_string, $this->invitation->invoice->getEFileName("xml"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -237,6 +237,15 @@ class BaseModel extends Model
|
||||
return $this->numberFormatter().'.'.$extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $extension
|
||||
* @return string
|
||||
*/
|
||||
public function getEFileName($extension = 'pdf')
|
||||
{
|
||||
return ctrans("texts.e_invoice"). "_" . $this->numberFormatter().'.'.$extension;
|
||||
}
|
||||
|
||||
public function numberFormatter()
|
||||
{
|
||||
$number = strlen($this->number) >= 1 ? $this->translate_entity() . "_" . $this->number : class_basename($this) . "_" . Str::random(5);
|
||||
@ -276,6 +285,7 @@ class BaseModel extends Model
|
||||
|
||||
/**
|
||||
* Returns the base64 encoded PDF string of the entity
|
||||
* @deprecated - unused implementation
|
||||
*/
|
||||
public function fullscreenPdfViewer($invitation = null): string
|
||||
{
|
||||
|
@ -707,6 +707,7 @@ class Company extends BaseModel
|
||||
|
||||
public function getSetting($setting)
|
||||
{
|
||||
//todo $this->setting ?? false
|
||||
if (property_exists($this->settings, $setting) != false) {
|
||||
return $this->settings->{$setting};
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ use Illuminate\Database\Eloquent\Model;
|
||||
* @property string $hash
|
||||
* @property float $fee_total
|
||||
* @property int|null $fee_invoice_id
|
||||
* @property mixed $data
|
||||
* @property \stdClass $data
|
||||
* @property int|null $payment_id
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
@ -38,9 +38,16 @@ class PaymentHash extends Model
|
||||
'data' => 'object',
|
||||
];
|
||||
|
||||
/**
|
||||
* @class \App\Models\PaymentHash $this
|
||||
* @property \App\Models\PaymentHash $data
|
||||
* @class \stdClass $data
|
||||
* @property string $raw_value
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @return mixed
|
||||
*/
|
||||
public function invoices()
|
||||
{
|
||||
@ -75,9 +82,8 @@ class PaymentHash extends Model
|
||||
|
||||
public function withData(string $property, $value): self
|
||||
{
|
||||
$this->data = array_merge((array) $this->data, [$property => $value]);
|
||||
$this->save();
|
||||
|
||||
return $this;
|
||||
$this->data = array_merge((array) $this->data, [$property => $value]); // @phpstan-ignore-line
|
||||
$this->save();// @phpstan-ignore-line
|
||||
return $this; // @phpstan-ignore-line
|
||||
}
|
||||
}
|
||||
|
@ -230,9 +230,9 @@ class Vendor extends BaseModel
|
||||
* Returns a vendor settings proxying company setting
|
||||
*
|
||||
* @param string $setting
|
||||
* @return string
|
||||
* @return mixed
|
||||
*/
|
||||
public function getSetting($setting): string
|
||||
public function getSetting($setting): mixed
|
||||
{
|
||||
if ((property_exists($this->company->settings, $setting) != false) && (isset($this->company->settings->{$setting}) !== false)) {
|
||||
return $this->company->settings->{$setting};
|
||||
|
@ -69,7 +69,7 @@ class CheckoutWebhook implements ShouldQueue
|
||||
{
|
||||
$payment_object = $this->webhook_array['data'];
|
||||
|
||||
$payment = Payment::withTrashed()->where('transaction_reference', $payment_object['id'])->first();
|
||||
$payment = Payment::query()->withTrashed()->where('transaction_reference', $payment_object['id'])->first();
|
||||
|
||||
if($payment && $payment->status_id == Payment::STATUS_COMPLETED)
|
||||
return;
|
||||
@ -84,18 +84,19 @@ class CheckoutWebhook implements ShouldQueue
|
||||
|
||||
$metadata = $this->webhook_array['metadata'];
|
||||
|
||||
$payment_hash = PaymentHash::where('hash', $metadata['udf2'])->first();
|
||||
$payment_hash = PaymentHash::query()->where('hash', $metadata['udf2'])->first();
|
||||
|
||||
$driver = $this->company_gateway->driver($payment_hash->fee_invoice->client)->init()->setPaymentMethod();
|
||||
|
||||
$payment_hash->data = array_merge((array) $payment_hash->data, $this->webhook_array);
|
||||
$payment_hash->save();
|
||||
$driver->setPaymentHash($payment_hash);
|
||||
$payment_hash->data = array_merge((array) $payment_hash->data, $this->webhook_array); // @phpstan-ignore-line
|
||||
$payment_hash->save();
|
||||
$driver->setPaymentHash($payment_hash);
|
||||
|
||||
// @phpstan-ignore-line
|
||||
$data = [
|
||||
'payment_method' => isset($this->webhook_array['source']['id']) ? $this->webhook_array['source']['id'] : '',
|
||||
'payment_type' => PaymentType::CREDIT_CARD_OTHER,
|
||||
'amount' => $payment_hash->data->raw_value,
|
||||
'amount' => $payment_hash->data->raw_value, // @phpstan-ignore-line
|
||||
'transaction_reference' => $payment_object['id'],
|
||||
'gateway_type_id' => GatewayType::CREDIT_CARD,
|
||||
];
|
||||
|
@ -66,8 +66,6 @@ class Webhook
|
||||
|
||||
/**
|
||||
* Lists the workflows in Checkout
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getWorkFlows()
|
||||
{
|
||||
|
@ -94,6 +94,7 @@ use App\Events\Misc\InvitationWasViewed;
|
||||
use App\Events\Payment\PaymentWasVoided;
|
||||
use App\Events\Vendor\VendorWasArchived;
|
||||
use App\Events\Vendor\VendorWasRestored;
|
||||
use App\Events\Account\StripeConnectFailure;
|
||||
use App\Listeners\Mail\MailSentListener;
|
||||
use App\Observers\ClientContactObserver;
|
||||
use App\Observers\PurchaseOrderObserver;
|
||||
@ -198,6 +199,8 @@ use App\Listeners\Invoice\InvoiceRestoredActivity;
|
||||
use App\Listeners\Invoice\InvoiceReversedActivity;
|
||||
use App\Listeners\Payment\PaymentRestoredActivity;
|
||||
use App\Listeners\Quote\QuoteApprovedNotification;
|
||||
use SocialiteProviders\Apple\AppleExtendSocialite;
|
||||
use SocialiteProviders\Manager\SocialiteWasCalled;
|
||||
use App\Events\Subscription\SubscriptionWasCreated;
|
||||
use App\Events\Subscription\SubscriptionWasDeleted;
|
||||
use App\Events\Subscription\SubscriptionWasUpdated;
|
||||
@ -221,10 +224,12 @@ use App\Listeners\Invoice\InvoiceEmailFailedActivity;
|
||||
use App\Events\PurchaseOrder\PurchaseOrderWasAccepted;
|
||||
use App\Events\PurchaseOrder\PurchaseOrderWasArchived;
|
||||
use App\Events\PurchaseOrder\PurchaseOrderWasRestored;
|
||||
use App\Listeners\Payment\PaymentEmailFailureActivity;
|
||||
use App\Listeners\Vendor\UpdateVendorContactLastLogin;
|
||||
use App\Events\RecurringQuote\RecurringQuoteWasCreated;
|
||||
use App\Events\RecurringQuote\RecurringQuoteWasDeleted;
|
||||
use App\Events\RecurringQuote\RecurringQuoteWasUpdated;
|
||||
use App\Listeners\Account\StripeConnectFailureListener;
|
||||
use App\Listeners\Activity\CreatedSubscriptionActivity;
|
||||
use App\Listeners\Activity\SubscriptionDeletedActivity;
|
||||
use App\Listeners\Activity\SubscriptionUpdatedActivity;
|
||||
@ -234,6 +239,7 @@ use App\Events\RecurringQuote\RecurringQuoteWasRestored;
|
||||
use App\Listeners\Activity\SubscriptionArchivedActivity;
|
||||
use App\Listeners\Activity\SubscriptionRestoredActivity;
|
||||
use App\Listeners\Invoice\InvoiceFailedEmailNotification;
|
||||
use SocialiteProviders\Microsoft\MicrosoftExtendSocialite;
|
||||
use App\Events\RecurringExpense\RecurringExpenseWasCreated;
|
||||
use App\Events\RecurringExpense\RecurringExpenseWasDeleted;
|
||||
use App\Events\RecurringExpense\RecurringExpenseWasUpdated;
|
||||
@ -587,6 +593,9 @@ class EventServiceProvider extends ServiceProvider
|
||||
TaskWasRestored::class => [
|
||||
TaskRestoredActivity::class,
|
||||
],
|
||||
StripeConnectFailure::class => [
|
||||
StripeConnectFailureListener::class,
|
||||
],
|
||||
SubscriptionWasCreated::class => [
|
||||
CreatedSubscriptionActivity::class,
|
||||
],
|
||||
|
@ -136,7 +136,11 @@ class InvoiceMigrationRepository extends BaseRepository
|
||||
|
||||
$state['finished_amount'] = $model->amount;
|
||||
|
||||
$model = $model->service()->applyNumber()->setReminder()->save();
|
||||
$model = $model->service()->applyNumber()->save();
|
||||
|
||||
if ($class->name == Invoice::class) {
|
||||
$model->service()->setReminder()->save();
|
||||
}
|
||||
|
||||
if ($class->name == Invoice::class || $class->name == RecurringInvoice::class) {
|
||||
if (($state['finished_amount'] != $state['starting_amount']) && ($model->status_id != Invoice::STATUS_DRAFT)) {
|
||||
|
@ -322,10 +322,10 @@ class EmailDefaults
|
||||
}
|
||||
/** E-Invoice xml file */
|
||||
if ($this->email->email_object->settings->enable_e_invoice && $this->email->email_object->entity instanceof Invoice) {
|
||||
$xinvoice_path = $this->email->email_object->entity->service()->getEInvoice();
|
||||
$xml_string = $this->email->email_object->entity->service()->getEInvoice();
|
||||
|
||||
if(Storage::disk(config('filesystems.default'))->exists($xinvoice_path))
|
||||
$this->email->email_object->attachments = array_merge($this->email->email_object->attachments, [['file' => base64_encode(Storage::get($xinvoice_path)), 'name' => explode(".", $this->email->email_object->entity->getFileName('xml'))[0]."-e_invoice.xml"]]);
|
||||
if($xml_string)
|
||||
$this->email->email_object->attachments = array_merge($this->email->email_object->attachments, [['file' => base64_encode($xml_string), 'name' => explode(".", $this->email->email_object->entity->getFileName('xml'))[0]."-e_invoice.xml"]]);
|
||||
}
|
||||
|
||||
if (!$this->email->email_object->settings->document_email_attachment || !$this->email->company->account->hasFeature(Account::FEATURE_DOCUMENTS)) {
|
||||
|
@ -22,12 +22,13 @@ use horstoeko\zugferd\codelists\ZugferdDutyTaxFeeCategories;
|
||||
|
||||
class ZugferdEInvoice extends AbstractService
|
||||
{
|
||||
public ZugferdDocumentBuilder $xrechnung;
|
||||
|
||||
public function __construct(public Invoice $invoice, private array $tax_map = [])
|
||||
public function __construct(public Invoice $invoice, private readonly bool $returnObject = false, private array $tax_map = [])
|
||||
{
|
||||
}
|
||||
|
||||
public function run()
|
||||
public function run(): self
|
||||
{
|
||||
|
||||
$company = $this->invoice->company;
|
||||
@ -45,10 +46,9 @@ class ZugferdEInvoice extends AbstractService
|
||||
default => ZugferdProfiles::PROFILE_EN16931,
|
||||
};
|
||||
|
||||
$this->xrechnung = ZugferdDocumentBuilder::CreateNew($profile);
|
||||
|
||||
$xrechnung = ZugferdDocumentBuilder::CreateNew($profile);
|
||||
|
||||
$xrechnung
|
||||
$this->xrechnung
|
||||
->setDocumentSupplyChainEvent(date_create($this->invoice->date))
|
||||
->setDocumentSeller($company->getSetting('name'))
|
||||
->setDocumentSellerAddress($company->getSetting("address1"), $company->getSetting("address2"), "", $company->getSetting("postal_code"), $company->getSetting("city"), $company->country()->iso_3166_2, $company->getSetting("state"))
|
||||
@ -59,32 +59,32 @@ class ZugferdEInvoice extends AbstractService
|
||||
->addDocumentPaymentTerm(ctrans("texts.xinvoice_payable", ['payeddue' => date_create($this->invoice->date)->diff(date_create($this->invoice->due_date))->format("%d"), 'paydate' => $this->invoice->due_date]));
|
||||
|
||||
if (!empty($this->invoice->public_notes)) {
|
||||
$xrechnung->addDocumentNote($this->invoice->public_notes);
|
||||
$this->xrechnung->addDocumentNote($this->invoice->public_notes);
|
||||
}
|
||||
if (empty($this->invoice->number)){
|
||||
$xrechnung->setDocumentInformation("DRAFT", "380", date_create($this->invoice->date), $this->invoice->client->getCurrencyCode());
|
||||
$this->xrechnung->setDocumentInformation("DRAFT", "380", date_create($this->invoice->date), $this->invoice->client->getCurrencyCode());
|
||||
} else {
|
||||
$xrechnung->setDocumentInformation($this->invoice->number, "380", date_create($this->invoice->date), $this->invoice->client->getCurrencyCode());
|
||||
$this->xrechnung->setDocumentInformation($this->invoice->number, "380", date_create($this->invoice->date), $this->invoice->client->getCurrencyCode());
|
||||
}
|
||||
if (!empty($this->invoice->po_number)) {
|
||||
$xrechnung->setDocumentBuyerOrderReferencedDocument($this->invoice->po_number);
|
||||
$this->xrechnung->setDocumentBuyerOrderReferencedDocument($this->invoice->po_number);
|
||||
}
|
||||
|
||||
if (empty($client->routing_id)) {
|
||||
$xrechnung->setDocumentBuyerReference(ctrans("texts.xinvoice_no_buyers_reference"));
|
||||
$this->xrechnung->setDocumentBuyerReference(ctrans("texts.xinvoice_no_buyers_reference"));
|
||||
} else {
|
||||
$xrechnung->setDocumentBuyerReference($client->routing_id);
|
||||
$this->xrechnung->setDocumentBuyerReference($client->routing_id);
|
||||
}
|
||||
if (!empty($client->shipping_address1)){
|
||||
$xrechnung->setDocumentShipToAddress($client->shipping_address1, $client->shipping_address2, "", $client->shipping_postal_code, $client->shipping_city, $client->shipping_country->iso_3166_2, $client->shipping_state);
|
||||
$this->xrechnung->setDocumentShipToAddress($client->shipping_address1, $client->shipping_address2, "", $client->shipping_postal_code, $client->shipping_city, $client->shipping_country->iso_3166_2, $client->shipping_state);
|
||||
}
|
||||
|
||||
$xrechnung->addDocumentPaymentMean(68, ctrans("texts.xinvoice_online_payment"));
|
||||
$this->xrechnung->addDocumentPaymentMean(68, ctrans("texts.xinvoice_online_payment"));
|
||||
|
||||
if (str_contains($company->getSetting('vat_number'), "/")) {
|
||||
$xrechnung->addDocumentSellerTaxRegistration("FC", $company->getSetting('vat_number'));
|
||||
$this->xrechnung->addDocumentSellerTaxRegistration("FC", $company->getSetting('vat_number'));
|
||||
} else {
|
||||
$xrechnung->addDocumentSellerTaxRegistration("VA", $company->getSetting('vat_number'));
|
||||
$this->xrechnung->addDocumentSellerTaxRegistration("VA", $company->getSetting('vat_number'));
|
||||
}
|
||||
|
||||
$invoicing_data = $this->invoice->calc();
|
||||
@ -92,29 +92,29 @@ class ZugferdEInvoice extends AbstractService
|
||||
//Create line items and calculate taxes
|
||||
foreach ($this->invoice->line_items as $index => $item) {
|
||||
/** @var \App\DataMapper\InvoiceItem $item **/
|
||||
$xrechnung->addNewPosition($index)
|
||||
$this->xrechnung->addNewPosition($index)
|
||||
->setDocumentPositionGrossPrice($item->gross_line_total)
|
||||
->setDocumentPositionNetPrice($item->line_total);
|
||||
if (!empty($item->product_key)){
|
||||
if (!empty($item->notes)){
|
||||
$xrechnung->setDocumentPositionProductDetails($item->product_key, $item->notes);
|
||||
$this->xrechnung->setDocumentPositionProductDetails($item->product_key, $item->notes);
|
||||
}
|
||||
else {
|
||||
$xrechnung->setDocumentPositionProductDetails($item->product_key);
|
||||
$this->xrechnung->setDocumentPositionProductDetails($item->product_key);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!empty($item->notes)){
|
||||
$xrechnung->setDocumentPositionProductDetails($item->notes);
|
||||
$this->xrechnung->setDocumentPositionProductDetails($item->notes);
|
||||
}
|
||||
else {
|
||||
$xrechnung->setDocumentPositionProductDetails("no product name defined");
|
||||
$this->xrechnung->setDocumentPositionProductDetails("no product name defined");
|
||||
}
|
||||
}
|
||||
if (isset($item->task_id)) {
|
||||
$xrechnung->setDocumentPositionQuantity($item->quantity, "HUR");
|
||||
$this->xrechnung->setDocumentPositionQuantity($item->quantity, "HUR");
|
||||
} else {
|
||||
$xrechnung->setDocumentPositionQuantity($item->quantity, "H87");
|
||||
$this->xrechnung->setDocumentPositionQuantity($item->quantity, "H87");
|
||||
}
|
||||
$linenetamount = $item->line_total;
|
||||
if ($item->discount > 0) {
|
||||
@ -124,18 +124,18 @@ class ZugferdEInvoice extends AbstractService
|
||||
$linenetamount -= $linenetamount * ($item->discount / 100);
|
||||
}
|
||||
}
|
||||
$xrechnung->setDocumentPositionLineSummation($linenetamount);
|
||||
$this->xrechnung->setDocumentPositionLineSummation($linenetamount);
|
||||
// According to european law, each line item can only have one tax rate
|
||||
if (!(empty($item->tax_name1) && empty($item->tax_name2) && empty($item->tax_name3))) {
|
||||
$taxtype = $this->getTaxType($item->tax_id);
|
||||
if (!empty($item->tax_name1)) {
|
||||
$xrechnung->addDocumentPositionTax($taxtype, 'VAT', $item->tax_rate1);
|
||||
$this->xrechnung->addDocumentPositionTax($taxtype, 'VAT', $item->tax_rate1);
|
||||
$this->addtoTaxMap($taxtype, $linenetamount, $item->tax_rate1);
|
||||
} elseif (!empty($item->tax_name2)) {
|
||||
$xrechnung->addDocumentPositionTax($taxtype, 'VAT', $item->tax_rate2);
|
||||
$this->xrechnung->addDocumentPositionTax($taxtype, 'VAT', $item->tax_rate2);
|
||||
$this->addtoTaxMap($taxtype, $linenetamount, $item->tax_rate2);
|
||||
} elseif (!empty($item->tax_name3)) {
|
||||
$xrechnung->addDocumentPositionTax($taxtype, 'VAT', $item->tax_rate3);
|
||||
$this->xrechnung->addDocumentPositionTax($taxtype, 'VAT', $item->tax_rate3);
|
||||
$this->addtoTaxMap($taxtype, $linenetamount, $item->tax_rate3);
|
||||
} else {
|
||||
nlog("Can't add correct tax position");
|
||||
@ -143,40 +143,45 @@ class ZugferdEInvoice extends AbstractService
|
||||
} else {
|
||||
if (!empty($this->invoice->tax_name1)) {
|
||||
$taxtype = $this->getTaxType($this->invoice->tax_name1);
|
||||
$xrechnung->addDocumentPositionTax($taxtype, 'VAT', $this->invoice->tax_rate1);
|
||||
$this->xrechnung->addDocumentPositionTax($taxtype, 'VAT', $this->invoice->tax_rate1);
|
||||
$this->addtoTaxMap($taxtype, $linenetamount, $this->invoice->tax_rate1);
|
||||
} elseif (!empty($this->invoice->tax_name2)) {
|
||||
$taxtype = $this->getTaxType($this->invoice->tax_name2);
|
||||
$xrechnung->addDocumentPositionTax($taxtype, 'VAT', $this->invoice->tax_rate2);
|
||||
$this->xrechnung->addDocumentPositionTax($taxtype, 'VAT', $this->invoice->tax_rate2);
|
||||
$this->addtoTaxMap($taxtype, $linenetamount, $this->invoice->tax_rate2);
|
||||
} elseif (!empty($this->invoice->tax_name3)) {
|
||||
$taxtype = $this->getTaxType($this->invoice->tax_name3);
|
||||
$xrechnung->addDocumentPositionTax($taxtype, 'VAT', $this->invoice->tax_rate3);
|
||||
$this->xrechnung->addDocumentPositionTax($taxtype, 'VAT', $this->invoice->tax_rate3);
|
||||
$this->addtoTaxMap($taxtype, $linenetamount, $this->invoice->tax_rate3);
|
||||
} else {
|
||||
$taxtype = ZugferdDutyTaxFeeCategories::ZERO_RATED_GOODS;
|
||||
$xrechnung->addDocumentPositionTax($taxtype, 'VAT', 0);
|
||||
$this->xrechnung->addDocumentPositionTax($taxtype, 'VAT', 0);
|
||||
$this->addtoTaxMap($taxtype, $linenetamount, 0);
|
||||
nlog("Can't add correct tax position");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$xrechnung->setDocumentSummation($this->invoice->amount, $this->invoice->balance, $invoicing_data->getSubTotal(), $invoicing_data->getTotalSurcharges(), $invoicing_data->getTotalDiscount(), $invoicing_data->getSubTotal(), $invoicing_data->getItemTotalTaxes(), 0.0, $this->invoice->amount-$this->invoice->balance);
|
||||
$this->xrechnung->setDocumentSummation($this->invoice->amount, $this->invoice->balance, $invoicing_data->getSubTotal(), $invoicing_data->getTotalSurcharges(), $invoicing_data->getTotalDiscount(), $invoicing_data->getSubTotal(), $invoicing_data->getItemTotalTaxes(), 0.0, $this->invoice->amount-$this->invoice->balance);
|
||||
|
||||
foreach ($this->tax_map as $item){
|
||||
$xrechnung->addDocumentTax($item["tax_type"], "VAT", $item["net_amount"], $item["tax_rate"]*$item["net_amount"], $item["tax_rate"]*100);
|
||||
}
|
||||
$disk = config('filesystems.default');
|
||||
|
||||
if (!Storage::disk($disk)->exists($client->e_invoice_filepath($this->invoice->invitations->first()))) {
|
||||
Storage::makeDirectory($client->e_invoice_filepath($this->invoice->invitations->first()));
|
||||
$this->xrechnung->addDocumentTax($item["tax_type"], "VAT", $item["net_amount"], $item["tax_rate"]*$item["net_amount"], $item["tax_rate"]*100);
|
||||
}
|
||||
|
||||
$xrechnung->writeFile(Storage::disk($disk)->path($client->e_invoice_filepath($this->invoice->invitations->first()) . $this->invoice->getFileName("xml")));
|
||||
// The validity can be checked using https://portal3.gefeg.com/invoice/validation or https://e-rechnung.bayern.de/app/#/upload
|
||||
|
||||
return $client->e_invoice_filepath($this->invoice->invitations->first()) . $this->invoice->getFileName("xml");
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the XML document
|
||||
* in string format
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getXml(): string
|
||||
{
|
||||
return $this->xrechnung->getContent();
|
||||
}
|
||||
|
||||
private function getTaxType($name): string
|
||||
|
@ -1,51 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Services\Invoice;
|
||||
|
||||
use App\Jobs\Invoice\CreateEInvoice;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\Invoice;
|
||||
use App\Services\AbstractService;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class GetInvoiceEInvoice extends AbstractService
|
||||
{
|
||||
public function __construct(public Invoice $invoice, public ?ClientContact $contact = null)
|
||||
{
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
if (! $this->contact) {
|
||||
$this->contact = $this->invoice->client->primary_contact()->first() ?: $this->invoice->client->contacts()->first();
|
||||
}
|
||||
|
||||
$invitation = $this->invoice->invitations->where('client_contact_id', $this->contact->id)->first();
|
||||
|
||||
if (! $invitation) {
|
||||
$invitation = $this->invoice->invitations->first();
|
||||
}
|
||||
|
||||
$file_path = $this->invoice->client->e_invoice_filepath($this->invoice->invitations->first()). $this->invoice->getFileName("xml");
|
||||
|
||||
// $disk = 'public';
|
||||
$disk = config('filesystems.default');
|
||||
|
||||
$file = Storage::disk($disk)->exists($file_path);
|
||||
|
||||
if (! $file) {
|
||||
$file_path = (new CreateEInvoice($this->invoice, false))->handle();
|
||||
}
|
||||
|
||||
return $file_path;
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@
|
||||
namespace App\Services\Invoice;
|
||||
|
||||
use App\Jobs\Entity\CreateEntityPdf;
|
||||
use App\Jobs\Invoice\CreateEInvoice;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\Invoice;
|
||||
use App\Services\AbstractService;
|
||||
@ -47,7 +48,7 @@ class GetInvoicePdf extends AbstractService
|
||||
if (! $file) {
|
||||
$file_path = (new CreateEntityPdf($invitation))->handle();
|
||||
}
|
||||
|
||||
|
||||
return $file_path;
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ namespace App\Services\Invoice;
|
||||
|
||||
use App\Events\Invoice\InvoiceWasArchived;
|
||||
use App\Jobs\Entity\CreateEntityPdf;
|
||||
use App\Jobs\Entity\CreateRawPdf;
|
||||
use App\Jobs\Inventory\AdjustProductInventory;
|
||||
use App\Jobs\Invoice\CreateEInvoice;
|
||||
use App\Libraries\Currency\Conversion\CurrencyApi;
|
||||
@ -187,6 +188,13 @@ class InvoiceService
|
||||
return (new GetInvoicePdf($this->invoice, $contact))->run();
|
||||
}
|
||||
|
||||
public function getRawInvoicePdf($contact = null)
|
||||
{
|
||||
$invitation = $contact ? $this->invoice->invitations()->where('contact_id', $contact->id)->first() : $this->invoice->invitations()->first();
|
||||
|
||||
return (new CreateRawPdf($invitation, $invitation->company->db))->handle();
|
||||
}
|
||||
|
||||
public function getInvoiceDeliveryNote(Invoice $invoice, \App\Models\ClientContact $contact = null)
|
||||
{
|
||||
return (new GenerateDeliveryNote($invoice, $contact))->run();
|
||||
@ -194,13 +202,9 @@ class InvoiceService
|
||||
|
||||
public function getEInvoice($contact = null)
|
||||
{
|
||||
return (new GetInvoiceEInvoice($this->invoice, $contact))->run();
|
||||
return (new CreateEInvoice($this->invoice))->handle();
|
||||
}
|
||||
|
||||
public function mergeEInvoice($contact = null): void
|
||||
{
|
||||
(new MergeEInvoice($this->invoice, $contact))->run();
|
||||
}
|
||||
public function sendEmail($contact = null)
|
||||
{
|
||||
$send_email = new SendEmail($this->invoice, null, $contact);
|
||||
@ -343,6 +347,7 @@ class InvoiceService
|
||||
})->toArray();
|
||||
|
||||
$this->deletePdf();
|
||||
$this->deleteEInvoice();
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -410,6 +415,7 @@ class InvoiceService
|
||||
|
||||
$this->invoice = $this->invoice->calc()->getInvoice();
|
||||
$this->deletePdf();
|
||||
$this->deleteEInvoice();
|
||||
|
||||
/* 24-03-2022 */
|
||||
$new_balance = $this->invoice->balance;
|
||||
@ -462,12 +468,6 @@ class InvoiceService
|
||||
if ($force) {
|
||||
$this->invoice->invitations->each(function ($invitation) {
|
||||
(new CreateEntityPdf($invitation))->handle();
|
||||
|
||||
if ($invitation->invoice->client->getSetting('enable_e_invoice') && $invitation instanceof InvoiceInvitation)
|
||||
{
|
||||
(new CreateEInvoice($invitation->invoice, true))->handle();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return $this;
|
||||
@ -478,7 +478,7 @@ class InvoiceService
|
||||
CreateEntityPdf::dispatch($invitation);
|
||||
|
||||
if ($invitation->invoice->client->getSetting('enable_e_invoice') && $invitation instanceof InvoiceInvitation) {
|
||||
CreateEInvoice::dispatch($invitation->invoice, true);
|
||||
CreateEInvoice::dispatch($invitation->invoice);
|
||||
}
|
||||
|
||||
});
|
||||
|
@ -1,61 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Invoice;
|
||||
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\Invoice;
|
||||
use App\Services\AbstractService;
|
||||
use horstoeko\zugferd\ZugferdDocumentPdfBuilder;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use horstoeko\zugferd\ZugferdDocumentReader;
|
||||
|
||||
class MergeEInvoice extends AbstractService
|
||||
{
|
||||
public function __construct(public Invoice $invoice, public ?ClientContact $contact = null)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
$e_invoice_type = $this->invoice->client->getSetting('e_invoice_type');
|
||||
switch ($e_invoice_type) {
|
||||
case "EN16931":
|
||||
case "XInvoice_2_2":
|
||||
case "XInvoice_2_1":
|
||||
case "XInvoice_2_0":
|
||||
case "XInvoice_1_0":
|
||||
case "XInvoice-Extended":
|
||||
case "XInvoice-BasicWL":
|
||||
case "XInvoice-Basic":
|
||||
$this->embedEInvoiceZuGFerD();
|
||||
//case "Facturae_3.2":
|
||||
//case "Facturae_3.2.1":
|
||||
//case "Facturae_3.2.2":
|
||||
//
|
||||
default:
|
||||
$this->embedEInvoiceZuGFerD();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function embedEInvoiceZuGFerD(): void
|
||||
{
|
||||
$filepath_pdf = $this->invoice->client->invoice_filepath($this->invoice->invitations->first()) . $this->invoice->getFileName();
|
||||
$e_invoice_path = $this->invoice->client->e_invoice_filepath($this->invoice->invitations->first()) . $this->invoice->getFileName("xml");
|
||||
$document = ZugferdDocumentReader::readAndGuessFromFile($e_invoice_path);
|
||||
$disk = config('filesystems.default');
|
||||
|
||||
if (!Storage::disk($disk)->exists($this->invoice->client->e_invoice_filepath($this->invoice->invitations->first()))) {
|
||||
Storage::makeDirectory($this->invoice->client->e_invoice_filepath($this->invoice->invitations->first()));
|
||||
}
|
||||
$pdfBuilder = new ZugferdDocumentPdfBuilder($document, Storage::disk($disk)->path($filepath_pdf));
|
||||
$pdfBuilder->generateDocument();
|
||||
$pdfBuilder->saveDocument(Storage::disk($disk)->path($filepath_pdf));
|
||||
}
|
||||
}
|
@ -57,7 +57,7 @@ class PurchaseOrderExpense
|
||||
|
||||
$expense->number = empty($expense->number) ? $this->getNextExpenseNumber($expense) : $expense->number;
|
||||
|
||||
$expense->save();
|
||||
$expense->saveQuietly();
|
||||
event('eloquent.created: App\Models\Expense', $expense);
|
||||
|
||||
$this->purchase_order->expense_id = $expense->id;
|
||||
|
@ -20,11 +20,8 @@ class PurchaseOrderService
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
public PurchaseOrder $purchase_order;
|
||||
|
||||
public function __construct(PurchaseOrder $purchase_order)
|
||||
public function __construct(public PurchaseOrder $purchase_order)
|
||||
{
|
||||
$this->purchase_order = $purchase_order;
|
||||
}
|
||||
|
||||
public function createInvitations()
|
||||
@ -156,7 +153,7 @@ class PurchaseOrderService
|
||||
|
||||
/**
|
||||
* Saves the purchase order.
|
||||
* @return \App\Models\PurchaseOrder object
|
||||
* @return \App\Models\PurchaseOrder
|
||||
*/
|
||||
public function save(): ?PurchaseOrder
|
||||
{
|
||||
|
@ -52,8 +52,8 @@ class TemplateEngine
|
||||
|
||||
public $template;
|
||||
|
||||
/** @var \App\Models\Invoice | \App\Models\Quote | \App\Models\Credit | \App\Models\PurchaseOrder | \App\Models\RecurringInvoice $entity_obj **/
|
||||
private \App\Models\Invoice | \App\Models\Quote | \App\Models\Credit | \App\Models\PurchaseOrder | \App\Models\RecurringInvoice $entity_obj;
|
||||
/** @var \App\Models\Invoice | \App\Models\Quote | \App\Models\Credit | \App\Models\PurchaseOrder | \App\Models\RecurringInvoice | \App\Models\Payment $entity_obj **/
|
||||
private \App\Models\Invoice | \App\Models\Quote | \App\Models\Credit | \App\Models\PurchaseOrder | \App\Models\RecurringInvoice | \App\Models\Payment $entity_obj;
|
||||
|
||||
/** @var \App\Models\Company | \App\Models\Client | null $settings_entity **/
|
||||
private $settings_entity;
|
||||
|
@ -79,7 +79,7 @@ trait Inviteable
|
||||
if (Ninja::isHosted()) {
|
||||
$domain = $this->company->domain();
|
||||
} else {
|
||||
$domain = strlen($this->company->portal_domain) > 5 ? $this->company->portal_domain : config('ninja.app_url');
|
||||
$domain = strlen($this->company->portal_domain ?? '') > 5 ? $this->company->portal_domain : config('ninja.app_url');
|
||||
}
|
||||
|
||||
$entity_type = Str::snake(class_basename($this->entityType()));
|
||||
@ -94,7 +94,7 @@ trait Inviteable
|
||||
if (Ninja::isHosted()) {
|
||||
$domain = $this->company->domain();
|
||||
} else {
|
||||
$domain = strlen($this->company->portal_domain) > 5 ? $this->company->portal_domain : config('ninja.app_url');
|
||||
$domain = strlen($this->company->portal_domain ?? '') > 5 ? $this->company->portal_domain : config('ninja.app_url');
|
||||
}
|
||||
|
||||
switch ($this->company->portal_mode) {
|
||||
@ -119,7 +119,7 @@ trait Inviteable
|
||||
if (Ninja::isHosted()) {
|
||||
$domain = $this->company->domain();
|
||||
} else {
|
||||
$domain = strlen($this->company->portal_domain) > 5 ? $this->company->portal_domain : config('ninja.app_url');
|
||||
$domain = strlen($this->company->portal_domain ?? '') > 5 ? $this->company->portal_domain : config('ninja.app_url');
|
||||
}
|
||||
|
||||
switch ($this->company->portal_mode) {
|
||||
|
815
composer.lock
generated
815
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -22,8 +22,8 @@ return [
|
||||
*/
|
||||
'batch' => true,
|
||||
|
||||
'cache_connection' => 'sentinel-cache',
|
||||
|
||||
'cache_connection' => 'sentinel-cache',
|
||||
// 'cache_connection' => 'cache',
|
||||
/*
|
||||
* The default key used to store
|
||||
* metrics for batching
|
||||
|
@ -15,8 +15,8 @@ return [
|
||||
'require_https' => env('REQUIRE_HTTPS', true),
|
||||
'app_url' => rtrim(env('APP_URL', ''), '/'),
|
||||
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
|
||||
'app_version' => env('APP_VERSION','5.6.30'),
|
||||
'app_tag' => env('APP_TAG','5.6.30'),
|
||||
'app_version' => env('APP_VERSION','5.6.31'),
|
||||
'app_tag' => env('APP_TAG','5.6.31'),
|
||||
'minimum_client_version' => '5.0.16',
|
||||
'terms_version' => '1.0.1',
|
||||
'api_secret' => env('API_SECRET', ''),
|
||||
|
@ -5101,7 +5101,7 @@ $LANG = array(
|
||||
'certificate_passphrase' => 'Certificate Passphrase',
|
||||
'valid_vat_number' => 'Valid VAT Number',
|
||||
'react_notification_link' => 'React Notification Links',
|
||||
'react_notification_link_help' => 'Admin emails will contain links to the react application',
|
||||
'react_notification_link_help' => 'Admin emails will contain links to the react application',
|
||||
'show_task_billable' => 'Show Task Billable',
|
||||
'credit_item' => 'Credit Item',
|
||||
'drop_file_here' => 'Drop file here',
|
||||
@ -5109,7 +5109,7 @@ $LANG = array(
|
||||
'camera' => 'Camera',
|
||||
'gallery' => 'Gallery',
|
||||
'project_location' => 'Project Location',
|
||||
'add_gateway_help_message' => 'Add a payment gateway (ie. Stripe, WePay or PayPal) to accept online payments',
|
||||
'add_gateway_help_message' => 'Add a payment gateway (ie. Stripe, WePay or PayPal) to accept online payments',
|
||||
'lang_Hungarian' => 'Hungarian',
|
||||
'use_mobile_to_manage_plan' => 'Use your phone subscription settings to manage your plan',
|
||||
'item_tax3' => 'Item Tax3',
|
||||
@ -5125,7 +5125,7 @@ $LANG = array(
|
||||
'lang_French - Swiss' => 'French - Swiss',
|
||||
'currency_swazi_lilangeni' => 'Swazi Lilangeni',
|
||||
'income' => 'Income',
|
||||
'amount_received_help' => 'Enter a value here if the total amount received was MORE than the invoice amount, or when recording a payment with no invoices. Otherwise this field should be left blank.',
|
||||
'amount_received_help' => 'Enter a value here if the total amount received was MORE than the invoice amount, or when recording a payment with no invoices. Otherwise this field should be left blank.',
|
||||
'vendor_phone' => 'Vendor Phone',
|
||||
'mercado_pago' => 'Mercado Pago',
|
||||
'mybank' => 'MyBank',
|
||||
@ -5143,6 +5143,7 @@ $LANG = array(
|
||||
'is_tax_exempt' => 'Tax Exempt',
|
||||
'drop_files_here' => 'Drop files here',
|
||||
'upload_files' => 'Upload Files',
|
||||
'download_e_invoice' => 'Download E-Invoice',
|
||||
'triangular_tax_info' => 'Intra-community triangular transaction',
|
||||
'intracommunity_tax_info' => 'Tax-free intra-community delivery',
|
||||
'reverse_tax_info' => 'Please note that this supply is subject to reverse charge',
|
||||
|
@ -8,19 +8,18 @@ parameters:
|
||||
excludePaths:
|
||||
- 'vendor/'
|
||||
- 'app/Jobs/Ninja/*'
|
||||
- 'app/Models/Presenters/*'
|
||||
- 'app/Console/Commands/*'
|
||||
- 'app/DataMapper/Analytics/*'
|
||||
- 'app/PaymentDrivers/Authorize/*'
|
||||
- 'app/Utils/Traits/*'
|
||||
- 'resources/views/*'
|
||||
universalObjectCratesClasses:
|
||||
- App\DataMapper\Tax\RuleInterface
|
||||
- App\DataMapper\FeesAndLimits
|
||||
reportUnmatchedIgnoredErrors: false
|
||||
ignoreErrors:
|
||||
- '#Call to an undefined method [a-zA-Z0-9\\_]+::company\(\)#'
|
||||
- '#Call to an undefined method [a-zA-Z0-9\\_]+::entityFilter\(\)#'
|
||||
- '#Call to an undefined method [a-zA-Z0-9\\_]+::exclude\(\)#'
|
||||
- '#Array has 2 duplicate keys with value#'
|
||||
- '#Undefined method#'
|
||||
- '#Call to an undefined method#'
|
||||
- '#makeHidden#'
|
||||
- '#Socialite#'
|
59
resources/views/email/admin/stripe_connect_failed.blade.php
Normal file
59
resources/views/email/admin/stripe_connect_failed.blade.php
Normal file
@ -0,0 +1,59 @@
|
||||
@component('email.template.admin', ['design' => 'light', 'settings' => $settings, 'logo' => $logo])
|
||||
<div class="center">
|
||||
@isset($greeting)
|
||||
<p>{{ $greeting }}</p>
|
||||
@endisset
|
||||
|
||||
@isset($title)
|
||||
<h1>{{ $title }}</h1>
|
||||
@endisset
|
||||
|
||||
@isset($h2)
|
||||
<h2>{{ $title }}</h2>
|
||||
@endisset
|
||||
|
||||
<div style="margin-top: 10px; margin-bottom: 30px;">
|
||||
@isset($body)
|
||||
{!! $body !!}
|
||||
@endisset
|
||||
|
||||
@isset($slot)
|
||||
{{ $slot }}
|
||||
@endisset
|
||||
</div>
|
||||
|
||||
@isset($additional_info)
|
||||
<p>{{ $additional_info }}</p>
|
||||
@endisset
|
||||
|
||||
@isset($url)
|
||||
|
||||
<!--[if (gte mso 9)|(IE)]>
|
||||
<table align="center" cellspacing="0" cellpadding="0" style="width: 600px;">
|
||||
<tr>
|
||||
<td align="center" valign="top">
|
||||
<![endif]-->
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" >
|
||||
<tbody><tr>
|
||||
<td align="center" class="new_button" style="border-radius: 2px; background-color: {{ $settings->primary_color }} ;">
|
||||
<a href="{{ $url }}" target="_blank" class="new_button" style="text-decoration: none; border: 1px solid {{ $settings->primary_color }}; display: inline-block; border-radius: 2px; padding-top: 15px; padding-bottom: 15px; padding-left: 25px; padding-right: 25px; font-size: 20px; color: #fff">
|
||||
<singleline label="cta button">{{ ctrans($button) }}</singleline>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!--[if (gte mso 9)|(IE)]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<![endif]-->
|
||||
|
||||
|
||||
@endisset
|
||||
|
||||
@isset($signature)
|
||||
<p>{!! nl2br($signature) !!}</p>
|
||||
@endisset
|
||||
</div>
|
||||
@endcomponent
|
@ -0,0 +1,5 @@
|
||||
{!! $title !!}
|
||||
|
||||
{!! $text_body !!}
|
||||
|
||||
{!! $url !!}
|
@ -1,5 +1,5 @@
|
||||
<div>
|
||||
<div class="flex flex-col items-end mb-2" x-data>
|
||||
<div class="flex flex-row space-x-2 float-right mb-2" x-data>
|
||||
<button wire:loading.attr="disabled" wire:click="downloadPdf" class="bg-primary text-white px-4 py-4 lg:px-2 lg:py-2 rounded" type="button">
|
||||
<span class="mr-0">{{ ctrans('texts.download_pdf') }}</span>
|
||||
<div wire:loading wire:target="downloadPdf">
|
||||
@ -9,6 +9,17 @@
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
@if($settings->enable_e_invoice && $entity_type == 'invoice')
|
||||
<button wire:loading.attr="disabled" wire:click="downloadEInvoice" class="bg-primary text-white px-4 py-4 lg:px-2 lg:py-2 rounded" type="button">
|
||||
<span>{{ ctrans('texts.download_e_invoice') }}</span>
|
||||
<div wire:loading wire:target="downloadEInvoice">
|
||||
<svg class="animate-spin h-5 w-5 text-blue" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
<div class="hidden lg:block">
|
||||
<div wire:init="getPdf()">
|
||||
@ -86,7 +97,7 @@ function waitForElement(querySelector, timeout){
|
||||
}
|
||||
});
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
if(timeout) timer = setTimeout(()=>{
|
||||
@ -97,4 +108,4 @@ function waitForElement(querySelector, timeout){
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
</script>
|
||||
|
@ -16,7 +16,7 @@
|
||||
<form action="{{ route('client.invoices.bulk') }}" method="post" id="bulkActions">
|
||||
@csrf
|
||||
<button type="submit" onclick="setTimeout(() => this.disabled = true, 0); setTimeout(() => this.disabled = false, 5000); return true;" class="button button-primary bg-primary" name="action" value="download">{{ ctrans('texts.download') }}</button>
|
||||
|
||||
@csrf
|
||||
@if(!empty(auth()->user()->client->service()->getPaymentMethods(0)))
|
||||
<button onclick="setTimeout(() => this.disabled = true, 0); return true;" type="submit" class="button button-primary bg-primary" name="action" value="payment">{{ ctrans('texts.pay_now') }}</button>
|
||||
@endif
|
||||
|
@ -38,6 +38,8 @@ class UserTest extends TestCase
|
||||
|
||||
private $default_email = 'attach@gmail.com';
|
||||
|
||||
public $faker;
|
||||
|
||||
protected function setUp() :void
|
||||
{
|
||||
parent::setUp();
|
||||
@ -50,7 +52,7 @@ class UserTest extends TestCase
|
||||
|
||||
Model::reguard();
|
||||
|
||||
$this->withoutExceptionHandling();
|
||||
// $this->withoutExceptionHandling();
|
||||
|
||||
$this->withoutMiddleware(
|
||||
ThrottleRequests::class,
|
||||
@ -58,10 +60,9 @@ class UserTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
public function testUserAttemptingtToDeleteThemselves()
|
||||
private function mockAccount()
|
||||
{
|
||||
|
||||
|
||||
$account = Account::factory()->create([
|
||||
'hosted_client_count' => 1000,
|
||||
'hosted_company_count' => 1000,
|
||||
@ -74,6 +75,7 @@ class UserTest extends TestCase
|
||||
'account_id' => $this->account->id,
|
||||
'confirmation_code' => 'xyz123',
|
||||
'email' => $this->faker->unique()->safeEmail(),
|
||||
'password' => \Illuminate\Support\Facades\Hash::make('ALongAndBriliantPassword'),
|
||||
]);
|
||||
|
||||
$settings = CompanySettings::defaults();
|
||||
@ -101,18 +103,164 @@ class UserTest extends TestCase
|
||||
$company_token->name = 'test token';
|
||||
$company_token->token = $token;
|
||||
$company_token->is_system = true;
|
||||
$company_token->save();
|
||||
|
||||
return $company_token;
|
||||
|
||||
}
|
||||
|
||||
public function testUserResponse()
|
||||
{
|
||||
$company_token = $this->mockAccount();
|
||||
|
||||
$data = [
|
||||
'first_name' => 'hey',
|
||||
'last_name' => 'you',
|
||||
'email' => 'normal_user@gmail.com',
|
||||
'company_user' => [
|
||||
'is_admin' => true,
|
||||
'is_owner' => false,
|
||||
'permissions' => 'create_client,create_invoice',
|
||||
],
|
||||
'phone' => null,
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $company_token->token,
|
||||
'X-API-PASSWORD' => 'ALongAndBriliantPassword',
|
||||
])->post('/api/v1/users?include=company_user', $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$user = $response->json();
|
||||
$user_id = $user['data']['id'];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $company_token->token,
|
||||
'X-API-PASSWORD' => 'ALongAndBriliantPassword',
|
||||
])->get('/api/v1/users', $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
$arr = $response->json();
|
||||
|
||||
$this->assertCount(2, $arr['data']);
|
||||
|
||||
//archive the user we just created:
|
||||
|
||||
$data = [
|
||||
'action' => 'archive',
|
||||
'ids' => [$user_id],
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $company_token->token,
|
||||
'X-API-PASSWORD' => 'ALongAndBriliantPassword',
|
||||
])->postJson('/api/v1/users/bulk', $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$this->assertCount(1, $response->json()['data']);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $company_token->token,
|
||||
'X-API-PASSWORD' => 'ALongAndBriliantPassword',
|
||||
])->get("/api/v1/users?without={$company_token->user->hashed_id}");
|
||||
|
||||
$response->assertStatus(200);
|
||||
$this->assertCount(1, $response->json()['data']);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $company_token->token,
|
||||
'X-API-PASSWORD' => 'ALongAndBriliantPassword',
|
||||
])->get("/api/v1/users?without={$company_token->user->hashed_id}&status=active");
|
||||
|
||||
$response->assertStatus(200);
|
||||
$this->assertCount(0, $response->json()['data']);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $company_token->token,
|
||||
'X-API-PASSWORD' => 'ALongAndBriliantPassword',
|
||||
])->get("/api/v1/users?without={$company_token->user->hashed_id}&status=archived");
|
||||
|
||||
$response->assertStatus(200);
|
||||
$this->assertCount(1, $response->json()['data']);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $company_token->token,
|
||||
'X-API-PASSWORD' => 'ALongAndBriliantPassword',
|
||||
])->get("/api/v1/users?without={$company_token->user->hashed_id}&status=deleted");
|
||||
|
||||
$response->assertStatus(200);
|
||||
$this->assertCount(0, $response->json()['data']);
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function testUserAttemptingtToDeleteThemselves()
|
||||
{
|
||||
|
||||
$account = Account::factory()->create([
|
||||
'hosted_client_count' => 1000,
|
||||
'hosted_company_count' => 1000,
|
||||
]);
|
||||
|
||||
$account->num_users = 3;
|
||||
$account->save();
|
||||
|
||||
$user = User::factory()->create([
|
||||
'account_id' => $this->account->id,
|
||||
'confirmation_code' => 'xyz123',
|
||||
'email' => $this->faker->unique()->safeEmail(),
|
||||
'password' => \Illuminate\Support\Facades\Hash::make('ALongAndBriliantPassword'),
|
||||
]);
|
||||
|
||||
$settings = CompanySettings::defaults();
|
||||
$settings->client_online_payment_notification = false;
|
||||
$settings->client_manual_payment_notification = false;
|
||||
|
||||
$company = Company::factory()->create([
|
||||
'account_id' => $account->id,
|
||||
'settings' => $settings,
|
||||
]);
|
||||
|
||||
|
||||
$cu = CompanyUserFactory::create($user->id, $company->id, $account->id);
|
||||
$cu->is_owner = true;
|
||||
$cu->is_admin = true;
|
||||
$cu->is_locked = false;
|
||||
$cu->save();
|
||||
|
||||
$token = \Illuminate\Support\Str::random(64);
|
||||
|
||||
$company_token = new CompanyToken();
|
||||
$company_token->user_id = $user->id;
|
||||
$company_token->company_id = $company->id;
|
||||
$company_token->account_id = $account->id;
|
||||
$company_token->name = 'test token';
|
||||
$company_token->token = $token;
|
||||
$company_token->is_system = true;
|
||||
$company_token->save();
|
||||
|
||||
$data = [
|
||||
'ids' => [$user->hashed_id],
|
||||
];
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $token,
|
||||
'X-API-PASSWORD' => 'ALongAndBriliantPassword',
|
||||
])->postJson('/api/v1/users/bulk?action=dete', $data)
|
||||
->assertStatus(403);
|
||||
])->postJson('/api/v1/users/bulk?action=delete', $data);
|
||||
|
||||
nlog($response);
|
||||
|
||||
$response->assertStatus(401);
|
||||
|
||||
}
|
||||
|
||||
|
@ -42,9 +42,8 @@ class EInvoiceTest extends TestCase
|
||||
$this->company->e_invoice_type = "EN16931";
|
||||
$this->invoice->client->routing_id = 'DE123456789';
|
||||
$this->invoice->client->save();
|
||||
$xinvoice = (new CreateEInvoice($this->invoice, false))->handle();
|
||||
$this->assertNotNull($xinvoice);
|
||||
$this->assertTrue(Storage::exists($xinvoice));
|
||||
$e_invoice = (new CreateEInvoice($this->invoice))->handle();
|
||||
$this->assertIsString($e_invoice);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -56,9 +55,8 @@ class EInvoiceTest extends TestCase
|
||||
$this->invoice->client->routing_id = 'DE123456789';
|
||||
$this->invoice->client->save();
|
||||
|
||||
$xinvoice = (new CreateEInvoice($this->invoice, false))->handle();
|
||||
nlog(Storage::path($xinvoice));
|
||||
$document = ZugferdDocumentReader::readAndGuessFromFile(Storage::path($xinvoice));
|
||||
$e_invoice = (new CreateEInvoice($this->invoice))->handle();
|
||||
$document = ZugferdDocumentReader::readAndGuessFromContent($e_invoice);
|
||||
$document->getDocumentInformation($documentno, $documenttypecode, $documentdate, $documentcurrency, $taxcurrency, $taxname, $documentlangeuage, $rest);
|
||||
$this->assertEquals($this->invoice->number, $documentno);
|
||||
}
|
||||
@ -69,8 +67,7 @@ class EInvoiceTest extends TestCase
|
||||
public function checkEmbededPDFFile()
|
||||
{
|
||||
$pdf = (new CreateEntityPdf($this->invoice->invitations()->first()))->handle();
|
||||
(new CreateEInvoice($this->invoice, true, $pdf))->handle();
|
||||
$document = ZugferdDocumentReader::readAndGuessFromFile($pdf);
|
||||
$document = ZugferdDocumentReader::readAndGuessFromContent($pdf);
|
||||
$document->getDocumentInformation($documentno, $documenttypecode, $documentdate, $documentcurrency, $taxcurrency, $taxname, $documentlangeuage, $rest);
|
||||
$this->assertEquals($this->invoice->number, $documentno);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user