Merge branch 'v5-develop' into designer

This commit is contained in:
David Bomba 2023-01-08 15:21:46 +11:00
commit 174d0d3338
47 changed files with 82292 additions and 81148 deletions

View File

@ -1 +1 @@
5.5.49
5.5.50

View File

@ -119,7 +119,8 @@ class CheckData extends Command
$this->checkDuplicateRecurringInvoices();
$this->checkOauthSanity();
$this->checkVendorSettings();
$this->checkClientSettings();
if(Ninja::isHosted()){
$this->checkAccountStatuses();
$this->checkNinjaPortalUrls();
@ -952,24 +953,24 @@ class CheckData extends Command
if ($this->option('fix') == 'true') {
Client::query()->whereNull('settings->currency_id')->cursor()->each(function ($client){
// Client::query()->whereNull('settings->currency_id')->cursor()->each(function ($client){
if(is_array($client->settings) && count($client->settings) == 0)
{
$settings = ClientSettings::defaults();
$settings->currency_id = $client->company->settings->currency_id;
}
else {
$settings = $client->settings;
$settings->currency_id = $client->company->settings->currency_id;
}
// if(is_array($client->settings) && count($client->settings) == 0)
// {
// $settings = ClientSettings::defaults();
// $settings->currency_id = $client->company->settings->currency_id;
// }
// else {
// $settings = $client->settings;
// $settings->currency_id = $client->company->settings->currency_id;
// }
$client->settings = $settings;
$client->save();
// $client->settings = $settings;
// $client->save();
$this->logMessage("Fixing currency for # {$client->id}");
// $this->logMessage("Fixing currency for # {$client->id}");
});
// });
Client::query()->whereNull('country_id')->cursor()->each(function ($client){

View File

@ -22,6 +22,7 @@ use App\Jobs\Ninja\CompanySizeCheck;
use App\Jobs\Ninja\QueueSize;
use App\Jobs\Ninja\SystemMaintenance;
use App\Jobs\Ninja\TaskScheduler;
use App\Jobs\Invoice\InvoiceCheckLateWebhook;
use App\Jobs\Quote\QuoteCheckExpired;
use App\Jobs\Subscription\CleanStaleInvoiceOrder;
use App\Jobs\Util\DiskCleanup;
@ -78,6 +79,9 @@ class Kernel extends ConsoleKernel
/* Fires notifications for expired Quotes */
$schedule->job(new QuoteCheckExpired)->dailyAt('05:10')->withoutOverlapping()->name('quote-expired-job')->onOneServer();
/* Fires webhooks for overdue Invoice */
$schedule->job(new InvoiceCheckLateWebhook)->dailyAt('07:00')->withoutOverlapping()->name('invoice-overdue-job')->onOneServer();
/* Performs auto billing */
$schedule->job(new AutoBillCron)->dailyAt('06:20')->withoutOverlapping()->name('auto-bill-job')->onOneServer();

View File

@ -0,0 +1,138 @@
<?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\Export\CSV;
use App\Libraries\MultiDB;
use App\Models\Client;
use App\Models\Company;
use App\Models\Credit;
use App\Models\Document;
use App\Models\Invoice;
use App\Models\Product;
use App\Transformers\ProductTransformer;
use App\Utils\Ninja;
use Illuminate\Support\Facades\App;
use League\Csv\Writer;
use Illuminate\Support\Carbon;
class ProductSalesExport extends BaseExport
{
private Company $company;
protected array $input;
protected $date_key = 'created_at';
protected array $entity_keys = [
'custom_value1' => 'custom_value1',
'custom_value2' => 'custom_value2',
'custom_value3' => 'custom_value3',
'custom_value4' => 'custom_value4',
'product_key' => 'product_key',
'notes' => 'notes',
'cost' => 'cost',
'price' => 'price',
'quantity' => 'quantity',
'tax_rate1' => 'tax_rate1',
'tax_rate2' => 'tax_rate2',
'tax_rate3' => 'tax_rate3',
'tax_name1' => 'tax_name1',
'tax_name2' => 'tax_name2',
'tax_name3' => 'tax_name3',
'is_amount_discount' => 'is_amount_discount',
'discount' => 'discount',
'line_total' => 'line_total',
'gross_line_total' => 'gross_line_total',
'status' => 'status',
'date' => 'date',
'currency' => 'currency',
'client' => 'client',
];
private array $decorate_keys = [
'client',
'currency',
'date',
];
public function __construct(Company $company, array $input)
{
$this->company = $company;
$this->input = $input;
}
public function run()
{
MultiDB::setDb($this->company->db);
App::forgetInstance('translator');
App::setLocale($this->company->locale());
$t = app('translator');
$t->replace(Ninja::transformTranslations($this->company->settings));
//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());
$query = Invoice::query()
->withTrashed()
->where('company_id', $this->company->id)
->where('is_deleted', 0)
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL, Invoice::STATUS_PAID]);
$query = $this->addDateRange($query);
$query->cursor()
->each(function ($invoice) {
foreach($invoice->line_items as $item)
$this->csv->insertOne($this->buildRow($invoice, $item));
});
return $this->csv->toString();
}
private function buildRow($invoice, $invoice_item) :array
{
$transformed_entity = (array)$invoice_item;
$entity = [];
foreach (array_values($this->input['report_keys']) as $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($invoice, $entity);
}
private function decorateAdvancedFields(Invoice $invoice, $entity) :array
{
$entity['client'] = $invoice->client->present()->name();
$entity['currency'] = $invoice->client->currency()->code;
$entity['status'] = $invoice->stringStatus($invoice->status_id);
$entity['date'] = Carbon::parse($invoice->date)->format($this->company->date_format());
return $entity;
}
}

View File

@ -218,6 +218,15 @@ abstract class QueryFilters
return $this->builder->where('client_id', $this->decodePrimaryKey($client_id));
}
public function vendor_id(string $vendor_id = '') :Builder
{
if (strlen($vendor_id) == 0) {
return $this->builder;
}
return $this->builder->where('vendor_id', $this->decodePrimaryKey($vendor_id));
}
public function filter_deleted_clients($value)
{
if ($value == 'true') {

View File

@ -85,14 +85,19 @@ class QuoteFilters extends QueryFilters
}
if (in_array('expired', $status_parameters)) {
$this->builder->orWhere('status_id', Quote::STATUS_SENT)
$this->builder->orWhere(function ($query){
$query->where('status_id', Quote::STATUS_SENT)
->whereNotNull('due_date')
->where('due_date', '<=', now()->toDateString());
});
}
if (in_array('upcoming', $status_parameters)) {
$this->builder->orWhere('status_id', Quote::STATUS_SENT)
$this->builder->orWhere(function ($query){
$query->where('status_id', Quote::STATUS_SENT)
->where('due_date', '>=', now()->toDateString())
->orderBy('due_date', 'DESC');
});
}
return $this->builder;

View File

@ -0,0 +1,129 @@
<?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\Filters;
use App\Models\User;
use App\Models\Webhook;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Gate;
/**
* SubscriptionFilters.
*/
class SubscriptionFilters extends QueryFilters
{
/**
* Filter based on search text.
*
* @param string query filter
* @return Builder
* @deprecated
*/
public function filter(string $filter = '') : Builder
{
if (strlen($filter) == 0) {
return $this->builder;
}
return $this->builder->where(function ($query) use ($filter) {
$query->where('name', 'like', '%'.$filter.'%');
});
}
/**
* Filters the list based on the status
* archived, active, deleted.
*
* @param string filter
* @return Builder
*/
public function status(string $filter = '') : Builder
{
if (strlen($filter) == 0) {
return $this->builder;
}
$table = 'subscriptions';
$filters = explode(',', $filter);
return $this->builder->where(function ($query) use ($filters, $table) {
$query->whereNull($table.'.id');
if (in_array(parent::STATUS_ACTIVE, $filters)) {
$query->orWhereNull($table.'.deleted_at');
}
if (in_array(parent::STATUS_ARCHIVED, $filters)) {
$query->orWhere(function ($query) use ($table) {
$query->whereNotNull($table.'.deleted_at');
if (! in_array($table, ['users'])) {
$query->where($table.'.is_deleted', '=', 0);
}
});
}
if (in_array(parent::STATUS_DELETED, $filters)) {
$query->orWhere($table.'.is_deleted', '=', 1);
}
});
}
/**
* Sorts the list based on $sort.
*
* @param string sort formatted as column|asc
* @return Builder
*/
public function sort(string $sort) : Builder
{
$sort_col = explode('|', $sort);
return $this->builder->orderBy($sort_col[0], $sort_col[1]);
}
/**
* Returns the base query.
*
* @param int company_id
* @param User $user
* @return Builder
* @deprecated
*/
public function baseQuery(int $company_id, User $user) : Builder
{
$query = DB::table('subscriptions')
->join('companies', 'companies.id', '=', 'subscriptions.company_id')
->where('subscriptions.company_id', '=', $company_id);
/*
* If the user does not have permissions to view all invoices
* limit the user to only the invoices they have created
*/
if (Gate::denies('view-list', Webhook::class)) {
$query->where('subscriptions.user_id', '=', $user->id);
}
return $query;
}
/**
* Filters the query by the users company ID.
*
* @return Illuminate\Database\Query\Builder
*/
public function entityFilter()
{
return $this->builder->company();
}
}

View File

@ -13,6 +13,7 @@ namespace App\Helpers\Epc;
use App\Models\Company;
use App\Models\Invoice;
use App\Models\RecurringInvoice;
use App\Utils\Ninja;
use BaconQrCode\Renderer\ImageRenderer;
use BaconQrCode\Renderer\Image\SvgImageBackEnd;
@ -35,7 +36,7 @@ class EpcQrGenerator
];
public function __construct(protected Company $company, protected Invoice $invoice, protected float $amount){}
public function __construct(protected Company $company, protected Invoice|RecurringInvoice $invoice, protected float $amount){}
public function getQrCode()
{

View File

@ -112,6 +112,7 @@ class ActivityController extends BaseController
'purchase_order' => $activity->purchase_order ? $activity->purchase_order : '',
'subscription' => $activity->subscription ? $activity->subscription : '',
'vendor_contact' => $activity->vendor_contact ? $activity->vendor_contact : '',
'recurring_expense' => $activity->recurring_expense ? $activity->recurring_expense : '',
];
return array_merge($arr, $activity->toArray());

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\Http\Controllers\Reports;
use App\Export\CSV\ProductExport;
use App\Export\CSV\ProductSalesExport;
use App\Http\Controllers\BaseController;
use App\Http\Requests\Report\GenericReportRequest;
use App\Http\Requests\Report\ProductSalesReportRequest;
use App\Jobs\Report\SendToAdmin;
use App\Models\Client;
use App\Utils\Traits\MakesHash;
use Illuminate\Http\Response;
class ProductSalesReportController extends BaseController
{
use MakesHash;
private string $filename = 'product_sales.csv';
public function __construct()
{
parent::__construct();
}
/**
* @OA\Post(
* path="/api/v1/reports/product_sales",
* operationId="getProductSalesReport",
* tags={"reports"},
* summary="Product Salesreports",
* description="Export product sales reports",
* @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(ProductSalesReportRequest $request)
{
if ($request->has('send_email') && $request->get('send_email')) {
SendToAdmin::dispatch(auth()->user()->company(), $request->all(), ProductSalesExport::class, $this->filename);
return response()->json(['message' => 'working...'], 200);
}
// expect a list of visible fields, or use the default
$export = new ProductSalesExport(auth()->user()->company(), $request->all());
$csv = $export->run();
$headers = [
'Content-Disposition' => 'attachment',
'Content-Type' => 'text/csv',
];
return response()->streamDownload(function () use ($csv) {
echo $csv;
}, $this->filename, $headers);
}
}

View File

@ -15,6 +15,7 @@ namespace App\Http\Controllers;
use App\Events\Subscription\SubscriptionWasCreated;
use App\Events\Subscription\SubscriptionWasUpdated;
use App\Factory\SubscriptionFactory;
use App\Filters\SubscriptionFilters;
use App\Http\Requests\Subscription\CreateSubscriptionRequest;
use App\Http\Requests\Subscription\DestroySubscriptionRequest;
use App\Http\Requests\Subscription\EditSubscriptionRequest;
@ -80,9 +81,9 @@ class SubscriptionController extends BaseController
* ),
* )
*/
public function index(): \Illuminate\Http\Response
public function index(SubscriptionFilters $filters): \Illuminate\Http\Response
{
$subscriptions = Subscription::query()->company();
$subscriptions = Subscription::filter($filters);
return $this->listResponse($subscriptions);
}

View File

@ -0,0 +1,71 @@
<?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;
use App\Utils\Traits\MakesHash;
use Illuminate\Validation\Rule;
class ProductSalesReportRequest extends Request
{
use MakesHash;
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return auth()->user()->isAdmin();
}
public function rules()
{
return [
'date_range' => 'bail|required|string',
'end_date' => 'bail|required_if:date_range,custom|nullable|date',
'start_date' => 'bail|required_if:date_range,custom|nullable|date',
'report_keys' => 'bail|present|array',
'send_email' => 'bail|required|bool',
'client_id' => 'bail|sometimes|exists:clients,id,company_id,'.auth()->user()->company()->id.',is_deleted,0',
];
}
public function prepareForValidation()
{
$input = $this->all();
if (! array_key_exists('date_range', $input)) {
$input['date_range'] = 'all';
}
if (! array_key_exists('report_keys', $input)) {
$input['report_keys'] = [];
}
if (! array_key_exists('send_email', $input)) {
$input['send_email'] = true;
}
if (array_key_exists('date_range', $input) && $input['date_range'] != 'custom') {
$input['start_date'] = null;
$input['end_date'] = null;
}
if(array_key_exists('client_id', $input) && strlen($input['client_id']) >=1)
$input['client_id'] = $this->decodePrimaryKey($input['client_id']);
$this->replace($input);
}
}

View File

@ -61,9 +61,9 @@ class InvoiceTransformer extends BaseTransformer
'discount' => $this->getFreshbookQuantityFloat($record, 'Discount Percentage'),
'is_amount_discount' => false,
'tax_name1' => $this->getString($record, 'Tax 1 Type'),
'tax_rate1' => $this->getFreshbookQuantityFloat($record, 'Tax 1 Amount'),
'tax_rate1' => $this->calcTaxRate($record, 'Tax 1 Amount'),
'tax_name2' => $this->getString($record, 'Tax 2 Type'),
'tax_rate2' => $this->getFreshbookQuantityFloat($record, 'Tax 2 Amount'),
'tax_rate2' => $this->calcTaxRate($record, 'Tax 2 Amount'),
];
$transformed['amount'] += $this->getFreshbookQuantityFloat($record, 'Line Total');
}
@ -79,6 +79,27 @@ class InvoiceTransformer extends BaseTransformer
return $transformed;
}
//Line Subtotal
public function calcTaxRate($record, $field)
{
if(isset($record['Line Subtotal']) && $record['Line Subtotal'] > 0)
return ($record[$field] / $record['Line Subtotal']) * 100;
$tax_amount1 = isset($record['Tax 1 Amount']) ? $record['Tax 1 Amount'] : 0;
$tax_amount2 = isset($record['Tax 2 Amount']) ? $record['Tax 2 Amount'] : 0;
$line_total = isset($record['Line Total']) ? $record['Line Total'] : 0;
$subtotal = $line_total - $tax_amount2 - $tax_amount1;
if($subtotal > 0)
return $record[$field] / $subtotal * 100;
return 0;
}
/** @return float */
public function getFreshbookQuantityFloat($data, $field)
{

View File

@ -1112,6 +1112,9 @@ class CompanyImport implements ShouldQueue
foreach((object)$this->getObject("documents") as $document)
{
//todo enable this for v5.5.51
if(!$this->transformDocumentId($document->documentable_id, $document->documentable_type))
continue;
$new_document = new Document();
$new_document->user_id = $this->transformId('users', $document->user_id);
@ -1152,6 +1155,10 @@ class CompanyImport implements ShouldQueue
{
try{
Storage::disk(config('filesystems.default'))->put($new_document->url, $file);
$new_document->disk = config('filesystems.default');
$new_document->save();
}
catch(\Exception $e)
{
@ -1279,6 +1286,9 @@ class CompanyImport implements ShouldQueue
case Payment::class:
return $this->transformId('payments', $id);
break;
case Project::class:
return $this->transformId('projects', $id);
break;
case Product::class:
return $this->transformId('products', $id);
break;
@ -1294,7 +1304,7 @@ class CompanyImport implements ShouldQueue
default:
# code...
return false;
break;
}
}

View File

@ -106,6 +106,24 @@ class CSVIngest implements ShouldQueue
$new_contact->is_primary = true;
$new_contact->save();
}
Client::with('contacts')->where('company_id', $this->company->id)->cursor()->each(function ($client){
$contact = $client->contacts()->first();
$contact->is_primary = true;
$contact->save();
});
Vendor::with('contacts')->where('company_id', $this->company->id)->cursor()->each(function ($vendor){
$contact = $vendor->contacts()->first();
$contact->is_primary = true;
$contact->save();
});
}
private function bootEngine()

