mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
Merge branch 'v5-develop' into designer
This commit is contained in:
commit
174d0d3338
@ -1 +1 @@
|
||||
5.5.49
|
||||
5.5.50
|
@ -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){
|
||||
|
@ -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();
|
||||
|
||||
|
138
app/Export/CSV/ProductSalesExport.php
Normal file
138
app/Export/CSV/ProductSalesExport.php
Normal 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;
|
||||
}
|
||||
}
|
@ -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') {
|
||||
|
@ -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;
|
||||
|
129
app/Filters/SubscriptionFilters.php
Normal file
129
app/Filters/SubscriptionFilters.php
Normal 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();
|
||||
}
|
||||
}
|
@ -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()
|
||||
{
|
||||
|
@ -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());
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
71
app/Http/Requests/Report/ProductSalesReportRequest.php
Normal file
71
app/Http/Requests/Report/ProductSalesReportRequest.php
Normal 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);
|
||||
}
|
||||
}
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
111
app/Jobs/Invoice/InvoiceCheckLateWebhook.php
Normal file
111
app/Jobs/Invoice/InvoiceCheckLateWebhook.php
Normal 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));
|
||||
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
|
||||
|
@ -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)) {
|
||||
|
@ -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;
|
||||
|
@ -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',
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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)));
|
||||
|
@ -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
957
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -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', ''),
|
||||
|
@ -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;
|
||||
|
6
public/flutter_service_worker.js
vendored
6
public/flutter_service_worker.js
vendored
@ -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
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
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
2518
public/main.profile.dart.js
vendored
2518
public/main.profile.dart.js
vendored
File diff suppressed because one or more lines are too long
@ -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"}
|
@ -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>
|
||||
|
@ -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']);
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 }}&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>
|
||||
|
@ -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
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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');
|
||||
|
@ -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);
|
||||
|
212
tests/Feature/Export/ProductSalesReportTest.php
Normal file
212
tests/Feature/Export/ProductSalesReportTest.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
@ -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,
|
||||
]);
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user