mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-13 22:54:25 +01:00
Merge branch 'v5-develop' of https://github.com/invoiceninja/invoiceninja into feature-inbound-email-expenses
This commit is contained in:
commit
ad964ca61a
@ -479,6 +479,8 @@ class CompanySettings extends BaseSettings
|
||||
|
||||
public $e_invoice_type = 'EN16931';
|
||||
|
||||
public $e_quote_type = 'OrderX_Comfort';
|
||||
|
||||
public $default_expense_payment_type_id = '0';
|
||||
|
||||
public $enable_e_invoice = false;
|
||||
@ -502,6 +504,7 @@ class CompanySettings extends BaseSettings
|
||||
public $enable_rappen_rounding = false;
|
||||
|
||||
public static $casts = [
|
||||
'e_quote_type' => 'string',
|
||||
'enable_rappen_rounding' => 'bool',
|
||||
'use_unapplied_payment' => 'string',
|
||||
'show_pdfhtml_on_mobile' => 'bool',
|
||||
|
@ -427,8 +427,11 @@ class BaseExport
|
||||
|
||||
protected array $task_report_keys = [
|
||||
'start_date' => 'task.start_date',
|
||||
'start_time' => 'task.start_time',
|
||||
'end_date' => 'task.end_date',
|
||||
'end_time' => 'task.end_time',
|
||||
'duration' => 'task.duration',
|
||||
'duration_words' => 'task.duration_words',
|
||||
'rate' => 'task.rate',
|
||||
'number' => 'task.number',
|
||||
'description' => 'task.description',
|
||||
|
@ -11,18 +11,19 @@
|
||||
|
||||
namespace App\Export\CSV;
|
||||
|
||||
use App\Export\Decorators\Decorator;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Company;
|
||||
use App\Models\DateFormat;
|
||||
use App\Models\Task;
|
||||
use App\Models\Timezone;
|
||||
use App\Transformers\TaskTransformer;
|
||||
use App\Utils\Ninja;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use League\Csv\Writer;
|
||||
use App\Models\Company;
|
||||
use App\Models\Timezone;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\DateFormat;
|
||||
use Carbon\CarbonInterval;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use League\Csv\Writer;
|
||||
use App\Export\Decorators\Decorator;
|
||||
use App\Transformers\TaskTransformer;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class TaskExport extends BaseExport
|
||||
{
|
||||
@ -177,19 +178,26 @@ class TaskExport extends BaseExport
|
||||
|
||||
foreach ($logs as $key => $item) {
|
||||
if (in_array('task.start_date', $this->input['report_keys']) || in_array('start_date', $this->input['report_keys'])) {
|
||||
$entity['task.start_date'] = Carbon::createFromTimeStamp($item[0])->setTimezone($timezone_name)->format($date_format_default);
|
||||
$carbon_object = Carbon::createFromTimeStamp($item[0])->setTimezone($timezone_name);
|
||||
$entity['task.start_date'] = $carbon_object->format($date_format_default);
|
||||
$entity['task.start_time'] = $carbon_object->format('H:i:s');
|
||||
}
|
||||
|
||||
if ((in_array('task.end_date', $this->input['report_keys']) || in_array('end_date', $this->input['report_keys'])) && $item[1] > 0) {
|
||||
$entity['task.end_date'] = Carbon::createFromTimeStamp($item[1])->setTimezone($timezone_name)->format($date_format_default);
|
||||
$carbon_object = Carbon::createFromTimeStamp($item[1])->setTimezone($timezone_name);
|
||||
$entity['task.end_date'] = $carbon_object->format($date_format_default);
|
||||
$entity['task.end_time'] = $carbon_object->format('H:i:s');
|
||||
}
|
||||
|
||||
if ((in_array('task.end_date', $this->input['report_keys']) || in_array('end_date', $this->input['report_keys'])) && $item[1] == 0) {
|
||||
$entity['task.end_date'] = ctrans('texts.is_running');
|
||||
$entity['task.end_time'] = ctrans('texts.is_running');
|
||||
}
|
||||
|
||||
if (in_array('task.duration', $this->input['report_keys']) || in_array('duration', $this->input['report_keys'])) {
|
||||
$entity['task.duration'] = $task->calcDuration();
|
||||
$seconds = $task->calcDuration();
|
||||
$entity['task.duration'] = $seconds;
|
||||
$entity['task.duration_words'] = CarbonInterval::seconds($seconds)->locale($this->company->locale())->cascade()->forHumans();
|
||||
}
|
||||
|
||||
$entity = $this->decorateAdvancedFields($task, $entity);
|
||||
@ -197,8 +205,12 @@ class TaskExport extends BaseExport
|
||||
$this->storage_array[] = $entity;
|
||||
|
||||
$entity['task.start_date'] = '';
|
||||
$entity['task.start_time'] = '';
|
||||
$entity['task.end_date'] = '';
|
||||
$entity['task.end_time'] = '';
|
||||
$entity['task.duration'] = '';
|
||||
$entity['task.duration_words'] = '';
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ class InvoiceDecorator extends Decorator implements DecoratorInterface
|
||||
$invoice = $entity;
|
||||
} elseif($entity->invoice) {
|
||||
$invoice = $entity->invoice;
|
||||
} elseif($entity->invoices()->exists()) {
|
||||
} elseif(method_exists($entity, 'invoices') && $entity->invoices()->exists()) {
|
||||
$invoice = $entity->invoices()->first();
|
||||
}
|
||||
|
||||
|
@ -200,14 +200,16 @@ class InvoiceFilters extends QueryFilters
|
||||
*/
|
||||
public function payable(string $client_id = ''): Builder
|
||||
{
|
||||
|
||||
if (strlen($client_id) == 0) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
return $this->builder->whereIn('status_id', [Invoice::STATUS_DRAFT, Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||
->where('balance', '>', 0)
|
||||
->where('is_deleted', 0)
|
||||
->where('client_id', $this->decodePrimaryKey($client_id));
|
||||
return $this->builder
|
||||
->where('client_id', $this->decodePrimaryKey($client_id))
|
||||
->whereIn('status_id', [Invoice::STATUS_DRAFT, Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||
->where('is_deleted', 0)
|
||||
->where('balance', '>', 0);
|
||||
}
|
||||
|
||||
|
||||
|
@ -246,8 +246,6 @@ class InvoiceSum
|
||||
|
||||
if ($this->invoice->status_id != Invoice::STATUS_DRAFT) {
|
||||
if ($this->invoice->amount != $this->invoice->balance) {
|
||||
// $paid_to_date = $this->invoice->amount - $this->invoice->balance;
|
||||
|
||||
$this->invoice->balance = Number::roundValue($this->getTotal(), $this->precision) - $this->invoice->paid_to_date; //21-02-2024 cannot use the calculated $paid_to_date here as it could send the balance backward.
|
||||
} else {
|
||||
$this->invoice->balance = Number::roundValue($this->getTotal(), $this->precision);
|
||||
@ -256,8 +254,10 @@ class InvoiceSum
|
||||
/* Set new calculated total */
|
||||
$this->invoice->amount = $this->formatValue($this->getTotal(), $this->precision);
|
||||
|
||||
if($this->rappen_rounding)
|
||||
if($this->rappen_rounding){
|
||||
$this->invoice->amount = $this->roundRappen($this->invoice->amount);
|
||||
$this->invoice->balance = $this->roundRappen($this->invoice->balance);
|
||||
}
|
||||
|
||||
$this->invoice->total_taxes = $this->getTotalTaxes();
|
||||
|
||||
|
@ -272,11 +272,11 @@ class InvoiceSumInclusive
|
||||
}
|
||||
|
||||
/* Set new calculated total */
|
||||
/** @todo - rappen rounding here */
|
||||
$this->invoice->amount = $this->formatValue($this->getTotal(), $this->precision);
|
||||
|
||||
if($this->rappen_rounding) {
|
||||
$this->invoice->amount = $this->roundRappen($this->invoice->amount);
|
||||
$this->invoice->balance = $this->roundRappen($this->invoice->balance);
|
||||
}
|
||||
|
||||
$this->invoice->total_taxes = $this->getTotalTaxes();
|
||||
|
@ -43,7 +43,7 @@ class ContactLoginController extends Controller
|
||||
|
||||
if ($request->session()->has('company_key')) {
|
||||
MultiDB::findAndSetDbByCompanyKey($request->session()->get('company_key'));
|
||||
$company = Company::where('company_key', $request->input('company_key'))->first();
|
||||
$company = Company::where('company_key', $request->session()->get('company_key'))->first();
|
||||
} elseif ($request->has('company_key')) {
|
||||
MultiDB::findAndSetDbByCompanyKey($request->input('company_key'));
|
||||
$company = Company::where('company_key', $request->input('company_key'))->first();
|
||||
|
@ -390,13 +390,20 @@ class LoginController extends BaseController
|
||||
$truth->setUser($user);
|
||||
$truth->setCompany($set_company);
|
||||
|
||||
$user->account->companies->each(function ($company) use ($user) {
|
||||
if ($company->tokens()->where('is_system', true)->count() == 0) {
|
||||
(new CreateCompanyToken($company, $user, request()->server('HTTP_USER_AGENT')))->handle();
|
||||
//21-03-2024
|
||||
$cu->each(function ($cu){
|
||||
if(CompanyToken::where('company_id', $cu->company_id)->where('user_id', $cu->user_id)->where('is_system', true)->doesntExist()){
|
||||
(new CreateCompanyToken($cu->company, $cu->user, request()->server('HTTP_USER_AGENT')))->handle();
|
||||
}
|
||||
});
|
||||
|
||||
$truth->setCompanyToken(CompanyToken::where('user_id', $user->id)->where('company_id', $set_company->id)->first());
|
||||
// $user->account->companies->each(function ($company) use ($user) {
|
||||
// if ($company->tokens()->where('user_id',$user->id)->where('is_system', true)->count() == 0) {
|
||||
// (new CreateCompanyToken($company, $user, request()->server('HTTP_USER_AGENT')))->handle();
|
||||
// }
|
||||
// });
|
||||
|
||||
$truth->setCompanyToken(CompanyToken::where('user_id', $user->id)->where('company_id', $set_company->id)->where('is_system', true)->first());
|
||||
|
||||
return CompanyUser::query()->where('user_id', $user->id);
|
||||
}
|
||||
|
@ -90,14 +90,13 @@ class YodleeController extends BaseController
|
||||
$bank_integration->balance = $account['current_balance'];
|
||||
$bank_integration->currency = $account['account_currency'];
|
||||
$bank_integration->from_date = now()->subYear();
|
||||
|
||||
$bank_integration->integration_type = BankIntegration::INTEGRATION_TYPE_YODLEE;
|
||||
$bank_integration->auto_sync = true;
|
||||
|
||||
$bank_integration->save();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$company->account->bank_integrations->where("integration_type", BankIntegration::INTEGRATION_TYPE_YODLEE)->where('auto_sync', true)->each(function ($bank_integration) use ($company) { // TODO: filter to yodlee only
|
||||
ProcessBankTransactionsYodlee::dispatch($company->account->id, $bank_integration);
|
||||
});
|
||||
|
@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
@ -12,17 +13,20 @@
|
||||
namespace App\Http\Controllers\ClientPortal;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\View\View;
|
||||
use App\Models\Invoice;
|
||||
|
||||
class DashboardController extends Controller
|
||||
{
|
||||
/**
|
||||
* @return Factory|View
|
||||
*/
|
||||
public function index()
|
||||
public function index(): \Illuminate\View\View
|
||||
{
|
||||
return redirect()->route('client.invoices.index');
|
||||
//return $this->render('dashboard.index');
|
||||
$total_invoices = Invoice::withTrashed()
|
||||
->where('client_id', auth()->guard('contact')->user()->client_id)
|
||||
->where('is_deleted', 0)
|
||||
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL, Invoice::STATUS_PAID])
|
||||
->sum('amount');
|
||||
|
||||
return $this->render('dashboard.index', [
|
||||
'total_invoices' => $total_invoices,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -727,6 +727,74 @@ class CreditController extends BaseController
|
||||
}, $credit->numberFormatter() . '.pdf', $headers);
|
||||
|
||||
}
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/api/v1/credit/{invitation_key}/download_e_credit",
|
||||
* operationId="downloadXcredit",
|
||||
* tags={"credit"},
|
||||
* summary="Download a specific x-credit by invitation key",
|
||||
* description="Downloads a specific x-credit",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/include"),
|
||||
* @OA\Parameter(
|
||||
* name="invitation_key",
|
||||
* in="path",
|
||||
* description="The credit Invitation Key",
|
||||
* example="D2J234DFA",
|
||||
* required=true,
|
||||
* @OA\Schema(
|
||||
* type="string",
|
||||
* format="string",
|
||||
* ),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Returns the x-credit pdf",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
*
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
* @param $invitation_key
|
||||
* @return \Symfony\Component\HttpFoundation\BinaryFileResponse
|
||||
*/
|
||||
public function downloadECredit($invitation_key)
|
||||
{
|
||||
$invitation = $this->credit_repository->getInvitationByKey($invitation_key);
|
||||
|
||||
if (! $invitation) {
|
||||
return response()->json(['message' => 'no record found'], 400);
|
||||
}
|
||||
|
||||
$contact = $invitation->contact;
|
||||
$credit = $invitation->credit;
|
||||
|
||||
$file = $credit->service()->getEInvoice($contact);
|
||||
$file_name = $credit->getFileName("xml");
|
||||
|
||||
$headers = ['Content-Type' => 'application/xml'];
|
||||
|
||||
if (request()->input('inline') == 'true') {
|
||||
$headers = array_merge($headers, ['Content-Disposition' => 'inline']);
|
||||
}
|
||||
|
||||
return response()->streamDownload(function () use ($file) {
|
||||
echo $file;
|
||||
}, $file_name, $headers);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
|
@ -851,4 +851,71 @@ class PurchaseOrderController extends BaseController
|
||||
echo $file;
|
||||
}, $purchase_order->numberFormatter().".pdf", $headers);
|
||||
}
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/api/v1/credit/{invitation_key}/download_e_purchase_order",
|
||||
* operationId="downloadEPurchaseOrder",
|
||||
* tags={"purchase_orders"},
|
||||
* summary="Download a specific E-Purchase-Order by invitation key",
|
||||
* description="Downloads a specific E-Purchase-Order",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/include"),
|
||||
* @OA\Parameter(
|
||||
* name="invitation_key",
|
||||
* in="path",
|
||||
* description="The E-Purchase-Order Invitation Key",
|
||||
* example="D2J234DFA",
|
||||
* required=true,
|
||||
* @OA\Schema(
|
||||
* type="string",
|
||||
* format="string",
|
||||
* ),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Returns the E-Purchase-Order pdf",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
*
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
* @param $invitation_key
|
||||
* @return \Symfony\Component\HttpFoundation\BinaryFileResponse
|
||||
*/
|
||||
public function downloadEPurchaseOrder($invitation_key)
|
||||
{
|
||||
$invitation = $this->purchase_order_repository->getInvitationByKey($invitation_key);
|
||||
|
||||
if (! $invitation) {
|
||||
return response()->json(['message' => 'no record found'], 400);
|
||||
}
|
||||
|
||||
$contact = $invitation->contact;
|
||||
$purchase_order = $invitation->purchase_order;
|
||||
|
||||
$file = $purchase_order->service()->getEPurchaseOrder($contact);
|
||||
$file_name = $purchase_order->getFileName("xml");
|
||||
|
||||
$headers = ['Content-Type' => 'application/xml'];
|
||||
|
||||
if (request()->input('inline') == 'true') {
|
||||
$headers = array_merge($headers, ['Content-Disposition' => 'inline']);
|
||||
}
|
||||
|
||||
return response()->streamDownload(function () use ($file) {
|
||||
echo $file;
|
||||
}, $file_name, $headers);
|
||||
}
|
||||
}
|
||||
|
@ -860,6 +860,75 @@ class QuoteController extends BaseController
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/api/v1/invoice/{invitation_key}/download_e_quote",
|
||||
* operationId="downloadXQuote",
|
||||
* tags={"quotes"},
|
||||
* summary="Download a specific x-quote by invitation key",
|
||||
* description="Downloads a specific x-quote",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-API-TOKEN"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/include"),
|
||||
* @OA\Parameter(
|
||||
* name="invitation_key",
|
||||
* in="path",
|
||||
* description="The Quote Invitation Key",
|
||||
* example="D2J234DFA",
|
||||
* required=true,
|
||||
* @OA\Schema(
|
||||
* type="string",
|
||||
* format="string",
|
||||
* ),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Returns the x-quote pdf",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
*
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
* @param $invitation_key
|
||||
* @return \Symfony\Component\HttpFoundation\BinaryFileResponse
|
||||
*/
|
||||
public function downloadEQuote($invitation_key)
|
||||
{
|
||||
$invitation = $this->quote_repo->getInvitationByKey($invitation_key);
|
||||
|
||||
if (! $invitation) {
|
||||
return response()->json(['message' => 'no record found'], 400);
|
||||
}
|
||||
|
||||
$contact = $invitation->contact;
|
||||
$quote = $invitation->quote;
|
||||
|
||||
$file = $quote->service()->getEInvoice($contact);
|
||||
$file_name = $quote->getFileName("xml");
|
||||
|
||||
$headers = ['Content-Type' => 'application/xml'];
|
||||
|
||||
if (request()->input('inline') == 'true') {
|
||||
$headers = array_merge($headers, ['Content-Disposition' => 'inline']);
|
||||
}
|
||||
|
||||
return response()->streamDownload(function () use ($file) {
|
||||
echo $file;
|
||||
}, $file_name, $headers);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
|
@ -260,8 +260,8 @@ class UserController extends BaseController
|
||||
/** @var \App\Models\User $logged_in_user */
|
||||
$logged_in_user = auth()->user();
|
||||
|
||||
$company_user = CompanyUser::whereUserId($user->id)
|
||||
->whereCompanyId($logged_in_user->companyId())
|
||||
$company_user = CompanyUser::where('user_id',$user->id)
|
||||
->where('company_id', $logged_in_user->companyId())
|
||||
->withTrashed()
|
||||
->first();
|
||||
|
||||
@ -269,14 +269,10 @@ class UserController extends BaseController
|
||||
return response()->json(['message', 'Cannot detach owner.'], 401);
|
||||
}
|
||||
|
||||
$token = $company_user->token->where('company_id', $company_user->company_id)->where('user_id', $company_user->user_id)->first();
|
||||
|
||||
if ($token) {
|
||||
$token->delete();
|
||||
}
|
||||
$company_user->tokens()->where('company_id', $company_user->company_id)->where('user_id', $company_user->user_id)->forceDelete();
|
||||
|
||||
if ($company_user) {
|
||||
$company_user->delete();
|
||||
$company_user->forceDelete();
|
||||
}
|
||||
|
||||
return response()->json(['message' => ctrans('texts.user_detached')], 200);
|
||||
|
@ -57,8 +57,6 @@ class RefundPaymentRequest extends Request
|
||||
|
||||
if (isset($input['credits'])) {
|
||||
unset($input['credits']);
|
||||
// foreach($input['credits'] as $key => $credit)
|
||||
// $input['credits'][$key]['credit_id'] = $this->decodePrimaryKey($credit['credit_id']);
|
||||
}
|
||||
|
||||
$this->replace($input);
|
||||
|
@ -16,7 +16,6 @@ use App\Http\ValidationRules\Credit\CreditsSumRule;
|
||||
use App\Http\ValidationRules\Credit\ValidCreditsRules;
|
||||
use App\Http\ValidationRules\Payment\ValidInvoicesRules;
|
||||
use App\Http\ValidationRules\PaymentAmountsBalanceRule;
|
||||
use App\Http\ValidationRules\ValidCreditsPresentRule;
|
||||
use App\Http\ValidationRules\ValidPayableInvoicesRule;
|
||||
use App\Models\Payment;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
@ -39,6 +38,41 @@ class StorePaymentRequest extends Request
|
||||
return $user->can('create', Payment::class);
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
$rules = [
|
||||
'client_id' => ['bail','required',Rule::exists('clients','id')->where('company_id',$user->company()->id)->where('is_deleted', 0)],
|
||||
'amount' => ['bail', 'numeric', new PaymentAmountsBalanceRule()],
|
||||
'invoices.*.amount' => ['bail','required'],
|
||||
'invoices.*.invoice_id' => ['bail','required','distinct', new ValidInvoicesRules($this->all()),Rule::exists('invoices','id')->where('company_id', $user->company()->id)->where('client_id', $this->client_id)],
|
||||
'credits.*.credit_id' => ['bail','required','distinct', new ValidCreditsRules($this->all()),Rule::exists('credits','id')->where('company_id', $user->company()->id)->where('client_id', $this->client_id)],
|
||||
'credits.*.amount' => ['bail','required', new CreditsSumRule($this->all())],
|
||||
'invoices' => ['bail','sometimes', 'nullable', 'array', new ValidPayableInvoicesRule()],
|
||||
'number' => ['bail', 'nullable', Rule::unique('payments')->where('company_id', $user->company()->id)],
|
||||
'idempotency_key' => ['nullable', 'bail', 'string','max:64', Rule::unique('payments')->where('company_id', $user->company()->id)],
|
||||
];
|
||||
|
||||
if ($this->file('documents') && is_array($this->file('documents'))) {
|
||||
$rules['documents.*'] = $this->fileValidation();
|
||||
} elseif ($this->file('documents')) {
|
||||
$rules['documents'] = $this->fileValidation();
|
||||
}else {
|
||||
$rules['documents'] = 'bail|sometimes|array';
|
||||
}
|
||||
|
||||
if ($this->file('file') && is_array($this->file('file'))) {
|
||||
$rules['file.*'] = $this->fileValidation();
|
||||
} elseif ($this->file('file')) {
|
||||
$rules['file'] = $this->fileValidation();
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
|
||||
public function prepareForValidation()
|
||||
{
|
||||
|
||||
@ -78,7 +112,6 @@ class StorePaymentRequest extends Request
|
||||
foreach ($input['credits'] as $key => $value) {
|
||||
if (array_key_exists('credit_id', $input['credits'][$key])) {
|
||||
$input['credits'][$key]['credit_id'] = $this->decodePrimaryKey($value['credit_id']);
|
||||
|
||||
$credits_total += $value['amount'];
|
||||
}
|
||||
}
|
||||
@ -103,39 +136,5 @@ class StorePaymentRequest extends Request
|
||||
$this->replace($input);
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
$rules = [
|
||||
'amount' => ['numeric', 'bail', new PaymentAmountsBalanceRule(), new ValidCreditsPresentRule($this->all())],
|
||||
'client_id' => 'bail|required|exists:clients,id,company_id,'.$user->company()->id.',is_deleted,0',
|
||||
'invoices.*.invoice_id' => 'bail|required|distinct|exists:invoices,id',
|
||||
'invoices.*.amount' => 'bail|required',
|
||||
'invoices.*.invoice_id' => new ValidInvoicesRules($this->all()),
|
||||
'credits.*.credit_id' => 'bail|required|exists:credits,id',
|
||||
'credits.*.credit_id' => new ValidCreditsRules($this->all()),
|
||||
'credits.*.amount' => ['bail','required', new CreditsSumRule($this->all())],
|
||||
'invoices' => new ValidPayableInvoicesRule(),
|
||||
'number' => ['nullable', 'bail', Rule::unique('payments')->where('company_id', $user->company()->id)],
|
||||
'idempotency_key' => ['nullable', 'bail', 'string','max:64', Rule::unique('payments')->where('company_id', $user->company()->id)],
|
||||
];
|
||||
|
||||
if ($this->file('documents') && is_array($this->file('documents'))) {
|
||||
$rules['documents.*'] = $this->fileValidation();
|
||||
} elseif ($this->file('documents')) {
|
||||
$rules['documents'] = $this->fileValidation();
|
||||
}else {
|
||||
$rules['documents'] = 'bail|sometimes|array';
|
||||
}
|
||||
|
||||
if ($this->file('file') && is_array($this->file('file'))) {
|
||||
$rules['file.*'] = $this->fileValidation();
|
||||
} elseif ($this->file('file')) {
|
||||
$rules['file'] = $this->fileValidation();
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,6 @@ namespace App\Http\Requests\Payment;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use App\Http\ValidationRules\PaymentAppliedValidAmount;
|
||||
use App\Http\ValidationRules\ValidCreditsPresentRule;
|
||||
use App\Utils\Traits\ChecksEntityStatus;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Validation\Rule;
|
||||
@ -41,16 +40,17 @@ class UpdatePaymentRequest extends Request
|
||||
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
|
||||
$rules = [
|
||||
'invoices' => ['array', new PaymentAppliedValidAmount($this->all()), new ValidCreditsPresentRule($this->all())],
|
||||
'invoices.*.invoice_id' => 'distinct',
|
||||
'client_id' => ['sometimes', 'bail', Rule::in([$this->payment->client_id])],
|
||||
'number' => ['sometimes', 'bail', Rule::unique('payments')->where('company_id', $user->company()->id)->ignore($this->payment->id)],
|
||||
'invoices' => ['sometimes', 'bail', 'nullable', 'array', new PaymentAppliedValidAmount($this->all())],
|
||||
'invoices.*.invoice_id' => ['sometimes','distinct',Rule::exists('invoices','id')->where('company_id', $user->company()->id)->where('client_id', $this->client_id)],
|
||||
'invoices.*.amount' => ['sometimes','numeric','min:0'],
|
||||
'credits.*.credit_id' => ['sometimes','bail','distinct',Rule::exists('credits','id')->where('company_id', $user->company()->id)->where('client_id', $this->client_id)],
|
||||
'credits.*.amount' => ['required', 'bail'],
|
||||
];
|
||||
|
||||
if ($this->number) {
|
||||
$rules['number'] = Rule::unique('payments')->where('company_id', $user->company()->id)->ignore($this->payment->id);
|
||||
}
|
||||
|
||||
if ($this->file('documents') && is_array($this->file('documents'))) {
|
||||
$rules['documents.*'] = $this->fileValidation();
|
||||
} elseif ($this->file('documents')) {
|
||||
@ -74,10 +74,6 @@ class UpdatePaymentRequest extends Request
|
||||
|
||||
$input = $this->decodePrimaryKeys($input);
|
||||
|
||||
if (isset($input['client_id'])) {
|
||||
unset($input['client_id']);
|
||||
}
|
||||
|
||||
if (isset($input['amount'])) {
|
||||
unset($input['amount']);
|
||||
}
|
||||
@ -85,7 +81,6 @@ class UpdatePaymentRequest extends Request
|
||||
if (isset($input['invoices']) && is_array($input['invoices']) !== false) {
|
||||
foreach ($input['invoices'] as $key => $value) {
|
||||
if(isset($input['invoices'][$key]['invoice_id'])) {
|
||||
// if (array_key_exists('invoice_id', $input['invoices'][$key])) {
|
||||
$input['invoices'][$key]['invoice_id'] = $this->decodePrimaryKey($value['invoice_id']);
|
||||
}
|
||||
}
|
||||
@ -93,7 +88,6 @@ class UpdatePaymentRequest extends Request
|
||||
|
||||
if (isset($input['credits']) && is_array($input['credits']) !== false) {
|
||||
foreach ($input['credits'] as $key => $value) {
|
||||
// if (array_key_exists('credits', $input['credits'][$key])) {
|
||||
if (isset($input['credits'][$key]['credit_id'])) {
|
||||
$input['credits'][$key]['credit_id'] = $this->decodePrimaryKey($value['credit_id']);
|
||||
}
|
||||
|
@ -57,9 +57,6 @@ class ValidRefundableRequest implements Rule
|
||||
|
||||
if ($payment->invoices()->exists()) {
|
||||
$this->checkInvoice($payment->invoices, $request_invoices);
|
||||
// foreach ($payment->invoices as $paymentable_invoice) {
|
||||
// $this->checkInvoice($paymentable_invoice, $request_invoices);
|
||||
// }
|
||||
}
|
||||
|
||||
foreach ($request_invoices as $request_invoice) {
|
||||
|
@ -61,7 +61,10 @@ class PaymentAppliedValidAmount implements Rule
|
||||
$payment_amounts = 0;
|
||||
$invoice_amounts = 0;
|
||||
|
||||
$payment_amounts = $payment->amount - $payment->refunded - $payment->applied;
|
||||
// $payment_amounts = $payment->amount - $payment->refunded - $payment->applied;
|
||||
|
||||
//20-03-2024 - applied amounts are never tainted by refunded amount.
|
||||
$payment_amounts = $payment->amount - $payment->applied;
|
||||
|
||||
if (request()->has('credits')
|
||||
&& is_array(request()->input('credits'))
|
||||
@ -84,10 +87,6 @@ class PaymentAppliedValidAmount implements Rule
|
||||
|
||||
$inv = $inv_collection->firstWhere('id', $invoice['invoice_id']);
|
||||
|
||||
nlog($inv->status_id);
|
||||
nlog($inv->amount);
|
||||
nlog($invoice['amount']);
|
||||
|
||||
if($inv->status_id == Invoice::STATUS_DRAFT && $inv->amount >= $invoice['amount']) {
|
||||
|
||||
} elseif ($inv->balance < $invoice['amount']) {
|
||||
|
@ -17,6 +17,7 @@ use Illuminate\Contracts\Validation\Rule;
|
||||
|
||||
/**
|
||||
* Class ValidCreditsPresentRule.
|
||||
* @deprecated 20-03-2024
|
||||
*/
|
||||
class ValidCreditsPresentRule implements Rule
|
||||
{
|
||||
@ -49,11 +50,8 @@ class ValidCreditsPresentRule implements Rule
|
||||
|
||||
private function validCreditsPresent(): bool
|
||||
{
|
||||
//todo need to ensure the clients credits are here not random ones!
|
||||
|
||||
if (array_key_exists('credits', $this->input) && is_array($this->input['credits']) && count($this->input['credits']) > 0) {
|
||||
$credit_collection = Credit::query()->whereIn('id', array_column($this->input['credits'], 'credit_id'))->count();
|
||||
|
||||
return $credit_collection == count($this->input['credits']);
|
||||
}
|
||||
|
||||
|
@ -101,9 +101,7 @@ class PortalComposer
|
||||
$enabled_modules = auth()->guard('contact')->user()->company->enabled_modules;
|
||||
$data = [];
|
||||
|
||||
// TODO: Enable dashboard once it's completed.
|
||||
// $this->settings->enable_client_portal_dashboard
|
||||
// $data[] = [ 'title' => ctrans('texts.dashboard'), 'url' => 'client.dashboard', 'icon' => 'activity'];
|
||||
$data[] = [ 'title' => ctrans('texts.dashboard'), 'url' => 'client.dashboard', 'icon' => 'activity'];
|
||||
|
||||
if (self::MODULE_INVOICES & $enabled_modules) {
|
||||
$data[] = ['title' => ctrans('texts.invoices'), 'url' => 'client.invoices.index', 'icon' => 'file-text'];
|
||||
|
@ -63,11 +63,14 @@ class UpdateCalculatedFields
|
||||
Project::query()->with('tasks')->whereHas('tasks', function ($query) {
|
||||
$query->where('updated_at', '>', now()->subHours(2));
|
||||
})
|
||||
->cursor()
|
||||
->each(function ($project) {
|
||||
$project->current_hours = $this->calculateDuration($project);
|
||||
$project->save();
|
||||
});
|
||||
->cursor()
|
||||
->each(function ($project) {
|
||||
$project->current_hours = $this->calculateDuration($project);
|
||||
$project->save();
|
||||
});
|
||||
|
||||
//Clean password resets table
|
||||
\DB::connection($db)->table('password_resets')->where('created_at', '<', now()->subHour())->delete();
|
||||
|
||||
}
|
||||
}
|
||||
|
139
app/Jobs/EDocument/CreateEDocument.php
Normal file
139
app/Jobs/EDocument/CreateEDocument.php
Normal file
@ -0,0 +1,139 @@
|
||||
<?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\Jobs\EDocument;
|
||||
|
||||
use App\Utils\Ninja;
|
||||
use App\Models\Quote;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\PurchaseOrder;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use horstoeko\zugferd\ZugferdDocumentBuilder;
|
||||
use App\Services\EDocument\Standards\OrderXDocument;
|
||||
use App\Services\EDocument\Standards\FacturaEInvoice;
|
||||
use App\Services\EDocument\Standards\ZugferdEDokument;
|
||||
|
||||
class CreateEDocument implements ShouldQueue
|
||||
{
|
||||
use Dispatchable;
|
||||
use InteractsWithQueue;
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
public function __construct(private object $document, private bool $returnObject = false)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return string|ZugferdDocumentBuilder
|
||||
*/
|
||||
public function handle(): string|ZugferdDocumentBuilder
|
||||
{
|
||||
/* Forget the singleton*/
|
||||
App::forgetInstance('translator');
|
||||
|
||||
/* Init a new copy of the translator*/
|
||||
$t = app('translator');
|
||||
/* Set the locale*/
|
||||
$settings_entity = ($this->document instanceof PurchaseOrder) ? $this->document->vendor : $this->document->client;
|
||||
App::setLocale($settings_entity->locale());
|
||||
|
||||
/* Set customized translations _NOW_ */
|
||||
$t->replace(Ninja::transformTranslations($this->document->client->getMergedSettings()));
|
||||
|
||||
$e_document_type = strlen($settings_entity->getSetting('e_invoice_type')) > 2 ? $settings_entity->getSetting('e_invoice_type') : "XInvoice_3_0";
|
||||
$e_quote_type = strlen($settings_entity->getSetting('e_quote_type')) > 2 ? $settings_entity->getSetting('e_quote_type') : "OrderX_Extended";
|
||||
|
||||
if ($this->document instanceof Invoice){
|
||||
switch ($e_document_type) {
|
||||
case "EN16931":
|
||||
case "XInvoice_3_0":
|
||||
case "XInvoice_2_3":
|
||||
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":
|
||||
$zugferd = (new ZugferdEDokument($this->document))->run();
|
||||
|
||||
return $this->returnObject ? $zugferd->xdocument : $zugferd->getXml();
|
||||
case "Facturae_3.2":
|
||||
case "Facturae_3.2.1":
|
||||
case "Facturae_3.2.2":
|
||||
return (new FacturaEInvoice($this->document, str_replace("Facturae_", "", $e_document_type)))->run();
|
||||
default:
|
||||
|
||||
$zugferd = (new ZugferdEDokument($this->document))->run();
|
||||
|
||||
return $this->returnObject ? $zugferd : $zugferd->getXml();
|
||||
|
||||
}
|
||||
}
|
||||
elseif ($this->document instanceof Quote){
|
||||
switch ($e_quote_type){
|
||||
case "OrderX_Basic":
|
||||
case "OrderX_Comfort":
|
||||
case "OrderX_Extended":
|
||||
$orderx = (new OrderXDocument($this->document))->run();
|
||||
return $this->returnObject ? $orderx->orderxdocument : $orderx->getXml();
|
||||
default:
|
||||
$orderx = (new OrderXDocument($this->document))->run();
|
||||
return $this->returnObject ? $orderx->orderxdocument : $orderx->getXml();
|
||||
}
|
||||
}
|
||||
elseif ($this->document instanceof PurchaseOrder){
|
||||
switch ($e_quote_type){
|
||||
case "OrderX_Basic":
|
||||
case "OrderX_Comfort":
|
||||
case "OrderX_Extended":
|
||||
$orderx = (new OrderXDocument($this->document))->run();
|
||||
return $this->returnObject ? $orderx->orderxdocument : $orderx->getXml();
|
||||
default:
|
||||
$orderx = (new OrderXDocument($this->document))->run();
|
||||
return $this->returnObject ? $orderx->orderxdocument : $orderx->getXml();
|
||||
}
|
||||
}
|
||||
elseif ($this->document instanceof Credit) {
|
||||
switch ($e_document_type) {
|
||||
case "EN16931":
|
||||
case "XInvoice_3_0":
|
||||
case "XInvoice_2_3":
|
||||
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":
|
||||
$zugferd = (new ZugferdEDokument($this->document))->run();
|
||||
return $this->returnObject ? $zugferd->xdocument : $zugferd->getXml();
|
||||
default:
|
||||
$zugferd = (new ZugferdEDokument($this->document))->run();
|
||||
return $this->returnObject ? $zugferd : $zugferd->getXml();
|
||||
}
|
||||
}
|
||||
else{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
@ -1,86 +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\Jobs\Invoice;
|
||||
|
||||
use App\Models\Invoice;
|
||||
use App\Services\Invoice\EInvoice\FacturaEInvoice;
|
||||
use App\Services\Invoice\EInvoice\ZugferdEInvoice;
|
||||
use App\Utils\Ninja;
|
||||
use horstoeko\zugferd\ZugferdDocumentBuilder;
|
||||
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 CreateEInvoice implements ShouldQueue
|
||||
{
|
||||
use Dispatchable;
|
||||
use InteractsWithQueue;
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
public function __construct(private Invoice $invoice, private bool $returnObject = false)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return string|ZugferdDocumentBuilder
|
||||
*/
|
||||
public function handle(): string|ZugferdDocumentBuilder
|
||||
{
|
||||
/* Forget the singleton*/
|
||||
App::forgetInstance('translator');
|
||||
|
||||
/* Init a new copy of the translator*/
|
||||
$t = app('translator');
|
||||
/* Set the locale*/
|
||||
App::setLocale($this->invoice->client->locale());
|
||||
|
||||
/* Set customized translations _NOW_ */
|
||||
$t->replace(Ninja::transformTranslations($this->invoice->client->getMergedSettings()));
|
||||
|
||||
$e_invoice_type = $this->invoice->client->getSetting('e_invoice_type');
|
||||
|
||||
switch ($e_invoice_type) {
|
||||
case "EN16931":
|
||||
case "XInvoice_3_0":
|
||||
case "XInvoice_2_3":
|
||||
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":
|
||||
$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:
|
||||
|
||||
$zugferd = (new ZugferdEInvoice($this->invoice))->run();
|
||||
|
||||
return $this->returnObject ? $zugferd : $zugferd->getXml();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -67,6 +67,11 @@ class ZipPurchaseOrders implements ShouldQueue
|
||||
try {
|
||||
foreach ($invitations as $invitation) {
|
||||
|
||||
if ($invitation->purchase_order->vendor->getSetting("enable_e_invoice")) {
|
||||
$xml = $invitation->purchase_order->service()->getEInvoice();
|
||||
$zipFile->addFromString($invitation->purchase_order->getFileName("xml"), $xml);
|
||||
}
|
||||
|
||||
$file = (new CreateRawPdf($invitation))->handle();
|
||||
|
||||
$zipFile->addFromString($invitation->purchase_order->numberFormatter().".pdf", $file);
|
||||
|
@ -63,6 +63,10 @@ class ZipQuotes implements ShouldQueue
|
||||
try {
|
||||
|
||||
foreach ($invitations as $invitation) {
|
||||
if ($invitation->quote->client->getSetting('enable_e_invoice')) {
|
||||
$xml = $invitation->quote->service()->getEInvoice();
|
||||
$zipFile->addFromString($invitation->quote->getFileName("xml"), $xml);
|
||||
}
|
||||
$file = (new \App\Jobs\Entity\CreateRawPdf($invitation))->handle();
|
||||
$zipFile->addFromString($invitation->quote->numberFormatter() . '.pdf', $file);
|
||||
}
|
||||
|
@ -12,7 +12,7 @@
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Jobs\Invoice\CreateEInvoice;
|
||||
use App\Jobs\EDocument\CreateEDocument;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\CreditInvitation;
|
||||
use App\Models\InvoiceInvitation;
|
||||
@ -113,7 +113,7 @@ class PdfSlot extends Component
|
||||
|
||||
$file_name = $this->entity->numberFormatter().'.xml';
|
||||
|
||||
$file = (new CreateEInvoice($this->entity))->handle();
|
||||
$file = (new CreateEDocument($this->entity))->handle();
|
||||
|
||||
$headers = ['Content-Type' => 'application/xml'];
|
||||
|
||||
|
@ -222,6 +222,8 @@ class RequiredClientInfo extends Component
|
||||
$this->show_form = true;
|
||||
|
||||
$hash = Cache::get(request()->input('hash'));
|
||||
|
||||
/** @var \App\Models\Invoice $invoice */
|
||||
$invoice = Invoice::find($this->decodePrimaryKey($hash['invoice_id']));
|
||||
|
||||
$this->invoice_terms = $invoice->terms;
|
||||
|
@ -140,7 +140,7 @@ class TemplateEmail extends Mailable
|
||||
'whitelabel' => $this->client->user->account->isPaid() ? true : false,
|
||||
'logo' => $this->company->present()->logo($settings),
|
||||
'links' => $this->build_email->getAttachmentLinks(),
|
||||
'email_preferences' => (Ninja::isHosted() && in_array($settings->email_sending_method, ['default', 'mailgun'])) ? $this->company->domain() . URL::signedRoute('client.email_preferences', ['entity' => $this->invitation->getEntityString(), 'invitation_key' => $this->invitation->key], absolute: false) : false,
|
||||
'email_preferences' => (Ninja::isHosted() && $this->invitation && in_array($settings->email_sending_method, ['default', 'mailgun'])) ? $this->company->domain() . URL::signedRoute('client.email_preferences', ['entity' => $this->invitation->getEntityString(), 'invitation_key' => $this->invitation->key], absolute: false) : false,
|
||||
]);
|
||||
|
||||
foreach ($this->build_email->getAttachments() as $file) {
|
||||
@ -159,15 +159,46 @@ class TemplateEmail extends Mailable
|
||||
}
|
||||
|
||||
}
|
||||
if ($this->invitation && $this->invitation->invoice && $this->invitation->invoice->client->getSetting('enable_e_invoice') && $this->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) {
|
||||
$xml_string = $this->invitation->invoice->service()->getEInvoice($this->invitation->contact);
|
||||
if ($this->invitation->invoice) {
|
||||
if ($this->invitation && $this->invitation->invoice && $this->invitation->invoice->client->getSetting('enable_e_invoice') && $this->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) {
|
||||
$xml_string = $this->invitation->invoice->service()->getEInvoice($this->invitation->contact);
|
||||
|
||||
if ($xml_string) {
|
||||
$this->attachData($xml_string, $this->invitation->invoice->getEFileName("xml"));
|
||||
}
|
||||
|
||||
if($xml_string) {
|
||||
$this->attachData($xml_string, $this->invitation->invoice->getEFileName("xml"));
|
||||
}
|
||||
|
||||
}
|
||||
elseif ($this->invitation->credit){
|
||||
if ($this->invitation && $this->invitation->credit && $this->invitation->credit->client->getSetting('enable_e_invoice') && $this->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) {
|
||||
$xml_string = $this->invitation->credit->service()->getECredit($this->invitation->contact);
|
||||
|
||||
if ($xml_string) {
|
||||
$this->attachData($xml_string, $this->invitation->credit->getEFileName("xml"));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
elseif ($this->invitation->quote){
|
||||
if ($this->invitation && $this->invitation->quote && $this->invitation->quote->client->getSetting('enable_e_invoice') && $this->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) {
|
||||
$xml_string = $this->invitation->quote->service()->getEQuote($this->invitation->contact);
|
||||
|
||||
if ($xml_string) {
|
||||
$this->attachData($xml_string, $this->invitation->quote->getEFileName("xml"));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
elseif ($this->invitation->purchase_order){
|
||||
if ($this->invitation && $this->invitation->purchase_order && $this->invitation->purchase_order->client->getSetting('enable_e_invoice') && $this->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) {
|
||||
$xml_string = $this->invitation->purchase_order->service()->getEPurchaseOrder($this->invitation->contact);
|
||||
|
||||
if ($xml_string) {
|
||||
$this->attachData($xml_string, $this->invitation->purchase_order->getEFileName("xml"));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@ -754,7 +754,7 @@ class Client extends BaseModel implements HasLocalePreference
|
||||
|
||||
return $this->company->company_key.'/'.$this->client_hash.'/'.$contact_key.'/invoices/';
|
||||
}
|
||||
public function e_invoice_filepath($invitation): string
|
||||
public function e_document_filepath($invitation): string
|
||||
{
|
||||
$contact_key = $invitation->contact->contact_key;
|
||||
|
||||
|
@ -202,7 +202,6 @@ class CompanyUser extends Pivot
|
||||
*/
|
||||
public function portalType(): bool
|
||||
{
|
||||
nlog(isset($this->react_settings->react_notification_link) && $this->react_settings->react_notification_link);
|
||||
return isset($this->react_settings->react_notification_link) && $this->react_settings->react_notification_link;
|
||||
}
|
||||
|
||||
|
@ -698,7 +698,7 @@ class RecurringInvoice extends BaseModel
|
||||
|
||||
public function subscription(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Subscription::class);
|
||||
return $this->belongsTo(Subscription::class)->withTrashed();
|
||||
}
|
||||
|
||||
public function translate_entity()
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace App\Models;
|
||||
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Carbon\CarbonInterval;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
@ -248,6 +249,7 @@ class Task extends BaseModel
|
||||
$duration += max($end_time - $start_time, 0);
|
||||
}
|
||||
|
||||
// return CarbonInterval::seconds(round($duration))->locale($this->company->locale())->cascade()->forHumans();
|
||||
return round($duration);
|
||||
}
|
||||
|
||||
|
@ -226,6 +226,7 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
return $truth->getCompanyToken();
|
||||
}
|
||||
|
||||
// if (request()->header('X-API-TOKEN')) {
|
||||
if (request()->header('X-API-TOKEN')) {
|
||||
return CompanyToken::with(['cu'])->where('token', request()->header('X-API-TOKEN'))->first();
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ use Laracasts\Presenter\PresentableTrait;
|
||||
* @property string|null $phone
|
||||
* @property string|null $private_notes
|
||||
* @property string|null $website
|
||||
* @property string|null $routing_id
|
||||
* @property bool $is_deleted
|
||||
* @property string|null $vat_number
|
||||
* @property string|null $transaction_name
|
||||
|
@ -126,14 +126,14 @@ class BaseDriver extends AbstractPaymentDriver
|
||||
$fields[] = ['name' => 'client_name', 'label' => ctrans('texts.client_name'), 'type' => 'text', 'validation' => 'required'];
|
||||
}
|
||||
|
||||
if ($this->company_gateway->require_contact_name) {
|
||||
// if ($this->company_gateway->require_contact_name) {
|
||||
$fields[] = ['name' => 'contact_first_name', 'label' => ctrans('texts.first_name'), 'type' => 'text', 'validation' => 'required'];
|
||||
$fields[] = ['name' => 'contact_last_name', 'label' => ctrans('texts.last_name'), 'type' => 'text', 'validation' => 'required'];
|
||||
}
|
||||
// }
|
||||
|
||||
if ($this->company_gateway->require_contact_email) {
|
||||
// if ($this->company_gateway->require_contact_email) {
|
||||
$fields[] = ['name' => 'contact_email', 'label' => ctrans('texts.email'), 'type' => 'text', 'validation' => 'required,email:rfc'];
|
||||
}
|
||||
// }
|
||||
|
||||
if ($this->company_gateway->require_client_phone) {
|
||||
$fields[] = ['name' => 'client_phone', 'label' => ctrans('texts.client_phone'), 'type' => 'tel', 'validation' => 'required'];
|
||||
@ -579,15 +579,18 @@ class BaseDriver extends AbstractPaymentDriver
|
||||
$nmo->company = $this->client->company;
|
||||
$nmo->settings = $this->client->company->settings;
|
||||
|
||||
$invoices = Invoice::query()->whereIn('id', $this->transformKeys(array_column($this->payment_hash->invoices(), 'invoice_id')))->withTrashed()->get();
|
||||
|
||||
$invoices->first()->invitations->each(function ($invitation) use ($nmo) {
|
||||
if (! $invitation->contact->trashed()) {
|
||||
$nmo->to_user = $invitation->contact;
|
||||
NinjaMailerJob::dispatch($nmo);
|
||||
}
|
||||
});
|
||||
if($this->payment_hash)
|
||||
{
|
||||
$invoices = Invoice::query()->whereIn('id', $this->transformKeys(array_column($this->payment_hash->invoices(), 'invoice_id')))->withTrashed()->get();
|
||||
|
||||
$invoices->first()->invitations->each(function ($invitation) use ($nmo) {
|
||||
if (! $invitation->contact->trashed()) {
|
||||
$nmo->to_user = $invitation->contact;
|
||||
NinjaMailerJob::dispatch($nmo);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$message = [
|
||||
'server_response' => $response,
|
||||
'data' => $this->payment_hash->data,
|
||||
|
@ -292,7 +292,32 @@ class PayPalPPCPPaymentDriver extends BaseDriver
|
||||
|
||||
}
|
||||
|
||||
$r = $this->gatewayRequest("/v2/checkout/orders/{$orderID}/capture", 'post', ['body' => '']);
|
||||
try {
|
||||
$r = $this->gatewayRequest("/v2/checkout/orders/{$orderID}/capture", 'post', ['body' => '']);
|
||||
} catch(\Exception $e) {
|
||||
|
||||
//Rescue for duplicate invoice_id
|
||||
if(stripos($e->getMessage(), 'DUPLICATE_INVOICE_ID') !== false) {
|
||||
|
||||
|
||||
$_invoice = collect($this->payment_hash->data->invoices)->first();
|
||||
$invoice = Invoice::withTrashed()->find($this->decodePrimaryKey($_invoice->invoice_id));
|
||||
$new_invoice_number = $invoice->number."_".Str::random(5);
|
||||
|
||||
$update_data =
|
||||
[[
|
||||
"op" => "replace",
|
||||
"path" => "/purchase_units/@reference_id=='default'/invoice_id",
|
||||
"value" => $new_invoice_number,
|
||||
]];
|
||||
|
||||
$r = $this->gatewayRequest("/v2/checkout/orders/{$orderID}", 'patch', $update_data);
|
||||
|
||||
$r = $this->gatewayRequest("/v2/checkout/orders/{$orderID}/capture", 'post', ['body' => '']);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$response = $r;
|
||||
|
||||
|
@ -18,6 +18,7 @@ use App\Models\Invoice;
|
||||
use App\Models\SystemLog;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\PaymentType;
|
||||
use Illuminate\Support\Str;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Exceptions\PaymentFailed;
|
||||
@ -211,7 +212,8 @@ class PayPalRestPaymentDriver extends BaseDriver
|
||||
|
||||
$request['gateway_response'] = str_replace("Error: ", "", $request['gateway_response']);
|
||||
$response = json_decode($request['gateway_response'], true);
|
||||
|
||||
|
||||
// nlog($response);
|
||||
//capture
|
||||
$orderID = $response['orderID'];
|
||||
|
||||
@ -235,7 +237,33 @@ class PayPalRestPaymentDriver extends BaseDriver
|
||||
|
||||
}
|
||||
|
||||
$r = $this->gatewayRequest("/v2/checkout/orders/{$orderID}/capture", 'post', ['body' => '']);
|
||||
try{
|
||||
$r = $this->gatewayRequest("/v2/checkout/orders/{$orderID}/capture", 'post', ['body' => '']);
|
||||
}
|
||||
catch(\Exception $e) {
|
||||
|
||||
//Rescue for duplicate invoice_id
|
||||
if(stripos($e->getMessage(), 'DUPLICATE_INVOICE_ID') !== false){
|
||||
|
||||
|
||||
$_invoice = collect($this->payment_hash->data->invoices)->first();
|
||||
$invoice = Invoice::withTrashed()->find($this->decodePrimaryKey($_invoice->invoice_id));
|
||||
$new_invoice_number = $invoice->number."_".Str::random(5);
|
||||
|
||||
$update_data =
|
||||
[[
|
||||
"op" => "replace",
|
||||
"path" => "/purchase_units/@reference_id=='default'/invoice_id",
|
||||
"value" => $new_invoice_number,
|
||||
]];
|
||||
|
||||
$r = $this->gatewayRequest("/v2/checkout/orders/{$orderID}", 'patch', $update_data);
|
||||
|
||||
$r = $this->gatewayRequest("/v2/checkout/orders/{$orderID}/capture", 'post', ['body' => '']);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$response = $r;
|
||||
|
||||
|
@ -252,6 +252,19 @@ class PaytracePaymentDriver extends BaseDriver
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getClientRequiredFields(): array
|
||||
{
|
||||
$fields = parent::getClientRequiredFields();
|
||||
|
||||
$fields[] = ['name' => 'client_address_line_1', 'label' => ctrans('texts.address1'), 'type' => 'text', 'validation' => 'required'];
|
||||
$fields[] = ['name' => 'client_city', 'label' => ctrans('texts.city'), 'type' => 'text', 'validation' => 'required'];
|
||||
$fields[] = ['name' => 'client_postal_code', 'label' => ctrans('texts.postal_code'), 'type' => 'text', 'validation' => 'required'];
|
||||
$fields[] = ['name' => 'client_state', 'label' => ctrans('texts.state'), 'type' => 'text', 'validation' => 'required'];
|
||||
$fields[] = ['name' => 'client_country_id', 'label' => ctrans('texts.country'), 'type' => 'text', 'validation' => 'required'];
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
public function auth(): bool
|
||||
{
|
||||
try {
|
||||
|
@ -109,15 +109,36 @@ class ACH
|
||||
|
||||
public function verificationView(ClientGatewayToken $token)
|
||||
{
|
||||
if (isset($token->meta->state) && $token->meta->state === 'authorized') {
|
||||
return redirect()
|
||||
->route('client.payment_methods.show', $token->hashed_id)
|
||||
->with('message', __('texts.payment_method_verified'));
|
||||
}
|
||||
|
||||
//double check here if we need to show the verification view.
|
||||
$this->stripe->init();
|
||||
|
||||
if(substr($token->token,0,2) == 'pm'){
|
||||
$pm = $this->stripe->getStripePaymentMethod($token->token);
|
||||
|
||||
if(!$pm->customer){
|
||||
|
||||
$meta = $token->meta;
|
||||
$meta->state = 'unauthorized';
|
||||
$token->meta = $meta;
|
||||
$token->save();
|
||||
|
||||
return redirect()
|
||||
->route('client.payment_methods.show', $token->hashed_id);
|
||||
|
||||
}
|
||||
|
||||
if (isset($token->meta->state) && $token->meta->state === 'authorized') {
|
||||
return redirect()
|
||||
->route('client.payment_methods.show', $token->hashed_id)
|
||||
->with('message', __('texts.payment_method_verified'));
|
||||
}
|
||||
|
||||
if($token->meta->next_action)
|
||||
return redirect($token->meta->next_action);
|
||||
|
||||
}
|
||||
|
||||
$bank_account = Customer::retrieveSource($token->gateway_customer_reference, $token->token, [], $this->stripe->stripe_connect_auth);
|
||||
|
||||
/* Catch externally validated bank accounts and mark them as verified */
|
||||
@ -319,6 +340,9 @@ class ACH
|
||||
$data['message'] = 'Too many requests made to the API too quickly';
|
||||
break;
|
||||
case $e instanceof InvalidRequestException:
|
||||
|
||||
return redirect()->route('client.payment_methods.verification', ['payment_method' => $cgt->hashed_id, 'method' => GatewayType::BANK_TRANSFER]);
|
||||
|
||||
$data['message'] = 'Invalid parameters were supplied to Stripe\'s API';
|
||||
break;
|
||||
case $e instanceof AuthenticationException:
|
||||
|
@ -332,14 +332,14 @@ class StripePaymentDriver extends BaseDriver
|
||||
$fields[] = ['name' => 'client_name', 'label' => ctrans('texts.client_name'), 'type' => 'text', 'validation' => 'required'];
|
||||
}
|
||||
|
||||
if ($this->company_gateway->require_contact_name) {
|
||||
// if ($this->company_gateway->require_contact_name) {
|
||||
$fields[] = ['name' => 'contact_first_name', 'label' => ctrans('texts.first_name'), 'type' => 'text', 'validation' => 'required'];
|
||||
$fields[] = ['name' => 'contact_last_name', 'label' => ctrans('texts.last_name'), 'type' => 'text', 'validation' => 'required'];
|
||||
}
|
||||
// }
|
||||
|
||||
if ($this->company_gateway->require_contact_email) {
|
||||
// if ($this->company_gateway->require_contact_email) {
|
||||
$fields[] = ['name' => 'contact_email', 'label' => ctrans('texts.email'), 'type' => 'text', 'validation' => 'required,email:rfc'];
|
||||
}
|
||||
// }
|
||||
|
||||
if ($this->company_gateway->require_client_phone) {
|
||||
$fields[] = ['name' => 'client_phone', 'label' => ctrans('texts.client_phone'), 'type' => 'tel', 'validation' => 'required'];
|
||||
|
@ -206,7 +206,8 @@ class UserRepository extends BaseRepository
|
||||
->first();
|
||||
|
||||
$cu->restore();
|
||||
|
||||
$cu->tokens()->restore();
|
||||
|
||||
event(new UserWasRestored($user, auth()->user(), auth()->user()->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace App\Services\Credit;
|
||||
|
||||
use App\Factory\PaymentFactory;
|
||||
use App\Jobs\EDocument\CreateEDocument;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentType;
|
||||
@ -37,6 +38,11 @@ class CreditService
|
||||
return (new GetCreditPdf($invitation))->run();
|
||||
}
|
||||
|
||||
public function getECredit($contact = null)
|
||||
{
|
||||
return (new CreateEDocument($this->credit))->handle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the invoice number.
|
||||
* @return $this InvoiceService object
|
||||
@ -232,6 +238,27 @@ class CreditService
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function deleteECredit()
|
||||
{
|
||||
$this->credit->load('invitations');
|
||||
|
||||
$this->credit->invitations->each(function ($invitation) {
|
||||
try {
|
||||
// if (Storage::disk(config('filesystems.default'))->exists($this->invoice->client->e_invoice_filepath($invitation).$this->invoice->getFileName("xml"))) {
|
||||
Storage::disk(config('filesystems.default'))->delete($this->credit->client->e_document_filepath($invitation).$this->credit->getFileName("xml"));
|
||||
// }
|
||||
|
||||
// if (Ninja::isHosted() && Storage::disk('public')->exists($this->invoice->client->e_invoice_filepath($invitation).$this->invoice->getFileName("xml"))) {
|
||||
if (Ninja::isHosted()) {
|
||||
Storage::disk('public')->delete($this->credit->client->e_document_filepath($invitation).$this->credit->getFileName("xml"));
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
nlog($e->getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
return $this;
|
||||
}
|
||||
public function triggeredActions($request)
|
||||
{
|
||||
$this->credit = (new TriggeredActions($this->credit, $request))->run();
|
||||
|
@ -9,7 +9,7 @@
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Services\Invoice\EInvoice;
|
||||
namespace App\Services\EDocument\Standards;
|
||||
|
||||
use App\Models\Invoice;
|
||||
use App\Models\PaymentType;
|
@ -9,7 +9,7 @@
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Services\Invoice\EInvoice;
|
||||
namespace App\Services\EDocument\Standards;
|
||||
|
||||
use App\Models\Invoice;
|
||||
use App\Services\AbstractService;
|
250
app/Services/EDocument/Standards/OrderXDocument.php
Normal file
250
app/Services/EDocument/Standards/OrderXDocument.php
Normal file
@ -0,0 +1,250 @@
|
||||
<?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\EDocument\Standards;
|
||||
|
||||
use App\Models\Credit;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Product;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\Quote;
|
||||
use App\Services\AbstractService;
|
||||
use horstoeko\orderx\codelists\OrderDocumentTypes;
|
||||
use horstoeko\orderx\codelists\OrderDutyTaxFeeCategories;
|
||||
use horstoeko\orderx\OrderDocumentBuilder;
|
||||
use horstoeko\orderx\OrderProfiles;
|
||||
|
||||
class OrderXDocument extends AbstractService
|
||||
{
|
||||
public OrderDocumentBuilder $orderxdocument;
|
||||
|
||||
public function __construct(public object $document, private readonly bool $returnObject = false, private array $tax_map = [])
|
||||
{
|
||||
}
|
||||
|
||||
public function run(): self
|
||||
{
|
||||
|
||||
$company = $this->document->company;
|
||||
$settings_entity = ($this->document instanceof PurchaseOrder) ? $this->document->vendor : $this->document->client;
|
||||
$profile = $settings_entity->getSetting('e_quote_type') ? $settings_entity->getSetting('e_quote_type') : "OrderX_Extended";
|
||||
|
||||
$profile = match ($profile) {
|
||||
"OrderX_Basic" => OrderProfiles::PROFILE_BASIC,
|
||||
"OrderX_Comfort" => OrderProfiles::PROFILE_COMFORT,
|
||||
"OrderX_Extended" => OrderProfiles::PROFILE_EXTENDED,
|
||||
default => OrderProfiles::PROFILE_EXTENDED,
|
||||
};
|
||||
|
||||
$this->orderxdocument = OrderDocumentBuilder::CreateNew($profile);
|
||||
|
||||
$this->orderxdocument
|
||||
->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"))
|
||||
->setDocumentSellerContact($this->document->user->present()->getFullName(), "", $this->document->user->present()->phone(), "", $this->document->user->email)
|
||||
->setDocumentBuyer($settings_entity->present()->name(), $settings_entity->number)
|
||||
->setDocumentBuyerAddress($settings_entity->address1, "", "", $settings_entity->postal_code, $settings_entity->city, $settings_entity->country->iso_3166_2, $settings_entity->state)
|
||||
->setDocumentBuyerContact($settings_entity->present()->primary_contact_name(), "", $settings_entity->present()->phone(), "", $settings_entity->present()->email())
|
||||
->addDocumentPaymentTerm(ctrans("texts.xinvoice_payable", ['payeddue' => date_create($this->document->date ?? now()->format('Y-m-d'))->diff(date_create($this->document->due_date ?? now()->format('Y-m-d')))->format("%d"), 'paydate' => $this->document->due_date]));
|
||||
|
||||
if (!empty($this->document->public_notes)) {
|
||||
$this->orderxdocument->addDocumentNote($this->document->public_notes ?? '');
|
||||
}
|
||||
// Document type
|
||||
$document_class = get_class($this->document);
|
||||
switch ($document_class){
|
||||
case Quote::class:
|
||||
// Probably wrong file code https://github.com/horstoeko/zugferd/blob/master/src/codelists/ZugferdInvoiceType.php
|
||||
if (empty($this->document->number)) {
|
||||
$this->orderxdocument->setDocumentInformation("DRAFT", OrderDocumentTypes::ORDER, date_create($this->document->date ?? now()->format('Y-m-d')), $settings_entity->getCurrencyCode());
|
||||
$this->orderxdocument->setIsTestDocument(true);
|
||||
} else {
|
||||
$this->orderxdocument->setDocumentInformation($this->document->number, OrderDocumentTypes::ORDER, date_create($this->document->date ?? now()->format('Y-m-d')), $settings_entity->getCurrencyCode());
|
||||
};
|
||||
break;
|
||||
case PurchaseOrder::class:
|
||||
if (empty($this->document->number)) {
|
||||
$this->orderxdocument->setDocumentInformation("DRAFT", OrderDocumentTypes::ORDER_RESPONSE, date_create($this->document->date ?? now()->format('Y-m-d')), $settings_entity->getCurrencyCode());
|
||||
$this->orderxdocument->setIsTestDocument(true);
|
||||
} else {
|
||||
$this->orderxdocument->setDocumentInformation($this->document->number, OrderDocumentTypes::ORDER_RESPONSE, date_create($this->document->date ?? now()->format('Y-m-d')), $settings_entity->getCurrencyCode());
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (isset($this->document->po_number)) {
|
||||
$this->orderxdocument->setDocumentBuyerOrderReferencedDocument($this->document->po_number);
|
||||
}
|
||||
|
||||
if (empty($settings_entity->routing_id)) {
|
||||
$this->orderxdocument->setDocumentBuyerReference(ctrans("texts.xinvoice_no_buyers_reference"));
|
||||
} else {
|
||||
$this->orderxdocument->setDocumentBuyerReference($settings_entity->routing_id);
|
||||
}
|
||||
if (isset($settings_entity->shipping_address1) && $settings_entity->shipping_country) {
|
||||
$this->orderxdocument->setDocumentShipToAddress($settings_entity->shipping_address1, $settings_entity->shipping_address2, "", $settings_entity->shipping_postal_code, $settings_entity->shipping_city, $settings_entity->shipping_country->iso_3166_2, $settings_entity->shipping_state);
|
||||
}
|
||||
|
||||
$this->orderxdocument->addDocumentPaymentMean(68, ctrans("texts.xinvoice_online_payment"));
|
||||
|
||||
if (str_contains($company->getSetting('vat_number'), "/")) {
|
||||
$this->orderxdocument->addDocumentSellerTaxRegistration("FC", $company->getSetting('vat_number'));
|
||||
} else {
|
||||
$this->orderxdocument->addDocumentSellerTaxRegistration("VA", $company->getSetting('vat_number'));
|
||||
}
|
||||
|
||||
$invoicing_data = $this->document->calc();
|
||||
|
||||
//Create line items and calculate taxes
|
||||
foreach ($this->document->line_items as $index => $item) {
|
||||
/** @var \App\DataMapper\InvoiceItem $item **/
|
||||
$this->orderxdocument->addNewPosition($index)
|
||||
->setDocumentPositionGrossPrice($item->gross_line_total)
|
||||
->setDocumentPositionNetPrice($item->line_total);
|
||||
if (!empty($item->product_key)) {
|
||||
if (!empty($item->notes)) {
|
||||
$this->orderxdocument->setDocumentPositionProductDetails($item->product_key, $item->notes);
|
||||
} else {
|
||||
$this->orderxdocument->setDocumentPositionProductDetails($item->product_key);
|
||||
}
|
||||
} else {
|
||||
if (!empty($item->notes)) {
|
||||
$this->orderxdocument->setDocumentPositionProductDetails($item->notes);
|
||||
} else {
|
||||
$this->orderxdocument->setDocumentPositionProductDetails("no product name defined");
|
||||
}
|
||||
}
|
||||
// TODO: add item classification (kg, m^3, ...)
|
||||
// if (isset($item->task_id)) {
|
||||
// $this->orderxdocument->setDocumentPositionQuantity($item->quantity, "HUR");
|
||||
// } else {
|
||||
// $this->orderxdocument->setDocumentPositionQuantity($item->quantity, "H87");
|
||||
// }
|
||||
$linenetamount = $item->line_total;
|
||||
if ($item->discount > 0) {
|
||||
if ($this->document->is_amount_discount) {
|
||||
$linenetamount -= $item->discount;
|
||||
} else {
|
||||
$linenetamount -= $linenetamount * ($item->discount / 100);
|
||||
}
|
||||
}
|
||||
$this->orderxdocument->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)) {
|
||||
$this->orderxdocument->addDocumentPositionTax($taxtype, 'VAT', $item->tax_rate1);
|
||||
$this->addtoTaxMap($taxtype, $linenetamount, $item->tax_rate1);
|
||||
} elseif (!empty($item->tax_name2)) {
|
||||
$this->orderxdocument->addDocumentPositionTax($taxtype, 'VAT', $item->tax_rate2);
|
||||
$this->addtoTaxMap($taxtype, $linenetamount, $item->tax_rate2);
|
||||
} elseif (!empty($item->tax_name3)) {
|
||||
$this->orderxdocument->addDocumentPositionTax($taxtype, 'VAT', $item->tax_rate3);
|
||||
$this->addtoTaxMap($taxtype, $linenetamount, $item->tax_rate3);
|
||||
} else {
|
||||
nlog("Can't add correct tax position");
|
||||
}
|
||||
} else {
|
||||
if (!empty($this->document->tax_name1)) {
|
||||
$taxtype = $this->getTaxType($this->document->tax_name1);
|
||||
$this->orderxdocument->addDocumentPositionTax($taxtype, 'VAT', $this->document->tax_rate1);
|
||||
$this->addtoTaxMap($taxtype, $linenetamount, $this->document->tax_rate1);
|
||||
} elseif (!empty($this->document->tax_name2)) {
|
||||
$taxtype = $this->getTaxType($this->document->tax_name2);
|
||||
$this->orderxdocument->addDocumentPositionTax($taxtype, 'VAT', $this->document->tax_rate2);
|
||||
$this->addtoTaxMap($taxtype, $linenetamount, $this->document->tax_rate2);
|
||||
} elseif (!empty($this->document->tax_name3)) {
|
||||
$taxtype = $this->getTaxType($this->document->tax_name3);
|
||||
$this->orderxdocument->addDocumentPositionTax($taxtype, 'VAT', $this->document->tax_rate3);
|
||||
$this->addtoTaxMap($taxtype, $linenetamount, $this->document->tax_rate3);
|
||||
} else {
|
||||
$taxtype = OrderDutyTaxFeeCategories::ZERO_RATED_GOODS;
|
||||
$this->orderxdocument->addDocumentPositionTax($taxtype, 'VAT', 0);
|
||||
$this->addtoTaxMap($taxtype, $linenetamount, 0);
|
||||
// nlog("Can't add correct tax position");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->orderxdocument->setDocumentSummation($this->document->amount, $this->document->balance, $invoicing_data->getSubTotal(), $invoicing_data->getTotalSurcharges(), $invoicing_data->getTotalDiscount(), $invoicing_data->getSubTotal(), $invoicing_data->getItemTotalTaxes(), 0.0, $this->document->amount - $this->document->balance);
|
||||
|
||||
foreach ($this->tax_map as $item) {
|
||||
$this->orderxdocument->addDocumentTax($item["tax_type"], "VAT", $item["net_amount"], $item["tax_rate"] * $item["net_amount"], $item["tax_rate"] * 100);
|
||||
}
|
||||
|
||||
// The validity can be checked using https://portal3.gefeg.com/invoice/validation or https://e-rechnung.bayern.de/app/#/upload
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the XML document
|
||||
* in string format
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getXml(): string
|
||||
{
|
||||
return $this->orderxdocument->getContent();
|
||||
}
|
||||
|
||||
private function getTaxType($name): string
|
||||
{
|
||||
$tax_type = null;
|
||||
switch ($name) {
|
||||
case Product::PRODUCT_TYPE_SERVICE:
|
||||
case Product::PRODUCT_TYPE_DIGITAL:
|
||||
case Product::PRODUCT_TYPE_PHYSICAL:
|
||||
case Product::PRODUCT_TYPE_SHIPPING:
|
||||
case Product::PRODUCT_TYPE_REDUCED_TAX:
|
||||
$tax_type = OrderDutyTaxFeeCategories::STANDARD_RATE;
|
||||
break;
|
||||
case Product::PRODUCT_TYPE_EXEMPT:
|
||||
$tax_type = OrderDutyTaxFeeCategories::EXEMPT_FROM_TAX;
|
||||
break;
|
||||
case Product::PRODUCT_TYPE_ZERO_RATED:
|
||||
$tax_type = OrderDutyTaxFeeCategories::ZERO_RATED_GOODS;
|
||||
break;
|
||||
case Product::PRODUCT_TYPE_REVERSE_TAX:
|
||||
$tax_type = OrderDutyTaxFeeCategories::VAT_REVERSE_CHARGE;
|
||||
break;
|
||||
}
|
||||
$eu_states = ["AT", "BE", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR", "DE", "EL", "GR", "HU", "IE", "IT", "LV", "LT", "LU", "MT", "NL", "PL", "PT", "RO", "SK", "SI", "ES", "SE", "IS", "LI", "NO", "CH"];
|
||||
if (empty($tax_type)) {
|
||||
if ((in_array($this->document->company->country()->iso_3166_2, $eu_states) && in_array($this->document->client->country->iso_3166_2, $eu_states)) && $this->document->company->country()->iso_3166_2 != $this->document->client->country->iso_3166_2) {
|
||||
$tax_type = OrderDutyTaxFeeCategories::VAT_EXEMPT_FOR_EEA_INTRACOMMUNITY_SUPPLY_OF_GOODS_AND_SERVICES;
|
||||
} elseif (!in_array($this->document->client->country->iso_3166_2, $eu_states)) {
|
||||
$tax_type = OrderDutyTaxFeeCategories::SERVICE_OUTSIDE_SCOPE_OF_TAX;
|
||||
} elseif ($this->document->client->country->iso_3166_2 == "ES-CN") {
|
||||
$tax_type = OrderDutyTaxFeeCategories::CANARY_ISLANDS_GENERAL_INDIRECT_TAX;
|
||||
} elseif (in_array($this->document->client->country->iso_3166_2, ["ES-CE", "ES-ML"])) {
|
||||
$tax_type = OrderDutyTaxFeeCategories::TAX_FOR_PRODUCTION_SERVICES_AND_IMPORTATION_IN_CEUTA_AND_MELILLA;
|
||||
} else {
|
||||
nlog("Unkown tax case for xinvoice");
|
||||
$tax_type = OrderDutyTaxFeeCategories::STANDARD_RATE;
|
||||
}
|
||||
}
|
||||
return $tax_type;
|
||||
}
|
||||
private function addtoTaxMap(string $tax_type, float $net_amount, float $tax_rate): void
|
||||
{
|
||||
$hash = hash("md5", $tax_type."-".$tax_rate);
|
||||
if (array_key_exists($hash, $this->tax_map)) {
|
||||
$this->tax_map[$hash]["net_amount"] += $net_amount;
|
||||
} else {
|
||||
$this->tax_map[$hash] = [
|
||||
"tax_type" => $tax_type,
|
||||
"net_amount" => $net_amount,
|
||||
"tax_rate" => $tax_rate / 100
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -9,28 +9,28 @@
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Services\Invoice\EInvoice;
|
||||
namespace App\Services\EDocument\Standards;
|
||||
|
||||
use App\Models\Invoice;
|
||||
use App\Services\AbstractService;
|
||||
use CleverIt\UBL\Invoice\Address;
|
||||
use CleverIt\UBL\Invoice\ClassifiedTaxCategory;
|
||||
use CleverIt\UBL\Invoice\Contact;
|
||||
use CleverIt\UBL\Invoice\Country;
|
||||
use CleverIt\UBL\Invoice\Generator;
|
||||
use CleverIt\UBL\Invoice\Invoice as UBLInvoice;
|
||||
use CleverIt\UBL\Invoice\InvoiceLine;
|
||||
use CleverIt\UBL\Invoice\Item;
|
||||
use CleverIt\UBL\Invoice\LegalEntity;
|
||||
use CleverIt\UBL\Invoice\LegalMonetaryTotal;
|
||||
use CleverIt\UBL\Invoice\Party;
|
||||
use CleverIt\UBL\Invoice\PayeeFinancialAccount;
|
||||
use CleverIt\UBL\Invoice\PaymentMeans;
|
||||
use CleverIt\UBL\Invoice\Price;
|
||||
use CleverIt\UBL\Invoice\TaxCategory;
|
||||
use CleverIt\UBL\Invoice\TaxScheme;
|
||||
use CleverIt\UBL\Invoice\TaxSubTotal;
|
||||
use CleverIt\UBL\Invoice\TaxTotal;
|
||||
use CleverIt\UBL\Invoice\PaymentMeans;
|
||||
use CleverIt\UBL\Invoice\PayeeFinancialAccount;
|
||||
use CleverIt\UBL\Invoice\LegalEntity;
|
||||
use CleverIt\UBL\Invoice\ClassifiedTaxCategory;
|
||||
use CleverIt\UBL\Invoice\Price;
|
||||
|
||||
class RoEInvoice extends AbstractService
|
||||
{
|
@ -9,28 +9,31 @@
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Services\Invoice\EInvoice;
|
||||
namespace App\Services\EDocument\Standards;
|
||||
|
||||
use App\Models\Credit;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Product;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\Quote;
|
||||
use App\Services\AbstractService;
|
||||
use horstoeko\zugferd\codelists\ZugferdDutyTaxFeeCategories;
|
||||
use horstoeko\zugferd\ZugferdDocumentBuilder;
|
||||
use horstoeko\zugferd\ZugferdProfiles;
|
||||
|
||||
class ZugferdEInvoice extends AbstractService
|
||||
class ZugferdEDokument extends AbstractService
|
||||
{
|
||||
public ZugferdDocumentBuilder $xrechnung;
|
||||
public ZugferdDocumentBuilder $xdocument;
|
||||
|
||||
public function __construct(public Invoice $invoice, private readonly bool $returnObject = false, private array $tax_map = [])
|
||||
public function __construct(public object $document, private readonly bool $returnObject = false, private array $tax_map = [])
|
||||
{
|
||||
}
|
||||
|
||||
public function run(): self
|
||||
{
|
||||
|
||||
$company = $this->invoice->company;
|
||||
$client = $this->invoice->client;
|
||||
$company = $this->document->company;
|
||||
$client = $this->document->client;
|
||||
$profile = $client->getSetting('e_invoice_type');
|
||||
|
||||
$profile = match ($profile) {
|
||||
@ -46,123 +49,143 @@ class ZugferdEInvoice extends AbstractService
|
||||
default => ZugferdProfiles::PROFILE_EN16931,
|
||||
};
|
||||
|
||||
$this->xrechnung = ZugferdDocumentBuilder::CreateNew($profile);
|
||||
$this->xdocument = ZugferdDocumentBuilder::CreateNew($profile);
|
||||
|
||||
$this->xrechnung
|
||||
->setDocumentSupplyChainEvent(date_create($this->invoice->date ?? now()->format('Y-m-d')))
|
||||
$this->xdocument
|
||||
->setDocumentSupplyChainEvent(date_create($this->document->date ?? now()->format('Y-m-d')))
|
||||
->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"))
|
||||
->setDocumentSellerContact($this->invoice->user->present()->getFullName(), "", $this->invoice->user->present()->phone(), "", $this->invoice->user->email)
|
||||
->setDocumentSellerContact($this->document->user->present()->getFullName(), "", $this->document->user->present()->phone(), "", $this->document->user->email)
|
||||
->setDocumentBuyer($client->present()->name(), $client->number)
|
||||
->setDocumentBuyerAddress($client->address1, "", "", $client->postal_code, $client->city, $client->country->iso_3166_2, $client->state)
|
||||
->setDocumentBuyerContact($client->present()->primary_contact_name(), "", $client->present()->phone(), "", $client->present()->email())
|
||||
->addDocumentPaymentTerm(ctrans("texts.xinvoice_payable", ['payeddue' => date_create($this->invoice->date ?? now()->format('Y-m-d'))->diff(date_create($this->invoice->due_date ?? now()->format('Y-m-d')))->format("%d"), 'paydate' => $this->invoice->due_date]));
|
||||
->addDocumentPaymentTerm(ctrans("texts.xinvoice_payable", ['payeddue' => date_create($this->document->date ?? now()->format('Y-m-d'))->diff(date_create($this->document->due_date ?? now()->format('Y-m-d')))->format("%d"), 'paydate' => $this->document->due_date]));
|
||||
|
||||
if (!empty($this->invoice->public_notes)) {
|
||||
$this->xrechnung->addDocumentNote($this->invoice->public_notes ?? '');
|
||||
if (!empty($this->document->public_notes)) {
|
||||
$this->xdocument->addDocumentNote($this->document->public_notes ?? '');
|
||||
}
|
||||
if (empty($this->invoice->number)) {
|
||||
$this->xrechnung->setDocumentInformation("DRAFT", "380", date_create($this->invoice->date ?? now()->format('Y-m-d')), $client->getCurrencyCode());
|
||||
} else {
|
||||
$this->xrechnung->setDocumentInformation($this->invoice->number, "380", date_create($this->invoice->date ?? now()->format('Y-m-d')), $client->getCurrencyCode());
|
||||
// Document type
|
||||
$document_class = get_class($this->document);
|
||||
switch ($document_class){
|
||||
case Quote::class:
|
||||
// Probably wrong file code https://github.com/horstoeko/zugferd/blob/master/src/codelists/ZugferdInvoiceType.php
|
||||
if (empty($this->document->number)) {
|
||||
$this->xdocument->setDocumentInformation("DRAFT", "84", date_create($this->document->date ?? now()->format('Y-m-d')), $client->getCurrencyCode());
|
||||
} else {
|
||||
$this->xdocument->setDocumentInformation($this->document->number, "84", date_create($this->document->date ?? now()->format('Y-m-d')), $client->getCurrencyCode());
|
||||
};
|
||||
break;
|
||||
case Invoice::class:
|
||||
if (empty($this->document->number)) {
|
||||
$this->xdocument->setDocumentInformation("DRAFT", "380", date_create($this->document->date ?? now()->format('Y-m-d')), $client->getCurrencyCode());
|
||||
} else {
|
||||
$this->xdocument->setDocumentInformation($this->document->number, "380", date_create($this->document->date ?? now()->format('Y-m-d')), $client->getCurrencyCode());
|
||||
}
|
||||
break;
|
||||
case Credit::class:
|
||||
if (empty($this->document->number)) {
|
||||
$this->xdocument->setDocumentInformation("DRAFT", "389", date_create($this->document->date ?? now()->format('Y-m-d')), $client->getCurrencyCode());
|
||||
} else {
|
||||
$this->xdocument->setDocumentInformation($this->document->number, "389", date_create($this->document->date ?? now()->format('Y-m-d')), $client->getCurrencyCode());
|
||||
}
|
||||
}
|
||||
if (isset($this->invoice->po_number)) {
|
||||
$this->xrechnung->setDocumentBuyerOrderReferencedDocument($this->invoice->po_number);
|
||||
if (isset($this->document->po_number)) {
|
||||
$this->xdocument->setDocumentBuyerOrderReferencedDocument($this->document->po_number);
|
||||
}
|
||||
|
||||
if (empty($client->routing_id)) {
|
||||
$this->xrechnung->setDocumentBuyerReference(ctrans("texts.xinvoice_no_buyers_reference"));
|
||||
$this->xdocument->setDocumentBuyerReference(ctrans("texts.xinvoice_no_buyers_reference"));
|
||||
} else {
|
||||
$this->xrechnung->setDocumentBuyerReference($client->routing_id);
|
||||
$this->xdocument->setDocumentBuyerReference($client->routing_id);
|
||||
}
|
||||
if (isset($client->shipping_address1) && $client->shipping_country) {
|
||||
$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);
|
||||
$this->xdocument->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->addDocumentPaymentMean(68, ctrans("texts.xinvoice_online_payment"));
|
||||
$this->xdocument->addDocumentPaymentMean(68, ctrans("texts.xinvoice_online_payment"));
|
||||
|
||||
if (str_contains($company->getSetting('vat_number'), "/")) {
|
||||
$this->xrechnung->addDocumentSellerTaxRegistration("FC", $company->getSetting('vat_number'));
|
||||
$this->xdocument->addDocumentSellerTaxRegistration("FC", $company->getSetting('vat_number'));
|
||||
} else {
|
||||
$this->xrechnung->addDocumentSellerTaxRegistration("VA", $company->getSetting('vat_number'));
|
||||
$this->xdocument->addDocumentSellerTaxRegistration("VA", $company->getSetting('vat_number'));
|
||||
}
|
||||
|
||||
$invoicing_data = $this->invoice->calc();
|
||||
$invoicing_data = $this->document->calc();
|
||||
|
||||
//Create line items and calculate taxes
|
||||
foreach ($this->invoice->line_items as $index => $item) {
|
||||
foreach ($this->document->line_items as $index => $item) {
|
||||
/** @var \App\DataMapper\InvoiceItem $item **/
|
||||
$this->xrechnung->addNewPosition($index)
|
||||
$this->xdocument->addNewPosition($index)
|
||||
->setDocumentPositionGrossPrice($item->gross_line_total)
|
||||
->setDocumentPositionNetPrice($item->line_total);
|
||||
if (!empty($item->product_key)) {
|
||||
if (!empty($item->notes)) {
|
||||
$this->xrechnung->setDocumentPositionProductDetails($item->product_key, $item->notes);
|
||||
$this->xdocument->setDocumentPositionProductDetails($item->product_key, $item->notes);
|
||||
} else {
|
||||
$this->xrechnung->setDocumentPositionProductDetails($item->product_key);
|
||||
$this->xdocument->setDocumentPositionProductDetails($item->product_key);
|
||||
}
|
||||
} else {
|
||||
if (!empty($item->notes)) {
|
||||
$this->xrechnung->setDocumentPositionProductDetails($item->notes);
|
||||
$this->xdocument->setDocumentPositionProductDetails($item->notes);
|
||||
} else {
|
||||
$this->xrechnung->setDocumentPositionProductDetails("no product name defined");
|
||||
$this->xdocument->setDocumentPositionProductDetails("no product name defined");
|
||||
}
|
||||
}
|
||||
if (isset($item->task_id)) {
|
||||
$this->xrechnung->setDocumentPositionQuantity($item->quantity, "HUR");
|
||||
if ($item->type_id == 2) {
|
||||
$this->xdocument->setDocumentPositionQuantity($item->quantity, "HUR");
|
||||
} else {
|
||||
$this->xrechnung->setDocumentPositionQuantity($item->quantity, "H87");
|
||||
$this->xdocument->setDocumentPositionQuantity($item->quantity, "H87");
|
||||
}
|
||||
$linenetamount = $item->line_total;
|
||||
if ($item->discount > 0) {
|
||||
if ($this->invoice->is_amount_discount) {
|
||||
if ($this->document->is_amount_discount) {
|
||||
$linenetamount -= $item->discount;
|
||||
} else {
|
||||
$linenetamount -= $linenetamount * ($item->discount / 100);
|
||||
}
|
||||
}
|
||||
$this->xrechnung->setDocumentPositionLineSummation($linenetamount);
|
||||
$this->xdocument->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)) {
|
||||
$this->xrechnung->addDocumentPositionTax($taxtype, 'VAT', $item->tax_rate1);
|
||||
$this->xdocument->addDocumentPositionTax($taxtype, 'VAT', $item->tax_rate1);
|
||||
$this->addtoTaxMap($taxtype, $linenetamount, $item->tax_rate1);
|
||||
} elseif (!empty($item->tax_name2)) {
|
||||
$this->xrechnung->addDocumentPositionTax($taxtype, 'VAT', $item->tax_rate2);
|
||||
$this->xdocument->addDocumentPositionTax($taxtype, 'VAT', $item->tax_rate2);
|
||||
$this->addtoTaxMap($taxtype, $linenetamount, $item->tax_rate2);
|
||||
} elseif (!empty($item->tax_name3)) {
|
||||
$this->xrechnung->addDocumentPositionTax($taxtype, 'VAT', $item->tax_rate3);
|
||||
$this->xdocument->addDocumentPositionTax($taxtype, 'VAT', $item->tax_rate3);
|
||||
$this->addtoTaxMap($taxtype, $linenetamount, $item->tax_rate3);
|
||||
} else {
|
||||
// nlog("Can't add correct tax position");
|
||||
}
|
||||
} else {
|
||||
if (!empty($this->invoice->tax_name1)) {
|
||||
$taxtype = $this->getTaxType($this->invoice->tax_name1);
|
||||
$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);
|
||||
$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);
|
||||
$this->xrechnung->addDocumentPositionTax($taxtype, 'VAT', $this->invoice->tax_rate3);
|
||||
$this->addtoTaxMap($taxtype, $linenetamount, $this->invoice->tax_rate3);
|
||||
if (!empty($this->document->tax_name1)) {
|
||||
$taxtype = $this->getTaxType($this->document->tax_name1);
|
||||
$this->xdocument->addDocumentPositionTax($taxtype, 'VAT', $this->document->tax_rate1);
|
||||
$this->addtoTaxMap($taxtype, $linenetamount, $this->document->tax_rate1);
|
||||
} elseif (!empty($this->document->tax_name2)) {
|
||||
$taxtype = $this->getTaxType($this->document->tax_name2);
|
||||
$this->xdocument->addDocumentPositionTax($taxtype, 'VAT', $this->document->tax_rate2);
|
||||
$this->addtoTaxMap($taxtype, $linenetamount, $this->document->tax_rate2);
|
||||
} elseif (!empty($this->document->tax_name3)) {
|
||||
$taxtype = $this->getTaxType($this->document->tax_name3);
|
||||
$this->xdocument->addDocumentPositionTax($taxtype, 'VAT', $this->document->tax_rate3);
|
||||
$this->addtoTaxMap($taxtype, $linenetamount, $this->document->tax_rate3);
|
||||
} else {
|
||||
$taxtype = ZugferdDutyTaxFeeCategories::ZERO_RATED_GOODS;
|
||||
$this->xrechnung->addDocumentPositionTax($taxtype, 'VAT', 0);
|
||||
$this->xdocument->addDocumentPositionTax($taxtype, 'VAT', 0);
|
||||
$this->addtoTaxMap($taxtype, $linenetamount, 0);
|
||||
// nlog("Can't add correct tax position");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$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);
|
||||
$this->xdocument->setDocumentSummation($this->document->amount, $this->document->balance, $invoicing_data->getSubTotal(), $invoicing_data->getTotalSurcharges(), $invoicing_data->getTotalDiscount(), $invoicing_data->getSubTotal(), $invoicing_data->getItemTotalTaxes(), 0.0, $this->document->amount - $this->document->balance);
|
||||
|
||||
foreach ($this->tax_map as $item) {
|
||||
$this->xrechnung->addDocumentTax($item["tax_type"], "VAT", $item["net_amount"], $item["tax_rate"] * $item["net_amount"], $item["tax_rate"] * 100);
|
||||
$this->xdocument->addDocumentTax($item["tax_type"], "VAT", $item["net_amount"], $item["tax_rate"] * $item["net_amount"], $item["tax_rate"] * 100);
|
||||
}
|
||||
|
||||
// The validity can be checked using https://portal3.gefeg.com/invoice/validation or https://e-rechnung.bayern.de/app/#/upload
|
||||
@ -178,7 +201,7 @@ class ZugferdEInvoice extends AbstractService
|
||||
*/
|
||||
public function getXml(): string
|
||||
{
|
||||
return $this->xrechnung->getContent();
|
||||
return $this->xdocument->getContent();
|
||||
}
|
||||
|
||||
private function getTaxType($name): string
|
||||
@ -204,13 +227,13 @@ class ZugferdEInvoice extends AbstractService
|
||||
}
|
||||
$eu_states = ["AT", "BE", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR", "DE", "EL", "GR", "HU", "IE", "IT", "LV", "LT", "LU", "MT", "NL", "PL", "PT", "RO", "SK", "SI", "ES", "SE", "IS", "LI", "NO", "CH"];
|
||||
if (empty($tax_type)) {
|
||||
if ((in_array($this->invoice->company->country()->iso_3166_2, $eu_states) && in_array($this->invoice->client->country->iso_3166_2, $eu_states)) && $this->invoice->company->country()->iso_3166_2 != $this->invoice->client->country->iso_3166_2) {
|
||||
if ((in_array($this->document->company->country()->iso_3166_2, $eu_states) && in_array($this->document->client->country->iso_3166_2, $eu_states)) && $this->document->company->country()->iso_3166_2 != $this->document->client->country->iso_3166_2) {
|
||||
$tax_type = ZugferdDutyTaxFeeCategories::VAT_EXEMPT_FOR_EEA_INTRACOMMUNITY_SUPPLY_OF_GOODS_AND_SERVICES;
|
||||
} elseif (!in_array($this->invoice->client->country->iso_3166_2, $eu_states)) {
|
||||
} elseif (!in_array($this->document->client->country->iso_3166_2, $eu_states)) {
|
||||
$tax_type = ZugferdDutyTaxFeeCategories::SERVICE_OUTSIDE_SCOPE_OF_TAX;
|
||||
} elseif ($this->invoice->client->country->iso_3166_2 == "ES-CN") {
|
||||
} elseif ($this->document->client->country->iso_3166_2 == "ES-CN") {
|
||||
$tax_type = ZugferdDutyTaxFeeCategories::CANARY_ISLANDS_GENERAL_INDIRECT_TAX;
|
||||
} elseif (in_array($this->invoice->client->country->iso_3166_2, ["ES-CE", "ES-ML"])) {
|
||||
} elseif (in_array($this->document->client->country->iso_3166_2, ["ES-CE", "ES-ML"])) {
|
||||
$tax_type = ZugferdDutyTaxFeeCategories::TAX_FOR_PRODUCTION_SERVICES_AND_IMPORTATION_IN_CEUTA_AND_MELILLA;
|
||||
} else {
|
||||
nlog("Unkown tax case for xinvoice");
|
@ -17,6 +17,8 @@ use App\Jobs\Invoice\CreateUbl;
|
||||
use App\Models\Account;
|
||||
use App\Models\Expense;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\Quote;
|
||||
use App\Models\Task;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
@ -318,7 +320,7 @@ class EmailDefaults
|
||||
}
|
||||
}
|
||||
/** E-Invoice xml file */
|
||||
if ($this->email->email_object->settings->enable_e_invoice && $this->email->email_object->entity instanceof Invoice) {
|
||||
if ($this->email->email_object->settings->enable_e_invoice && ! $this->email->email_object->entity instanceof PurchaseOrder) {
|
||||
$xml_string = $this->email->email_object->entity->service()->getEInvoice();
|
||||
|
||||
if($xml_string) {
|
||||
|
@ -11,21 +11,21 @@
|
||||
|
||||
namespace App\Services\Invoice;
|
||||
|
||||
use App\Models\Task;
|
||||
use App\Utils\Ninja;
|
||||
use App\Events\Invoice\InvoiceWasArchived;
|
||||
use App\Jobs\EDocument\CreateEDocument;
|
||||
use App\Jobs\Entity\CreateRawPdf;
|
||||
use App\Jobs\Inventory\AdjustProductInventory;
|
||||
use App\Libraries\Currency\Conversion\CurrencyApi;
|
||||
use App\Models\CompanyGateway;
|
||||
use App\Models\Expense;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\Subscription;
|
||||
use App\Models\CompanyGateway;
|
||||
use Illuminate\Support\Carbon;
|
||||
use App\Models\Task;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Jobs\Entity\CreateRawPdf;
|
||||
use App\Jobs\Invoice\CreateEInvoice;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use App\Events\Invoice\InvoiceWasArchived;
|
||||
use App\Jobs\Inventory\AdjustProductInventory;
|
||||
use App\Libraries\Currency\Conversion\CurrencyApi;
|
||||
|
||||
class InvoiceService
|
||||
{
|
||||
@ -201,7 +201,7 @@ class InvoiceService
|
||||
|
||||
public function getEInvoice($contact = null)
|
||||
{
|
||||
return (new CreateEInvoice($this->invoice))->handle();
|
||||
return (new CreateEDocument($this->invoice))->handle();
|
||||
}
|
||||
|
||||
public function sendEmail($contact = null)
|
||||
@ -409,12 +409,12 @@ class InvoiceService
|
||||
$this->invoice->invitations->each(function ($invitation) {
|
||||
try {
|
||||
// if (Storage::disk(config('filesystems.default'))->exists($this->invoice->client->e_invoice_filepath($invitation).$this->invoice->getFileName("xml"))) {
|
||||
Storage::disk(config('filesystems.default'))->delete($this->invoice->client->e_invoice_filepath($invitation).$this->invoice->getFileName("xml"));
|
||||
Storage::disk(config('filesystems.default'))->delete($this->invoice->client->e_document_filepath($invitation).$this->invoice->getFileName("xml"));
|
||||
// }
|
||||
|
||||
// if (Ninja::isHosted() && Storage::disk('public')->exists($this->invoice->client->e_invoice_filepath($invitation).$this->invoice->getFileName("xml"))) {
|
||||
if (Ninja::isHosted()) {
|
||||
Storage::disk('public')->delete($this->invoice->client->e_invoice_filepath($invitation).$this->invoice->getFileName("xml"));
|
||||
Storage::disk('public')->delete($this->invoice->client->e_document_filepath($invitation).$this->invoice->getFileName("xml"));
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
nlog($e->getMessage());
|
||||
|
@ -11,7 +11,7 @@
|
||||
|
||||
namespace App\Services\Pdf;
|
||||
|
||||
use App\Jobs\Invoice\CreateEInvoice;
|
||||
use App\Jobs\EDocument\CreateEDocument;
|
||||
use App\Models\Company;
|
||||
use App\Models\CreditInvitation;
|
||||
use App\Models\Invoice;
|
||||
@ -216,7 +216,7 @@ class PdfService
|
||||
{
|
||||
try {
|
||||
|
||||
$e_rechnung = (new CreateEInvoice($this->config->entity, true))->handle();
|
||||
$e_rechnung = (new CreateEDocument($this->config->entity, true))->handle();
|
||||
$pdfBuilder = new ZugferdDocumentPdfBuilder($e_rechnung, $pdf);
|
||||
$pdfBuilder->generateDocument();
|
||||
|
||||
|
@ -11,8 +11,11 @@
|
||||
|
||||
namespace App\Services\PurchaseOrder;
|
||||
|
||||
use App\Jobs\EDocument\CreateEDocument;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class PurchaseOrderService
|
||||
{
|
||||
@ -75,6 +78,32 @@ class PurchaseOrderService
|
||||
return (new GetPurchaseOrderPdf($this->purchase_order, $contact))->run();
|
||||
}
|
||||
|
||||
public function getEPurchaseOrder($contact = null)
|
||||
{
|
||||
return (new CreateEDocument($this->purchase_order))->handle();
|
||||
}
|
||||
public function deleteEPurchaseOrder()
|
||||
{
|
||||
$this->purchase_order->load('invitations');
|
||||
|
||||
$this->purchase_order->invitations->each(function ($invitation) {
|
||||
try {
|
||||
// if (Storage::disk(config('filesystems.default'))->exists($this->invoice->client->e_invoice_filepath($invitation).$this->invoice->getFileName("xml"))) {
|
||||
Storage::disk(config('filesystems.default'))->delete($this->purchase_order->vendor->e_document_filepath($invitation).$this->purchase_order->getFileName("xml"));
|
||||
// }
|
||||
|
||||
// if (Ninja::isHosted() && Storage::disk('public')->exists($this->invoice->client->e_invoice_filepath($invitation).$this->invoice->getFileName("xml"))) {
|
||||
if (Ninja::isHosted()) {
|
||||
Storage::disk('public')->delete($this->purchase_order->vendor->e_document_filepath($invitation).$this->purchase_order->getFileName("xml"));
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
nlog($e->getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setStatus($status)
|
||||
{
|
||||
$this->purchase_order->status_id = $status;
|
||||
|
@ -13,6 +13,7 @@ namespace App\Services\Quote;
|
||||
|
||||
use App\Events\Quote\QuoteWasApproved;
|
||||
use App\Exceptions\QuoteConversion;
|
||||
use App\Jobs\EDocument\CreateEDocument;
|
||||
use App\Models\Project;
|
||||
use App\Models\Quote;
|
||||
use App\Repositories\QuoteRepository;
|
||||
@ -72,6 +73,11 @@ class QuoteService
|
||||
return (new GetQuotePdf($this->quote, $contact))->run();
|
||||
}
|
||||
|
||||
public function getEQuote($contact = null)
|
||||
{
|
||||
return (new CreateEDocument($this->quote))->handle();
|
||||
}
|
||||
|
||||
public function sendEmail($contact = null): self
|
||||
{
|
||||
$send_email = new SendEmail($this->quote, null, $contact);
|
||||
@ -226,6 +232,27 @@ class QuoteService
|
||||
|
||||
return $this;
|
||||
}
|
||||
public function deleteEQuote()
|
||||
{
|
||||
$this->quote->load('invitations');
|
||||
|
||||
$this->quote->invitations->each(function ($invitation) {
|
||||
try {
|
||||
// if (Storage::disk(config('filesystems.default'))->exists($this->invoice->client->e_invoice_filepath($invitation).$this->invoice->getFileName("xml"))) {
|
||||
Storage::disk(config('filesystems.default'))->delete($this->quote->client->e_document_filepath($invitation).$this->quote->getFileName("xml"));
|
||||
// }
|
||||
|
||||
// if (Ninja::isHosted() && Storage::disk('public')->exists($this->invoice->client->e_invoice_filepath($invitation).$this->invoice->getFileName("xml"))) {
|
||||
if (Ninja::isHosted()) {
|
||||
Storage::disk('public')->delete($this->quote->client->e_document_filepath($invitation).$this->quote->getFileName("xml"));
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
nlog($e->getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the quote.
|
||||
|
@ -92,7 +92,8 @@ class AccountTransformer extends EntityTransformer
|
||||
'account_sms_verified' => (bool) $account->account_sms_verified,
|
||||
'has_iap_plan' => (bool)$account->inapp_transaction_id,
|
||||
'tax_api_enabled' => (bool) config('services.tax.zip_tax.key') ? true : false,
|
||||
'nordigen_enabled' => (bool) (config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key')) ? true : false
|
||||
'nordigen_enabled' => (bool) (config('ninja.nordigen.secret_id') && config('ninja.nordigen.secret_key')) ? true : false,
|
||||
'upload_extensions' => (string) config('ninja.upload_extensions'),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -109,7 +109,10 @@ class UserTransformer extends EntityTransformer
|
||||
|
||||
$transformer = new CompanyUserTransformer($this->serializer);
|
||||
|
||||
$cu = $user->company_users()->whereCompanyId($user->company_id)->first();
|
||||
$cu = $user->company_users()->where('company_id',$user->company_id)->first();
|
||||
|
||||
if(!$cu)
|
||||
return null;
|
||||
|
||||
return $this->includeItem($cu, $transformer, CompanyUser::class);
|
||||
}
|
||||
|
@ -107,6 +107,7 @@ class VendorTransformer extends EntityTransformer
|
||||
'display_name' => (string) $vendor->present()->name(),
|
||||
'invoicing_email' => (string) $vendor->invoicing_email ?: '',
|
||||
'invoicing_domain' => (string) $vendor->invoicing_domain ?: '',
|
||||
'routing_id' => (string) $vendor->routing_id ?: '',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -56,6 +56,7 @@
|
||||
"hashids/hashids": "^4.0",
|
||||
"hedii/laravel-gelf-logger": "^8",
|
||||
"horstoeko/zugferd": "^1",
|
||||
"horstoeko/orderx": "^1",
|
||||
"imdhemy/laravel-purchases": "^1.7",
|
||||
"intervention/image": "^2.5",
|
||||
"invoiceninja/inspector": "^2.0",
|
||||
@ -115,7 +116,6 @@
|
||||
"barryvdh/laravel-ide-helper": "^2.13",
|
||||
"beyondcode/laravel-query-detector": "^1.8",
|
||||
"brianium/paratest": "^7",
|
||||
"fakerphp/faker": "^1.14",
|
||||
"filp/whoops": "^2.7",
|
||||
"friendsofphp/php-cs-fixer": "^3.14",
|
||||
"laracasts/cypress": "^3.0",
|
||||
|
201
composer.lock
generated
201
composer.lock
generated
@ -4,8 +4,48 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "fdea921aefca562c17db327acd8df062",
|
||||
"content-hash": "a7fb762c099a95d6168ef390844bb87b",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adrienrn/php-mimetyper",
|
||||
"version": "0.2.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/adrienrn/php-mimetyper.git",
|
||||
"reference": "702e00a604b4baed34d69730ce055e05c0f43932"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/adrienrn/php-mimetyper/zipball/702e00a604b4baed34d69730ce055e05c0f43932",
|
||||
"reference": "702e00a604b4baed34d69730ce055e05c0f43932",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"dflydev/apache-mime-types": "^1.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"MimeTyper\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Hussard",
|
||||
"email": "adrien.ricartnoblet@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "PHP mime type and extension mapping library: compatible with Symfony, powered by jshttp/mime-db",
|
||||
"support": {
|
||||
"issues": "https://github.com/adrienrn/php-mimetyper/issues",
|
||||
"source": "https://github.com/adrienrn/php-mimetyper/tree/0.2.2"
|
||||
},
|
||||
"time": "2018-09-27T09:45:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "afosto/yaac",
|
||||
"version": "v1.5.2",
|
||||
@ -2096,6 +2136,65 @@
|
||||
],
|
||||
"time": "2023-11-20T14:41:54+00:00"
|
||||
},
|
||||
{
|
||||
"name": "dflydev/apache-mime-types",
|
||||
"version": "v1.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dflydev/dflydev-apache-mime-types.git",
|
||||
"reference": "f30a57e59b7476e4c5270b6a0727d79c9c0eb861"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/dflydev/dflydev-apache-mime-types/zipball/f30a57e59b7476e4c5270b6a0727d79c9c0eb861",
|
||||
"reference": "f30a57e59b7476e4c5270b6a0727d79c9c0eb861",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"twig/twig": "1.*"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Dflydev\\ApacheMimeTypes": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Dragonfly Development Inc.",
|
||||
"email": "info@dflydev.com",
|
||||
"homepage": "http://dflydev.com"
|
||||
},
|
||||
{
|
||||
"name": "Beau Simensen",
|
||||
"email": "beau@dflydev.com",
|
||||
"homepage": "http://beausimensen.com"
|
||||
}
|
||||
],
|
||||
"description": "Apache MIME Types",
|
||||
"keywords": [
|
||||
"apache",
|
||||
"mime",
|
||||
"mimetypes"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/dflydev/dflydev-apache-mime-types/issues",
|
||||
"source": "https://github.com/dflydev/dflydev-apache-mime-types/tree/v1.0.1"
|
||||
},
|
||||
"time": "2013-05-14T02:02:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "dflydev/dot-access-data",
|
||||
"version": "v3.0.2",
|
||||
@ -4379,6 +4478,74 @@
|
||||
},
|
||||
"time": "2023-09-22T20:17:48+00:00"
|
||||
},
|
||||
{
|
||||
"name": "horstoeko/orderx",
|
||||
"version": "v1.0.20",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/horstoeko/orderx.git",
|
||||
"reference": "d8957cc0fea19b098d799a0c438a73504e7b326c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/horstoeko/orderx/zipball/d8957cc0fea19b098d799a0c438a73504e7b326c",
|
||||
"reference": "d8957cc0fea19b098d799a0c438a73504e7b326c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"adrienrn/php-mimetyper": "^0.2",
|
||||
"ext-simplexml": "*",
|
||||
"goetas-webservices/xsd2php-runtime": "^0.2.13",
|
||||
"horstoeko/stringmanagement": "^1",
|
||||
"jms/serializer": "^3",
|
||||
"php": "^7.3|^7.4|^8",
|
||||
"setasign/fpdf": "^1",
|
||||
"setasign/fpdi": "^2",
|
||||
"smalot/pdfparser": "^0",
|
||||
"symfony/validator": "^5|^6",
|
||||
"symfony/yaml": "^5|^6"
|
||||
},
|
||||
"require-dev": {
|
||||
"goetas-webservices/xsd2php": "^0",
|
||||
"pdepend/pdepend": "^2",
|
||||
"phploc/phploc": "^7",
|
||||
"phpmd/phpmd": "^2",
|
||||
"phpstan/phpstan": "^1.8",
|
||||
"phpunit/phpunit": "^9",
|
||||
"sebastian/phpcpd": "^6",
|
||||
"squizlabs/php_codesniffer": "^3"
|
||||
},
|
||||
"type": "package",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"horstoeko\\orderx\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Daniel Erling",
|
||||
"email": "daniel@erling.com.de",
|
||||
"role": "lead"
|
||||
}
|
||||
],
|
||||
"description": "A library for creating and reading Order-X document",
|
||||
"homepage": "https://github.com/horstoeko/orderx",
|
||||
"keywords": [
|
||||
"electronic",
|
||||
"order",
|
||||
"order-x",
|
||||
"orderx"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/horstoeko/orderx/issues",
|
||||
"source": "https://github.com/horstoeko/orderx/tree/v1.0.20"
|
||||
},
|
||||
"time": "2024-03-21T04:28:54+00:00"
|
||||
},
|
||||
{
|
||||
"name": "horstoeko/stringmanagement",
|
||||
"version": "v1.0.11",
|
||||
@ -4435,16 +4602,16 @@
|
||||
},
|
||||
{
|
||||
"name": "horstoeko/zugferd",
|
||||
"version": "v1.0.36",
|
||||
"version": "v1.0.37",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/horstoeko/zugferd.git",
|
||||
"reference": "0d15c305328c137365648fe1c34a584d877127fa"
|
||||
"reference": "05f58ad4dbcc23d767fceb15f46b46097ffd43f1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/horstoeko/zugferd/zipball/0d15c305328c137365648fe1c34a584d877127fa",
|
||||
"reference": "0d15c305328c137365648fe1c34a584d877127fa",
|
||||
"url": "https://api.github.com/repos/horstoeko/zugferd/zipball/05f58ad4dbcc23d767fceb15f46b46097ffd43f1",
|
||||
"reference": "05f58ad4dbcc23d767fceb15f46b46097ffd43f1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -4462,6 +4629,7 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"goetas-webservices/xsd2php": "^0",
|
||||
"nette/php-generator": "*",
|
||||
"pdepend/pdepend": "^2",
|
||||
"phploc/phploc": "^7",
|
||||
"phpmd/phpmd": "^2",
|
||||
@ -4502,9 +4670,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/horstoeko/zugferd/issues",
|
||||
"source": "https://github.com/horstoeko/zugferd/tree/v1.0.36"
|
||||
"source": "https://github.com/horstoeko/zugferd/tree/v1.0.37"
|
||||
},
|
||||
"time": "2024-03-11T04:34:59+00:00"
|
||||
"time": "2024-03-24T11:31:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "http-interop/http-factory-guzzle",
|
||||
@ -11638,24 +11806,27 @@
|
||||
},
|
||||
{
|
||||
"name": "smalot/pdfparser",
|
||||
"version": "v2.9.0",
|
||||
"version": "v0.19.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/smalot/pdfparser.git",
|
||||
"reference": "6b53144fcb24af77093d4150dd7d0dd571f25761"
|
||||
"reference": "1895c17417aefa4508e35836c46da61988d61f26"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/smalot/pdfparser/zipball/6b53144fcb24af77093d4150dd7d0dd571f25761",
|
||||
"reference": "6b53144fcb24af77093d4150dd7d0dd571f25761",
|
||||
"url": "https://api.github.com/repos/smalot/pdfparser/zipball/1895c17417aefa4508e35836c46da61988d61f26",
|
||||
"reference": "1895c17417aefa4508e35836c46da61988d61f26",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-iconv": "*",
|
||||
"ext-zlib": "*",
|
||||
"php": ">=7.1",
|
||||
"php": ">=5.6",
|
||||
"symfony/polyfill-mbstring": "^1.18"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^2.16",
|
||||
"symfony/phpunit-bridge": "^5.2"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
@ -11683,9 +11854,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/smalot/pdfparser/issues",
|
||||
"source": "https://github.com/smalot/pdfparser/tree/v2.9.0"
|
||||
"source": "https://github.com/smalot/pdfparser/tree/v0.19.0"
|
||||
},
|
||||
"time": "2024-03-01T09:51:10+00:00"
|
||||
"time": "2021-04-13T08:27:56+00:00"
|
||||
},
|
||||
{
|
||||
"name": "socialiteproviders/apple",
|
||||
|
@ -1,98 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
/*
|
||||
* This value will be sent along with your trace.
|
||||
*
|
||||
* When set to `null`, the app name will be used
|
||||
*/
|
||||
'default_trace_name' => null,
|
||||
|
||||
/*
|
||||
* A driver is responsible for transmitting any measurements.
|
||||
*/
|
||||
'drivers' => [
|
||||
Spatie\OpenTelemetry\Drivers\HttpDriver::class => [
|
||||
'url' => 'http://localhost:9411/api/v2/spans',
|
||||
// 'url' => 'http://localhost:4318/v1/traces'
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
* This class determines if your measurements should actually be sent
|
||||
* to the reporting drivers.
|
||||
*/
|
||||
'sampler' => Spatie\OpenTelemetry\Support\Samplers\AlwaysSampler::class,
|
||||
|
||||
/*
|
||||
* Tags can be added to any measurement. These classes will determine the
|
||||
* values of the tags when a new trace starts.
|
||||
*/
|
||||
'trace_tag_providers' => [
|
||||
\Spatie\OpenTelemetry\Support\TagProviders\DefaultTagsProvider::class,
|
||||
],
|
||||
|
||||
/*
|
||||
* Tags can be added to any measurement. These classes will determine the
|
||||
* values of the tags when a new span starts.
|
||||
*/
|
||||
'span_tag_providers' => [
|
||||
|
||||
],
|
||||
|
||||
'queue' => [
|
||||
/*
|
||||
* When enabled, any measurements (spans) you make in a queued job that implements
|
||||
* `TraceAware` will automatically belong to the same trace that was
|
||||
* started in the process that dispatched the job.
|
||||
*/
|
||||
'make_queue_trace_aware' => true,
|
||||
|
||||
/*
|
||||
* When this is set to `false`, only jobs the implement
|
||||
* `TraceAware` will be trace aware.
|
||||
*/
|
||||
'all_jobs_are_trace_aware_by_default' => true,
|
||||
|
||||
/*
|
||||
* When set to `true` all jobs will
|
||||
* automatically start a span.
|
||||
*/
|
||||
'all_jobs_auto_start_a_span' => true,
|
||||
|
||||
/*
|
||||
* These jobs will be trace aware even if they don't
|
||||
* implement the `TraceAware` interface.
|
||||
*/
|
||||
'trace_aware_jobs' => [
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
* These jobs will never trace aware, regardless of `all_jobs_are_trace_aware_by_default`.
|
||||
*/
|
||||
'not_trace_aware_jobs' => [
|
||||
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
* These actions can be overridden to have fine-grained control over how
|
||||
* the package performs certain tasks.
|
||||
*
|
||||
* In most cases, you should use the default values.
|
||||
*/
|
||||
'actions' => [
|
||||
'make_queue_trace_aware' => Spatie\OpenTelemetry\Actions\MakeQueueTraceAwareAction::class,
|
||||
],
|
||||
|
||||
/*
|
||||
* This class determines how the package measures time.
|
||||
*/
|
||||
'stopwatch' => Spatie\OpenTelemetry\Support\Stopwatch::class,
|
||||
|
||||
/*
|
||||
* This class generates IDs for traces and spans.
|
||||
*/
|
||||
'id_generator' => Spatie\OpenTelemetry\Support\IdGenerator::class,
|
||||
];
|
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
|
||||
Schema::table('vendors', function (Blueprint $table) {
|
||||
$table->string('routing_id')->nullable();
|
||||
});
|
||||
|
||||
\App\Models\Company::query()
|
||||
->cursor()
|
||||
->each(function ($c){
|
||||
$settings = $c->settings;
|
||||
$settings->e_quote_type = 'OrderX_Comfort';
|
||||
$settings->enable_rappen_rounding = false;
|
||||
|
||||
$c->settings = $settings;
|
||||
$c->save();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
|
||||
}
|
||||
};
|
@ -461,8 +461,8 @@ $lang = array(
|
||||
'delete_token' => 'Delete Token',
|
||||
'token' => 'Token',
|
||||
'add_gateway' => 'Add Payment Gateway',
|
||||
'delete_gateway' => 'Delete Gateway',
|
||||
'edit_gateway' => 'Edit Gateway',
|
||||
'delete_gateway' => 'Delete Payment Gateway',
|
||||
'edit_gateway' => 'Edit Payment Gateway',
|
||||
'updated_gateway' => 'Successfully updated gateway',
|
||||
'created_gateway' => 'Successfully created gateway',
|
||||
'deleted_gateway' => 'Successfully deleted gateway',
|
||||
@ -5104,6 +5104,8 @@ $lang = array(
|
||||
'drop_files_here' => 'Drop files here',
|
||||
'upload_files' => 'Upload Files',
|
||||
'download_e_invoice' => 'Download E-Invoice',
|
||||
'download_e_credit' => 'Download E-Credit',
|
||||
'download_e_quote' => 'Download E-Quote',
|
||||
'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',
|
||||
@ -5262,8 +5264,13 @@ $lang = array(
|
||||
'purchase_order_items' => 'Purchase Order Items',
|
||||
'csv_rows_length' => 'No data found in this CSV file',
|
||||
'accept_payments_online' => 'Accept Payments Online',
|
||||
'all_payment_gateways' => 'View all payment gateways',
|
||||
'all_payment_gateways' => 'View all payment gateways',
|
||||
'product_cost' => 'Product cost',
|
||||
'enable_rappen_roudning' => 'Enable Rappen Rounding',
|
||||
'enable_rappen_rounding_help' => 'Rounds totals to nearest 5',
|
||||
'duration_words' => 'Duration in words',
|
||||
'upcoming_recurring_invoices' => 'Upcoming Recurring Invoices',
|
||||
'total_invoices' => 'Total Invoices',
|
||||
);
|
||||
|
||||
return $lang;
|
||||
|
@ -2194,6 +2194,8 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette
|
||||
'encryption' => 'Cryptage',
|
||||
'mailgun_domain' => 'Domaine Mailgun',
|
||||
'mailgun_private_key' => 'Clé privée Mailgun',
|
||||
'brevo_domain' => 'Domaine Brevo',
|
||||
'brevo_private_key' => 'Clé privée Brevo',
|
||||
'send_test_email' => 'Envoyer un courriel test',
|
||||
'select_label' => 'Sélectionnez le libellé',
|
||||
'label' => 'Libellé',
|
||||
@ -4844,6 +4846,7 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette
|
||||
'email_alignment' => 'Justification du courriel',
|
||||
'pdf_preview_location' => 'Emplacement de prévisualisation du PDF',
|
||||
'mailgun' => 'Mailgun',
|
||||
'brevo' => 'Brevo',
|
||||
'postmark' => 'Postmark',
|
||||
'microsoft' => 'Microsoft',
|
||||
'click_plus_to_create_record' => 'Cliquez sur + pour créer un enregistrement',
|
||||
@ -5096,6 +5099,8 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette
|
||||
'drop_files_here' => 'Déposez les fichiers ici',
|
||||
'upload_files' => 'Téléverser les fichiers',
|
||||
'download_e_invoice' => 'Télécharger la facture électronique',
|
||||
'download_e_credit' => 'Télécharger E-Credit',
|
||||
'download_e_quote' => 'Télécharger E-Quote',
|
||||
'triangular_tax_info' => 'Transactions intra-communautaire triangulaire',
|
||||
'intracommunity_tax_info' => 'Livraison intra-communautaure sans taxe',
|
||||
'reverse_tax_info' => 'Veuillez noter que cette provision est sujette à une charge renversée',
|
||||
@ -5253,6 +5258,9 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette
|
||||
'select_email_provider' => 'Définir le courriel pour l\'envoi',
|
||||
'purchase_order_items' => 'Articles du bon d\'achat',
|
||||
'csv_rows_length' => 'Aucune donnée dans ce fichier CSV',
|
||||
'accept_payments_online' => 'Accepter les paiements en ligne',
|
||||
'all_payment_gateways' => 'Voir toutes les passerelles de paiements',
|
||||
'product_cost' => 'Coût du produit',
|
||||
);
|
||||
|
||||
return $lang;
|
||||
|
2
package-lock.json
generated
2
package-lock.json
generated
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@invoiceninja/invoiceninja",
|
||||
"name": "invoiceninja",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -9,7 +9,7 @@
|
||||
]
|
||||
},
|
||||
"resources/js/app.js": {
|
||||
"file": "assets/app-b98bbdda.js",
|
||||
"file": "assets/app-c80ec97e.js",
|
||||
"imports": [
|
||||
"_index-08e160a7.js",
|
||||
"__commonjsHelpers-725317a4.js"
|
||||
@ -240,7 +240,7 @@
|
||||
"src": "resources/js/setup/setup.js"
|
||||
},
|
||||
"resources/sass/app.scss": {
|
||||
"file": "assets/app-17bd8d2c.css",
|
||||
"file": "assets/app-91a05c24.css",
|
||||
"isEntry": true,
|
||||
"src": "resources/sass/app.scss"
|
||||
}
|
||||
|
@ -20,6 +20,40 @@
|
||||
</div>
|
||||
</button>
|
||||
@endif
|
||||
@if($entity_type == 'credit' && $settings->enable_e_invoice)
|
||||
<button wire:loading.attr="disabled" wire:click="downloadECreit" class="bg-primary text-white px-4 py-4 lg:px-2 lg:py-2 rounded" type="button">
|
||||
<span>{{ ctrans('texts.download_e_credit') }}</span>
|
||||
<div wire:loading wire:target="downloadECredit">
|
||||
<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
|
||||
@if($entity_type == 'quote' && $settings->enable_e_invoice)
|
||||
<button wire:loading.attr="disabled" wire:click="downloadEQuote" class="bg-primary text-white px-4 py-4 lg:px-2 lg:py-2 rounded" type="button">
|
||||
<span>{{ ctrans('texts.download_e_quote') }}</span>
|
||||
<div wire:loading wire:target="downloadEQuote">
|
||||
<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
|
||||
{{-- Not implemented yet--}}
|
||||
{{-- @if($entity_type == 'purchase_order' && $settings->enable_e_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>
|
||||
@if($html_entity_option)
|
||||
<div class="hidden lg:block">
|
||||
|
113
resources/views/portal/ninja2020/dashboard/index-old.blade.php
Normal file
113
resources/views/portal/ninja2020/dashboard/index-old.blade.php
Normal file
@ -0,0 +1,113 @@
|
||||
@extends('portal.ninja2020.layout.app')
|
||||
@section('meta_title', ctrans('texts.dashboard'))
|
||||
|
||||
@section('header')
|
||||
@if(!empty($client->getSetting('custom_message_dashboard')))
|
||||
@component('portal.ninja2020.components.message')
|
||||
{!! CustomMessage::client($client)
|
||||
->company($client->company)
|
||||
->message($client->getSetting('custom_message_dashboard')) !!}
|
||||
@endcomponent
|
||||
@endif
|
||||
@endsection
|
||||
|
||||
@section('body')
|
||||
<div>
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||
{{ ctrans('texts.hello') }}, {{ $contact->first_name }}
|
||||
</h3>
|
||||
|
||||
<div class="mt-5 grid grid-cols-1 gap-5 sm:grid-cols-2">
|
||||
<div class="bg-white overflow-hidden shadow rounded">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<dl>
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500 truncate">
|
||||
{{ ctrans('texts.paid_to_date') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-3xl leading-9 font-semibold text-gray-900">
|
||||
{{ App\Utils\Number::formatMoney($client->paid_to_date, $client) }}
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white overflow-hidden shadow rounded">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<dl>
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500 truncate">
|
||||
{{ ctrans('texts.open_balance') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-3xl leading-9 font-semibold text-gray-900">
|
||||
{{ App\Utils\Number::formatMoney($client->balance, $client) }}
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid md:grid-cols-12 gap-4 mt-6">
|
||||
<div class="col-span-6">
|
||||
<div class="bg-white rounded shadow px-4 py-5 border-b border-gray-200 sm:px-6">
|
||||
<div class="-ml-4 -mt-4 flex justify-between items-center flex-wrap sm:flex-nowrap">
|
||||
<div class="ml-4 mt-4 w-full">
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900 mb-4 capitalize">
|
||||
{{ ctrans('texts.group_documents') }}
|
||||
</h3>
|
||||
|
||||
<div class="flex flex-col h-auto overflow-y-auto">
|
||||
@if($client->group_settings)
|
||||
@forelse($client->group_settings->documents as $document)
|
||||
<a href="{{ route('client.documents.show', $document->hashed_id) }}" target="_blank"
|
||||
class="block inline-flex items-center text-sm button-link text-primary">
|
||||
<span>{{ Illuminate\Support\Str::limit($document->name, 40) }}</span>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" class="ml-2 text-primary h-6 w-4">
|
||||
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
|
||||
<polyline points="15 3 21 3 21 9"></polyline>
|
||||
<line x1="10" y1="14" x2="21" y2="3"></line>
|
||||
</svg>
|
||||
</a>
|
||||
@empty
|
||||
<p class="text-sm">{{ ctrans('texts.no_records_found') }}.</p>
|
||||
@endforelse
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-6">
|
||||
<div class="bg-white rounded shadow px-4 py-5 border-b border-gray-200 sm:px-6">
|
||||
<div class="-ml-4 -mt-4 flex justify-between items-center flex-wrap sm:flex-nowrap">
|
||||
<div class="ml-4 mt-4 w-full">
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900 mb-4 capitalize">
|
||||
{{ ctrans('texts.default_documents') }}
|
||||
</h3>
|
||||
|
||||
<div class="flex flex-col h-auto overflow-y-auto">
|
||||
@forelse($client->company->documents as $document)
|
||||
<a href="{{ route('client.documents.show', $document->hashed_id) }}" target="_blank"
|
||||
class="block inline-flex items-center text-sm button-link text-primary">
|
||||
<span>{{ Illuminate\Support\Str::limit($document->name, 40) }}</span>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" class="ml-2 text-primary h-6 w-4">
|
||||
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
|
||||
<polyline points="15 3 21 3 21 9"></polyline>
|
||||
<line x1="10" y1="14" x2="21" y2="3"></line>
|
||||
</svg>
|
||||
</a>
|
||||
@empty
|
||||
<p class="text-sm">{{ ctrans('texts.no_records_found') }}.</p>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
@ -1,113 +1,96 @@
|
||||
@extends('portal.ninja2020.layout.app')
|
||||
@section('meta_title', ctrans('texts.dashboard'))
|
||||
|
||||
@section('header')
|
||||
@if(!empty($client->getSetting('custom_message_dashboard')))
|
||||
@component('portal.ninja2020.components.message')
|
||||
{!! CustomMessage::client($client)
|
||||
->company($client->company)
|
||||
->message($client->getSetting('custom_message_dashboard')) !!}
|
||||
@endcomponent
|
||||
@endif
|
||||
@endsection
|
||||
|
||||
@section('body')
|
||||
<div>
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||
{{ ctrans('texts.hello') }}, {{ $contact->first_name }}
|
||||
</h3>
|
||||
<div class="flex flex-col xl:flex-row gap-4">
|
||||
<div class="w-full rounded-md border border-[#E5E7EB] bg-white p-5 text-sm text-[#6C727F]">
|
||||
<h3 class="mb-4 text-xl font-semibold text-[#212529]">{{ $contact->first_name }} {{ $contact->last_name }}</h3>
|
||||
<p class="mb-1.5">{{ $contact->phone }}</p>
|
||||
<p class="mb-4">{{ $client->address1 }}</p>
|
||||
<p class="mb-1.5">{{ $client->city }}, {{ $client->state }}</p>
|
||||
<p class="mb-1.5">{{ $client->postal_code }}</p>
|
||||
<p>{{ App\Models\Country::find($client->country_id)?->name }}</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-5 grid grid-cols-1 gap-5 sm:grid-cols-2">
|
||||
<div class="bg-white overflow-hidden shadow rounded">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<dl>
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500 truncate">
|
||||
{{ ctrans('texts.paid_to_date') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-3xl leading-9 font-semibold text-gray-900">
|
||||
{{ App\Utils\Number::formatMoney($client->paid_to_date, $client) }}
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="w-full flex flex-row items-center rounded-md border border-[#E5E7EB] bg-white p-5 md:flex-col md:justify-center">
|
||||
<div class="bg-blue-light mr-3 flex h-12 w-12 items-center justify-center rounded md:mb-6 md:mr-0">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="25" height="24" viewBox="0 0 25 24" fill="none">
|
||||
<path d="M14 9.75C14 9.55109 13.921 9.36032 13.7803 9.21967C13.6397 9.07902 13.4489 9 13.25 9H7.25C7.05109 9 6.86032 9.07902 6.71967 9.21967C6.57902 9.36032 6.5 9.55109 6.5 9.75C6.5 9.94891 6.57902 10.1397 6.71967 10.2803C6.86032 10.421 7.05109 10.5 7.25 10.5H13.25C13.4489 10.5 13.6397 10.421 13.7803 10.2803C13.921 10.1397 14 9.94891 14 9.75ZM13 12.75C13 12.5511 12.921 12.3603 12.7803 12.2197C12.6397 12.079 12.4489 12 12.25 12H7.25C7.05109 12 6.86032 12.079 6.71967 12.2197C6.57902 12.3603 6.5 12.5511 6.5 12.75C6.5 12.9489 6.57902 13.1397 6.71967 13.2803C6.86032 13.421 7.05109 13.5 7.25 13.5H12.25C12.4489 13.5 12.6397 13.421 12.7803 13.2803C12.921 13.1397 13 12.9489 13 12.75ZM13.25 15C13.4489 15 13.6397 15.079 13.7803 15.2197C13.921 15.3603 14 15.5511 14 15.75C14 15.9489 13.921 16.1397 13.7803 16.2803C13.6397 16.421 13.4489 16.5 13.25 16.5H7.25C7.05109 16.5 6.86032 16.421 6.71967 16.2803C6.57902 16.1397 6.5 15.9489 6.5 15.75C6.5 15.5511 6.57902 15.3603 6.71967 15.2197C6.86032 15.079 7.05109 15 7.25 15H13.25Z" fill="{{ $settings->primary_color }}" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.5 21.7499H19.5C20.2293 21.7499 20.9288 21.4601 21.4445 20.9444C21.9603 20.4287 22.25 19.7292 22.25 18.9999V13.4999C22.25 13.301 22.171 13.1102 22.0303 12.9695C21.8897 12.8289 21.6989 12.7499 21.5 12.7499H18.25V4.94287C18.25 3.51987 16.641 2.69188 15.483 3.51888L15.308 3.64388C14.9248 3.9159 14.4663 4.0617 13.9964 4.06099C13.5264 4.06027 13.0684 3.91307 12.686 3.63988C12.0476 3.18554 11.2835 2.94141 10.5 2.94141C9.71645 2.94141 8.95238 3.18554 8.314 3.63988C7.93162 3.91307 7.47359 4.06027 7.00364 4.06099C6.53369 4.0617 6.07521 3.9159 5.692 3.64388L5.517 3.51888C4.359 2.69188 2.75 3.51887 2.75 4.94287V17.9999C2.75 18.9944 3.14509 19.9483 3.84835 20.6515C4.55161 21.3548 5.50544 21.7499 6.5 21.7499ZM9.186 4.85988C9.56995 4.58732 10.0291 4.4409 10.5 4.4409C10.9709 4.4409 11.4301 4.58732 11.814 4.85988C12.4507 5.31499 13.2136 5.56009 13.9962 5.56099C14.7788 5.56188 15.5423 5.31853 16.18 4.86487L16.355 4.73987C16.3923 4.71328 16.4363 4.69747 16.482 4.69418C16.5277 4.69088 16.5735 4.70022 16.6143 4.72117C16.6551 4.74213 16.6893 4.7739 16.7132 4.813C16.7372 4.8521 16.7499 4.89703 16.75 4.94287V18.9999C16.75 19.4499 16.858 19.8749 17.05 20.2499H6.5C5.90326 20.2499 5.33097 20.0128 4.90901 19.5909C4.48705 19.1689 4.25 18.5966 4.25 17.9999V4.94287C4.25012 4.89703 4.26284 4.8521 4.28678 4.813C4.31072 4.7739 4.34495 4.74213 4.38573 4.72117C4.4265 4.70022 4.47226 4.69088 4.51798 4.69418C4.56371 4.69747 4.60765 4.71328 4.645 4.73987L4.82 4.86487C5.45775 5.31853 6.22116 5.56188 7.0038 5.56099C7.78644 5.56009 8.54929 5.31499 9.186 4.85988ZM18.25 18.9999V14.2499H20.75V18.9999C20.75 19.3314 20.6183 19.6493 20.3839 19.8838C20.1495 20.1182 19.8315 20.2499 19.5 20.2499C19.1685 20.2499 18.8505 20.1182 18.6161 19.8838C18.3817 19.6493 18.25 19.3314 18.25 18.9999Z" fill="{{ $settings->primary_color }}" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="bg-white overflow-hidden shadow rounded">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<dl>
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500 truncate">
|
||||
{{ ctrans('texts.open_balance') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-3xl leading-9 font-semibold text-gray-900">
|
||||
{{ App\Utils\Number::formatMoney($client->balance, $client) }}
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="md:text-center">
|
||||
<p class="text-light-grey-text mb-2 text-xs md:text-sm">{{ ctrans('texts.total_invoices') }}</p>
|
||||
<p class="text-2xl font-semibold text-[#212529] md:text-[32px]">
|
||||
{{ App\Utils\Number::formatMoney($total_invoices, $client) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row items-center rounded-md border border-[#E5E7EB] bg-white p-5 md:flex-col md:justify-center w-full">
|
||||
<div class="bg-blue-light mr-3 flex h-12 w-12 items-center justify-center rounded md:mb-6 md:mr-0">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="21" height="20" viewBox="0 0 21 20" fill="none">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.20216 6.89268C7.96951 7.13715 7.9 7.34117 7.9 7.5C7.9 7.65883 7.96951 7.86285 8.20216 8.10732C8.43825 8.35539 8.81295 8.61064 9.32952 8.84023C10.3609 9.29862 11.8348 9.6 13.5 9.6C15.1652 9.6 16.6391 9.29862 17.6705 8.84023C18.187 8.61064 18.5618 8.35539 18.7978 8.10732C19.0305 7.86285 19.1 7.65883 19.1 7.5C19.1 7.34117 19.0305 7.13715 18.7978 6.89268C18.5618 6.64461 18.187 6.38936 17.6705 6.15977C16.6391 5.70138 15.1652 5.4 13.5 5.4C11.8348 5.4 10.3609 5.70138 9.32952 6.15977C8.81295 6.38936 8.43825 6.64461 8.20216 6.89268ZM8.76093 4.88043C10.0097 4.32542 11.6858 4 13.5 4C15.3142 4 16.9903 4.32542 18.2391 4.88043C18.8626 5.15755 19.4105 5.50564 19.812 5.92754C20.2169 6.35305 20.5 6.88563 20.5 7.5C20.5 8.11437 20.2169 8.64695 19.812 9.07246C19.4105 9.49436 18.8626 9.84246 18.2391 10.1196C16.9903 10.6746 15.3142 11 13.5 11C11.6858 11 10.0097 10.6746 8.76093 10.1196C8.13743 9.84246 7.58952 9.49436 7.18801 9.07246C6.78307 8.64695 6.5 8.11437 6.5 7.5C6.5 6.88563 6.78307 6.35305 7.18801 5.92754C7.58952 5.50564 8.13743 5.15755 8.76093 4.88043Z" fill="{{ $settings->primary_color }}" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.2 7C7.5866 7 7.9 7.32335 7.9 7.72222V16.3889C7.9 16.552 7.96903 16.762 8.20027 17.0138C8.4349 17.2692 8.80769 17.5325 9.32271 17.7696C10.351 18.243 11.8245 18.5556 13.5 18.5556C15.1755 18.5556 16.649 18.243 17.6773 17.7696C18.1923 17.5325 18.5651 17.2692 18.7997 17.0138C19.031 16.762 19.1 16.552 19.1 16.3889V7.72222C19.1 7.32335 19.4134 7 19.8 7C20.1866 7 20.5 7.32335 20.5 7.72222V16.3889C20.5 17.0202 20.219 17.5685 19.8159 18.0074C19.4161 18.4426 18.8702 18.8022 18.2477 19.0887C17.001 19.6626 15.3245 20 13.5 20C11.6755 20 9.99897 19.6626 8.75229 19.0887C8.12981 18.8022 7.58385 18.4426 7.18411 18.0074C6.78097 17.5685 6.5 17.0202 6.5 16.3889V7.72222C6.5 7.32335 6.8134 7 7.2 7Z" fill="{{ $settings->primary_color }}" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.62478 0.0127665C9.72231 -0.0922152 11.802 0.452205 13.5894 1.57442C13.9251 1.7852 14.0293 2.23294 13.8221 2.57448C13.615 2.91602 13.1749 3.02202 12.8392 2.81125C11.2931 1.84058 9.49357 1.37113 7.67921 1.4652C7.6671 1.46583 7.65498 1.46614 7.64286 1.46614C5.94066 1.46614 4.43684 1.78055 3.3853 2.25712C2.85861 2.49582 2.4767 2.76099 2.23613 3.01836C1.9989 3.27215 1.92857 3.4832 1.92857 3.64622C1.92857 3.81909 2.0091 4.05001 2.29175 4.33065C2.57662 4.6135 3.02405 4.90009 3.62983 5.15464C3.99444 5.30786 4.16793 5.73278 4.01733 6.10372C3.86673 6.47467 3.44907 6.65117 3.08446 6.49795C2.37595 6.20023 1.75195 5.82552 1.29396 5.37078C0.833752 4.91383 0.5 4.33085 0.5 3.64622C0.5 3.00988 0.788603 2.4579 1.20092 2.01679C1.60991 1.57926 2.16817 1.21766 2.80399 0.929505C4.0733 0.354242 5.7768 0.0149655 7.62478 0.0127665ZM6.92857 11.6398C7.32306 11.6398 7.64286 11.9652 7.64286 12.3665C7.64286 12.5307 7.71329 12.742 7.94925 12.9953C8.18867 13.2523 8.56907 13.5173 9.0946 13.7558C10.1439 14.2321 11.6475 14.5466 13.3571 14.5466C15.0668 14.5466 16.5704 14.2321 17.6197 13.7558C18.1452 13.5173 18.5256 13.2523 18.765 12.9953C19.001 12.742 19.0714 12.5307 19.0714 12.3665C19.0714 11.9652 19.3912 11.6398 19.7857 11.6398C20.1802 11.6398 20.5 11.9652 20.5 12.3665C20.5 13.0017 20.2133 13.5535 19.8019 13.9951C19.394 14.4329 18.8369 14.7948 18.2017 15.0831C16.9296 15.6605 15.2189 16 13.3571 16C11.4954 16 9.78466 15.6605 8.51254 15.0831C7.87735 14.7948 7.32026 14.4329 6.91235 13.9951C6.50099 13.5535 6.21429 13.0017 6.21429 12.3665C6.21429 11.9652 6.53408 11.6398 6.92857 11.6398Z" fill="{{ $settings->primary_color }}" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.29996 3C1.74176 3 2.09992 3.31603 2.09992 3.70587V12.1763C2.09992 12.3442 2.19011 12.5685 2.50666 12.8411C2.82569 13.1159 3.32679 13.3943 4.00522 13.6415C4.41357 13.7904 4.60787 14.2031 4.4392 14.5634C4.27054 14.9237 3.80278 15.0952 3.39444 14.9464C2.60096 14.6572 1.90212 14.2932 1.38919 13.8515C0.873783 13.4076 0.5 12.8413 0.5 12.1763V3.70587C0.5 3.31603 0.858153 3 1.29996 3Z" fill="{{ $settings->primary_color }}" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.29996 7C1.74176 7 2.09992 7.35815 2.09992 7.79996C2.09992 7.99025 2.19011 8.24445 2.50666 8.55339C2.82569 8.86476 3.32679 9.18024 4.00522 9.46046C4.41357 9.62913 4.60787 10.0969 4.4392 10.5052C4.27054 10.9136 3.80278 11.1079 3.39444 10.9392C2.60096 10.6115 1.90212 10.199 1.38919 9.69838C0.873783 9.19536 0.5 8.55361 0.5 7.79996C0.5 7.35815 0.858153 7 1.29996 7Z" fill="{{ $settings->primary_color }}" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="md:text-center">
|
||||
<p class="text-light-grey-text mb-2 text-xs md:text-sm">{{ ctrans('texts.paid_to_date') }}</p>
|
||||
<p class="text-2xl font-semibold text-[#212529] md:text-[32px]">
|
||||
{{ App\Utils\Number::formatMoney($client->paid_to_date, $client) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row items-center rounded-md border border-[#E5E7EB] bg-white p-5 md:flex-col md:justify-center w-full">
|
||||
<div class="bg-blue-light mr-3 flex h-12 w-12 items-center justify-center rounded md:mb-6 md:mr-0">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="23" height="22" viewBox="0 0 23 22" fill="none">
|
||||
<path d="M11.5 8.5V1V8.5ZM5.5 10L11.5 8.5L17.5 7M13.5 14L17.5 7L13.5 14ZM21.5 14L17.5 7L21.5 14ZM9.5 17L5.5 10L9.5 17ZM1.5 17L5.5 10L1.5 17Z" fill="{{ $settings->primary_color }}" />
|
||||
<path d="M11.5 8.5V1M11.5 8.5L5.5 10M11.5 8.5L17.5 7M5.5 10L9.5 17M5.5 10L1.5 17M17.5 7L13.5 14M17.5 7L21.5 14" stroke="{{ $settings->primary_color }}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M5.5 21C6.56087 21 7.57828 20.5786 8.32843 19.8284C9.07857 19.0783 9.5 18.0609 9.5 17H1.5C1.5 18.0609 1.92143 19.0783 2.67157 19.8284C3.42172 20.5786 4.43913 21 5.5 21ZM17.5 18C18.5609 18 19.5783 17.5786 20.3284 16.8284C21.0786 16.0783 21.5 15.0609 21.5 14H13.5C13.5 15.0609 13.9214 16.0783 14.6716 16.8284C15.4217 17.5786 16.4391 18 17.5 18Z" stroke="{{ $settings->primary_color }}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="md:text-center">
|
||||
<p class="text-light-grey-text mb-2 text-xs md:text-sm">
|
||||
{{ ctrans('texts.open_balance') }}
|
||||
</p>
|
||||
<p class="text-2xl font-semibold text-[#212529] md:text-[32px]">
|
||||
{{ App\Utils\Number::formatMoney($client->balance, $client) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid md:grid-cols-12 gap-4 mt-6">
|
||||
<div class="col-span-6">
|
||||
<div class="bg-white rounded shadow px-4 py-5 border-b border-gray-200 sm:px-6">
|
||||
<div class="-ml-4 -mt-4 flex justify-between items-center flex-wrap sm:flex-nowrap">
|
||||
<div class="ml-4 mt-4 w-full">
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900 mb-4 capitalize">
|
||||
{{ ctrans('texts.group_documents') }}
|
||||
</h3>
|
||||
|
||||
<div class="flex flex-col h-auto overflow-y-auto">
|
||||
@if($client->group_settings)
|
||||
@forelse($client->group_settings->documents as $document)
|
||||
<a href="{{ route('client.documents.show', $document->hashed_id) }}" target="_blank"
|
||||
class="block inline-flex items-center text-sm button-link text-primary">
|
||||
<span>{{ Illuminate\Support\Str::limit($document->name, 40) }}</span>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" class="ml-2 text-primary h-6 w-4">
|
||||
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
|
||||
<polyline points="15 3 21 3 21 9"></polyline>
|
||||
<line x1="10" y1="14" x2="21" y2="3"></line>
|
||||
</svg>
|
||||
</a>
|
||||
@empty
|
||||
<p class="text-sm">{{ ctrans('texts.no_records_found') }}.</p>
|
||||
@endforelse
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-stretch rounded-md border border-[#E5E7EB] bg-white p-4 md:gap-y-6 xl:flex-nowrap mt-4">
|
||||
<div class="flex basis-1/2 items-center xl:basis-auto xl:border-r xl:border-[#E5E7EB] xl:pr-20">
|
||||
<p class="text-base font-semibold text-[#212529]">{{ ctrans('texts.invoice_from') }}</p>
|
||||
</div>
|
||||
<div class="flex w-full xl:w-auto mt-2 xl:mt-0 items-center xl:basis-auto xl:justify-center xl:border-r xl:border-[#E5E7EB] xl:px-20">
|
||||
<div class="flex items-center">
|
||||
<div class="h-6 w-6 overflow-hidden rounded">
|
||||
<img src="{{ $client->company->getLogo() }}" alt="company-logo" class="h-fit w-full" />
|
||||
</div>
|
||||
<div class="pl-1.5">
|
||||
<p class="text-xs font-semibold leading-normal text-black">
|
||||
{{ $client->company->settings->name }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-6">
|
||||
<div class="bg-white rounded shadow px-4 py-5 border-b border-gray-200 sm:px-6">
|
||||
<div class="-ml-4 -mt-4 flex justify-between items-center flex-wrap sm:flex-nowrap">
|
||||
<div class="ml-4 mt-4 w-full">
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900 mb-4 capitalize">
|
||||
{{ ctrans('texts.default_documents') }}
|
||||
</h3>
|
||||
|
||||
<div class="flex flex-col h-auto overflow-y-auto">
|
||||
@forelse($client->company->documents as $document)
|
||||
<a href="{{ route('client.documents.show', $document->hashed_id) }}" target="_blank"
|
||||
class="block inline-flex items-center text-sm button-link text-primary">
|
||||
<span>{{ Illuminate\Support\Str::limit($document->name, 40) }}</span>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" class="ml-2 text-primary h-6 w-4">
|
||||
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
|
||||
<polyline points="15 3 21 3 21 9"></polyline>
|
||||
<line x1="10" y1="14" x2="21" y2="3"></line>
|
||||
</svg>
|
||||
</a>
|
||||
@empty
|
||||
<p class="text-sm">{{ ctrans('texts.no_records_found') }}.</p>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-light-grey-text flex grow basis-full flex-col justify-center pt-5 text-sm md:basis-1/2 md:border-r md:border-[#E5E7EB] md:pt-0 xl:basis-auto xl:px-5">
|
||||
<p class="mb-2">{{ $client->company->settings->address1 }}</p>
|
||||
<p class="mb-2">{{ $client->company->settings->address2 }}</p>
|
||||
<p class="mb-2">{{ $client->company->settings->postal_code }}</p>
|
||||
<p>{{ App\Models\Country::find($client->company->settings->country_id)?->name }}</p>
|
||||
</div>
|
||||
<div class="text-light-grey-text flex grow basis-full flex-col justify-center text-sm md:basis-1/2 md:pl-4 xl:basis-auto xl:px-5">
|
||||
<p class="mb-2">{{ $client->company->settings->email }}</p>
|
||||
<p class="mb-2">{{ $client->company->settings->phone }}</p>
|
||||
<p>{{ $client->company->settings->website }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@stop
|
||||
|
@ -46,8 +46,8 @@
|
||||
|
||||
var errorDetail = Array.isArray(data.details) && data.details[0];
|
||||
if (errorDetail && ['INSTRUMENT_DECLINED', 'PAYER_ACTION_REQUIRED'].includes(errorDetail.issue)) {
|
||||
return actions.restart();
|
||||
}
|
||||
return actions.restart();
|
||||
}
|
||||
|
||||
document.getElementById("gateway_response").value =JSON.stringify( data );
|
||||
document.getElementById("server_response").submit();
|
||||
|
@ -208,6 +208,8 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale']
|
||||
Route::get('credits/{credit}/{action}', [CreditController::class, 'action'])->name('credits.action');
|
||||
Route::post('credits/bulk', [CreditController::class, 'bulk'])->name('credits.bulk');
|
||||
Route::get('credit/{invitation_key}/download', [CreditController::class, 'downloadPdf'])->name('credits.downloadPdf');
|
||||
Route::get('credit/{invitation_key}/download_e_credit', [CreditController::class, 'downloadECredit'])->name('credits.downloadECredit');
|
||||
|
||||
|
||||
Route::resource('designs', DesignController::class); // name = (payments. index / create / show / update / destroy / edit
|
||||
Route::post('designs/bulk', [DesignController::class, 'bulk'])->name('designs.bulk');
|
||||
@ -285,12 +287,14 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale']
|
||||
Route::put('purchase_orders/{purchase_order}/upload', [PurchaseOrderController::class, 'upload']);
|
||||
Route::get('purchase_orders/{purchase_order}/{action}', [PurchaseOrderController::class, 'action'])->name('purchase_orders.action');
|
||||
Route::get('purchase_order/{invitation_key}/download', [PurchaseOrderController::class, 'downloadPdf'])->name('purchase_orders.downloadPdf');
|
||||
Route::get('purchase_order/{invitation_key}/download_e_purchase_order', [PurchaseOrderController::class, 'downloadEPurchaseOrder'])->name('purchase_orders.downloadEPurchaseOrder');
|
||||
|
||||
Route::resource('quotes', QuoteController::class); // name = (quotes. index / create / show / update / destroy / edit
|
||||
Route::get('quotes/{quote}/{action}', [QuoteController::class, 'action'])->name('quotes.action');
|
||||
Route::post('quotes/bulk', [QuoteController::class, 'bulk'])->name('quotes.bulk');
|
||||
Route::put('quotes/{quote}/upload', [QuoteController::class, 'upload']);
|
||||
Route::get('quote/{invitation_key}/download', [QuoteController::class, 'downloadPdf'])->name('quotes.downloadPdf');
|
||||
Route::get('quote/{invitation_key}/download_e_quote', [QuoteController::class, 'downloadEQuote'])->name('quotes.downloadEQuote');
|
||||
|
||||
Route::resource('recurring_expenses', RecurringExpenseController::class);
|
||||
Route::post('recurring_expenses/bulk', [RecurringExpenseController::class, 'bulk'])->name('recurring_expenses.bulk');
|
||||
|
@ -49,7 +49,7 @@ Route::group(['middleware' => ['auth:contact', 'locale', 'domain_db','check_clie
|
||||
Route::get('dashboard', [App\Http\Controllers\ClientPortal\DashboardController::class, 'index'])->name('dashboard'); // name = (dashboard. index / create / show / update / destroy / edit
|
||||
|
||||
Route::get('plan', [App\Http\Controllers\ClientPortal\NinjaPlanController::class, 'plan'])->name('plan'); // name = (dashboard. index / create / show / update / destroy / edit
|
||||
|
||||
|
||||
Route::get('showBlob/{hash}', [App\Http\Controllers\ClientPortal\InvoiceController::class, 'showBlob'])->name('invoices.showBlob');
|
||||
Route::get('invoices', [App\Http\Controllers\ClientPortal\InvoiceController::class, 'index'])->name('invoices.index')->middleware('portal_enabled');
|
||||
Route::post('invoices/payment', [App\Http\Controllers\ClientPortal\InvoiceController::class, 'bulk'])->name('invoices.bulk');
|
||||
@ -131,7 +131,9 @@ Route::group(['middleware' => ['invite_db'], 'prefix' => 'client', 'as' => 'clie
|
||||
Route::get('invoice/{invitation_key}/download_pdf', [InvoiceController::class, 'downloadPdf'])->name('invoice.download_invitation_key')->middleware('token_auth');
|
||||
Route::get('invoice/{invitation_key}/download_e_invoice', [InvoiceController::class, 'downloadEInvoice'])->name('invoice.download_e_invoice')->middleware('token_auth');
|
||||
Route::get('quote/{invitation_key}/download_pdf', [QuoteController::class, 'downloadPdf'])->name('quote.download_invitation_key')->middleware('token_auth');
|
||||
Route::get('quote/{invitation_key}/download_e_quote', [QuoteController::class, "downloadEQuote"])->name('invoice.download_e_quote')->middleware('token_auth');
|
||||
Route::get('credit/{invitation_key}/download_pdf', [CreditController::class, 'downloadPdf'])->name('credit.download_invitation_key')->middleware('token_auth');
|
||||
Route::get('credit/{invitation_key}/download_e_credit', [CreditController::class, 'downloadECredit'])->name('credit.download_e_credit')->middleware('token_auth');
|
||||
Route::get('{entity}/{invitation_key}/download', [App\Http\Controllers\ClientPortal\InvitationController::class, 'routerForDownload'])->middleware('token_auth');
|
||||
Route::get('pay/{invitation_key}', [App\Http\Controllers\ClientPortal\InvitationController::class, 'payInvoice'])->name('pay.invoice');
|
||||
|
||||
@ -142,7 +144,7 @@ Route::group(['middleware' => ['invite_db'], 'prefix' => 'client', 'as' => 'clie
|
||||
});
|
||||
|
||||
Route::get('route/{hash}', function ($hash) {
|
||||
|
||||
|
||||
return redirect(decrypt($hash));
|
||||
|
||||
});
|
||||
|
@ -11,10 +11,8 @@
|
||||
|
||||
namespace Tests\Feature\EInvoice;
|
||||
|
||||
use App\Services\Invoice\EInvoice\FacturaEInvoice;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Tests\MockAccountData;
|
||||
use Tests\TestCase;
|
||||
|
||||
@ -40,11 +38,11 @@ class FacturaeTest extends TestCase
|
||||
public function testInvoiceGeneration()
|
||||
{
|
||||
|
||||
$f = new FacturaEInvoice($this->invoice, "3.2.2");
|
||||
$f = new \App\Services\EDocument\Standards\FacturaEInvoice($this->invoice, "3.2.2");
|
||||
$path = $f->run();
|
||||
|
||||
$this->assertNotNull($f->run());
|
||||
|
||||
|
||||
// nlog($f->run());
|
||||
|
||||
// $this->assertTrue($this->validateInvoiceXML($path));
|
||||
|
@ -11,7 +11,7 @@
|
||||
|
||||
namespace Tests\Feature\EInvoice;
|
||||
|
||||
use App\Services\Invoice\EInvoice\FatturaPA;
|
||||
use App\Services\EDocument\Standards\FatturaPA;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||
use Tests\MockAccountData;
|
||||
@ -42,7 +42,7 @@ class FatturaPATest extends TestCase
|
||||
$xml = $fat->run();
|
||||
|
||||
// nlog($xml);
|
||||
|
||||
|
||||
$this->assertnotNull($xml);
|
||||
}
|
||||
}
|
||||
|
@ -62,6 +62,127 @@ class PaymentTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
public function testClientIdValidation()
|
||||
{
|
||||
$p = Payment::factory()->create([
|
||||
'company_id' => $this->company->id,
|
||||
'user_id' => $this->user->id,
|
||||
'client_id' => $this->client->id,
|
||||
'status_id' => Payment::STATUS_COMPLETED,
|
||||
'amount' => 100
|
||||
]);
|
||||
|
||||
|
||||
$data = [
|
||||
'date' => now()->addDay()->format('Y-m-d')
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->putJson('/api/v1/payments/'.$p->hashed_id, $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$data = [
|
||||
'date' => now()->addDay()->format('Y-m-d'),
|
||||
'client_id' => $this->client->hashed_id,
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->putJson('/api/v1/payments/'.$p->hashed_id, $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$c = Client::factory()->create([
|
||||
'user_id' => $this->user->id,
|
||||
'company_id' => $this->company->id,
|
||||
]);
|
||||
|
||||
$data = [
|
||||
'date' => now()->addDay()->format('Y-m-d'),
|
||||
'client_id' => $c->hashed_id,
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->putJson('/api/v1/payments/'.$p->hashed_id, $data);
|
||||
|
||||
$response->assertStatus(422);
|
||||
|
||||
}
|
||||
|
||||
public function testNegativeAppliedAmounts()
|
||||
{
|
||||
$p = Payment::factory()->create([
|
||||
'company_id' => $this->company->id,
|
||||
'user_id' => $this->user->id,
|
||||
'client_id' => $this->client->id,
|
||||
'status_id' => Payment::STATUS_COMPLETED,
|
||||
'amount' => 100
|
||||
]);
|
||||
|
||||
$i = Invoice::factory()->create([
|
||||
'company_id' => $this->company->id,
|
||||
'user_id' => $this->user->id,
|
||||
'client_id' => $this->client->id,
|
||||
'status_id' => Invoice::STATUS_SENT,
|
||||
]);
|
||||
|
||||
$i->calc()->getInvoice()->service()->markSent()->save();
|
||||
|
||||
$this->assertGreaterThan(0, $i->balance);
|
||||
|
||||
|
||||
$data = [
|
||||
'amount' => 5,
|
||||
'client_id' => $this->client->hashed_id,
|
||||
'invoices' => [
|
||||
[
|
||||
'invoice_id' => $this->invoice->hashed_id,
|
||||
'amount' => 5,
|
||||
],
|
||||
],
|
||||
'date' => '2020/12/11',
|
||||
'idempotency_key' => \Illuminate\Support\Str::uuid()->toString()
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->postJson('/api/v1/payments/', $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$payment_id = $response->json()['data']['id'];
|
||||
|
||||
$payment = Payment::find($this->decodePrimaryKey($payment_id));
|
||||
|
||||
$this->assertNotNull($payment);
|
||||
|
||||
$data = [
|
||||
'client_id' => $this->client->hashed_id,
|
||||
'invoices' => [
|
||||
[
|
||||
'invoice_id' => $this->invoice->hashed_id,
|
||||
'amount' => -5,
|
||||
],
|
||||
],
|
||||
'date' => '2020/12/11',
|
||||
'idempotency_key' => \Illuminate\Support\Str::uuid()->toString()
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->putJson('/api/v1/payments/'.$payment_id, $data);
|
||||
|
||||
$response->assertStatus(422);
|
||||
|
||||
}
|
||||
|
||||
public function testCompletedPaymentLogic()
|
||||
{
|
||||
@ -299,10 +420,9 @@ class PaymentTest extends TestCase
|
||||
|
||||
public function testPaymentRESTEndPoints()
|
||||
{
|
||||
Payment::factory()->create(['user_id' => $this->user->id, 'company_id' => $this->company->id, 'client_id' => $this->client->id]);
|
||||
|
||||
$Payment = Payment::all()->last();
|
||||
|
||||
$Payment = Payment::factory()->create(['user_id' => $this->user->id, 'company_id' => $this->company->id, 'client_id' => $this->client->id]);
|
||||
$Payment->name = \Illuminate\Support\Str::random(54);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
@ -470,19 +590,13 @@ class PaymentTest extends TestCase
|
||||
|
||||
$response = false;
|
||||
|
||||
// try {
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->postJson('/api/v1/payments?include=invoices', $data);
|
||||
// } catch (ValidationException $e) {
|
||||
// $message = json_decode($e->validator->getMessageBag(), 1);
|
||||
// $this->assertNotNull($message);
|
||||
// }
|
||||
|
||||
// if ($response) {
|
||||
$response->assertStatus(200);
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
public function testPartialPaymentAmount()
|
||||
@ -1395,8 +1509,9 @@ class PaymentTest extends TestCase
|
||||
$invoice_calc = new InvoiceSum($invoice);
|
||||
$invoice_calc->build();
|
||||
|
||||
$invoice = $invoice_calc->getInvoice();
|
||||
$invoice->save();
|
||||
$invoice = $invoice_calc->getInvoice()->service()->markSent()->save();
|
||||
$this->assertEquals(10, $invoice->amount);
|
||||
$this->assertEquals(10, $invoice->balance);
|
||||
|
||||
$credit = CreditFactory::create($this->company->id, $this->user->id);
|
||||
$credit->client_id = $client->id;
|
||||
@ -1410,8 +1525,10 @@ class PaymentTest extends TestCase
|
||||
$credit_calc = new InvoiceSum($credit);
|
||||
$credit_calc->build();
|
||||
|
||||
$credit = $credit_calc->getCredit();
|
||||
$credit->save(); //$10 credit
|
||||
$credit = $credit_calc->getCredit()->service()->markSent()->save(); //$10 credit
|
||||
|
||||
$this->assertEquals(10, $credit->amount);
|
||||
$this->assertEquals(10, $credit->balance);
|
||||
|
||||
$data = [
|
||||
'amount' => $invoice->amount,
|
||||
|
@ -163,18 +163,11 @@ class CreditPaymentTest extends TestCase
|
||||
'date' => '2019/12/12',
|
||||
];
|
||||
|
||||
$response = false;
|
||||
|
||||
try {
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/payments/', $data);
|
||||
} catch (ValidationException $e) {
|
||||
$message = json_decode($e->validator->getMessageBag(), 1);
|
||||
nlog($e->validator->getMessageBag());
|
||||
}
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->postJson('/api/v1/payments/', $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$arr = $response->json();
|
||||
|
@ -11,22 +11,24 @@
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Factory\ClientFactory;
|
||||
use App\Factory\CreditFactory;
|
||||
use App\Factory\InvoiceFactory;
|
||||
use App\Helpers\Invoice\InvoiceSum;
|
||||
use App\Models\ClientContact;
|
||||
use Tests\TestCase;
|
||||
use App\Models\Client;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use Tests\MockAccountData;
|
||||
use App\Models\ClientContact;
|
||||
use App\Factory\ClientFactory;
|
||||
use App\Factory\CreditFactory;
|
||||
use App\DataMapper\InvoiceItem;
|
||||
use App\Factory\InvoiceFactory;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Helpers\Invoice\InvoiceSum;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Tests\MockAccountData;
|
||||
use Tests\TestCase;
|
||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
|
||||
/**
|
||||
* @test
|
||||
@ -59,6 +61,132 @@ class RefundTest extends TestCase
|
||||
// $this->withoutExceptionHandling();
|
||||
}
|
||||
|
||||
public function testRefundAndAppliedAmounts()
|
||||
{
|
||||
|
||||
$data = [
|
||||
'amount' => 500,
|
||||
'client_id' => $this->client->hashed_id,
|
||||
'date' => '2020/12/12',
|
||||
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->postJson('/api/v1/payments', $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$payment_id = $arr['data']['id'];
|
||||
|
||||
$item = new InvoiceItem;
|
||||
$item->cost = 300;
|
||||
$item->quantity = 1;
|
||||
|
||||
$i = Invoice::factory()
|
||||
->create([
|
||||
'user_id' => $this->user->id,
|
||||
'company_id' => $this->company->id,
|
||||
'client_id' => $this->client->id,
|
||||
'line_items' => [$item],
|
||||
'discount' => 0,
|
||||
'tax_name1' => '',
|
||||
'tax_name2' => '',
|
||||
'tax_name3' => '',
|
||||
'tax_rate1' => 0,
|
||||
'tax_rate2' => 0,
|
||||
'tax_rate3' => 0,
|
||||
]);
|
||||
|
||||
$i->calc()->getInvoice();
|
||||
$i->service()->markSent()->save();
|
||||
|
||||
$this->assertEquals(300, $i->balance);
|
||||
|
||||
$data = [
|
||||
'client_id' => $this->client->hashed_id,
|
||||
'invoices' => [
|
||||
[
|
||||
'invoice_id' => $i->hashed_id,
|
||||
'amount' => 300
|
||||
],
|
||||
]
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->putJson('/api/v1/payments/'.$payment_id, $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$i = $i->fresh();
|
||||
|
||||
$this->assertEquals(0, $i->balance);
|
||||
|
||||
$payment = Payment::find($this->decodePrimaryKey($payment_id));
|
||||
|
||||
$this->assertNotNull($payment);
|
||||
$this->assertEquals(500, $payment->amount);
|
||||
$this->assertEquals(300, $payment->applied);
|
||||
$this->assertEquals(0, $payment->refunded);
|
||||
|
||||
$data = [
|
||||
'id' => $this->encodePrimaryKey($payment->id),
|
||||
'invoices' => [
|
||||
[
|
||||
'invoice_id' => $i->hashed_id,
|
||||
'amount' => $i->amount,
|
||||
],
|
||||
],
|
||||
'date' => '2020/12/12',
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->postJson('/api/v1/payments/refund', $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$payment = $payment->fresh();
|
||||
$i = $i->fresh();
|
||||
|
||||
$this->assertEquals(300, $payment->refunded);
|
||||
$this->assertEquals(300, $i->balance);
|
||||
$this->assertEquals(2, $i->status_id);
|
||||
|
||||
|
||||
$data = [
|
||||
'client_id' => $this->client->hashed_id,
|
||||
'invoices' => [
|
||||
[
|
||||
'invoice_id' => $i->hashed_id,
|
||||
'amount' => 200
|
||||
],
|
||||
]
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->putJson('/api/v1/payments/'.$payment_id, $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$payment = $payment->fresh();
|
||||
$i = $i->fresh();
|
||||
|
||||
$this->assertEquals(300, $payment->refunded);
|
||||
$this->assertEquals(100, $i->balance);
|
||||
$this->assertEquals(3, $i->status_id);
|
||||
$this->assertEquals(500, $payment->applied);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a simple payment of $50
|
||||
* is able to be refunded.
|
||||
@ -552,29 +680,22 @@ class RefundTest extends TestCase
|
||||
|
||||
$this->invoice = InvoiceFactory::create($this->company->id, $this->user->id); //stub the company and user_id
|
||||
$this->invoice->client_id = $client->id;
|
||||
$this->invoice->status_id = Invoice::STATUS_SENT;
|
||||
|
||||
$this->invoice->line_items = $this->buildLineItems();
|
||||
$this->invoice->uses_inclusive_taxes = false;
|
||||
$this->invoice->client_id = $client->id;
|
||||
|
||||
$this->invoice->save();
|
||||
$invoice_calc = new InvoiceSum($this->invoice);
|
||||
$invoice_calc->build();
|
||||
|
||||
$this->invoice = $invoice_calc->getInvoice();
|
||||
$this->invoice->save();
|
||||
$this->invoice->calc()->getInvoice()->service()->markSent()->save();
|
||||
|
||||
$this->credit = CreditFactory::create($this->company->id, $this->user->id);
|
||||
$this->credit->client_id = $client->id;
|
||||
$this->credit->status_id = 2;
|
||||
|
||||
$this->credit->line_items = $this->buildLineItems();
|
||||
$this->credit->amount = 10;
|
||||
$this->credit->balance = 10;
|
||||
|
||||
$this->credit->uses_inclusive_taxes = false;
|
||||
$this->credit->save();
|
||||
$this->credit->date = now()->format('Y-m-d');
|
||||
$this->credit->due_date = now()->addMonth()->format('Y-m-d');
|
||||
$this->credit->calc()->getCredit()->service()->markSent()->save();
|
||||
|
||||
$this->assertEquals(10, $this->credit->amount);
|
||||
$this->assertEquals(10, $this->credit->balance);
|
||||
|
||||
$data = [
|
||||
'amount' => 50,
|
||||
@ -656,26 +777,62 @@ class RefundTest extends TestCase
|
||||
|
||||
public function testRefundsWhenCreditsArePresent()
|
||||
{
|
||||
$cl = Client::factory()->create([
|
||||
'company_id' => $this->company->id,
|
||||
'user_id' => $this->user->id,
|
||||
]);
|
||||
|
||||
nlog($cl->id);
|
||||
|
||||
$i = Invoice::factory()->create([
|
||||
'company_id' => $this->company->id,
|
||||
'user_id' => $this->user->id,
|
||||
'client_id' => $this->client->id,
|
||||
'client_id' => $cl->id,
|
||||
'status_id' => Invoice::STATUS_SENT,
|
||||
'amount' => 1000,
|
||||
'balance' => 1000,
|
||||
]);
|
||||
|
||||
$item = new InvoiceItem;
|
||||
$item->cost = 1000;
|
||||
$item->quantity = 1;
|
||||
|
||||
$i->line_items = [$item];
|
||||
|
||||
$i->service()->markSent()->save();
|
||||
|
||||
$this->assertEquals(1000, $i->balance);
|
||||
|
||||
$c = Credit::factory()->create([
|
||||
'company_id' => $this->company->id,
|
||||
'user_id' => $this->user->id,
|
||||
'client_id' => $this->client->id,
|
||||
'client_id' => $cl->id,
|
||||
'status_id' => Invoice::STATUS_SENT,
|
||||
'amount' => 100,
|
||||
'balance' => 100,
|
||||
'date' => now()->format('Y-m-d'),
|
||||
'due_date' => now()->addMonth()->format('Y-m-d'),
|
||||
]);
|
||||
|
||||
$item = new InvoiceItem();
|
||||
$item->cost = 100;
|
||||
$item->quantity = 1;
|
||||
|
||||
$c->line_items = [$item];
|
||||
|
||||
$c->service()->markSent()->save();
|
||||
|
||||
$this->assertEquals(100, $c->balance);
|
||||
$this->assertNotNull($c);
|
||||
$this->assertEquals(2, $c->status_id);
|
||||
|
||||
$this->assertEquals($cl->id, $c->client_id);
|
||||
|
||||
$this->assertEquals($cl->id, $i->client_id);
|
||||
|
||||
$data = [
|
||||
'client_id' => $this->client->hashed_id,
|
||||
'amount' => 900,
|
||||
'client_id' => $cl->hashed_id,
|
||||
'invoices' => [
|
||||
[
|
||||
'invoice_id' => $i->hashed_id,
|
||||
@ -706,7 +863,7 @@ class RefundTest extends TestCase
|
||||
|
||||
$refund = [
|
||||
'id' => $payment_id,
|
||||
'client_id' => $this->client->hashed_id,
|
||||
'client_id' => $cl->hashed_id,
|
||||
'amount' => 10,
|
||||
'date' => now()->format('Y-m-d'),
|
||||
'invoices' => [
|
||||
|
@ -9,8 +9,8 @@
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
use App\Jobs\EDocument\CreateEDocument;
|
||||
use App\Jobs\Entity\CreateRawPdf;
|
||||
use App\Jobs\Invoice\CreateEInvoice;
|
||||
use horstoeko\zugferd\ZugferdDocumentReader;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||
@ -41,7 +41,7 @@ class EInvoiceTest extends TestCase
|
||||
$this->company->e_invoice_type = "EN16931";
|
||||
$this->invoice->client->routing_id = 'DE123456789';
|
||||
$this->invoice->client->save();
|
||||
$e_invoice = (new CreateEInvoice($this->invoice))->handle();
|
||||
$e_invoice = (new CreateEDocument($this->invoice))->handle();
|
||||
$this->assertIsString($e_invoice);
|
||||
}
|
||||
|
||||
@ -54,7 +54,7 @@ class EInvoiceTest extends TestCase
|
||||
$this->invoice->client->routing_id = 'DE123456789';
|
||||
$this->invoice->client->save();
|
||||
|
||||
$e_invoice = (new CreateEInvoice($this->invoice))->handle();
|
||||
$e_invoice = (new CreateEDocument($this->invoice))->handle();
|
||||
$document = ZugferdDocumentReader::readAndGuessFromContent($e_invoice);
|
||||
$document->getDocumentInformation($documentno, $documenttypecode, $documentdate, $documentcurrency, $taxcurrency, $taxname, $documentlangeuage, $rest);
|
||||
$this->assertEquals($this->invoice->number, $documentno);
|
||||
|
@ -61,6 +61,8 @@ class InvoiceTest extends TestCase
|
||||
'settings' => $c_settings,
|
||||
]);
|
||||
|
||||
$this->assertEquals(0, $c->balance);
|
||||
|
||||
$item = InvoiceItemFactory::create();
|
||||
$item->quantity = 1;
|
||||
$item->cost = 10.01;
|
||||
@ -88,6 +90,10 @@ class InvoiceTest extends TestCase
|
||||
|
||||
$this->assertEquals(10, $ii->amount);
|
||||
|
||||
$ii->service()->markSent()->save();
|
||||
|
||||
$this->assertEquals(10, $c->fresh()->balance);
|
||||
|
||||
}
|
||||
|
||||
public function testRappenRoundingUp()
|
||||
@ -129,6 +135,26 @@ class InvoiceTest extends TestCase
|
||||
|
||||
$this->assertEquals(10.10, round($ii->amount,2));
|
||||
|
||||
$ii->service()->markSent()->save();
|
||||
|
||||
$this->assertEquals(10.10, $c->fresh()->balance);
|
||||
|
||||
$item = InvoiceItemFactory::create();
|
||||
$item->quantity = 2;
|
||||
$item->cost = 10.09;
|
||||
$item->type_id = '1';
|
||||
$item->tax_id = '1';
|
||||
|
||||
$i->line_items = [$item];
|
||||
|
||||
$invoice_calc = new InvoiceSum($i);
|
||||
$ii = $invoice_calc->build()->getInvoice();
|
||||
|
||||
$ii->client->service()->calculateBalance($ii);
|
||||
|
||||
$this->assertEquals(20.20, round($ii->amount,2));
|
||||
$this->assertEquals(20.20, round($ii->balance,2));
|
||||
$this->assertEquals(20.20, round($c->fresh()->balance,2));
|
||||
}
|
||||
|
||||
public function testPartialDueDateCast()
|
||||
|
Loading…
Reference in New Issue
Block a user