View File

@ -0,0 +1,111 @@
<?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\Invoice;
use App\Jobs\Util\WebhookHandler;
use App\Libraries\MultiDB;
use App\Models\Invoice;
use App\Models\Webhook;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class InvoiceCheckLateWebhook implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* Create a new job instance.
*/
public function __construct() {}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
nlog("sending overdue webhooks for invoices");
if (! config('ninja.db.multi_db_enabled')){
$company_ids = Webhook::where('event_id', Webhook::EVENT_LATE_INVOICE)
->where('is_deleted', 0)
->pluck('company_id');
Invoice::query()
->where('is_deleted', false)
->whereNull('deleted_at')
->whereNotNull('due_date')
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
->where('balance', '>', 0)
->whereIn('company_id', $company_ids)
->whereHas('client', function ($query) {
$query->where('is_deleted', 0)
->where('deleted_at', null);
})
->whereHas('company', function ($query){
$query->where('is_disabled', 0);
})
->whereBetween('due_date', [now()->subDay()->startOfDay(), now()->startOfDay()->subSecond()])
->cursor()
->each(function ($invoice){
WebhookHandler::dispatch(Webhook::EVENT_LATE_INVOICE, $invoice, $invoice->company, 'client')->delay(now()->addSeconds(2));
});
}
else {
foreach (MultiDB::$dbs as $db)
{
MultiDB::setDB($db);
$company_ids = Webhook::where('event_id', Webhook::EVENT_LATE_INVOICE)
->where('is_deleted', 0)
->pluck('company_id');
Invoice::query()
->where('is_deleted', false)
->whereNull('deleted_at')
->whereNotNull('due_date')
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
->where('balance', '>', 0)
->whereIn('company_id', $company_ids)
->whereHas('client', function ($query) {
$query->where('is_deleted', 0)
->where('deleted_at', null);
})
->whereHas('company', function ($query){
$query->where('is_disabled', 0);
})
->whereBetween('due_date', [now()->subDay()->startOfDay(), now()->startOfDay()->subSecond()])
->cursor()
->each(function ($invoice){
WebhookHandler::dispatch(Webhook::EVENT_LATE_INVOICE, $invoice, $invoice->company, 'client')->delay(now()->addSeconds(2));
});
}
}
}
}

View File

@ -349,6 +349,26 @@ class Import implements ShouldQueue
}
$account = $this->company->account;
/* If the user has upgraded their account, do not wipe their payment plan*/
if($account->isPaid() || (isset($data['plan']) && $data['plan'] == 'white_label'))
{
if(isset($data['plan']))
unset($data['plan']);
if(isset($data['plan_term']))
unset($data['plan_term']);
if(isset($data['plan_paid']))
unset($data['plan_paid']);
if(isset($data['plan_started']))
unset($data['plan_started']);
if(isset($data['plan_expires']))
unset($data['plan_expires']);
}
$account->fill($data);
$account->save();

View File

@ -214,21 +214,33 @@ class Activity extends StaticModel
'backup',
];
/**
* @return mixed
*/
public function getHashedIdAttribute()
{
return $this->encodePrimaryKey($this->id);
}
/**
* @return mixed
*/
public function getEntityType()
{
return self::class;
}
/**
* @return mixed
*/
public function backup()
{
return $this->hasOne(Backup::class);
}
/**
* @return mixed
*/
public function history()
{
return $this->hasOne(Backup::class);
@ -266,6 +278,9 @@ class Activity extends StaticModel
return $this->belongsTo(Invoice::class)->withTrashed();
}
/**
* @return mixed
*/
public function vendor()
{
return $this->belongsTo(Vendor::class)->withTrashed();
@ -279,6 +294,9 @@ class Activity extends StaticModel
return $this->belongsTo(RecurringInvoice::class)->withTrashed();
}
/**
* @return mixed
*/
public function credit()
{
return $this->belongsTo(Credit::class)->withTrashed();
@ -308,31 +326,57 @@ class Activity extends StaticModel
return $this->belongsTo(Payment::class)->withTrashed();
}
/**
* @return mixed
*/
public function expense()
{
return $this->belongsTo(Expense::class)->withTrashed();
}
/**
* @return mixed
*/
public function recurring_expense()
{
return $this->belongsTo(RecurringExpense::class)->withTrashed();
}
/**
* @return mixed
*/
public function purchase_order()
{
return $this->belongsTo(PurchaseOrder::class)->withTrashed();
}
/**
* @return mixed
*/
public function vendor_contact()
{
return $this->belongsTo(VendorContact::class)->withTrashed();
}
/**
* @return mixed
*/
public function task()
{
return $this->belongsTo(Task::class)->withTrashed();
}
/**
* @return mixed
*/
public function company()
{
return $this->belongsTo(Company::class);
}
/**
* @return mixed
*/
public function resolveRouteBinding($value, $field = null)
{
if (is_numeric($value)) {

View File

@ -102,21 +102,23 @@ class Gateway extends StaticModel
break;
case 20:
return [
GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true],
GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded', 'charge.failed', 'payment_intent.payment_failed']],
GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true, 'webhooks' => ['payment_intent.succeeded']],
GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']],
GatewayType::ALIPAY => ['refund' => false, 'token_billing' => false],
GatewayType::APPLE_PAY => ['refund' => false, 'token_billing' => false],
GatewayType::SOFORT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']], //Stripe
GatewayType::SEPA => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']],
GatewayType::PRZELEWY24 => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']],
GatewayType::GIROPAY => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']],
GatewayType::EPS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']],
GatewayType::BANCONTACT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']],
GatewayType::BECS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']],
GatewayType::IDEAL => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']],
GatewayType::ACSS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']],
GatewayType::SOFORT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
GatewayType::KLARNA => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
GatewayType::FPX => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']], ];
GatewayType::SEPA => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
GatewayType::PRZELEWY24 => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
GatewayType::GIROPAY => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
GatewayType::EPS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
GatewayType::BANCONTACT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
GatewayType::BECS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
GatewayType::IDEAL => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
GatewayType::ACSS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
GatewayType::FPX => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']],
];
break;
case 39:
return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true, 'webhooks' => [' ']]]; //Checkout
break;

View File

@ -11,6 +11,7 @@
namespace App\Models;
use App\Models\Filterable;
use App\Models\RecurringInvoice;
use App\Services\Subscription\SubscriptionService;
use Illuminate\Database\Eloquent\Factories\HasFactory;
@ -19,7 +20,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
class Subscription extends BaseModel
{
use HasFactory, SoftDeletes;
use HasFactory, SoftDeletes, Filterable;
protected $hidden = [
'id',

View File

@ -230,7 +230,7 @@ class CreditCard implements MethodInterface
private function completePayment($paymentRequest, PaymentResponseRequest $request)
{
$paymentRequest->amount = $this->checkout->payment_hash->data->value;
$paymentRequest->reference = $this->checkout->getDescription();
$paymentRequest->reference = substr($this->checkout->getDescription(),0 , 49);
$paymentRequest->customer = $this->checkout->getCustomer();
$paymentRequest->metadata = ['udf1' => 'Invoice Ninja'];
$paymentRequest->currency = $this->checkout->client->getCurrencyCode();

View File

@ -87,7 +87,7 @@ class CreditCard
//success
$cgt = [];
$cgt['token'] = $response->Customer->TokenCustomerID;
$cgt['token'] = strval($response->Customer->TokenCustomerID);
$cgt['payment_method_id'] = GatewayType::CREDIT_CARD;
$payment_meta = new \stdClass;

View File

@ -627,7 +627,9 @@ class StripePaymentDriver extends BaseDriver
public function processWebhookRequest(PaymentWebhookRequest $request)
{
// if($request->type === 'payment_intent.requires_action')
// nlog($request->all());
//payment_intent.succeeded - this will confirm or cancel the payment
if ($request->type === 'payment_intent.succeeded') {
PaymentIntentWebhook::dispatch($request->data, $request->company_key, $this->company_gateway->id)->delay(now()->addSeconds(rand(5, 10)));

View File

@ -596,7 +596,7 @@ class HtmlEngine
$data['$payments'] = ['value' => $payment_list, 'label' => ctrans('texts.payments')];
}
if($this->entity_string == 'invoice' && isset($this->company?->custom_fields?->company1))
if(($this->entity_string == 'invoice' || $this->entity_string == 'recurring_invoice') && isset($this->company?->custom_fields?->company1))
{
$data['$sepa_qr_code'] = ['value' => (new EpcQrGenerator($this->company, $this->entity,$data['$amount_raw']['value']))->getQrCode(), 'label' => ''];
}

957
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -14,8 +14,8 @@ return [
'require_https' => env('REQUIRE_HTTPS', true),
'app_url' => rtrim(env('APP_URL', ''), '/'),
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
'app_version' => '5.5.49',
'app_tag' => '5.5.49',
'app_version' => '5.5.50',
'app_tag' => '5.5.50',
'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', ''),

View File

@ -4912,6 +4912,16 @@ $LANG = array(
'notification_purchase_order_sent' => 'The following vendor :vendor was emailed Purchase Order :purchase_order for :amount.',
'subscription_blocked' => 'This product is a restricted item, please contact the vendor for further information.',
'subscription_blocked_title' => 'Product not available.',
'purchase_order_created' => 'Purchase Order Created',
'purchase_order_sent' => 'Purchase Order Sent',
'purchase_order_viewed' => 'Purchase Order Viewed',
'purchase_order_accepted' => 'Purchase Order Accepted',
'credit_payment_error' => 'The credit amount can not be greater than the payment amount',
'convert_payment_currency_help' => 'Set an exchange rate when entering a manual payment',
'convert_expense_currency_help' => 'Set an exchange rate when creating an expense',
'matomo_url' => 'Matomo URL',
'matomo_id' => 'Matomo Id',
'action_add_to_invoice' => 'Add To Invoice',
);
return $LANG;

View File

@ -3,7 +3,7 @@ const MANIFEST = 'flutter-app-manifest';
const TEMP = 'flutter-temp-cache';
const CACHE_NAME = 'flutter-app-cache';
const RESOURCES = {
"main.dart.js": "f55038b140208aba52df77b789b5a19a",
"main.dart.js": "9445fa11f73d795670b45a3413ddc6f1",
"canvaskit/canvaskit.wasm": "bf50631470eb967688cca13ee181af62",
"canvaskit/profiling/canvaskit.wasm": "95a45378b69e77af5ed2bc72b2209b94",
"canvaskit/profiling/canvaskit.js": "38164e5a72bdad0faa4ce740c9b8e564",
@ -299,9 +299,9 @@ const RESOURCES = {
"assets/assets/images/payment_types/carteblanche.png": "d936e11fa3884b8c9f1bd5c914be8629",
"assets/assets/google_fonts/Roboto-Regular.ttf": "8a36205bd9b83e03af0591a004bc97f4",
"assets/NOTICES": "1a34e70168d56fad075adfb4bdbb20eb",
"/": "e0f4109ad2212563540a468fdf6d87dd",
"/": "086c7ccb621482956fc20d188409794a",
"favicon.png": "dca91c54388f52eded692718d5a98b8b",
"version.json": "24384ab74df4959b7306375afd5b672f"
"version.json": "43e72e92e1557ca9db0b6a8ef41236ef"
};
// The application shell files that are downloaded before a service worker can

78993
public/main.dart.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

79821
public/main.foss.dart.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{"app_name":"invoiceninja_flutter","version":"5.0.103","build_number":"103","package_name":"invoiceninja_flutter"}
{"app_name":"invoiceninja_flutter","version":"5.0.104","build_number":"104","package_name":"invoiceninja_flutter"}

View File

@ -9,7 +9,7 @@
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
_paq.push(['setUserId', '{{ auth()->guard('contact')->user()->company->present()->name }}']);
_paq.push(['setUserId', '{{ auth()->guard('contact')->user()->present()->name() }}']);
(function() {
var u="{{ $company->matomo_url }}";
_paq.push(['setTrackerUrl', u+'matomo.php']);
@ -57,7 +57,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="@yield('meta_description')"/>
<!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}">
@ -169,7 +169,7 @@
}
</script>
@endif
</body>
<footer>

View File

@ -9,7 +9,7 @@
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
_paq.push(['setUserId', '{{ auth()->guard('contact')->user()->company->present()->name }}']);
_paq.push(['setUserId', '{{ auth()->guard('contact')->user()->present()->name() }}']);
(function() {
var u="{{ $company->matomo_url }}";
_paq.push(['setTrackerUrl', u+'matomo.php']);

View File

@ -9,7 +9,6 @@
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
_paq.push(['setUserId', '{{ auth()->guard('contact')->user()->company->present()->name }}']);
(function() {
var u="{{ $company->matomo_url }}";
_paq.push(['setTrackerUrl', u+'matomo.php']);
@ -46,7 +45,7 @@
@endif
<!-- Title -->
@auth()
@auth('contact')
<title>@yield('meta_title', '') {{ auth()->guard('contact')->user()->user->account->isPaid() ? auth()->guard('contact')->user()->company->present()->name() : 'Invoice Ninja' }}</title>
@endauth

View File

@ -3,8 +3,23 @@
<head>
<!-- Error: {{ session('error') }} -->
@if (config('services.analytics.tracking_id'))
@if (isset($company) && $company->matomo_url && $company->matomo_id)
<script>
var _paq = window._paq = window._paq || [];
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
_paq.push(['setUserId', '{{ auth()->guard('vendor')->user()->present()->name() }}']);
(function() {
var u="{{ $company->matomo_url }}";
_paq.push(['setTrackerUrl', u+'matomo.php']);
_paq.push(['setSiteId', '{{ $company->matomo_id }}']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
})();
</script>
<noscript><p><img src="{{ $company->matomo_url }}/matomo.php?idsite={{ $company->matomo_id }}&amp;rec=1" style="border:0;" alt="" /></p></noscript>
@elseif (config('services.analytics.tracking_id'))
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-122229484-1"></script>
<script>
window.dataLayer = window.dataLayer || [];
@ -42,7 +57,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="@yield('meta_description')"/>
<!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}">
@ -157,7 +172,7 @@
}
</script>
@endif
</body>
<footer>

View File

@ -31,7 +31,7 @@
@endif
<!-- Title -->
@auth()
@auth('contact')
<title>@yield('meta_title', '') {{ auth()->guard('contact')->user()->user->account->isPaid() ? auth()->guard('contact')->user()->company->present()->name() : 'Invoice Ninja' }}</title>
@endauth

View File

@ -70,6 +70,7 @@ use App\Http\Controllers\Reports\InvoiceItemReportController;
use App\Http\Controllers\Reports\InvoiceReportController;
use App\Http\Controllers\Reports\PaymentReportController;
use App\Http\Controllers\Reports\ProductReportController;
use App\Http\Controllers\Reports\ProductSalesReportController;
use App\Http\Controllers\Reports\ProfitAndLossController;
use App\Http\Controllers\Reports\QuoteItemReportController;
use App\Http\Controllers\Reports\QuoteReportController;
@ -270,6 +271,7 @@ Route::group(['middleware' => ['throttle:300,1', 'api_db', 'token_auth', 'locale
Route::post('reports/recurring_invoices', RecurringInvoiceReportController::class);
Route::post('reports/payments', PaymentReportController::class);
Route::post('reports/products', ProductReportController::class);
Route::post('reports/product_sales', ProductSalesReportController::class);
Route::post('reports/tasks', TaskReportController::class);
Route::post('reports/profitloss', ProfitAndLossController::class);

View File

@ -100,7 +100,7 @@ class CreditsTest extends TestCase
$c2->load('client');
$c3->load('client');
Livewire::test(CreditsTable::class, ['company' => $company])
Livewire::test(CreditsTable::class, ['company_id' => $company->id, 'db' => $company->db])
->assertDontSee('testing-number-01')
->assertSee('testing-number-02')
->assertSee('testing-number-03');
@ -167,7 +167,7 @@ class CreditsTest extends TestCase
$this->actingAs($client->contacts->first(), 'contact');
Livewire::test(CreditsTable::class, ['company' => $company])
Livewire::test(CreditsTable::class, ['company_id' => $company->id, 'db' => $company->db])
->assertSee('testing-number-01')
->assertSee('testing-number-02')
->assertSee('testing-number-03');

View File

@ -86,12 +86,12 @@ class InvoicesTest extends TestCase
$this->actingAs($client->contacts->first(), 'contact');
Livewire::test(InvoicesTable::class, ['company' => $company])
Livewire::test(InvoicesTable::class, ['company_id' => $company->id, 'db' => $company->db])
->assertSee($sent->number)
->assertSee($paid->number)
->assertSee($unpaid->number);
Livewire::test(InvoicesTable::class, ['company' => $company])
Livewire::test(InvoicesTable::class, ['company_id' => $company->id, 'db' => $company->db])
->set('status', ['paid'])
->assertSee($paid->number)
->assertDontSee($unpaid->number);

View File

@ -0,0 +1,212 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace Tests\Feature\Export;
use App\DataMapper\ClientSettings;
use App\DataMapper\CompanySettings;
use App\Export\CSV\ProductSalesExport;
use App\Factory\ExpenseCategoryFactory;
use App\Factory\ExpenseFactory;
use App\Factory\InvoiceFactory;
use App\Factory\InvoiceItemFactory;
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\ProductSalesExport
*/
class ProductSalesReportTest extends TestCase
{
use MakesHash;
public $faker;
protected 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 testProductSalesInstance()
{
$this->buildData();
$pl = new ProductSalesExport($this->company, $this->payload);
$this->assertInstanceOf(ProductSalesExport::class, $pl);
$this->account->delete();
}
public function testSimpleReport()
{
$this->buildData();
$client = Client::factory()->create([
'user_id' => $this->user->id,
'company_id' => $this->company->id,
'is_deleted' => 0,
]);
$this->payload = [
'start_date' => '2000-01-01',
'end_date' => '2030-01-11',
'date_range' => 'custom',
'client_id' => $client->id,
'report_keys' => []
];
$i = Invoice::factory()->create([
'client_id' => $client->id,
'user_id' => $this->user->id,
'company_id' => $this->company->id,
'amount' => 0,
'balance' => 0,
'status_id' => 2,
'total_taxes' => 1,
'date' => now()->format('Y-m-d'),
'terms' => 'nada',
'discount' => 0,
'tax_rate1' => 0,
'tax_rate2' => 0,
'tax_rate3' => 0,
'tax_name1' => '',
'tax_name2' => '',
'tax_name3' => '',
'uses_inclusive_taxes' => false,
'line_items' => $this->buildLineItems(),
]);
$i = $i->calc()->getInvoice();
$pl = new ProductSalesExport($this->company, $this->payload);
$response = $pl->run();
$this->assertIsString($response);
// nlog($response);
$this->account->delete();
}
private function buildLineItems()
{
$line_items = [];
$item = InvoiceItemFactory::create();
$item->quantity = 1;
$item->cost = 10;
$item->product_key = 'test';
$item->notes = 'test_product';
// $item->task_id = $this->encodePrimaryKey($this->task->id);
// $item->expense_id = $this->encodePrimaryKey($this->expense->id);
$line_items[] = $item;
$item = InvoiceItemFactory::create();
$item->quantity = 1;
$item->cost = 10;
$item->product_key = 'pumpkin';
$item->notes = 'test_pumpkin';
// $item->task_id = $this->encodePrimaryKey($this->task->id);
// $item->expense_id = $this->encodePrimaryKey($this->expense->id);
$line_items[] = $item;
return $line_items;
}
}

View File

@ -144,7 +144,7 @@ class ProfitAndLossReportTest extends TestCase
'balance' => 11,
'status_id' => 2,
'total_taxes' => 1,
'date' => '2022-01-01',
'date' => now()->format('Y-m-d'),
'terms' => 'nada',
'discount' => 0,
'tax_rate1' => 0,
@ -183,7 +183,7 @@ class ProfitAndLossReportTest extends TestCase
'balance' => 10,
'status_id' => 2,
'total_taxes' => 1,
'date' => '2022-01-01',
'date' => now()->format('Y-m-d'),
'terms' => 'nada',
'discount' => 0,
'tax_rate1' => 10,
@ -226,7 +226,7 @@ class ProfitAndLossReportTest extends TestCase
'balance' => 10,
'status_id' => 2,
'total_taxes' => 1,
'date' => '2022-01-01',
'date' => now()->format('Y-m-d'),
'terms' => 'nada',
'discount' => 0,
'tax_rate1' => 10,
@ -282,7 +282,7 @@ class ProfitAndLossReportTest extends TestCase
'balance' => 10,
'status_id' => 2,
'total_taxes' => 0,
'date' => '2022-01-01',
'date' => now()->format('Y-m-d'),
'terms' => 'nada',
'discount' => 0,
'tax_rate1' => 0,
@ -313,7 +313,7 @@ class ProfitAndLossReportTest extends TestCase
'amount' => 10,
'company_id' => $this->company->id,
'user_id' => $this->user->id,
'date' => '2022-01-01',
'date' => now()->format('Y-m-d'),
]);
$pl = new ProfitLoss($this->company, $this->payload);
@ -334,7 +334,7 @@ class ProfitAndLossReportTest extends TestCase
$e = ExpenseFactory::create($this->company->id, $this->user->id);
$e->amount = 10;
$e->date = '2022-01-01';
$e->date = now()->format('Y-m-d');
$e->calculate_tax_by_amount = true;
$e->tax_amount1 = 10;
$e->save();
@ -358,7 +358,7 @@ class ProfitAndLossReportTest extends TestCase
$e = ExpenseFactory::create($this->company->id, $this->user->id);
$e->amount = 10;
$e->date = '2022-01-01';
$e->date = now()->format('Y-m-d');
$e->tax_rate1 = 10;
$e->tax_name1 = 'GST';
$e->uses_inclusive_taxes = false;
@ -383,7 +383,7 @@ class ProfitAndLossReportTest extends TestCase
$e = ExpenseFactory::create($this->company->id, $this->user->id);
$e->amount = 10;
$e->date = '2022-01-01';
$e->date = now()->format('Y-m-d');
$e->tax_rate1 = 10;
$e->tax_name1 = 'GST';
$e->uses_inclusive_taxes = false;
@ -410,7 +410,7 @@ class ProfitAndLossReportTest extends TestCase
'amount' => 10,
'company_id' => $this->company->id,
'user_id' => $this->user->id,
'date' => '2022-01-01',
'date' => now()->format('Y-m-d'),
'exchange_rate' => 1,
'currency_id' => $this->company->settings->currency_id,
]);
@ -440,7 +440,7 @@ class ProfitAndLossReportTest extends TestCase
'amount' => 10,
'company_id' => $this->company->id,
'user_id' => $this->user->id,
'date' => '2022-01-01',
'date' => now()->format('Y-m-d'),
'exchange_rate' => 1,
'currency_id' => $this->company->settings->currency_id,
]);
@ -454,7 +454,7 @@ class ProfitAndLossReportTest extends TestCase
'amount' => 10,
'company_id' => $this->company->id,
'user_id' => $this->user->id,
'date' => '2022-01-01',
'date' => now()->format('Y-m-d'),
'exchange_rate' => 1,
'currency_id' => $this->company->settings->currency_id,
]);
@ -489,7 +489,7 @@ class ProfitAndLossReportTest extends TestCase
'balance' => 10,
'status_id' => 2,
'total_taxes' => 1,
'date' => '2022-01-01',
'date' => now()->format('Y-m-d'),
'terms' => 'nada',
'discount' => 0,
'tax_rate1' => 10,
@ -510,7 +510,7 @@ class ProfitAndLossReportTest extends TestCase
'amount' => 10,
'company_id' => $this->company->id,
'user_id' => $this->user->id,
'date' => '2022-01-01',
'date' => now()->format('Y-m-d'),
'exchange_rate' => 1,
'currency_id' => $this->company->settings->currency_id,
]);
@ -524,7 +524,7 @@ class ProfitAndLossReportTest extends TestCase
'amount' => 10,
'company_id' => $this->company->id,
'user_id' => $this->user->id,
'date' => '2022-01-01',
'date' => now()->format('Y-m-d'),
'exchange_rate' => 1,
'currency_id' => $this->company->settings->currency_id,
]);

