mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
commit
035300bfcf
@ -1 +1 @@
|
|||||||
5.6.30
|
5.6.31
|
@ -471,7 +471,7 @@ class CheckData extends Command
|
|||||||
$ii->saveQuietly();
|
$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) {
|
if ($entity::doesntHave('invitations')->count() > 0) {
|
||||||
$entity::doesntHave('invitations')->cursor()->each(function ($entity) {
|
$entity::doesntHave('invitations')->cursor()->each(function ($entity) {
|
||||||
$client_vendor_key = 'client_id';
|
$client_vendor_key = 'client_id';
|
||||||
@ -694,7 +694,7 @@ class CheckData extends Command
|
|||||||
{
|
{
|
||||||
$this->wrong_balances = 0;
|
$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) {
|
$client->invoices->where('is_deleted', false)->whereIn('status_id', '!=', Invoice::STATUS_DRAFT)->each(function ($invoice) use ($client) {
|
||||||
$total_paid = $invoice->payments()
|
$total_paid = $invoice->payments()
|
||||||
->where('is_deleted', false)->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment::STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])
|
->where('is_deleted', false)->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment::STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])
|
||||||
@ -876,7 +876,7 @@ class CheckData extends Command
|
|||||||
$this->wrong_balances = 0;
|
$this->wrong_balances = 0;
|
||||||
$this->wrong_paid_to_dates = 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');
|
$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();
|
$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;
|
||||||
|
}
|
||||||
|
}
|
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.'%')
|
->orWhere('credits.custom_value4', 'like', '%'.$filter.'%')
|
||||||
->orWhereHas('client', function ($q) use ($filter) {
|
->orWhereHas('client', function ($q) use ($filter) {
|
||||||
$q->where('name', 'like', '%'.$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.'%')
|
->orWhere('custom_value4', 'like', '%'.$filter.'%')
|
||||||
->orWhereHas('client', function ($q) use ($filter) {
|
->orWhereHas('client', function ($q) use ($filter) {
|
||||||
$q->where('name', 'like', '%'.$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.'%')
|
->orWhere('custom_value4', 'like', '%'.$filter.'%')
|
||||||
->orWhereHas('client', function ($q) use ($filter) {
|
->orWhereHas('client', function ($q) use ($filter) {
|
||||||
$q->where('name', 'like', '%'.$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.'%')
|
->orWhere('custom_value4', 'like', '%'.$filter.'%')
|
||||||
->orWhereHas('client', function ($q) use ($filter) {
|
->orWhereHas('client', function ($q) use ($filter) {
|
||||||
$q->where('name', 'like', '%'.$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.'%')
|
->orWhere('custom_value4', 'like', '%'.$filter.'%')
|
||||||
->orWhereHas('client', function ($q) use ($filter) {
|
->orWhereHas('client', function ($q) use ($filter) {
|
||||||
$q->where('name', 'like', '%'.$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) {
|
->orWhereHas('client', function ($q) use ($filter) {
|
||||||
$q->where('name', 'like', '%'.$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.'%');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,7 +162,9 @@ class InvoiceController extends BaseController
|
|||||||
*/
|
*/
|
||||||
public function create(CreateInvoiceRequest $request)
|
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);
|
return $this->itemResponse($invoice);
|
||||||
}
|
}
|
||||||
@ -211,7 +213,11 @@ class InvoiceController extends BaseController
|
|||||||
*/
|
*/
|
||||||
public function store(StoreInvoiceRequest $request)
|
public function store(StoreInvoiceRequest $request)
|
||||||
{
|
{
|
||||||
$invoice = $this->invoice_repo->save($request->all(), InvoiceFactory::create(auth()->user()->company()->id, auth()->user()->id));
|
|
||||||
|
/** @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()
|
$invoice = $invoice->service()
|
||||||
->fillDefaults()
|
->fillDefaults()
|
||||||
@ -219,7 +225,7 @@ class InvoiceController extends BaseController
|
|||||||
->adjustInventory()
|
->adjustInventory()
|
||||||
->save();
|
->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 = [
|
$transaction = [
|
||||||
'invoice' => $invoice->transaction_event(),
|
'invoice' => $invoice->transaction_event(),
|
||||||
@ -473,62 +479,17 @@ class InvoiceController extends BaseController
|
|||||||
return $this->itemResponse($invoice->fresh());
|
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)
|
public function bulk(BulkInvoiceRequest $request)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
/** @var \App\Models\User $user */
|
||||||
|
$user = auth()->user();
|
||||||
|
|
||||||
$action = $request->input('action');
|
$action = $request->input('action');
|
||||||
|
|
||||||
$ids = $request->input('ids');
|
$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);
|
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) {
|
if ($action == 'bulk_download' && $invoices->count() > 1) {
|
||||||
$invoices->each(function ($invoice) {
|
$invoices->each(function ($invoice) use($user) {
|
||||||
if (auth()->user()->cannot('view', $invoice)) {
|
if ($user->cannot('view', $invoice)) {
|
||||||
nlog('access denied');
|
nlog('access denied');
|
||||||
|
|
||||||
return response()->json(['message' => ctrans('text.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);
|
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();
|
$file = $invoices->first()->service()->getInvoicePdf();
|
||||||
|
|
||||||
return response()->streamDownload(function () use ($file) {
|
return response()->streamDownload(function () use ($file) {
|
||||||
@ -564,7 +525,7 @@ class InvoiceController extends BaseController
|
|||||||
}, basename($file), ['Content-Type' => 'application/pdf']);
|
}, 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) {
|
$paths = $invoices->map(function ($invoice) {
|
||||||
return $invoice->service()->getInvoicePdf();
|
return $invoice->service()->getInvoicePdf();
|
||||||
});
|
});
|
||||||
@ -579,15 +540,15 @@ class InvoiceController extends BaseController
|
|||||||
/*
|
/*
|
||||||
* Send the other actions to the switch
|
* Send the other actions to the switch
|
||||||
*/
|
*/
|
||||||
$invoices->each(function ($invoice, $key) use ($action) {
|
$invoices->each(function ($invoice, $key) use ($action, $user) {
|
||||||
if (auth()->user()->can('edit', $invoice)) {
|
if ($user->can('edit', $invoice)) {
|
||||||
$this->performAction($invoice, $action, true);
|
$this->performAction($invoice, $action, true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/* Need to understand which permission are required for the given bulk action ie. view / edit */
|
/* 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());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1005,16 +966,17 @@ class InvoiceController extends BaseController
|
|||||||
*/
|
*/
|
||||||
public function upload(UploadInvoiceRequest $request, Invoice $invoice)
|
public function upload(UploadInvoiceRequest $request, Invoice $invoice)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (! $this->checkFeature(Account::FEATURE_DOCUMENTS)) {
|
if (! $this->checkFeature(Account::FEATURE_DOCUMENTS)) {
|
||||||
return $this->featureFailure();
|
return $this->featureFailure();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->has('documents')) {
|
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')) {
|
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());
|
return $this->itemResponse($invoice->fresh());
|
||||||
@ -1022,7 +984,10 @@ class InvoiceController extends BaseController
|
|||||||
|
|
||||||
public function update_reminders(UpdateReminderRequest $request)
|
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);
|
return response()->json(['message' => 'Updating reminders'], 200);
|
||||||
}
|
}
|
||||||
|
@ -62,11 +62,11 @@ class BillingPortalPurchase extends Component
|
|||||||
public $password;
|
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.
|
* Instance of client contact.
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Livewire;
|
namespace App\Http\Livewire;
|
||||||
|
|
||||||
|
use App\Services\Invoice\GetInvoiceEInvoice;
|
||||||
use App\Utils\Number;
|
use App\Utils\Number;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use App\Utils\HtmlEngine;
|
use App\Utils\HtmlEngine;
|
||||||
@ -95,6 +96,20 @@ class PdfSlot extends Component
|
|||||||
echo $file;
|
echo $file;
|
||||||
}, $file_name, $headers);
|
}, $file_name, $headers);
|
||||||
|
|
||||||
|
}
|
||||||
|
public function downloadEInvoice()
|
||||||
|
{
|
||||||
|
|
||||||
|
$file_name = $this->entity->numberFormatter().'.xml';
|
||||||
|
|
||||||
|
$file = (new GetInvoiceEInvoice($this->entity))->run();
|
||||||
|
|
||||||
|
$headers = ['Content-Type' => 'application/xml'];
|
||||||
|
|
||||||
|
return response()->streamDownload(function () use ($file) {
|
||||||
|
echo $file;
|
||||||
|
}, $file_name, $headers);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
@ -106,6 +121,7 @@ class PdfSlot extends Component
|
|||||||
|
|
||||||
$this->show_cost = in_array('$product.unit_cost', $this->settings->pdf_variables->product_columns);
|
$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_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 ){
|
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);
|
$this->show_cost = in_array('$product.unit_cost', $this->settings->pdf_variables->product_quote_columns);
|
||||||
|
@ -23,7 +23,9 @@ class UploadInvoiceRequest extends Request
|
|||||||
*/
|
*/
|
||||||
public function authorize() : bool
|
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()
|
public function rules()
|
||||||
@ -42,6 +44,8 @@ class UploadInvoiceRequest extends Request
|
|||||||
$rules['file'] = $this->file_validation;
|
$rules['file'] = $this->file_validation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$rules['is_public'] = 'sometimes|boolean';
|
||||||
|
|
||||||
return $rules;
|
return $rules;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ class Request extends FormRequest
|
|||||||
use MakesHash;
|
use MakesHash;
|
||||||
use RuntimeFormRequest;
|
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.
|
* Get the validation rules that apply to the request.
|
||||||
*
|
*
|
||||||
|
@ -13,6 +13,7 @@ namespace App\Jobs\Entity;
|
|||||||
|
|
||||||
use App\Exceptions\FilePermissionsFailure;
|
use App\Exceptions\FilePermissionsFailure;
|
||||||
use App\Jobs\Invoice\CreateEInvoice;
|
use App\Jobs\Invoice\CreateEInvoice;
|
||||||
|
use App\Jobs\Invoice\MergeEInvoice;
|
||||||
use App\Libraries\MultiDB;
|
use App\Libraries\MultiDB;
|
||||||
use App\Models\Credit;
|
use App\Models\Credit;
|
||||||
use App\Models\CreditInvitation;
|
use App\Models\CreditInvitation;
|
||||||
@ -214,7 +215,9 @@ class CreateEntityPdf implements ShouldQueue
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($this->entity_string == "invoice" && $this->client->getSetting('enable_e_invoice')){
|
if ($this->entity_string == "invoice" && $this->client->getSetting('enable_e_invoice')){
|
||||||
(new CreateEInvoice($this->entity, true))->handle();
|
(new CreateEInvoice($this->entity))->handle();
|
||||||
|
(new MergeEInvoice($this->entity))->handle();
|
||||||
|
|
||||||
}
|
}
|
||||||
$this->invitation = null;
|
$this->invitation = null;
|
||||||
// $this->entity = null;
|
// $this->entity = null;
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
namespace App\Jobs\Entity;
|
namespace App\Jobs\Entity;
|
||||||
|
|
||||||
use App\Exceptions\FilePermissionsFailure;
|
use App\Exceptions\FilePermissionsFailure;
|
||||||
|
use App\Jobs\Invoice\MergeEInvoice;
|
||||||
use App\Libraries\MultiDB;
|
use App\Libraries\MultiDB;
|
||||||
use App\Models\Credit;
|
use App\Models\Credit;
|
||||||
use App\Models\CreditInvitation;
|
use App\Models\CreditInvitation;
|
||||||
@ -202,6 +203,12 @@ class CreateRawPdf implements ShouldQueue
|
|||||||
if ($pdf) {
|
if ($pdf) {
|
||||||
$maker =null;
|
$maker =null;
|
||||||
$state = null;
|
$state = null;
|
||||||
|
if ($this->invitation->invoice->client->getSetting('enable_e_invoice') && $this->entity_string == "invoice"){
|
||||||
|
$filename = tempnam(sys_get_temp_dir(), 'InvoiceNinja').".pdf";
|
||||||
|
file_put_contents($filename, $pdf);
|
||||||
|
(new \App\Services\Invoice\MergeEInvoice($this->invitation->invoice, $filename))->run();
|
||||||
|
return file_get_contents($filename);
|
||||||
|
};
|
||||||
return $pdf;
|
return $pdf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ namespace App\Jobs\Invoice;
|
|||||||
|
|
||||||
use App\Utils\Ninja;
|
use App\Utils\Ninja;
|
||||||
use App\Models\Invoice;
|
use App\Models\Invoice;
|
||||||
|
use horstoeko\zugferd\ZugferdDocumentBuilder;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Support\Facades\App;
|
use Illuminate\Support\Facades\App;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
@ -29,7 +30,7 @@ class CreateEInvoice implements ShouldQueue
|
|||||||
|
|
||||||
public $deleteWhenMissingModels = true;
|
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)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,7 +40,7 @@ class CreateEInvoice implements ShouldQueue
|
|||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function handle(): string
|
public function handle(): string|ZugferdDocumentBuilder
|
||||||
{
|
{
|
||||||
/* Forget the singleton*/
|
/* Forget the singleton*/
|
||||||
App::forgetInstance('translator');
|
App::forgetInstance('translator');
|
||||||
@ -63,13 +64,13 @@ class CreateEInvoice implements ShouldQueue
|
|||||||
case "XInvoice-Extended":
|
case "XInvoice-Extended":
|
||||||
case "XInvoice-BasicWL":
|
case "XInvoice-BasicWL":
|
||||||
case "XInvoice-Basic":
|
case "XInvoice-Basic":
|
||||||
return (new ZugferdEInvoice($this->invoice))->run();
|
return (new ZugferdEInvoice($this->invoice, $this->returnObject))->run();
|
||||||
case "Facturae_3.2":
|
case "Facturae_3.2":
|
||||||
case "Facturae_3.2.1":
|
case "Facturae_3.2.1":
|
||||||
case "Facturae_3.2.2":
|
case "Facturae_3.2.2":
|
||||||
return (new FacturaEInvoice($this->invoice, str_replace("Facturae_", "", $e_invoice_type)))->run();
|
return (new FacturaEInvoice($this->invoice, str_replace("Facturae_", "", $e_invoice_type)))->run();
|
||||||
default:
|
default:
|
||||||
return (new ZugferdEInvoice($this->invoice))->run();
|
return (new ZugferdEInvoice($this->invoice, $this->returnObject))->run();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
68
app/Jobs/Invoice/MergeEInvoice.php
Normal file
68
app/Jobs/Invoice/MergeEInvoice.php
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\Invoice;
|
||||||
|
|
||||||
|
use App\Models\ClientContact;
|
||||||
|
use App\Models\Invoice;
|
||||||
|
use horstoeko\zugferd\ZugferdDocumentPdfBuilder;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use horstoeko\zugferd\ZugferdDocumentReader;
|
||||||
|
|
||||||
|
class MergeEInvoice implements ShouldQueue
|
||||||
|
{
|
||||||
|
public function __construct(public Invoice $invoice, private string $pdf_path = "")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function handle(): 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 = !empty($this->pdf_path) ? $this->pdf_path : $this->invoice->service()->getInvoicePdf();
|
||||||
|
$disk = config('filesystems.default');
|
||||||
|
$e_rechnung = (new CreateEInvoice($this->invoice, true))->handle();
|
||||||
|
if (!empty($this->pdf_path)){
|
||||||
|
$realpath_pdf = $filepath_pdf;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$realpath_pdf = Storage::disk($disk)->path($filepath_pdf);
|
||||||
|
}
|
||||||
|
if (file_exists($realpath_pdf)){
|
||||||
|
$pdfBuilder = new ZugferdDocumentPdfBuilder($e_rechnung, $realpath_pdf);
|
||||||
|
$pdfBuilder->generateDocument();
|
||||||
|
$pdfBuilder->saveDocument($realpath_pdf);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
nlog("E_Invoice Merge failed - file to merge not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -76,7 +76,8 @@ class ZipInvoices implements ShouldQueue
|
|||||||
$this->invoices->each(function ($invoice) {
|
$this->invoices->each(function ($invoice) {
|
||||||
(new CreateEntityPdf($invoice->invitations()->first()))->handle();
|
(new CreateEntityPdf($invoice->invitations()->first()))->handle();
|
||||||
if ($invoice->client->getSetting('enable_e_invoice')){
|
if ($invoice->client->getSetting('enable_e_invoice')){
|
||||||
(new CreateEInvoice($invoice, false))->handle();
|
(new CreateEInvoice($invoice))->handle();
|
||||||
|
(new MergeEInvoice($invoice))->handle();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -36,10 +36,10 @@ class UserEmailChanged implements ShouldQueue
|
|||||||
* Create a new job instance.
|
* Create a new job instance.
|
||||||
*
|
*
|
||||||
* @param \App\Models\User $new_user
|
* @param \App\Models\User $new_user
|
||||||
* @param \App\Models\User $old_user
|
* @param \stdClass $old_user
|
||||||
* @param \App\Models\Company $company
|
* @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;
|
$this->settings = $this->company->settings;
|
||||||
}
|
}
|
||||||
|
@ -1821,7 +1821,7 @@ class Import implements ShouldQueue
|
|||||||
|
|
||||||
private function processActivities(array $data): void
|
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();
|
$a->forceDelete();
|
||||||
nlog("deleting {$a->id}");
|
nlog("deleting {$a->id}");
|
||||||
});
|
});
|
||||||
|
@ -53,7 +53,7 @@ class UploadFile implements ShouldQueue
|
|||||||
|
|
||||||
public $disk;
|
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->file = $file;
|
||||||
$this->type = $type;
|
$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;
|
protected $activity_repo;
|
||||||
|
|
||||||
|
public $delay = 5;
|
||||||
/**
|
/**
|
||||||
* Create the event listener.
|
* Create the event listener.
|
||||||
*
|
*
|
||||||
|
@ -21,6 +21,8 @@ class InvoicePaidActivity implements ShouldQueue
|
|||||||
{
|
{
|
||||||
protected $activity_repo;
|
protected $activity_repo;
|
||||||
|
|
||||||
|
public $delay = 10;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the event listener.
|
* Create the event listener.
|
||||||
*
|
*
|
||||||
|
@ -11,14 +11,16 @@
|
|||||||
|
|
||||||
namespace App\Listeners\Payment;
|
namespace App\Listeners\Payment;
|
||||||
|
|
||||||
|
use App\Utils\Ninja;
|
||||||
|
use App\Libraries\MultiDB;
|
||||||
use App\Jobs\Mail\NinjaMailer;
|
use App\Jobs\Mail\NinjaMailer;
|
||||||
use App\Jobs\Mail\NinjaMailerJob;
|
use App\Jobs\Mail\NinjaMailerJob;
|
||||||
use App\Jobs\Mail\NinjaMailerObject;
|
use App\Jobs\Mail\NinjaMailerObject;
|
||||||
use App\Libraries\MultiDB;
|
|
||||||
use App\Mail\Admin\EntityPaidObject;
|
use App\Mail\Admin\EntityPaidObject;
|
||||||
use App\Utils\Ninja;
|
use Turbo124\Beacon\Facades\LightLogs;
|
||||||
use App\Utils\Traits\Notifications\UserNotifies;
|
use App\DataMapper\Analytics\RevenueTrack;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use App\Utils\Traits\Notifications\UserNotifies;
|
||||||
|
|
||||||
class PaymentNotification implements ShouldQueue
|
class PaymentNotification implements ShouldQueue
|
||||||
{
|
{
|
||||||
@ -158,6 +160,15 @@ class PaymentNotification implements ShouldQueue
|
|||||||
|
|
||||||
$url = $base."&t=item&in={$item}&ip={$amount}&iq=1";
|
$url = $base."&t=item&in={$item}&ip={$amount}&iq=1";
|
||||||
$this->sendAnalytics($url);
|
$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:['' => ''],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -707,6 +707,7 @@ class Company extends BaseModel
|
|||||||
|
|
||||||
public function getSetting($setting)
|
public function getSetting($setting)
|
||||||
{
|
{
|
||||||
|
//todo $this->setting ?? false
|
||||||
if (property_exists($this->settings, $setting) != false) {
|
if (property_exists($this->settings, $setting) != false) {
|
||||||
return $this->settings->{$setting};
|
return $this->settings->{$setting};
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ use Illuminate\Database\Eloquent\Model;
|
|||||||
* @property string $hash
|
* @property string $hash
|
||||||
* @property float $fee_total
|
* @property float $fee_total
|
||||||
* @property int|null $fee_invoice_id
|
* @property int|null $fee_invoice_id
|
||||||
* @property mixed $data
|
* @property \stdClass $data
|
||||||
* @property int|null $payment_id
|
* @property int|null $payment_id
|
||||||
* @property \Illuminate\Support\Carbon|null $created_at
|
* @property \Illuminate\Support\Carbon|null $created_at
|
||||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||||
@ -38,9 +38,16 @@ class PaymentHash extends Model
|
|||||||
'data' => 'object',
|
'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()
|
public function invoices()
|
||||||
{
|
{
|
||||||
@ -75,9 +82,8 @@ class PaymentHash extends Model
|
|||||||
|
|
||||||
public function withData(string $property, $value): self
|
public function withData(string $property, $value): self
|
||||||
{
|
{
|
||||||
$this->data = array_merge((array) $this->data, [$property => $value]);
|
$this->data = array_merge((array) $this->data, [$property => $value]); // @phpstan-ignore-line
|
||||||
$this->save();
|
$this->save();// @phpstan-ignore-line
|
||||||
|
return $this; // @phpstan-ignore-line
|
||||||
return $this;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -230,9 +230,9 @@ class Vendor extends BaseModel
|
|||||||
* Returns a vendor settings proxying company setting
|
* Returns a vendor settings proxying company setting
|
||||||
*
|
*
|
||||||
* @param string $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)) {
|
if ((property_exists($this->company->settings, $setting) != false) && (isset($this->company->settings->{$setting}) !== false)) {
|
||||||
return $this->company->settings->{$setting};
|
return $this->company->settings->{$setting};
|
||||||
|
@ -69,7 +69,7 @@ class CheckoutWebhook implements ShouldQueue
|
|||||||
{
|
{
|
||||||
$payment_object = $this->webhook_array['data'];
|
$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)
|
if($payment && $payment->status_id == Payment::STATUS_COMPLETED)
|
||||||
return;
|
return;
|
||||||
@ -84,18 +84,19 @@ class CheckoutWebhook implements ShouldQueue
|
|||||||
|
|
||||||
$metadata = $this->webhook_array['metadata'];
|
$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();
|
$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->data = array_merge((array) $payment_hash->data, $this->webhook_array); // @phpstan-ignore-line
|
||||||
$payment_hash->save();
|
$payment_hash->save();
|
||||||
$driver->setPaymentHash($payment_hash);
|
$driver->setPaymentHash($payment_hash);
|
||||||
|
|
||||||
|
// @phpstan-ignore-line
|
||||||
$data = [
|
$data = [
|
||||||
'payment_method' => isset($this->webhook_array['source']['id']) ? $this->webhook_array['source']['id'] : '',
|
'payment_method' => isset($this->webhook_array['source']['id']) ? $this->webhook_array['source']['id'] : '',
|
||||||
'payment_type' => PaymentType::CREDIT_CARD_OTHER,
|
'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'],
|
'transaction_reference' => $payment_object['id'],
|
||||||
'gateway_type_id' => GatewayType::CREDIT_CARD,
|
'gateway_type_id' => GatewayType::CREDIT_CARD,
|
||||||
];
|
];
|
||||||
|
@ -66,8 +66,6 @@ class Webhook
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Lists the workflows in Checkout
|
* Lists the workflows in Checkout
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
public function getWorkFlows()
|
public function getWorkFlows()
|
||||||
{
|
{
|
||||||
|
@ -94,6 +94,7 @@ use App\Events\Misc\InvitationWasViewed;
|
|||||||
use App\Events\Payment\PaymentWasVoided;
|
use App\Events\Payment\PaymentWasVoided;
|
||||||
use App\Events\Vendor\VendorWasArchived;
|
use App\Events\Vendor\VendorWasArchived;
|
||||||
use App\Events\Vendor\VendorWasRestored;
|
use App\Events\Vendor\VendorWasRestored;
|
||||||
|
use App\Events\Account\StripeConnectFailure;
|
||||||
use App\Listeners\Mail\MailSentListener;
|
use App\Listeners\Mail\MailSentListener;
|
||||||
use App\Observers\ClientContactObserver;
|
use App\Observers\ClientContactObserver;
|
||||||
use App\Observers\PurchaseOrderObserver;
|
use App\Observers\PurchaseOrderObserver;
|
||||||
@ -198,6 +199,8 @@ use App\Listeners\Invoice\InvoiceRestoredActivity;
|
|||||||
use App\Listeners\Invoice\InvoiceReversedActivity;
|
use App\Listeners\Invoice\InvoiceReversedActivity;
|
||||||
use App\Listeners\Payment\PaymentRestoredActivity;
|
use App\Listeners\Payment\PaymentRestoredActivity;
|
||||||
use App\Listeners\Quote\QuoteApprovedNotification;
|
use App\Listeners\Quote\QuoteApprovedNotification;
|
||||||
|
use SocialiteProviders\Apple\AppleExtendSocialite;
|
||||||
|
use SocialiteProviders\Manager\SocialiteWasCalled;
|
||||||
use App\Events\Subscription\SubscriptionWasCreated;
|
use App\Events\Subscription\SubscriptionWasCreated;
|
||||||
use App\Events\Subscription\SubscriptionWasDeleted;
|
use App\Events\Subscription\SubscriptionWasDeleted;
|
||||||
use App\Events\Subscription\SubscriptionWasUpdated;
|
use App\Events\Subscription\SubscriptionWasUpdated;
|
||||||
@ -221,10 +224,12 @@ use App\Listeners\Invoice\InvoiceEmailFailedActivity;
|
|||||||
use App\Events\PurchaseOrder\PurchaseOrderWasAccepted;
|
use App\Events\PurchaseOrder\PurchaseOrderWasAccepted;
|
||||||
use App\Events\PurchaseOrder\PurchaseOrderWasArchived;
|
use App\Events\PurchaseOrder\PurchaseOrderWasArchived;
|
||||||
use App\Events\PurchaseOrder\PurchaseOrderWasRestored;
|
use App\Events\PurchaseOrder\PurchaseOrderWasRestored;
|
||||||
|
use App\Listeners\Payment\PaymentEmailFailureActivity;
|
||||||
use App\Listeners\Vendor\UpdateVendorContactLastLogin;
|
use App\Listeners\Vendor\UpdateVendorContactLastLogin;
|
||||||
use App\Events\RecurringQuote\RecurringQuoteWasCreated;
|
use App\Events\RecurringQuote\RecurringQuoteWasCreated;
|
||||||
use App\Events\RecurringQuote\RecurringQuoteWasDeleted;
|
use App\Events\RecurringQuote\RecurringQuoteWasDeleted;
|
||||||
use App\Events\RecurringQuote\RecurringQuoteWasUpdated;
|
use App\Events\RecurringQuote\RecurringQuoteWasUpdated;
|
||||||
|
use App\Listeners\Account\StripeConnectFailureListener;
|
||||||
use App\Listeners\Activity\CreatedSubscriptionActivity;
|
use App\Listeners\Activity\CreatedSubscriptionActivity;
|
||||||
use App\Listeners\Activity\SubscriptionDeletedActivity;
|
use App\Listeners\Activity\SubscriptionDeletedActivity;
|
||||||
use App\Listeners\Activity\SubscriptionUpdatedActivity;
|
use App\Listeners\Activity\SubscriptionUpdatedActivity;
|
||||||
@ -234,6 +239,7 @@ use App\Events\RecurringQuote\RecurringQuoteWasRestored;
|
|||||||
use App\Listeners\Activity\SubscriptionArchivedActivity;
|
use App\Listeners\Activity\SubscriptionArchivedActivity;
|
||||||
use App\Listeners\Activity\SubscriptionRestoredActivity;
|
use App\Listeners\Activity\SubscriptionRestoredActivity;
|
||||||
use App\Listeners\Invoice\InvoiceFailedEmailNotification;
|
use App\Listeners\Invoice\InvoiceFailedEmailNotification;
|
||||||
|
use SocialiteProviders\Microsoft\MicrosoftExtendSocialite;
|
||||||
use App\Events\RecurringExpense\RecurringExpenseWasCreated;
|
use App\Events\RecurringExpense\RecurringExpenseWasCreated;
|
||||||
use App\Events\RecurringExpense\RecurringExpenseWasDeleted;
|
use App\Events\RecurringExpense\RecurringExpenseWasDeleted;
|
||||||
use App\Events\RecurringExpense\RecurringExpenseWasUpdated;
|
use App\Events\RecurringExpense\RecurringExpenseWasUpdated;
|
||||||
@ -587,6 +593,9 @@ class EventServiceProvider extends ServiceProvider
|
|||||||
TaskWasRestored::class => [
|
TaskWasRestored::class => [
|
||||||
TaskRestoredActivity::class,
|
TaskRestoredActivity::class,
|
||||||
],
|
],
|
||||||
|
StripeConnectFailure::class => [
|
||||||
|
StripeConnectFailureListener::class,
|
||||||
|
],
|
||||||
SubscriptionWasCreated::class => [
|
SubscriptionWasCreated::class => [
|
||||||
CreatedSubscriptionActivity::class,
|
CreatedSubscriptionActivity::class,
|
||||||
],
|
],
|
||||||
|
@ -136,7 +136,11 @@ class InvoiceMigrationRepository extends BaseRepository
|
|||||||
|
|
||||||
$state['finished_amount'] = $model->amount;
|
$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 ($class->name == Invoice::class || $class->name == RecurringInvoice::class) {
|
||||||
if (($state['finished_amount'] != $state['starting_amount']) && ($model->status_id != Invoice::STATUS_DRAFT)) {
|
if (($state['finished_amount'] != $state['starting_amount']) && ($model->status_id != Invoice::STATUS_DRAFT)) {
|
||||||
|
@ -23,11 +23,11 @@ use horstoeko\zugferd\codelists\ZugferdDutyTaxFeeCategories;
|
|||||||
class ZugferdEInvoice extends AbstractService
|
class ZugferdEInvoice extends AbstractService
|
||||||
{
|
{
|
||||||
|
|
||||||
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(): string|ZugferdDocumentBuilder
|
||||||
{
|
{
|
||||||
|
|
||||||
$company = $this->invoice->company;
|
$company = $this->invoice->company;
|
||||||
@ -175,7 +175,9 @@ class ZugferdEInvoice extends AbstractService
|
|||||||
|
|
||||||
$xrechnung->writeFile(Storage::disk($disk)->path($client->e_invoice_filepath($this->invoice->invitations->first()) . $this->invoice->getFileName("xml")));
|
$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
|
// The validity can be checked using https://portal3.gefeg.com/invoice/validation or https://e-rechnung.bayern.de/app/#/upload
|
||||||
|
if ($this->returnObject){
|
||||||
|
return $xrechnung;
|
||||||
|
}
|
||||||
return $client->e_invoice_filepath($this->invoice->invitations->first()) . $this->invoice->getFileName("xml");
|
return $client->e_invoice_filepath($this->invoice->invitations->first()) . $this->invoice->getFileName("xml");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,9 +43,10 @@ class GetInvoiceEInvoice extends AbstractService
|
|||||||
$file = Storage::disk($disk)->exists($file_path);
|
$file = Storage::disk($disk)->exists($file_path);
|
||||||
|
|
||||||
if (! $file) {
|
if (! $file) {
|
||||||
$file_path = (new CreateEInvoice($this->invoice, false))->handle();
|
$file_path = (new CreateEInvoice($this->invoice))->handle();
|
||||||
}
|
(new \App\Jobs\Invoice\MergeEInvoice($this->invoice))->handle();
|
||||||
|
|
||||||
|
}
|
||||||
return $file_path;
|
return $file_path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
namespace App\Services\Invoice;
|
namespace App\Services\Invoice;
|
||||||
|
|
||||||
use App\Jobs\Entity\CreateEntityPdf;
|
use App\Jobs\Entity\CreateEntityPdf;
|
||||||
|
use App\Jobs\Invoice\CreateEInvoice;
|
||||||
|
use App\Jobs\Invoice\MergeEInvoice;
|
||||||
use App\Models\ClientContact;
|
use App\Models\ClientContact;
|
||||||
use App\Models\Invoice;
|
use App\Models\Invoice;
|
||||||
use App\Services\AbstractService;
|
use App\Services\AbstractService;
|
||||||
@ -47,7 +49,10 @@ class GetInvoicePdf extends AbstractService
|
|||||||
if (! $file) {
|
if (! $file) {
|
||||||
$file_path = (new CreateEntityPdf($invitation))->handle();
|
$file_path = (new CreateEntityPdf($invitation))->handle();
|
||||||
}
|
}
|
||||||
|
if ($this->invoice->client->getSetting('enable_e_invoice')){
|
||||||
|
(new CreateEInvoice($this->invoice))->handle();
|
||||||
|
(new MergeEInvoice($this->invoice, $file_path))->handle();
|
||||||
|
}
|
||||||
return $file_path;
|
return $file_path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -343,6 +343,7 @@ class InvoiceService
|
|||||||
})->toArray();
|
})->toArray();
|
||||||
|
|
||||||
$this->deletePdf();
|
$this->deletePdf();
|
||||||
|
$this->deleteEInvoice();
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@ -410,6 +411,7 @@ class InvoiceService
|
|||||||
|
|
||||||
$this->invoice = $this->invoice->calc()->getInvoice();
|
$this->invoice = $this->invoice->calc()->getInvoice();
|
||||||
$this->deletePdf();
|
$this->deletePdf();
|
||||||
|
$this->deleteEInvoice();
|
||||||
|
|
||||||
/* 24-03-2022 */
|
/* 24-03-2022 */
|
||||||
$new_balance = $this->invoice->balance;
|
$new_balance = $this->invoice->balance;
|
||||||
@ -465,7 +467,8 @@ class InvoiceService
|
|||||||
|
|
||||||
if ($invitation->invoice->client->getSetting('enable_e_invoice') && $invitation instanceof InvoiceInvitation)
|
if ($invitation->invoice->client->getSetting('enable_e_invoice') && $invitation instanceof InvoiceInvitation)
|
||||||
{
|
{
|
||||||
(new CreateEInvoice($invitation->invoice, true))->handle();
|
(new CreateEInvoice($invitation->invoice))->handle();
|
||||||
|
(new MergeEInvoice($invitation->invoice))->run();
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -478,7 +481,7 @@ class InvoiceService
|
|||||||
CreateEntityPdf::dispatch($invitation);
|
CreateEntityPdf::dispatch($invitation);
|
||||||
|
|
||||||
if ($invitation->invoice->client->getSetting('enable_e_invoice') && $invitation instanceof InvoiceInvitation) {
|
if ($invitation->invoice->client->getSetting('enable_e_invoice') && $invitation instanceof InvoiceInvitation) {
|
||||||
CreateEInvoice::dispatch($invitation->invoice, true);
|
CreateEInvoice::dispatch($invitation->invoice);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -4,58 +4,26 @@ namespace App\Services\Invoice;
|
|||||||
|
|
||||||
use App\Models\ClientContact;
|
use App\Models\ClientContact;
|
||||||
use App\Models\Invoice;
|
use App\Models\Invoice;
|
||||||
use App\Services\AbstractService;
|
|
||||||
use horstoeko\zugferd\ZugferdDocumentPdfBuilder;
|
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use horstoeko\zugferd\ZugferdDocumentReader;
|
|
||||||
|
|
||||||
class MergeEInvoice extends AbstractService
|
class MergeEInvoice
|
||||||
{
|
{
|
||||||
public function __construct(public Invoice $invoice, public ?ClientContact $contact = null)
|
|
||||||
|
/**
|
||||||
|
* @param Invoice $invoice
|
||||||
|
*/
|
||||||
|
public function __construct(public Invoice $invoice, public string $pdf_path = "")
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws \Exception
|
|
||||||
*/
|
|
||||||
public function run(): void
|
public function run(): void
|
||||||
{
|
{
|
||||||
$e_invoice_type = $this->invoice->client->getSetting('e_invoice_type');
|
if (!empty($this->pdf_path)) {
|
||||||
switch ($e_invoice_type) {
|
(new \App\Jobs\Invoice\MergeEInvoice($this->invoice, $this->pdf_path))->handle();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
else {
|
||||||
|
(new \App\Jobs\Invoice\MergeEInvoice($this->invoice))->handle();
|
||||||
/**
|
|
||||||
* @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->number = empty($expense->number) ? $this->getNextExpenseNumber($expense) : $expense->number;
|
||||||
|
|
||||||
$expense->save();
|
$expense->saveQuietly();
|
||||||
event('eloquent.created: App\Models\Expense', $expense);
|
event('eloquent.created: App\Models\Expense', $expense);
|
||||||
|
|
||||||
$this->purchase_order->expense_id = $expense->id;
|
$this->purchase_order->expense_id = $expense->id;
|
||||||
|
@ -20,11 +20,8 @@ class PurchaseOrderService
|
|||||||
{
|
{
|
||||||
use MakesHash;
|
use MakesHash;
|
||||||
|
|
||||||
public PurchaseOrder $purchase_order;
|
public function __construct(public PurchaseOrder $purchase_order)
|
||||||
|
|
||||||
public function __construct(PurchaseOrder $purchase_order)
|
|
||||||
{
|
{
|
||||||
$this->purchase_order = $purchase_order;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createInvitations()
|
public function createInvitations()
|
||||||
@ -156,7 +153,7 @@ class PurchaseOrderService
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves the purchase order.
|
* Saves the purchase order.
|
||||||
* @return \App\Models\PurchaseOrder object
|
* @return \App\Models\PurchaseOrder
|
||||||
*/
|
*/
|
||||||
public function save(): ?PurchaseOrder
|
public function save(): ?PurchaseOrder
|
||||||
{
|
{
|
||||||
|
@ -52,8 +52,8 @@ class TemplateEngine
|
|||||||
|
|
||||||
public $template;
|
public $template;
|
||||||
|
|
||||||
/** @var \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 $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 **/
|
/** @var \App\Models\Company | \App\Models\Client | null $settings_entity **/
|
||||||
private $settings_entity;
|
private $settings_entity;
|
||||||
|
743
composer.lock
generated
743
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -22,8 +22,8 @@ return [
|
|||||||
*/
|
*/
|
||||||
'batch' => true,
|
'batch' => true,
|
||||||
|
|
||||||
'cache_connection' => 'sentinel-cache',
|
'cache_connection' => 'sentinel-cache',
|
||||||
|
// 'cache_connection' => 'cache',
|
||||||
/*
|
/*
|
||||||
* The default key used to store
|
* The default key used to store
|
||||||
* metrics for batching
|
* metrics for batching
|
||||||
|
@ -15,8 +15,8 @@ return [
|
|||||||
'require_https' => env('REQUIRE_HTTPS', true),
|
'require_https' => env('REQUIRE_HTTPS', true),
|
||||||
'app_url' => rtrim(env('APP_URL', ''), '/'),
|
'app_url' => rtrim(env('APP_URL', ''), '/'),
|
||||||
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
|
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
|
||||||
'app_version' => env('APP_VERSION','5.6.30'),
|
'app_version' => env('APP_VERSION','5.6.31'),
|
||||||
'app_tag' => env('APP_TAG','5.6.30'),
|
'app_tag' => env('APP_TAG','5.6.31'),
|
||||||
'minimum_client_version' => '5.0.16',
|
'minimum_client_version' => '5.0.16',
|
||||||
'terms_version' => '1.0.1',
|
'terms_version' => '1.0.1',
|
||||||
'api_secret' => env('API_SECRET', ''),
|
'api_secret' => env('API_SECRET', ''),
|
||||||
|
@ -5143,6 +5143,7 @@ $LANG = array(
|
|||||||
'is_tax_exempt' => 'Tax Exempt',
|
'is_tax_exempt' => 'Tax Exempt',
|
||||||
'drop_files_here' => 'Drop files here',
|
'drop_files_here' => 'Drop files here',
|
||||||
'upload_files' => 'Upload Files',
|
'upload_files' => 'Upload Files',
|
||||||
|
'download_e_invoice' => 'Download E-Invoice',
|
||||||
'triangular_tax_info' => 'Intra-community triangular transaction',
|
'triangular_tax_info' => 'Intra-community triangular transaction',
|
||||||
'intracommunity_tax_info' => 'Tax-free intra-community delivery',
|
'intracommunity_tax_info' => 'Tax-free intra-community delivery',
|
||||||
'reverse_tax_info' => 'Please note that this supply is subject to reverse charge',
|
'reverse_tax_info' => 'Please note that this supply is subject to reverse charge',
|
||||||
|
@ -8,6 +8,7 @@ parameters:
|
|||||||
excludePaths:
|
excludePaths:
|
||||||
- 'vendor/'
|
- 'vendor/'
|
||||||
- 'app/Jobs/Ninja/*'
|
- 'app/Jobs/Ninja/*'
|
||||||
|
- 'app/Models/Presenters/*'
|
||||||
- 'app/Console/Commands/*'
|
- 'app/Console/Commands/*'
|
||||||
- 'app/DataMapper/Analytics/*'
|
- 'app/DataMapper/Analytics/*'
|
||||||
- 'app/PaymentDrivers/Authorize/*'
|
- 'app/PaymentDrivers/Authorize/*'
|
||||||
@ -17,10 +18,7 @@ parameters:
|
|||||||
- App\DataMapper\FeesAndLimits
|
- App\DataMapper\FeesAndLimits
|
||||||
reportUnmatchedIgnoredErrors: false
|
reportUnmatchedIgnoredErrors: false
|
||||||
ignoreErrors:
|
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#'
|
- '#Array has 2 duplicate keys with value#'
|
||||||
- '#Undefined method#'
|
- '#Call to an undefined method#'
|
||||||
- '#makeHidden#'
|
- '#makeHidden#'
|
||||||
- '#Socialite#'
|
- '#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 !!}
|
@ -9,6 +9,15 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
<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>
|
||||||
</div>
|
</div>
|
||||||
<div class="hidden lg:block">
|
<div class="hidden lg:block">
|
||||||
<div wire:init="getPdf()">
|
<div wire:init="getPdf()">
|
||||||
|
@ -16,7 +16,8 @@
|
|||||||
<form action="{{ route('client.invoices.bulk') }}" method="post" id="bulkActions">
|
<form action="{{ route('client.invoices.bulk') }}" method="post" id="bulkActions">
|
||||||
@csrf
|
@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>
|
<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
|
||||||
|
<button type="submit" onclick="setTimeout(() => this.disabled = true, 0); setTimeout(() => this.disabled = false, 5000); return true;" class="button button-primary bg-secondary" name="action-xml" value="download">{{ ctrans('texts.download_xml') }}</button>
|
||||||
@if(!empty(auth()->user()->client->service()->getPaymentMethods(0)))
|
@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>
|
<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
|
@endif
|
||||||
|
@ -42,9 +42,10 @@ class EInvoiceTest extends TestCase
|
|||||||
$this->company->e_invoice_type = "EN16931";
|
$this->company->e_invoice_type = "EN16931";
|
||||||
$this->invoice->client->routing_id = 'DE123456789';
|
$this->invoice->client->routing_id = 'DE123456789';
|
||||||
$this->invoice->client->save();
|
$this->invoice->client->save();
|
||||||
$xinvoice = (new CreateEInvoice($this->invoice, false))->handle();
|
$e_invoice = (new CreateEInvoice($this->invoice))->handle();
|
||||||
$this->assertNotNull($xinvoice);
|
(new \App\Jobs\Invoice\MergeEInvoice($this->invoice))->handle();
|
||||||
$this->assertTrue(Storage::exists($xinvoice));
|
$this->assertNotNull($e_invoice);
|
||||||
|
$this->assertTrue(Storage::exists($e_invoice));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -56,9 +57,8 @@ class EInvoiceTest extends TestCase
|
|||||||
$this->invoice->client->routing_id = 'DE123456789';
|
$this->invoice->client->routing_id = 'DE123456789';
|
||||||
$this->invoice->client->save();
|
$this->invoice->client->save();
|
||||||
|
|
||||||
$xinvoice = (new CreateEInvoice($this->invoice, false))->handle();
|
$e_invoice = (new CreateEInvoice($this->invoice))->handle();
|
||||||
nlog(Storage::path($xinvoice));
|
$document = ZugferdDocumentReader::readAndGuessFromFile(Storage::path($e_invoice));
|
||||||
$document = ZugferdDocumentReader::readAndGuessFromFile(Storage::path($xinvoice));
|
|
||||||
$document->getDocumentInformation($documentno, $documenttypecode, $documentdate, $documentcurrency, $taxcurrency, $taxname, $documentlangeuage, $rest);
|
$document->getDocumentInformation($documentno, $documenttypecode, $documentdate, $documentcurrency, $taxcurrency, $taxname, $documentlangeuage, $rest);
|
||||||
$this->assertEquals($this->invoice->number, $documentno);
|
$this->assertEquals($this->invoice->number, $documentno);
|
||||||
}
|
}
|
||||||
@ -69,7 +69,8 @@ class EInvoiceTest extends TestCase
|
|||||||
public function checkEmbededPDFFile()
|
public function checkEmbededPDFFile()
|
||||||
{
|
{
|
||||||
$pdf = (new CreateEntityPdf($this->invoice->invitations()->first()))->handle();
|
$pdf = (new CreateEntityPdf($this->invoice->invitations()->first()))->handle();
|
||||||
(new CreateEInvoice($this->invoice, true, $pdf))->handle();
|
(new CreateEInvoice($this->invoice))->handle();
|
||||||
|
(new \App\Jobs\Invoice\MergeEInvoice($this->invoice))->handle();
|
||||||
$document = ZugferdDocumentReader::readAndGuessFromFile($pdf);
|
$document = ZugferdDocumentReader::readAndGuessFromFile($pdf);
|
||||||
$document->getDocumentInformation($documentno, $documenttypecode, $documentdate, $documentcurrency, $taxcurrency, $taxname, $documentlangeuage, $rest);
|
$document->getDocumentInformation($documentno, $documenttypecode, $documentdate, $documentcurrency, $taxcurrency, $taxname, $documentlangeuage, $rest);
|
||||||
$this->assertEquals($this->invoice->number, $documentno);
|
$this->assertEquals($this->invoice->number, $documentno);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user