Merge pull request #9507 from turbo124/v5-develop

Cleanup for Stripe webhooks.
This commit is contained in:
David Bomba 2024-05-07 11:11:56 +10:00 committed by GitHub
commit 6524b0d955
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
87 changed files with 972 additions and 331 deletions

View File

@ -64,7 +64,7 @@ jobs:
- name: Build project
run: |
shopt -s dotglob
tar --exclude='public/storage' --exclude='.htaccess' --exclude='invoiceninja.zip' -zcvf /home/runner/work/invoiceninja/react-invoiceninja.tar *
tar --exclude='public/storage' --exclude='./htaccess' --exclude='invoiceninja.zip' -zcvf /home/runner/work/invoiceninja/react-invoiceninja.tar *
- name: Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')

View File

@ -74,7 +74,7 @@ jobs:
run: |
zip -r /home/runner/work/invoiceninja/invoiceninja.zip .* -x "../*"
shopt -s dotglob
tar --exclude='public/storage' --exclude='.htaccess' --exclude='invoiceninja.zip' -zcvf /home/runner/work/invoiceninja/invoiceninja.tar *
tar --exclude='public/storage' --exclude='./htaccess' --exclude='invoiceninja.zip' -zcvf /home/runner/work/invoiceninja/invoiceninja.tar *
- name: Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')

View File

@ -1 +1 @@
5.8.53
5.8.54

View File

@ -208,7 +208,7 @@ class CheckData extends Command
->cursor()
->each(function ($client) {
if ($client->recurring_invoices()->where('is_deleted', 0)->where('deleted_at', null)->count() > 1) {
$this->logMessage("Duplicate Recurring Invoice => {$client->custom_value1}");
$this->logMessage("Duplicate Recurring Invoice => {$client->custom_value1} || {$client->id}}");
}
});
}

View File

@ -11,11 +11,12 @@
namespace App\DataMapper\Tax;
use App\DataMapper\Tax\ZipTax\Response;
use App\DataProviders\USStates;
use App\Models\Quote;
use App\Models\Client;
use App\Models\Invoice;
use App\Models\Product;
use App\DataProviders\USStates;
use App\DataMapper\Tax\ZipTax\Response;
class BaseRule implements RuleInterface
{
@ -210,7 +211,7 @@ class BaseRule implements RuleInterface
}
/** Applies the tax data to the invoice */
if($this->invoice instanceof Invoice && $tax_data) {
if(($this->invoice instanceof Invoice || $this->invoice instanceof Quote) && $tax_data) {
$this->invoice->tax_data = $tax_data;

View File

@ -0,0 +1,42 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Events\Statement;
use App\Models\Client;
use App\Models\Company;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
/**
* Class StatementWasEmailed.
*/
class StatementWasEmailed
{
use Dispatchable;
use InteractsWithSockets;
use SerializesModels;
public function __construct(public Client $client, public Company $company, public string $end_date, public array $event_vars)
{
}
// /**
// * Get the channels the event should broadcast on.
// *
// * @return Channel|array
// */
public function broadcastOn()
{
return [];
}
}

View File

@ -448,6 +448,7 @@ class BaseExport
'custom_value4' => 'task.custom_value4',
'status' => 'task.status_id',
'project' => 'task.project_id',
'billable' => 'task.billable',
];
protected array $forced_client_fields = [

View File

@ -178,13 +178,13 @@ class SwissQrGenerator
if(is_iterable($qrBill->getViolations())) {
foreach ($qrBill->getViolations() as $key => $violation) {
nlog("qr");
nlog($violation);
// nlog("qr");
// nlog($violation);
}
}
nlog($e->getMessage());
// nlog($e->getMessage());
return '';
// return $e->getMessage();

View File

@ -37,18 +37,18 @@ class StripeConnectController extends BaseController
MultiDB::findAndSetDbByCompanyKey($request->getTokenContent()['company_key']);
$company_gateway = CompanyGateway::query()
->where('gateway_key', 'd14dd26a47cecc30fdd65700bfb67b34')
->where('company_id', $request->getCompany()->id)
->first();
// $company_gateway = CompanyGateway::query()
// ->where('gateway_key', 'd14dd26a47cecc30fdd65700bfb67b34')
// ->where('company_id', $request->getCompany()->id)
// ->first();
if ($company_gateway) {
$config = $company_gateway->getConfig();
// if ($company_gateway) {
// $config = $company_gateway->getConfig();
if (property_exists($config, 'account_id') && strlen($config->account_id) > 5) {
return view('auth.connect.existing');
}
}
// if (property_exists($config, 'account_id') && strlen($config->account_id) > 5) {
// return view('auth.connect.existing');
// }
// }
$stripe_client_id = config('ninja.ninja_stripe_client_id');
$redirect_uri = config('ninja.app_url').'/stripe/completed';
@ -127,7 +127,6 @@ class StripeConnectController extends BaseController
'refresh_token' => $response->refresh_token,
'access_token' => $response->access_token,
'appleDomainVerification' => '',
// "statementDescriptor" => "",
];
$company_gateway->setConfig($payload);
@ -145,9 +144,6 @@ class StripeConnectController extends BaseController
nlog("could not harvest stripe company name");
}
// nlog("Stripe Connect Redirect URI = {$redirect_uri}");
// StripeWebhook::dispatch($company->company_key, $company_gateway->id);
if(isset($request->getTokenContent()['is_react']) && $request->getTokenContent()['is_react']) {
$redirect_uri = config('ninja.react_url').'/#/settings/online_payments';
} else {
@ -158,7 +154,7 @@ class StripeConnectController extends BaseController
//response here
return view('auth.connect.completed', ['url' => $redirect_uri]);
// return redirect($redirect_uri);
}
}

View File

@ -129,6 +129,10 @@ class TwilioController extends BaseController
$user->verified_phone_number = true;
$user->save();
if (class_exists(\Modules\Admin\Jobs\Account\UserQualityCheck::class)) {
\Modules\Admin\Jobs\Account\UserQualityCheck::dispatch($user, $user->company()->db);
}
return response()->json(['message' => 'SMS verified'], 200);
}

View File

@ -41,7 +41,7 @@ class StoreBankTransactionRequest extends Request
$rules = [];
$rules['bank_integration_id'] = 'bail|required|exists:bank_integrations,id,company_id,'.$user->company()->id.',is_deleted,0';
$rules['amount'] = ['sometimes', 'bail', 'numeric', 'nullable', 'max:99999999999999'];
return $rules;
}
@ -55,6 +55,7 @@ class StoreBankTransactionRequest extends Request
$input['bank_integration_id'] = $this->decodePrimaryKey($input['bank_integration_id']);
}
$this->replace($input);
}
}

View File

@ -44,9 +44,7 @@ class UpdateBankTransactionRequest extends Request
$rules['vendor_id'] = 'bail|required|exists:vendors,id,company_id,'.auth()->user()->company()->id.',is_deleted,0';
}
// if (isset($this->expense_id)) {
// $rules['expense_id'] = 'bail|required|exists:expenses,id,company_id,'.auth()->user()->company()->id.',is_deleted,0';
// }
$rules['amount'] = ['sometimes', 'bail', 'nullable', 'numeric', 'max:99999999999999'];
$rules['bank_integration_id'] = 'bail|required|exists:bank_integrations,id,company_id,'.auth()->user()->company()->id.',is_deleted,0';

View File

@ -67,7 +67,7 @@ class StoreCreditRequest extends Request
// $rules['number'] = new UniqueCreditNumberRule($this->all());
$rules['number'] = ['nullable', Rule::unique('credits')->where('company_id', $user->company()->id)];
$rules['discount'] = 'sometimes|numeric';
$rules['discount'] = 'sometimes|numeric|max:99999999999999';
$rules['is_amount_discount'] = ['boolean'];
$rules['tax_rate1'] = 'bail|sometimes|numeric';
$rules['tax_rate2'] = 'bail|sometimes|numeric';
@ -76,6 +76,7 @@ class StoreCreditRequest extends Request
$rules['tax_name2'] = 'bail|sometimes|string|nullable';
$rules['tax_name3'] = 'bail|sometimes|string|nullable';
$rules['exchange_rate'] = 'bail|sometimes|numeric';
$rules['amount'] = ['sometimes', 'bail', 'numeric', 'max:99999999999999'];
if ($this->invoice_id) {
$rules['invoice_id'] = new ValidInvoiceCreditRule();
@ -101,6 +102,7 @@ class StoreCreditRequest extends Request
$input = $this->decodePrimaryKeys($input);
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
$input['amount'] = $this->entityTotalAmount($input['line_items']);
if (array_key_exists('exchange_rate', $input) && is_null($input['exchange_rate'])) {
$input['exchange_rate'] = 1;

View File

@ -67,7 +67,8 @@ class UpdateCreditRequest extends Request
$rules['client_id'] = ['bail', 'sometimes',Rule::in([$this->credit->client_id])];
$rules['line_items'] = 'array';
$rules['discount'] = 'sometimes|numeric';
$rules['discount'] = 'sometimes|numeric|max:99999999999999';
$rules['is_amount_discount'] = ['boolean'];
$rules['tax_rate1'] = 'bail|sometimes|numeric';
$rules['tax_rate2'] = 'bail|sometimes|numeric';
@ -76,6 +77,7 @@ class UpdateCreditRequest extends Request
$rules['tax_name2'] = 'bail|sometimes|string|nullable';
$rules['tax_name3'] = 'bail|sometimes|string|nullable';
$rules['exchange_rate'] = 'bail|sometimes|numeric';
$rules['amount'] = ['sometimes', 'bail', 'numeric', 'max:99999999999999'];
return $rules;
}
@ -92,6 +94,8 @@ class UpdateCreditRequest extends Request
if (isset($input['line_items'])) {
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
$input['amount'] = $this->entityTotalAmount($input['line_items']);
}
if (array_key_exists('exchange_rate', $input) && is_null($input['exchange_rate'])) {

View File

@ -53,6 +53,7 @@ class StoreExpenseRequest extends Request
$rules['payment_date'] = 'bail|nullable|sometimes|date:Y-m-d';
$rules['date'] = 'bail|sometimes|date:Y-m-d';
$rules['documents'] = 'bail|sometimes|array';
$rules['amount'] = ['sometimes', 'bail', 'nullable', 'numeric', 'max:99999999999999'];
return $this->globalRules($rules);
}

View File

@ -55,7 +55,7 @@ class UpdateExpenseRequest extends Request
$rules['transaction_id'] = 'bail|sometimes|nullable|exists:bank_transactions,id,company_id,'.$user->company()->id;
$rules['invoice_id'] = 'bail|sometimes|nullable|exists:invoices,id,company_id,'.$user->company()->id;
$rules['documents'] = 'bail|sometimes|array';
$rules['amount'] = ['sometimes', 'bail', 'nullable', 'numeric', 'max:99999999999999'];
return $this->globalRules($rules);
}

View File

@ -67,7 +67,7 @@ class StoreInvoiceRequest extends Request
$rules['is_amount_discount'] = ['boolean'];
$rules['line_items'] = 'array';
$rules['discount'] = 'sometimes|numeric';
$rules['discount'] = 'sometimes|numeric|max:99999999999999';
$rules['tax_rate1'] = 'bail|sometimes|numeric';
$rules['tax_rate2'] = 'bail|sometimes|numeric';
$rules['tax_rate3'] = 'bail|sometimes|numeric';
@ -77,9 +77,11 @@ class StoreInvoiceRequest extends Request
$rules['exchange_rate'] = 'bail|sometimes|numeric';
$rules['partial'] = 'bail|sometimes|nullable|numeric|gte:0';
$rules['partial_due_date'] = ['bail', 'sometimes', 'exclude_if:partial,0', Rule::requiredIf(fn () => $this->partial > 0), 'date'];
$rules['amount'] = ['sometimes', 'bail', 'numeric', 'max:99999999999999'];
// $rules['amount'] = ['sometimes', 'bail', 'max:99999999999999'];
// $rules['due_date'] = ['bail', 'sometimes', 'nullable', 'after:partial_due_date', Rule::requiredIf(fn () => strlen($this->partial_due_date) > 1), 'date'];
return $rules;
}
@ -89,17 +91,18 @@ class StoreInvoiceRequest extends Request
$input = $this->decodePrimaryKeys($input);
$input['amount'] = 0;
$input['balance'] = 0;
if (isset($input['line_items']) && is_array($input['line_items'])) {
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
$input['amount'] = $this->entityTotalAmount($input['line_items']);
}
if(isset($input['partial']) && $input['partial'] == 0) {
$input['partial_due_date'] = null;
}
$input['amount'] = 0;
$input['balance'] = 0;
if (array_key_exists('tax_rate1', $input) && is_null($input['tax_rate1'])) {
$input['tax_rate1'] = 0;
}

