commit
bf6526683f
60 changed files with 219122 additions and 217916 deletions
|
|
@ -10,9 +10,7 @@
|
|||
|
||||
## [Hosted](https://www.invoiceninja.com) | [Self-Hosted](https://www.invoiceninja.org)
|
||||
|
||||
Join us on [Slack](http://slack.invoiceninja.com), [Discord](https://discord.com/channels/1071654583870435439/1071654584390537279) [Discourse](https://forum.invoiceninja.com) -
|
||||
or [StackOverflow](https://stackoverflow.com/tags/invoice-ninja/) if you like,
|
||||
just make sure to add the `invoice-ninja` tag to your question.
|
||||
Join us on [Slack](http://slack.invoiceninja.com), [Discord](https://discord.gg/ZwEdtfCwXA), [Support Forum](https://forum.invoiceninja.com)
|
||||
|
||||
## Introduction
|
||||
|
||||
|
|
@ -27,13 +25,13 @@ We offer a $30 per year white-label license to remove the Invoice Ninja branding
|
|||
* [API Documentation](https://app.swaggerhub.com/apis/invoiceninja/invoiceninja)
|
||||
* [APP Documentation](https://invoiceninja.github.io/)
|
||||
* [Support Forum](https://forum.invoiceninja.com)
|
||||
* [StackOverflow](https://stackoverflow.com/tags/invoice-ninja/)
|
||||
|
||||
## Setup
|
||||
|
||||
### Mobile Apps
|
||||
* [iPhone](https://apps.apple.com/app/id1503970375?platform=iphone)
|
||||
* [Android](https://play.google.com/store/apps/details?id=com.invoiceninja.app)
|
||||
* [F-Droid](https://f-droid.org/en/packages/com.invoiceninja.app)
|
||||
|
||||
### Desktop Apps
|
||||
* [macOS](https://apps.apple.com/app/id1503970375?platform=mac)
|
||||
|
|
@ -55,7 +53,7 @@ We offer a $30 per year white-label license to remove the Invoice Ninja branding
|
|||
git clone https://github.com/invoiceninja/invoiceninja.git
|
||||
git checkout v5-stable
|
||||
cp .env.example .env
|
||||
composer update
|
||||
composer i -o --no-dev
|
||||
php artisan key:generate
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
5.5.68
|
||||
5.5.69
|
||||
|
|
@ -834,7 +834,6 @@ class CompanySettings extends BaseSettings
|
|||
'$client.address1',
|
||||
'$client.address2',
|
||||
'$client.city_state_postal',
|
||||
'$client.postal_city',
|
||||
'$client.country',
|
||||
'$client.phone',
|
||||
'$contact.email',
|
||||
|
|
@ -846,7 +845,6 @@ class CompanySettings extends BaseSettings
|
|||
'$vendor.address1',
|
||||
'$vendor.address2',
|
||||
'$vendor.city_state_postal',
|
||||
'$vendor.postal_city',
|
||||
'$vendor.country',
|
||||
'$vendor.phone',
|
||||
'$contact.email',
|
||||
|
|
@ -871,7 +869,6 @@ class CompanySettings extends BaseSettings
|
|||
'$company.address1',
|
||||
'$company.address2',
|
||||
'$company.city_state_postal',
|
||||
'$company.postal_city',
|
||||
'$company.country',
|
||||
],
|
||||
'invoice_details' => [
|
||||
|
|
|
|||
|
|
@ -1031,6 +1031,11 @@ class BaseController extends Controller
|
|||
public function flutterRoute()
|
||||
{
|
||||
if ((bool) $this->checkAppSetup() !== false && $account = Account::first()) {
|
||||
|
||||
//always redirect invoicing.co to invoicing.co
|
||||
if(Ninja::isHosted() && (request()->getSchemeAndHttpHost() != 'https://invoicing.co'))
|
||||
return redirect()->secure('https://invoicing.co');
|
||||
|
||||
if (config('ninja.require_https') && ! request()->isSecure()) {
|
||||
return redirect()->secure(request()->getRequestUri());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -322,6 +322,7 @@ class BillingPortalPurchasev2 extends Component
|
|||
'total' => $total,
|
||||
'qty' => $qty,
|
||||
'is_recurring' => true,
|
||||
'product_image' => $p->product_image,
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ class GenericReportRequest extends Request
|
|||
{
|
||||
$input = $this->all();
|
||||
|
||||
if (! array_key_exists('date_range', $input)) {
|
||||
if (! array_key_exists('date_range', $input) || $input['date_range'] == '') {
|
||||
$input['date_range'] = 'all';
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ class ProductSalesReportRequest extends Request
|
|||
{
|
||||
$input = $this->all();
|
||||
|
||||
if (! array_key_exists('date_range', $input)) {
|
||||
if (! array_key_exists('date_range', $input) || $input['date_range'] == '') {
|
||||
$input['date_range'] = 'all';
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ class ProfitLossRequest extends Request
|
|||
{
|
||||
$input = $this->all();
|
||||
|
||||
if (! array_key_exists('date_range', $input)) {
|
||||
if (! array_key_exists('date_range', $input) || $input['date_range'] == '') {
|
||||
$input['date_range'] = 'all';
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -195,4 +195,56 @@ class Request extends FormRequest
|
|||
public function prepareForValidation()
|
||||
{
|
||||
}
|
||||
|
||||
public function checkTimeLog(array $log): bool
|
||||
{
|
||||
if(count($log) == 0)
|
||||
return true;
|
||||
|
||||
/*Get first value of all arrays*/
|
||||
$result = array_column($log, 0);
|
||||
|
||||
/*Sort the array in ascending order*/
|
||||
asort($result);
|
||||
|
||||
$new_array = [];
|
||||
|
||||
/*Rebuild the array in order*/
|
||||
foreach($result as $key => $value)
|
||||
$new_array[] = $log[$key];
|
||||
|
||||
/*Iterate through the array and perform checks*/
|
||||
foreach($new_array as $key => $array)
|
||||
{
|
||||
/*Flag which helps us know if there is a NEXT timelog*/
|
||||
$next = false;
|
||||
/* If there are more than 1 time log in the array, ensure the last timestamp is not zero*/
|
||||
if(count($new_array) >1 && $array[1] == 0)
|
||||
return false;
|
||||
|
||||
/* Check if the start time is greater than the end time */
|
||||
/* Ignore the last value for now, we'll do a separate check for this */
|
||||
if($array[0] > $array[1] && $array[1] != 0)
|
||||
return false;
|
||||
|
||||
/* Find the next time log value - if it exists */
|
||||
if(array_key_exists($key+1, $new_array))
|
||||
$next = $new_array[$key+1];
|
||||
|
||||
/* check the next time log and ensure the start time is GREATER than the end time of the previous record */
|
||||
if($next && $next[0] < $array[1])
|
||||
return false;
|
||||
|
||||
/* Get the last row of the timelog*/
|
||||
$last_row = end($new_array);
|
||||
|
||||
/*If the last value is NOT zero, ensure start time is not GREATER than the endtime */
|
||||
if($last_row[1] != 0 && $last_row[0] > $last_row[1])
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,9 @@ class StoreTaskRequest extends Request
|
|||
$fail('The '.$attribute.' - '.print_r($k,1).' is invalid. Unix timestamps only.');
|
||||
}
|
||||
|
||||
if(!$this->checkTimeLog($values))
|
||||
$fail('Please correct overlapping values');
|
||||
|
||||
}];
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -59,6 +59,9 @@ class UpdateTaskRequest extends Request
|
|||
$fail('The '.$attribute.' - '.print_r($k,1).' is invalid. Unix timestamps only.');
|
||||
}
|
||||
|
||||
if(!$this->checkTimeLog($values))
|
||||
$fail('Please correct overlapping values');
|
||||
|
||||
}];
|
||||
|
||||
return $this->globalRules($rules);
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ class StoreWebhookRequest extends Request
|
|||
{
|
||||
$input = $this->all();
|
||||
|
||||
if(!isset($input['rest_method']))
|
||||
$input['rest_method'] = 'post';
|
||||
// if(isset($input['headers']) && count($input['headers']) == 0)
|
||||
// $input['headers'] = null;
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,9 @@ class UpdateWebhookRequest extends Request
|
|||
{
|
||||
$input = $this->all();
|
||||
|
||||
if(!isset($input['rest_method']))
|
||||
$input['rest_method'] = 'post';
|
||||
|
||||
// if(isset($input['headers']) && count($input['headers']) == 0)
|
||||
// $input['headers'] = null;
|
||||
|
||||
|
|
|
|||
|
|
@ -138,6 +138,17 @@ class InvoiceEmailEngine extends BaseEmailEngine
|
|||
if ($this->client->getSetting('document_email_attachment') !== false && $this->invoice->company->account->hasFeature(Account::FEATURE_DOCUMENTS)) {
|
||||
|
||||
|
||||
if($this->invoice->recurring_invoice()->exists())
|
||||
{
|
||||
foreach ($this->invoice->recurring_invoice->documents as $document) {
|
||||
|
||||
if($document->size > $this->max_attachment_size)
|
||||
$this->setAttachmentLinks(["<a class='doc_links' href='" . URL::signedRoute('documents.public_download', ['document_hash' => $document->hash]) ."'>". $document->name ."</a>"]);
|
||||
else
|
||||
$this->setAttachments([['file' => base64_encode($document->getFile()), 'path' => $document->filePath(), 'name' => $document->name, 'mime' => NULL, ]]);
|
||||
}
|
||||
}
|
||||
|
||||
// Storage::url
|
||||
foreach ($this->invoice->documents as $document) {
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
|||
class BankTransactionRule extends BaseModel
|
||||
{
|
||||
use SoftDeletes;
|
||||
use MakesHash;
|
||||
use Filterable;
|
||||
|
||||
protected $fillable = [
|
||||
|
|
@ -66,6 +65,37 @@ class BankTransactionRule extends BaseModel
|
|||
|
||||
private array $search_results = [];
|
||||
|
||||
public function getEntityType()
|
||||
{
|
||||
return self::class;
|
||||
}
|
||||
|
||||
public function company()
|
||||
{
|
||||
return $this->belongsTo(Company::class);
|
||||
}
|
||||
|
||||
public function vendor()
|
||||
{
|
||||
return $this->belongsTo(Vendor::class);
|
||||
}
|
||||
|
||||
public function client()
|
||||
{
|
||||
return $this->belongsTo(Client::class);
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class)->withTrashed();
|
||||
}
|
||||
|
||||
public function expense_category()
|
||||
{
|
||||
return $this->belongsTo(ExpenseCategory::class, 'category_id')->withTrashed();
|
||||
}
|
||||
|
||||
|
||||
// rule object looks like this:
|
||||
//[
|
||||
// {
|
||||
|
|
@ -138,34 +168,5 @@ class BankTransactionRule extends BaseModel
|
|||
// }
|
||||
// }
|
||||
|
||||
public function getEntityType()
|
||||
{
|
||||
return self::class;
|
||||
}
|
||||
|
||||
public function company()
|
||||
{
|
||||
return $this->belongsTo(Company::class);
|
||||
}
|
||||
|
||||
public function vendor()
|
||||
{
|
||||
return $this->belongsTo(Vendor::class);
|
||||
}
|
||||
|
||||
public function client()
|
||||
{
|
||||
return $this->belongsTo(Client::class);
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class)->withTrashed();
|
||||
}
|
||||
|
||||
public function expense_cateogry()
|
||||
{
|
||||
return $this->belongsTo(ExpenseCategory::class)->withTrashed();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@
|
|||
namespace App\Models;
|
||||
|
||||
use App\DataMapper\ClientSettings;
|
||||
use App\Jobs\Util\WebhookHandler;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Utils\Traits\UserSessionAttributes;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
|
@ -167,7 +168,7 @@ class BaseModel extends Model
|
|||
*/
|
||||
public function resolveRouteBinding($value, $field = null)
|
||||
{
|
||||
|
||||
|
||||
if (is_numeric($value)) {
|
||||
throw new ModelNotFoundException("Record with value {$value} not found");
|
||||
}
|
||||
|
|
@ -189,12 +190,12 @@ class BaseModel extends Model
|
|||
|
||||
public function numberFormatter()
|
||||
{
|
||||
$number = strlen($this->number) >= 1 ? $this->translate_entity() . "_" . $this->number : class_basename($this) . "_" . Str::random(5);
|
||||
$number = strlen($this->number) >= 1 ? $this->translate_entity() . "_" . $this->number : class_basename($this) . "_" . Str::random(5);
|
||||
|
||||
$formatted_number = mb_ereg_replace("([^\w\s\d\-_~,;\[\]\(\).])", '', $number);
|
||||
|
||||
|
||||
$formatted_number = mb_ereg_replace("([\.]{2,})", '', $formatted_number);
|
||||
|
||||
|
||||
$formatted_number = preg_replace('/\s+/', '_', $formatted_number);
|
||||
|
||||
return $formatted_number;
|
||||
|
|
@ -205,4 +206,24 @@ class BaseModel extends Model
|
|||
return ctrans('texts.item');
|
||||
}
|
||||
|
||||
/**
|
||||
* Model helper to send events for webhooks
|
||||
*
|
||||
* @param int $event_id
|
||||
* @param string $additional_data optional includes
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function sendEvent(int $event_id, string $additional_data = ""): void
|
||||
{
|
||||
$subscriptions = Webhook::where('company_id', $this->company_id)
|
||||
->where('event_id', $event_id)
|
||||
->exists();
|
||||
|
||||
if ($subscriptions) {
|
||||
WebhookHandler::dispatch($event_id, $this, $this->company, $additional_data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ class Product extends BaseModel
|
|||
'in_stock_quantity',
|
||||
'stock_notification_threshold',
|
||||
'stock_notification',
|
||||
'max_quantity',
|
||||
'product_image',
|
||||
];
|
||||
|
||||
protected $touches = [];
|
||||
|
|
|
|||
|
|
@ -136,6 +136,14 @@ class Webhook extends BaseModel
|
|||
|
||||
const EVENT_ARCHIVE_PURCHASE_ORDER = 59; //tested
|
||||
|
||||
const EVENT_SENT_INVOICE = 60;
|
||||
|
||||
const EVENT_SENT_QUOTE = 61;
|
||||
|
||||
const EVENT_SENT_CREDIT = 62;
|
||||
|
||||
const EVENT_SENT_PURCHASE_ORDER = 63;
|
||||
|
||||
public static $valid_events = [
|
||||
self::EVENT_CREATE_PURCHASE_ORDER,
|
||||
self::EVENT_UPDATE_PURCHASE_ORDER,
|
||||
|
|
@ -194,7 +202,11 @@ class Webhook extends BaseModel
|
|||
self::EVENT_RESTORE_QUOTE,
|
||||
self::EVENT_RESTORE_INVOICE,
|
||||
self::EVENT_RESTORE_PAYMENT,
|
||||
self::EVENT_RESTORE_VENDOR
|
||||
self::EVENT_RESTORE_VENDOR,
|
||||
self::EVENT_SENT_INVOICE,
|
||||
self::EVENT_SENT_QUOTE,
|
||||
self::EVENT_SENT_CREDIT,
|
||||
self::EVENT_SENT_PURCHASE_ORDER
|
||||
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ class ClientContactObserver
|
|||
|
||||
CreditInvitation::withTrashed()->where('client_contact_id', $client_contact_id)->cursor()->each(function ($invite){
|
||||
|
||||
if($invite->credits()->doesnthave('invitations'))
|
||||
if($invite->credit()->doesnthave('invitations'))
|
||||
$invite->credit->service()->createInvitations();
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ class ClientObserver
|
|||
->exists();
|
||||
|
||||
if ($subscriptions)
|
||||
WebhookHandler::dispatch($event, $client, $client->company)->delay(0);
|
||||
WebhookHandler::dispatch($event, $client, $client->company, 'client')->delay(0);
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ class InvoiceObserver
|
|||
->exists();
|
||||
|
||||
if ($subscriptions)
|
||||
WebhookHandler::dispatch($event, $invoice, $invoice->company)->delay(0);
|
||||
WebhookHandler::dispatch($event, $invoice, $invoice->company, 'client')->delay(0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -43,6 +43,11 @@ class CustomPaymentDriver extends BaseDriver
|
|||
return $types;
|
||||
}
|
||||
|
||||
public function init()
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setPaymentMethod($payment_method_id)
|
||||
{
|
||||
$this->payment_method = $payment_method_id;
|
||||
|
|
@ -101,4 +106,9 @@ class CustomPaymentDriver extends BaseDriver
|
|||
{
|
||||
// Driver doesn't support this feature.
|
||||
}
|
||||
|
||||
public function getClientRequiredFields(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@
|
|||
namespace App\Services\Credit;
|
||||
|
||||
use App\Events\Credit\CreditWasMarkedSent;
|
||||
use App\Jobs\Util\WebhookHandler;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Webhook;
|
||||
use App\Utils\Ninja;
|
||||
|
||||
class MarkSent
|
||||
|
|
@ -49,9 +51,11 @@ class MarkSent
|
|||
->service()
|
||||
->adjustCreditBalance($this->credit->amount)
|
||||
->save();
|
||||
|
||||
|
||||
event(new CreditWasMarkedSent($this->credit, $this->credit->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||
|
||||
$this->credit->sendEvent(Webhook::EVENT_SENT_CREDIT);
|
||||
|
||||
return $this->credit;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ class ApplyNumber extends AbstractService
|
|||
|
||||
public function run()
|
||||
{
|
||||
|
||||
if ($this->invoice->number != '') {
|
||||
return $this->invoice;
|
||||
}
|
||||
|
|
@ -45,7 +46,7 @@ class ApplyNumber extends AbstractService
|
|||
$this->trySaving();
|
||||
break;
|
||||
case 'when_sent':
|
||||
if ($this->invoice->status_id == Invoice::STATUS_SENT) {
|
||||
if ($this->invoice->status_id >= Invoice::STATUS_SENT) {
|
||||
$this->trySaving();
|
||||
}
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -12,8 +12,10 @@
|
|||
namespace App\Services\Invoice;
|
||||
|
||||
use App\Events\Invoice\InvoiceWasUpdated;
|
||||
use App\Jobs\Util\WebhookHandler;
|
||||
use App\Models\Client;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Webhook;
|
||||
use App\Services\AbstractService;
|
||||
use App\Utils\Ninja;
|
||||
|
||||
|
|
@ -71,6 +73,7 @@ class MarkSent extends AbstractService
|
|||
if($fire_webhook)
|
||||
event('eloquent.updated: App\Models\Invoice', $this->invoice);
|
||||
|
||||
$this->invoice->sendEvent(Webhook::EVENT_SENT_INVOICE, "client");
|
||||
|
||||
return $this->invoice->fresh();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,7 +85,11 @@ class UpdateInvoicePayment
|
|||
|
||||
if($invoice->is_proforma)
|
||||
{
|
||||
$invoice->number = '';
|
||||
|
||||
if(strlen($invoice->number) > 1 && str_starts_with($invoice->number,"####"))
|
||||
$invoice->number = '';
|
||||
|
||||
|
||||
$invoice->is_proforma = false;
|
||||
|
||||
$invoice->service()
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@
|
|||
|
||||
namespace App\Services\PurchaseOrder;
|
||||
|
||||
use App\Jobs\Util\WebhookHandler;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\Webhook;
|
||||
use App\Utils\Ninja;
|
||||
|
||||
class MarkSent
|
||||
|
|
@ -43,6 +45,8 @@ class MarkSent
|
|||
->adjustBalance($this->purchase_order->amount) //why was this commented out previously?
|
||||
->save();
|
||||
|
||||
$this->purchase_order->sendEvent(Webhook::EVENT_SENT_PURCHASE_ORDER, "vendor");
|
||||
|
||||
return $this->purchase_order;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@
|
|||
namespace App\Services\Quote;
|
||||
|
||||
use App\Events\Quote\QuoteWasMarkedSent;
|
||||
use App\Jobs\Util\WebhookHandler;
|
||||
use App\Models\Quote;
|
||||
use App\Models\Webhook;
|
||||
use App\Utils\Ninja;
|
||||
use Carbon\Carbon;
|
||||
|
||||
|
|
@ -52,6 +54,8 @@ class MarkSent
|
|||
|
||||
event(new QuoteWasMarkedSent($this->quote, $this->quote->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||
|
||||
$this->quote->sendEvent(Webhook::EVENT_SENT_QUOTE, "client");
|
||||
|
||||
return $this->quote;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -958,7 +958,7 @@ class SubscriptionService
|
|||
$invoice->subscription_id = $this->subscription->id;
|
||||
$invoice->client_id = $client_id;
|
||||
$invoice->is_proforma = true;
|
||||
$invoice->number = ctrans('texts.subscription') . "_" . now()->format('Y-m-d') . "_" . rand(0,100000);
|
||||
$invoice->number = "####" . ctrans('texts.subscription') . "_" . now()->format('Y-m-d') . "_" . rand(0,100000);
|
||||
$line_items = $bundle->map(function ($item){
|
||||
|
||||
$line_item = new InvoiceItem;
|
||||
|
|
|
|||
|
|
@ -11,12 +11,14 @@
|
|||
|
||||
namespace App\Transformers;
|
||||
|
||||
use App\Models\Account;
|
||||
use App\Models\BankTransaction;
|
||||
use App\Models\BankTransactionRule;
|
||||
use App\Models\Client;
|
||||
use App\Models\Company;
|
||||
use App\Models\Expense;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\ExpenseCategory;
|
||||
use App\Models\Vendor;
|
||||
use App\Transformers\ExpenseCategoryTransformer;
|
||||
use App\Transformers\ExpenseCateogryTransformer;
|
||||
use App\Transformers\VendorTransformer;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
|
||||
|
|
@ -75,16 +77,35 @@ class BankTransactionRuleTransformer extends EntityTransformer
|
|||
|
||||
public function includeClient(BankTransactionRule $bank_transaction_rule)
|
||||
{
|
||||
$transformer = new ClientTransformer($this->serializer);
|
||||
|
||||
return $this->includeItem($bank_transaction_rule->expense, $transformer, Client::class);
|
||||
if(!$bank_transaction_rule->client)
|
||||
return null;
|
||||
|
||||
$transformer = new ClientTransformer($this->serializer);
|
||||
|
||||
return $this->includeItem($bank_transaction_rule->client, $transformer, Client::class);
|
||||
}
|
||||
|
||||
public function includeVendor(BankTransactionRule $bank_transaction_rule)
|
||||
{
|
||||
|
||||
if(!$bank_transaction_rule->vendor)
|
||||
return null;
|
||||
|
||||
$transformer = new VendorTransformer($this->serializer);
|
||||
|
||||
return $this->includeItem($bank_transaction_rule->vendor, $transformer, Vendor::class);
|
||||
}
|
||||
|
||||
public function includeExpenseCategory(BankTransactionRule $bank_transaction_rule)
|
||||
{
|
||||
|
||||
if(!$bank_transaction_rule->expense_category)
|
||||
return null;
|
||||
|
||||
$transformer = new ExpenseCategoryTransformer($this->serializer);
|
||||
|
||||
return $this->includeItem($bank_transaction_rule->expense_category, $transformer, ExpenseCategory::class);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,6 +93,8 @@ class ProductTransformer extends EntityTransformer
|
|||
'in_stock_quantity' => (int) $product->in_stock_quantity ?: 0,
|
||||
'stock_notification' => (bool) $product->stock_notification,
|
||||
'stock_notification_threshold' => (int) $product->stock_notification_threshold,
|
||||
'max_quantity' => (int) $product->max_quantity,
|
||||
'product_image' => (string) $product->product_image ?: '',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -223,15 +223,15 @@ class Number
|
|||
|
||||
/* 08-01-2022 allow increased precision for unit price*/
|
||||
$v = rtrim(sprintf('%f', $value), '0');
|
||||
// $precision = strlen(substr(strrchr($v, $decimal), 1));
|
||||
|
||||
if ($v < 1) {
|
||||
/* 08-02-2023 special if block to render $0.5 to $0.50*/
|
||||
if ($v < 1 && strlen($v) == 3) {
|
||||
$precision = 2;
|
||||
}
|
||||
elseif ($v < 1) {
|
||||
$precision = strlen($v) - strrpos($v, '.') - 1;
|
||||
}
|
||||
|
||||
// if($precision == 1)
|
||||
// $precision = 2;
|
||||
|
||||
$value = number_format($v, $precision, $decimal, $thousand);
|
||||
$symbol = $currency->symbol;
|
||||
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@
|
|||
"omnipay/paypal": "^3.0",
|
||||
"payfast/payfast-php-sdk": "^1.1",
|
||||
"pragmarx/google2fa": "^8.0",
|
||||
"turbo124/predis": "^1.1",
|
||||
"predis/predis": "2.*",
|
||||
"razorpay/razorpay": "2.*",
|
||||
"sentry/sentry-laravel": "^3",
|
||||
"setasign/fpdf": "^1.8",
|
||||
|
|
|
|||
690
composer.lock
generated
690
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.68',
|
||||
'app_tag' => '5.5.68',
|
||||
'app_version' => '5.5.69',
|
||||
'app_tag' => '5.5.69',
|
||||
'minimum_client_version' => '5.0.16',
|
||||
'terms_version' => '1.0.1',
|
||||
'api_secret' => env('API_SECRET', ''),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Company;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('products', function (Blueprint $table){
|
||||
$table->unsignedInteger("max_quantity")->nullable();
|
||||
$table->string("product_image", 191)->nullable();
|
||||
});
|
||||
|
||||
Company::query()
|
||||
->chunk(1000, function ($companies) {
|
||||
|
||||
foreach($companies as $c)
|
||||
{
|
||||
|
||||
$settings = $c->settings;
|
||||
$settings->font_size = 16;
|
||||
$c->settings = $settings;
|
||||
$c->save();
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
};
|
||||
4
public/flutter_service_worker.js
vendored
4
public/flutter_service_worker.js
vendored
|
|
@ -4,8 +4,8 @@ const TEMP = 'flutter-temp-cache';
|
|||
const CACHE_NAME = 'flutter-app-cache';
|
||||
const RESOURCES = {
|
||||
"flutter.js": "f85e6fb278b0fd20c349186fb46ae36d",
|
||||
"/": "6a3595d7c5906d433b5fb9a8a70c928d",
|
||||
"main.dart.js": "18a61fecfbb7fe6dab328ee81428b7da",
|
||||
"/": "0a440124fb4146691e44c002bbf24bb8",
|
||||
"main.dart.js": "811f1fb072771983db12e1a6f2c3b89d",
|
||||
"manifest.json": "ef43d90e57aa7682d7e2cfba2f484a40",
|
||||
"favicon.ico": "51636d3a390451561744c42188ccd628",
|
||||
"favicon.png": "dca91c54388f52eded692718d5a98b8b",
|
||||
|
|
|
|||
2
public/js/clients/quotes/approve.js
vendored
2
public/js/clients/quotes/approve.js
vendored
|
|
@ -1,2 +1,2 @@
|
|||
/*! For license information please see approve.js.LICENSE.txt */
|
||||
(()=>{function e(e,t){for(var n=0;n<t.length;n++){var u=t[n];u.enumerable=u.enumerable||!1,u.configurable=!0,"value"in u&&(u.writable=!0),Object.defineProperty(e,u.key,u)}}var t=function(){function t(e,n,u){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,t),this.shouldDisplaySignature=e,this.shouldDisplayTerms=n,this.shouldDisplayUserInput=u,this.termsAccepted=!1}var n,u,a;return n=t,(u=[{key:"submitForm",value:function(){document.getElementById("approve-form").submit()}},{key:"displaySignature",value:function(){document.getElementById("displaySignatureModal").removeAttribute("style");var e=new SignaturePad(document.getElementById("signature-pad"),{penColor:"rgb(0, 0, 0)"});e.onEnd=function(){document.getElementById("signature-next-step").disabled=!1},this.signaturePad=e}},{key:"displayTerms",value:function(){document.getElementById("displayTermsModal").removeAttribute("style")}},{key:"displayInput",value:function(){document.getElementById("displayInputModal").removeAttribute("style")}},{key:"handle",value:function(){var e=this;document.getElementById("signature-next-step").disabled=!0,document.getElementById("close_button").addEventListener("click",(function(){var e=document.getElementById("approve-button");e&&(e.disabled=!1)})),document.getElementById("hide_close").addEventListener("click",(function(){var e=document.getElementById("approve-button");e&&(e.disabled=!1)})),document.getElementById("approve-button").addEventListener("click",(function(){e.shouldDisplaySignature||e.shouldDisplayTerms||!e.shouldDisplayUserInput||(e.displayInput(),document.getElementById("input-next-step").addEventListener("click",(function(){document.querySelector('input[name="user_input"').value=document.getElementById("user_input").value,e.termsAccepted=!0,e.submitForm()}))),e.shouldDisplayUserInput&&e.displayInput(),e.shouldDisplaySignature&&e.shouldDisplayTerms&&(e.displaySignature(),document.getElementById("signature-next-step").addEventListener("click",(function(){e.displayTerms(),document.getElementById("accept-terms-button").addEventListener("click",(function(){document.querySelector('input[name="signature"').value=e.signaturePad.toDataURL(),document.querySelector('input[name="user_input"').value=document.getElementById("user_input").value,e.termsAccepted=!0,e.submitForm()}))}))),e.shouldDisplaySignature&&!e.shouldDisplayTerms&&(e.displaySignature(),document.getElementById("signature-next-step").addEventListener("click",(function(){document.querySelector('input[name="signature"').value=e.signaturePad.toDataURL(),document.querySelector('input[name="user_input"').value=document.getElementById("user_input").value,e.submitForm()}))),!e.shouldDisplaySignature&&e.shouldDisplayTerms&&(e.displayTerms(),document.getElementById("accept-terms-button").addEventListener("click",(function(){e.termsAccepted=!0,e.submitForm()}))),e.shouldDisplaySignature||e.shouldDisplayTerms||!e.shouldDisplayUserInput||e.submitForm()}))}}])&&e(n.prototype,u),a&&e(n,a),Object.defineProperty(n,"prototype",{writable:!1}),t}(),n=document.querySelector('meta[name="require-quote-signature"]').content,u=document.querySelector('meta[name="show-quote-terms"]').content,a=document.querySelector('meta[name="accept-user-input"]').content;new t(Boolean(+n),Boolean(+u),Boolean(+a)).handle()})();
|
||||
(()=>{function e(e,t){for(var n=0;n<t.length;n++){var u=t[n];u.enumerable=u.enumerable||!1,u.configurable=!0,"value"in u&&(u.writable=!0),Object.defineProperty(e,u.key,u)}}var t=function(){function t(e,n,u){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,t),this.shouldDisplaySignature=e,this.shouldDisplayTerms=n,this.shouldDisplayUserInput=u,this.termsAccepted=!1}var n,u,a;return n=t,(u=[{key:"submitForm",value:function(){document.getElementById("approve-form").submit()}},{key:"displaySignature",value:function(){document.getElementById("displaySignatureModal").removeAttribute("style");var e=new SignaturePad(document.getElementById("signature-pad"),{penColor:"rgb(0, 0, 0)"});e.onEnd=function(){document.getElementById("signature-next-step").disabled=!1},this.signaturePad=e}},{key:"displayTerms",value:function(){document.getElementById("displayTermsModal").removeAttribute("style")}},{key:"displayInput",value:function(){document.getElementById("displayInputModal").removeAttribute("style")}},{key:"handle",value:function(){var e=this;document.getElementById("signature-next-step").disabled=!0,document.getElementById("close_button").addEventListener("click",(function(){var e=document.getElementById("approve-button");e&&(e.disabled=!1)})),document.getElementById("hide_close").addEventListener("click",(function(){var e=document.getElementById("approve-button");e&&(e.disabled=!1)})),document.getElementById("approve-button").addEventListener("click",(function(){e.shouldDisplaySignature||e.shouldDisplayTerms||!e.shouldDisplayUserInput||(e.displayInput(),document.getElementById("input-next-step").addEventListener("click",(function(){document.querySelector('input[name="user_input"').value=document.getElementById("user_input").value,e.termsAccepted=!0,e.submitForm()}))),e.shouldDisplayUserInput&&e.displayInput(),e.shouldDisplaySignature&&e.shouldDisplayTerms&&(e.displaySignature(),document.getElementById("signature-next-step").addEventListener("click",(function(){e.displayTerms(),document.getElementById("accept-terms-button").addEventListener("click",(function(){document.querySelector('input[name="signature"').value=e.signaturePad.toDataURL(),document.querySelector('input[name="user_input"').value=document.getElementById("user_input").value,e.termsAccepted=!0,e.submitForm()}))}))),e.shouldDisplaySignature&&!e.shouldDisplayTerms&&(e.displaySignature(),document.getElementById("signature-next-step").addEventListener("click",(function(){document.querySelector('input[name="signature"').value=e.signaturePad.toDataURL(),document.querySelector('input[name="user_input"').value=document.getElementById("user_input").value,e.submitForm()}))),!e.shouldDisplaySignature&&e.shouldDisplayTerms&&(e.displayTerms(),document.getElementById("accept-terms-button").addEventListener("click",(function(){e.termsAccepted=!0,e.submitForm()}))),e.shouldDisplaySignature||e.shouldDisplayTerms||e.shouldDisplayUserInput||e.submitForm()}))}}])&&e(n.prototype,u),a&&e(n,a),Object.defineProperty(n,"prototype",{writable:!1}),t}(),n=document.querySelector('meta[name="require-quote-signature"]').content,u=document.querySelector('meta[name="show-quote-terms"]').content,a=document.querySelector('meta[name="accept-user-input"]').content;new t(Boolean(+n),Boolean(+u),Boolean(+a)).handle()})();
|
||||
212318
public/main.dart.js
vendored
212318
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
206422
public/main.foss.dart.js
vendored
206422
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
16866
public/main.profile.dart.js
vendored
16866
public/main.profile.dart.js
vendored
File diff suppressed because one or more lines are too long
|
|
@ -14,7 +14,7 @@
|
|||
"/js/clients/payments/stripe-alipay.js": "/js/clients/payments/stripe-alipay.js?id=0274ab4f8d2b411f2a2fe5142301e7af",
|
||||
"/js/clients/payments/checkout-credit-card.js": "/js/clients/payments/checkout-credit-card.js?id=4bd34a0b160f6f29b3096d870ac4d308",
|
||||
"/js/clients/quotes/action-selectors.js": "/js/clients/quotes/action-selectors.js?id=6fb63bae43d077b5061f4dadfe8dffc8",
|
||||
"/js/clients/quotes/approve.js": "/js/clients/quotes/approve.js?id=61a346e1977d3a1fec3634b234baa25c",
|
||||
"/js/clients/quotes/approve.js": "/js/clients/quotes/approve.js?id=2cb18f2df99d0eca47fa34f1d652c34f",
|
||||
"/js/clients/payments/stripe-credit-card.js": "/js/clients/payments/stripe-credit-card.js?id=809de47258a681f0ffebe787dd6a9a93",
|
||||
"/js/setup/setup.js": "/js/setup/setup.js?id=27560b012f166f8b9417ced2188aab70",
|
||||
"/js/clients/payments/card-js.min.js": "/js/clients/payments/card-js.min.js?id=8ce33c3deae058ad314fb8357e5be63b",
|
||||
|
|
|
|||
4
resources/js/clients/quotes/approve.js
vendored
4
resources/js/clients/quotes/approve.js
vendored
|
|
@ -75,7 +75,7 @@ class Approve {
|
|||
.getElementById('approve-button')
|
||||
.addEventListener('click', () => {
|
||||
|
||||
if (!this.shouldDisplaySignature && !this.shouldDisplayTerms && this.shouldDisplayUserInput){
|
||||
if (!this.shouldDisplaySignature && !this.shouldDisplayTerms && this.shouldDisplayUserInput){
|
||||
this.displayInput();
|
||||
|
||||
document
|
||||
|
|
@ -146,7 +146,7 @@ class Approve {
|
|||
});
|
||||
}
|
||||
|
||||
if (!this.shouldDisplaySignature && !this.shouldDisplayTerms && this.shouldDisplayUserInput) {
|
||||
if (!this.shouldDisplaySignature && !this.shouldDisplayTerms && !this.shouldDisplayUserInput) {
|
||||
this.submitForm();
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -11,10 +11,15 @@
|
|||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-family: $font_name, Helvetica, sans-serif;
|
||||
font-size: "$font_size";
|
||||
font-size: $font_size !important;
|
||||
zoom: 80%;
|
||||
}
|
||||
|
||||
table tr td, table tr, th {
|
||||
font-size: $font_size !important;
|
||||
}
|
||||
|
||||
|
||||
body, html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
|
@ -114,7 +119,7 @@
|
|||
min-width: 100%;
|
||||
table-layout: fixed;
|
||||
overflow-wrap: break-word;
|
||||
margin-top: 3rem;
|
||||
margin-top: 0rem;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,10 +16,16 @@
|
|||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-family: $font_name, Helvetica, sans-serif;
|
||||
font-size: "$font_size";
|
||||
font-size: $font_size !important;
|
||||
zoom: 80%;
|
||||
}
|
||||
|
||||
|
||||
table tr td, table tr, th {
|
||||
font-size: $font_size !important;
|
||||
}
|
||||
|
||||
|
||||
@page {
|
||||
margin-left: $global_margin;
|
||||
margin-right: $global_margin;
|
||||
|
|
|
|||
|
|
@ -11,10 +11,15 @@
|
|||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-family: $font_name, Helvetica, sans-serif;
|
||||
font-size: "$font_size";
|
||||
font-size: $font_size !important;
|
||||
zoom: 80%;
|
||||
}
|
||||
|
||||
|
||||
table tr td, table tr, th {
|
||||
font-size: $font_size !important;
|
||||
}
|
||||
|
||||
html {
|
||||
margin: 0;
|
||||
padding-top: 1rem;
|
||||
|
|
|
|||
|
|
@ -16,10 +16,14 @@
|
|||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-family: $font_name, Helvetica, sans-serif;
|
||||
font-size: "$font_size";
|
||||
font-size: $font_size !important;
|
||||
zoom: 80%;
|
||||
}
|
||||
|
||||
table tr td, table tr, th {
|
||||
font-size: $font_size !important;
|
||||
}
|
||||
|
||||
@page {
|
||||
margin-left: $global_margin;
|
||||
margin-right: $global_margin;
|
||||
|
|
|
|||
|
|
@ -16,10 +16,15 @@
|
|||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-family: $font_name, Helvetica, sans-serif;
|
||||
font-size: "$font_size";
|
||||
font-size: $font_size !important;
|
||||
zoom: 80%;
|
||||
}
|
||||
|
||||
|
||||
table tr td, table tr, th {
|
||||
font-size: $font_size !important;
|
||||
}
|
||||
|
||||
@page {
|
||||
margin-left: $global_margin;
|
||||
margin-right: $global_margin;
|
||||
|
|
|
|||
|
|
@ -16,10 +16,14 @@
|
|||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-family: $font_name, Helvetica, sans-serif;
|
||||
font-size: "$font_size";
|
||||
font-size: $font_size !important;
|
||||
zoom: 80%;
|
||||
}
|
||||
|
||||
table tr td, table tr, th {
|
||||
font-size: $font_size !important;
|
||||
}
|
||||
|
||||
@page {
|
||||
margin: $global_margin;
|
||||
size: $page_size $page_layout;
|
||||
|
|
|
|||
|
|
@ -16,10 +16,14 @@
|
|||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-family: $font_name, Helvetica, sans-serif;
|
||||
font-size: "$font_size";
|
||||
font-size: $font_size !important;
|
||||
zoom: 80%;
|
||||
}
|
||||
|
||||
table tr td, table tr, th {
|
||||
font-size: $font_size !important;
|
||||
}
|
||||
|
||||
@page {
|
||||
margin: $global_margin;
|
||||
size: $page_size $page_layout;
|
||||
|
|
|
|||
|
|
@ -15,10 +15,14 @@
|
|||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-family: $font_name, Helvetica, sans-serif;
|
||||
font-size: "7px";
|
||||
font-size: $font_size !important;
|
||||
zoom: 80%;
|
||||
}
|
||||
|
||||
table tr td, table tr, th {
|
||||
font-size: $font_size !important;
|
||||
}
|
||||
|
||||
body, html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
|
|
|||
|
|
@ -11,10 +11,14 @@
|
|||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-family: $font_name, Helvetica, sans-serif;
|
||||
font-size: "$font_size";
|
||||
font-size: $font_size !important;
|
||||
zoom: 80%;
|
||||
}
|
||||
|
||||
table tr td, table tr, th {
|
||||
font-size: $font_size !important;
|
||||
}
|
||||
|
||||
html {
|
||||
width: 210mm;
|
||||
height: 200mm;
|
||||
|
|
|
|||
|
|
@ -11,10 +11,14 @@
|
|||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-family: $font_name, Helvetica, sans-serif;
|
||||
font-size: "$font_size";
|
||||
font-size: $font_size !important;
|
||||
zoom: 80%;
|
||||
}
|
||||
|
||||
table tr td, table tr, th {
|
||||
font-size: $font_size !important;
|
||||
}
|
||||
|
||||
html {
|
||||
width: 210mm;
|
||||
height: 200mm;
|
||||
|
|
|
|||
|
|
@ -15,11 +15,15 @@
|
|||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-family: $font_name, Helvetica, sans-serif;
|
||||
font-size: "$font_size";
|
||||
font-size: $font_size !important;
|
||||
zoom: 80%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
table tr td, table tr, th {
|
||||
font-size: $font_size !important;
|
||||
}
|
||||
|
||||
@page {
|
||||
margin: 0;
|
||||
size: $page_size $page_layout;
|
||||
|
|
|
|||
|
|
@ -36,9 +36,9 @@
|
|||
@if(!empty($subscription->recurring_product_ids))
|
||||
@foreach($recurring_products as $index => $product)
|
||||
<li class="flex py-6">
|
||||
@if(filter_var($product->custom_value1, FILTER_VALIDATE_URL))
|
||||
@if(filter_var($product->product_image, FILTER_VALIDATE_URL))
|
||||
<div class="h-24 w-24 flex-shrink-0 overflow-hidden rounded-md border border-gray-200 mr-2">
|
||||
<img src="{{$product->custom_value1}}" alt="" class="h-full w-full object-cover object-center p-2">
|
||||
<img src="{{$product->product_image}}" alt="" class="h-full w-full object-cover object-center p-2">
|
||||
</div>
|
||||
@endif
|
||||
<div class="ml-0 flex flex-1 flex-col">
|
||||
|
|
@ -74,7 +74,7 @@
|
|||
@endfor
|
||||
}
|
||||
@else
|
||||
@for ($i = 2; $i <= ($subscription->use_inventory_management ? min($product->in_stock_quantity, max(100,$product->custom_value2)) : max(100,$product->custom_value2)); $i++)
|
||||
@for ($i = 2; $i <= ($subscription->use_inventory_management ? min($product->in_stock_quantity, max(100,$product->max_quantity)) : max(100,$product->max_quantity)); $i++)
|
||||
<option value="{{$i}}">{{$i}}</option>
|
||||
@endfor
|
||||
@endif
|
||||
|
|
@ -96,9 +96,9 @@
|
|||
@if(!empty($subscription->product_ids))
|
||||
@foreach($products as $product)
|
||||
<li class="flex py-6">
|
||||
@if(filter_var($product->custom_value1, FILTER_VALIDATE_URL))
|
||||
@if(filter_var($product->product_image, FILTER_VALIDATE_URL))
|
||||
<div class="h-24 w-24 flex-shrink-0 overflow-hidden rounded-md border border-gray-200 mr-2">
|
||||
<img src="{{$product->custom_value1}}" alt="" class="h-full w-full object-cover object-center p-2">
|
||||
<img src="{{$product->product_image}}" alt="" class="h-full w-full object-cover object-center p-2">
|
||||
</div>
|
||||
@endif
|
||||
<div class="ml-0 flex flex-1 flex-col">
|
||||
|
|
@ -135,9 +135,9 @@
|
|||
@if(!empty($subscription->optional_recurring_product_ids))
|
||||
@foreach($optional_recurring_products as $index => $product)
|
||||
<li class="flex py-6">
|
||||
@if(filter_var($product->custom_value1, FILTER_VALIDATE_URL))
|
||||
@if(filter_var($product->product_image, FILTER_VALIDATE_URL))
|
||||
<div class="h-24 w-24 flex-shrink-0 overflow-hidden rounded-md border border-gray-200 mr-2">
|
||||
<img src="{{$product->custom_value1}}" alt="" class="h-full w-full object-cover object-center p-2">
|
||||
<img src="{{$product->product_image}}" alt="" class="h-full w-full object-cover object-center p-2">
|
||||
</div>
|
||||
@endif
|
||||
<div class="ml-0 flex flex-1 flex-col">
|
||||
|
|
@ -148,7 +148,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between text-sm mt-1">
|
||||
@if(is_numeric($product->custom_value2))
|
||||
@if(is_numeric($product->max_quantity))
|
||||
<p class="text-gray-500 w-3/4"></p>
|
||||
<div class="flex place-content-end">
|
||||
@if($subscription->use_inventory_management && $product->in_stock_quantity == 0)
|
||||
|
|
@ -162,7 +162,7 @@
|
|||
@endif
|
||||
>
|
||||
<option value="0" selected="selected">0</option>
|
||||
@for ($i = 1; $i <= ($subscription->use_inventory_management ? min($product->in_stock_quantity, max(100,$product->custom_value2)) : max(100,$product->custom_value2)); $i++)
|
||||
@for ($i = 1; $i <= ($subscription->use_inventory_management ? min($product->in_stock_quantity, max(100,$product->max_quantity)) : max(100,$product->max_quantity)); $i++)
|
||||
<option value="{{$i}}">{{$i}}</option>
|
||||
@endfor
|
||||
</select>
|
||||
|
|
@ -176,9 +176,9 @@
|
|||
@if(!empty($subscription->optional_product_ids))
|
||||
@foreach($optional_products as $index => $product)
|
||||
<li class="flex py-6">
|
||||
@if(filter_var($product->custom_value1, FILTER_VALIDATE_URL))
|
||||
@if(filter_var($product->product_image, FILTER_VALIDATE_URL))
|
||||
<div class="h-24 w-24 flex-shrink-0 overflow-hidden rounded-md border border-gray-200 mr-2">
|
||||
<img src="{{$product->custom_value1}}" alt="" class="h-full w-full object-cover object-center p-2">
|
||||
<img src="{{$product->product_image}}" alt="" class="h-full w-full object-cover object-center p-2">
|
||||
</div>
|
||||
@endif
|
||||
<div class="ml-0 flex flex-1 flex-col">
|
||||
|
|
@ -190,7 +190,7 @@
|
|||
<p class="mt-1 text-sm text-gray-500"></p>
|
||||
</div>
|
||||
<div class="flex justify-between text-sm mt-1">
|
||||
@if(is_numeric($product->custom_value2))
|
||||
@if(is_numeric($product->max_quantity))
|
||||
<p class="text-gray-500 w-3/4"></p>
|
||||
<div class="flex place-content-end">
|
||||
@if($subscription->use_inventory_management && $product->in_stock_quantity == 0)
|
||||
|
|
@ -200,7 +200,7 @@
|
|||
@endif
|
||||
<select wire:model.debounce.300ms="data.{{ $index }}.optional_qty" class="rounded-md border-gray-300 shadow-sm sm:text-sm">
|
||||
<option value="0" selected="selected">0</option>
|
||||
@for ($i = 1; $i <= ($subscription->use_inventory_management ? min($product->in_stock_quantity, min(100,$product->custom_value2)) : min(100,$product->custom_value2)); $i++)
|
||||
@for ($i = 1; $i <= ($subscription->use_inventory_management ? min($product->in_stock_quantity, min(100,$product->max_quantity)) : min(100,$product->max_quantity)); $i++)
|
||||
<option value="{{$i}}">{{$i}}</option>
|
||||
@endfor
|
||||
</select>
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@ class BankTransactionRuleTest extends TestCase
|
|||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->putJson('/api/v1/bank_transaction_rules/'. $br->hashed_id, $data);
|
||||
])->putJson('/api/v1/bank_transaction_rules/'. $br->hashed_id. '?include=expense_category', $data);
|
||||
|
||||
} catch (ValidationException $e) {
|
||||
$message = json_decode($e->validator->getMessageBag(), 1);
|
||||
|
|
@ -194,7 +194,7 @@ class BankTransactionRuleTest extends TestCase
|
|||
|
||||
if($response){
|
||||
$arr = $response->json();
|
||||
|
||||
nlog($arr);
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -43,6 +43,198 @@ class TaskApiTest extends TestCase
|
|||
Model::reguard();
|
||||
}
|
||||
|
||||
private function checkTimeLog(array $log): bool
|
||||
{
|
||||
if(count($log) == 0)
|
||||
return true;
|
||||
|
||||
/*Get first value of all arrays*/
|
||||
$result = array_column($log, 0);
|
||||
|
||||
/*Sort the array in ascending order*/
|
||||
asort($result);
|
||||
|
||||
$new_array = [];
|
||||
|
||||
/*Rebuild the array in order*/
|
||||
foreach($result as $key => $value)
|
||||
$new_array[] = $log[$key];
|
||||
|
||||
/*Iterate through the array and perform checks*/
|
||||
foreach($new_array as $key => $array)
|
||||
{
|
||||
/*Flag which helps us know if there is a NEXT timelog*/
|
||||
$next = false;
|
||||
/* If there are more than 1 time log in the array, ensure the last timestamp is not zero*/
|
||||
if(count($new_array) >1 && $array[1] == 0)
|
||||
return false;
|
||||
|
||||
/* Check if the start time is greater than the end time */
|
||||
/* Ignore the last value for now, we'll do a separate check for this */
|
||||
if($array[0] > $array[1] && $array[1] != 0)
|
||||
return false;
|
||||
|
||||
/* Find the next time log value - if it exists */
|
||||
if(array_key_exists($key+1, $new_array))
|
||||
$next = $new_array[$key+1];
|
||||
|
||||
/* check the next time log and ensure the start time is GREATER than the end time of the previous record */
|
||||
if($next && $next[0] < $array[1])
|
||||
return false;
|
||||
|
||||
/* Get the last row of the timelog*/
|
||||
$last_row = end($new_array);
|
||||
|
||||
/*If the last value is NOT zero, ensure start time is not GREATER than the endtime */
|
||||
if($last_row[1] != 0 && $last_row[0] > $last_row[1])
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function testTimeLogChecker1()
|
||||
{
|
||||
|
||||
$log = [
|
||||
[50,0]
|
||||
];
|
||||
|
||||
$this->assertTrue($this->checkTimeLog($log));
|
||||
|
||||
}
|
||||
|
||||
public function testTimeLogChecker2()
|
||||
{
|
||||
|
||||
$log = [
|
||||
[4,5],
|
||||
[5,1]
|
||||
];
|
||||
|
||||
|
||||
$this->assertFalse($this->checkTimeLog($log));
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function testTimeLogChecker3()
|
||||
{
|
||||
|
||||
$log = [
|
||||
[4,5],
|
||||
[3,50]
|
||||
];
|
||||
|
||||
|
||||
$this->assertFalse($this->checkTimeLog($log));
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function testTimeLogChecker4()
|
||||
{
|
||||
|
||||
$log = [
|
||||
[4,5],
|
||||
[3,0]
|
||||
];
|
||||
|
||||
|
||||
$this->assertFalse($this->checkTimeLog($log));
|
||||
|
||||
}
|
||||
|
||||
public function testTimeLogChecker5()
|
||||
{
|
||||
|
||||
$log = [
|
||||
[4,5],
|
||||
[3,1]
|
||||
];
|
||||
|
||||
|
||||
$this->assertFalse($this->checkTimeLog($log));
|
||||
|
||||
}
|
||||
|
||||
public function testTimeLogChecker6()
|
||||
{
|
||||
|
||||
$log = [
|
||||
[4,5],
|
||||
[1,3],
|
||||
];
|
||||
|
||||
|
||||
$this->assertTrue($this->checkTimeLog($log));
|
||||
|
||||
}
|
||||
|
||||
public function testTimeLogChecker7()
|
||||
{
|
||||
|
||||
$log = [
|
||||
[1,3],
|
||||
[4,5]
|
||||
];
|
||||
|
||||
|
||||
$this->assertTrue($this->checkTimeLog($log));
|
||||
|
||||
}
|
||||
|
||||
public function testTimeLogChecker8()
|
||||
{
|
||||
|
||||
$log = [
|
||||
[1,3],
|
||||
[50,0]
|
||||
];
|
||||
|
||||
$this->assertTrue($this->checkTimeLog($log));
|
||||
|
||||
}
|
||||
|
||||
public function testTimeLogChecker9()
|
||||
{
|
||||
|
||||
$log = [
|
||||
[4,5,'bb'],
|
||||
[50,0,'aa'],
|
||||
];
|
||||
|
||||
$this->assertTrue($this->checkTimeLog($log));
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function testTimeLogChecker10()
|
||||
{
|
||||
|
||||
$log = [
|
||||
[4,5,'5'],
|
||||
[50,0,'3'],
|
||||
];
|
||||
|
||||
$this->assertTrue($this->checkTimeLog($log));
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function testTimeLogChecker11()
|
||||
{
|
||||
|
||||
$log = [
|
||||
[1,2,'a'],
|
||||
[3,4,'d'],
|
||||
];
|
||||
|
||||
$this->assertTrue($this->checkTimeLog($log));
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function testTaskListClientStatus()
|
||||
|
|
@ -58,7 +250,7 @@ class TaskApiTest extends TestCase
|
|||
public function testTaskLockingGate()
|
||||
{
|
||||
$data = [
|
||||
'timelog' => [[1,2],[3,4]],
|
||||
'timelog' => [[1,2,'a'],[3,4,'d']],
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
|
|
@ -194,7 +386,7 @@ class TaskApiTest extends TestCase
|
|||
public function testTimeLogValidation3()
|
||||
{
|
||||
$data = [
|
||||
'timelog' => [["a","b"],["c","d"]],
|
||||
'timelog' => [["a","b",'d'],["c","d",'d']],
|
||||
];
|
||||
|
||||
try {
|
||||
|
|
@ -213,7 +405,7 @@ class TaskApiTest extends TestCase
|
|||
public function testTimeLogValidation4()
|
||||
{
|
||||
$data = [
|
||||
'timelog' => [[1,2],[3,0]],
|
||||
'timelog' => [[1,2,'d'],[3,0,'d']],
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
|
|
@ -232,8 +424,8 @@ class TaskApiTest extends TestCase
|
|||
public function testStartTask()
|
||||
{
|
||||
$log = [
|
||||
[2, 1],
|
||||
[10, 20],
|
||||
[2, 1,'d'],
|
||||
[10, 20,'d'],
|
||||
];
|
||||
|
||||
$last = end($log);
|
||||
|
|
|
|||
|
|
@ -49,33 +49,6 @@ class WebhookAPITest extends TestCase
|
|||
$this->withoutExceptionHandling();
|
||||
}
|
||||
|
||||
// public function testClientWebhooks()
|
||||
// {
|
||||
// // client archived = 37
|
||||
// $data = [
|
||||
// 'target_url' => 'http://hook.com',
|
||||
// 'event_id' => 37,
|
||||
// 'rest_method' => 'post',
|
||||
// 'format' => 'JSON',
|
||||
// ];
|
||||
|
||||
// $response = $this->withHeaders([
|
||||
// 'X-API-SECRET' => config('ninja.api_secret'),
|
||||
// 'X-API-TOKEN' => $this->token,
|
||||
// ])->post('/api/v1/webhooks', $data);
|
||||
|
||||
// $repo = new ClientRepository(new ClientContactRepository());
|
||||
|
||||
// $repo->archive($this->client);
|
||||
|
||||
// \Illuminate\Support\Facades\Queue::after(function (WebhookHandler $event) {
|
||||
// $this->assertTrue($event->job->isReleased());
|
||||
// });
|
||||
|
||||
// \Illuminate\Support\Facades\Queue::assertPushed(WebhookHandler::class);
|
||||
|
||||
// }
|
||||
|
||||
public function testWebhookGetFilter()
|
||||
{
|
||||
$response = $this->withHeaders([
|
||||
|
|
@ -98,6 +71,20 @@ class WebhookAPITest extends TestCase
|
|||
|
||||
public function testWebhookPostRoute()
|
||||
{
|
||||
|
||||
$data = [
|
||||
'target_url' => 'http://hook.com',
|
||||
'event_id' => 1,
|
||||
'format' => 'JSON',
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/webhooks', $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$data = [
|
||||
'target_url' => 'http://hook.com',
|
||||
'event_id' => 1,
|
||||
|
|
@ -116,6 +103,20 @@ class WebhookAPITest extends TestCase
|
|||
|
||||
$this->assertEquals(1, $arr['data']['event_id']);
|
||||
|
||||
$data = [
|
||||
'target_url' => 'http://hook.com',
|
||||
'event_id' => 2,
|
||||
'format' => 'JSON',
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->put('/api/v1/webhooks/'.$arr['data']['id'], $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
|
||||
$data = [
|
||||
'target_url' => 'http://hook.com',
|
||||
'event_id' => 2,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue