diff --git a/app/Classes/General/Eloquent/Filters/DoesNotHaveSegments.php b/app/Classes/General/Eloquent/Filters/DoesNotHaveSegments.php new file mode 100644 index 00000000..88a1ab3d --- /dev/null +++ b/app/Classes/General/Eloquent/Filters/DoesNotHaveSegments.php @@ -0,0 +1,21 @@ +whereDoesntHave('segments', function ($segment) use ($value) { + $segment->whereIn('id', $value); + }); + } +} diff --git a/app/Classes/General/Eloquent/Filters/IssuerNot.php b/app/Classes/General/Eloquent/Filters/IssuerNot.php new file mode 100644 index 00000000..77423b5d --- /dev/null +++ b/app/Classes/General/Eloquent/Filters/IssuerNot.php @@ -0,0 +1,20 @@ +where('issuer', '!=', $value); + } + +} \ No newline at end of file diff --git a/app/Classes/General/Eloquent/Filters/SegmentsIn.php b/app/Classes/General/Eloquent/Filters/SegmentsIn.php new file mode 100644 index 00000000..3f756810 --- /dev/null +++ b/app/Classes/General/Eloquent/Filters/SegmentsIn.php @@ -0,0 +1,21 @@ +whereHas('segments', function ($segment) use ($value) { + $segment->whereIn('id', $value); + }); + } +} diff --git a/app/Classes/General/Eloquent/Filters/WithOutTransactions.php b/app/Classes/General/Eloquent/Filters/WithOutTransactions.php new file mode 100644 index 00000000..3b551f65 --- /dev/null +++ b/app/Classes/General/Eloquent/Filters/WithOutTransactions.php @@ -0,0 +1,20 @@ +whereDoesntHave('transactions'); + } +} \ No newline at end of file diff --git a/app/Classes/General/Eloquent/Filters/WithTotalPayments.php b/app/Classes/General/Eloquent/Filters/WithTotalPayments.php new file mode 100644 index 00000000..d93e55e2 --- /dev/null +++ b/app/Classes/General/Eloquent/Filters/WithTotalPayments.php @@ -0,0 +1,25 @@ +leftJoin('bookings', 'companies.id', '=', 'bookings.company_id')->rightJoin('transactions', function ($join) { + $join->on('bookings.id', '=', 'transactions.booking_id')->where('transactions.type', TransactionType::PAYMENT)->whereIn('transactions.status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]); + })->addSelect(['companies.*', DB::raw('SUM(transactions.amount) as total_payments')])->groupBy(['companies.id']); + } +} \ No newline at end of file diff --git a/app/Classes/General/Eloquent/Filters/WithoutConfirmedPayments.php b/app/Classes/General/Eloquent/Filters/WithoutConfirmedPayments.php new file mode 100644 index 00000000..e17ed5da --- /dev/null +++ b/app/Classes/General/Eloquent/Filters/WithoutConfirmedPayments.php @@ -0,0 +1,24 @@ +whereDoesntHave('transactions', function($transaction){ + return $transaction->where('transactions.type', TransactionType::PAYMENT)->whereIn('transactions.status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]); + }); + } +} \ No newline at end of file diff --git a/app/Classes/Modules/Accounts/ControllersLogic/AddNewMemberLogic.php b/app/Classes/Modules/Accounts/ControllersLogic/AddNewMemberLogic.php new file mode 100644 index 00000000..1744f50c --- /dev/null +++ b/app/Classes/Modules/Accounts/ControllersLogic/AddNewMemberLogic.php @@ -0,0 +1,78 @@ + 'Updated Email', + 'message' => 'Successfully updated email' + ]; + } + + /** + * @var AssignEmployeeProcessor + */ + private $assignEmployeeProcessor; + + /** + * @var GenerateEmailVerificationAttemptProcessor + */ + private $generateEmailVerificationAttemptProcessor; + + /** + * AddNewMemberLogic constructor. + * @param AssignEmployeeProcessor $assignEmployeeProcessor + * @param GenerateEmailVerificationAttemptProcessor $generateEmailVerificationAttemptProcessor + */ + public function __construct(AssignEmployeeProcessor $assignEmployeeProcessor, GenerateEmailVerificationAttemptProcessor $generateEmailVerificationAttemptProcessor) + { + $this->assignEmployeeProcessor = $assignEmployeeProcessor; + $this->generateEmailVerificationAttemptProcessor = $generateEmailVerificationAttemptProcessor; + } + + /** + * @param Request $request + * @return JsonResponse + * @throws ResourceConflictException + * @throws \App\Classes\Exceptions\AccessForbiddenException + * @throws \App\Classes\Exceptions\MalformedRequestException + * @throws \App\Classes\Exceptions\RequestValidationException + */ + public function logic(Request $request): JsonResponse + { + try { + $user = Auth::user()->replicate(); + $user->email = $request->input('email'); + $user->status = ApprovalStatus::PENDING_VERIFICATION; + $user->save(); + } catch (QueryException $exception){ + throw new ResourceConflictException('Unable to change your email address as it already exists'); + } + + if($company = Auth::user()->company()->first()){ + $Object = new EmploymentObject($company, $user); + $this->assignEmployeeProcessor->execute($Object); + } + + $this->generateEmailVerificationAttemptProcessor->execute($user); + + return $this->resourceResponse(new UserResource($user)); + } +} diff --git a/app/Classes/Modules/Bookings/ControllersLogic/CreateBookingPaymentLogic.php b/app/Classes/Modules/Bookings/ControllersLogic/CreateBookingPaymentLogic.php index b2a0732a..df2a7960 100644 --- a/app/Classes/Modules/Bookings/ControllersLogic/CreateBookingPaymentLogic.php +++ b/app/Classes/Modules/Bookings/ControllersLogic/CreateBookingPaymentLogic.php @@ -80,7 +80,7 @@ class CreateBookingPaymentLogic extends AbstractControllerLogic $outstanding = $this->calculatesBookingOutstanding->execute($booking); - if($conversionObject->getAmount() > $outstanding) throw new MalformedRequestException('Your payment must not be greater than '. $outstanding .'.'); + if($conversionObject->getAmount() > round($outstanding, 2)) throw new MalformedRequestException('Your payment must not be greater than '. $outstanding .'.'); $configurations = $this->fetchBookingQuotation->execute($booking->company, $conversionObject); diff --git a/app/Classes/Modules/Bookings/ControllersLogic/FetchBookingPaymentQuotationLogic.php b/app/Classes/Modules/Bookings/ControllersLogic/FetchBookingPaymentQuotationLogic.php index c20450d8..5885d560 100644 --- a/app/Classes/Modules/Bookings/ControllersLogic/FetchBookingPaymentQuotationLogic.php +++ b/app/Classes/Modules/Bookings/ControllersLogic/FetchBookingPaymentQuotationLogic.php @@ -68,7 +68,7 @@ class FetchBookingPaymentQuotationLogic extends AbstractControllerLogic $conversionObject = new CurrencyConversionObject(floatval(str_replace(',', '', $request->input('amount'))), $booking->convertible_currency_id, $booking->service_id, $booking->fix_currency_id === 1 ? 0:1, PaymentMethodType::PAYMENT_METHODS[$request->input('payment_method')]); $outstanding = $this->calculatesBookingOutstanding->execute($booking); - if($conversionObject->getAmount() > $outstanding) throw new MalformedRequestException('Your payment must not be greater than '.$booking->fixedCurrency->short_code.' '. number_format((float)$outstanding, 2, '.', ',')); + if($conversionObject->getAmount() > round($outstanding, 2)) throw new MalformedRequestException('Your payment must not be greater than '.$booking->fixedCurrency->short_code.' '. number_format((float)$outstanding, 2, '.', ',')); return $this->response(['data' => $this->generatesBookingQuotation->execute( $this->fetchBookingQuotation->execute($booking->company, $conversionObject), diff --git a/app/Classes/Modules/Bookings/Services/CalculatesBookingCurrencyAverageRate.php b/app/Classes/Modules/Bookings/Services/CalculatesBookingCurrencyAverageRate.php index e00ab114..2a6de8d2 100644 --- a/app/Classes/Modules/Bookings/Services/CalculatesBookingCurrencyAverageRate.php +++ b/app/Classes/Modules/Bookings/Services/CalculatesBookingCurrencyAverageRate.php @@ -9,19 +9,29 @@ use Carbon\Carbon; class CalculatesBookingCurrencyAverageRate { + /** @var CalculatesBookingPayableAmount */ + private $calculatesBookingPayableAmount; + + /** + * CalculatesBookingCurrencyAverageRate constructor. + * @param CalculatesBookingPayableAmount $calculatesBookingPayableAmount + */ + public function __construct(CalculatesBookingPayableAmount $calculatesBookingPayableAmount) + { + $this->calculatesBookingPayableAmount = $calculatesBookingPayableAmount; + } + + public function execute(Booking $booking, $type){ if ($type == TransactionType::PAYMENT) { - return $booking->transactions() - ->where('type', TransactionType::PAYMENT) - ->whereIn('status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]) - ->avg('currency_rate'); + $totalPayment = $booking->fix_currency_id === 1 ? $booking->transactions()->payments()->complete()->sum('original_amount') : + $booking->transactions()->payments()->complete()->selectRaw('sum(amount - service_charge - tax) as sub_total')->get()->sum('sub_total'); + return $this->calculatesBookingPayableAmount->execute($booking, $booking->fix_currency_id) / $totalPayment; + } else if ($type == TransactionType::BILL) { - return $booking->transactions() - ->where('type', TransactionType::BILL) - ->whereIn('status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED]) - ->avg('currency_rate'); + return $booking->transactions()->bills()->complete()->sum('original_amount') / $booking->transactions()->bills()->complete()->sum('amount'); } } diff --git a/app/Classes/ValueObjects/Constants/RoleTypes.php b/app/Classes/ValueObjects/Constants/RoleTypes.php index 3027bcb1..ec5fe961 100644 --- a/app/Classes/ValueObjects/Constants/RoleTypes.php +++ b/app/Classes/ValueObjects/Constants/RoleTypes.php @@ -16,4 +16,12 @@ final class RoleTypes public const USER = 3; + public const CURRENCY_SUPPLIER = 4; + + public const MONEY_MULE = 5; + + public const ORIGIN_ACCOUNT_ADMIN = 6; + + public const DESTINATION_ACCOUNT_ADMIN = 7; + } \ No newline at end of file diff --git a/app/Http/Controllers/Companies/AddNewMemberController.php b/app/Http/Controllers/Companies/AddNewMemberController.php new file mode 100644 index 00000000..6307ac37 --- /dev/null +++ b/app/Http/Controllers/Companies/AddNewMemberController.php @@ -0,0 +1,22 @@ +execute($request); + } +} diff --git a/app/Http/Resources/CompanyResource.php b/app/Http/Resources/CompanyResource.php index 00ca7624..0c988ac1 100644 --- a/app/Http/Resources/CompanyResource.php +++ b/app/Http/Resources/CompanyResource.php @@ -3,13 +3,17 @@ namespace App\Http\Resources; use App\Classes\Modules\Companies\Services\FetchesCompanyServices; +use App\Classes\ValueObjects\Constants\ApprovalStatus; use App\Classes\ValueObjects\Constants\BankAccountType; use App\Classes\ValueObjects\Constants\BusinessType; use App\Classes\ValueObjects\Constants\DocumentType; +use App\Classes\ValueObjects\Constants\RoleTypes; use App\Classes\ValueObjects\Constants\SegmentConstants; +use App\Classes\ValueObjects\Constants\TransactionType; use App\Models\Currency; use App\Models\SegmentConstant; use Illuminate\Http\Resources\Json\JsonResource; +use Illuminate\Support\Facades\Auth; class CompanyResource extends JsonResource { @@ -21,6 +25,7 @@ class CompanyResource extends JsonResource */ public function toArray($request) { + $lastPayment = $this->transactions()->where('transactions.type', TransactionType::PAYMENT)->whereIn('transactions.status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED])->orderBy('id', 'DESC')->first(); return [ 'id' => $this->id, 'name' => $this->name, @@ -30,9 +35,11 @@ class CompanyResource extends JsonResource 'status' => (int) $this->status, 'contact' => new ContactResource ($this->when($this->has('contacts'), $this->contacts->first())), 'address' => new AddressResource($this->when($this->has('addresses'), $this->addresses->where('billing', true)->first())), - 'employee' => new UserResource($this->employees->first()), + 'employee' => new UserResource(Auth::user()->type === RoleTypes::USER ? $this->employees()->where('email', '=', Auth::user()->email)->first() : $this->employees()->where('users.status', '=', ApprovalStatus::APPROVED)->orderBy('id', 'DESC')->first()), 'identification' => new DocumentResource($this->documents->whereIn('document_type', DocumentType::IDENTIFICATION_DOCUMENTS)->first()), 'bookings' => $this->whenLoaded('bookings', $this->bookings()->orderBy('id', 'DESC')->get(), []), + 'total_payments' => (float) $this->transactions()->where('transactions.type', TransactionType::PAYMENT)->whereIn('transactions.status', [ApprovalStatus::APPROVED, ApprovalStatus::COMPLETED])->sum('amount'), + 'last_payment' => $lastPayment ? $lastPayment->created_at->diffForHumans() : 'No Payments', 'personal_banks' => BankResource::collection($this->banks->where('type', BankAccountType::PERSONAL)), 'recipient_banks' => [ 'accounts' => BankResource::collection($this->banks->where('type', BankAccountType::EXTERNAL)), @@ -43,7 +50,8 @@ class CompanyResource extends JsonResource 'currencies' => $this->when($this->business_type === BusinessType::CURRENCY_VENDOR, function(){ $segment = SegmentConstant::where('reference', SegmentConstants::SUPPLIER_CURRENCIES)->where('detail->id', $this->id)->first(); return $segment ? CurrencyResource::collection(Currency::whereIn('id', $segment->detail->currencies)->get()) : []; - }) + }), + 'created_at' => $this->created_at->format('d-m-Y') ]; diff --git a/app/Http/Resources/TransactionResource.php b/app/Http/Resources/TransactionResource.php index fc0dff73..1793d191 100644 --- a/app/Http/Resources/TransactionResource.php +++ b/app/Http/Resources/TransactionResource.php @@ -21,6 +21,7 @@ class TransactionResource extends JsonResource 'booking' => new BookingResource($this->booking), 'type' => (int) $this->type, 'bill_no' => $this->bill_no, + 'recipient_bank_account' => new BankResource($this->when((int) $this->type === TransactionType::BILL,$this->booking->bank)), 'amount' => (double) $this->amount, 'original_amount' => (double) $this->original_amount, 'currency' => new CurrencyResource($this->currency), diff --git a/app/Models/Company.php b/app/Models/Company.php index c1a6a191..e2c9dda4 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -12,6 +12,9 @@ use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\MorphMany; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Collection; +use Staudenmeir\EloquentHasManyDeep\HasManyDeep; +use Staudenmeir\EloquentHasManyDeep\HasRelationships; + /** * Class Company @@ -27,11 +30,12 @@ use Illuminate\Support\Collection; */ class Company extends AbstractModel implements Documentable { + use HasRelationships; use SoftDeletes; protected $table = 'companies'; - protected $dates = ['deleted_at']; + protected $dates = ['deleted_at', 'created_at']; /** @@ -90,6 +94,14 @@ class Company extends AbstractModel implements Documentable return $this->HasMany(Booking::class, 'company_id'); } + /** + * @return hasManyDeep + */ + public function transactions(): hasManyDeep + { + return $this->hasManyDeep(Transaction::class, [Booking::class], ['company_id', 'booking_id'], ['id', 'id']); + } + /** * @return Builder */ diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php index 9d1ed5b9..2624117b 100644 --- a/app/Models/Transaction.php +++ b/app/Models/Transaction.php @@ -9,6 +9,8 @@ use Carbon\Carbon; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\HasOne; +use Illuminate\Database\Eloquent\Relations\HasOneThrough; use Illuminate\Database\Eloquent\Relations\MorphMany; @@ -24,6 +26,14 @@ class Transaction extends AbstractModel implements Documentable return $this->BelongsTo( Booking::class, 'booking_id', 'id'); } + /** + * @return BelongsTo + */ + public function recipientBankAccount(): BelongsTo + { + return $this->BelongsTo(Bank::class); + } + /** * @return MorphMany */ diff --git a/composer.json b/composer.json index 7a3ac9b3..377c8932 100644 --- a/composer.json +++ b/composer.json @@ -11,6 +11,7 @@ "php": "^7.2.5", "ext-fileinfo": "*", "ext-json": "^1.6", + "ext-zip": "*", "barryvdh/laravel-dompdf": "^0.9.0", "carlos-meneses/laravel-mpdf": "^2.1", "fideloper/proxy": "^4.2", @@ -23,6 +24,7 @@ "rinvex/countries": "^6.1", "spatie/laravel-activitylog": "^3.14", "spatie/laravel-permission": "^3.17", + "staudenmeir/eloquent-has-many-deep": "^1.7", "tymon/jwt-auth": "^1.0" }, "require-dev": { diff --git a/config/database.php b/config/database.php index b42d9b30..aeabec6f 100644 --- a/config/database.php +++ b/config/database.php @@ -56,7 +56,7 @@ return [ 'collation' => 'utf8mb4_unicode_ci', 'prefix' => '', 'prefix_indexes' => true, - 'strict' => true, + 'strict' => false, 'engine' => null, 'options' => extension_loaded('pdo_mysql') ? array_filter([ PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), diff --git a/resources/assets/vue/components/banks/elements/BankInComponent.vue b/resources/assets/vue/components/banks/elements/BankInComponent.vue new file mode 100644 index 00000000..ff42850a --- /dev/null +++ b/resources/assets/vue/components/banks/elements/BankInComponent.vue @@ -0,0 +1,36 @@ + + + \ No newline at end of file diff --git a/resources/assets/vue/components/bookings/elements/BookingComponent.vue b/resources/assets/vue/components/bookings/elements/BookingComponent.vue index 509e9840..99c3228c 100644 --- a/resources/assets/vue/components/bookings/elements/BookingComponent.vue +++ b/resources/assets/vue/components/bookings/elements/BookingComponent.vue @@ -40,13 +40,13 @@
Payable Amount
{{this.item.amount}} {{this.item.fixed_currency.short_code}}
-
+
status
{{item.status === 3 ? 'Complete' : 'In Progress'}}
-
+
Billing
Pending... diff --git a/resources/assets/vue/components/bookings/elements/PaymentProofComponent.vue b/resources/assets/vue/components/bookings/elements/PaymentProofComponent.vue index b684d64e..c403af9d 100644 --- a/resources/assets/vue/components/bookings/elements/PaymentProofComponent.vue +++ b/resources/assets/vue/components/bookings/elements/PaymentProofComponent.vue @@ -3,53 +3,50 @@
-
-
-
+
+
+
-
Reference
- -
- {{item.booking.marking}} + -
-
Date
-
- {{item.updated_at}}
+
-
-
Marking
-
- {{item.booking.company.reference}} -
-
-
-
Currency
-
- {{item.original_currency.short_code}} -
-
-
-
Amount
-
- {{(Math.round((item.original_amount + Number.EPSILON) * 100) / 100).toFixed(2).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}} -
-
-
+
+
+
+
+
+ style=" fill:#000000;">
- +
@@ -63,6 +60,16 @@ diff --git a/resources/assets/vue/components/bookings/forms/BookingFormComponent.vue b/resources/assets/vue/components/bookings/forms/BookingFormComponent.vue index 414f1a42..65b355f8 100644 --- a/resources/assets/vue/components/bookings/forms/BookingFormComponent.vue +++ b/resources/assets/vue/components/bookings/forms/BookingFormComponent.vue @@ -7,7 +7,7 @@
- +
@@ -21,7 +21,7 @@
- +
diff --git a/resources/assets/vue/components/bookings/forms/PaymentProofFormComponent.vue b/resources/assets/vue/components/bookings/forms/PaymentProofFormComponent.vue index 612b8735..0c85a3f7 100644 --- a/resources/assets/vue/components/bookings/forms/PaymentProofFormComponent.vue +++ b/resources/assets/vue/components/bookings/forms/PaymentProofFormComponent.vue @@ -10,6 +10,7 @@
+
diff --git a/resources/assets/vue/components/bookings/forms/PurchaseOrderFormComponent.vue b/resources/assets/vue/components/bookings/forms/PurchaseOrderFormComponent.vue index de55e62c..ba3a39b1 100644 --- a/resources/assets/vue/components/bookings/forms/PurchaseOrderFormComponent.vue +++ b/resources/assets/vue/components/bookings/forms/PurchaseOrderFormComponent.vue @@ -101,9 +101,9 @@
-
+
-