diff --git a/app/Http/Controllers/ClientPortal/PaymentController.php b/app/Http/Controllers/ClientPortal/PaymentController.php index c74d6721e7e5..6b7dc20d2a07 100644 --- a/app/Http/Controllers/ClientPortal/PaymentController.php +++ b/app/Http/Controllers/ClientPortal/PaymentController.php @@ -155,7 +155,7 @@ class PaymentController extends Controller }); } - $payment_methods = auth()->user()->client->getPaymentMethods(array_sum(array_column($payable_invoices, 'amount_with_fee'))); + //$payment_methods = auth()->user()->client->getPaymentMethods(array_sum(array_column($payable_invoices, 'amount_with_fee'))); $payment_method_id = request()->input('payment_method_id'); @@ -176,7 +176,7 @@ class PaymentController extends Controller // $fee_totals += $fee_tax; // } - $first_invoice->service()->addGatewayFee($gateway, $invoice_totals)->save(); + $first_invoice->service()->addGatewayFee($gateway, $payment_method_id, $invoice_totals)->save(); /** * diff --git a/app/Models/Client.php b/app/Models/Client.php index f651ca1f7725..f4af6cee2fab 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -467,45 +467,39 @@ class Client extends BaseModel implements HasLocalePreference $company_gateways = $this->getSetting('company_gateway_ids'); - if ($company_gateways || $company_gateways == '0') { //we need to check for "0" here as we disable a payment gateway for a client with the number "0" + //we need to check for "0" here as we disable a payment gateway for a client with the number "0" + if ($company_gateways || $company_gateways == '0') { $transformed_ids = $this->transformKeys(explode(',', $company_gateways)); $gateways = $this->company ->company_gateways ->whereIn('id', $transformed_ids) - ->sortby(function ($model) use ($transformed_ids) { - return array_search($model->id, $transformed_ids); + ->sortby(function ($model) use ($transformed_ids) { //company gateways are sorted in order of priority + return array_search($model->id, $transformed_ids);// this closure sorts for us }); } else { $gateways = $this->company->company_gateways->where('is_deleted', false); } - $valid_gateways = $gateways->filter(function ($method) use ($amount) { - if (isset($method->fees_and_limits)) { - //sometimes the key value of the fees and limits object are not static, - //we have to harvest the key value as follows - $properties = array_keys(get_object_vars($method->fees_and_limits)); - $fees_and_limits = $method->fees_and_limits->{$properties[0]}; - } else { - return true; - } - - if ((property_exists($fees_and_limits, 'min_limit')) && $fees_and_limits->min_limit !== null && $fees_and_limits->min_limit != -1 && $amount < $fees_and_limits->min_limit) { - return false; - } - - if ((property_exists($fees_and_limits, 'max_limit')) && $fees_and_limits->max_limit !== null && $fees_and_limits->max_limit != -1 && $amount > $fees_and_limits->max_limit) { - return false; - } - - return true; - })->all(); - $payment_methods = []; - foreach ($valid_gateways as $gateway) { + foreach ($gateways as $gateway) { foreach ($gateway->driver($this)->gatewayTypes() as $type) { - $payment_methods[] = [$gateway->id => $type]; + + if(isset($gateway->fees_and_limits) && property_exists($gateway->fees_and_limits, $type)) + { + + if($this->validGatewayForAmount($gateway->fees_and_limits->{$type}, $amount)) + $payment_methods[] = [$gateway->id => $type]; + + } + else + { + + $payment_methods[] = [$gateway->id => $type]; + + } + } } @@ -526,13 +520,32 @@ class Client extends BaseModel implements HasLocalePreference 'label' => ctrans('texts.'.$gateway->getTypeAlias($gateway_type_id)).$fee_label, 'company_gateway_id' => $gateway_id, 'gateway_type_id' => $gateway_type_id, - ]; + ]; } } return $payment_urls; } + public function validGatewayForAmount($fees_and_limits_for_payment_type, $amount) :bool + { + if (isset($fees_and_limits_for_payment_type)) { + $fees_and_limits = $fees_and_limits_for_payment_type; + } else { + return true; + } + + if ((property_exists($fees_and_limits, 'min_limit')) && $fees_and_limits->min_limit !== null && $fees_and_limits->min_limit != -1 && $amount < $fees_and_limits->min_limit) { + return false; + } + + if ((property_exists($fees_and_limits, 'max_limit')) && $fees_and_limits->max_limit !== null && $fees_and_limits->max_limit != -1 && $amount > $fees_and_limits->max_limit) { + return false; + } + + return true; + } + public function preferredLocale() { $languages = Cache::get('languages'); diff --git a/app/Models/CompanyGateway.php b/app/Models/CompanyGateway.php index d569f26b96b1..b05d20a94b05 100644 --- a/app/Models/CompanyGateway.php +++ b/app/Models/CompanyGateway.php @@ -79,10 +79,6 @@ class CompanyGateway extends BaseModel public function getTypeAlias($gateway_type_id) { - if ($gateway_type_id == 'token') { - $gateway_type_id = 1; - } - return GatewayType::find($gateway_type_id)->alias; } @@ -230,19 +226,14 @@ class CompanyGateway extends BaseModel return $this->getConfigField('publishableKey'); } - public function getFeesAndLimits() + public function getFeesAndLimits($gateway_type_id) { - if (is_null($this->fees_and_limits)) { + if (is_null($this->fees_and_limits) || empty($this->fees_and_limits)) { return false; } - $fees_and_limits = new \stdClass; + return $this->fees_and_limits->{$gateway_type_id}; - foreach ($this->fees_and_limits as $key => $value) { - $fees_and_limits = $this->fees_and_limits->{$key}; - } - - return $fees_and_limits; } /** @@ -252,7 +243,7 @@ class CompanyGateway extends BaseModel * @param Client $client The client object * @return string The fee amount formatted in the client currency */ - public function calcGatewayFeeLabel($amount, Client $client) :string + public function calcGatewayFeeLabel($amount, Client $client, $gateway_type_id = GatewayType::CREDIT_CARD) :string { $label = ''; @@ -260,7 +251,7 @@ class CompanyGateway extends BaseModel return $label; } - $fee = $this->calcGatewayFee($amount); + $fee = $this->calcGatewayFee($amount, $gateway_type_id); if ($fee > 0) { $fee = Number::formatMoney(round($fee, 2), $client); @@ -270,9 +261,10 @@ class CompanyGateway extends BaseModel return $label; } - public function calcGatewayFee($amount, $include_taxes = false) + public function calcGatewayFee($amount, $include_taxes = false, $gateway_type_id = GatewayType::CREDIT_CARD) { - $fees_and_limits = $this->getFeesAndLimits(); + + $fees_and_limits = $this->getFeesAndLimits($gateway_type_id); if (! $fees_and_limits) { return 0; diff --git a/app/Models/Gateway.php b/app/Models/Gateway.php index 978838a98182..82aab212b1e0 100644 --- a/app/Models/Gateway.php +++ b/app/Models/Gateway.php @@ -20,7 +20,7 @@ class Gateway extends StaticModel 'is_offsite' => 'boolean', 'is_secure' => 'boolean', 'recommended' => 'boolean', - //'visible' => 'boolean', + 'visible' => 'boolean', 'sort_order' => 'int', 'updated_at' => 'timestamp', 'created_at' => 'timestamp', diff --git a/app/Models/GatewayType.php b/app/Models/GatewayType.php index 7e60467856ab..058fd116e8f5 100644 --- a/app/Models/GatewayType.php +++ b/app/Models/GatewayType.php @@ -22,17 +22,12 @@ class GatewayType extends StaticModel const BANK_TRANSFER = 2; const PAYPAL = 3; const CRYPTO = 4; - const DWOLLA = 5; - const CUSTOM1 = 6; - const ALIPAY = 7; - const SOFORT = 8; + const CUSTOM = 5; + const ALIPAY = 6; + const SOFORT = 7; + const APPLE_PAY = 8; const SEPA = 9; - const GOCARDLESS = 10; - const APPLE_PAY = 11; - const CUSTOM2 = 12; - const CUSTOM3 = 13; - const TOKEN = 'token'; - + public function gateway() { return $this->belongsTo(Gateway::class); diff --git a/app/Models/Project.php b/app/Models/Project.php index 62900d4e986f..e3669bc3ad7b 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -61,6 +61,11 @@ class Project extends BaseModel return $this->belongsTo(Client::class)->withTrashed(); } + public function documents() + { + return $this->morphMany(Document::class, 'documentable'); + } + // /** // * @return \Illuminate\Database\Eloquent\Relations\HasMany // */ diff --git a/app/Models/Vendor.php b/app/Models/Vendor.php index 8cd1a54d5686..3cb8b05265ff 100644 --- a/app/Models/Vendor.php +++ b/app/Models/Vendor.php @@ -26,6 +26,7 @@ class Vendor extends BaseModel protected $fillable = [ 'name', + 'assigned_user_id', 'id_number', 'vat_number', 'work_phone', diff --git a/app/PaymentDrivers/BaseDriver.php b/app/PaymentDrivers/BaseDriver.php index d6089437a2e2..678d2728ba77 100644 --- a/app/PaymentDrivers/BaseDriver.php +++ b/app/PaymentDrivers/BaseDriver.php @@ -72,8 +72,8 @@ class BaseDriver extends AbstractPaymentDriver * Authorize a payment method. * * Returns a reusable token for storage for future payments - * @param const $payment_method the GatewayType::constant - * @return view Return a view for collecting payment method information + * @param const $payment_method The GatewayType::constant + * @return view Return a view for collecting payment method information */ public function authorize($payment_method) { @@ -82,8 +82,8 @@ class BaseDriver extends AbstractPaymentDriver /** * Executes purchase attempt for a given amount. * - * @param float $amount The amount to be collected - * @param bool $return_client_response Whether the method needs to return a response (otherwise we assume an unattended payment) + * @param float $amount The amount to be collected + * @param bool $return_client_response Whether the method needs to return a response (otherwise we assume an unattended payment) * @return mixed */ public function purchase($amount, $return_client_response = false) @@ -95,7 +95,7 @@ class BaseDriver extends AbstractPaymentDriver * * @param Payment $payment The Payment Object * @param float $amount The amount to be refunded - * @param bool $return_client_response Whether the method needs to return a response (otherwise we assume an unattended payment) + * @param bool $return_client_response Whether the method needs to return a response (otherwise we assume an unattended payment) * @return mixed */ public function refund(Payment $payment, $amount, $return_client_response = false) diff --git a/app/PaymentDrivers/CheckoutComPaymentDriver.php b/app/PaymentDrivers/CheckoutComPaymentDriver.php index bf6b4aa42a30..4d854817df27 100644 --- a/app/PaymentDrivers/CheckoutComPaymentDriver.php +++ b/app/PaymentDrivers/CheckoutComPaymentDriver.php @@ -102,16 +102,11 @@ class CheckoutComPaymentDriver extends BaseDriver */ public function viewForType($gateway_type_id) { - + //currently only ever token or creditcard so no need for switches $this->payment_method = $gateway_type_id; - if ($gateway_type_id == GatewayType::CREDIT_CARD) { - return 'gateways.checkout.credit_card'; - } - - if ($gateway_type_id == GatewayType::TOKEN) { - return 'gateways.checkout.credit_card'; - } + return 'gateways.checkout.credit_card'; + } /** diff --git a/app/PaymentDrivers/Stripe/Alipay.php b/app/PaymentDrivers/Stripe/Alipay.php index c2f77cf490e8..06a37692066a 100644 --- a/app/PaymentDrivers/Stripe/Alipay.php +++ b/app/PaymentDrivers/Stripe/Alipay.php @@ -47,11 +47,11 @@ class Alipay { return route('client.payments.response', [ 'company_gateway_id' => $this->stripe->company_gateway->id, - 'gateway_type_id' => GatewayType::SOFORT, + 'gateway_type_id' => GatewayType::ALIPAY, 'hashed_ids' => implode(',', $data['hashed_ids']), 'amount' => $data['amount'], 'fee' => $data['fee'], - 'payment_method_id' => GatewayType::SOFORT, + 'payment_method_id' => GatewayType::ALIPAY, ]); } diff --git a/app/PaymentDrivers/StripePaymentDriver.php b/app/PaymentDrivers/StripePaymentDriver.php index 4d11d07b0295..9deed999d228 100644 --- a/app/PaymentDrivers/StripePaymentDriver.php +++ b/app/PaymentDrivers/StripePaymentDriver.php @@ -108,7 +108,6 @@ class StripePaymentDriver extends BaseDriver { $types = [ GatewayType::CREDIT_CARD, - //GatewayType::TOKEN, ]; if ($this->company_gateway->getSofortEnabled() && $this->invitation && $this->client() && isset($this->client()->country) && in_array($this->client()->country, ['AUT', 'BEL', 'DEU', 'ITA', 'NLD', 'ESP'])) { @@ -142,7 +141,6 @@ class StripePaymentDriver extends BaseDriver { switch ($gateway_type_id) { case GatewayType::CREDIT_CARD: - case GatewayType::TOKEN: return 'gateways.stripe.credit_card'; break; case GatewayType::SOFORT: diff --git a/app/Services/Invoice/AddGatewayFee.php b/app/Services/Invoice/AddGatewayFee.php index bc88092d3048..84af200f026e 100644 --- a/app/Services/Invoice/AddGatewayFee.php +++ b/app/Services/Invoice/AddGatewayFee.php @@ -31,21 +31,25 @@ class AddGatewayFee extends AbstractService private $amount; - public function __construct(CompanyGateway $company_gateway, Invoice $invoice, float $amount) + private $gateway_type_id; + + public function __construct(CompanyGateway $company_gateway, int $gateway_type_id, Invoice $invoice, float $amount) { $this->company_gateway = $company_gateway; $this->invoice = $invoice; $this->amount = $amount; + + $this->gateway_type_id = $gateway_type_id; } public function run() { - $gateway_fee = round($this->company_gateway->calcGatewayFee($this->amount), $this->invoice->client->currency()->precision); + $gateway_fee = round($this->company_gateway->calcGatewayFee($this->amount, $this->gateway_type_id), $this->invoice->client->currency()->precision); if((int)$gateway_fee == 0) - return; + return $this->invoice; $this->cleanPendingGatewayFees(); @@ -78,7 +82,7 @@ class AddGatewayFee extends AbstractService $invoice_item->quantity = 1; $invoice_item->cost = $gateway_fee; - if ($fees_and_limits = $this->company_gateway->getFeesAndLimits()) { + if ($fees_and_limits = $this->company_gateway->getFeesAndLimits($this->gateway_type_id)) { $invoice_item->tax_rate1 = $fees_and_limits->fee_tax_rate1; $invoice_item->tax_rate2 = $fees_and_limits->fee_tax_rate2; $invoice_item->tax_rate3 = $fees_and_limits->fee_tax_rate3; diff --git a/app/Services/Invoice/AutoBillInvoice.php b/app/Services/Invoice/AutoBillInvoice.php index f94a4d98ef47..42bc0498e094 100644 --- a/app/Services/Invoice/AutoBillInvoice.php +++ b/app/Services/Invoice/AutoBillInvoice.php @@ -15,6 +15,7 @@ use App\DataMapper\InvoiceItem; use App\Events\Payment\PaymentWasCreated; use App\Factory\PaymentFactory; use App\Models\Client; +use App\Models\Credit; use App\Models\Invoice; use App\Models\Payment; use App\Models\PaymentHash; @@ -56,6 +57,9 @@ class AutoBillInvoice extends AbstractService //if the credits cover the payments, we stop here, build the payment with credits and exit early $this->applyCreditPayment(); + info("partial = {$this->invoice->partial}"); + info("balance = {$this->invoice->balance}"); + /* Determine $amount */ if ($this->invoice->partial > 0) $amount = $this->invoice->partial; @@ -64,6 +68,8 @@ class AutoBillInvoice extends AbstractService else return $this->finalizePaymentUsingCredits(); + info("balance remains to be paid!!"); + $gateway_token = $this->getGateway($amount); /* Bail out if no payment methods available */ @@ -71,7 +77,9 @@ class AutoBillInvoice extends AbstractService return $this->invoice; /* $gateway fee */ - $fee = $gateway_token->gateway->calcGatewayFee($amount); + $fee = $gateway_token->gateway->calcGatewayFee($amount, $this->invoice->uses_inclusive_taxes); + + //todo determine exact fee as per PaymentController /* Build payment hash */ $payment_hash = PaymentHash::create([ @@ -103,6 +111,7 @@ class AutoBillInvoice extends AbstractService $payment = PaymentFactory::create($this->invoice->company_id, $this->invoice->user_id); $payment->amount = $amount; + $payment->applied = $amount; $payment->client_id = $this->invoice->client_id; $payment->currency_id = $this->invoice->client->getSetting('currency_id'); $payment->date = now(); @@ -113,10 +122,12 @@ class AutoBillInvoice extends AbstractService $this->invoice->service()->setStatus(Invoice::STATUS_PAID)->save(); - foreach($this->used_credit as $credit) - { - $payment->credits()->attach($credit['credit_id'], ['amount' => $credit['amount']]); - } + foreach($this->used_credit as $credit) + { + $current_credit = Credit::find($credit['credit_id']); + $payment->credits()->attach($current_credit->id, ['amount' => $credit['amount']]); + $this->applyPaymentToCredit($current_credit, $credit['amount']); + } $payment->ledger() ->updatePaymentBalance($amount * -1) @@ -210,7 +221,7 @@ class AutoBillInvoice extends AbstractService - private function applyPaymentToCredit($credit, $amount) + private function applyPaymentToCredit($credit, $amount) :Credit { $credit_item = new InvoiceItem; @@ -226,7 +237,9 @@ class AutoBillInvoice extends AbstractService $credit->line_items = $credit_items; $credit = $credit->calc()->getCredit(); + $credit->save(); + return $credit; } /** diff --git a/app/Services/Invoice/InvoiceService.php b/app/Services/Invoice/InvoiceService.php index d814e382f7e8..325b8328ccea 100644 --- a/app/Services/Invoice/InvoiceService.php +++ b/app/Services/Invoice/InvoiceService.php @@ -93,9 +93,9 @@ class InvoiceService return $this; } - public function addGatewayFee(CompanyGateway $company_gateway, float $amount) + public function addGatewayFee(CompanyGateway $company_gateway, $gateway_type_id, float $amount) { - $this->invoice = (new AddGatewayFee($company_gateway, $this->invoice, $amount))->run(); + $this->invoice = (new AddGatewayFee($company_gateway, $gateway_type_id, $this->invoice, $amount))->run(); return $this; } diff --git a/app/Transformers/ExpenseTransformer.php b/app/Transformers/ExpenseTransformer.php index 7a78910a0b6a..8dcde2bfd23d 100644 --- a/app/Transformers/ExpenseTransformer.php +++ b/app/Transformers/ExpenseTransformer.php @@ -11,6 +11,7 @@ namespace App\Transformers; +use App\Models\Document; use App\Models\Expense; use App\Utils\Traits\MakesHash; use Illuminate\Database\Eloquent\SoftDeletes; @@ -23,15 +24,23 @@ class ExpenseTransformer extends EntityTransformer use MakesHash; use SoftDeletes; protected $defaultIncludes = [ + 'documents', ]; /** * @var array */ protected $availableIncludes = [ - + 'documents', ]; + public function includeDocuments(Expense $expense) + { + $transformer = new DocumentTransformer($this->serializer); + + return $this->includeCollection($expense->documents, $transformer, Document::class); + } + /** * @param Expense $expense * diff --git a/app/Transformers/InvoiceTransformer.php b/app/Transformers/InvoiceTransformer.php index 980e1a2acf0b..a35986307900 100644 --- a/app/Transformers/InvoiceTransformer.php +++ b/app/Transformers/InvoiceTransformer.php @@ -95,6 +95,7 @@ class InvoiceTransformer extends EntityTransformer 'vendor_id' => (string) $this->encodePrimaryKey($invoice->vendor_id), 'status_id' => (string) ($invoice->status_id ?: 1), 'design_id' => (string) $this->encodePrimaryKey($invoice->design_id), + 'recurring_id' => (string) $this->encodePrimaryKey($invoice->recurring_id), 'created_at' => (int) $invoice->created_at, 'updated_at' => (int) $invoice->updated_at, 'archived_at' => (int) $invoice->deleted_at, diff --git a/app/Transformers/ProjectTransformer.php b/app/Transformers/ProjectTransformer.php index 8232603d14e3..6ba7d0749b97 100644 --- a/app/Transformers/ProjectTransformer.php +++ b/app/Transformers/ProjectTransformer.php @@ -11,6 +11,7 @@ namespace App\Transformers; +use App\Models\Document; use App\Models\Project; use App\Utils\Traits\MakesHash; @@ -22,14 +23,23 @@ class ProjectTransformer extends EntityTransformer use MakesHash; protected $defaultIncludes = [ + 'documents', ]; /** * @var array */ protected $availableIncludes = [ + 'documents' ]; + public function includeDocuments(Project $project) + { + $transformer = new DocumentTransformer($this->serializer); + + return $this->includeCollection($project->documents, $transformer, Document::class); + } + public function transform(Project $project) { return [ diff --git a/app/Transformers/TaskTransformer.php b/app/Transformers/TaskTransformer.php index b3d875349752..d5b21a4eacd9 100644 --- a/app/Transformers/TaskTransformer.php +++ b/app/Transformers/TaskTransformer.php @@ -11,6 +11,7 @@ namespace App\Transformers; +use App\Models\Document; use App\Models\Task; use App\Utils\Traits\MakesHash; @@ -22,14 +23,23 @@ class TaskTransformer extends EntityTransformer use MakesHash; protected $defaultIncludes = [ + 'documents' ]; /** * @var array */ protected $availableIncludes = [ + 'documents' ]; + public function includeDocuments(Task $task) + { + $transformer = new DocumentTransformer($this->serializer); + + return $this->includeCollection($task->documents, $transformer, Document::class); + } + public function transform(Task $task) { return [ diff --git a/app/Transformers/VendorTransformer.php b/app/Transformers/VendorTransformer.php index 26cfc5e83c4b..e6a4c7799c03 100644 --- a/app/Transformers/VendorTransformer.php +++ b/app/Transformers/VendorTransformer.php @@ -12,6 +12,7 @@ namespace App\Transformers; use App\Models\Activity; +use App\Models\Document; use App\Models\Vendor; use App\Models\VendorContact; use App\Models\VendorGatewayToken; @@ -29,6 +30,7 @@ class VendorTransformer extends EntityTransformer protected $defaultIncludes = [ 'contacts', + 'documents' ]; /** @@ -36,6 +38,7 @@ class VendorTransformer extends EntityTransformer */ protected $availableIncludes = [ 'activities', + 'documents', ]; /** @@ -62,6 +65,13 @@ class VendorTransformer extends EntityTransformer return $this->includeCollection($vendor->contacts, $transformer, VendorContact::class); } + public function includeDocuments(Vendor $vendor) + { + $transformer = new DocumentTransformer($this->serializer); + + return $this->includeCollection($vendor->documents, $transformer, Document::class); + } + /** * @param Vendor $vendor * @@ -85,6 +95,7 @@ class VendorTransformer extends EntityTransformer 'state' => $vendor->state ?: '', 'postal_code' => $vendor->postal_code ?: '', 'country_id' => (string) $vendor->country_id ?: '', + 'currency_id' => (string) $vendor->currency_id ?: '', 'custom_value1' => $vendor->custom_value1 ?: '', 'custom_value2' => $vendor->custom_value2 ?: '', 'custom_value3' => $vendor->custom_value3 ?: '', diff --git a/database/migrations/2014_10_13_000000_create_users_table.php b/database/migrations/2014_10_13_000000_create_users_table.php index f83680924a20..b4aa48dde235 100644 --- a/database/migrations/2014_10_13_000000_create_users_table.php +++ b/database/migrations/2014_10_13_000000_create_users_table.php @@ -1239,6 +1239,7 @@ class CreateUsersTable extends Migration $table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade')->onUpdate('cascade'); }); + Schema::create('expense_categories', function ($table) { $table->increments('id'); $table->unsignedInteger('user_id'); diff --git a/database/migrations/2020_09_27_215800_update_gateway_table_visible_column.php b/database/migrations/2020_09_27_215800_update_gateway_table_visible_column.php index c5b029f921f8..e991241343f2 100644 --- a/database/migrations/2020_09_27_215800_update_gateway_table_visible_column.php +++ b/database/migrations/2020_09_27_215800_update_gateway_table_visible_column.php @@ -42,6 +42,7 @@ class UpdateGatewayTableVisibleColumn extends Migration $t->boolean('is_deleted')->default(0); }); + } diff --git a/database/migrations/2020_10_11_211122_vendor_schema_update.php b/database/migrations/2020_10_11_211122_vendor_schema_update.php new file mode 100644 index 000000000000..cafedf9a10b5 --- /dev/null +++ b/database/migrations/2020_10_11_211122_vendor_schema_update.php @@ -0,0 +1,47 @@ +timestamp('email_verified_at')->nullable(); + $table->string('confirmation_code')->nullable(); + $table->boolean('confirmed')->default(false); + $table->timestamp('last_login')->nullable(); + $table->smallInteger('failed_logins')->nullable(); + $table->string('oauth_user_id', 100)->nullable()->unique(); + $table->unsignedInteger('oauth_provider_id')->nullable()->unique(); + $table->string('google_2fa_secret')->nullable(); + $table->string('accepted_terms_version')->nullable(); + $table->string('avatar', 255)->nullable(); + $table->string('avatar_type', 255)->nullable(); + $table->string('avatar_size', 255)->nullable(); + $table->string('password'); + $table->string('token')->nullable(); + $table->boolean('is_locked')->default(false); + $table->string('contact_key')->nullable(); + $table->rememberToken(); + $table->index(['company_id', 'email', 'deleted_at']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // + } +} diff --git a/tests/Feature/CompanyGatewayResolutionTest.php b/tests/Feature/CompanyGatewayResolutionTest.php new file mode 100644 index 000000000000..fd2cc0db0f1d --- /dev/null +++ b/tests/Feature/CompanyGatewayResolutionTest.php @@ -0,0 +1,177 @@ +withoutMiddleware( + ThrottleRequests::class + ); + + if (! config('ninja.testvars.stripe')) { + $this->markTestSkipped('Skip test no company gateways installed'); + } + + $this->faker = \Faker\Factory::create(); + + Model::reguard(); + + $this->makeTestData(); + + $this->withoutExceptionHandling(); + + $data = []; + $data[1]['min_limit'] = -1; + $data[1]['max_limit'] = -1; + $data[1]['fee_amount'] = 0.00; + $data[1]['fee_percent'] = 2; + $data[1]['fee_tax_name1'] = 'GST'; + $data[1]['fee_tax_rate1'] = 10; + $data[1]['fee_tax_name2'] = 'GST'; + $data[1]['fee_tax_rate2'] = 10; + $data[1]['fee_tax_name3'] = 'GST'; + $data[1]['fee_tax_rate3'] = 10; + $data[1]['fee_cap'] = 0; + + $data[2]['min_limit'] = -1; + $data[2]['max_limit'] = -1; + $data[2]['fee_amount'] = 0.00; + $data[2]['fee_percent'] = 1; + $data[2]['fee_tax_name1'] = 'GST'; + $data[2]['fee_tax_rate1'] = 10; + $data[2]['fee_tax_name2'] = 'GST'; + $data[2]['fee_tax_rate2'] = 10; + $data[2]['fee_tax_name3'] = 'GST'; + $data[2]['fee_tax_rate3'] = 10; + $data[2]['fee_cap'] = 0; + + //disable ach here + $json_config = json_decode(config('ninja.testvars.stripe')); + $json_config->enable_ach = "0"; + + $this->cg = new CompanyGateway; + $this->cg->company_id = $this->company->id; + $this->cg->user_id = $this->user->id; + $this->cg->gateway_key = 'd14dd26a37cecc30fdd65700bfb55b23'; + $this->cg->require_cvv = true; + $this->cg->show_billing_address = true; + $this->cg->show_shipping_address = true; + $this->cg->update_details = true; + $this->cg->config = encrypt(json_encode($json_config)); + $this->cg->fees_and_limits = $data; + $this->cg->save(); + + + } + + /** + * @covers \App\Models\CompanyGateway::calcGatewayFee() + */ + public function testGatewayResolution() + { + + $fee = $this->cg->calcGatewayFee(10, false, GatewayType::CREDIT_CARD); + $this->assertEquals(0.2, $fee); + $fee = $this->cg->calcGatewayFee(10, false, GatewayType::BANK_TRANSFER); + $this->assertEquals(0.1, $fee); + + } + + /** + * @covers \App|Models\Client::validGatewayForAmount() + */ + + public function testValidationForGatewayAmount() + { + $this->assertTrue($this->client->validGatewayForAmount($this->cg->fees_and_limits->{1}, 10)); + $this->assertTrue($this->client->validGatewayForAmount($this->cg->fees_and_limits->{2}, 10)); + } + + public function testAvailablePaymentMethodsCount() + { + $amount = 10; + $payment_methods = []; + + $this->assertInstanceOf("\\stdClass", $this->cg->fees_and_limits); + $this->assertObjectHasAttribute('min_limit',$this->cg->fees_and_limits->{1}); + + foreach ($this->cg->driver($this->client)->gatewayTypes() as $type) + { + + if(property_exists($this->cg->fees_and_limits, $type)) + { + if($this->client->validGatewayForAmount($this->cg->fees_and_limits->{$type}, $amount)){ + $payment_methods[] = [$this->cg->id => $type]; + } + } + else + { + $payment_methods[] = [$this->cg->id => $type]; + } + + } + + $this->assertEquals(3, count($payment_methods)); + } + + public function testAddAchBackIntoMethods() + { + $this->assertEquals(3, count($this->cg->driver($this->client)->gatewayTypes())); + + $cg_config = json_decode(decrypt($this->cg->config)); + $cg_config->enable_ach = "1"; + $this->cg->config = encrypt(json_encode($cg_config)); + $this->cg->save(); + + $this->assertEquals(4, count($this->cg->driver($this->client)->gatewayTypes())); + + info(print_r($this->client->getPaymentMethods(10),1)); + } +} diff --git a/tests/Feature/CompanyGatewayTest.php b/tests/Feature/CompanyGatewayTest.php index 7da9cdd2977a..8dc1499fc17a 100644 --- a/tests/Feature/CompanyGatewayTest.php +++ b/tests/Feature/CompanyGatewayTest.php @@ -11,6 +11,7 @@ namespace Tests\Feature; use App\Models\CompanyGateway; +use App\Models\GatewayType; use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Support\Facades\URL; use Tests\MockAccountData; @@ -141,7 +142,7 @@ class CompanyGatewayTest extends TestCase $balance = $this->invoice->balance; - $this->invoice = $this->invoice->service()->addGatewayFee($cg, $this->invoice->balance)->save(); + $this->invoice = $this->invoice->service()->addGatewayFee($cg, GatewayType::CREDIT_CARD, $this->invoice->balance)->save(); $this->invoice = $this->invoice->calc()->getInvoice(); $items = $this->invoice->line_items; @@ -178,12 +179,12 @@ class CompanyGatewayTest extends TestCase $total = 10.93; $total_invoice_count = 5; - $total_gateway_fee = round($cg->calcGatewayFee($total, true), 2); + $total_gateway_fee = round($cg->calcGatewayFee($total, true, GatewayType::CREDIT_CARD), 2); $this->assertEquals(1.58, $total_gateway_fee); /*simple pro rata*/ - $fees_and_limits = $cg->getFeesAndLimits(); + $fees_and_limits = $cg->getFeesAndLimits(GatewayType::CREDIT_CARD); /*Calculate all subcomponents of the fee*/ diff --git a/tests/Feature/DocumentsApiTest.php b/tests/Feature/DocumentsApiTest.php new file mode 100644 index 000000000000..1b0ad8454674 --- /dev/null +++ b/tests/Feature/DocumentsApiTest.php @@ -0,0 +1,157 @@ +makeTestData(); + + Session::start(); + + $this->faker = \Faker\Factory::create(); + + Model::reguard(); + } + + public function testClientDocuments() + { + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->get('/api/v1/clients'); + + $response->assertStatus(200); + $arr = $response->json(); + $this->assertArrayHasKey('documents', $arr['data'][0]); + + } + + + public function testInvoiceDocuments() + { + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->get('/api/v1/invoices'); + + $response->assertStatus(200); + $arr = $response->json(); + $this->assertArrayHasKey('documents', $arr['data'][0]); + + } + + + public function testProjectsDocuments() + { + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->get('/api/v1/projects'); + + $response->assertStatus(200); + $arr = $response->json(); + $this->assertArrayHasKey('documents', $arr['data'][0]); + + } + + + public function testExpenseDocuments() + { + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->get('/api/v1/expenses'); + + $response->assertStatus(200); + $arr = $response->json(); + $this->assertArrayHasKey('documents', $arr['data'][0]); + + } + + + public function testVendorDocuments() + { + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->get('/api/v1/vendors'); + + $response->assertStatus(200); + $arr = $response->json(); + $this->assertArrayHasKey('documents', $arr['data'][0]); + + } + + + public function testProductDocuments() + { + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->get('/api/v1/products'); + + $response->assertStatus(200); + $arr = $response->json(); + $this->assertArrayHasKey('documents', $arr['data'][0]); + + } + + // public function testTaskDocuments() + // { + + // $response = $this->withHeaders([ + // 'X-API-SECRET' => config('ninja.api_secret'), + // 'X-API-TOKEN' => $this->token, + // ])->get('/api/v1/tasks'); + + // $response->assertStatus(200); + // $arr = $response->json(); + // $this->assertArrayHasKey('documents', $arr['data'][0]); + + // } + +} diff --git a/tests/MockAccountData.php b/tests/MockAccountData.php index 31c88f16fd76..27acbaf2b934 100644 --- a/tests/MockAccountData.php +++ b/tests/MockAccountData.php @@ -35,6 +35,7 @@ use App\Models\Expense; use App\Models\GroupSetting; use App\Models\Invoice; use App\Models\InvoiceInvitation; +use App\Models\Product; use App\Models\Project; use App\Models\Quote; use App\Models\QuoteInvitation; @@ -159,6 +160,11 @@ trait MockAccountData $company_token->save(); + Product::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + ]); + $this->client = Client::factory()->create([ 'user_id' => $this->user->id, 'company_id' => $this->company->id, diff --git a/tests/Unit/AutoBillInvoiceTest.php b/tests/Unit/AutoBillInvoiceTest.php index e31ddd2f15c6..5b7e4c89e05c 100644 --- a/tests/Unit/AutoBillInvoiceTest.php +++ b/tests/Unit/AutoBillInvoiceTest.php @@ -34,24 +34,19 @@ class AutoBillInvoiceTest extends TestCase public function testAutoBillFunctionality() { - // info("client balance = {$this->client->balance}"); - // info("invoice balance = {$this->invoice->balance}"); - - $this->assertEquals($this->client->balance, 10); $this->assertEquals($this->client->paid_to_date, 0); $this->assertEquals($this->client->credit_balance, 10); $this->invoice->service()->markSent()->autoBill()->save(); - // info(print_r($this->invoice->payments()->first()->toArray(),1)); $this->assertNotNull($this->invoice->payments()); $this->assertEquals(10, $this->invoice->payments()->sum('payments.amount')); - //info(print_r($this->invoice->payments()->get(),1)); $this->assertEquals($this->client->balance, 0); $this->assertEquals($this->client->paid_to_date, 10); $this->assertEquals($this->client->credit_balance, 0); + } } diff --git a/tests/Unit/Migration/FeesAndLimitsTest.php b/tests/Unit/Migration/FeesAndLimitsTest.php index 7550bf1a644b..ef737f075d6a 100644 --- a/tests/Unit/Migration/FeesAndLimitsTest.php +++ b/tests/Unit/Migration/FeesAndLimitsTest.php @@ -36,7 +36,8 @@ class FeesAndLimitsTest extends TestCase $data['fee_tax_rate2'] = ''; $data['fee_tax_name3'] = ''; $data['fee_tax_rate3'] = 0; - + $data['fee_cap'] = 0; + $fees_and_limits_array = []; $fees_and_limits_array[] = $data;