View File

@ -66,7 +66,8 @@ class UpdateInvoiceRequest extends Request
$rules['is_amount_discount'] = ['boolean'];
$rules['client_id'] = ['bail', 'sometimes', Rule::in([$this->invoice->client_id])];
$rules['line_items'] = 'array';
$rules['discount'] = 'sometimes|numeric';
$rules['discount'] = 'sometimes|numeric|max:99999999999999';
$rules['project_id'] = ['bail', 'sometimes', new ValidProjectForClient($this->all())];
$rules['tax_rate1'] = 'bail|sometimes|numeric';
$rules['tax_rate2'] = 'bail|sometimes|numeric';
@ -77,6 +78,8 @@ class UpdateInvoiceRequest extends Request
$rules['status_id'] = 'bail|sometimes|not_in:5'; //do not allow cancelled invoices to be modfified.
$rules['exchange_rate'] = 'bail|sometimes|numeric';
$rules['partial'] = 'bail|sometimes|nullable|numeric';
$rules['amount'] = ['sometimes', 'bail', 'numeric', 'max:99999999999999'];
// $rules['partial_due_date'] = ['bail', 'sometimes', 'exclude_if:partial,0', Rule::requiredIf(fn () => $this->partial > 0), 'date', 'before:due_date'];
// $rules['due_date'] = ['bail', 'sometimes', 'nullable', 'after:partial_due_date', Rule::requiredIf(fn () => strlen($this->partial_due_date) > 1), 'date'];
@ -97,6 +100,7 @@ class UpdateInvoiceRequest extends Request
if (isset($input['line_items']) && is_array($input['line_items'])) {
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
$input['amount'] = $this->entityTotalAmount($input['line_items']);
}
if (array_key_exists('documents', $input)) {

View File

@ -67,9 +67,8 @@ class RefundPaymentRequest extends Request
$input = $this->all();
$rules = [
'id' => 'bail|required', //@phpstan-ignore-line
'id' => new ValidRefundableRequest($input),
'amount' => 'numeric',
'id' => ['bail','required', new ValidRefundableRequest($input)],
'amount' => ['numeric', 'max:99999999999999'],
'date' => 'required',
'invoices.*.invoice_id' => 'required',
'invoices.*.amount' => 'required',

View File

@ -50,7 +50,7 @@ class StorePaymentRequest extends Request
'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())],
'amount' => ['bail', 'numeric', new PaymentAmountsBalanceRule()],
'amount' => ['bail', 'numeric', new PaymentAmountsBalanceRule(), 'max:99999999999999'],
'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)],
];

View File

@ -49,7 +49,8 @@ class StorePurchaseOrderRequest extends Request
$rules['vendor_id'] = 'bail|required|exists:vendors,id,company_id,'.$user->company()->id.',is_deleted,0';
$rules['number'] = ['nullable', Rule::unique('purchase_orders')->where('company_id', $user->company()->id)];
$rules['discount'] = 'sometimes|numeric';
$rules['discount'] = 'sometimes|numeric|max:99999999999999';
$rules['is_amount_discount'] = ['boolean'];
$rules['line_items'] = 'array';
@ -70,6 +71,8 @@ class StorePurchaseOrderRequest extends Request
$rules['status_id'] = 'nullable|integer|in:1,2,3,4,5';
$rules['exchange_rate'] = 'bail|sometimes|numeric';
$rules['amount'] = ['sometimes', 'bail', 'numeric', 'max:99999999999999'];
return $rules;
}
@ -79,16 +82,18 @@ class StorePurchaseOrderRequest extends Request
$input = $this->decodePrimaryKeys($input);
$input['amount'] = 0;
$input['balance'] = 0;
if(isset($input['partial']) && $input['partial'] == 0) {
$input['partial_due_date'] = null;
}
if (isset($input['line_items']) && is_array($input['line_items'])) {
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
}
$input['amount'] = $this->entityTotalAmount($input['line_items']);
$input['amount'] = 0;
$input['balance'] = 0;
}
if (array_key_exists('exchange_rate', $input) && is_null($input['exchange_rate'])) {
$input['exchange_rate'] = 1;

View File

@ -52,7 +52,8 @@ class UpdatePurchaseOrderRequest extends Request
$rules['vendor_id'] = ['bail', 'sometimes', Rule::in([$this->purchase_order->vendor_id])];
$rules['line_items'] = 'array';
$rules['discount'] = 'sometimes|numeric';
$rules['discount'] = 'sometimes|numeric|max:99999999999999';
$rules['is_amount_discount'] = ['boolean'];
if ($this->file('documents') && is_array($this->file('documents'))) {
@ -71,6 +72,7 @@ class UpdatePurchaseOrderRequest extends Request
$rules['status_id'] = 'sometimes|integer|in:1,2,3,4,5';
$rules['exchange_rate'] = 'bail|sometimes|numeric';
$rules['amount'] = ['sometimes', 'bail', 'numeric', 'max:99999999999999'];
return $rules;
}
@ -89,6 +91,7 @@ class UpdatePurchaseOrderRequest extends Request
if (isset($input['line_items']) && is_array($input['line_items'])) {
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
$input['amount'] = $this->entityTotalAmount($input['line_items']);
}
if (array_key_exists('exchange_rate', $input) && is_null($input['exchange_rate'])) {

View File

@ -60,12 +60,14 @@ class StoreQuoteRequest extends Request
}
$rules['number'] = ['nullable', Rule::unique('quotes')->where('company_id', $user->company()->id)];
$rules['discount'] = 'sometimes|numeric';
$rules['discount'] = 'sometimes|numeric|max:99999999999999';
$rules['is_amount_discount'] = ['boolean'];
$rules['exchange_rate'] = 'bail|sometimes|numeric';
$rules['line_items'] = 'array';
$rules['partial_due_date'] = ['bail', 'sometimes', 'exclude_if:partial,0', Rule::requiredIf(fn () => $this->partial > 0), 'date', 'before:due_date', 'after_or_equal:date'];
$rules['due_date'] = ['bail', 'sometimes', 'nullable', 'after:partial_due_date', Rule::requiredIf(fn () => strlen($this->partial_due_date) > 1), 'date'];
$rules['amount'] = ['sometimes', 'bail', 'numeric', 'max:99999999999999'];
return $rules;
}
@ -79,10 +81,14 @@ class StoreQuoteRequest extends Request
$input = $this->decodePrimaryKeys($input);
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
$input['amount'] = 0;
$input['balance'] = 0;
if (isset($input['line_items']) && is_array($input['line_items'])) {
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
$input['amount'] = $this->entityTotalAmount($input['line_items']);
}
if (array_key_exists('exchange_rate', $input) && is_null($input['exchange_rate'])) {
$input['exchange_rate'] = 1;
}

View File