View File

@ -37,12 +37,12 @@ class PdfCreatorTest extends TestCase
);
}
public function testCreditPdfCreated()
{
$credit_path = (new CreateEntityPdf($this->credit->invitations->first()))->handle();
// public function testCreditPdfCreated()
// {
// $credit_path = (new CreateEntityPdf($this->credit->invitations->first()))->handle();
$this->assertTrue(Storage::exists($credit_path));
}
// $this->assertTrue(Storage::exists($credit_path));
// }
public function testInvoicePdfCreated()
{

View File

@ -32,6 +32,7 @@ use App\Models\ClientContact;
use App\Models\Company;
use App\Models\CompanyGateway;
use App\Models\CompanyToken;
use App\Models\Credit;
use App\Models\CreditInvitation;
use App\Models\Expense;
use App\Models\ExpenseCategory;
@ -105,6 +106,11 @@ trait MockAccountData
*/
public $recurring_quote;
/**
* @var
*/
public $credit;
/**
* @var
*/
@ -477,6 +483,49 @@ trait MockAccountData
$this->quote->save();
$this->credit = Credit::factory()->create([
'user_id' => $user_id,
'client_id' => $this->client->id,
'company_id' => $this->company->id,
]);
$this->credit->line_items = $this->buildLineItems();
$this->credit->uses_inclusive_taxes = false;
$this->credit->save();
$this->credit_calc = new InvoiceSum($this->credit);
$this->credit_calc->build();
$this->credit = $this->credit_calc->getCredit();
$this->credit->status_id = Quote::STATUS_SENT;
$this->credit->number = $this->getNextCreditNumber($this->client, $this->credit);
CreditInvitation::factory()->create([
'user_id' => $user_id,
'company_id' => $this->company->id,
'client_contact_id' => $contact->id,
'credit_id' => $this->credit->id,
]);
CreditInvitation::factory()->create([
'user_id' => $user_id,
'company_id' => $this->company->id,
'client_contact_id' => $contact2->id,
'credit_id' => $this->credit->id,
]);
$this->credit->setRelation('client', $this->client);
$this->credit->setRelation('company', $this->company);
$this->credit->save();
$this->credit->service()->createInvitations()->markSent();
$this->purchase_order = PurchaseOrderFactory::create($this->company->id, $user_id);
$this->purchase_order->vendor_id = $this->vendor->id;

View File

@ -115,8 +115,8 @@ class GeneratesConvertedQuoteCounterTest extends TestCase
$this->assertNotNull($invoice);
$this->assertEquals('2022-Q0001', $quote->number);
$this->assertEquals('2022-I0001', $invoice->number);
$this->assertEquals(now()->format('Y'). '-Q0001', $quote->number);
$this->assertEquals(now()->format('Y'). '-I0001', $invoice->number);
$settings = $this->client->getMergedSettings();
$settings->invoice_number_counter = 100;