1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-10 21:22:58 +01:00

Merge pull request #7428 from turbo124/v5-develop

Fixes for Client CSV Export
This commit is contained in:
David Bomba 2022-05-14 08:01:01 +10:00 committed by GitHub
commit 8d9683f633
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 2369 additions and 1068 deletions

View File

@ -102,17 +102,9 @@ class CheckData extends Command
config(['database.default' => $database]);
}
$this->checkInvoiceBalances();
$this->checkInvoiceBalancesNew();
//$this->checkInvoicePayments();
//$this->checkPaidToDates();
$this->checkInvoiceBalances();
$this->checkPaidToDatesNew();
// $this->checkPaidToCompanyDates();
$this->checkClientBalances();
$this->checkContacts();
$this->checkVendorContacts();
$this->checkEntityInvitations();
@ -123,7 +115,6 @@ class CheckData extends Command
if (! $this->option('client_id')) {
$this->checkOAuth();
//$this->checkFailedJobs();
}
$this->logMessage('Done: '.strtoupper($this->isValid ? Account::RESULT_SUCCESS : Account::RESULT_FAILURE));
@ -359,7 +350,6 @@ class CheckData extends Command
}
}
private function checkEntityInvitations()
{
@ -420,35 +410,6 @@ class CheckData extends Command
}
// private function checkPaidToCompanyDates()
// {
// Company::cursor()->each(function ($company){
// $payments = Payment::where('is_deleted', 0)
// ->where('company_id', $company->id)
// ->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED])
// ->pluck('id');
// $unapplied = Payment::where('is_deleted', 0)
// ->where('company_id', $company->id)
// ->whereIn('status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])
// ->sum(\DB::Raw('amount - applied'));
// $paymentables = Paymentable::whereIn('payment_id', $payments)->sum(\DB::Raw('amount - refunded'));
// $client_paid_to_date = Client::where('company_id', $company->id)->where('is_deleted', 0)->withTrashed()->sum('paid_to_date');
// $total_payments = $paymentables + $unapplied;
// if (round($total_payments, 2) != round($client_paid_to_date, 2)) {
// $this->wrong_paid_to_dates++;
// $this->logMessage($company->present()->name.' id = # '.$company->id." - Paid to date does not match Client Paid To Date = {$client_paid_to_date} - Invoice Payments = {$total_payments}");
// }
// });
// }
private function clientPaidToDateQuery()
{
$results = \DB::select( \DB::raw("
@ -528,14 +489,11 @@ class CheckData extends Command
}
private function checkPaidToDates()
{
$this->wrong_paid_to_dates = 0;
$credit_total_applied = 0;
$clients = DB::table('clients')
->leftJoin('payments', function($join) {
$join->on('payments.client_id', '=', 'clients.id')
@ -605,29 +563,6 @@ class CheckData extends Command
$this->logMessage("{$this->wrong_paid_to_dates} clients with incorrect paid to dates");
}
/*
SELECT
SUM(payments.applied) as payments_applied,
SUM(invoices.amount - invoices.balance) as invoices_paid_amount,
SUM(credits.amount - credits.balance) as credits_balance,
SUM(invoices.balance) as invoices_balance,
clients.id
FROM payments
JOIN clients
ON clients.id = payments.client_id
JOIN credits
ON credits.client_id = clients.id
JOIN invoices
ON invoices.client_id = payments.client_id
WHERE payments.is_deleted = 0
AND payments.status_id IN (1,4,5,6)
AND invoices.is_deleted = 0
AND invoices.status_id != 1
GROUP BY clients.id
HAVING (payments_applied - credits_balance - invoices_balance) != invoices_paid_amount
ORDER BY clients.id;
*/
private function checkInvoicePayments()
{
$this->wrong_balances = 0;
@ -660,33 +595,6 @@ ORDER BY clients.id;
$this->logMessage("{$this->wrong_balances} clients with incorrect invoice balances");
}
// $clients = DB::table('clients')
// ->leftJoin('invoices', function($join){
// $join->on('invoices.client_id', '=', 'clients.id')
// ->where('invoices.is_deleted',0)
// ->where('invoices.status_id', '>', 1);
// })
// ->leftJoin('credits', function($join){
// $join->on('credits.client_id', '=', 'clients.id')
// ->where('credits.is_deleted',0)
// ->where('credits.status_id', '>', 1);
// })
// ->leftJoin('payments', function($join) {
// $join->on('payments.client_id', '=', 'clients.id')
// ->where('payments.is_deleted', 0)
// ->whereIn('payments.status_id', [Payment::STATUS_COMPLETED, Payment:: STATUS_PENDING, Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED]);
// })
// ->where('clients.is_deleted',0)
// //->where('clients.updated_at', '>', now()->subDays(2))
// ->groupBy('clients.id')
// ->havingRaw('sum(coalesce(invoices.amount - invoices.balance - credits.amount)) != sum(coalesce(payments.amount - payments.refunded, 0))')
// ->get(['clients.id', DB::raw('sum(coalesce(invoices.amount - invoices.balance - credits.amount)) as invoice_amount'), DB::raw('sum(coalesce(payments.amount - payments.refunded, 0)) as payment_amount')]);
private function clientBalanceQuery()
{
$results = \DB::select( \DB::raw("
@ -708,9 +616,6 @@ ORDER BY clients.id;
return $results;
}
private function checkClientBalances()
{
$this->wrong_balances = 0;
@ -722,66 +627,30 @@ ORDER BY clients.id;
{
$client = (array)$client;
$invoice_balance = $client['invoice_balance'];
// $ledger = CompanyLedger::where('client_id', $client['client_id'])->orderBy('id', 'DESC')->first();
if ((string) $invoice_balance != (string) $client['client_balance']) {
if ((string) $client['invoice_balance'] != (string) $client['client_balance']) {
$this->wrong_paid_to_dates++;
$client_object = Client::withTrashed()->find($client['client_id']);
$this->logMessage($client_object->present()->name.' - '.$client_object->id." - calculated client balances do not match Invoice Balances = {$invoice_balance} - Client Balance = ".rtrim($client['client_balance'], '0'));
$this->logMessage($client_object->present()->name.' - '.$client_object->id." - calculated client balances do not match Invoice Balances = ". $client['invoice_balance'] ." - Client Balance = ".rtrim($client['client_balance'], '0'));
if($this->option('ledger_balance')){
if($this->option('client_balance')){
$this->logMessage("# {$client_object->id} " . $client_object->present()->name.' - '.$client_object->number." Fixing {$client_object->balance} to {$invoice_balance}");
$client_object->balance = $invoice_balance;
$this->logMessage("# {$client_object->id} " . $client_object->present()->name.' - '.$client_object->number." Fixing {$client_object->balance} to " . $client['invoice_balance']);
$client_object->balance = $client['invoice_balance'];
$client_object->save();
// $ledger->adjustment = $invoice_balance;
// $ledger->balance = $invoice_balance;
// $ledger->notes = 'Ledger Adjustment';
// $ledger->save();
}
$this->isValid = false;
}
}
// foreach (Client::cursor()->where('is_deleted', 0)->where('clients.updated_at', '>', now()->subDays(2)) as $client) {
// $invoice_balance = Invoice::where('client_id', $client->id)->where('is_deleted', false)->where('status_id', '>', 1)->withTrashed()->sum('balance');
// $credit_balance = Credit::where('client_id', $client->id)->where('is_deleted', false)->withTrashed()->sum('balance');
// if($client->balance != $invoice_balance)
// $invoice_balance -= $credit_balance;
// $ledger = CompanyLedger::where('client_id', $client->id)->orderBy('id', 'DESC')->first();
// if ($ledger && (string) $invoice_balance != (string) $client->balance) {
// $this->wrong_paid_to_dates++;
// $this->logMessage($client->present()->name.' - '.$client->id." - calculated client balances do not match Invoice Balances = {$invoice_balance} - Client Balance = ".rtrim($client->balance, '0'). " Ledger balance = {$ledger->balance}");
// $this->isValid = false;
// }
// }
$this->logMessage("{$this->wrong_paid_to_dates} clients with incorrect client balances");
}
//fix for client balances =
//$adjustment = ($invoice_balance-$client->balance)
//$client->balance += $adjustment;
//$ledger_adjustment = $ledger->balance - $client->balance;
//$ledger->balance += $ledger_adjustment
private function invoiceBalanceQuery()
{
$results = \DB::select( \DB::raw("
@ -803,7 +672,7 @@ ORDER BY clients.id;
return $results;
}
private function checkInvoiceBalancesNew()
private function checkInvoiceBalances()
{
$this->wrong_balances = 0;
$this->wrong_paid_to_dates = 0;
@ -818,25 +687,30 @@ ORDER BY clients.id;
$ledger = CompanyLedger::where('client_id', $client->id)->orderBy('id', 'DESC')->first();
if ($ledger && number_format($invoice_balance, 4) != number_format($client->balance, 4)) {
if (number_format($invoice_balance, 4) != number_format($client->balance, 4)) {
$this->wrong_balances++;
$this->logMessage("# {$client->id} " . $client->present()->name.' - '.$client->number." - Balance Failure - Invoice Balances = {$invoice_balance} Client Balance = {$client->balance} Ledger Balance = {$ledger->balance}");
$ledger_balance = $ledger ? $ledger->balance : 0;
$this->logMessage("# {$client->id} " . $client->present()->name.' - '.$client->number." - Balance Failure - Invoice Balances = {$invoice_balance} Client Balance = {$client->balance} Ledger Balance = {$ledger_balance}");
$this->isValid = false;
if($this->option('client_balance')){
$this->logMessage("# {$client->id} " . $client->present()->name.' - '.$client->number." Fixing {$client->balance} to {$invoice_balance}");
$client->balance = $invoice_balance;
$client->save();
}
if($ledger && (number_format($invoice_balance, 4) != number_format($ledger->balance, 4)))
{
$ledger->adjustment = $invoice_balance;
$ledger->balance = $invoice_balance;
$ledger->notes = 'Ledger Adjustment';
$ledger->save();
}
}
}
@ -844,7 +718,7 @@ ORDER BY clients.id;
}
private function checkInvoiceBalances()
private function checkLedgerBalances()
{
$this->wrong_balances = 0;
$this->wrong_paid_to_dates = 0;
@ -880,26 +754,6 @@ ORDER BY clients.id;
private function checkLogoFiles()
{
// $accounts = DB::table('accounts')
// ->where('logo', '!=', '')
// ->orderBy('id')
// ->get(['logo']);
// $countMissing = 0;
// foreach ($accounts as $account) {
// $path = public_path('logo/' . $account->logo);
// if (! file_exists($path)) {
// $this->logMessage('Missing file: ' . $account->logo);
// $countMissing++;
// }
// }
// if ($countMissing > 0) {
// $this->isValid = false;
// }
// $this->logMessage($countMissing . ' missing logo files');
}
/**
@ -985,60 +839,21 @@ ORDER BY clients.id;
{
Account::where('plan_expires', '<=', now()->subDays(2))->cursor()->each(function ($account){
$client = Client::on('db-ninja-01')->where('company_id', config('ninja.ninja_default_company_id'))->where('custom_value2', $account->key)->first();
if($client){
$payment = Payment::on('db-ninja-01')
->where('company_id', config('ninja.ninja_default_company_id'))
->where('client_id', $client->id)
->where('date', '>=', now()->subDays(2))
->exists();
if($payment)
$this->logMessage("I found a payment for {$account->key}");
$client = Client::on('db-ninja-01')->where('company_id', config('ninja.ninja_default_company_id'))->where('custom_value2', $account->key)->first();
if($client){
$payment = Payment::on('db-ninja-01')
->where('company_id', config('ninja.ninja_default_company_id'))
->where('client_id', $client->id)
->where('date', '>=', now()->subDays(2))
->exists();
if($payment)
$this->logMessage("I found a payment for {$account->key}");
}
}
});
}
}
/* //used to set a company owner on the company_users table
$c = Company::whereDoesntHave('company_users', function ($query){
$query->where('is_owner', true)->withTrashed();
})->cursor()->each(function ($company){
if(!$company->company_users()->exists()){
echo "No company users AT ALL {$company->id}\n";
}
else{
$cu = $company->company_users()->orderBy('id', 'ASC')->orderBy('is_admin', 'ASC')->first();
echo "{$company->id} - {$cu->id} \n";
$cu->is_owner=true;
$cu->save();
}
});
*/
/* query if we want to company company ledger to client balance
$results = \DB::select( \DB::raw("
SELECT
clients.id as client_id,
clients.balance as client_balance
from clients,
(select max(company_ledgers.id) as cid, company_ledgers.client_id as client_id, company_ledgers.balance as balance
FROM company_ledgers) ledger
where clients.id=ledger.client_id
AND clients.balance != ledger.balance
GROUP BY clients.id
ORDER BY clients.id;
") );
*/
}

View File

@ -131,6 +131,7 @@ class DemoMode extends Command
'enabled_modules' => 32767,
'company_key' => 'KEY',
'enable_shop_api' => true,
'markdown_email_enabled' => false,
]);
$settings = $company->settings;

View File

@ -70,6 +70,7 @@ class BaseExport
$header = [];
foreach($this->input['report_keys'] as $value){
$key = array_search ($value, $this->entity_keys);
$key = str_replace("item.", "", $key);
@ -77,7 +78,6 @@ class BaseExport
$key = str_replace("client.", "", $key);
$key = str_replace("contact.", "", $key);
$header[] = ctrans("texts.{$key}");
}

View File

@ -54,7 +54,7 @@ class ClientExport extends BaseExport
'name' => 'client.name',
'number' => 'client.number',
'paid_to_date' => 'client.paid_to_date',
'phone' => 'client.phone',
'client_phone' => 'client.phone',
'postal_code' => 'client.postal_code',
'private_notes' => 'client.private_notes',
'public_notes' => 'client.public_notes',
@ -70,7 +70,7 @@ class ClientExport extends BaseExport
'currency' => 'client.currency',
'first_name' => 'contact.first_name',
'last_name' => 'contact.last_name',
'phone' => 'contact.phone',
'contact_phone' => 'contact.phone',
'contact_custom_value1' => 'contact.custom_value1',
'contact_custom_value2' => 'contact.custom_value2',
'contact_custom_value3' => 'contact.custom_value3',
@ -78,46 +78,6 @@ class ClientExport extends BaseExport
'email' => 'contact.email',
];
protected array $all_keys = [
'client.address1',
'client.address2',
'client.balance',
'client.city',
'client.country_id',
'client.credit_balance',
'client.custom_value1',
'client.custom_value2',
'client.custom_value3',
'client.custom_value4',
'client.id_number',
'client.industry_id',
'client.last_login',
'client.name',
'client.number',
'client.paid_to_date',
'client.phone',
'client.postal_code',
'client.private_notes',
'client.public_notes',
'client.shipping_address1',
'client.shipping_address2',
'client.shipping_city',
'client.shipping_country_id',
'client.shipping_postal_code',
'client.shipping_state',
'client.state',
'client.vat_number',
'client.website',
'client.currency',
'contact.first_name',
'contact.last_name',
'contact.phone',
'contact.custom_value1',
'contact.custom_value2',
'contact.custom_value3',
'contact.custom_value4',
'contact.email',
];
private array $decorate_keys = [
'client.country_id',
'client.shipping_country_id',
@ -146,7 +106,7 @@ class ClientExport extends BaseExport
$this->csv = Writer::createFromString();
if(count($this->input['report_keys']) == 0)
$this->input['report_keys'] = $this->all_keys;
$this->input['report_keys'] = array_values($this->entity_keys);
//insert the header
$this->csv->insertOne($this->buildHeader());
@ -178,20 +138,22 @@ class ClientExport extends BaseExport
if($contact = $client->contacts()->first())
$transformed_contact = $this->contact_transformer->transform($contact);
$entity = [];
foreach(array_values($this->input['report_keys']) as $key){
$parts = explode(".",$key);
$entity[$parts[1]] = "";
$keyval = array_search($key, $this->entity_keys);
if($parts[0] == 'client' && array_key_exists($parts[1], $transformed_client)) {
$entity[$parts[1]] = $transformed_client[$parts[1]];
$entity[$keyval] = $transformed_client[$parts[1]];
}
elseif($parts[0] == 'contact' && array_key_exists($parts[1], $transformed_client)) {
$entity[$parts[1]] = $transformed_contact[$parts[1]];
elseif($parts[0] == 'contact' && array_key_exists($parts[1], $transformed_contact)) {
$entity[$keyval] = $transformed_contact[$parts[1]];
}
else
$entity[$keyval] = "";
}
@ -202,16 +164,16 @@ class ClientExport extends BaseExport
private function decorateAdvancedFields(Client $client, array $entity) :array
{
if(in_array('country_id', $this->input['report_keys']))
$entity['country_id'] = $client->country ? ctrans("texts.country_{$client->country->name}") : "";
if(in_array('client.country_id', $this->input['report_keys']))
$entity['country'] = $client->country ? ctrans("texts.country_{$client->country->name}") : "";
if(in_array('shipping_country_id', $this->input['report_keys']))
$entity['shipping_country_id'] = $client->shipping_country ? ctrans("texts.country_{$client->shipping_country->name}") : "";
if(in_array('client.shipping_country_id', $this->input['report_keys']))
$entity['shipping_country'] = $client->shipping_country ? ctrans("texts.country_{$client->shipping_country->name}") : "";
if(in_array('currency', $this->input['report_keys']))
$entity['currency_id'] = $client->currency() ? $client->currency()->code : $client->company->currency()->code;
if(in_array('client.currency', $this->input['report_keys']))
$entity['currency'] = $client->currency() ? $client->currency()->code : $client->company->currency()->code;
if(in_array('industry_id', $this->input['report_keys']))
if(in_array('client.industry_id', $this->input['report_keys']))
$entity['industry_id'] = $client->industry ? ctrans("texts.industry_{$client->industry->name}") : "";
return $entity;

View File

@ -50,7 +50,7 @@ class ContactExport extends BaseExport
'name' => 'client.name',
'number' => 'client.number',
'paid_to_date' => 'client.paid_to_date',
'phone' => 'client.phone',
'client_phone' => 'client.phone',
'postal_code' => 'client.postal_code',
'private_notes' => 'client.private_notes',
'public_notes' => 'client.public_notes',
@ -66,7 +66,7 @@ class ContactExport extends BaseExport
'currency' => 'client.currency',
'first_name' => 'contact.first_name',
'last_name' => 'contact.last_name',
'phone' => 'contact.phone',
'contact_phone' => 'contact.phone',
'contact_custom_value1' => 'contact.custom_value1',
'contact_custom_value2' => 'contact.custom_value2',
'contact_custom_value3' => 'contact.custom_value3',
@ -74,49 +74,6 @@ class ContactExport extends BaseExport
'email' => 'contact.email',
];
protected array $all_keys = [
'client.address1',
'client.address2',
'client.balance',
'client.city',
'client.country_id',
'client.credit_balance',
'client.custom_value1',
'client.custom_value2',
'client.custom_value3',
'client.custom_value4',
'client.id_number',
'client.industry_id',
'client.last_login',
'client.name',
'client.number',
'client.paid_to_date',
'client.phone',
'client.postal_code',
'client.private_notes',
'client.public_notes',
'client.shipping_address1',
'client.shipping_address2',
'client.shipping_city',
'client.shipping_country_id',
'client.shipping_postal_code',
'client.shipping_state',
'client.state',
'client.vat_number',
'client.website',
'client.currency',
'contact.first_name',
'contact.last_name',
'contact.phone',
'contact.custom_value1',
'contact.custom_value2',
'contact.custom_value3',
'contact.custom_value4',
'contact.email',
];
private array $decorate_keys = [
'client.country_id',
'client.shipping_country_id',
@ -145,7 +102,7 @@ class ContactExport extends BaseExport
$this->csv = Writer::createFromString();
if(count($this->input['report_keys']) == 0)
$this->input['report_keys'] = $this->all_keys;
$this->input['report_keys'] = array_values($this->entity_keys);
//insert the header
$this->csv->insertOne($this->buildHeader());
@ -178,15 +135,16 @@ class ContactExport extends BaseExport
foreach(array_values($this->input['report_keys']) as $key){
$parts = explode(".",$key);
$entity[$parts[1]] = "";
$keyval = array_search($key, $this->entity_keys);
if($parts[0] == 'client') {
$entity[$parts[1]] = $transformed_client[$parts[1]];
if($parts[0] == 'client' && array_key_exists($parts[1], $transformed_client)) {
$entity[$keyval] = $transformed_client[$parts[1]];
}
elseif($parts[0] == 'contact') {
$entity[$parts[1]] = $transformed_contact[$parts[1]];
elseif($parts[0] == 'contact' && array_key_exists($parts[1], $transformed_contact)) {
$entity[$keyval] = $transformed_contact[$parts[1]];
}
else
$entity[$keyval] = "";
}
return $this->decorateAdvancedFields($contact->client, $entity);
@ -196,16 +154,16 @@ class ContactExport extends BaseExport
private function decorateAdvancedFields(Client $client, array $entity) :array
{
if(array_key_exists('country_id', $entity))
$entity['country_id'] = $client->country ? ctrans("texts.country_{$client->country->name}") : "";
if(in_array('client.country_id', $this->input['report_keys']))
$entity['country'] = $client->country ? ctrans("texts.country_{$client->country->name}") : "";
if(array_key_exists('shipping_country_id', $entity))
$entity['shipping_country_id'] = $client->shipping_country ? ctrans("texts.country_{$client->shipping_country->name}") : "";
if(in_array('client.shipping_country_id', $this->input['report_keys']))
$entity['shipping_country'] = $client->shipping_country ? ctrans("texts.country_{$client->shipping_country->name}") : "";
if(array_key_exists('currency', $entity))
$entity['currency'] = $client->currency()->code;
if(in_array('client.currency', $this->input['report_keys']))
$entity['currency'] = $client->currency() ? $client->currency()->code : $client->company->currency()->code;
if(array_key_exists('industry_id', $entity))
if(in_array('client.industry_id', $this->input['report_keys']))
$entity['industry_id'] = $client->industry ? ctrans("texts.industry_{$client->industry->name}") : "";
return $entity;

View File

@ -68,45 +68,6 @@ class CreditExport extends BaseExport
'currency' => 'currency'
];
protected array $all_keys = [
'amount',
'balance',
'client_id',
'custom_surcharge1',
'custom_surcharge2',
'custom_surcharge3',
'custom_surcharge4',
'country_id',
'custom_value1',
'custom_value2',
'custom_value3',
'custom_value4',
'date',
'discount',
'due_date',
'exchange_rate',
'footer',
'invoice_id',
'number',
'paid_to_date',
'partial',
'partial_due_date',
'po_number',
'private_notes',
'public_notes',
'status_id',
'tax_name1',
'tax_name2',
'tax_name3',
'tax_rate1',
'tax_rate2',
'tax_rate3',
'terms',
'total_taxes',
'currency'
];
private array $decorate_keys = [
'country',
'client',
@ -133,12 +94,12 @@ class CreditExport extends BaseExport
//load the CSV document from a string
$this->csv = Writer::createFromString();
if(count($this->input['report_keys']) == 0)
$this->input['report_keys'] = array_values($this->entity_keys);
//insert the header
$this->csv->insertOne($this->buildHeader());
if(count($this->input['report_keys']) == 0)
$this->input['report_keys'] = $this->all_keys;
$query = Credit::query()
->withTrashed()
->with('client')->where('company_id', $this->company->id)
@ -166,7 +127,13 @@ class CreditExport extends BaseExport
foreach(array_values($this->input['report_keys']) as $key){
$entity[$key] = $transformed_credit[$key];
$keyval = array_search($key, $this->entity_keys);
if(array_key_exists($key, $transformed_credit))
$entity[$keyval] = $transformed_credit[$key];
else
$entity[$keyval] = '';
}
return $this->decorateAdvancedFields($credit, $entity);
@ -176,17 +143,20 @@ class CreditExport extends BaseExport
private function decorateAdvancedFields(Credit $credit, array $entity) :array
{
if(array_key_exists('country_id', $entity))
$entity['country_id'] = $credit->client->country ? ctrans("texts.country_{$credit->client->country->name}") : "";
if(in_array('country_id', $this->input['report_keys']))
$entity['country'] = $credit->client->country ? ctrans("texts.country_{$credit->client->country->name}") : "";
if(array_key_exists('currency', $entity))
$entity['currency'] = $credit->client->currency()->code;
if(in_array('currency_id', $this->input['report_keys']))
$entity['currency_id'] = $credit->client->currency() ? $credit->client->currency()->code : $invoice->company->currency()->code;;
if(array_key_exists('invoice_id', $entity))
$entity['invoice_id'] = $credit->invoice ? $credit->invoice->number : "";
if(in_array('invoice_id', $this->input['report_keys']))
$entity['invoice'] = $credit->invoice ? $credit->invoice->number : "";
if(array_key_exists('client_id', $entity))
$entity['client_id'] = $credit->client->present()->name();
if(in_array('client_id', $this->input['report_keys']))
$entity['client'] = $credit->client->present()->name();
if(in_array('status_id',$this->input['report_keys']))
$entity['status'] = $credit->stringStatus($credit->status_id);
return $entity;
}

View File

@ -33,20 +33,12 @@ class DocumentExport extends BaseExport
protected array $entity_keys = [
'record_type' => 'record_type',
'record_name' => 'record_name',
// 'record_name' => 'record_name',
'name' => 'name',
'type' => 'type',
'created_at' => 'created_at',
];
protected array $all_keys = [
'record_type',
'record_name',
'name',
'type',
'created_at',
];
private array $decorate_keys = [
];
@ -71,7 +63,7 @@ class DocumentExport extends BaseExport
$this->csv = Writer::createFromString();
if(count($this->input['report_keys']) == 0)
$this->input['report_keys'] = $this->all_keys;
$this->input['report_keys'] = array_values($this->entity_keys);
//insert the header
$this->csv->insertOne($this->buildHeader());
@ -100,8 +92,12 @@ class DocumentExport extends BaseExport
foreach(array_values($this->input['report_keys']) as $key){
$entity[$key] = $transformed_entity[$key];
$keyval = array_search($key, $this->entity_keys);
if(array_key_exists($key, $transformed_entity))
$entity[$keyval] = $transformed_entity[$key];
else
$entity[$keyval] = '';
}
return $this->decorateAdvancedFields($document, $entity);
@ -111,11 +107,11 @@ class DocumentExport extends BaseExport
private function decorateAdvancedFields(Document $document, array $entity) :array
{
if(array_key_exists('record_type', $entity))
if(in_array('record_type', $this->input['report_keys']))
$entity['record_type'] = class_basename($document->documentable);
if(array_key_exists('record_name', $entity))
$entity['record_name'] = $document->hashed_id;
// if(in_array('record_name', $this->input['report_keys']))
// $entity['record_name'] = $document->hashed_id;
return $entity;
}

View File

@ -63,40 +63,6 @@ class ExpenseExport extends BaseExport
'invoice' => 'invoice_id',
];
protected array $all_keys = [
'amount',
'category_id',
'client_id',
'custom_value1',
'custom_value2',
'custom_value3',
'custom_value4',
'currency_id',
'date',
'exchange_rate',
'foreign_amount',
'invoice_currency_id',
'payment_date',
'number',
'payment_type_id',
'private_notes',
'project_id',
'public_notes',
'tax_amount1',
'tax_amount2',
'tax_amount3',
'tax_name1',
'tax_name2',
'tax_name3',
'tax_rate1',
'tax_rate2',
'tax_rate3',
'transaction_reference',
'vendor_id',
'invoice_id',
];
private array $decorate_keys = [
'client',
'currency',
@ -127,7 +93,7 @@ class ExpenseExport extends BaseExport
$this->csv = Writer::createFromString();
if(count($this->input['report_keys']) == 0)
$this->input['report_keys'] = $this->all_keys;
$this->input['report_keys'] = array_values($this->entity_keys);
//insert the header
$this->csv->insertOne($this->buildHeader());
@ -161,7 +127,13 @@ class ExpenseExport extends BaseExport
foreach(array_values($this->input['report_keys']) as $key){
$entity[$key] = $transformed_expense[$key];
$keyval = array_search($key, $this->entity_keys);
if(array_key_exists($key, $transformed_expense))
$entity[$keyval] = $transformed_expense[$key];
else
$entity[$keyval] = '';
}
return $this->decorateAdvancedFields($expense, $entity);
@ -170,26 +142,26 @@ class ExpenseExport extends BaseExport
private function decorateAdvancedFields(Expense $expense, array $entity) :array
{
if(array_key_exists('currency_id', $entity))
$entity['currency_id'] = $expense->currency ? $expense->currency->code : "";
if(in_array('currency_id', $this->input['report_keys']))
$entity['currency'] = $expense->currency ? $expense->currency->code : "";
if(array_key_exists('client_id', $entity))
$entity['client_id'] = $expense->client ? $expense->client->present()->name() : "";
if(in_array('client_id', $this->input['report_keys']))
$entity['client'] = $expense->client ? $expense->client->present()->name() : "";
if(array_key_exists('invoice_id', $entity))
$entity['invoice_id'] = $expense->invoice ? $expense->invoice->number : "";
if(in_array('invoice_id', $this->input['report_keys']))
$entity['invoice'] = $expense->invoice ? $expense->invoice->number : "";
if(array_key_exists('category_id', $entity))
$entity['category_id'] = $expense->category ? $expense->category->name : "";
if(in_array('category_id', $this->input['report_keys']))
$entity['category'] = $expense->category ? $expense->category->name : "";
if(array_key_exists('vendor_id', $entity))
$entity['vendor_id'] = $expense->vendor ? $expense->vendor->name : "";
if(in_array('vendor_id', $this->input['report_keys']))
$entity['vendor'] = $expense->vendor ? $expense->vendor->name : "";
if(array_key_exists('payment_type_id', $entity))
$entity['payment_type_id'] = $expense->payment_type ? $expense->payment_type->name : "";
if(in_array('payment_type_id', $this->input['report_keys']))
$entity['payment_type'] = $expense->payment_type ? $expense->payment_type->name : "";
if(array_key_exists('project_id', $entity))
$entity['project_id'] = $expense->project ? $expense->project->name : "";
if(in_array('project_id', $this->input['report_keys']))
$entity['project'] = $expense->project ? $expense->project->name : "";
return $entity;

View File

@ -66,48 +66,13 @@ class InvoiceExport extends BaseExport
'currency_id' => 'currency_id'
];
protected array $all_keys = [
'amount',
'balance',
'client_id',
'custom_surcharge1',
'custom_surcharge2',
'custom_surcharge3',
'custom_surcharge4',
'custom_value1',
'custom_value2',
'custom_value3',
'custom_value4',
'date',
'discount',
'due_date',
'exchange_rate',
'footer',
'number',
'paid_to_date',
'partial',
'partial_due_date',
'po_number',
'private_notes',
'public_notes',
'status_id',
'tax_name1',
'tax_name2',
'tax_name3',
'tax_rate1',
'tax_rate2',
'tax_rate3',
'terms',
'total_taxes',
'currency_id',
];
private array $decorate_keys = [
'country',
'client',
'currency_id',
'status',
'vendor',
'project',
];
public function __construct(Company $company, array $input)
@ -130,14 +95,15 @@ class InvoiceExport extends BaseExport
$this->csv = Writer::createFromString();
if(count($this->input['report_keys']) == 0)
$this->input['report_keys'] = $this->all_keys;
$this->input['report_keys'] = array_values($this->entity_keys);
//insert the header
$this->csv->insertOne($this->buildHeader());
$query = Invoice::query()
->withTrashed()
->with('client')->where('company_id', $this->company->id)
->with('client')
->where('company_id', $this->company->id)
->where('is_deleted',0);
$query = $this->addDateRange($query);
@ -162,8 +128,12 @@ class InvoiceExport extends BaseExport
foreach(array_values($this->input['report_keys']) as $key){
if(array_key_exists($key, $transformed_invoice))
$entity[$key] = $transformed_invoice[$key];
$keyval = array_search($key, $this->entity_keys);
if(array_key_exists($key, $transformed_invoice))
$entity[$keyval] = $transformed_invoice[$key];
else
$entity[$keyval] = '';
}
return $this->decorateAdvancedFields($invoice, $entity);
@ -172,14 +142,17 @@ class InvoiceExport extends BaseExport
private function decorateAdvancedFields(Invoice $invoice, array $entity) :array
{
if(in_array('currency_id',$this->input['report_keys']))
$entity['currency_id'] = $invoice->client->currency()->code ?: $invoice->company->currency()->code;
if(in_array('country_id', $this->input['report_keys']))
$entity['country'] = $invoice->client->country ? ctrans("texts.country_{$invoice->client->country->name}") : "";
if(in_array('client_id',$this->input['report_keys']))
$entity['client_id'] = $invoice->client->present()->name();
if(in_array('currency_id', $this->input['report_keys']))
$entity['currency_id'] = $invoice->client->currency() ? $invoice->client->currency()->code : $invoice->company->currency()->code;
if(in_array('client_id', $this->input['report_keys']))
$entity['client'] = $invoice->client->present()->name();
if(in_array('status_id',$this->input['report_keys']))
$entity['status_id'] = $invoice->stringStatus($invoice->status_id);
$entity['status'] = $invoice->stringStatus($invoice->status_id);
return $entity;
}

View File

@ -64,7 +64,7 @@ class InvoiceItemExport extends BaseExport
'terms' => 'terms',
'total_taxes' => 'total_taxes',
'currency' => 'currency_id',
'qty' => 'item.quantity',
'quantity' => 'item.quantity',
'unit_cost' => 'item.cost',
'product_key' => 'item.product_key',
'cost' => 'item.product_cost',
@ -85,64 +85,9 @@ class InvoiceItemExport extends BaseExport
'invoice4' => 'item.custom_value4',
];
protected array $all_keys = [
'amount',
'balance',
'client_id',
'custom_surcharge1',
'custom_surcharge2',
'custom_surcharge3',
'custom_surcharge4',
'custom_value1',
'custom_value2',
'custom_value3',
'custom_value4',
'date',
'discount',
'due_date',
'exchange_rate',
'footer',
'number',
'paid_to_date',
'partial',
'partial_due_date',
'po_number',
'private_notes',
'public_notes',
'status_id',
'tax_name1',
'tax_name2',
'tax_name3',
'tax_rate1',
'tax_rate2',
'tax_rate3',
'terms',
'total_taxes',
// 'currency_id',
'item.quantity',
'item.cost',
'item.product_key',
'item.product_cost',
'item.notes',
'item.discount',
'item.is_amount_discount',
'item.tax_rate1',
'item.tax_rate2',
'item.tax_rate3',
'item.tax_name1',
'item.tax_name2',
'item.tax_name3',
'item.line_total',
'item.gross_line_total',
'item.custom_value1',
'item.custom_value2',
'item.custom_value3',
'item.custom_value4',
];
private array $decorate_keys = [
'client',
'currency',
'currency_id',
];
public function __construct(Company $company, array $input)
@ -165,7 +110,7 @@ class InvoiceItemExport extends BaseExport
$this->csv = Writer::createFromString();
if(count($this->input['report_keys']) == 0)
$this->input['report_keys'] = ksort($this->all_keys);
$this->input['report_keys'] = array_values($this->entity_keys);
//insert the header
$this->csv->insertOne($this->buildHeader());
@ -209,16 +154,20 @@ class InvoiceItemExport extends BaseExport
$entity = [];
$transformed_items = array_merge($transformed_invoice, $item_array);
$transformed_items = $this->decorateAdvancedFields($invoice, $transformed_items);
foreach(array_values($this->input['report_keys']) as $key)
{
$key = str_replace("item.", "", $key);
$entity[$key] = $transformed_items[$key];
$keyval = array_search($key, $this->entity_keys);
if(array_key_exists($key, $transformed_items))
$entity[$keyval] = $transformed_items[$key];
else
$entity[$keyval] = "";
}
$transformed_items = array_merge($transformed_invoice, $item_array);
$entity = $this->decorateAdvancedFields($invoice, $transformed_items);
$this->csv->insertOne($entity);
}
@ -234,8 +183,12 @@ class InvoiceItemExport extends BaseExport
foreach(array_values($this->input['report_keys']) as $key){
if(!str_contains($key, "item."))
$entity[$key] = $transformed_invoice[$key];
$keyval = array_search($key, $this->entity_keys);
if(array_key_exists($key, $transformed_invoice))
$entity[$keyval] = $transformed_invoice[$key];
else
$entity[$keyval] = "";
}
@ -245,14 +198,14 @@ class InvoiceItemExport extends BaseExport
private function decorateAdvancedFields(Invoice $invoice, array $entity) :array
{
if(array_key_exists('currency_id', $entity))
$entity['currency_id'] = $invoice->client->currency()->code;
if(in_array('currency_id', $this->input['report_keys']))
$entity['currency'] = $invoice->client->currency() ? $invoice->client->currency()->code : $invoice->company->currency()->code;
if(array_key_exists('client_id', $entity))
$entity['client_id'] = $invoice->client->present()->name();
if(in_array('client_id', $this->input['report_keys']))
$entity['client'] = $invoice->client->present()->name();
if(array_key_exists('status_id', $entity))
$entity['status_id'] = $invoice->stringStatus($invoice->status_id);
if(in_array('status_id', $this->input['report_keys']))
$entity['status'] = $invoice->stringStatus($invoice->status_id);
return $entity;
}

View File

@ -43,7 +43,7 @@ class PaymentExport extends BaseExport
'custom_value4' => 'custom_value4',
'date' => 'date',
'exchange_currency' => 'exchange_currency_id',
'gateway_type' => 'gateway_type_id',
'gateway' => 'gateway_type_id',
'number' => 'number',
'private_notes' => 'private_notes',
'project' => 'project_id',
@ -54,28 +54,6 @@ class PaymentExport extends BaseExport
'vendor' => 'vendor_id',
];
protected array $all_keys = [
'amount',
'applied',
'client_id',
'currency_id',
'custom_value1',
'custom_value2',
'custom_value3',
'custom_value4',
'date',
'exchange_currency_id',
'gateway_type_id',
'number',
'private_notes',
'project_id',
'refunded',
'status_id',
'transaction_reference',
'type_id',
'vendor_id',
];
private array $decorate_keys = [
'vendor',
'status',
@ -106,7 +84,7 @@ class PaymentExport extends BaseExport
$this->csv = Writer::createFromString();
if(count($this->input['report_keys']) == 0)
$this->input['report_keys'] = $this->all_keys;
$this->input['report_keys'] = array_values($this->entity_keys);
//insert the header
$this->csv->insertOne($this->buildHeader());
@ -135,7 +113,12 @@ class PaymentExport extends BaseExport
foreach(array_values($this->input['report_keys']) as $key){
$entity[$key] = $transformed_entity[$key];
$keyval = array_search($key, $this->entity_keys);
if(array_key_exists($key, $transformed_entity))
$entity[$keyval] = $transformed_entity[$key];
else
$entity[$keyval] = '';
}
@ -146,26 +129,29 @@ class PaymentExport extends BaseExport
private function decorateAdvancedFields(Payment $payment, array $entity) :array
{
if(array_key_exists('status_id', $entity))
$entity['status_id'] = $payment->stringStatus($payment->status_id);
if(in_array('status_id', $this->input['report_keys']))
$entity['status'] = $payment->stringStatus($payment->status_id);
if(array_key_exists('vendor_id', $entity))
$entity['vendor_id'] = $payment->vendor()->exists() ? $payment->vendor->name : '';
if(in_array('vendor_id', $this->input['report_keys']))
$entity['vendor'] = $payment->vendor()->exists() ? $payment->vendor->name : '';
if(array_key_exists('project_id', $entity))
$entity['project_id'] = $payment->project()->exists() ? $payment->project->name : '';
if(in_array('project_id', $this->input['report_keys']))
$entity['project'] = $payment->project()->exists() ? $payment->project->name : '';
if(array_key_exists('currency_id', $entity))
$entity['currency_id'] = $payment->currency()->exists() ? $payment->currency->code : '';
if(in_array('currency_id', $this->input['report_keys']))
$entity['currency'] = $payment->currency()->exists() ? $payment->currency->code : '';
if(array_key_exists('exchange_currency_id', $entity))
$entity['exchange_currency_id'] = $payment->exchange_currency()->exists() ? $payment->exchange_currency->code : '';
if(in_array('exchange_currency_id', $this->input['report_keys']))
$entity['exchange_currency'] = $payment->exchange_currency()->exists() ? $payment->exchange_currency->code : '';
if(array_key_exists('client_id', $entity))
$entity['client_id'] = $payment->client->present()->name();
if(in_array('client_id', $this->input['report_keys']))
$entity['client'] = $payment->client->present()->name();
if(array_key_exists('type_id', $entity))
$entity['type_id'] = $payment->translatedType();
if(in_array('type_id', $this->input['report_keys']))
$entity['type'] = $payment->translatedType();
if(in_array('gateway_type_id', $this->input['report_keys']))
$entity['gateway'] = $payment->gateway_type ? $payment->gateway_type->name : "Unknown Type";
return $entity;
}

View File

@ -52,26 +52,6 @@ class ProductExport extends BaseExport
'tax_name3' => 'tax_name3',
];
protected array $all_keys = [
'project_id',
'vendor_id',
'custom_value1',
'custom_value2',
'custom_value3',
'custom_value4',
'product_key',
'notes',
'cost',
'price',
'quantity',
'tax_rate1',
'tax_rate2',
'tax_rate3',
'tax_name1',
'tax_name2',
'tax_name3',
];
private array $decorate_keys = [
'vendor',
'project',
@ -97,7 +77,7 @@ class ProductExport extends BaseExport
$this->csv = Writer::createFromString();
if(count($this->input['report_keys']) == 0)
$this->input['report_keys'] = $this->all_keys;
$this->input['report_keys'] = array_values($this->entity_keys);
//insert the header
$this->csv->insertOne($this->buildHeader());
@ -126,7 +106,13 @@ class ProductExport extends BaseExport
foreach(array_values($this->input['report_keys']) as $key){
$entity[$key] = $transformed_entity[$key];
$keyval = array_search($key, $this->entity_keys);
if(array_key_exists($key, $transformed_entity))
$entity[$keyval] = $transformed_entity[$key];
else
$entity[$keyval] = '';
}
@ -137,11 +123,11 @@ class ProductExport extends BaseExport
private function decorateAdvancedFields(Product $product, array $entity) :array
{
if(array_key_exists('vendor_id', $entity))
$entity['vendor_id'] = $product->vendor()->exists() ? $product->vendor->name : '';
if(in_array('vendor_id', $this->input['report_keys']))
$entity['vendor'] = $product->vendor()->exists() ? $product->vendor->name : '';
if(array_key_exists('project_id', $entity))
$entity['project_id'] = $product->project()->exists() ? $product->project->name : '';
if(array_key_exists('project_id', $this->input['report_keys']))
$entity['project'] = $product->project()->exists() ? $product->project->name : '';
return $entity;
}

View File

@ -63,48 +63,10 @@ class QuoteExport extends BaseExport
'tax_rate3' => 'tax_rate3',
'terms' => 'terms',
'total_taxes' => 'total_taxes',
'currency' => 'client_id',
'currency' => 'currency_id',
'invoice' => 'invoice_id',
];
protected array $all_keys = [
'amount',
'balance',
'client_id',
'custom_surcharge1',
'custom_surcharge2',
'custom_surcharge3',
'custom_surcharge4',
'custom_value1',
'custom_value2',
'custom_value3',
'custom_value4',
'date',
'discount',
'due_date',
'exchange_rate',
'footer',
'number',
'paid_to_date',
'partial',
'partial_due_date',
'po_number',
'private_notes',
'public_notes',
'status_id',
'tax_name1',
'tax_name2',
'tax_name3',
'tax_rate1',
'tax_rate2',
'tax_rate3',
'terms',
'total_taxes',
'client_id',
'invoice_id',
];
private array $decorate_keys = [
'client',
'currency',
@ -131,13 +93,14 @@ class QuoteExport extends BaseExport
$this->csv = Writer::createFromString();
if(count($this->input['report_keys']) == 0)
$this->input['report_keys'] = $this->all_keys;
$this->input['report_keys'] = array_values($this->entity_keys);
//insert the header
$this->csv->insertOne($this->buildHeader());
$query = Quote::query()
->with('client')->where('company_id', $this->company->id)
->with('client')
->where('company_id', $this->company->id)
->where('is_deleted',0);
$query = $this->addDateRange($query);
@ -163,7 +126,12 @@ class QuoteExport extends BaseExport
foreach(array_values($this->input['report_keys']) as $key){
$entity[$key] = $transformed_quote[$key];
$keyval = array_search($key, $this->entity_keys);
if(array_key_exists($key, $transformed_quote))
$entity[$keyval] = $transformed_quote[$key];
else
$entity[$keyval] = '';
}
return $this->decorateAdvancedFields($quote, $entity);
@ -172,13 +140,16 @@ class QuoteExport extends BaseExport
private function decorateAdvancedFields(Quote $quote, array $entity) :array
{
if(array_key_exists('currency', $entity))
if(in_array('currency_id', $this->input['report_keys']))
$entity['currency'] = $quote->client->currency()->code;
if(array_key_exists('client_id', $entity))
$entity['client_id'] = $quote->client->present()->name();
if(in_array('client_id', $this->input['report_keys']))
$entity['client'] = $quote->client->present()->name();
if(array_key_exists('invoice', $entity))
if(in_array('status_id',$this->input['report_keys']))
$entity['status'] = $quote->stringStatus($quote->status_id);
if(in_array('invoice_id', $this->input['report_keys']))
$entity['invoice'] = $quote->invoice ? $quote->invoice->number : "";
return $entity;

View File

@ -79,66 +79,12 @@ class QuoteItemExport extends BaseExport
'tax_name3' => 'item.tax_name3',
'line_total' => 'item.line_total',
'gross_line_total' => 'item.gross_line_total',
'invoice1' => 'item.custom_value1',
'invoice2' => 'item.custom_value2',
'invoice3' => 'item.custom_value3',
'invoice4' => 'item.custom_value4',
'custom_value1' => 'item.custom_value1',
'custom_value2' => 'item.custom_value2',
'custom_value3' => 'item.custom_value3',
'custom_value4' => 'item.custom_value4',
];
protected array $all_keys = [
'amount',
'balance',
'client_id',
'custom_surcharge1',
'custom_surcharge2',
'custom_surcharge3',
'custom_surcharge4',
'custom_value1',
'custom_value2',
'custom_value3',
'custom_value4',
'date',
'discount',
'due_date',
'exchange_rate',
'footer',
'number',
'paid_to_date',
'partial',
'partial_due_date',
'po_number',
'private_notes',
'public_notes',
'status_id',
'tax_name1',
'tax_name2',
'tax_name3',
'tax_rate1',
'tax_rate2',
'tax_rate3',
'terms',
'total_taxes',
'currency_id',
'item.quantity',
'item.cost',
'item.product_key',
'item.product_cost',
'item.notes',
'item.discount',
'item.is_amount_discount',
'item.tax_rate1',
'item.tax_rate2',
'item.tax_rate3',
'item.tax_name1',
'item.tax_name2',
'item.tax_name3',
'item.line_total',
'item.gross_line_total',
'item.custom_value1',
'item.custom_value2',
'item.custom_value3',
'item.custom_value4',
];
private array $decorate_keys = [
'client',
@ -165,7 +111,8 @@ class QuoteItemExport extends BaseExport
$this->csv = Writer::createFromString();
if(count($this->input['report_keys']) == 0)
$this->input['report_keys'] = $this->all_keys;
$this->input['report_keys'] = array_values($this->entity_keys);
//insert the header
$this->csv->insertOne($this->buildHeader());
@ -209,16 +156,20 @@ class QuoteItemExport extends BaseExport
$entity = [];
$transformed_items = array_merge($transformed_quote, $item_array);
$transformed_items = $this->decorateAdvancedFields($quote, $transformed_items);
foreach(array_values($this->input['report_keys']) as $key)
{
$key = str_replace("item.", "", $key);
$entity[$key] = $transformed_items[$key];
$keyval = array_search($key, $this->entity_keys);
if(array_key_exists($key, $transformed_items))
$entity[$keyval] = $transformed_items[$key];
else
$entity[$keyval] = "";
}
$transformed_items = array_merge($transformed_quote, $item_array);
$entity = $this->decorateAdvancedFields($quote, $transformed_items);
$this->csv->insertOne($entity);
}
@ -234,8 +185,12 @@ class QuoteItemExport extends BaseExport
foreach(array_values($this->input['report_keys']) as $key){
if(!str_contains($key, "item."))
$entity[$key] = $transformed_quote[$key];
$keyval = array_search($key, $this->entity_keys);
if(array_key_exists($key, $transformed_quote))
$entity[$keyval] = $transformed_quote[$key];
else
$entity[$keyval] = "";
}
@ -245,14 +200,14 @@ class QuoteItemExport extends BaseExport
private function decorateAdvancedFields(Quote $quote, array $entity) :array
{
if(array_key_exists('currency_id', $entity))
$entity['currency_id'] = $quote->client->currency()->code;
if(in_array('currency_id', $this->input['report_keys']))
$entity['currency'] = $quote->client->currency() ? $quote->client->currency()->code : $quote->company->currency()->code;
if(array_key_exists('client_id', $entity))
$entity['client_id'] = $quote->client->present()->name();
if(in_array('client_id', $this->input['report_keys']))
$entity['client'] = $quote->client->present()->name();
if(array_key_exists('status_id', $entity))
$entity['status_id'] = $quote->stringStatus($quote->status_id);
if(in_array('status_id', $this->input['report_keys']))
$entity['status'] = $quote->stringStatus($quote->status_id);
return $entity;
}

View File

@ -63,49 +63,11 @@ class RecurringInvoiceExport extends BaseExport
'tax_rate3' => 'tax_rate3',
'terms' => 'terms',
'total_taxes' => 'total_taxes',
'currency' => 'client_id',
'currency' => 'currency_id',
'vendor' => 'vendor_id',
'project' => 'project_id',
];
protected array $all_keys = [
'amount',
'balance',
'client_id',
'custom_surcharge1',
'custom_surcharge2',
'custom_surcharge3',
'custom_surcharge4',
'custom_value1',
'custom_value2',
'custom_value3',
'custom_value4',
'date',
'discount',
'due_date',
'exchange_rate',
'footer',
'number',
'paid_to_date',
'partial',
'partial_due_date',
'po_number',
'private_notes',
'public_notes',
'status_id',
'tax_name1',
'tax_name2',
'tax_name3',
'tax_rate1',
'tax_rate2',
'tax_rate3',
'terms',
'total_taxes',
'client_id',
'vendor_id',
'project_id',
];
private array $decorate_keys = [
'country',
'client',
@ -135,7 +97,7 @@ class RecurringInvoiceExport extends BaseExport
$this->csv = Writer::createFromString();
if(count($this->input['report_keys']) == 0)
$this->input['report_keys'] = $this->all_keys;
$this->input['report_keys'] = array_values($this->entity_keys);
//insert the header
$this->csv->insertOne($this->buildHeader());
@ -167,7 +129,13 @@ class RecurringInvoiceExport extends BaseExport
foreach(array_values($this->input['report_keys']) as $key){
$entity[$key] = $transformed_invoice[$key];
$keyval = array_search($key, $this->entity_keys);
if(array_key_exists($key, $transformed_invoice))
$entity[$keyval] = $transformed_invoice[$key];
else
$entity[$keyval] = '';
}
return $this->decorateAdvancedFields($invoice, $entity);
@ -176,20 +144,23 @@ class RecurringInvoiceExport extends BaseExport
private function decorateAdvancedFields(RecurringInvoice $invoice, array $entity) :array
{
if(array_key_exists('currency', $entity))
$entity['currency'] = $invoice->client->currency()->code;
if(in_array('country_id', $this->input['report_keys']))
$entity['country'] = $invoice->client->country ? ctrans("texts.country_{$invoice->client->country->name}") : "";
if(array_key_exists('client_id', $entity))
$entity['client_id'] = $invoice->client->present()->name();
if(in_array('currency_id', $this->input['report_keys']))
$entity['currency'] = $invoice->client->currency() ? $invoice->client->currency()->code : $invoice->company->currency()->code;
if(array_key_exists('status_id', $entity))
$entity['status_id'] = $invoice->stringStatus($invoice->status_id);
if(in_array('client_id', $this->input['report_keys']))
$entity['client'] = $invoice->client->present()->name();
if(array_key_exists('vendor_id', $entity))
$entity['vendor_id'] = $invoice->vendor()->exists() ? $invoice->vendor->name : '';
if(in_array('status_id',$this->input['report_keys']))
$entity['status'] = $invoice->stringStatus($invoice->status_id);
if(array_key_exists('project_id', $entity))
$entity['project'] = $invoice->project()->exists() ? $invoice->project->name : '';
if(in_array('project_id',$this->input['report_keys']))
$entity['project'] = $invoice->project ? $invoice->project->name : "";
if(in_array('vendor_id',$this->input['report_keys']))
$entity['vendor'] = $invoice->vendor ? $invoice->vendor->name : "";
return $entity;
}

View File

@ -52,23 +52,6 @@ class TaskExport extends BaseExport
'client' => 'client_id',
];
protected array $all_keys = [
'start_date',
'end_date',
'duration',
'rate',
'number',
'description',
'custom_value1',
'custom_value2',
'custom_value3',
'custom_value4',
'status_id',
'project_id',
'invoice_id',
'client_id',
];
private array $decorate_keys = [
'status',
'project',
@ -99,10 +82,12 @@ class TaskExport extends BaseExport
//load the CSV document from a string
$this->csv = Writer::createFromString();
ksort($this->entity_keys);
if(count($this->input['report_keys']) == 0)
$this->input['report_keys'] = $this->all_keys;
$this->input['report_keys'] = array_values($this->entity_keys);
//insert the header
$this->csv->insertOne($this->buildHeader());
@ -132,27 +117,35 @@ class TaskExport extends BaseExport
foreach(array_values($this->input['report_keys']) as $key){
$keyval = array_search($key, $this->entity_keys);
if(array_key_exists($key, $transformed_entity))
$entity[$key] = $transformed_entity[$key];
$entity[$keyval] = $transformed_entity[$key];
else
$entity[$key] = '';
$entity[$keyval] = '';
}
$entity['start_date'] = "";
$entity['end_date'] = "";
$entity['duration'] = "";
$entity = $this->decorateAdvancedFields($task, $entity);
ksort($entity);
$this->csv->insertOne($entity);
}
elseif(is_array(json_decode($task->time_log,1)) && count(json_decode($task->time_log,1)) > 0) {
foreach(array_values($this->input['report_keys']) as $key){
if(array_key_exists($key, $transformed_entity))
$entity[$key] = $transformed_entity[$key];
else
$entity[$key] = '';
}
$keyval = array_search($key, $this->entity_keys);
$entity = $this->decorateAdvancedFields($task, $entity);
if(array_key_exists($key, $transformed_entity))
$entity[$keyval] = $transformed_entity[$key];
else
$entity[$keyval] = '';
}
$this->iterateLogs($task, $entity);
}
@ -168,30 +161,45 @@ class TaskExport extends BaseExport
$timezone_name = $timezone->name;
$logs = json_decode($task->time_log,1);
$date_format_default = "Y-m-d";
$date_format = DateFormat::find($task->company->settings->date_format_id);
if($date_format)
$date_format_default = $date_format->format;
foreach($logs as $key => $item)
{
if(in_array("start_date",$this->input['report_keys'])){
$entity['start_date'] = Carbon::createFromTimeStamp($item[0])->setTimezone($timezone_name);
nlog("start date" . $entity['start_date']);
if(in_array("start_date", $this->input['report_keys'])){
$entity['start_date'] = Carbon::createFromTimeStamp($item[0])->setTimezone($timezone_name)->format($date_format_default);
}
if(in_array("end_date",$this->input['report_keys']) && $item[1] > 0){
$entity['end_date'] = Carbon::createFromTimeStamp($item[1])->setTimezone($timezone_name);
nlog("start date" . $entity['end_date']);
if(in_array("end_date", $this->input['report_keys']) && $item[1] > 0){
$entity['end_date'] = Carbon::createFromTimeStamp($item[1])->setTimezone($timezone_name)->format($date_format_default);
}
if(in_array("end_date",$this->input['report_keys']) && $item[1] == 0){
if(in_array("end_date", $this->input['report_keys']) && $item[1] == 0){
$entity['end_date'] = ctrans('texts.is_running');
nlog("start date" . $entity['end_date']);
}
if(in_array("duration",$this->input['report_keys'])){
if(in_array("duration", $this->input['report_keys'])){
$entity['duration'] = $task->calcDuration();
nlog("duration" . $entity['duration']);
}
if(!array_key_exists('duration', $entity))
$entity['duration'] = "";
if(!array_key_exists('start_date', $entity))
$entity['start_date'] = "";
if(!array_key_exists('end_date', $entity))
$entity['end_date'] = "";
$entity = $this->decorateAdvancedFields($task, $entity);
ksort($entity);
$this->csv->insertOne($entity);
unset($entity['start_date']);
@ -204,16 +212,17 @@ class TaskExport extends BaseExport
private function decorateAdvancedFields(Task $task, array $entity) :array
{
if(array_key_exists('status_id', $entity))
$entity['status_id'] = $task->status()->exists() ? $task->status->name : '';
if(in_array('status_id', $this->input['report_keys']))
$entity['status'] = $task->status()->exists() ? $task->status->name : '';
if(array_key_exists('project_id', $entity))
$entity['project_id'] = $task->project()->exists() ? $task->project->name : '';
if(array_key_exists('client_id', $entity))
$entity['client_id'] = $task->client->present()->name();
if(in_array('project_id', $this->input['report_keys']))
$entity['project'] = $task->project()->exists() ? $task->project->name : '';
if(in_array('client_id', $this->input['report_keys']))
$entity['client'] = $task->client ? $task->client->present()->name() : "";
if(in_array('invoice_id', $this->input['report_keys']))
$entity['invoice'] = $task->invoice ? $task->invoice->number : "";
return $entity;
}

View File

@ -39,6 +39,9 @@ class ExpenseFactory
$expense->custom_value2 = '';
$expense->custom_value3 = '';
$expense->custom_value4 = '';
$expense->tax_amount1 = 0;
$expense->tax_amount2 = 0;
$expense->tax_amount3 = 0;
return $expense;
}

View File

@ -33,6 +33,7 @@ class PaymentFactory
$payment->transaction_reference = null;
$payment->payer_id = null;
$payment->status_id = Payment::STATUS_PENDING;
$payment->exchange_rate = 1;
return $payment;
}

View File

@ -30,6 +30,11 @@ class PaymentMethodController extends Controller
{
use MakesDates;
public function __construct()
{
$this->middleware('throttle:10,1')->only('store');
}
/**
* Display a listing of the resource.
*
@ -92,7 +97,6 @@ class PaymentMethodController extends Controller
public function verify(ClientGatewayToken $payment_method)
{
// $gateway = $this->getClientGateway();
return $payment_method->gateway
->driver(auth()->user()->client)

View File

@ -0,0 +1,87 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\Controllers\Reports;
use App\Export\CSV\PaymentExport;
use App\Http\Controllers\BaseController;
use App\Http\Requests\Report\ProfitLossRequest;
use App\Models\Client;
use App\Services\Report\ProfitLoss;
use App\Utils\Traits\MakesHash;
use Illuminate\Http\Response;
class ProfitAndLossController extends BaseController
{
use MakesHash;
private string $filename = 'profit_and_loss.csv';
public function __construct()
{
parent::__construct();
}
/**
* @OA\Post(
* path="/api/v1/reports/profitloss",
* operationId="getProfitLossReport",
* tags={"reports"},
* summary="Profit loss reports",
* description="Profit loss report",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\RequestBody(
* required=true,
* @OA\JsonContent(ref="#/components/schemas/GenericReportSchema")
* ),
* @OA\Response(
* response=200,
* description="success",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function __invoke(ProfitLossRequest $request)
{
// expect a list of visible fields, or use the default
$pnl = new ProfitLoss(auth()->user()->company(), $request->all());
$pnl->build();
$csv = $pnl->getCsv();
$headers = array(
'Content-Disposition' => 'attachment',
'Content-Type' => 'text/csv',
);
return response()->streamDownload(function () use ($csv) {
echo $csv;
}, $this->filename, $headers);
}
}

View File

@ -32,7 +32,7 @@ class GenericReportRequest extends Request
'end_date' => 'string|date',
'date_key' => 'string',
'date_range' => 'string',
'report_keys' => 'sometimes|array'
'report_keys' => 'present|array'
];
}
}

View File

@ -0,0 +1,39 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\Requests\Report;
use App\Http\Requests\Request;
class ProfitLossRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->isAdmin();
}
public function rules()
{
return [
'start_date' => 'string|date',
'end_date' => 'string|date',
'is_income_billed' => 'required|bail|bool',
'is_expense_billed' => 'required|bail|bool',
'include_tax' => 'required|bail|bool',
'date_range' => 'required|bail|string'
];
}
}

View File

@ -21,6 +21,8 @@ class BlackListRule implements Rule
{
private array $blacklist = [
'candassociates.com',
'vusra.com',
'fourthgenet.com',
];
/**

View File

@ -277,11 +277,13 @@ class CompanyImport implements ShouldQueue
'errors' => []
];
$_company = Company::find($this->company->id);
$nmo = new NinjaMailerObject;
$nmo->mailable = new ImportCompleted($this->company, $data);
$nmo->company = $this->company;
$nmo->settings = $this->company->settings;
$nmo->to_user = $this->company->owner();
$nmo->mailable = new ImportCompleted($_company, $data);
$nmo->company = $_company;
$nmo->settings = $_company->settings;
$nmo->to_user = $_company->owner();
NinjaMailerJob::dispatchNow($nmo);
}
@ -1450,6 +1452,20 @@ class CompanyImport implements ShouldQueue
$new_obj->save(['timestamps' => false]);
$new_obj->number = $this->getNextRecurringExpenseNumber($new_obj);
}
elseif($class == 'App\Models\Project' && is_null($obj->{$match_key})){
$new_obj = new Project();
$new_obj->company_id = $this->company->id;
$new_obj->fill($obj_array);
$new_obj->save(['timestamps' => false]);
$new_obj->number = $this->getNextProjectNumber($new_obj);
}
elseif($class == 'App\Models\Task' && is_null($obj->{$match_key})){
$new_obj = new Task();
$new_obj->company_id = $this->company->id;
$new_obj->fill($obj_array);
$new_obj->save(['timestamps' => false]);
$new_obj->number = $this->getNextTaskNumber($new_obj);
}
elseif($class == 'App\Models\CompanyLedger'){
$new_obj = $class::firstOrNew(
[$match_key => $obj->{$match_key}, 'company_id' => $this->company->id],
@ -1514,10 +1530,9 @@ class CompanyImport implements ShouldQueue
}
if (! array_key_exists($resource, $this->ids)) {
nlog($resource);
$this->sendImportMail("The Import failed due to missing data in the import file. Resource {$resource} not available.");
nlog($this->ids);
throw new \Exception("Resource {$resource} not available.");
}
@ -1547,8 +1562,10 @@ class CompanyImport implements ShouldQueue
$t = app('translator');
$t->replace(Ninja::transformTranslations($this->company->settings));
$_company = Company::find($this->company->id);
$nmo = new NinjaMailerObject;
$nmo->mailable = new CompanyImportFailure($this->company, $message);
$nmo->mailable = new CompanyImportFailure($_company, $message);
$nmo->company = $this->company;
$nmo->settings = $this->company->settings;
$nmo->to_user = $this->company->owner();

View File

@ -131,7 +131,7 @@ class NinjaMailerJob implements ShouldQueue
$response = $e->getResponse();
$message_body = json_decode($response->getBody()->getContents());
if(property_exists($message_body, 'Message')){
if($message_body && property_exists($message_body, 'Message')){
$message = $message_body->Message;
nlog($message);
}
@ -268,9 +268,10 @@ class NinjaMailerJob implements ShouldQueue
return false;
/* On the hosted platform, if the user is over the email quotas, we do not send the email. */
if(Ninja::isHosted() && $this->company->account->emailQuotaExceeded())
if(Ninja::isHosted() && $this->company->account && $this->company->account->emailQuotaExceeded())
return true;
/* Ensure the user has a valid email address */
if(!str_contains($this->nmo->to_user->email, "@"))
return true;

View File

@ -0,0 +1,89 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Jobs\Report;
use App\Libraries\MultiDB;
use App\Models\Company;
use App\Services\Report\ProfitLoss;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ProfitAndLoss implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected Company $company;
protected array $payload;
/**
* Create a new job instance.
*
* @param RecurringInvoice $recurring_invoice
* @param string $db
*/
public function __construct(Company $company, array $payload)
{
$this->company = $company;
$this->payload = $payload;
}
/**
* Execute the job.
*
* @return void
*/
public function handle() : void
{
MultiDB::setDb($this->company->db);
/*
payload variables.
start_date - Y-m-d
end_date - Y-m-d
date_range -
all
last7
last30
this_month
last_month
this_quarter
last_quarter
this_year
custom
income_billed - true = Invoiced || false = Payments
expense_billed - true = Expensed || false = Expenses marked as paid
include_tax - true tax_included || false - tax_excluded
*/
$pl = new ProfitLoss($this->company, $this->payload);
$pl->build();
}
public function failed($exception = null)
{
}
}

View File

@ -50,10 +50,6 @@ class CompanyDeleted extends Mailable
public function build()
{
App::forgetInstance('translator');
if($this->company)
App::setLocale($this->company->getLocale());
$t = app('translator');
$t->replace(Ninja::transformTranslations($this->settings));

View File

@ -14,14 +14,19 @@ namespace App\Mail\Engine;
use App\DataMapper\EmailTemplateDefaults;
use App\Jobs\Entity\CreateEntityPdf;
use App\Models\Account;
use App\Models\Expense;
use App\Models\Task;
use App\Utils\HtmlEngine;
use App\Utils\Ninja;
use App\Utils\Number;
use App\Utils\Traits\MakesHash;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Lang;
class InvoiceEmailEngine extends BaseEmailEngine
{
use MakesHash;
public $invitation;
public $client;
@ -146,6 +151,53 @@ class InvoiceEmailEngine extends BaseEmailEngine
$this->setAttachments([['path' => $document->filePath(), 'name' => $document->name, 'mime' => $document->type]]);
}
$line_items = $this->invoice->line_items;
$expense_ids = [];
foreach($line_items as $item)
{
if(property_exists($item, 'expense_id'))
{
$expense_ids[] = $item->expense_id;
}
if(count($expense_ids) > 0){
$expenses = Expense::whereIn('id', $this->transformKeys($expense_ids))
->where('invoice_documents', 1)
->cursor()
->each(function ($expense){
foreach($expense->documents as $document)
{
$this->setAttachments([['path' => $document->filePath(), 'name' => $document->name, 'mime' => $document->type]]);
}
});
}
if(property_exists($item, 'task_id'))
{
$task_ids[] = $item->task_id;
}
if(count($task_ids) > 0 && $this->invoice->company->invoice_task_documents){
$tasks = Task::whereIn('id', $this->transformKeys($task_ids))
->cursor()
->each(function ($task){
foreach($task->documents as $document)
{
$this->setAttachments([['path' => $document->filePath(), 'name' => $document->name, 'mime' => $document->type]]);
}
});
}
}
}

View File

@ -58,6 +58,22 @@ class ImportCompleted extends Mailable
'logo' => $this->company->present()->logo(),
'settings' => $this->company->settings,
'company' => $this->company,
'client_count' => $this->company->clients()->count(),
'product_count' => $this->company->products()->count(),
'invoice_count' => $this->company->invoices()->count(),
'quote_count' => $this->company->quotes()->count(),
'credit_count' => $this->company->credits()->count(),
'project_count' => $this->company->projects()->count(),
'task_count' => $this->company->tasks()->count(),
'vendor_count' => $this->company->vendors()->count(),
'payment_count' => $this->company->payments()->count(),
'recurring_invoice_count' => $this->company->recurring_invoices()->count(),
'expense_count' => $this->company->expenses()->count(),
'company_gateway_count' => $this->company->company_gateways()->count(),
'client_gateway_token_count' => $this->company->client_gateway_tokens()->count(),
'tax_rate_count' => $this->company->tax_rates()->count(),
'document_count' => $this->company->documents()->count(),
]);
return $this

View File

@ -253,7 +253,7 @@ class Client extends BaseModel implements HasLocalePreference
public function system_logs()
{
return $this->hasMany(SystemLog::class)->orderBy('id', 'desc');
return $this->hasMany(SystemLog::class)->take(50)->orderBy('id', 'desc');
}
public function timezone()

View File

@ -118,9 +118,7 @@ class Company extends BaseModel
'client_registration_fields' => 'array',
];
protected $with = [
// 'tokens'
];
protected $with = [];
public static $modules = [
self::ENTITY_RECURRING_INVOICE => 1,
@ -553,4 +551,17 @@ class Company extends BaseModel
{
return ctrans('texts.company');
}
public function date_format()
{
$date_formats = Cache::get('date_formats');
if(!$date_formats)
$this->buildCache(true);
return $date_formats->filter(function ($item) {
return $item->id == $this->getSetting('date_format_id');
})->first()->format;
}
}

View File

@ -319,4 +319,25 @@ class Credit extends BaseModel
{
return ctrans('texts.credit');
}
public static function stringStatus(int $status)
{
switch ($status) {
case self::STATUS_DRAFT:
return ctrans('texts.draft');
break;
case self::STATUS_SENT:
return ctrans('texts.sent');
break;
case self::STATUS_PARTIAL:
return ctrans('texts.partial');
break;
case self::STATUS_APPLIED:
return ctrans('texts.applied');
break;
default:
return "";
break;
}
}
}

View File

@ -114,7 +114,7 @@ class Expense extends BaseModel
public function category()
{
return $this->belongsTo(ExpenseCategory::class);
return $this->belongsTo(ExpenseCategory::class)->withTrashed();
}
public function payment_type()

View File

@ -55,6 +55,11 @@ class Product extends BaseModel
return $this->belongsTo(User::class)->withTrashed();
}
public function vendor()
{
return $this->belongsTo(Vendor::class)->withTrashed();
}
public function assigned_user()
{
return $this->belongsTo(User::class, 'assigned_user_id', 'id')->withTrashed();

View File

@ -72,11 +72,15 @@ class WePayFailureNotification extends Notification
public function toSlack($notifiable)
{
$ip = "";
if(request())
$ip = request()->getClientIp();
(new SlackMessage)
return (new SlackMessage)
->success()
->from(ctrans('texts.notification_bot'))
->image('https://app.invoiceninja.com/favicon.png')
->content("New WePay ACH Failure from Company ID: ". $this->company_id);
->content("New WePay ACH Failure from Company ID: {$this->company_id} IP: {$ip}" );
}
}

View File

@ -230,14 +230,11 @@ class GoCardlessPaymentDriver extends BaseDriver
public function processWebhookRequest(PaymentWebhookRequest $request)
{
// Allow app to catch up with webhook request.
sleep(2);
$this->init();
nlog("GoCardless Event");
nlog($request->all());
if(!is_array($request->events) || !is_object($request->events)){
nlog("No GoCardless events to process in response?");
@ -245,19 +242,24 @@ class GoCardlessPaymentDriver extends BaseDriver
}
sleep(1);
foreach ($request->events as $event) {
if ($event['action'] === 'confirmed') {
nlog("Searching for transaction reference");
$payment = Payment::query()
->where('transaction_reference', $event['links']['payment'])
->where('company_id', $request->getCompany()->id)
// ->where('company_id', $request->getCompany()->id)
->first();
if ($payment) {
$payment->status_id = Payment::STATUS_COMPLETED;
$payment->save();
}
else
nlog("I was unable to find the payment for this reference");
//finalize payments on invoices here.
}
@ -266,7 +268,7 @@ class GoCardlessPaymentDriver extends BaseDriver
$payment = Payment::query()
->where('transaction_reference', $event['links']['payment'])
->where('company_id', $request->getCompany()->id)
// ->where('company_id', $request->getCompany()->id)
->first();
if ($payment) {

View File

@ -601,9 +601,14 @@ class StripePaymentDriver extends BaseDriver
}
} elseif ($request->type === 'source.chargeable') {
$this->init();
foreach ($request->data as $transaction) {
if(!$request->data['object']['amount'] || empty($request->data['object']['amount']))
continue;
$charge = \Stripe\Charge::create([
'amount' => $request->data['object']['amount'],
'currency' => $request->data['object']['currency'],
@ -619,6 +624,7 @@ class StripePaymentDriver extends BaseDriver
->orWhere('transaction_reference', $transaction['id']);
})
->first();
if ($payment) {
$payment->status_id = Payment::STATUS_COMPLETED;
$payment->save();

View File

@ -12,8 +12,10 @@
namespace App\Repositories;
use App\Factory\ExpenseFactory;
use App\Libraries\Currency\Conversion\CurrencyApi;
use App\Models\Expense;
use App\Utils\Traits\GeneratesCounter;
use Illuminate\Support\Carbon;
/**
* ExpenseRepository.
@ -34,6 +36,10 @@ class ExpenseRepository extends BaseRepository
public function save(array $data, Expense $expense) : ?Expense
{
$expense->fill($data);
if(!$expense->id)
$expense = $this->processExchangeRates($data, $expense);
$expense->number = empty($expense->number) ? $this->getNextExpenseNumber($expense) : $expense->number;
$expense->save();
@ -57,4 +63,27 @@ class ExpenseRepository extends BaseRepository
ExpenseFactory::create(auth()->user()->company()->id, auth()->user()->id)
);
}
public function processExchangeRates($data, $expense)
{
if(array_key_exists('exchange_rate', $data) && isset($data['exchange_rate']) && $data['exchange_rate'] != 1){
return $expense;
}
$expense_currency = $data['currency_id'];
$company_currency = $expense->company->settings->currency_id;
if ($company_currency != $expense_currency) {
$exchange_rate = new CurrencyApi();
$expense->exchange_rate = $exchange_rate->exchangeRate($expense_currency, $company_currency, Carbon::parse($expense->date));
return $expense;
}
return $expense;
}
}

View File

@ -66,7 +66,11 @@ class PaymentRepository extends BaseRepository {
//check currencies here and fill the exchange rate data if necessary
if (! $payment->id) {
$this->processExchangeRates($data, $payment);
$payment = $this->processExchangeRates($data, $payment);
/* This is needed here otherwise the ->fill() overwrites anything that exists*/
if($payment->exchange_rate != 1)
unset($data['exchange_rate']);
$is_existing_payment = false;
$client = Client::where('id', $data['client_id'])->withTrashed()->first();
@ -100,7 +104,12 @@ class PaymentRepository extends BaseRepository {
$payment->status_id = Payment::STATUS_COMPLETED;
if (! $payment->currency_id && $client) {
$payment->currency_id = $client->company->settings->currency_id;
if(property_exists($client->settings, 'currency_id'))
$payment->currency_id = $client->settings->currency_id;
else
$payment->currency_id = $client->company->settings->currency_id;
}
$payment->save();
@ -199,8 +208,9 @@ class PaymentRepository extends BaseRepository {
public function processExchangeRates($data, $payment)
{
if(array_key_exists('exchange_rate', $data) && isset($data['exchange_rate']))
if(array_key_exists('exchange_rate', $data) && isset($data['exchange_rate']) && $data['exchange_rate'] != 1){
return $payment;
}
$client = Client::withTrashed()->find($data['client_id']);
@ -212,7 +222,6 @@ class PaymentRepository extends BaseRepository {
$exchange_rate = new CurrencyApi();
$payment->exchange_rate = $exchange_rate->exchangeRate($client_currency, $company_currency, Carbon::parse($payment->date));
// $payment->exchange_currency_id = $client_currency;
$payment->exchange_currency_id = $company_currency;
$payment->currency_id = $client_currency;
@ -221,7 +230,6 @@ class PaymentRepository extends BaseRepository {
$payment->currency_id = $company_currency;
return $payment;
}

View File

@ -226,6 +226,7 @@ class Statement
->whereIn('status_id', $this->invoiceStatuses())
->whereBetween('date', [Carbon::parse($this->options['start_date']), Carbon::parse($this->options['end_date'])])
->orderBy('due_date', 'ASC')
->orderBy('date', 'ASC')
->cursor();
}
@ -241,10 +242,10 @@ class Statement
return [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL, Invoice::STATUS_PAID];
break;
case 'paid':
return [Invoice::STATUS_PARTIAL, Invoice::STATUS_PAID];
return [Invoice::STATUS_PAID];
break;
case 'unpaid':
return [Invoice::STATUS_SENT];
return [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL];
break;
default:

View File

@ -61,7 +61,7 @@ class MarkPaid extends AbstractService
$payment->transaction_reference = ctrans('texts.manual_entry');
$payment->currency_id = $this->invoice->client->getSetting('currency_id');
$payment->is_manual = true;
if($this->invoice->company->timezone())
$payment->date = now()->addSeconds($this->invoice->company->timezone()->utc_offset)->format('Y-m-d');
@ -149,7 +149,7 @@ class MarkPaid extends AbstractService
//$payment->exchange_currency_id = $client_currency; // 23/06/2021
$payment->exchange_currency_id = $company_currency;
$payment->save();
$payment->saveQuietly();
}

View File

@ -226,7 +226,7 @@ class Design extends BaseDesign
{
if ($this->type === 'statement') {
$s_date = $this->translateDate($this->options['end_date'], $this->client->date_format(), $this->client->locale());
$s_date = $this->translateDate(now()->format($client->company->date_format()), $this->client->date_format(), $this->client->locale());
return [
['element' => 'tr', 'properties' => ['data-ref' => 'statement-label'], 'elements' => [

View File

@ -37,7 +37,7 @@ class ApplyNumber
switch ($this->client->getSetting('counter_number_applied')) {
case 'when_saved':
$quote = $this->trySaving($quote);
// $quote->number = $this->getNextQuoteNumber($this->client, $quote);
// $quote->number = $this->getNextQuoteNumber($this->client, $quote);
break;
case 'when_sent':
if ($quote->status_id == Quote::STATUS_SENT) {

View File

@ -0,0 +1,679 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Services\Report;
use App\Libraries\Currency\Conversion\CurrencyApi;
use App\Libraries\MultiDB;
use App\Models\Company;
use App\Models\Currency;
use App\Models\Expense;
use App\Models\Payment;
use App\Utils\Ninja;
use App\Utils\Number;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\App;
use League\Csv\Writer;
use Illuminate\Support\Str;
class ProfitLoss
{
private bool $is_income_billed = true;
private bool $is_expense_billed = true;
private bool $is_tax_included = true;
private $start_date;
private $end_date;
private float $income = 0;
private float $income_taxes = 0;
private float $credit = 0;
private float $credit_invoice = 0;
private float $credit_taxes = 0;
private array $invoice_payment_map = [];
private array $expenses = [];
private array $expense_break_down = [];
private array $income_map;
private array $foreign_income = [];
protected CurrencyApi $currency_api;
/*
payload variables.
start_date - Y-m-d
end_date - Y-m-d
date_range -
all
last7
last30
this_month
last_month
this_quarter
last_quarter
this_year
custom
is_income_billed - true = Invoiced || false = Payments
is_expense_billed - true = Expensed || false = Expenses marked as paid
include_tax - true tax_included || false - tax_excluded
*/
protected array $payload;
protected Company $company;
public function __construct(Company $company, array $payload)
{
$this->currency_api = new CurrencyApi();
$this->company = $company;
$this->payload = $payload;
$this->setBillingReportType();
}
public function build()
{
MultiDB::setDb($this->company->db);
if($this->is_income_billed){ //get invoiced amounts
$this->filterIncome();
}else {
//$this->filterPaymentIncome();
$this->filterInvoicePaymentIncome();
}
$this->expenseData()->buildExpenseBreakDown();
return $this;
}
public function getIncome() :float
{
return round($this->income,2);
}
public function getIncomeMap() :array
{
return $this->income_map;
}
public function getIncomeTaxes() :float
{
return round($this->income_taxes,2);
}
public function getExpenses() :array
{
return $this->expenses;
}
public function getExpenseBreakDown() :array
{
ksort($this->expense_break_down);
return $this->expense_break_down;
}
private function filterIncome()
{
$invoices = $this->invoiceIncome();
$this->foreign_income = [];
$this->income = 0;
$this->income_taxes = 0;
$this->income_map = $invoices;
foreach($invoices as $invoice){
$this->income += $invoice->net_converted_amount;
$this->income_taxes += $invoice->net_converted_taxes;
$currency = Currency::find(intval(str_replace('"','',$invoice->currency_id)));
$currency->name = ctrans('texts.currency_'.Str::slug($currency->name, '_'));
$this->foreign_income[] = ['currency' => $currency->name, 'amount' => $invoice->amount, 'total_taxes' => $invoice->total_taxes];
}
return $this;
}
private function filterInvoicePaymentIncome()
{
$this->paymentEloquentIncome();
foreach($this->invoice_payment_map as $map) {
$this->income += $map->amount_payment_paid_converted - $map->tax_amount_converted;
$this->income_taxes += $map->tax_amount_converted;
$this->credit += $map->amount_credit_paid_converted - $map->tax_amount_credit_converted;
$this->credit_taxes += $map->tax_amount_credit_converted;
}
// $invoices = $this->invoicePaymentIncome();
// $this->income = 0;
// $this->income_taxes = 0;
// $this->income_map = $invoices;
// foreach($invoices as $invoice){
// $this->income += $invoice->net_converted_amount;
// $this->income_taxes += $invoice->net_converted_taxes;
// }
return $this;
}
private function getForeignIncome() :array
{
return $this->foreign_income;
}
private function filterPaymentIncome()
{
$payments = $this->paymentIncome();
return $this;
}
/*
//returns an array of objects
=> [
{#2047
+"amount": "706.480000",
+"total_taxes": "35.950000",
+"currency_id": ""1"",
+"net_converted_amount": "670.5300000000",
+"net_converted_taxes": "10"
},
{#2444
+"amount": "200.000000",
+"total_taxes": "0.000000",
+"currency_id": ""23"",
+"net_converted_amount": "1.7129479802",
+"net_converted_taxes": "10"
},
{#2654
+"amount": "140.000000",
+"total_taxes": "40.000000",
+"currency_id": ""12"",
+"net_converted_amount": "69.3275024282",
+"net_converted_taxes": "10"
},
]
*/
private function invoiceIncome()
{
return \DB::select( \DB::raw("
SELECT
sum(invoices.amount) as amount,
sum(invoices.total_taxes) as total_taxes,
(sum(invoices.total_taxes) / IFNULL(invoices.exchange_rate, 1)) AS net_converted_taxes,
sum(invoices.amount - invoices.total_taxes) as net_amount,
IFNULL(JSON_EXTRACT( settings, '$.currency_id' ), :company_currency) AS currency_id,
(sum(invoices.amount - invoices.total_taxes) / IFNULL(invoices.exchange_rate, 1)) AS net_converted_amount
FROM clients
JOIN invoices
on invoices.client_id = clients.id
WHERE invoices.status_id IN (2,3,4)
AND invoices.company_id = :company_id
AND invoices.amount > 0
AND clients.is_deleted = 0
AND invoices.is_deleted = 0
AND (invoices.date BETWEEN :start_date AND :end_date)
GROUP BY currency_id
"), ['company_currency' => $this->company->settings->currency_id, 'company_id' => $this->company->id, 'start_date' => $this->start_date, 'end_date' => $this->end_date] );
}
/**
* The income calculation is based on the total payments received during
* the selected time period.
*
* Once we have the payments we iterate through the attached invoices and
* we also determine the total taxes paid as our
* Profit and loss statement should be net of all taxes
*
* This calculation also considers partial payments and pro rata's any taxes.
*
* This calculation also considers exchange rates and we convert (based on the payment exchange rate)
* to the native company currency.
*/
private function paymentEloquentIncome()
{
$this->invoice_payment_map = [];
Payment::where('company_id', $this->company->id)
->whereIn('status_id', [1,4,5])
->where('is_deleted', 0)
->whereBetween('date', [$this->start_date, $this->end_date])
->whereHas('client', function ($query) {
$query->where('is_deleted',0);
})
->with(['company','client'])
->cursor()
->each(function ($payment){
$company = $payment->company;
$client = $payment->client;
$map = new \stdClass;
$amount_payment_paid = 0;
$amount_credit_paid = 0;
$amount_payment_paid_converted = 0;
$amount_credit_paid_converted = 0;
$tax_amount = 0;
$tax_amount_converted = 0;
$tax_amount_credit = 0;
$tax_amount_credit_converted = $tax_amount_credit_converted = 0;
foreach($payment->paymentables as $pivot)
{
if($pivot->paymentable instanceOf \App\Models\Invoice){
$invoice = $pivot->paymentable;
$amount_payment_paid += $pivot->amount - $pivot->refunded;
$amount_payment_paid_converted += $amount_payment_paid / ($payment->exchange_rate ?: 1);
$tax_amount += ($amount_payment_paid / $invoice->amount) * $invoice->total_taxes;
$tax_amount_converted += (($amount_payment_paid / $invoice->amount) * $invoice->total_taxes) / $payment->exchange_rate;
}
if($pivot->paymentable instanceOf \App\Models\Credit){
$amount_credit_paid += $pivot->amount - $pivot->refunded;
$amount_credit_paid_converted += $amount_payment_paid / ($payment->exchange_rate ?: 1);
$tax_amount_credit += ($amount_payment_paid / $invoice->amount) * $invoice->total_taxes;
$tax_amount_credit_converted += (($amount_payment_paid / $invoice->amount) * $invoice->total_taxes) / $payment->exchange_rate;
}
}
$map->amount_payment_paid = $amount_payment_paid;
$map->amount_payment_paid_converted = $amount_payment_paid_converted;
$map->tax_amount = $tax_amount;
$map->tax_amount_converted = $tax_amount_converted;
$map->amount_credit_paid = $amount_credit_paid;
$map->amount_credit_paid_converted = $amount_credit_paid_converted;
$map->tax_amount_credit = $tax_amount_credit;
$map->tax_amount_credit_converted = $tax_amount_credit_converted;
$map->currency_id = $payment->currency_id;
$this->invoice_payment_map[] = $map;
});
return $this;
}
/**
=> [
{#2047
+"amount": "110.000000",
+"total_taxes": "10.0000000000000000",
+"net_converted_amount": "110.0000000000",
+"net_converted_taxes": "10.00000000000000000000",
+"currency_id": ""1"",
},
{#2444
+"amount": "50.000000",
+"total_taxes": "4.5454545454545455",
+"net_converted_amount": "61.1682150381",
+"net_converted_taxes": "5.56074682164393914741",
+"currency_id": ""2"",
},
]
*/
public function getCsv()
{
MultiDB::setDb($this->company->db);
App::forgetInstance('translator');
App::setLocale($this->company->locale());
$t = app('translator');
$t->replace(Ninja::transformTranslations($this->company->settings));
$csv = Writer::createFromString();
$csv->insertOne([ctrans('texts.profit_and_loss')]);
$csv->insertOne([ctrans('texts.company_name'), $this->company->present()->name()]);
$csv->insertOne([ctrans('texts.date_range'), Carbon::parse($this->start_date)->format($this->company->date_format()), Carbon::parse($this->end_date)->format($this->company->date_format())]);
//gross sales ex tax
$csv->insertOne(['--------------------']);
$csv->insertOne([ctrans('texts.total_revenue'), Number::formatMoney($this->income, $this->company)]);
//total taxes
$csv->insertOne([ctrans('texts.total_taxes'), Number::formatMoney($this->income_taxes, $this->company)]);
//expense
$csv->insertOne(['--------------------']);
foreach($this->expense_break_down as $expense_breakdown)
{
$csv->insertOne([$expense_breakdown['category_name'], Number::formatMoney($expense_breakdown['total'], $this->company)]);
}
//total expense taxes
$csv->insertOne(['--------------------']);
$csv->insertOne([ctrans('texts.total_expenses'), Number::formatMoney(array_sum(array_column($this->expense_break_down, 'total')), $this->company)]);
$csv->insertOne([ctrans('texts.total_taxes'), Number::formatMoney(array_sum(array_column($this->expense_break_down, 'tax')), $this->company)]);
$csv->insertOne(['--------------------']);
$csv->insertOne([ctrans('texts.total_profit'), Number::formatMoney($this->income - array_sum(array_column($this->expense_break_down, 'total')), $this->company)]);
//net profit
$csv->insertOne(['--------------------']);
$csv->insertOne(['']);
$csv->insertOne(['']);
$csv->insertOne([ctrans('texts.currency'), ctrans('texts.amount'), ctrans('texts.total_taxes')]);
foreach($this->foreign_income as $foreign_income)
{
$csv->insertOne([$foreign_income['currency'], ($foreign_income['amount'] - $foreign_income['total_taxes']), $foreign_income['total_taxes']]);
}
return $csv->toString();
}
private function invoicePaymentIncome()
{
return \DB::select( \DB::raw("
SELECT
sum(invoices.amount - invoices.balance) as amount,
sum(invoices.total_taxes) * ((sum(invoices.amount - invoices.balance)/invoices.amount)) as total_taxes,
(sum(invoices.amount - invoices.balance) / IFNULL(invoices.exchange_rate, 1)) AS net_converted_amount,
(sum(invoices.total_taxes) * ((sum(invoices.amount - invoices.balance)/invoices.amount)) / IFNULL(invoices.exchange_rate, 1)) AS net_converted_taxes,
IFNULL(JSON_EXTRACT( settings, '$.currency_id' ), :company_currency) AS currency_id
FROM clients
JOIN invoices
on invoices.client_id = clients.id
WHERE invoices.status_id IN (3,4)
AND invoices.company_id = :company_id
AND invoices.amount > 0
AND clients.is_deleted = 0
AND invoices.is_deleted = 0
AND (invoices.date BETWEEN :start_date AND :end_date)
GROUP BY currency_id
"), ['company_currency' => $this->company->settings->currency_id, 'company_id' => $this->company->id, 'start_date' => $this->start_date, 'end_date' => $this->end_date] );
}
/**
+"payments": "12260.870000",
+"payments_converted": "12260.870000000000",
+"currency_id": 1,
*/
private function paymentIncome()
{
return \DB::select( \DB::raw("
SELECT
SUM(coalesce(payments.amount - payments.refunded,0)) as payments,
SUM(coalesce(payments.amount - payments.refunded,0)) * IFNULL(payments.exchange_rate ,1) as payments_converted,
payments.currency_id as currency_id
FROM clients
INNER JOIN
payments ON
clients.id=payments.client_id
WHERE payments.status_id IN (1,4,5,6)
AND clients.is_deleted = false
AND payments.is_deleted = false
AND payments.company_id = :company_id
AND (payments.date BETWEEN :start_date AND :end_date)
GROUP BY currency_id
ORDER BY currency_id;
"), ['company_id' => $this->company->id, 'start_date' => $this->start_date, 'end_date' => $this->end_date]);
}
private function expenseData()
{
$expenses = Expense::where('company_id', $this->company->id)
->where('is_deleted', 0)
->withTrashed()
->whereBetween('date', [$this->start_date, $this->end_date])
->cursor();
$this->expenses = [];
foreach($expenses as $expense)
{
$map = new \stdClass;
$amount = $expense->amount;
$map->total = $expense->amount;
$map->converted_total = $converted_total = $this->getConvertedTotal($expense->amount, $expense->exchange_rate);
$map->tax = $tax = $this->getTax($expense);
$map->net_converted_total = $expense->uses_inclusive_taxes ? ( $converted_total - $tax ) : $converted_total;
$map->category_id = $expense->category_id;
$map->category_name = $expense->category ? $expense->category->name : "No Category Defined";
$map->currency_id = $expense->currency_id ?: $expense->company->settings->currency_id;
$this->expenses[] = $map;
}
return $this;
}
private function buildExpenseBreakDown()
{
$data = [];
foreach($this->expenses as $expense)
{
if(!array_key_exists($expense->category_id, $data))
$data[$expense->category_id] = [];
if(!array_key_exists('total', $data[$expense->category_id]))
$data[$expense->category_id]['total'] = 0;
if(!array_key_exists('tax', $data[$expense->category_id]))
$data[$expense->category_id]['tax'] = 0;
$data[$expense->category_id]['total'] += $expense->net_converted_total;
$data[$expense->category_id]['category_name'] = $expense->category_name;
$data[$expense->category_id]['tax'] += $expense->tax;
}
$this->expense_break_down = $data;
return $this;
}
private function getTax($expense)
{
$amount = $expense->amount;
//is amount tax
if($expense->calculate_tax_by_amount)
{
nlog($expense->tax_amount1);
nlog($expense->tax_amount2);
nlog($expense->tax_amount3);
return $expense->tax_amount1 + $expense->tax_amount2 + $expense->tax_amount3;
}
if($expense->uses_inclusive_taxes){
$inclusive = 0;
$inclusive += ($amount - ($amount / (1 + ($expense->tax_rate1 / 100))));
$inclusive += ($amount - ($amount / (1 + ($expense->tax_rate2 / 100))));
$inclusive += ($amount - ($amount / (1 + ($expense->tax_rate3 / 100))));
return round($inclusive,2);
}
$exclusive = 0;
$exclusive += $amount * ($expense->tax_rate1 / 100);
$exclusive += $amount * ($expense->tax_rate2 / 100);
$exclusive += $amount * ($expense->tax_rate3 / 100);
return $exclusive;
}
private function getConvertedTotal($amount, $exchange_rate = 1)
{
return round(($amount * $exchange_rate) ,2);
}
private function expenseCalcWithTax()
{
return \DB::select( \DB::raw("
SELECT sum(expenses.amount) as amount,
IFNULL(expenses.currency_id, :company_currency) as currency_id
FROM expenses
WHERE expenses.is_deleted = 0
AND expenses.company_id = :company_id
AND (expenses.date BETWEEN :start_date AND :end_date)
GROUP BY currency_id
"), ['company_currency' => $this->company->settings->currency_id, 'company_id' => $this->company->id, 'start_date' => $this->start_date, 'end_date' => $this->end_date] );
}
private function setBillingReportType()
{
if(array_key_exists('is_income_billed', $this->payload))
$this->is_income_billed = boolval($this->payload['is_income_billed']);
if(array_key_exists('is_expense_billed', $this->payload))
$this->is_expense_billed = boolval($this->payload['is_expense_billed']);
if(array_key_exists('include_tax', $this->payload))
$this->is_tax_included = boolval($this->payload['include_tax']);
$this->addDateRange();
return $this;
}
private function addDateRange()
{
$date_range = 'this_year';
if(array_key_exists('date_range', $this->payload))
$date_range = $this->payload['date_range'];
try{
$custom_start_date = Carbon::parse($this->payload['start_date']);
$custom_end_date = Carbon::parse($this->payload['end_date']);
}
catch(\Exception $e){
$custom_start_date = now()->startOfYear();
$custom_end_date = now();
}
switch ($date_range) {
case 'all':
$this->start_date = now()->subYears(50);
$this->end_date = now();
// return $query;
case 'last7':
$this->start_date = now()->subDays(7);
$this->end_date = now();
// return $query->whereBetween($this->date_key, [now()->subDays(7), now()])->orderBy($this->date_key, 'ASC');
case 'last30':
$this->start_date = now()->subDays(30);
$this->end_date = now();
// return $query->whereBetween($this->date_key, [now()->subDays(30), now()])->orderBy($this->date_key, 'ASC');
case 'this_month':
$this->start_date = now()->startOfMonth();
$this->end_date = now();
//return $query->whereBetween($this->date_key, [now()->startOfMonth(), now()])->orderBy($this->date_key, 'ASC');
case 'last_month':
$this->start_date = now()->startOfMonth()->subMonth();
$this->end_date = now()->startOfMonth()->subMonth()->endOfMonth();
//return $query->whereBetween($this->date_key, [now()->startOfMonth()->subMonth(), now()->startOfMonth()->subMonth()->endOfMonth()])->orderBy($this->date_key, 'ASC');
case 'this_quarter':
$this->start_date = (new \Carbon\Carbon('-3 months'))->firstOfQuarter();
$this->end_date = (new \Carbon\Carbon('-3 months'))->lastOfQuarter();
//return $query->whereBetween($this->date_key, [(new \Carbon\Carbon('-3 months'))->firstOfQuarter(), (new \Carbon\Carbon('-3 months'))->lastOfQuarter()])->orderBy($this->date_key, 'ASC');
case 'last_quarter':
$this->start_date = (new \Carbon\Carbon('-6 months'))->firstOfQuarter();
$this->end_date = (new \Carbon\Carbon('-6 months'))->lastOfQuarter();
//return $query->whereBetween($this->date_key, [(new \Carbon\Carbon('-6 months'))->firstOfQuarter(), (new \Carbon\Carbon('-6 months'))->lastOfQuarter()])->orderBy($this->date_key, 'ASC');
case 'this_year':
$this->start_date = now()->startOfYear();
$this->end_date = now();
//return $query->whereBetween($this->date_key, [now()->startOfYear(), now()])->orderBy($this->date_key, 'ASC');
case 'custom':
$this->start_date = $custom_start_date;
$this->end_date = $custom_end_date;
//return $query->whereBetween($this->date_key, [$custom_start_date, $custom_end_date])->orderBy($this->date_key, 'ASC');
default:
$this->start_date = now()->startOfYear();
$this->end_date = now();
// return $query->whereBetween($this->date_key, [now()->startOfYear(), now()])->orderBy($this->date_key, 'ASC');
}
return $this;
}
}

View File

@ -40,18 +40,6 @@ class CompanyFactory extends Factory
'default_password_timeout' => 30*60000,
'enabled_modules' => config('ninja.enabled_modules'),
'custom_fields' => (object) [
//'invoice1' => 'Custom Date|date',
// 'invoice2' => '2|switch',
// 'invoice3' => '3|',
// 'invoice4' => '4',
// 'client1'=>'1',
// 'client2'=>'2',
// 'client3'=>'3|date',
// 'client4'=>'4|switch',
// 'company1'=>'1|date',
// 'company2'=>'2|switch',
// 'company3'=>'3',
// 'company4'=>'4',
],
];
}

View File

@ -22,7 +22,7 @@ $LANG = array(
'currency_id' => 'Währung',
'size_id' => 'Firmengröße',
'industry_id' => 'Branche',
'private_notes' => 'Private Notizen',
'private_notes' => 'Interne Notizen',
'invoice' => 'Rechnung',
'client' => 'Kunde',
'invoice_date' => 'Rechnungsdatum',
@ -115,7 +115,7 @@ $LANG = array(
'upcoming_invoices' => 'Ausstehende Rechnungen',
'average_invoice' => 'Durchschnittlicher Rechnungsbetrag',
'archive' => 'Archivieren',
'delete' => 'löschen',
'delete' => 'Löschen',
'archive_client' => 'Kunde archivieren',
'delete_client' => 'Kunde löschen',
'archive_payment' => 'Zahlung archivieren',
@ -172,7 +172,7 @@ $LANG = array(
'localization' => 'Lokalisierung',
'remove_logo' => 'Logo entfernen',
'logo_help' => 'Unterstützt: JPEG, GIF und PNG',
'payment_gateway' => 'Zahlungseingang',
'payment_gateway' => 'Zahlungs-Gateway',
'gateway_id' => 'Zahlungsanbieter',
'email_notifications' => 'E-Mail Benachrichtigungen',
'email_sent' => 'Benachrichtigen, wenn eine Rechnung <strong>versendet</strong> wurde',
@ -246,7 +246,7 @@ $LANG = array(
'payment_subject' => 'Zahlungseingang',
'payment_message' => 'Vielen Dank für Ihre Zahlung von :amount.',
'email_salutation' => 'Sehr geehrte/r :name,',
'email_signature' => 'Mit freundlichen Grüßen,',
'email_signature' => 'Mit freundlichen Grüßen',
'email_from' => 'Das InvoiceNinja Team',
'invoice_link_message' => 'Um deine Kundenrechnung anzuschauen, klicke auf den folgenden Link:',
'notification_invoice_paid_subject' => 'Die Rechnung :invoice wurde von :client bezahlt.',
@ -272,13 +272,13 @@ $LANG = array(
'erase_data' => 'Ihr Konto ist nicht registriert, diese Aktion wird Ihre Daten unwiderruflich löschen.',
'password' => 'Passwort',
'pro_plan_product' => 'Pro Plan',
'pro_plan_success' => 'Danke, dass Sie Invoice Ninja\'s Pro gewählt haben!<p/>&nbsp;<br/>
'pro_plan_success' => 'Danke, dass Sie Invoice Ninja\'s Pro-Tarif gewählt haben!<p/>&nbsp;<br/>
<b>Nächste Schritte</b>Eine bezahlbare Rechnung wurde an die Mailadresse,
welche mit Ihrem Account verbunden ist, geschickt. Um alle der umfangreichen
Pro Funktionen freizuschalten, folgen Sie bitte den Anweisungen in der Rechnung um ein Jahr
die Pro Funktionen zu nutzen.
Sie finden die Rechnung nicht? Sie benötigen weitere Hilfe? Wir helfen gerne
-- schicken Sie uns doch eine Email an contact@invoice-ninja.com',
-- schicken Sie uns doch eine E-Mail an contact@invoice-ninja.com',
'unsaved_changes' => 'Es liegen ungespeicherte Änderungen vor',
'custom_fields' => 'Benutzerdefinierte Felder',
'company_fields' => 'Firmenfelder',
@ -504,7 +504,7 @@ $LANG = array(
'notification_quote_approved_subject' => 'Angebot :invoice wurde von :client angenommen.',
'notification_quote_approved' => 'Der folgende Kunde :client nahm das Angebot :invoice über :amount an.',
'resend_confirmation' => 'Bestätigungsmail erneut senden',
'confirmation_resent' => 'Bestätigungsemail wurde erneut versendet',
'confirmation_resent' => 'Bestätigungs-E-Mail wurde erneut versendet',
'gateway_help_42' => ':link zum Registrieren auf BitPay.<br/>Hinweis: benutze einen Legacy API Key, keinen API token.',
'payment_type_credit_card' => 'Kreditkarte',
'payment_type_paypal' => 'PayPal',
@ -602,7 +602,7 @@ $LANG = array(
'pro_plan_feature5' => 'Multi-Benutzer Zugriff & Aktivitätstracking',
'pro_plan_feature6' => 'Angebote & pro-forma Rechnungen erstellen',
'pro_plan_feature7' => 'Rechungstitelfelder und Nummerierung anpassen',
'pro_plan_feature8' => 'PDFs an Kunden-Emails anhängen',
'pro_plan_feature8' => 'PDFs an Kunden-E-Mails anhängen',
'resume' => 'Fortfahren',
'break_duration' => 'Pause',
'edit_details' => 'Details bearbeiten',
@ -645,8 +645,8 @@ $LANG = array(
'styles' => 'Stile',
'defaults' => 'Standards',
'margins' => 'Außenabstände',
'header' => 'Kopf',
'footer' => 'Fußzeile',
'header' => 'Header-Code',
'footer' => 'Footer-Code',
'custom' => 'Benutzerdefiniert',
'invoice_to' => 'Rechnung an',
'invoice_no' => 'Rechnung Nr.',
@ -689,9 +689,9 @@ $LANG = array(
'auto_bill' => 'Automatische Verrechnung',
'military_time' => '24-Stunden-Zeit',
'last_sent' => 'Zuletzt versendet',
'reminder_emails' => 'Erinnerungs-Emails',
'quote_reminder_emails' => 'Angebot Erinngerungs Emails',
'templates_and_reminders' => 'Vorlagen & Erinnerungen',
'reminder_emails' => 'Mahnungs-E-Mails',
'quote_reminder_emails' => 'Angebots-Erinngerungs-E-Mails',
'templates_and_reminders' => 'Vorlagen & Mahnungen',
'subject' => 'Betreff',
'body' => 'Inhalt',
'first_reminder' => 'Erste Erinnerung',
@ -889,9 +889,9 @@ $LANG = array(
'custom_invoice_charges_helps' => 'Füge ein Rechnungsgebührenfeld hinzu. Erfasse die Kosten, wenn eine neue Rechnung erstellt wird und addiere sie in den Zwischensummen der Rechnung.',
'token_expired' => 'Validierungstoken ist abgelaufen. Bitte probieren Sie es erneut.',
'invoice_link' => 'Link zur Rechnung',
'button_confirmation_message' => 'Klicke um Deine Email zu bestätigen',
'button_confirmation_message' => 'Bestätige deine E-Mail-Adresse.',
'confirm' => 'Bestätigen',
'email_preferences' => 'Email Einstellungen',
'email_preferences' => 'E-Mail-Einstellungen',
'created_invoices' => ':count Rechnung(en) erfolgreich erstellt',
'next_invoice_number' => 'Die nächste Rechnungsnummer ist :number.',
'next_quote_number' => 'Die nächste Angebotsnummer ist :number.',
@ -1048,13 +1048,13 @@ $LANG = array(
'invitation_status_sent' => 'Gesendet',
'invitation_status_opened' => 'Geöffnet',
'invitation_status_viewed' => 'Gesehen',
'email_error_inactive_client' => 'Emails können nicht zu inaktiven Kunden gesendet werden',
'email_error_inactive_contact' => 'Emails können nicht zu inaktiven Kontakten gesendet werden',
'email_error_inactive_invoice' => 'Emails können nicht zu inaktiven Rechnungen gesendet werden',
'email_error_inactive_proposal' => 'Emails können nicht für inaktive Vorschläge gesendet werden',
'email_error_user_unregistered' => 'Bitte registrieren Sie sich um Emails zu versenden',
'email_error_user_unconfirmed' => 'Bitte bestätigen Sie Ihr Konto um Emails zu senden',
'email_error_invalid_contact_email' => 'Ungültige Kontakt Email Adresse',
'email_error_inactive_client' => 'E-Mails können nicht zu inaktiven Kunden gesendet werden',
'email_error_inactive_contact' => 'E-Mails können nicht zu inaktiven Kontakten gesendet werden',
'email_error_inactive_invoice' => 'E-Mails können nicht zu inaktiven Rechnungen gesendet werden',
'email_error_inactive_proposal' => 'E-Mails können nicht für inaktive Vorschläge gesendet werden',
'email_error_user_unregistered' => 'Bitte registrieren Sie sich um E-Mails zu versenden',
'email_error_user_unconfirmed' => 'Bitte bestätigen Sie Ihr Konto um E-Mails zu versenden',
'email_error_invalid_contact_email' => 'Ungültige Kontakt-E-Mail-Adresse',
'navigation' => 'Navigation',
'list_invoices' => 'Rechnungen anzeigen',
@ -1316,7 +1316,7 @@ Sobald Sie die Beträge erhalten haben, kommen Sie bitte wieder zurück zu diese
'enabled' => 'Aktiviert',
'paypal' => 'PayPal',
'braintree_enable_paypal' => 'PayPal Zahlungen mittels BrainTree aktivieren',
'braintree_paypal_disabled_help' => 'Das PayPal Gateway bearbeitet PayPal-Zahlungen',
'braintree_paypal_disabled_help' => 'Das PayPal-Gateway bearbeitet gerade PayPal-Zahlungen',
'braintree_paypal_help' => 'Sie müssen auch :link',
'braintree_paypal_help_link_text' => 'PayPal Konto mit BrainTree verknüpfen',
'token_billing_braintree_paypal' => 'Zahlungsdetails speichern',
@ -1339,7 +1339,7 @@ Sobald Sie die Beträge erhalten haben, kommen Sie bitte wieder zurück zu diese
'wepay_description_help' => 'Zweck des Kontos.',
'wepay_tos_agree' => 'Ich stimme den :link zu',
'wepay_tos_link_text' => 'WePay Servicebedingungen',
'resend_confirmation_email' => 'Bestätigungsemail nochmal senden',
'resend_confirmation_email' => 'Bestätigungs-E-Mail nochmal senden',
'manage_account' => 'Account managen',
'action_required' => 'Handeln erforderlich',
'finish_setup' => 'Setup abschliessen',
@ -1846,7 +1846,7 @@ Sobald Sie die Beträge erhalten haben, kommen Sie bitte wieder zurück zu diese
'buy_now_buttons_disabled' => 'Diese Funktion setzt voraus, dass ein Produkt erstellt und ein Zahlungs-Gateway konfiguriert wurde.',
'enable_buy_now_buttons_help' => 'Aktiviere Unterstützung für "Kaufe jetzt"-Buttons',
'changes_take_effect_immediately' => 'Anmerkung: Änderungen treten sofort in Kraft',
'wepay_account_description' => 'Zahlungsanbieter für Invoice Ninja',
'wepay_account_description' => 'Zahlungs-Gateway für Invoice Ninja',
'payment_error_code' => 'Bei der Bearbeitung Ihrer Zahlung [:code] gab es einen Fehler. Bitte versuchen Sie es später erneut.',
'standard_fees_apply' => 'Standardgebühren werden erhoben: 2,9% + 0,25€ pro erfolgreicher Belastung bei nicht-europäischen Kreditkarten und 1,4% + 0,25€ bei europäischen Kreditkarten.',
'limit_import_rows' => 'Daten müssen in Stapeln von :count Zeilen oder weniger importiert werden',
@ -1986,38 +1986,6 @@ Sobald Sie die Beträge erhalten haben, kommen Sie bitte wieder zurück zu diese
'authorization' => 'Genehmigung',
'signed' => 'unterzeichnet',
// BlueVine
'bluevine_promo' => 'Factoring und Bonitätsauskünfte von BlueVine bestellen.',
'bluevine_modal_label' => 'Anmelden mit BlueVine',
'bluevine_modal_text' => '<h3>Schnelle Finanzierung ohne Papierkram.</h3>
<ul><li>Flexible Bonitätsprüfung und Factoring.</li></ul>',
'bluevine_create_account' => 'Konto erstellen',
'quote_types' => 'Angebot erhalten für',
'invoice_factoring' => 'Factoring',
'line_of_credit' => 'Bonitätsprüfung',
'fico_score' => 'Ihre FICO Bewertung',
'business_inception' => 'Gründungsdatum',
'average_bank_balance' => 'durchschnittlicher Kontostand',
'annual_revenue' => 'Jahresertrag',
'desired_credit_limit_factoring' => 'Gewünschtes Factoring Limit',
'desired_credit_limit_loc' => 'gewünschter Kreditrahmen',
'desired_credit_limit' => 'gewünschtes Kreditlimit',
'bluevine_credit_line_type_required' => 'Sie müssen mindestens eine auswählen',
'bluevine_field_required' => 'Dies ist ein Pflichtfeld',
'bluevine_unexpected_error' => 'Ein unerwarteter Fehler ist aufgetreten.',
'bluevine_no_conditional_offer' => 'Mehr Information ist vonnöten um ein Angebot erstellen zu können. Bitte klicken Sie unten auf Weiter.',
'bluevine_invoice_factoring' => 'Factoring',
'bluevine_conditional_offer' => 'Freibleibendes Angebot',
'bluevine_credit_line_amount' => 'Kreditline',
'bluevine_advance_rate' => 'Finanzierungsanteil',
'bluevine_weekly_discount_rate' => 'Wöchentlicher Rabatt',
'bluevine_minimum_fee_rate' => 'Minimale Gebühr',
'bluevine_line_of_credit' => 'Kreditline',
'bluevine_interest_rate' => 'Zinssatz',
'bluevine_weekly_draw_rate' => 'Wöchtentliche Rückzahlungsquote',
'bluevine_continue' => 'Weiter zu BlueVine',
'bluevine_completed' => 'BlueVine Anmeldung abgeschlossen',
'vendor_name' => 'Lieferant',
'entity_state' => 'Status',
'client_created_at' => 'Erstellungsdatum',
@ -2286,8 +2254,8 @@ Sobald Sie die Beträge erhalten haben, kommen Sie bitte wieder zurück zu diese
'plan_price' => 'Tarifkosten',
'wrong_confirmation' => 'Falscher Bestätigungscode',
'oauth_taken' => 'Dieses Konto ist bereits registriert',
'emailed_payment' => 'Zahlungs eMail erfolgreich gesendet',
'email_payment' => 'Sende Zahlungs eMail',
'emailed_payment' => 'Zahlungs-E-Mail erfolgreich gesendet',
'email_payment' => 'Sende Zahlungs-E-Mail',
'invoiceplane_import' => 'Benutzer :link für die Datenmigration von InvoicePlane.',
'duplicate_expense_warning' => 'Achtung: :link evtl. schon vorhanden.',
'expense_link' => 'Ausgabe',
@ -2568,7 +2536,7 @@ Sobald Sie die Beträge erhalten haben, kommen Sie bitte wieder zurück zu diese
'time_hr' => 'Stunde',
'time_hrs' => 'Stunden',
'clear' => 'Löschen',
'warn_payment_gateway' => 'Hinweis: Die Annahme von Online-Zahlungen erfordert einen Zahlungsanbieter. Zum hinzufügen :link.',
'warn_payment_gateway' => 'Hinweis: Die Annahme von Online-Zahlungen erfordert ein Zahlungs-Gateway. Zum hinzufügen :link.',
'task_rate' => 'Kosten für Tätigkeit',
'task_rate_help' => 'Legen Sie den Standardtarif für fakturierte Aufgaben fest.',
'past_due' => 'Überfällig',
@ -2687,10 +2655,10 @@ Sobald Sie die Beträge erhalten haben, kommen Sie bitte wieder zurück zu diese
'module_task' => 'Aufgaben und Projekte',
'module_expense' => 'Ausgaben & Lieferanten',
'module_ticket' => 'Tickets',
'reminders' => 'Erinnerungen',
'send_client_reminders' => 'E-Mail Erinnerungen versenden',
'reminders' => 'Mahnungen',
'send_client_reminders' => 'Mahnung per E-Mail versenden',
'can_view_tasks' => 'Aufgaben sind im Portal sichtbar',
'is_not_sent_reminders' => 'Erinnerungen werden nicht gesendet',
'is_not_sent_reminders' => 'Mahnungen werden nicht versendet',
'promotion_footer' => 'Ihre Promotion läuft bald ab, :link, um jetzt ein Upgrade durchzuführen.',
'unable_to_delete_primary' => 'Hinweis: Um diese Firma zu löschen, löschen Sie zunächst alle verknüpften Unternehmen.',
'please_register' => 'Bitte erstellen Sie sich einen Account',
@ -2853,7 +2821,7 @@ Sobald Sie die Beträge erhalten haben, kommen Sie bitte wieder zurück zu diese
'accept' => 'Akzeptieren',
'accepted_terms' => 'Die neuesten Nutzungsbedingungen wurden akzeptiert.',
'invalid_url' => 'Ungültige URL',
'workflow_settings' => 'Workflow Einstellungen',
'workflow_settings' => 'Workflow-Einstellungen',
'auto_email_invoice' => 'Automatische Email',
'auto_email_invoice_help' => 'Senden Sie wiederkehrende Rechnungen automatisch per E-Mail, wenn sie erstellt werden.',
'auto_archive_invoice' => 'Automatisches Archiv',
@ -2872,7 +2840,7 @@ Sobald Sie die Beträge erhalten haben, kommen Sie bitte wieder zurück zu diese
'purge_client_warning' => 'Alle zugehörigen Datensätze (Rechnungen, Aufgaben, Ausgaben, Dokumente usw.) werden ebenfalls gelöscht.',
'clone_product' => 'Produkt duplizieren',
'item_details' => 'Artikeldetails',
'send_item_details_help' => 'Senden Sie die Einzelpostendetails an das Zahlungsportal.',
'send_item_details_help' => 'Senden Sie die Einzelpostendetails an das Zahlungs-Gateway.',
'view_proposal' => 'Vorschlag ansehen',
'view_in_portal' => 'Im Portal anzeigen',
'cookie_message' => 'Diese Website verwendet Cookies, um sicherzustellen, dass Sie das beste Ergebnis auf unserer Website erzielen.',
@ -2974,7 +2942,7 @@ Sobald Sie die Beträge erhalten haben, kommen Sie bitte wieder zurück zu diese
'size' => 'Größe',
'net' => 'Netto',
'show_tasks' => 'Aufgaben anzeigen',
'email_reminders' => 'E-Mail Erinnerungen',
'email_reminders' => 'Mahnungs-E-Mail',
'reminder1' => 'Erste Erinnerung',
'reminder2' => 'Zweite Erinnerung',
'reminder3' => 'Dritte Erinnerung',
@ -3217,7 +3185,7 @@ Sobald Sie die Beträge erhalten haben, kommen Sie bitte wieder zurück zu diese
'surcharge_field' => 'Zuschlagsfeld',
'company_value' => 'Firmenwert',
'credit_field' => 'Kredit-Feld',
'payment_field' => 'Zahlungs-Feld',
'payment_field' => 'Zahlungsfeld',
'group_field' => 'Gruppen-Feld',
'number_counter' => 'Nummernzähler',
'number_pattern' => 'Nummernschema',
@ -3256,7 +3224,7 @@ Sobald Sie die Beträge erhalten haben, kommen Sie bitte wieder zurück zu diese
'ocde' => 'Code',
'date_format' => 'Datumsformat',
'datetime_format' => 'Datums-/Zeitformat',
'send_reminders' => 'Erinnerungen senden',
'send_reminders' => 'Mahnung senden',
'timezone' => 'Zeitzone',
'filtered_by_group' => 'Gefiltert nach Gruppe',
'filtered_by_invoice' => 'Gefiltert nach Rechnung',
@ -3274,7 +3242,7 @@ Sobald Sie die Beträge erhalten haben, kommen Sie bitte wieder zurück zu diese
'upload_logo' => 'Logo hochladen',
'uploaded_logo' => 'Logo erfolgreich hochgeladen',
'saved_settings' => 'Einstellungen erfolgreich gespeichert',
'device_settings' => 'Geräteeinstellungen',
'device_settings' => 'Geräte-Einstellungen',
'credit_cards_and_banks' => 'Kreditkarten & Banken',
'price' => 'Preis',
'email_sign_up' => 'E-Mail-Registrierung',
@ -3454,9 +3422,9 @@ Sobald Sie die Beträge erhalten haben, kommen Sie bitte wieder zurück zu diese
'default_tax_rate_2' => 'Standard-Steuersatz 2',
'default_tax_name_3' => 'Standard-Steuername 3',
'default_tax_rate_3' => 'Standard-Steuersatz 3',
'email_subject_invoice' => 'EMail Rechnung Betreff',
'email_subject_quote' => 'EMail Angebot Betreff',
'email_subject_payment' => 'EMail Zahlung Betreff',
'email_subject_invoice' => 'E-Mail Rechnung Betreff',
'email_subject_quote' => 'E-Mail Angebot Betreff',
'email_subject_payment' => 'E-Mail Zahlung Betreff',
'switch_list_table' => 'Listenansicht umschalten',
'client_city' => 'Kunden-Stadt',
'client_state' => 'Kunden-Bundesland/Kanton',
@ -3544,7 +3512,7 @@ Sobald Sie die Beträge erhalten haben, kommen Sie bitte wieder zurück zu diese
'clone_to_credit' => 'Duplizieren in Gutschrift',
'emailed_credit' => 'Guthaben erfolgreich per E-Mail versendet',
'marked_credit_as_sent' => 'Guthaben erfolgreich als versendet markiert',
'email_subject_payment_partial' => 'EMail Teilzahlung Betreff',
'email_subject_payment_partial' => 'E-Mail Teilzahlung Betreff',
'is_approved' => 'Wurde angenommen',
'migration_went_wrong' => 'Upps, da ist etwas schiefgelaufen! Stellen Sie sicher, dass Sie InvoiceNinja v5 richtig eingerichtet haben, bevor Sie die Migration starten.',
'cross_migration_message' => 'Kontoübergreifende Migration ist nicht erlaubt. Mehr Informationen finden Sie hier:
@ -3591,7 +3559,7 @@ https://invoiceninja.github.io/docs/migration/#troubleshooting',
'search_users' => 'Suche Benutzer',
'search_tax_rates' => 'Suche Steuersatz',
'search_tasks' => 'Suche Aufgaben',
'search_settings' => 'Suche Einstellungen',
'search_settings' => 'Such-Einstellungen',
'search_projects' => 'Suche nach Projekten',
'search_expenses' => 'Suche Ausgaben',
'search_payments' => 'Suche Zahlungen',
@ -3652,7 +3620,7 @@ https://invoiceninja.github.io/docs/migration/#troubleshooting',
'gross' => 'Gesamtbetrag',
'net_amount' => 'Netto Betrag',
'net_balance' => 'Netto Betrag',
'client_settings' => 'Kundeneinstellungen',
'client_settings' => 'Kunden-Einstellungen',
'selected_invoices' => 'Ausgewählte Rechnungen',
'selected_payments' => 'Ausgewählte Zahlungen',
'selected_quotes' => 'Ausgewählte Angebote',
@ -3724,16 +3692,16 @@ https://invoiceninja.github.io/docs/migration/#troubleshooting',
'mark_invoiceable_help' => 'Ermögliche diese Ausgabe in Rechnung zu stellen',
'add_documents_to_invoice_help' => 'Dokumente sichtbar machen',
'convert_currency_help' => 'Wechselkurs setzen',
'expense_settings' => 'Ausgaben Einstellungen',
'expense_settings' => 'Ausgaben-Einstellungen',
'clone_to_recurring' => 'Duplizieren zu Widerkehrend',
'crypto' => 'Verschlüsselung',
'user_field' => 'Benutzer Feld',
'user_field' => 'Benutzerfeld',
'variables' => 'Variablen',
'show_password' => 'Zeige Passwort',
'hide_password' => 'Verstecke Passwort',
'copy_error' => 'Kopier Fehler',
'capture_card' => 'Zahlungsmittel für die weitere Verwendung speichern',
'auto_bill_enabled' => 'Automatische Rechnungsstellung aktivieren',
'auto_bill_enabled' => 'Automatische Bezahlung aktivieren',
'total_taxes' => 'Gesamt Steuern',
'line_taxes' => 'Belegposition Steuer',
'total_fields' => 'Gesamt Felder',
@ -3741,7 +3709,7 @@ https://invoiceninja.github.io/docs/migration/#troubleshooting',
'started_recurring_invoice' => 'Wiederkehrende Rechnung erfolgreich gestartet',
'resumed_recurring_invoice' => 'Wiederkehrende Rechnung erfolgreich fortgesetzt',
'gateway_refund' => 'Zahlungsanbieter Rückerstattung',
'gateway_refund_help' => 'Bearbeite die Rückerstattung über den Zahlungsanbieter',
'gateway_refund_help' => 'Rückerstattung über das Zahlungs-Gateway abwickeln',
'due_date_days' => 'Fälligkeitsdatum',
'paused' => 'Pausiert',
'day_count' => 'Tag :count',
@ -3785,7 +3753,7 @@ https://invoiceninja.github.io/docs/migration/#troubleshooting',
'invoice_task_timelog_help' => 'Zeitdetails in der Rechnungsposition ausweisen',
'auto_start_tasks_help' => 'Beginne Aufgabe vor dem Speichern',
'configure_statuses' => 'Stati bearbeiten',
'task_settings' => 'Aufgaben Einstellungen',
'task_settings' => 'Aufgaben-Einstellungen',
'configure_categories' => 'Kategorien bearbeiten',
'edit_expense_category' => 'Ausgaben Kategorie bearbeiten',
'removed_expense_category' => 'Ausgaben Kategorie erfolgreich entfernt',
@ -3924,7 +3892,7 @@ https://invoiceninja.github.io/docs/migration/#troubleshooting',
'before_taxes' => 'Vor Steuern',
'after_taxes' => 'Nach Steuern',
'color' => 'Farbe',
'show' => 'anzeigen',
'show' => 'Anzeigen',
'empty_columns' => 'Leere Spalten',
'project_name' => 'Projektname',
'counter_pattern_error' => 'Um :client_counter zu verwenden, fügen Sie bitte entweder :client_number oder :client_id_number hinzu, um Konflikte zu vermeiden',
@ -4180,7 +4148,7 @@ https://invoiceninja.github.io/docs/migration/#troubleshooting',
'count_days' => ':count Tage',
'web_session_timeout' => 'Web-Sitzungs-Timeout',
'security_settings' => 'Sicherheitseinstellungen',
'resend_email' => 'Bestätigungsemail erneut versenden ',
'resend_email' => 'Bestätigungs-E-Mail erneut versenden ',
'confirm_your_email_address' => 'Bitte bestätigen Sie Ihre E-Mail-Adresse',
'freshbooks' => 'FreshBooks',
'invoice2go' => 'Invoice2go',
@ -4195,7 +4163,7 @@ https://invoiceninja.github.io/docs/migration/#troubleshooting',
'billing_coupon_notice' => 'Ihr Rabatt wird an der Kasse abgezogen.',
'use_last_email' => 'Vorherige E-Mail benutzen',
'activate_company' => 'Unternehmen aktivieren',
'activate_company_help' => 'Aktivieren sie Email, wiederkehrende Rechnungen und Benachrichtigungen',
'activate_company_help' => 'E-Mails, wiederkehrende Rechnungen und Benachrichtigungen aktivieren',
'an_error_occurred_try_again' => 'Ein Fehler ist aufgetreten, bitte versuchen Sie es erneut.',
'please_first_set_a_password' => 'Bitte vergeben Sie zuerst ein Passwort.',
'changing_phone_disables_two_factor' => 'Achtung: Das Ändern deiner Telefonnummer wird die Zwei-Faktor-Authentifizierung deaktivieren',
@ -4269,7 +4237,7 @@ https://invoiceninja.github.io/docs/migration/#troubleshooting',
'user_duplicate_error' => 'Derselbe Benutzer kann nicht derselben Firma hinzugefügt werden',
'user_cross_linked_error' => 'Der Benutzer ist vorhanden, kann aber nicht mit mehreren Konten verknüpft werden',
'ach_verification_notification_label' => 'ACH-Verifizierung',
'ach_verification_notification' => 'Für die Verbindung von Bankkonten ist eine Überprüfung erforderlich. Das Zahlungsgateway sendet zu diesem Zweck automatisch zwei kleine Einzahlungen. Es dauert 1-2 Werktage, bis diese Einzahlungen auf dem Online-Kontoauszug des Kunden erscheinen.',
'ach_verification_notification' => 'Für die Verbindung von Bankkonten ist eine Überprüfung erforderlich. Das Zahlungs-Gateway sendet zu diesem Zweck automatisch zwei kleine Einzahlungen. Es dauert 1-2 Werktage, bis diese Einzahlungen auf dem Online-Kontoauszug des Kunden erscheinen.',
'login_link_requested_label' => 'Anmeldelink angefordert',
'login_link_requested' => 'Es gab eine Aufforderung, sich über einen Link anzumelden. Wenn Sie dies nicht angefordert haben, können Sie es ignorieren.',
'invoices_backup_subject' => 'Ihre Rechnungen stehen zum Download bereit',
@ -4281,7 +4249,7 @@ https://invoiceninja.github.io/docs/migration/#troubleshooting',
'company_import_failure_subject' => 'Fehler beim Importieren von :company',
'company_import_failure_body' => 'Beim Importieren der Unternehmensdaten ist ein Fehler aufgetreten, die Fehlermeldung lautete:',
'recurring_invoice_due_date' => 'Fälligkeitsdatum',
'amount_cents' => 'Betrag in Pfennigen, Pence oder Cents',
'amount_cents' => 'Betrag in Pennies, Pence oder Cent, d. h. für $0.10 bitte 10 eingeben',
'default_payment_method_label' => 'Standard Zahlungsart',
'default_payment_method' => 'Machen Sie dies zu Ihrer bevorzugten Zahlungsmethode',
'already_default_payment_method' => 'Dies ist die von Ihnen bevorzugte Art der Bezahlung.',
@ -4363,7 +4331,7 @@ https://invoiceninja.github.io/docs/migration/#troubleshooting',
'show_pdf_preview_help' => 'PDF-Vorschau bei der Bearbeitung von Rechnungen anzeigen',
'print_pdf' => 'PDF drucken',
'remind_me' => 'Erinnere mich',
'instant_bank_pay' => 'Instant Bank Pay',
'instant_bank_pay' => 'Sofortige Banküberweisung',
'click_selected' => 'Ausgewähltes anklicken',
'hide_preview' => 'Vorschau ausblenden',
'edit_record' => 'Datensatz bearbeiten',
@ -4380,8 +4348,8 @@ https://invoiceninja.github.io/docs/migration/#troubleshooting',
'persist_ui_help' => 'UI-Status lokal speichern, damit die Anwendung an der letzten Position startet (Deaktivierung kann die Leistung verbessern)',
'client_postal_code' => 'Postleitzahl des Kunden',
'client_vat_number' => 'Umsatzsteuer-Identifikationsnummer des Kunden',
'has_tasks' => 'Has Tasks',
'registration' => 'Registration',
'has_tasks' => 'Hat Aufgaben',
'registration' => 'Registrierung',
'unauthorized_stripe_warning' => 'Bitte autorisieren Sie Stripe zur Annahme von Online-Zahlungen.',
'fpx' => 'FPX',
'update_all_records' => 'Alle Datensätze aktualisieren',
@ -4389,14 +4357,14 @@ https://invoiceninja.github.io/docs/migration/#troubleshooting',
'updated_company' => 'Unternehmen wurde erfolgreich aktualisiert',
'kbc' => 'KBC',
'why_are_you_leaving' => 'Helfen Sie uns, uns zu verbessern, indem Sie uns sagen, warum (optional)',
'webhook_success' => 'Webhook Success',
'webhook_success' => 'Webhook erfolgreich',
'error_cross_client_tasks' => 'Die Aufgaben müssen alle zum selben Kunden gehören',
'error_cross_client_expenses' => 'Die Ausgaben müssen alle zu demselben Kunden gehören',
'app' => 'App',
'for_best_performance' => 'Für die beste Leistung laden Sie die App herunter :app',
'bulk_email_invoice' => 'Email Invoice',
'bulk_email_quote' => 'Email Quote',
'bulk_email_credit' => 'Email Credit',
'bulk_email_invoice' => 'Email Rechnung',
'bulk_email_quote' => 'Angebot per E-Mail senden',
'bulk_email_credit' => 'Guthaben per E-Mail senden',
'removed_recurring_expense' => 'Erfolgreich wiederkehrende Ausgaben entfernt',
'search_recurring_expense' => 'Wiederkehrende Ausgaben suchen',
'search_recurring_expenses' => 'Wiederkehrende Ausgaben suchen',
@ -4404,18 +4372,18 @@ https://invoiceninja.github.io/docs/migration/#troubleshooting',
'include_drafts' => 'Entwürfe einschließen',
'include_drafts_help' => 'Entwürfe von Aufzeichnungen in Berichte einbeziehen',
'is_invoiced' => 'Ist in Rechnung gestellt',
'change_plan' => 'Change Plan',
'change_plan' => 'Tarif ändern',
'persist_data' => 'Daten aufbewahren',
'customer_count' => 'Kundenzahl',
'verify_customers' => 'Kunden überprüfen',
'google_analytics_tracking_id' => 'Google Analytics Tracking ID',
'decimal_comma' => 'Decimal Comma',
'decimal_comma' => 'Dezimaltrennzeichen',
'use_comma_as_decimal_place' => 'Komma als Dezimalstelle in Formularen verwenden',
'select_method' => 'Select Method',
'select_platform' => 'Select Platform',
'select_method' => 'Methode auswählen',
'select_platform' => 'Plattform auswählen',
'use_web_app_to_connect_gmail' => 'Bitte verwenden Sie die Web-App, um sich mit Gmail zu verbinden',
'expense_tax_help' => 'Postensteuersätze sind deaktiviert',
'enable_markdown' => 'Enable Markdown',
'enable_markdown' => 'Markdown verwenden',
'enable_markdown_help' => 'Konvertierung von Markdown in HTML in der PDF-Datei',
'add_second_contact' => 'Zweiten Kontakt hinzufügen',
'previous_page' => 'Vorherige Seite',
@ -4462,36 +4430,36 @@ https://invoiceninja.github.io/docs/migration/#troubleshooting',
'include_deleted_clients_help' => 'Datensätze von gelöschten Kunden laden',
'step_1_sign_in' => 'Schritt 1: Registrieren',
'step_2_authorize' => 'Schritt 2: autorisieren',
'account_id' => 'Account ID',
'account_id' => 'Konto-ID',
'migration_not_yet_completed' => 'Die Migration ist noch nicht abgeschlossen',
'show_task_end_date' => 'Ende der Aufgabe anzeigen',
'show_task_end_date_help' => 'Aktivieren Sie die Angabe des Enddatums der Aufgabe',
'gateway_setup' => 'Gateway-Einstellungen',
'preview_sidebar' => 'Vorschau der Seitenleiste',
'years_data_shown' => 'Years Data Shown',
'years_data_shown' => 'Daten für wie viele Jahre anzeigen?',
'ended_all_sessions' => 'alle Sitzungen erfolgreich beendet',
'end_all_sessions' => 'Alle Sitzungen beenden',
'count_session' => '1 Session',
'count_sessions' => ':count Sessions',
'count_session' => '1 Sitzung',
'count_sessions' => ':count Sitzungen',
'invoice_created' => 'Rechnung erstellt',
'quote_created' => 'Angebot erstellt',
'credit_created' => 'Gutschrift erstellt',
'enterprise' => 'Enterprise',
'invoice_item' => 'Invoice Item',
'quote_item' => 'Quote Item',
'order' => 'Order',
'search_kanban' => 'Search Kanban',
'search_kanbans' => 'Search Kanban',
'move_top' => 'Nach oben bewegen',
'move_up' => 'Nach unten bewegen',
'move_down' => 'Move Down',
'move_bottom' => 'Move Bottom',
'invoice_item' => 'Rechnungsposition',
'quote_item' => 'Angebotsposition',
'order' => 'Bestellung',
'search_kanban' => 'Kanban auswählen',
'search_kanbans' => 'Kanban auswählen',
'move_top' => 'Ganz nach oben verschieben',
'move_up' => 'Nach oben verschieben',
'move_down' => 'Nach unten verschieben',
'move_bottom' => 'Ganz nach unten verschieben',
'body_variable_missing' => 'Fehler: das benutzerdefinierte E-Mail Template muss die :body Variable beinhalten',
'add_body_variable_message' => 'bitte stelle sicher das die :body Variable eingefügt ist',
'view_date_formats' => 'View Date Formats',
'is_viewed' => 'Is Viewed',
'view_date_formats' => 'Zeige Datumsformate',
'is_viewed' => 'Ist angesehen',
'letter' => 'Letter',
'legal' => 'Legal',
'legal' => 'Rechtliches',
'page_layout' => 'Seiten Layout',
'portrait' => 'Hochformat',
'landscape' => 'Querformat',
@ -4499,31 +4467,31 @@ https://invoiceninja.github.io/docs/migration/#troubleshooting',
'upgrade_to_paid_plan' => 'Führen Sie ein Upgrade auf einen kostenpflichtigen Plan durch, um die erweiterten Einstellungen zu aktivieren',
'invoice_payment_terms' => 'Zahlungsbedingungen für Rechnungen',
'quote_valid_until' => 'Angebot gültig bis',
'no_headers' => 'No Headers',
'add_header' => 'Add Header',
'remove_header' => 'Remove Header',
'return_url' => 'Return URL',
'rest_method' => 'REST Method',
'header_key' => 'Header Key',
'header_value' => 'Header Value',
'recurring_products' => 'Recurring Products',
'promo_discount' => 'Promo Discount',
'allow_cancellation' => 'Allow Cancellation',
'per_seat_enabled' => 'Per Seat Enabled',
'max_seats_limit' => 'Max Seats Limit',
'trial_enabled' => 'Trial Enabled',
'trial_duration' => 'Trial Duration',
'allow_query_overrides' => 'Allow Query Overrides',
'allow_plan_changes' => 'Allow Plan Changes',
'no_headers' => 'Keine Header',
'add_header' => 'Header hinzufügen',
'remove_header' => 'Kopfzeile entfernen',
'return_url' => 'Return-URL',
'rest_method' => 'REST-Methode',
'header_key' => 'Header-Key',
'header_value' => 'Header-Wert',
'recurring_products' => 'Wiederkehrende Produkte',
'promo_discount' => 'Promo-Rabatt',
'allow_cancellation' => 'Ermögliche Storno',
'per_seat_enabled' => 'Pro Platz Aktiviert',
'max_seats_limit' => 'Max. Plätze Limit',
'trial_enabled' => 'Testversion aktiv',
'trial_duration' => 'Testzeitraum',
'allow_query_overrides' => 'Überschreiben von Abfragen zulassen',
'allow_plan_changes' => 'Planänderungen zulassen',
'plan_map' => 'Plan Map',
'refund_period' => 'Refund Period',
'webhook_configuration' => 'Webhook Configuration',
'purchase_page' => 'Purchase Page',
'refund_period' => 'Erstattungszeitraum',
'webhook_configuration' => 'Webhook-Konfiguration',
'purchase_page' => 'Kauf-Seite',
'email_bounced' => 'E-Mail zurückgesendet',
'email_spam_complaint' => 'Spam Complaint',
'email_spam_complaint' => 'Spam-Beschwerde',
'email_delivery' => 'E-Mail-Zustellung',
'webhook_response' => 'Webhook Response',
'pdf_response' => 'PDF Response',
'webhook_response' => 'Webhook-Antwort',
'pdf_response' => 'PDF-Antwort',
'authentication_failure' => 'Authentifizierungsfehler',
'pdf_failed' => 'PDF fehgeschlagen',
'pdf_success' => 'PDF erfolgreich',
@ -4532,7 +4500,7 @@ https://invoiceninja.github.io/docs/migration/#troubleshooting',
'html_mode_help' => 'Vorschau von Aktualisierungen schneller, aber weniger genau',
'status_color_theme' => 'Status Farbschema',
'load_color_theme' => 'lade Farbschema',
'lang_Estonian' => 'Estonian',
'lang_Estonian' => 'estnisch',
'marked_credit_as_paid' => 'Guthaben erfolgreich als bezahlt markiert',
'marked_credits_as_paid' => 'Erfolgreich Kredite als bezahlt markiert',
'wait_for_loading' => 'Daten werden geladen - bitte warten Sie, bis der Vorgang abgeschlossen ist',
@ -4546,7 +4514,69 @@ https://invoiceninja.github.io/docs/migration/#troubleshooting',
'activity_123' => ':user löschte wiederkehrende Ausgabe :recurring_expense',
'activity_124' => ':user stellte wiederkehrende Ausgabe :recurring_expense wieder her',
'fpx' => "FPX",
'to_view_entity_set_password' => 'Um die :entity zu sehen, müssen Sie ein Passwort festlegen.',
'unsubscribe' => 'Deabonnieren',
'unsubscribed' => 'Deabonniert',
'unsubscribed_text' => 'Du erhältst nun keine Benachrichtigungen für dieses Dokument mehr.',
'client_shipping_state' => 'Liefer-Region Kunde',
'client_shipping_city' => 'Lieferort Kunde',
'client_shipping_postal_code' => 'Liefer-PLZ Kunde',
'client_shipping_country' => 'Kunde Lieferung LAND',
'load_pdf' => 'PDF laden',
'start_free_trial' => 'Kostenlose Testversion starten',
'start_free_trial_message' => 'Teste den Pro-Tarif GRATIS für 14 Tage',
'due_on_receipt' => 'Fällig bei Erhalt',
'is_paid' => 'Ist bezahlt',
'age_group_paid' => 'Bezahlt',
'id' => 'ID',
'convert_to' => 'Umwandeln in',
'client_currency' => 'Kundenwährung',
'company_currency' => 'Firmenwährung',
'custom_emails_disabled_help' => 'Um Spam zu verhindern braucht es ein Upgrade zu einem bezahlten Account um das E-Mail anzupassen.',
'upgrade_to_add_company' => 'Upgrade deinen Tarif um weitere Firmen hinzuzufügen',
'file_saved_in_downloads_folder' => 'Die Datei wurde im Downloads-Ordner gespeichert',
'small' => 'Klein',
'quotes_backup_subject' => 'Deine Angebote stehen zum Download bereit',
'credits_backup_subject' => 'Deine Guthaben stehen zum Download bereit',
'document_download_subject' => 'Deine Dokumente stehen zum Download bereit',
'reminder_message' => 'Mahnung für Rechnung :number über :balance',
'gmail_credentials_invalid_subject' => 'Senden mit ungültigen GMail-Anmeldedaten',
'gmail_credentials_invalid_body' => 'Ihre GMail-Anmeldedaten sind nicht korrekt. Bitte melden Sie sich im Administratorportal an und navigieren Sie zu Einstellungen > Benutzerdetails und trennen Sie Ihr GMail-Konto und verbinden Sie es erneut. Wir werden Ihnen diese Benachrichtigung täglich senden, bis das Problem behoben ist',
'notification_invoice_sent' => 'Rechnung :invoice über :amount wurde an den Kunden :client versendet.',
'total_columns' => 'Felder insgesamt',
'view_task' => 'Aufgabe anzeugen',
'cancel_invoice' => 'Abbrechen',
'changed_status' => 'Erfolgreich Aufgabenstatus geändert',
'change_status' => 'Status ändern',
'enable_touch_events' => 'Touchscreen-Modus aktivieren',
'enable_touch_events_help' => 'Scrollen durch wischen',
'after_saving' => 'Nach dem Speichern',
'view_record' => 'Datensatz anzeigen',
'enable_email_markdown' => 'Markdown in E-Mails verwenden',
'enable_email_markdown_help' => 'Visuellen Markdown-Editor für E-Mails verwenden',
'enable_pdf_markdown' => 'Markdown in PDFs verwenden',
'json_help' => 'Achtung: JSON-Dateien, die mit v4 der App erstellt wurden, werden nicht unterstützt',
'release_notes' => 'Versionshinweise',
'upgrade_to_view_reports' => 'Upgrade deinen Tarif um Berichte anzusehen',
'started_tasks' => ':value Aufgaben erfolgreich gestartet',
'stopped_tasks' => ':value Aufgaben erfolgreich angehalten',
'approved_quote' => 'Angebot erfolgreich angenommen',
'approved_quotes' => ':value Angebote erfolgreich angenommen',
'client_website' => 'Kunden-Website',
'invalid_time' => 'Ungültige Zeit',
'signed_in_as' => 'Angemeldet als',
'total_results' => 'Ergebnisse insgesamt',
'restore_company_gateway' => 'Zahlungs-Gateway wiederherstellen',
'archive_company_gateway' => 'Zahlungs-Gateway aktivieren',
'delete_company_gateway' => 'Zahlungs-Gateway löschen',
'exchange_currency' => 'Währung wechseln',
'tax_amount1' => 'Steuerhöhe 1',
'tax_amount2' => 'Steuerhöhe 2',
'tax_amount3' => 'Steuerhöhe 3',
'update_project' => 'Projekt aktualisieren',
'auto_archive_invoice_cancelled' => 'Auto-Archivieren Stornierte Rechnung',
'auto_archive_invoice_cancelled_help' => 'Automatisch Rechnungen archivieren wenn sie storniert werden',
);
return $LANG;

View File

@ -4574,6 +4574,14 @@ $LANG = array(
'update_project' => 'Update Project',
'auto_archive_invoice_cancelled' => 'Auto Archive Cancelled Invoice',
'auto_archive_invoice_cancelled_help' => 'Automatically archive invoices when they are cancelled',
'no_invoices_found' => 'No invoices found',
'created_record' => 'Successfully created record',
'auto_archive_paid_invoices' => 'Auto Archive Paid',
'auto_archive_paid_invoices_help' => 'Automatically archive invoices when they are paid.',
'auto_archive_cancelled_invoices' => 'Auto Archive Cancelled',
'auto_archive_cancelled_invoices_help' => 'Automatically archive invoices when they are cancelled.',
'alternate_pdf_viewer' => 'Alternate PDF Viewer',
'alternate_pdf_viewer_help' => 'Improve scrolling over the PDF preview [BETA]',
);

View File

@ -5,66 +5,66 @@
<p><b>If your logo imported correctly it will display below. If it didn't import, you'll need to reupload your logo</b></p>
<p><img src="{{ $company->present()->logo() }}"></p>
<p><img src="{{ $logo }}"></p>
@if(isset($company) && $company->clients->count() >=1)
<p><b>{{ ctrans('texts.clients') }}:</b> {{ $company->clients->count() }} </p>
@if(isset($company))
<p><b>{{ ctrans('texts.clients') }}:</b> {{ $client_count }} </p>
@endif
@if(isset($company) && count($company->products) >=1)
<p><b>{{ ctrans('texts.products') }}:</b> {{ count($company->products) }} </p>
@if(isset($company))
<p><b>{{ ctrans('texts.products') }}:</b> {{ $product_count }} </p>
@endif
@if(isset($company) && count($company->invoices) >=1)
<p><b>{{ ctrans('texts.invoices') }}:</b> {{ count($company->invoices) }} </p>
@if(isset($company))
<p><b>{{ ctrans('texts.invoices') }}:</b> {{ $invoice_count }} </p>
@endif
@if(isset($company) && count($company->payments) >=1)
<p><b>{{ ctrans('texts.payments') }}:</b> {{ count($company->payments) }} </p>
@if(isset($company))
<p><b>{{ ctrans('texts.payments') }}:</b> {{ $payment_count }} </p>
@endif
@if(isset($company) && count($company->recurring_invoices) >=1)
<p><b>{{ ctrans('texts.recurring_invoices') }}:</b> {{ count($company->recurring_invoices) }} </p>
@if(isset($company))
<p><b>{{ ctrans('texts.recurring_invoices') }}:</b> {{ $recurring_invoice_count }} </p>
@endif
@if(isset($company) && count($company->quotes) >=1)
<p><b>{{ ctrans('texts.quotes') }}:</b> {{ count($company->quotes) }} </p>
@if(isset($company))
<p><b>{{ ctrans('texts.quotes') }}:</b> {{ $quote_count }} </p>
@endif
@if(isset($company) && count($company->credits) >=1)
<p><b>{{ ctrans('texts.credits') }}:</b> {{ count($company->credits) }} </p>
@if(isset($company))
<p><b>{{ ctrans('texts.credits') }}:</b> {{ $credit_count }} </p>
@endif
@if(isset($company) && count($company->projects) >=1)
<p><b>{{ ctrans('texts.projects') }}:</b> {{ count($company->projects) }} </p>
@if(isset($company))
<p><b>{{ ctrans('texts.projects') }}:</b> {{ $project_count }} </p>
@endif
@if(isset($company) && count($company->tasks) >=1)
<p><b>{{ ctrans('texts.tasks') }}:</b> {{ count($company->tasks) }} </p>
@if(isset($company))
<p><b>{{ ctrans('texts.tasks') }}:</b> {{ $task_count }} </p>
@endif
@if(isset($company) && count($company->vendors) >=1)
<p><b>{{ ctrans('texts.vendors') }}:</b> {{ count($company->vendors) }} </p>
@if(isset($company))
<p><b>{{ ctrans('texts.vendors') }}:</b> {{ $vendor_count }} </p>
@endif
@if(isset($company) && count($company->expenses) >=1)
<p><b>{{ ctrans('texts.expenses') }}:</b> {{ count($company->expenses) }} </p>
@if(isset($company))
<p><b>{{ ctrans('texts.expenses') }}:</b> {{ $expense_count }} </p>
@endif
@if(isset($company) && count($company->company_gateways) >=1)
<p><b>{{ ctrans('texts.gateways') }}:</b> {{ count($company->company_gateways) }} </p>
@if(isset($company))
<p><b>{{ ctrans('texts.gateways') }}:</b> {{ $company_gateway_count }} </p>
@endif
@if(isset($company) && count($company->client_gateway_tokens) >=1)
<p><b>{{ ctrans('texts.tokens') }}:</b> {{ count($company->client_gateway_tokens) }} </p>
@if(isset($company))
<p><b>{{ ctrans('texts.tokens') }}:</b> {{ $client_gateway_token_count }} </p>
@endif
@if(isset($company) && count($company->tax_rates) >=1)
<p><b>{{ ctrans('texts.tax_rates') }}:</b> {{ count($company->tax_rates) }} </p>
@if(isset($company))
<p><b>{{ ctrans('texts.tax_rates') }}:</b> {{ $tax_rate_count }} </p>
@endif
@if(isset($company) && count($company->documents) >=1)
<p><b>{{ ctrans('texts.documents') }}:</b> {{ count($company->documents) }} </p>
@if(isset($company))
<p><b>{{ ctrans('texts.documents') }}:</b> {{ $document_count }} </p>
@endif
@if(isset($check_data))

View File

@ -19,11 +19,11 @@ Route::group(['middleware' => ['throttle:300,1', 'api_secret_check']], function
});
Route::group(['middleware' => ['throttle:10,1','api_secret_check','email_db']], function () {
Route::post('api/v1/login', 'Auth\LoginController@apiLogin')->name('login.submit');
Route::post('api/v1/login', 'Auth\LoginController@apiLogin')->name('login.submit')->middleware('throttle:20,1');;
Route::post('api/v1/reset_password', 'Auth\ForgotPasswordController@sendResetLinkEmail');
});
Route::group(['middleware' => ['throttle:300,1', 'api_db', 'token_auth', 'locale'], 'prefix' => 'api/v1', 'as' => 'api.'], function () {
Route::group(['middleware' => ['throttle:100,1', 'api_db', 'token_auth', 'locale'], 'prefix' => 'api/v1', 'as' => 'api.'], function () {
Route::post('check_subdomain', 'SubdomainController@index')->name('check_subdomain');
Route::get('ping', 'PingController@index')->name('ping');
Route::get('health_check', 'PingController@health')->name('health_check');
@ -152,7 +152,7 @@ Route::group(['middleware' => ['throttle:300,1', 'api_db', 'token_auth', 'locale
Route::post('recurring_quotes/bulk', 'RecurringQuoteController@bulk')->name('recurring_quotes.bulk');
Route::put('recurring_quotes/{recurring_quote}/upload', 'RecurringQuoteController@upload');
Route::post('refresh', 'Auth\LoginController@refresh');
Route::post('refresh', 'Auth\LoginController@refresh')->middleware('throttle:30,1');
Route::post('reports/clients', 'Reports\ClientReportController');
Route::post('reports/contacts', 'Reports\ClientContactReportController');
@ -167,6 +167,7 @@ Route::group(['middleware' => ['throttle:300,1', 'api_db', 'token_auth', 'locale
Route::post('reports/payments', 'Reports\PaymentReportController');
Route::post('reports/products', 'Reports\ProductReportController');
Route::post('reports/tasks', 'Reports\TaskReportController');
Route::post('reports/profitloss', 'Reports\ProfitAndLossController');
Route::get('scheduler', 'SchedulerController@index');
Route::post('support/messages/send', 'Support\Messages\SendingController');

View File

@ -8,7 +8,7 @@ Route::get('client/login', 'Auth\ContactLoginController@showLoginForm')->name('c
Route::post('client/login', 'Auth\ContactLoginController@login')->name('client.login.submit');
Route::get('client/register/{company_key?}', 'Auth\ContactRegisterController@showRegisterForm')->name('client.register')->middleware(['domain_db', 'contact_account', 'contact_register','locale']);
Route::post('client/register/{company_key?}', 'Auth\ContactRegisterController@register')->middleware(['domain_db', 'contact_account', 'contact_register', 'locale','throttle:10,1']);
Route::post('client/register/{company_key?}', 'Auth\ContactRegisterController@register')->middleware(['domain_db', 'contact_account', 'contact_register', 'locale', 'throttle:10,1']);
Route::get('client/password/reset', 'Auth\ContactForgotPasswordController@showLinkRequestForm')->name('client.password.request')->middleware(['domain_db', 'contact_account','locale']);
Route::post('client/password/email', 'Auth\ContactForgotPasswordController@sendResetLinkEmail')->name('client.password.email')->middleware('locale');
@ -62,7 +62,7 @@ Route::group(['middleware' => ['auth:contact', 'locale', 'domain_db','check_clie
Route::put('profile/{client_contact}/localization', 'ClientPortal\ProfileController@updateClientLocalization')->name('profile.edit_localization');
Route::get('payment_methods/{payment_method}/verification', 'ClientPortal\PaymentMethodController@verify')->name('payment_methods.verification');
Route::post('payment_methods/{payment_method}/verification', 'ClientPortal\PaymentMethodController@processVerification');
Route::post('payment_methods/{payment_method}/verification', 'ClientPortal\PaymentMethodController@processVerification')->middleware(['throttle:10,1']);
Route::get('payment_methods/confirm', 'ClientPortal\PaymentMethodController@store')->name('payment_methods.confirm');

View File

@ -0,0 +1,76 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace Tests\Feature\Export;
use App\Models\Invoice;
use App\Utils\Traits\MakesHash;
use Illuminate\Routing\Middleware\ThrottleRequests;
use Illuminate\Support\Facades\Storage;
use League\Csv\Writer;
use Tests\MockAccountData;
use Tests\TestCase;
/**
* @test
*/
class ClientCsvTest extends TestCase
{
use MakesHash;
use MockAccountData;
public function setUp() :void
{
parent::setUp();
$this->withoutMiddleware(
ThrottleRequests::class
);
$this->makeTestData();
$this->withoutExceptionHandling();
}
public function testClientExportCsv()
{
$data = [
"date_range" => "this_year",
"report_keys" => [],
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/reports/clients' , $data);
$response->assertStatus(200);
}
public function testContactExportCsv()
{
$data = [
"date_range" => "this_year",
"report_keys" => [],
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/reports/contacts' , $data);
$response->assertStatus(200);
}
}

View File

@ -0,0 +1,566 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace Tests\Feature\Export;
use App\DataMapper\ClientSettings;
use App\DataMapper\CompanySettings;
use App\Factory\ExpenseCategoryFactory;
use App\Factory\ExpenseFactory;
use App\Factory\InvoiceFactory;
use App\Models\Account;
use App\Models\Client;
use App\Models\ClientContact;
use App\Models\Company;
use App\Models\Expense;
use App\Models\ExpenseCategory;
use App\Models\Invoice;
use App\Models\User;
use App\Services\Report\ProfitLoss;
use App\Utils\Traits\MakesHash;
use Database\Factories\ClientContactFactory;
use Illuminate\Routing\Middleware\ThrottleRequests;
use Illuminate\Support\Facades\Storage;
use League\Csv\Writer;
use Tests\MockAccountData;
use Tests\TestCase;
/**
* @test
* @covers App\Services\Report\ProfitLoss
*/
class ProfitAndLossReportTest extends TestCase
{
use MakesHash;
public $faker;
public function setUp() :void
{
parent::setUp();
$this->faker = \Faker\Factory::create();
$this->withoutMiddleware(
ThrottleRequests::class
);
$this->withoutExceptionHandling();
}
public $company;
public $user;
public $payload;
public $account;
/**
*
* start_date - Y-m-d
end_date - Y-m-d
date_range -
all
last7
last30
this_month
last_month
this_quarter
last_quarter
this_year
custom
is_income_billed - true = Invoiced || false = Payments
expense_billed - true = Expensed || false = Expenses marked as paid
include_tax - true tax_included || false - tax_excluded
*/
private function buildData()
{
$this->account = Account::factory()->create([
'hosted_client_count' => 1000,
'hosted_company_count' => 1000
]);
$this->account->num_users = 3;
$this->account->save();
$this->user = User::factory()->create([
'account_id' => $this->account->id,
'confirmation_code' => 'xyz123',
'email' => $this->faker->unique()->safeEmail,
]);
$settings = CompanySettings::defaults();
$settings->client_online_payment_notification = false;
$settings->client_manual_payment_notification = false;
$this->company = Company::factory()->create([
'account_id' => $this->account->id,
'settings' => $settings
]);
$this->payload = [
'start_date' => '2000-01-01',
'end_date' => '2030-01-11',
'date_range' => 'custom',
'is_income_billed' => true,
'include_tax' => false
];
}
public function testProfitLossInstance()
{
$this->buildData();
$pl = new ProfitLoss($this->company, $this->payload);
$this->assertInstanceOf(ProfitLoss::class, $pl);
$this->account->delete();
}
public function testSimpleInvoiceIncome()
{
$this->buildData();
$client = Client::factory()->create([
'user_id' => $this->user->id,
'company_id' => $this->company->id,
'is_deleted' => 0,
]);
Invoice::factory()->count(2)->create([
'client_id' => $client->id,
'user_id' => $this->user->id,
'company_id' => $this->company->id,
'amount' => 11,
'balance' => 11,
'status_id' => 2,
'total_taxes' => 1,
'date' => '2022-01-01',
'terms' => 'nada',
'discount' => 0,
'tax_rate1' => 0,
'tax_rate2' => 0,
'tax_rate3' => 0,
'tax_name1' => '',
'tax_name2' => '',
'tax_name3' => '',
'uses_inclusive_taxes' => false,
]);
$pl = new ProfitLoss($this->company, $this->payload);
$pl->build();
$this->assertEquals(20.0, $pl->getIncome());
$this->assertEquals(2, $pl->getIncomeTaxes());
$this->account->delete();
}
public function testSimpleInvoiceIncomeWithInclusivesTaxes()
{
$this->buildData();
$client = Client::factory()->create([
'user_id' => $this->user->id,
'company_id' => $this->company->id,
'is_deleted' => 0,
]);
Invoice::factory()->count(2)->create([
'client_id' => $client->id,
'user_id' => $this->user->id,
'company_id' => $this->company->id,
'amount' => 10,
'balance' => 10,
'status_id' => 2,
'total_taxes' => 1,
'date' => '2022-01-01',
'terms' => 'nada',
'discount' => 0,
'tax_rate1' => 10,
'tax_rate2' => 0,
'tax_rate3' => 0,
'tax_name1' => "GST",
'tax_name2' => '',
'tax_name3' => '',
'uses_inclusive_taxes' => true,
]);
$pl = new ProfitLoss($this->company, $this->payload);
$pl->build();
$this->assertEquals(18.0, $pl->getIncome());
$this->assertEquals(2, $pl->getIncomeTaxes());
$this->account->delete();
}
public function testSimpleInvoiceIncomeWithForeignExchange()
{
$this->buildData();
$settings = ClientSettings::defaults();
$settings->currency_id = "2";
$client = Client::factory()->create([
'user_id' => $this->user->id,
'company_id' => $this->company->id,
'is_deleted' => 0,
'settings' => $settings,
]);
Invoice::factory()->count(2)->create([
'client_id' => $client->id,
'user_id' => $this->user->id,
'company_id' => $this->company->id,
'amount' => 10,
'balance' => 10,
'status_id' => 2,
'total_taxes' => 1,
'date' => '2022-01-01',
'terms' => 'nada',
'discount' => 0,
'tax_rate1' => 10,
'tax_rate2' => 0,
'tax_rate3' => 0,
'tax_name1' => "GST",
'tax_name2' => '',
'tax_name3' => '',
'uses_inclusive_taxes' => true,
'exchange_rate' => 0.5
]);
$pl = new ProfitLoss($this->company, $this->payload);
$pl->build();
$this->assertEquals(36.0, $pl->getIncome());
$this->assertEquals(4, $pl->getIncomeTaxes());
$this->account->delete();
}
public function testSimpleInvoicePaymentIncome()
{
$this->buildData();
$this->payload = [
'start_date' => '2000-01-01',
'end_date' => '2030-01-11',
'date_range' => 'custom',
'is_income_billed' => false,
'include_tax' => false
];
$settings = ClientSettings::defaults();
$settings->currency_id = "1";
$client = Client::factory()->create([
'user_id' => $this->user->id,
'company_id' => $this->company->id,
'is_deleted' => 0,
'settings' => $settings,
]);
$contact = ClientContact::factory()->create([
'client_id' => $client->id
]);
$i = Invoice::factory()->create([
'client_id' => $client->id,
'user_id' => $this->user->id,
'company_id' => $this->company->id,
'amount' => 10,
'balance' => 10,
'status_id' => 2,
'total_taxes' => 0,
'date' => '2022-01-01',
'terms' => 'nada',
'discount' => 0,
'tax_rate1' => 0,
'tax_rate2' => 0,
'tax_rate3' => 0,
'tax_name1' => "",
'tax_name2' => '',
'tax_name3' => '',
'uses_inclusive_taxes' => true,
'exchange_rate' => 1
]);
$i->service()->markPaid()->save();
$pl = new ProfitLoss($this->company, $this->payload);
$pl->build();
$this->assertEquals(10.0, $pl->getIncome());
$this->account->delete();
}
public function testSimpleExpense()
{
$this->buildData();
$e = Expense::factory()->create([
'amount' => 10,
'company_id' => $this->company->id,
'user_id' => $this->user->id,
'date' => '2022-01-01',
]);
$pl = new ProfitLoss($this->company, $this->payload);
$pl->build();
$expenses = $pl->getExpenses();
$expense = $expenses[0];
$this->assertEquals(10, $expense->total);
$this->account->delete();
}
public function testSimpleExpenseAmountTax()
{
$this->buildData();
$e = ExpenseFactory::create($this->company->id, $this->user->id);
$e->amount = 10;
$e->date = '2022-01-01';
$e->calculate_tax_by_amount = true;
$e->tax_amount1 = 10;
$e->save();
$pl = new ProfitLoss($this->company, $this->payload);
$pl->build();
$expenses = $pl->getExpenses();
$expense = $expenses[0];
$this->assertEquals(10, $expense->total);
$this->assertEquals(10, $expense->tax);
$this->account->delete();
}
public function testSimpleExpenseTaxRateExclusive()
{
$this->buildData();
$e = ExpenseFactory::create($this->company->id, $this->user->id);
$e->amount = 10;
$e->date = '2022-01-01';
$e->tax_rate1 = 10;
$e->tax_name1 = 'GST';
$e->uses_inclusive_taxes = false;
$e->save();
$pl = new ProfitLoss($this->company, $this->payload);
$pl->build();
$expenses = $pl->getExpenses();
$expense = $expenses[0];
$this->assertEquals(10, $expense->total);
$this->assertEquals(1, $expense->tax);
$this->account->delete();
}
public function testSimpleExpenseTaxRateInclusive()
{
$this->buildData();
$e = ExpenseFactory::create($this->company->id, $this->user->id);
$e->amount = 10;
$e->date = '2022-01-01';
$e->tax_rate1 = 10;
$e->tax_name1 = 'GST';
$e->uses_inclusive_taxes = false;
$e->save();
$pl = new ProfitLoss($this->company, $this->payload);
$pl->build();
$expenses = $pl->getExpenses();
$expense = $expenses[0];
$this->assertEquals(10, $expense->total);
$this->assertEquals(1, $expense->tax);
$this->account->delete();
}
public function testSimpleExpenseBreakdown()
{
$this->buildData();
$e = Expense::factory()->create([
'amount' => 10,
'company_id' => $this->company->id,
'user_id' => $this->user->id,
'date' => '2022-01-01',
'exchange_rate' => 1,
'currency_id' => $this->company->settings->currency_id
]);
$pl = new ProfitLoss($this->company, $this->payload);
$pl->build();
$expenses = $pl->getExpenses();
$bd = $pl->getExpenseBreakDown();
$this->assertEquals(array_sum(array_column($bd,'total')), 10);
$this->account->delete();
}
public function testSimpleExpenseCategoriesBreakdown()
{
$this->buildData();
$ec = ExpenseCategoryFactory::create($this->company->id, $this->user->id);
$ec->name = 'Accounting';
$ec->save();
$e = Expense::factory()->create([
'category_id' => $ec->id,
'amount' => 10,
'company_id' => $this->company->id,
'user_id' => $this->user->id,
'date' => '2022-01-01',
'exchange_rate' => 1,
'currency_id' => $this->company->settings->currency_id
]);
$ec = ExpenseCategoryFactory::create($this->company->id, $this->user->id);
$ec->name = 'Fuel';
$ec->save();
$e = Expense::factory(2)->create([
'category_id' => $ec->id,
'amount' => 10,
'company_id' => $this->company->id,
'user_id' => $this->user->id,
'date' => '2022-01-01',
'exchange_rate' => 1,
'currency_id' => $this->company->settings->currency_id
]);
$pl = new ProfitLoss($this->company, $this->payload);
$pl->build();
$expenses = $pl->getExpenses();
$bd = $pl->getExpenseBreakDown();
$this->assertEquals(array_sum(array_column($bd,'total')), 30);
$this->account->delete();
}
public function testCsvGeneration()
{
$this->buildData();
$client = Client::factory()->create([
'user_id' => $this->user->id,
'company_id' => $this->company->id,
'is_deleted' => 0,
]);
Invoice::factory()->count(1)->create([
'client_id' => $client->id,
'user_id' => $this->user->id,
'company_id' => $this->company->id,
'amount' => 10,
'balance' => 10,
'status_id' => 2,
'total_taxes' => 1,
'date' => '2022-01-01',
'terms' => 'nada',
'discount' => 0,
'tax_rate1' => 10,
'tax_rate2' => 0,
'tax_rate3' => 0,
'tax_name1' => "GST",
'tax_name2' => '',
'tax_name3' => '',
'uses_inclusive_taxes' => true,
]);
$ec = ExpenseCategoryFactory::create($this->company->id, $this->user->id);
$ec->name = 'Accounting';
$ec->save();
$e = Expense::factory()->create([
'category_id' => $ec->id,
'amount' => 10,
'company_id' => $this->company->id,
'user_id' => $this->user->id,
'date' => '2022-01-01',
'exchange_rate' => 1,
'currency_id' => $this->company->settings->currency_id
]);
$ec = ExpenseCategoryFactory::create($this->company->id, $this->user->id);
$ec->name = 'Fuel';
$ec->save();
$e = Expense::factory(2)->create([
'category_id' => $ec->id,
'amount' => 10,
'company_id' => $this->company->id,
'user_id' => $this->user->id,
'date' => '2022-01-01',
'exchange_rate' => 1,
'currency_id' => $this->company->settings->currency_id
]);
$pl = new ProfitLoss($this->company, $this->payload);
$pl->build();
echo($pl->getCsv());
$this->assertNotNull($pl->getCsv());
$this->account->delete();
}
}

View File

@ -0,0 +1,81 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace Tests\Unit\ValidationRules;
use App\Http\Requests\Invoice\StoreInvoiceRequest;
use App\Http\ValidationRules\Account\BlackListRule;
use App\Models\Invoice;
use App\Utils\Traits\MakesHash;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Validator;
use Tests\MockAccountData;
use Tests\TestCase;
/**
* @test
*/
class UniqueInvoiceNumberValidationTest extends TestCase
{
use MakesHash;
use MockAccountData;
public function setUp() :void
{
parent::setUp();
$this->withoutMiddleware(
ThrottleRequests::class
);
$this->makeTestData();
$this->withoutExceptionHandling();
}
public function testValidEmailRule()
{
auth()->login($this->user);
auth()->user()->setCompany($this->company);
Invoice::factory()->create([
'client_id' => $this->client->id,
'user_id' => $this->user->id,
'company_id' => $this->company->id,
'paid_to_date' => 100,
'status_id' => 4,
'date' => now(),
'due_date'=> now(),
'number' => 'db_record'
]);
$data = [
'client_id' => $this->client->hashed_id,
'paid_to_date' => 100,
'status_id' => 4,
'date' => now(),
'due_date'=> now(),
'number' => 'db_record'
];
$rules = (new StoreInvoiceRequest())->rules();
$validator = Validator::make($data, $rules);
$this->assertFalse($validator->passes());
}
}