@ -59,12 +59,13 @@ class UpdateQuoteRequest extends Request
$rules['number'] = ['bail', 'sometimes', 'nullable', Rule::unique('quotes')->where('company_id', $user->company()->id)->ignore($this->quote->id)];
$rules['client_id'] = ['bail', 'sometimes', Rule::in([$this->quote->client_id])];
$rules['line_items'] = 'array';
$rules['discount'] = 'sometimes|numeric';
$rules['discount'] = 'sometimes|numeric|max:99999999999999';
$rules['is_amount_discount'] = ['boolean'];
$rules['exchange_rate'] = 'bail|sometimes|numeric';
$rules['partial_due_date'] = ['bail', 'sometimes', 'exclude_if:partial,0', Rule::requiredIf(fn () => $this->partial > 0), 'date', 'before:due_date'];
$rules['due_date'] = ['bail', 'sometimes', 'nullable', 'after:partial_due_date', 'after_or_equal:date', Rule::requiredIf(fn () => strlen($this->partial_due_date) > 1), 'date'];
$rules['amount'] = ['sometimes', 'bail', 'numeric', 'max:99999999999999'];
return $rules;
}
@ -79,6 +80,7 @@ class UpdateQuoteRequest extends Request
if (isset($input['line_items'])) {
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
$input['amount'] = $this->entityTotalAmount($input['line_items']);
}
if (array_key_exists('documents', $input)) {

View File

@ -79,6 +79,8 @@ class StoreRecurringInvoiceRequest extends Request
$rules['exchange_rate'] = 'bail|sometimes|numeric';
$rules['next_send_date'] = 'bail|required|date|after:yesterday';
$rules['amount'] = ['sometimes', 'bail', 'numeric', 'max:99999999999999'];
return $rules;
}
@ -145,6 +147,7 @@ class StoreRecurringInvoiceRequest extends Request
}
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
$input['amount'] = $this->entityTotalAmount($input['line_items']);
if (isset($input['auto_bill'])) {
$input['auto_bill_enabled'] = $this->setAutoBillFlag($input['auto_bill']);

View File

@ -72,6 +72,7 @@ class UpdateRecurringInvoiceRequest extends Request
$rules['tax_name3'] = 'bail|sometimes|string|nullable';
$rules['exchange_rate'] = 'bail|sometimes|numeric';
$rules['next_send_date'] = 'bail|required|date|after:yesterday';
$rules['amount'] = ['sometimes', 'bail', 'numeric', 'max:99999999999999'];
return $rules;
}
@ -126,6 +127,7 @@ class UpdateRecurringInvoiceRequest extends Request
if (isset($input['line_items'])) {
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
$input['amount'] = $this->entityTotalAmount($input['line_items']);
}
if (array_key_exists('auto_bill', $input) && isset($input['auto_bill'])) {

View File

@ -42,7 +42,7 @@ class ExpenseTransformer extends BaseTransformer
'client_id' => isset($data['expense.client'])
? $this->getClientId($data['expense.client'])
: null,
'date' => strlen($this->getString($data, 'expense.date') > 1) ? date('Y-m-d', strtotime(str_replace("/", "-", $data['expense.date']))) : now()->format('Y-m-d'),
'date' => strlen($this->getString($data, 'expense.date') > 1) ? $this->parseDate($data['expense.date']) : now()->format('Y-m-d'),
'public_notes' => $this->getString($data, 'expense.public_notes'),
'private_notes' => $this->getString($data, 'expense.private_notes'),
'category_id' => isset($data['expense.category'])
@ -55,7 +55,7 @@ class ExpenseTransformer extends BaseTransformer
? $this->getPaymentTypeId($data['expense.payment_type'])
: null,
'payment_date' => isset($data['expense.payment_date'])
? date('Y-m-d', strtotime(str_replace("/", "-", $data['expense.payment_date'])))
? $this->parseDate($data['expense.payment_date'])
: null,
'custom_value1' => $this->getString($data, 'expense.custom_value1'),
'custom_value2' => $this->getString($data, 'expense.custom_value2'),

View File

@ -93,11 +93,8 @@ class InvoiceTransformer extends BaseTransformer
'invoice.custom_value4'
),
'footer' => $this->getString($invoice_data, 'invoice.footer'),
'partial' => $this->getFloat($invoice_data, 'invoice.partial') > 0 ?: null,
'partial_due_date' => $this->getString(
$invoice_data,
'invoice.partial_due_date'
),
'partial' => $this->getFloat($invoice_data, 'invoice.partial') > 0 ? $this->getFloat($invoice_data, 'invoice.partial') : null,
'partial_due_date' => isset($invoice_data['invoice.partial_due_date']) ? $this->parseDate($invoice_data['invoice.partial_due_date']) : null,
'custom_surcharge1' => $this->getFloat(
$invoice_data,
'invoice.custom_surcharge1'

View File

@ -56,10 +56,10 @@ class QuoteTransformer extends BaseTransformer
'discount' => $this->getFloat($quote_data, 'quote.discount'),
'po_number' => $this->getString($quote_data, 'quote.po_number'),
'date' => isset($quote_data['quote.date'])
? date('Y-m-d', strtotime(str_replace("/", "-", $quote_data['quote.date'])))
? $this->parseDate($quote_data['quote.date'])
: now()->format('Y-m-d'),
'due_date' => isset($quote_data['quote.due_date'])
? date('Y-m-d', strtotime(str_replace("/", "-", $quote_data['quote.due_date'])))
? $this->parseDate($quote_data['quote.due_date'])
: null,
'terms' => $this->getString($quote_data, 'quote.terms'),
'public_notes' => $this->getString(
@ -94,10 +94,7 @@ class QuoteTransformer extends BaseTransformer
),
'footer' => $this->getString($quote_data, 'quote.footer'),
'partial' => $this->getFloat($quote_data, 'quote.partial'),
'partial_due_date' => $this->getString(
$quote_data,
'quote.partial_due_date'
),
'partial_due_date' => isset($invoice_data['quote.partial_due_date']) ? $this->parseDate($quote_data['quote.partial_due_date']) : null,
'custom_surcharge1' => $this->getString(
$quote_data,
'quote.custom_surcharge1'
@ -139,10 +136,7 @@ class QuoteTransformer extends BaseTransformer
$transformed['payments'] = [
[
'date' => isset($quote_data['payment.date'])
? date(
'Y-m-d',
strtotime($quote_data['payment.date'])
)
? $this->parseDate($quote_data['payment.date'])
: date('y-m-d'),
'transaction_reference' => $this->getString(
$quote_data,
@ -158,10 +152,7 @@ class QuoteTransformer extends BaseTransformer
$transformed['payments'] = [
[
'date' => isset($quote_data['payment.date'])
? date(
'Y-m-d',
strtotime($quote_data['payment.date'])
)
? $this->parseDate($quote_data['payment.date'])
: date('y-m-d'),
'transaction_reference' => $this->getString(
$quote_data,
@ -181,10 +172,7 @@ class QuoteTransformer extends BaseTransformer
$transformed['payments'] = [
[
'date' => isset($quote_data['payment.date'])
? date(
'Y-m-d',
strtotime($quote_data['payment.date'])
)
? $this->parseDate($quote_data['payment.date'])
: date('y-m-d'),
'transaction_reference' => $this->getString(
$quote_data,

View File

@ -65,9 +65,7 @@ class RecurringInvoiceTransformer extends BaseTransformer
'next_send_date_client' => isset($invoice_data['invoice.next_send_date'])
? $this->parseDate($invoice_data['invoice.next_send_date'])
: now()->format('Y-m-d'),
'due_date' => isset($invoice_data['invoice.due_date'])
? $this->parseDate($invoice_data['invoice.due_date'])
: null,
'due_date' => isset($invoice_data['invoice.due_date']) ? $this->parseDate($invoice_data['invoice.due_date']) : null,
'terms' => $this->getString($invoice_data, 'invoice.terms'),
'due_date_days' => 'terms',
'public_notes' => $this->getString(
@ -101,11 +99,8 @@ class RecurringInvoiceTransformer extends BaseTransformer
'invoice.custom_value4'
),
'footer' => $this->getString($invoice_data, 'invoice.footer'),
'partial' => $this->getFloat($invoice_data, 'invoice.partial') > 0 ?: null,
'partial_due_date' => $this->getString(
$invoice_data,
'invoice.partial_due_date'
),
'partial' => $this->getFloat($invoice_data, 'invoice.partial') > 0 ? $this->getFloat($invoice_data, 'invoice.partial') : null,
'partial_due_date' => isset($invoice_data['invoice.partial_due_date']) ? $this->parseDate($invoice_data['invoice.partial_due_date']) : null,
'custom_surcharge1' => $this->getString(
$invoice_data,
'invoice.custom_surcharge1'

View File

@ -49,7 +49,7 @@ class TaskTransformer extends BaseTransformer
'client_id' => $clientId,
'project_id' => $this->getProjectId($projectId, $clientId),
'description' => $this->getString($task_data, 'task.description'),
'status' => $this->getTaskStatusId($task_data),
'status_id' => $this->getTaskStatusId($task_data),
'custom_value1' => $this->getString($task_data, 'task.custom_value1'),
'custom_value2' => $this->getString($task_data, 'task.custom_value2'),
'custom_value3' => $this->getString($task_data, 'task.custom_value3'),
@ -84,7 +84,7 @@ class TaskTransformer extends BaseTransformer
} elseif(isset($item['task.billable']) && is_bool($item['task.billable'])) {
$is_billable = $item['task.billable'];
} else {
$is_billable = false;
$is_billable = true;
}
if(isset($item['task.start_date']) &&

View File

@ -42,8 +42,7 @@ class InvoiceTransformer extends BaseTransformer
'company_id' => $this->company->id,
'client_id' => $this->getClient($this->getString($invoice_data, 'Client Name'), null),
'number' => $this->getString($invoice_data, 'Invoice #'),
'date' => isset($invoice_data['Date Issued']) ? date('Y-m-d', strtotime($invoice_data['Date Issued'])) : null,
// 'currency_id' => $this->getCurrencyByCode( $invoice_data, 'Currency' ),
'date' => isset($invoice_data['Date Issued']) ? $this->parseDate($invoice_data['Date Issued']) : null,
'amount' => 0,
'status_id' => $invoiceStatusMap[$status =
strtolower($this->getString($invoice_data, 'Invoice Status'))] ?? Invoice::STATUS_SENT,
@ -70,7 +69,7 @@ class InvoiceTransformer extends BaseTransformer
if (! empty($invoice_data['Date Paid'])) {
$transformed['payments'] = [[
'date' => date('Y-m-d', strtotime($invoice_data['Date Paid'])),
'date' => $this->parseDate($invoice_data['Date Paid']),
'amount' => $transformed['amount'],
]];
}

View File

@ -45,7 +45,7 @@ class InvoiceTransformer extends BaseTransformer
'company_id' => $this->company->id,
'number' => $this->getString($invoice_data, 'DocumentNumber'),
'notes' => $this->getString($invoice_data, 'Comment'),
'date' => isset($invoice_data['DocumentDate']) ? date('Y-m-d', strtotime($invoice_data['DocumentDate'])) : null,
'date' => isset($invoice_data['DocumentDate']) ? $this->parseDate($invoice_data['DocumentDate']) : null,
// 'currency_id' => $this->getCurrencyByCode( $invoice_data, 'Currency' ),
'amount' => $this->getFloat($invoice_data, 'TotalAmount'),
'status_id' => $invoiceStatusMap[$status =
@ -92,7 +92,7 @@ class InvoiceTransformer extends BaseTransformer
if (! empty($invoice_data['Date Paid'])) {
$transformed['payments'] = [
[
'date' => date('Y-m-d', strtotime($invoice_data['DatePaid'])),
'date' => $this->parseDate($invoice_data['DatePaid']),
'amount' => $this->getFloat($invoice_data, 'Payments'),
],
];

View File

@ -35,8 +35,8 @@ class InvoiceTransformer extends BaseTransformer
'company_id' => $this->company->id,
'client_id' => $this->getClient($this->getString($data, 'Client'), null),
'number' => $this->getString($data, 'Details'),
'date' => isset($data['Date']) ? date('Y-m-d', strtotime($data['Date'])) : null,
'due_date' => isset($data['Due']) ? date('Y-m-d', strtotime($data['Due'])) : null,
'date' => isset($data['Date']) ? $this->parseDate($data['Date']) : null,
'due_date' => isset($data['Due']) ? $this->parseDate($data['Due']) : null,
'status_id' => Invoice::STATUS_SENT,
'line_items' => [
[

View File

@ -43,7 +43,7 @@ class ExpenseTransformer extends BaseTransformer
'vendor_id' => $this->getVendorIdOrCreate($this->getString($data, 'Vendor')),
'number' => $this->getString($data, 'Bill Number'),
'public_notes' => $this->getString($data, 'Notes / Memo'),
'date' => date('Y-m-d', strtotime($data['Transaction Date Added'])) ?: now()->format('Y-m-d'), //27-01-2022
'date' => $this->parseDate($data['Transaction Date Added']) ?: now()->format('Y-m-d'), //27-01-2022
'currency_id' => $this->company->settings->currency_id,
'category_id' => $this->getOrCreateExpenseCategry($data['Account Name']),
'amount' => $amount,

View File

@ -49,10 +49,10 @@ class InvoiceTransformer extends BaseTransformer
'company_id' => $this->company->id,
'client_id' => $this->getClient($customer_name = $this->getString($invoice_data, $customer_key), null),
'number' => $invoice_number = $this->getString($invoice_data, 'Invoice Number'),
'date' => date('Y-m-d', strtotime($invoice_data[$date_key])) ?: now()->format('Y-m-d'), //27-01-2022
'date' => $this->parseDate($invoice_data[$date_key]) ?: now()->format('Y-m-d'), //27-01-2022
'currency_id' => $this->getCurrencyByCode($invoice_data, 'Currency'),
'status_id' => Invoice::STATUS_SENT,
'due_date' => array_key_exists('Due Date', $invoice_data) ? date('Y-m-d', strtotime($invoice_data['Due Date'])) : null,
'due_date' => array_key_exists('Due Date', $invoice_data) ? $this->parseDate($invoice_data['Due Date']) : null,
];
$line_items = [];
@ -81,7 +81,7 @@ class InvoiceTransformer extends BaseTransformer
} elseif (array_key_exists('Account Type', $record) && $record['Account Type'] === 'System Receivable Invoice') {
// This is a payment
$payments[] = [
'date' => date('Y-m-d', strtotime($invoice_data[$date_key])),
'date' => $this->parseDate($invoice_data[$date_key]),
'amount' => $this->getFloat($record, 'Amount (One column)'),
];
} else {
@ -103,7 +103,7 @@ class InvoiceTransformer extends BaseTransformer
if (array_key_exists('Invoice Paid', $record) && $record['Invoice Paid'] > 0) {
$payments[] = [
'date' => date('Y-m-d', strtotime($record['Last Payment Date'])),
'date' => $this->parseDate($record['Last Payment Date']),
'amount' => $this->getFloat($record, 'Invoice Paid'),
];
}

View File

@ -43,8 +43,8 @@ class InvoiceTransformer extends BaseTransformer
// 'client_id' => $this->getClient($this->getString($invoice_data, 'Customer ID'), $this->getString($invoice_data, 'Primary Contact EmailID')),
'client_id' => $this->harvestClient($invoice_data),
'number' => $this->getString($invoice_data, 'Invoice Number'),
'date' => isset($invoice_data['Invoice Date']) ? date('Y-m-d', strtotime($invoice_data['Invoice Date'])) : null,
'due_date' => isset($invoice_data['Due Date']) ? date('Y-m-d', strtotime($invoice_data['Due Date'])) : null,
'date' => isset($invoice_data['Invoice Date']) ? $this->parseDate($invoice_data['Invoice Date']) : null,
'due_date' => isset($invoice_data['Due Date']) ? $this->parseDate($invoice_data['Due Date']) : null,
'po_number' => $this->getString($invoice_data, 'PurchaseOrder'),
'public_notes' => $this->getString($invoice_data, 'Notes'),
'currency_id' => $this->getCurrencyByCode($invoice_data, 'Currency'),
@ -74,7 +74,7 @@ class InvoiceTransformer extends BaseTransformer
if ($transformed['balance'] < $transformed['amount']) {
$transformed['payments'] = [[
'date' => isset($invoice_data['Last Payment Date']) ? date('Y-m-d', strtotime($invoice_data['Invoice Date'])) : date('Y-m-d'),
'date' => isset($invoice_data['Last Payment Date']) ? $this->parseDate($invoice_data['Invoice Date']) : date('Y-m-d'),
'amount' => $transformed['amount'] - $transformed['balance'],
]];
}

View File

@ -232,7 +232,6 @@ class SendReminders implements ShouldQueue
}
$invoice->service()->touchReminder($template)->save();
// $invoice->save();
}
/**

View File

@ -0,0 +1,56 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Listeners\Statement;
use App\Libraries\MultiDB;
use App\Models\Activity;
use App\Repositories\ActivityRepository;
use Illuminate\Contracts\Queue\ShouldQueue;
use stdClass;
class StatementEmailedActivity implements ShouldQueue
{
protected $activityRepo;
/**
* Create the event listener.
*
* @param ActivityRepository $activityRepo
*/
public function __construct(ActivityRepository $activityRepo)
{
$this->activityRepo = $activityRepo;
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
MultiDB::setDb($event->company->db);
$fields = new stdClass();
$user_id = isset($event->event_vars['user_id']) ? $event->event_vars['user_id'] : $event->client->id;
$fields->user_id = $user_id;
$fields->client_id = $event->client->id;
$fields->notes = $event->end_date;
$fields->company_id = $event->company->id;
$fields->activity_type_id = Activity::EMAIL_STATEMENT;
$this->activityRepo->save($fields, $event->client, $event->event_vars);
}
}

View File

@ -42,18 +42,18 @@ class ClientRegisterService
}
foreach ($rules as $field => $properties) {
if ($field === 'email') {
if ($field == 'email') {
$rules[$field] = array_merge($rules[$field], ['email:rfc', 'max:191', Rule::unique('client_contacts')->where('company_id', $this->company->id)]);
}
if ($field === 'current_password' || $field === 'password') {
if ($field == 'current_password' || $field == 'password') {
$rules[$field] = array_merge($rules[$field], ['string', 'min:6', 'confirmed']);
}
}
if ($this->company->settings->client_portal_terms || $this->company->settings->client_portal_privacy_policy) {
$rules['terms'] = ['required'];
}
// if ($this->company->settings->client_portal_terms || $this->company->settings->client_portal_privacy_policy) {
// $rules['terms'] = ['required'];
// }
foreach ($this->additional as $field) {
if ($field['visible'] ?? true) {

View File

@ -59,7 +59,6 @@ class Register extends Component
public function register(array $data)
{
nlog($data);
$service = new ClientRegisterService(
company: $this->subscription->company,
@ -68,13 +67,8 @@ class Register extends Component
$rules = $service->rules();
nlog($rules);
$data = Validator::make($data, $rules)->validate();
nlog("validated data");
nlog($data);
$client = $service->createClient($data);
$contact = $service->createClientContact($data, $client);

View File

@ -202,6 +202,10 @@ class RegisterOrLogin extends Component
$this->register_fields = [...collect($this->subscription->company->client_registration_fields ?? [])->toArray()];
// if ($this->subscription->company->settings->client_portal_terms || $this->subscription->company->settings->client_portal_privacy_policy) {
// $this->register_fields[] = ['key' => 'terms', 'required' => true, 'visible' => 'true'];
// }
$first_gateway = collect($this->subscription->company->company_gateways)
->sortBy('sort_order')
->first();

View File

@ -258,6 +258,9 @@ class Activity extends StaticModel
public const PAYMENT_EMAILED = 138;
public const VENDOR_NOTIFICATION_EMAIL = 139;
public const EMAIL_STATEMENT = 140;
protected $casts = [
'is_system' => 'boolean',
@ -469,6 +472,8 @@ class Activity extends StaticModel
':adjustment' => $translation = [substr($variable, 1) => [ 'label' => Number::formatMoney($this?->payment?->refunded, $this?->payment?->client ?? $this->company) ?? '', 'hashed_id' => '']],
':ip' => $translation = [ 'ip' => $this->ip ?? ''],
':contact' => $translation = $this->resolveContact(),
':notes' => $translation = [ 'notes' => $this->notes ?? ''],
default => $translation = [],
};

View File

@ -164,6 +164,7 @@ class Quote extends BaseModel
protected $casts = [
// 'date' => 'date:Y-m-d',
'tax_data' => 'object',
'due_date' => 'date:Y-m-d',
'partial_due_date' => 'date:Y-m-d',
'line_items' => 'object',

View File

@ -13,15 +13,16 @@
namespace App\PaymentDrivers\Authorize;
use App\Models\Invoice;
use App\PaymentDrivers\AuthorizePaymentDriver;
use App\Utils\Traits\MakesHash;
use net\authorize\api\contract\v1\CreateTransactionRequest;
use net\authorize\api\contract\v1\ExtendedAmountType;
use net\authorize\api\contract\v1\OpaqueDataType;
use App\PaymentDrivers\Authorize\FDSReview;
use net\authorize\api\contract\v1\OrderType;
use App\PaymentDrivers\AuthorizePaymentDriver;
use net\authorize\api\contract\v1\PaymentType;
use net\authorize\api\contract\v1\SettingType;
use net\authorize\api\contract\v1\OpaqueDataType;
use net\authorize\api\contract\v1\ExtendedAmountType;
use net\authorize\api\contract\v1\TransactionRequestType;
use net\authorize\api\contract\v1\CreateTransactionRequest;
use net\authorize\api\controller\CreateTransactionController;
/**
@ -143,6 +144,12 @@ class AuthorizeTransaction
nlog(' Code : '.$tresponse->getMessages()[0]->getCode());
nlog(' Description : '.$tresponse->getMessages()[0]->getDescription());
nlog(print_r($tresponse->getMessages()[0], 1));
if($tresponse->getResponseCode() == "4"){
//notify user that this transaction is being held under FDS review:
FDSReview::dispatch((string)$tresponse->getTransId(), $this->authorize->payment_hash, $this->authorize->company_gateway->company->db);
}
} else {
nlog('Transaction Failed ');
if ($tresponse->getErrors() != null) {

View File

@ -13,14 +13,15 @@
namespace App\PaymentDrivers\Authorize;
use App\Models\Invoice;
use App\PaymentDrivers\AuthorizePaymentDriver;
use App\Utils\Traits\MakesHash;
use net\authorize\api\contract\v1\CreateTransactionRequest;
use net\authorize\api\contract\v1\CustomerProfilePaymentType;
use net\authorize\api\contract\v1\ExtendedAmountType;
use App\PaymentDrivers\Authorize\FDSReview;
use net\authorize\api\contract\v1\OrderType;
use App\PaymentDrivers\AuthorizePaymentDriver;
use net\authorize\api\contract\v1\ExtendedAmountType;
use net\authorize\api\contract\v1\PaymentProfileType;
use net\authorize\api\contract\v1\TransactionRequestType;
use net\authorize\api\contract\v1\CreateTransactionRequest;
use net\authorize\api\contract\v1\CustomerProfilePaymentType;
use net\authorize\api\controller\CreateTransactionController;
/**
@ -109,6 +110,12 @@ class ChargePaymentProfile
nlog(' Code : '.$tresponse->getMessages()[0]->getCode());
nlog(' Description : '.$tresponse->getMessages()[0]->getDescription());
nlog(print_r($tresponse->getMessages()[0], 1));
if($tresponse->getResponseCode() == "4") {
//notify user that this transaction is being held under FDS review:
FDSReview::dispatch((string)$tresponse->getTransId(), $this->authorize?->payment_hash, $this->authorize->company_gateway->company->db);
}
} else {
nlog('Transaction Failed ');
if ($tresponse->getErrors() != null) {

View File

@ -0,0 +1,67 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\PaymentDrivers\Authorize;
use App\Libraries\MultiDB;
use App\Models\PaymentHash;
use App\Services\Email\Email;
use Illuminate\Bus\Queueable;
use App\Services\Email\EmailObject;
use Illuminate\Support\Facades\App;
use Illuminate\Mail\Mailables\Address;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
/*Multi Mailer implemented*/
class FDSReview implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
use Queueable;
use SerializesModels;
public $tries = 1; //number of retries
public $deleteWhenMissingModels = true;
public function __construct(private string $transaction_reference, private PaymentHash $payment_hash, private string $db)
{
}
public function handle()
{
MultiDB::setDB($this->db);
$company = $this->payment_hash->fee_invoice->company;
App::setLocale($company->getLocale());
$invoices_string = \implode(', ', collect($this->payment_hash->invoices())->pluck('invoice_number')->toArray()) ?: '';
$body = "Transaction {$this->transaction_reference} has been held for your review in Auth.net based on your Fraud Detection Settings.\n\n\nWe have marked invoices {$invoices_string} as paid in Invoice Ninja.\n\n\nPlease review this transaction in your auth.net account, and authorize if correct to ensure the transaction is finalized as expected.\n\n\nIf these charges need to be cancelled, you will need to delete the payments that have been created in Invoice Ninja.";
$mo = new EmailObject();
$mo->subject = "Transaction {$this->transaction_reference} held for review by auth.net";
$mo->body = nl2br($body);
$mo->text_body = $body;
$mo->company_key = $company->company_key;
$mo->html_template = 'email.template.generic';
$mo->to = [new Address($company->owner()->email, $company->owner()->present()->name())];
Email::dispatch($mo, $company);
}
}

View File

@ -52,25 +52,33 @@ class PaymentIntentFailureWebhook implements ShouldQueue
public function handle()
{
nlog("payment intent failed");
MultiDB::findAndSetDbByCompanyKey($this->company_key);
nlog($this->stripe_request);
$company = Company::query()->where('company_key', $this->company_key)->first();
foreach ($this->stripe_request as $transaction) {
if (array_key_exists('payment_intent', $transaction)) {
$payment = Payment::query()
->where('company_id', $company->id)
->where(function ($query) use ($transaction) {
$query->where('transaction_reference', $transaction['payment_intent'])
->orWhere('transaction_reference', $transaction['id']);
})
->first();
} else {
$payment = Payment::query()
->where('company_id', $company->id)
->where('transaction_reference', $transaction['id'])
->first();
}
nlog($transaction);
$payment = Payment::query()
->where('company_id', $company->id)
->where(function ($query) use ($transaction) {
if(isset($transaction['payment_intent']))
$query->where('transaction_reference', $transaction['payment_intent']);
if(isset($transaction['payment_intent']) && isset($transaction['id']))
$query->orWhere('transaction_reference', $transaction['id']);
if(!isset($transaction['payment_intent']) && isset($transaction['id'])) {
$query->where('transaction_reference', $transaction['id']);
}
})
->first();
if ($payment) {
$client = $payment->client;

View File

@ -64,17 +64,28 @@ class PaymentIntentProcessingWebhook implements ShouldQueue
$company = Company::query()->where('company_key', $this->company_key)->first();
foreach ($this->stripe_request as $transaction) {
if (array_key_exists('payment_intent', $transaction)) {
$payment = Payment::query()
->where('company_id', $company->id)
->where('transaction_reference', $transaction['payment_intent'])
->first();
} else {
$payment = Payment::query()
->where('company_id', $company->id)
->where('transaction_reference', $transaction['id'])
->first();
}
$payment = Payment::query()
->where('company_id', $company->id)
->where(function ($query) use ($transaction) {
if(isset($transaction['payment_intent'])) {
$query->where('transaction_reference', $transaction['payment_intent']);
}
if(isset($transaction['payment_intent']) && isset($transaction['id'])) {
$query->orWhere('transaction_reference', $transaction['id']);
}
if(!isset($transaction['payment_intent']) && isset($transaction['id'])) {
$query->where('transaction_reference', $transaction['id']);
}
})
->first();
if ($payment) {
$payment->status_id = Payment::STATUS_PENDING;

View File

@ -63,17 +63,27 @@ class PaymentIntentWebhook implements ShouldQueue
$company = Company::query()->where('company_key', $this->company_key)->first();
foreach ($this->stripe_request as $transaction) {
if (array_key_exists('payment_intent', $transaction)) {
$payment = Payment::query()
->where('company_id', $company->id)
->where('transaction_reference', $transaction['payment_intent'])
->first();
} else {
$payment = Payment::query()
->where('company_id', $company->id)
->where('transaction_reference', $transaction['id'])
->first();
}
$payment = Payment::query()
->where('company_id', $company->id)
->where(function ($query) use ($transaction) {
if(isset($transaction['payment_intent'])) {
$query->where('transaction_reference', $transaction['payment_intent']);
}
if(isset($transaction['payment_intent']) && isset($transaction['id'])) {
$query->orWhere('transaction_reference', $transaction['id']);
}
if(!isset($transaction['payment_intent']) && isset($transaction['id'])) {
$query->where('transaction_reference', $transaction['id']);
}
})
->first();
if ($payment) {
$payment->status_id = Payment::STATUS_COMPLETED;

View File

@ -680,7 +680,7 @@ class StripePaymentDriver extends BaseDriver
}
if ($request->type === 'payment_intent.processing') {
PaymentIntentProcessingWebhook::dispatch($request->data, $request->company_key, $this->company_gateway->id)->delay(now()->addSeconds(2));
PaymentIntentProcessingWebhook::dispatch($request->data, $request->company_key, $this->company_gateway->id)->delay(now()->addSeconds(rand(10,12)));
return response()->json([], 200);
}
@ -692,7 +692,7 @@ class StripePaymentDriver extends BaseDriver
}
if ($request->type === 'payment_intent.partially_funded') {
PaymentIntentPartiallyFundedWebhook::dispatch($request->data, $request->company_key, $this->company_gateway->id)->delay(now()->addSeconds(rand(5, 10)));
PaymentIntentPartiallyFundedWebhook::dispatch($request->data, $request->company_key, $this->company_gateway->id)->delay(now()->addSeconds(rand(10, 15)));
return response()->json([], 200);
}
@ -705,20 +705,27 @@ class StripePaymentDriver extends BaseDriver
if ($request->type === 'charge.succeeded') {
foreach ($request->data as $transaction) {
if (array_key_exists('payment_intent', $transaction) && $transaction['payment_intent']) {
$payment = Payment::query()
// ->where('company_id', $request->getCompany()->id)
->where(function ($query) use ($transaction) {
$query->where('transaction_reference', $transaction['payment_intent'])
->orWhere('transaction_reference', $transaction['id']);
})
->first();
} else {
$payment = Payment::query()
// ->where('company_id', $request->getCompany()->id)
->where('transaction_reference', $transaction['id'])
->first();
}
$payment = Payment::query()
->where('company_id', $this->company_gateway->company_id)
->where(function ($query) use ($transaction) {
if(isset($transaction['payment_intent'])) {
$query->where('transaction_reference', $transaction['payment_intent']);
}
if(isset($transaction['payment_intent']) && isset($transaction['id'])) {
$query->orWhere('transaction_reference', $transaction['id']);
}
if(!isset($transaction['payment_intent']) && isset($transaction['id'])) {
$query->where('transaction_reference', $transaction['id']);
}
})
->first();
if ($payment) {
@ -745,19 +752,28 @@ class StripePaymentDriver extends BaseDriver
], $this->stripe_connect_auth);
if ($charge->captured) {
$payment = false;
$payment = Payment::query()
->where('company_id', $this->company_gateway->company_id)
->where(function ($query) use ($transaction) {
if(isset($transaction['payment_intent'])) {
$query->where('transaction_reference', $transaction['payment_intent']);
}
if(isset($transaction['payment_intent']) && isset($transaction['id'])) {
$query->orWhere('transaction_reference', $transaction['id']);
}
if(!isset($transaction['payment_intent']) && isset($transaction['id'])) {
$query->where('transaction_reference', $transaction['id']);
}
})
->first();
if (isset($transaction['payment_intent'])) {
$payment = Payment::query()
->where('transaction_reference', $transaction['payment_intent'])
->where('company_id', $request->getCompany()->id)
->first();
} elseif (isset($transaction['id'])) {
$payment = Payment::query()
->where('transaction_reference', $transaction['id'])
->where('company_id', $request->getCompany()->id)
->first();
}
if ($payment) {
$payment->status_id = Payment::STATUS_COMPLETED;

View File

@ -97,6 +97,7 @@ use App\Events\RecurringQuote\RecurringQuoteWasCreated;
use App\Events\RecurringQuote\RecurringQuoteWasDeleted;
use App\Events\RecurringQuote\RecurringQuoteWasRestored;
use App\Events\RecurringQuote\RecurringQuoteWasUpdated;
use App\Events\Statement\StatementWasEmailed;
use App\Events\Subscription\SubscriptionWasArchived;
use App\Events\Subscription\SubscriptionWasCreated;
use App\Events\Subscription\SubscriptionWasDeleted;
@ -222,6 +223,7 @@ use App\Listeners\RecurringQuote\RecurringQuoteDeletedActivity;
use App\Listeners\RecurringQuote\RecurringQuoteRestoredActivity;
use App\Listeners\RecurringQuote\UpdateRecurringQuoteActivity;
use App\Listeners\SendVerificationNotification;
use App\Listeners\Statement\StatementEmailedActivity;
use App\Listeners\User\ArchivedUserActivity;
use App\Listeners\User\CreatedUserActivity;
use App\Listeners\User\DeletedUserActivity;
@ -577,6 +579,9 @@ class EventServiceProvider extends ServiceProvider
RecurringInvoiceWasRestored::class => [
RecurringInvoiceRestoredActivity::class,
],
StatementWasEmailed::class => [
StatementEmailedActivity::class,
],
TaskWasCreated::class => [
CreatedTaskActivity::class,
],

View File

@ -11,6 +11,7 @@
namespace App\Services\Client;
use App\Utils\Ninja;
use App\Utils\Number;
use App\Models\Client;
use App\Models\Credit;
@ -23,6 +24,7 @@ use App\Services\Email\EmailObject;
use App\Utils\Traits\GeneratesCounter;
use Illuminate\Mail\Mailables\Address;
use Illuminate\Database\QueryException;
use App\Events\Statement\StatementWasEmailed;
class ClientService
{
@ -275,6 +277,9 @@ class ClientService
$email_object = $this->buildStatementMailableData($pdf);
Email::dispatch($email_object, $this->client->company);
event(new StatementWasEmailed($this->client, $this->client->company, $this->client_end_date, Ninja::eventVars()));
}
/**
@ -307,8 +312,8 @@ class ClientService
$email_object->attachments = [['file' => base64_encode($pdf), 'name' => ctrans('texts.statement') . ".pdf"]];
$email_object->client_id = $this->client->id;
$email_object->entity_class = Invoice::class;
$email_object->entity_id = $invoice->id ?? null;
$email_object->invitation_id = $invoice->invitations->first()->id ?? null;
$email_object->entity_id = $invoice?->id ?? null;
$email_object->invitation_id = $invoice?->invitations?->first()?->id ?? null;
$email_object->email_template_subject = 'email_subject_statement';
$email_object->email_template_body = 'email_template_statement';
$email_object->variables = [

View File

@ -44,6 +44,8 @@ class EmailStatementService
$query->cursor()
->each(function ($_client) {
/**@var \App\Models\Client $_client */
$this->client = $_client;
//work out the date range

View File

@ -90,4 +90,18 @@ trait CleanLineItems
return $item;
}
private function entityTotalAmount($items)
{
$total = 0;
foreach($items as $item)
{
$total += ($item['cost'] * $item['quantity']);
}
nlog($total);
return $total;
}
}

View File

@ -17,8 +17,8 @@ return [
'require_https' => env('REQUIRE_HTTPS', true),
'app_url' => rtrim(env('APP_URL', ''), '/'),
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
'app_version' => env('APP_VERSION', '5.8.53'),
'app_tag' => env('APP_TAG', '5.8.53'),
'app_version' => env('APP_VERSION', '5.8.54'),
'app_tag' => env('APP_TAG', '5.8.54'),
'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', false),

View File

@ -4010,7 +4010,7 @@ $lang = array(
'user_detached' => 'فصل المستخدم عن الشركة',
'create_webhook_failure' => 'فشل إنشاء Webhook',
'payment_message_extended' => 'شكرًا لك على دفعك لـ :amount مقابل :invoice',
'online_payments_minimum_note' => 'ملاحظة: يتم دعم المدفوعات عبر الإنترنت فقط إذا كان المبلغ أكبر من دولار واحد أو ما يعادله بالعملة.',
'online_payments_minimum_note' => 'Note: Online payments are supported only if amount is larger than $1 or currency equivalent.',
'payment_token_not_found' => 'لم يتم العثور على رمز الدفع ، يرجى المحاولة مرة أخرى. إذا استمرت المشكلة ، فحاول استخدام طريقة دفع أخرى',
'vendor_address1' => 'شارع البائع',
'vendor_address2' => 'بائع شقة / جناح',
@ -5278,6 +5278,9 @@ $lang = array(
'rappen_rounding_help' => 'جولة المبلغ إلى 5 سنتات',
'assign_group' => 'تعيين المجموعة',
'paypal_advanced_cards' => 'Advanced Card Payments',
'local_domain_help' => 'EHLO domain (optional)',
'port_help' => 'ie. 25,587,465',
'host_help' => 'ie. smtp.gmail.com',
);
return $lang;

View File

@ -4029,7 +4029,7 @@ $lang = array(
'user_detached' => 'User detached from company',
'create_webhook_failure' => 'Failed to create Webhook',
'payment_message_extended' => 'Thank you for your payment of :amount for :invoice',
'online_payments_minimum_note' => 'Note: Online payments are supported only if amount is bigger than $1 or currency equivalent.',
'online_payments_minimum_note' => 'Note: Online payments are supported only if amount is larger than $1 or currency equivalent.',
'payment_token_not_found' => 'Payment token not found, please try again. If an issue still persist, try with another payment method',
'vendor_address1' => 'Vendor Street',
'vendor_address2' => 'Vendor Apt/Suite',
@ -5297,6 +5297,9 @@ $lang = array(
'rappen_rounding_help' => 'Import rodó a 5 cèntims',
'assign_group' => 'Assigna un grup',
'paypal_advanced_cards' => 'Advanced Card Payments',
'local_domain_help' => 'EHLO domain (optional)',
'port_help' => 'ie. 25,587,465',
'host_help' => 'ie. smtp.gmail.com',
);
return $lang;

View File

@ -4028,7 +4028,7 @@ $lang = array(
'user_detached' => 'Bruger løsrevet fra selskabet',
'create_webhook_failure' => 'Opret Webhook mislykkedes',
'payment_message_extended' => 'Tak for din Betaling på :amount for :invoice',
'online_payments_minimum_note' => 'Bemærk : Online Betalinger understøttes kun, hvis Beløb er større end $1 eller tilsvarende i valuta.',
'online_payments_minimum_note' => 'Note: Online payments are supported only if amount is larger than $1 or currency equivalent.',
'payment_token_not_found' => 'Betaling blev ikke fundet, prøv venligst igen. Hvis et problem stadig fortsætter, kan du prøve med en anden Betaling',
'vendor_address1' => 'Sælger Gade',
'vendor_address2' => 'Sælger Apt/Suite',
@ -5296,6 +5296,9 @@ $lang = array(
'rappen_rounding_help' => 'Rund Beløb til 5 øre',
'assign_group' => 'Tildel gruppe',
'paypal_advanced_cards' => 'Advanced Card Payments',
'local_domain_help' => 'EHLO domain (optional)',
'port_help' => 'ie. 25,587,465',
'host_help' => 'ie. smtp.gmail.com',
);
return $lang;

View File

@ -4031,7 +4031,7 @@ https://invoiceninja.github.io/docs/migration/#troubleshooting',
'user_detached' => 'Nutzer wurden vom Unternehmen entkoppelt',
'create_webhook_failure' => 'Webhook konnte nicht erstellt werden',
'payment_message_extended' => 'Vielen Dank für Ihre Zahlung von :amount für die Rechnung :invoice',
'online_payments_minimum_note' => 'Hinweis: Online-Zahlungen werden nur unterstützt, wenn der Betrag größer als 1€ oder der entsprechende Währungsbetrag ist.',
'online_payments_minimum_note' => 'Note: Online payments are supported only if amount is larger than $1 or currency equivalent.',
'payment_token_not_found' => 'Zahlungstoken nicht gefunden, bitte versuchen Sie es erneut. Wenn das Problem weiterhin besteht, versuchen Sie es mit einer anderen Zahlungsmethode',
'vendor_address1' => 'Straße Lieferant',
'vendor_address2' => 'Lieferant Apt/Suite',
@ -5301,6 +5301,9 @@ Leistungsempfängers',
'rappen_rounding_help' => 'Betrag auf 5 Cent aufrunden',
'assign_group' => 'Gruppe zuweisen',
'paypal_advanced_cards' => 'Advanced Card Payments',
'local_domain_help' => 'EHLO domain (optional)',
'port_help' => 'ie. 25,587,465',
'host_help' => 'ie. smtp.gmail.com',
);
return $lang;

View File

@ -5300,6 +5300,10 @@ $lang = array(
'local_domain_help' => 'EHLO domain (optional)',
'port_help' => 'ie. 25,587,465',
'host_help' => 'ie. smtp.gmail.com',
'always_show_required_fields' => 'Allows show required fields form',
'always_show_required_fields_help' => 'Displays the required fields form always at checkout',
'advanced_cards' => 'Advanced Cards',
'activity_140' => 'Statement sent to :client',
);
return $lang;

View File

@ -4028,7 +4028,7 @@ $lang = array(
'user_detached' => 'Usuario desvinculado de la empresa',
'create_webhook_failure' => 'No se pudo crear el webhook',
'payment_message_extended' => 'Gracias por su pago de :amount por :invoice',
'online_payments_minimum_note' => 'Nota: Los pagos en línea solo se admiten si el monto es superior a $ 1 o su equivalente en moneda.',
'online_payments_minimum_note' => 'Note: Online payments are supported only if amount is larger than $1 or currency equivalent.',
'payment_token_not_found' => 'No se encontró el token de pago, inténtelo de nuevo. Si el problema persiste, intente con otro método de pago',
'vendor_address1' => 'Calle del vendedor',
'vendor_address2' => 'Proveedor Apt/Suite',
@ -5296,6 +5296,9 @@ $lang = array(
'rappen_rounding_help' => 'Monto redondo a 5 centavos',
'assign_group' => 'Asignar grupo',
'paypal_advanced_cards' => 'Advanced Card Payments',
'local_domain_help' => 'EHLO domain (optional)',
'port_help' => 'ie. 25,587,465',
'host_help' => 'ie. smtp.gmail.com',
);
return $lang;

View File

@ -2193,7 +2193,7 @@ Una vez que tenga los montos, vuelva a esta página de métodos de pago y haga c
'mailgun_private_key' => 'Mailgun Private Key',
'brevo_domain' => 'Dominio Brevo',
'brevo_private_key' => 'Clave privada de Brevo',
'send_test_email' => 'Send Test Email',
'send_test_email' => 'Enviar correo electrónico de prueba',
'select_label' => 'Seleccionar etiqueta',
'label' => 'Etiqueta',
'service' => 'Servicio',
@ -4025,7 +4025,7 @@ Una vez que tenga los montos, vuelva a esta página de métodos de pago y haga c
'user_detached' => 'Usuario desligado de la compañía',
'create_webhook_failure' => 'Fallo al crear Webhook',
'payment_message_extended' => 'Gracias por su pago de :amount para :invoice',
'online_payments_minimum_note' => 'Nota: Los pagos online están soportados si la cantidad es mayor que $1 o moneda equivalente.',
'online_payments_minimum_note' => 'Nota: Los pagos en línea solo se admiten si el importe es superior a 1 € o moneda equivalente.',
'payment_token_not_found' => 'No se ha encontrado el token de pago, inténtelo de nuevo. Si el problema persiste, inténtelo con otro método de pago',
'vendor_address1' => 'Calle de Proveedor',
'vendor_address2' => 'Bloq/Pta del Proveedor',
@ -4166,7 +4166,7 @@ Una vez que tenga los montos, vuelva a esta página de métodos de pago y haga c
'one_time_purchases' => 'Compras únicas',
'recurring_purchases' => 'Pagos recurrentes',
'you_might_be_interested_in_following' => 'Puede que te interese lo siguiente',
'quotes_with_status_sent_can_be_approved' => 'Only quotes with "Sent" status can be approved. Expired quotes cannot be approved.',
'quotes_with_status_sent_can_be_approved' => 'Sólo se pueden aprobar presupuestos con estado "Enviado". Los presupuestos vencidos no se pueden aprobar.',
'no_quotes_available_for_download' => 'No hay presupuestos disponibles para descargar.',
'copyright' => 'Derechos de autor',
'user_created_user' => ':user creó :created_user a las :time',
@ -5292,8 +5292,11 @@ De lo contrario, este campo deberá dejarse en blanco.',
'flutter_web_warning' => 'Recomendamos utilizar la nueva aplicación web o la aplicación de escritorio para obtener el mejor rendimiento.',
'rappen_rounding' => 'Redondeo de rappen',
'rappen_rounding_help' => 'Cantidad redonda a 5 centimos',
'assign_group' => 'Assign group',
'paypal_advanced_cards' => 'Advanced Card Payments',
'assign_group' => 'Asignar grupo',
'paypal_advanced_cards' => 'Pagos avanzados con tarjeta',
'local_domain_help' => 'Dominio EHLO (opcional)',
'port_help' => 'Ej. 25.587.465',
'host_help' => 'Ej. smtp.gmail.com',
);
return $lang;

View File

@ -4029,7 +4029,7 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette
'user_detached' => 'Utilisateur détaché de l\'entreprise',
'create_webhook_failure' => 'Échec de la création du Webhook',
'payment_message_extended' => 'Merci pour votre paiement de :amount pour :invoice',
'online_payments_minimum_note' => 'Remarque : Les paiements en ligne ne sont pris en charge que si le montant est supérieur à 1 $ ou l\'équivalent en devise.',
'online_payments_minimum_note' => 'Note: Online payments are supported only if amount is larger than $1 or currency equivalent.',
'payment_token_not_found' => 'Le jeton de paiement est introuvable. Veuillez essayer de nouveau. Si le problème persiste, essayez avec un autre mode de paiement',
'vendor_address1' => 'Rue du fournisseur',
'vendor_address2' => 'Appt/Bâtiment du fournisseur',
@ -5297,6 +5297,9 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette
'rappen_rounding_help' => 'Montant rond à 5 centimes',
'assign_group' => 'Attribuer un groupe',
'paypal_advanced_cards' => 'Advanced Card Payments',
'local_domain_help' => 'EHLO domain (optional)',
'port_help' => 'ie. 25,587,465',
'host_help' => 'ie. smtp.gmail.com',
);
return $lang;

View File

@ -4026,7 +4026,7 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette
'user_detached' => 'L\'utilisateur a été détaché de l\'entreprise',
'create_webhook_failure' => 'Création Webhook impossible',
'payment_message_extended' => 'Merci pour votre paiement d\'un montant de :amount',
'online_payments_minimum_note' => 'Note: Les paiements en ligne sont acceptés seulement si le montant est plus élevé que 1$ ou en devise équivalente.',
'online_payments_minimum_note' => 'Note: Les paiements en ligne sont possibles seulement si le montant est supérieur à 1$ ou l\'équivalent en devise.',
'payment_token_not_found' => 'Le jeton de paiement est introuvable. Veuillez essayer de nouveau. Si le problème persiste, essayez avec un autre mode de paiement',
'vendor_address1' => 'Rue du fournisseur',
'vendor_address2' => 'App du fournisseur',
@ -5294,6 +5294,9 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette
'rappen_rounding_help' => 'Arrondir au 5 cents',
'assign_group' => 'Assigner un groupe',
'paypal_advanced_cards' => 'Paiements avancés pour carte',
'local_domain_help' => 'Domaine EHLO (facultatif)',
'port_help' => 'ex. 25,587,465',
'host_help' => 'ex. smtp.gmail.com',
);
return $lang;

View File

@ -4026,7 +4026,7 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette
'user_detached' => 'L\'utilisateur a été détaché de l\'entreprise',
'create_webhook_failure' => 'Création Webhook impossible',
'payment_message_extended' => 'Merci pour votre paiement d\'un montant de :amount',
'online_payments_minimum_note' => 'Note: Les paiements en ligne sont acceptés seulement si le montant est plus élevé que 1$ ou en devise équivalente.',
'online_payments_minimum_note' => 'Note: Online payments are supported only if amount is larger than $1 or currency equivalent.',
'payment_token_not_found' => 'Le jeton de paiement est introuvable. Veuillez essayer de nouveau. Si le problème persiste, essayez avec un autre mode de paiement',
'vendor_address1' => 'Rue du fournisseur',
'vendor_address2' => 'App du fournisseur',
@ -5294,6 +5294,9 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette
'rappen_rounding_help' => 'Montant rond à 5 centimes',
'assign_group' => 'Attribuer un groupe',
'paypal_advanced_cards' => 'Advanced Card Payments',
'local_domain_help' => 'EHLO domain (optional)',
'port_help' => 'ie. 25,587,465',
'host_help' => 'ie. smtp.gmail.com',
);
return $lang;

View File

@ -4027,7 +4027,7 @@ $lang = array(
'user_detached' => 'משתמש מנותק מהחברה',
'create_webhook_failure' => 'יצירת Webhook נכשלה',
'payment_message_extended' => 'תודה על התשלום בסך :amount עבור :invoice',
'online_payments_minimum_note' => 'הערה: תשלומים מקוונים נתמכים רק אם הסכום גדול מ-$1 או שווה ערך במטבע.',
'online_payments_minimum_note' => 'Note: Online payments are supported only if amount is larger than $1 or currency equivalent.',
'payment_token_not_found' => 'אסימון תשלום לא נמצא, אנא נסה שוב. אם הבעיה עדיין נמשכת, נסה עם אמצעי תשלום אחר',
'vendor_address1' => 'רחוב הספקים',
'vendor_address2' => 'דירת ספק/סוויטה',
@ -5295,6 +5295,9 @@ $lang = array(
'rappen_rounding_help' => 'סכום עגול עד 5 סנט',
'assign_group' => 'הקצה קבוצה',
'paypal_advanced_cards' => 'Advanced Card Payments',
'local_domain_help' => 'EHLO domain (optional)',
'port_help' => 'ie. 25,587,465',
'host_help' => 'ie. smtp.gmail.com',
);
return $lang;

View File

@ -4013,7 +4013,7 @@ adva :date',
'user_detached' => 'Felhasználó leválasztva',
'create_webhook_failure' => 'Webhook létrehozása sikertelen',
'payment_message_extended' => 'Fizetési üzenet kiterjesztve',
'online_payments_minimum_note' => 'Online fizetések minimális megjegyzése',
'online_payments_minimum_note' => 'Note: Online payments are supported only if amount is larger than $1 or currency equivalent.',
'payment_token_not_found' => 'Fizetési token nem található',
'vendor_address1' => 'Szállító címe 1',
'vendor_address2' => 'Szállító címe 2',
@ -5281,6 +5281,9 @@ adva :date',
'rappen_rounding_help' => 'Kerek összeg 5 cent',
'assign_group' => 'Csoport hozzárendelése',
'paypal_advanced_cards' => 'Advanced Card Payments',
'local_domain_help' => 'EHLO domain (optional)',
'port_help' => 'ie. 25,587,465',
'host_help' => 'ie. smtp.gmail.com',
);
return $lang;

View File

@ -4020,7 +4020,7 @@ $lang = array(
'user_detached' => 'Utente separato dall\'azienda',
'create_webhook_failure' => 'Impossibile creare il webhook',
'payment_message_extended' => 'Grazie per il pagamento di :amount per :invoice',
'online_payments_minimum_note' => 'Nota: i pagamenti online sono supportati solo se l&#39;importo è superiore a $ 1 o equivalente in valuta.',
'online_payments_minimum_note' => 'Note: Online payments are supported only if amount is larger than $1 or currency equivalent.',
'payment_token_not_found' => 'Token di pagamento non trovato, riprova. Se il problema persiste, prova con un altro metodo di pagamento',
'vendor_address1' => 'Via Fornitore',
'vendor_address2' => 'Scala/Appartamento Fornitore',
@ -5288,6 +5288,9 @@ $lang = array(
'rappen_rounding_help' => 'Ammontare tondo a 5 centesimi',
'assign_group' => 'Assegna gruppo',
'paypal_advanced_cards' => 'Advanced Card Payments',
'local_domain_help' => 'EHLO domain (optional)',
'port_help' => 'ie. 25,587,465',
'host_help' => 'ie. smtp.gmail.com',
);
return $lang;

View File

@ -2197,7 +2197,7 @@ $lang = array(
'mailgun_private_key' => 'Mailgun Private Key',
'brevo_domain' => 'Brevo Domain',
'brevo_private_key' => 'Brevo Private Key',
'send_test_email' => 'Send test email',
'send_test_email' => 'Send Test Email',
'select_label' => 'Select Label',
'label' => 'Label',
'service' => 'Service',
@ -4029,7 +4029,7 @@ $lang = array(
'user_detached' => 'User detached from company',
'create_webhook_failure' => 'Failed to create Webhook',
'payment_message_extended' => ':invoice に対して :amount をお支払いいただきありがとうございます。',
'online_payments_minimum_note' => '注: オンラインでの支払いは、金額が 1 ドルまたは同等の通貨を超える場合にのみサポートされます。',
'online_payments_minimum_note' => 'Note: Online payments are supported only if amount is larger than $1 or currency equivalent.',
'payment_token_not_found' => 'Payment token not found, please try again. If an issue still persist, try with another payment method',
'vendor_address1' => 'Vendor Street',
'vendor_address2' => 'Vendor Apt/Suite',
@ -4170,7 +4170,7 @@ $lang = array(
'one_time_purchases' => 'One time purchases',
'recurring_purchases' => 'Recurring purchases',
'you_might_be_interested_in_following' => 'You might be interested in the following',
'quotes_with_status_sent_can_be_approved' => 'Only quotes with "Sent" status can be approved.',
'quotes_with_status_sent_can_be_approved' => 'Only quotes with "Sent" status can be approved. Expired quotes cannot be approved.',
'no_quotes_available_for_download' => 'No quotes available for download.',
'copyright' => 'Copyright',
'user_created_user' => ':user は :created_user を :time に作成しました',
@ -5295,6 +5295,11 @@ $lang = array(
'flutter_web_warning' => 'We recommend using the new web app or the desktop app for the best performance',
'rappen_rounding' => 'Rappen Rounding',
'rappen_rounding_help' => 'Round amount to 5 cents',
'assign_group' => 'Assign group',
'paypal_advanced_cards' => 'Advanced Card Payments',
'local_domain_help' => 'EHLO domain (optional)',
'port_help' => 'ie. 25,587,465',
'host_help' => 'ie. smtp.gmail.com',
);
return $lang;

View File

@ -4009,7 +4009,7 @@ $lang = array(
'user_detached' => 'អ្នកប្រើប្រាស់ត្រូវបានផ្តាច់ចេញពីក្រុមហ៊ុន',
'create_webhook_failure' => 'បរាជ័យក្នុងការបង្កើត Webhook',
'payment_message_extended' => 'សូមអរគុណចំពោះការបង់ប្រាក់របស់អ្នក :amount សម្រាប់ :invoice',
'online_payments_minimum_note' => 'ចំណាំ៖ ការទូទាត់តាមអ៊ីនធឺណិតត្រូវបានគាំទ្រលុះត្រាតែចំនួនទឹកប្រាក់ធំជាង $1 ឬសមមូលរូបិយប័ណ្ណ។',
'online_payments_minimum_note' => 'Note: Online payments are supported only if amount is larger than $1 or currency equivalent.',
'payment_token_not_found' => 'រកមិនឃើញនិមិត្តសញ្ញាបង់ប្រាក់ទេ សូមព្យាយាមម្តងទៀត។ ប្រសិនបើបញ្ហានៅតែកើតមាន សូមសាកល្បងជាមួយវិធីបង់ប្រាក់ផ្សេងទៀត។',
'vendor_address1' => 'ផ្លូវអ្នកលក់',
'vendor_address2' => 'អ្នកលក់ Apt/Suite',
@ -5277,6 +5277,9 @@ $lang = array(
'rappen_rounding_help' => 'ចំនួនទឹកប្រាក់ជុំដល់ 5 សេន',
'assign_group' => 'ចាត់តាំងក្រុម',
'paypal_advanced_cards' => 'Advanced Card Payments',
'local_domain_help' => 'EHLO domain (optional)',
'port_help' => 'ie. 25,587,465',
'host_help' => 'ie. smtp.gmail.com',
);
return $lang;

View File

@ -4029,7 +4029,7 @@ $lang = array(
'user_detached' => 'ຜູ້ໃຊ້ຖືກແຍກອອກຈາກບໍລິສັດ',
'create_webhook_failure' => 'ລົ້ມເຫລວໃນການສ້າງ Webhook',
'payment_message_extended' => 'ຂໍຂອບໃຈສຳລັບການຈ່າຍເງິນ:ຈຳນວນເງິນ:ໃບແຈ້ງໜີ້',
'online_payments_minimum_note' => 'ໝາຍເຫດ: ການຈ່າຍເງິນອອນລາຍແມ່ນຮອງຮັບໄດ້ພຽງແຕ່ຖ້າຈຳນວນເງິນໃຫຍ່ກວ່າ $1 ຫຼື ທຽບເທົ່າສະກຸນເງິນເທົ່ານັ້ນ.',
'online_payments_minimum_note' => 'Note: Online payments are supported only if amount is larger than $1 or currency equivalent.',
'payment_token_not_found' => 'ບໍ່ພົບໂທເຄັນການຈ່າຍເງິນ, ກະລຸນາລອງໃໝ່ອີກຄັ້ງ. ຖ້າບັນຫາຍັງຄົງຢູ່, ໃຫ້ລອງໃຊ້ວິທີຈ່າຍເງິນອື່ນ',
'vendor_address1' => 'ຖະໜົນຜູ້ຂາຍ',
'vendor_address2' => 'ຜູ້ຂາຍ Apt/Suite',
@ -5297,6 +5297,9 @@ $lang = array(
'rappen_rounding_help' => 'ຈໍານວນຮອບເປັນ 5 ເຊັນ',
'assign_group' => 'ກຳນົດກຸ່ມ',
'paypal_advanced_cards' => 'Advanced Card Payments',
'local_domain_help' => 'EHLO domain (optional)',
'port_help' => 'ie. 25,587,465',
'host_help' => 'ie. smtp.gmail.com',
);
return $lang;

View File

@ -4026,7 +4026,7 @@ Kom terug naar deze betalingsmethode pagina zodra u de bedragen heeft ontvangen
'user_detached' => 'Gebruiker losgekoppeld van bedrijf',
'create_webhook_failure' => 'Maken van webhook is mislukt',
'payment_message_extended' => 'Bedankt voor uw betaling van :amount voor :invoice',
'online_payments_minimum_note' => 'Opmerking: Online betalingen worden alleen ondersteund als het bedrag groter is dan € 1 of het equivalent in een andere valuta.',
'online_payments_minimum_note' => 'Note: Online payments are supported only if amount is larger than $1 or currency equivalent.',
'payment_token_not_found' => 'Betalingstoken niet gevonden. Probeer het opnieuw. Als het probleem zich blijft voordoen, probeer het dan met een andere betaalmethode',
'vendor_address1' => 'Leverancier straatnaam',
'vendor_address2' => 'Leverancier Apt / Suite',
@ -5297,6 +5297,9 @@ Email: :email<b><br><b>',
'rappen_rounding_help' => 'Rond het bedrag af op 5 cent',
'assign_group' => 'Groep toewijzen',
'paypal_advanced_cards' => 'Advanced Card Payments',
'local_domain_help' => 'EHLO domain (optional)',
'port_help' => 'ie. 25,587,465',
'host_help' => 'ie. smtp.gmail.com',
);
return $lang;

View File

@ -4026,7 +4026,7 @@ Quando tiver as quantias, volte a esta página de formas de pagamento e clique "
'user_detached' => 'Usuário desconectado da empresa',
'create_webhook_failure' => 'Falha ao criar Webhook',
'payment_message_extended' => 'Obrigado pelo seu pagamento de :amount por :invoice',
'online_payments_minimum_note' => 'Nota: Os pagamentos on-line são suportados apenas se o valor for superior a US$ 1 ou o equivalente em moeda.',
'online_payments_minimum_note' => 'Note: Online payments are supported only if amount is larger than $1 or currency equivalent.',
'payment_token_not_found' => 'Token de pagamento não encontrado. Tente novamente. Se o problema persistir, tente outra forma de pagamento',
'vendor_address1' => 'Rua do Vendedor',
'vendor_address2' => 'Apartamento/Suíte do Vendedor',
@ -5294,6 +5294,9 @@ Quando tiver as quantias, volte a esta página de formas de pagamento e clique "
'rappen_rounding_help' => 'Montante redondo para 5 centavos',
'assign_group' => 'Atribuir grupo',
'paypal_advanced_cards' => 'Advanced Card Payments',
'local_domain_help' => 'EHLO domain (optional)',
'port_help' => 'ie. 25,587,465',
'host_help' => 'ie. smtp.gmail.com',
);
return $lang;

View File

@ -4028,7 +4028,7 @@ debitar da sua conta de acordo com essas instruções. Está elegível a um reem
'user_detached' => 'Utilizador não vinculado à empresa',
'create_webhook_failure' => 'Falha ao criar Webhook',
'payment_message_extended' => 'Obrigado pelo pagamento de :amount para :invoice',
'online_payments_minimum_note' => 'Nota: Pagamentos online estão disponíveis se a quantia for superior a 1$ ou ao equivalente na moeda utilizada.',
'online_payments_minimum_note' => 'Note: Online payments are supported only if amount is larger than $1 or currency equivalent.',
'payment_token_not_found' => 'Token de pagamento não encontrada, por favor tente novamente. Se este erro persistir, tente outro método de pagamento',
'vendor_address1' => 'Morada Fornecedor',
'vendor_address2' => 'Andar / Fração Fornecedor',
@ -5297,6 +5297,9 @@ O envio de E-mails foi suspenso. Será retomado às 23:00 UTC.',
'rappen_rounding_help' => 'Montante redondo para 5 centavos',
'assign_group' => 'Atribuir grupo',
'paypal_advanced_cards' => 'Advanced Card Payments',
'local_domain_help' => 'EHLO domain (optional)',
'port_help' => 'ie. 25,587,465',
'host_help' => 'ie. smtp.gmail.com',
);
return $lang;

View File

@ -4030,7 +4030,7 @@ Odată ce sumele au ajuns la dumneavoastră, reveniți la pagina cu metode de pl
'user_detached' => 'Utilizatorul s-a detașat de companie',
'create_webhook_failure' => 'Webhook nu a putut fi creat',
'payment_message_extended' => 'Mulțumim că ați efectuat plata în valoare de :amount pentru :invoice',
'online_payments_minimum_note' => 'Notă: Plățile online pot fi efectuate doar dacă suma depășește $1 sau echivalentul în valută.',
'online_payments_minimum_note' => 'Note: Online payments are supported only if amount is larger than $1 or currency equivalent.',
'payment_token_not_found' => 'Token-ul de plată nu a putut fi găsit. Încercați din nou. Dacă nu funcționează, încercați o altă metodă de plată',
'vendor_address1' => 'Stradă furnizor',
'vendor_address2' => 'Apartament furnizor',
@ -5298,6 +5298,9 @@ Odată ce sumele au ajuns la dumneavoastră, reveniți la pagina cu metode de pl
'rappen_rounding_help' => 'Suma rotundă la 5 cenți',
'assign_group' => 'Atribuiți grup',
'paypal_advanced_cards' => 'Advanced Card Payments',
'local_domain_help' => 'EHLO domain (optional)',
'port_help' => 'ie. 25,587,465',
'host_help' => 'ie. smtp.gmail.com',
);
return $lang;

View File

@ -2847,7 +2847,7 @@ $lang = array(
'reminder1' => 'Prvá Upomienka',
'reminder2' => 'Druhá Upomienka',
'reminder3' => 'Tretia Upomienka',
'send' => 'Odosla',
'send' => 'Odoslať',
'auto_billing' => 'Automatické účtovanie',
'button' => 'Tlačidlo',
'more' => 'VIac',
@ -4016,7 +4016,7 @@ $lang = array(
'user_detached' => 'Používateľ je oddelený od spoločnosti',
'create_webhook_failure' => 'Nepodarilo sa vytvoriť webhook',
'payment_message_extended' => 'Ďakujeme za platbu vo výške :amount za :invoice',
'online_payments_minimum_note' => 'Poznámka: Online platby sú podporované iba vtedy, ak je suma vyššia ako 1 $ alebo ekvivalent v inej mene.',
'online_payments_minimum_note' => 'Note: Online payments are supported only if amount is larger than $1 or currency equivalent.',
'payment_token_not_found' => 'Platobný token sa nenašiel, skúste to znova. Ak problém stále pretrváva, skúste použiť iný spôsob platby ',
'vendor_address1' => 'Ulica predajcu',
'vendor_address2' => 'Apartmán/byt dodávateľa',
@ -4223,7 +4223,7 @@ $lang = array(
'payment_type_Bancontact' => 'Zákaz kontaktu',
'payment_type_BECS' => 'BECS',
'payment_type_ACSS' => 'ACSS',
'gross_line_total' => 'Hrubý riadok celkom',
'gross_line_total' => 'Celková suma brutto',
'lang_Slovak' => 'Slovensky',
'normal' => 'Normálne',
'large' => 'Veľké',
@ -5284,6 +5284,9 @@ $lang = array(
'rappen_rounding_help' => 'Okrúhla suma do 5 centov',
'assign_group' => 'Priradiť skupinu',
'paypal_advanced_cards' => 'Advanced Card Payments',
'local_domain_help' => 'EHLO domain (optional)',
'port_help' => 'ie. 25,587,465',
'host_help' => 'ie. smtp.gmail.com',
);
return $lang;

View File

@ -4029,7 +4029,7 @@ Kada budete imali iznose, vratite se na ovu stranicu sa načinima plaćanja i k
'user_detached' => 'Korisnik je odvojen od kompanije',
'create_webhook_failure' => 'Neuspešno kreiranje Webhook-a',
'payment_message_extended' => 'Hvala vam na vašoj uplati od :amount za :invoice',
'online_payments_minimum_note' => 'Napomena: onlajn plaćanja su podržana samo ako je iznos veći od 1 USD ili ekvivalentne valute.',
'online_payments_minimum_note' => 'Note: Online payments are supported only if amount is larger than $1 or currency equivalent.',
'payment_token_not_found' => 'Token za plaćanje nije pronađen, pokušajte ponovo. Ako problem i dalje postoji, pokušajte sa drugim načinom plaćanja ',
'vendor_address1' => 'Ulica dobavljača',
'vendor_address2' => 'Stan/apartman dobavljača',
@ -5297,6 +5297,9 @@ Kada budete imali iznose, vratite se na ovu stranicu sa načinima plaćanja i k
'rappen_rounding_help' => 'Round amount to 5 cents',
'assign_group' => 'Assign group',
'paypal_advanced_cards' => 'Advanced Card Payments',
'local_domain_help' => 'EHLO domain (optional)',
'port_help' => 'ie. 25,587,465',
'host_help' => 'ie. smtp.gmail.com',
);
return $lang;

View File

@ -4037,7 +4037,7 @@ Den här funktionen kräver att en produkt skapas och en betalningsgateway är k
'user_detached' => 'Användaren har kopplats bort från företaget',
'create_webhook_failure' => 'Det gick inte att skapa Webhook',
'payment_message_extended' => 'Tack för din betalning på :amount för :invoice',
'online_payments_minimum_note' => 'Notera: Online betalningar stöds endast om beloppet är större än 10:- eller motsvarande valuta.',
'online_payments_minimum_note' => 'Note: Online payments are supported only if amount is larger than $1 or currency equivalent.',
'payment_token_not_found' => 'Betalningstoken hittades inte. Försök igen. Om problemet fortfarande kvarstår kan du prova med en annan betalningsmetod',
'vendor_address1' => 'Leverantörsadress',
'vendor_address2' => 'Leverantörsadress 2',
@ -5305,6 +5305,9 @@ Den här funktionen kräver att en produkt skapas och en betalningsgateway är k
'rappen_rounding_help' => 'Round amount to 5 cents',
'assign_group' => 'Assign group',
'paypal_advanced_cards' => 'Advanced Card Payments',
'local_domain_help' => 'EHLO domain (optional)',
'port_help' => 'ie. 25,587,465',
'host_help' => 'ie. smtp.gmail.com',
);
return $lang;

View File

@ -4029,7 +4029,7 @@ $lang = array(
'user_detached' => '用戶脫離公司',
'create_webhook_failure' => '建立 Webhook 失敗',
'payment_message_extended' => '感謝您為:amount支付:invoice',
'online_payments_minimum_note' => '注意:僅當金額大於 1 美元或等值貨幣時才支援線上付款。',
'online_payments_minimum_note' => 'Note: Online payments are supported only if amount is larger than $1 or currency equivalent.',
'payment_token_not_found' => '未找到支付令牌,請重試。如果問題仍然存在,請嘗試使用其他付款方式',
'vendor_address1' => '供應商街',
'vendor_address2' => '供應商 公寓/套房',
@ -5297,6 +5297,9 @@ $lang = array(
'rappen_rounding_help' => '金額為 5 美分',
'assign_group' => '分配群組',
'paypal_advanced_cards' => 'Advanced Card Payments',
'local_domain_help' => 'EHLO domain (optional)',
'port_help' => 'ie. 25,587,465',
'host_help' => 'ie. smtp.gmail.com',
);
return $lang;

File diff suppressed because one or more lines are too long

109
public/build/assets/app-bfac6a32.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -9,7 +9,7 @@
]
},
"resources/js/app.js": {
"file": "assets/app-042e859e.js",
"file": "assets/app-bfac6a32.js",
"imports": [
"_index-08e160a7.js",
"__commonjsHelpers-725317a4.js"

View File

@ -96,7 +96,7 @@ class TaskImportTest extends TestCase
$time_log = json_decode($task->time_log);
foreach($time_log as $log) {
$this->assertFalse($log[3]);
$this->assertTrue($log[3]);
}
$task = Task::where('company_id', $this->company->id)->where('number', 'x1233')->first();
@ -106,7 +106,7 @@ class TaskImportTest extends TestCase
$time_log = json_decode($task->time_log);
foreach($time_log as $log) {
$this->assertFalse($log[3]);
$this->assertTrue($log[3]);
}

View File

@ -0,0 +1,199 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace Tests\Feature;
use Tests\TestCase;
use App\Models\Quote;
use App\Models\Credit;
use App\Models\Invoice;
use Tests\MockAccountData;
use App\Models\PurchaseOrder;
use App\DataMapper\InvoiceItem;
use App\Models\RecurringInvoice;
use Illuminate\Validation\ValidationException;
use Illuminate\Routing\Middleware\ThrottleRequests;
use Illuminate\Foundation\Testing\DatabaseTransactions;
/**
* @test
* @covers App\Http\Controllers\ActivityController
*/
class MaxAmountTest extends TestCase
{
use DatabaseTransactions;
use MockAccountData;
protected function setUp() :void
{
parent::setUp();
$this->makeTestData();
$this->withoutMiddleware(
ThrottleRequests::class
);
}
public function testInvoiceMaxAmount()
{
$item = new InvoiceItem();
$item->cost = 10000000000000000;
$item->quantity = 100;
$data = [
'user_id' => $this->user->id,
'client_id' => $this->client->id,
'company_id' => $this->company->id,
'line_items' => [$item]
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->postJson('/api/v1/invoices', $data);
$response->assertStatus(422);
$i = Invoice::factory()->create($data);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->putJson("/api/v1/invoices/{$i->hashed_id}", $data);
$response->assertStatus(422);
}
public function testCreditMaxAmount()
{
$item = new InvoiceItem();
$item->cost = 10000000000000000;
$item->quantity = 100;
$data = [
'user_id' => $this->user->id,
'client_id' => $this->client->id,
'company_id' => $this->company->id,
'line_items' => [$item]
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->postJson('/api/v1/credits', $data);
$response->assertStatus(422);
$i = Credit::factory()->create($data);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->putJson("/api/v1/credits/{$i->hashed_id}", $data);
$response->assertStatus(422);
}
public function testQuoteMaxAmount()
{
$item = new InvoiceItem();
$item->cost = 10000000000000000;
$item->quantity = 100;
$data = [
'user_id' => $this->user->id,
'client_id' => $this->client->id,
'company_id' => $this->company->id,
'line_items' => [$item]
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->postJson('/api/v1/quotes', $data);
$response->assertStatus(422);
$i = Quote::factory()->create($data);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->putJson("/api/v1/quotes/{$i->hashed_id}", $data);
$response->assertStatus(422);
}
public function testPurchaseOrderMaxAmount()
{
$item = new InvoiceItem();
$item->cost = 10000000000000000;
$item->quantity = 100;
$data = [
'user_id' => $this->user->id,
'vendor_id' => $this->vendor->id,
'company_id' => $this->company->id,
'line_items' => [$item]
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->postJson('/api/v1/purchase_orders', $data);
$response->assertStatus(422);
$i = PurchaseOrder::factory()->create($data);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->putJson("/api/v1/purchase_orders/{$i->hashed_id}", $data);
$response->assertStatus(422);
}
public function testRecurringInvoiceMaxAmount()
{
$item = new InvoiceItem();
$item->cost = 10000000000000000;
$item->quantity = 100;
$data = [
'user_id' => $this->user->id,
'client_id' => $this->client->id,
'company_id' => $this->company->id,
'line_items' => [$item],
'frequency_id' => 5
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->postJson('/api/v1/recurring_invoices', $data);
$response->assertStatus(422);
$i = RecurringInvoice::factory()->create($data);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->putJson("/api/v1/recurring_invoices/{$i->hashed_id}", $data);
$response->assertStatus(422);
}
}

View File

@ -104,6 +104,33 @@ class TaskApiTest extends TestCase
}
}
public function testTimeLogWithSameStartAndStopTimes()
{
$settings = ClientSettings::defaults();
$settings->default_task_rate = 41;
$c = Client::factory()->create([
'user_id' => $this->user->id,
'company_id' => $this->company->id,
'settings' => $settings,
]);
$data = [
'client_id' => $c->hashed_id,
'description' => 'Test Task',
'time_log' => '[[1681165417,1681165432,"sumtin",true],[1681165446,1681165446]]',
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->postJson("/api/v1/tasks", $data);
$response->assertStatus(200);
$arr = $response->json();
}
public function testRoundingViaApi()
{

View File

@ -0,0 +1,73 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace Tests\Unit;
use Tests\TestCase;
/**
* @test
*/
class TaskRoundingTest extends TestCase
{
public int $task_round_to_nearest = 1;
public bool $task_round_up = true;
protected function setUp(): void
{
parent::setUp();
}
public function testRoundUp()
{
$start_time = 1714942800;
$end_time = 1714943220; //7:07am
$this->task_round_to_nearest = 600;
//calculated time = 7:10am
$rounded = 1714943400;
$this->assertEquals($rounded, $this->roundTimeLog($start_time, $end_time));
}
public function testRoundDown()
{
$start_time = 1714942800;
$end_time = 1714943220; //7:07am
$this->task_round_to_nearest = 600;
$this->task_round_up = false;
//calculated time = 7:10am
$rounded = $start_time;
$this->assertEquals($rounded, $this->roundTimeLog($start_time, $end_time));
}
public function roundTimeLog(int $start_time, int $end_time): int
{
if($this->task_round_to_nearest == 1)
return $end_time;
$interval = $end_time - $start_time;
if($this->task_round_up)
return $start_time + (int)ceil($interval/$this->task_round_to_nearest)*$this->task_round_to_nearest;
return $start_time - (int)floor($interval/$this->task_round_to_nearest) * $this->task_round_to_nearest;
}
}