Created
June 20, 2024 09:14
-
-
Save nsilva/2e55b7a4e6a41eb67cf1d8f970c608bb to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| namespace App\Domains\Altix\Services; | |
| use App\Domains\Altix\Models\Document; | |
| use App\Domains\Altix\Models\DocumentTemplate; | |
| use App\Domains\Altix\Models\Enums\DocumentTemplateType as DocumentTemplateTypeEnum; | |
| use App\Domains\Altix\Models\Enums\DocumentTypeId; | |
| use App\Domains\Altix\Models\Fund; | |
| use App\Domains\Altix\Models\Investment; | |
| use App\Domains\Altix\Models\UserFund; | |
| use App\Domains\Altix\Models\UserFundDocument; | |
| use App\Domains\Altix\Models\UserSignedDocument; | |
| use App\Domains\Altix\Services\Traits\CanDownloadPdfFile; | |
| use App\Domains\Auth\Models\User; | |
| use App\Exceptions\Codes\MysqlErrorCodes; | |
| use App\Services\BaseService; | |
| use Carbon\Carbon; | |
| use Illuminate\Database\Connection; | |
| use Illuminate\Database\Eloquent\Builder as EloquentBuilder; | |
| use Illuminate\Database\Eloquent\Collection; | |
| use Illuminate\Database\Eloquent\ModelNotFoundException; | |
| use Illuminate\Database\Eloquent\Relations\Relation; | |
| use Illuminate\Database\QueryException; | |
| use Illuminate\Support\Collection as SupportCollection; | |
| use Illuminate\Support\Facades\DB; | |
| use Illuminate\Support\Facades\Log; | |
| use Symfony\Component\HttpFoundation\StreamedResponse; | |
| class FundService extends BaseService | |
| { | |
| use CanDownloadPdfFile; | |
| protected DocumentService|null $documentService = null; | |
| protected DocumentTemplateService|null $documentTemplateService = null; | |
| protected InvestmentService|null $investmentService = null; | |
| /** | |
| * FundService constructor. | |
| * | |
| * @param Fund $fund | |
| */ | |
| public function __construct(Fund $fund) | |
| { | |
| $this->model = $fund; | |
| } | |
| /** | |
| * Check whether the user can invest the supplied amount in the supplied fund. | |
| * | |
| * @param User $user | |
| * @param Fund|string $fund Fund instance or fund UUID. | |
| * @param int $amount | |
| * @return string|null Error message, if there is a problem with the amount to invest. | |
| */ | |
| public function checkFundInvestmentAmountByUser(User $user, Fund|string $fund, int $amount): ?string | |
| { | |
| if (! ($fund instanceof Fund)) { | |
| $fund = $this->getByUuid($fund, ['id', 'fund_size', 'min_ticket', 'amount_committed']); | |
| } | |
| $userAlreadyInvestedIntoFund = $user->hasAlreadyInvestedInFund($fund->id); | |
| return $this->checkFundInvestmentAmountAlreadyInvested($fund, $amount, $userAlreadyInvestedIntoFund); | |
| } | |
| /** | |
| * Check the amount to invest into a fund in which the user possibly already invested. | |
| * Check for: | |
| * - Maximum amount | |
| * - Room in the fund, if enabled | |
| * - If the user already has invested into the fund, the minimum increase amount | |
| * - If the user did not yet invest into the fund, the maximum of the minimum amount to invest and the minimum ticket for the fund | |
| * | |
| * @param Fund $fund | |
| * @param int $amount | |
| * @param bool|null $userAlreadyInvestedIntoFund | |
| * @return string|null Error message, if there is a problem with the amount to invest. | |
| */ | |
| public function checkFundInvestmentAmountAlreadyInvested(Fund $fund, int $amount, bool $userAlreadyInvestedIntoFund = null): ?string | |
| { | |
| $message = null; | |
| // check for maximum amount of investment | |
| $maximumAmount = config('altix.investment.amount.maximum'); | |
| if ($amount > $maximumAmount) { | |
| $message = __('The maximum amount to invest is :maximum.', [ | |
| 'maximum' => numberWithCurrency($maximumAmount), | |
| ]); | |
| } | |
| // check for room within fund, if enabled | |
| if (config('altix.investment.amount.check-room')) { | |
| $maximumAmount = $fund->getRoomAmount(); | |
| if ($amount > $maximumAmount) { | |
| $message = __('The maximum amount to invest in this fund is :maximum.', [ | |
| 'maximum' => numberWithCurrency($maximumAmount), | |
| ]); | |
| } | |
| } | |
| // if the user already invested in the fund the amount should be at least the minimum increase amount | |
| if ($userAlreadyInvestedIntoFund) { | |
| $minimumIncreaseAmount = config('altix.investment.amount.minimum-increase'); | |
| if ($amount < $minimumIncreaseAmount) { | |
| $message = __('The minimum amount to invest in this fund is :minimum.', [ | |
| 'minimum' => numberWithCurrency($minimumIncreaseAmount), | |
| ]); | |
| } | |
| } else { | |
| // if the user has not yet invested in the fund the amount should be at least the maximum of the minimum ticket and the minimum amount | |
| $minimumAmount = config('altix.investment.amount.minimum'); | |
| $minimumTicket = $fund->min_ticket; | |
| if ($minimumTicket > $minimumAmount) { | |
| if ($amount < $minimumTicket) { | |
| $message = __('The minimum amount to invest in this fund is :minimum.', [ | |
| 'minimum' => numberWithCurrency($minimumTicket), | |
| ]); | |
| } | |
| } elseif ($amount < $minimumAmount) { | |
| $message = __('The minimum amount to invest is :minimum.', [ | |
| 'minimum' => numberWithCurrency($minimumAmount), | |
| ]); | |
| } | |
| } | |
| return $message; | |
| } | |
| /** | |
| * Register click from user to view more information on fund. | |
| * | |
| * @param User $user | |
| * @param Fund $fund | |
| * @return void | |
| */ | |
| public function clickMore(User $user, Fund $fund): void | |
| { | |
| UserFund::query()->updateOrCreate([ | |
| 'user_id' => $user->id, | |
| 'fund_id' => $fund->id, | |
| ], [ | |
| 'clicked_detail_at' => now(), | |
| ]); | |
| } | |
| /** | |
| * Retrieve fund by its slug. | |
| * | |
| * @param string $slug | |
| * @param string[]|array|string $columns | |
| * @param bool $noException | |
| * @return Fund|null | |
| */ | |
| public function getBySlug(string $slug, array|string $columns = ['*'], bool $noException = false): ?Fund | |
| { | |
| $query = $this->getEnabledBuilder() | |
| ->where('slug', $slug); | |
| if ($noException) { | |
| return $query->first($columns); | |
| } | |
| return $query->firstOrFail($columns); | |
| } | |
| /** | |
| * Retrieve the latest fund document by the slug of the document type. | |
| * | |
| * @param Fund $fund | |
| * @param string $slug | |
| * @param array|string|string[] $columns | |
| * @return Document|null | |
| */ | |
| public function getDocumentByTypeSlug(Fund $fund, string $slug, array|string $columns = ['*']): ?Document | |
| { | |
| $document = $fund->documents() | |
| ->joinRelationship('documentType') | |
| ->select($columns) | |
| ->where('document_types.slug', $slug) | |
| ->orderBy('documents.id', 'desc') | |
| ->limit(1) | |
| ->first(); | |
| if (null === $document) { | |
| $message = sprintf('Last document of type by slug \'%s\' for fund #%u: not found', $slug, $fund->id); | |
| Log::error($message); | |
| throw (new ModelNotFoundException())->setModel(Document::class); | |
| } | |
| return $document; | |
| } | |
| /** | |
| * Get fund document download response for user. | |
| * @param User $user | |
| * @param Fund $fund | |
| * @param Document $document | |
| * @param bool $inline | |
| * @return StreamedResponse|null | |
| */ | |
| public function getDownloadDocumentResponse(User $user, Fund $fund, Document $document, bool $inline = false): ?StreamedResponse | |
| { | |
| return DB::transaction(function (Connection $connection) use ($user, $fund, $document, $inline) { | |
| $this->registerDocumentDownload($user, $fund, $document); | |
| [$path, $filename] = $this->getDocumentService()->getPathAndFilename($document); | |
| $response = $this->getDownloadFileResponse($path, $filename, $inline, 'encrypted'); | |
| if (null === $response) { | |
| $connection->rollBack(); | |
| } | |
| return $response; | |
| }); | |
| } | |
| /** | |
| * Retrieve the downloaded documents of a fund, indexed by document ID. | |
| * | |
| * @param User $user | |
| * @param int $fundId | |
| * @return UserFundDocument[]|Collection | |
| */ | |
| public function getDownloadedDocuments(User $user, int $fundId): Collection | |
| { | |
| return $user->userFundDocuments($fundId) | |
| ->with('documentType') | |
| ->get() | |
| ->keyBy('document_id'); | |
| } | |
| /** | |
| * Retrieve the fund documents. | |
| * | |
| * @TODO Handle multiple versions of document types. | |
| * | |
| * @param int $fundId | |
| * @return Document[]|Collection | |
| */ | |
| public function getFundDocuments(int $fundId): Collection | |
| { | |
| return Document::with('documentType') | |
| ->select('documents.*') | |
| ->where( | |
| [ | |
| 'model_type' => Fund::class, | |
| 'model_id' => $fundId, | |
| ] | |
| ) | |
| ->join('document_types', 'document_types.id', '=', 'documents.document_type_id') | |
| ->orderBy('document_types.order') | |
| ->get(); | |
| } | |
| /** | |
| * Retrieve the fund documents. | |
| * | |
| * @TODO Handle multiple versions of document types. | |
| * | |
| * @param User $user | |
| * @param int $fundId | |
| * @return Document[]|Collection | |
| */ | |
| public function getFundDocumentsForUser(User $user, int $fundId): Collection | |
| { | |
| return Document::with('documentType') | |
| ->join('document_types', 'document_types.id', '=', 'documents.document_type_id') | |
| ->leftJoin('user_fund_documents', function ($join) use ($fundId, $user) { | |
| $join->on('user_fund_documents.document_id', '=', 'documents.id') | |
| ->where('user_fund_documents.fund_id', $fundId) | |
| ->where('user_fund_documents.user_id', $user->id); | |
| }) | |
| ->distinct() | |
| ->select('documents.*') | |
| ->addSelect('document_types.order') | |
| ->addSelect('user_fund_documents.downloaded_at') | |
| ->where( | |
| [ | |
| 'documents.model_type' => Fund::class, | |
| 'documents.model_id' => $fundId, | |
| ] | |
| ) | |
| ->whereNotIn('documents.document_type_id', [ | |
| DocumentTypeId::INVESTOR_AGREEMENT, | |
| DocumentTypeId::OTHER, | |
| ]) | |
| ->orderBy('document_types.order') | |
| ->get(); | |
| } | |
| /** | |
| * Retrieve the fund documents with the corresponding documents to sign by the user. | |
| * | |
| * @param User $user | |
| * @param int $fundId | |
| * @param int|null $investmentId | |
| * @return Document[]|Collection | |
| */ | |
| public function getFundDocumentsWithUserSignedDocuments(User $user, int $fundId): Collection | |
| { | |
| // retrieve fund documents | |
| $documents = $this->getFundDocumentsForUser($user, $fundId); | |
| // retrieve investments of the user into fund | |
| $investmentIds = $this->getUserFundInvestments($user, $fundId); | |
| // retrieve documents to sign by user that the user actually signed | |
| $userSignedDocuments = $this->getUserSignedDocumentsForInvestments($user, $fundId, $investmentIds); | |
| $userSignedDocuments->each(function (UserSignedDocument $userSignedDocument) { | |
| $documentType = $userSignedDocument->documentType ?? $userSignedDocument->documentTemplateType?->getDocumentType(); | |
| $userSignedDocument->document_type_id = $documentType?->id; | |
| $userSignedDocument->setRelation('document_type', $documentType); | |
| }); | |
| $documents->each(function (Document $document) use ($userSignedDocuments) { | |
| $documentTypeId = $document->document_type_id; | |
| $documentUserSignedDocuments = $userSignedDocuments->filter(function (UserSignedDocument $userSignedDocument) use ($document, $documentTypeId) { | |
| return $userSignedDocument->document_id === $document->id || $userSignedDocument->document_type_id === $documentTypeId; | |
| }); | |
| $document->setRelation('userSignedDocuments', $documentUserSignedDocuments); | |
| }); | |
| return $documents; | |
| } | |
| /** | |
| * Retrieve the fund documents by type. | |
| * | |
| * @TODO Handle multiple versions of document types. | |
| * | |
| * @param int $fundId | |
| * @param array $documentTypes | |
| * @return Document[]|Collection | |
| */ | |
| public function getFundDocumentsByType(int $fundId, array $documentTypes): Collection | |
| { | |
| return Document::query() | |
| ->select('documents.*') | |
| ->where( | |
| [ | |
| 'model_type' => Fund::class, | |
| 'model_id' => $fundId, | |
| ] | |
| ) | |
| ->join('document_types', 'document_types.id', '=', 'documents.document_type_id') | |
| ->whereIn('document_types.id', $documentTypes) | |
| ->orderBy('document_types.order') | |
| ->get(); | |
| } | |
| /** | |
| * Retrieve the document templates of a fund for a specific investment. | |
| * | |
| * @param int $fundId | |
| * @param Investment $investment | |
| * @param DocumentTemplateTypeEnum[]|array $documentTemplateTypes | |
| * @param array|string $columns | |
| * @return SupportCollection | |
| */ | |
| public function getFundDocumentTemplatesByTypeForInvestment(int $fundId, Investment $investment, array $documentTemplateTypes, array|string $columns = ['*']): SupportCollection | |
| { | |
| /** @var DocumentTemplate[]|Collection $documentTemplates */ | |
| $documentTemplates = collect(); | |
| foreach ($documentTemplateTypes as $documentTemplateType) { | |
| $documentTemplates[$documentTemplateType->value] = $this->getDocumentTemplateService()->getLastDocumentTemplateByType($documentTemplateType, $fundId, $investment->company_type_id, $columns); | |
| } | |
| return $documentTemplates; | |
| } | |
| /** | |
| * Retrieve the number of funds for which the user has pending actions. | |
| * That is, funds for which the user has pending: | |
| * - signing the fund NDA to unlock the fund documentation | |
| * - investment for a fund | |
| * | |
| * @param User $user | |
| * @return int | |
| */ | |
| public function getPendingFundsCount(User $user): int | |
| { | |
| return $this->getPendingFunds($user, 'id')->count(); | |
| } | |
| /** | |
| * Retrieve funds for which the user has pending actions. | |
| * That is, funds for which the user has pending: | |
| * - signing the fund NDA to unlock the fund documentation | |
| * - investment for a fund | |
| * | |
| * @param User $user | |
| * @param array|string $columns | |
| * @return Fund[]|Collection | |
| */ | |
| public function getPendingFunds(User $user, array|string $columns = ['*']): Collection | |
| { | |
| $columns = $this->buildModelColumns($columns); | |
| // fund documents pending to sign | |
| /** @var Fund[]|Collection $funds */ | |
| $funds = $user->userSignedDocuments() | |
| ->inProgress() | |
| ->joinRelationship('fund') | |
| ->select($columns) | |
| ->distinct() | |
| ->get(); | |
| $fundIds = $funds->pluck('id'); | |
| $investmentFunds = $user->investments() | |
| ->pendingUser() | |
| ->joinRelationship('fund') | |
| ->distinct() | |
| ->select($columns) | |
| ->whereNotIn('funds.id', $fundIds) | |
| ->get(); | |
| return $funds->concat($investmentFunds); | |
| } | |
| /** | |
| * Retrieve the user fund record, creating it if necessary. | |
| * | |
| * @param int $userId | |
| * @param int $fundId | |
| * @return UserFund | |
| */ | |
| public function getOrCreateUserFund(int $userId, int $fundId): UserFund | |
| { | |
| try { | |
| /** @var UserFund $userFund */ | |
| $userFund = UserFund::query()->firstOrCreate( | |
| [ | |
| 'user_id' => $userId, | |
| 'fund_id' => $fundId, | |
| ] | |
| ); | |
| return $userFund; | |
| } catch (QueryException $e) { | |
| // if the record was created after checking whether it existed return that records | |
| if (MysqlErrorCodes::DUPLICATE_ENTRY === $e->getCode()) { | |
| return $this->getUserFundById($userId, $fundId); | |
| } | |
| throw $e; | |
| } | |
| } | |
| /** | |
| * Retrieve user fund record. | |
| * | |
| * @param User $user | |
| * @param int $fundId | |
| * @return UserFund|null | |
| */ | |
| public function getUserFund(User $user, int $fundId): ?UserFund | |
| { | |
| return $user->userFund($fundId) | |
| ->first(); | |
| } | |
| /** | |
| * Retrieve user fund record by ID's. | |
| * | |
| * @param int $userId | |
| * @param int $fundId | |
| * @return UserFund|null | |
| */ | |
| public function getUserFundById(int $userId, int $fundId): ?UserFund | |
| { | |
| /** @var UserFund|null $userFund */ | |
| $userFund = UserFund::query()->where([ | |
| 'user_id' => $userId, | |
| 'fund_id' => $fundId, | |
| ]) | |
| ->first(); | |
| return $userFund; | |
| } | |
| /** | |
| * Retrieve the fund documents with the corresponding documents to sign by the user. | |
| * | |
| * @param User $user | |
| * @return Fund[]|Collection | |
| */ | |
| public function getUserFundsWithDocuments(User $user): Collection | |
| { | |
| $funds = $user->funds() | |
| ->orderBy('funds.name') | |
| ->get(['funds.id', 'funds.uuid', 'funds.slug', 'funds.name']); | |
| $funds->each(function (Fund $fund) use ($user) { | |
| $documents = $this->getFundDocumentsWithUserSignedDocuments($user, $fund->id); | |
| $fund->setRelation('documents', $documents); | |
| $userFund = $this->getUserFund($user, $fund->id); | |
| $fund->setRelation('userFund', $userFund); | |
| // retrieve investment contracts for fund | |
| $investmentService = $this->getInvestmentService(); | |
| $documentTemplates = collect(); | |
| $fund->investments = $investmentService->getUserFundInvestments($user, $fund->id)->sortBy('id'); | |
| foreach ($fund->investments as $investment) { | |
| $documentTemplates[] = $investmentService->getInvestmentContractWithUserSignedDocuments($investment); | |
| } | |
| $documentTemplates->sortBy('id'); | |
| $fund->setRelation('documentTemplates', $documentTemplates); | |
| }); | |
| return $funds; | |
| } | |
| /** | |
| * Determine whether the user has investments as a company into fund. | |
| * | |
| * @param User $user | |
| * @param int $fundId | |
| * @return bool | |
| */ | |
| public function hasUserFundInvestmentsAsCompany(User $user, int $fundId): bool | |
| { | |
| $count = $this->getUserFundInvestmentsAsCompanyBuilder($user, $fundId) | |
| ->count(); | |
| return 0 < $count; | |
| } | |
| /** | |
| * Increase the amount committed in a fund. | |
| * | |
| * @param Fund $fund | |
| * @param int $amount | |
| * @return bool | |
| */ | |
| public function increaseAmountCommitted(Fund $fund, int $amount): bool | |
| { | |
| return DB::transaction(function () use ($amount, $fund) { | |
| $fund = Fund::query()->lockForUpdate()->find($fund->id); | |
| if (! $fund->hasRoomForAmount($amount)) { | |
| return false; | |
| } | |
| return $fund->update([ | |
| 'amount_committed' => DB::raw("amount_committed + {$amount}"), | |
| ]); | |
| }); | |
| } | |
| /** | |
| * Register the download of a fund document by a user. | |
| * | |
| * @pre NB: It is assumed that this function is called within a transaction. | |
| * | |
| * @param User $user | |
| * @param Fund $fund | |
| * @param Document $document | |
| * @return void | |
| */ | |
| public function registerDocumentDownload(User $user, Fund $fund, Document $document): void | |
| { | |
| $wheres = [ | |
| 'user_id' => $user->id, | |
| 'fund_id' => $fund->id, | |
| 'document_id' => $document->id, | |
| ]; | |
| $data = [ | |
| 'downloaded_at' => now(), | |
| ]; | |
| $userFundDocument = UserFundDocument::query()->lockForUpdate()->where($wheres)->first(); | |
| if (null === $userFundDocument) { | |
| // try to create it | |
| try { | |
| $userFundDocument = UserFundDocument::create($wheres + $data); | |
| } catch (QueryException $e) { | |
| if (MysqlErrorCodes::DUPLICATE_ENTRY !== $e->getCode()) { | |
| throw $e; | |
| } | |
| // create failed, so now it does exists, so retrieve it, again | |
| $userFundDocument = UserFundDocument::query()->lockForUpdate()->where($wheres)->first(); | |
| } | |
| } | |
| // record already exists | |
| if (null === $userFundDocument->download_at) { | |
| $userFundDocument->update($data); | |
| } | |
| // determine whether all information and contracts have been seen | |
| $this->updateUserFundSeen($user, $fund->id); | |
| } | |
| /** | |
| * Register user having seen the fund contracts. | |
| * | |
| * @param int $userId | |
| * @param int $fundId | |
| * @return void | |
| */ | |
| public function registerFundContractsSeen(int $userId, int $fundId): void | |
| { | |
| $this->updateUserFund($userId, $fundId, 'seen_contracts_at', now()); | |
| } | |
| /** | |
| * Register user having seen the fund information. | |
| * | |
| * @param int $userId | |
| * @param int $fundId | |
| * @return void | |
| */ | |
| public function registerFundInformationSeen(int $userId, int $fundId): void | |
| { | |
| $this->updateUserFund($userId, $fundId, 'seen_information_at', now()); | |
| } | |
| /** | |
| * Register user signing fund NDA in user fund table. | |
| * | |
| * @param int $userId | |
| * @param int $fundId | |
| * @param Carbon $signedAt | |
| * @return void | |
| */ | |
| public function registerNdaSign(int $userId, int $fundId, Carbon $signedAt): void | |
| { | |
| $this->updateUserFund($userId, $fundId, 'nda_signed_at', $signedAt); | |
| } | |
| /** | |
| * Retrieve the investments into fund by user. | |
| * | |
| * @param User $user | |
| * @param int $fundId | |
| * @return EloquentBuilder[]|Collection | |
| */ | |
| protected function getUserFundInvestments(User $user, int $fundId, array|string $columns = ['id']): array|Collection | |
| { | |
| return $this->getUserFundInvestmentsBuilder($user, $fundId) | |
| ->get($columns); | |
| } | |
| /** | |
| * Retrieve documents to sign by user that the user actually signed for specific investments. | |
| * | |
| * @param User $user | |
| * @param int $fundId | |
| * @param Collection|array $investmentIds | |
| * @param DocumentTypeId[]|int[]|array|null $documentTypeIds | |
| * @return Collection | |
| * | |
| * @TODO Move to UserSignedDocumentService | |
| */ | |
| public function getUserSignedDocumentsForInvestments(User $user, int $fundId, Collection|array $investmentIds, array $documentTypeIds = null): Collection | |
| { | |
| $query = UserSignedDocument::query() | |
| ->with('document') | |
| ->with('documentTemplate:id,document_template_type_id') | |
| ->with('documentTemplateType:document_template_types.id,document_template_types.name') | |
| ->where([ | |
| 'user_signed_documents.user_id' => $user->id, | |
| ]) | |
| ->where(function ($query) use ($fundId, $investmentIds) { | |
| $query->orWhere('user_signed_documents.fund_id', $fundId) | |
| ->orWhereIn('user_signed_documents.investment_id', $investmentIds); | |
| }) | |
| ->where(function ($query) { | |
| $query->orWhereNotNull('user_signed_documents.signed_at') | |
| ->orWhereNotNull('user_signed_documents.path_signed_altix'); | |
| }) | |
| ->orderBy('user_signed_documents.id'); | |
| if (null !== $documentTypeIds) { | |
| $documentTemplateTypeEmums = []; | |
| foreach ($documentTypeIds as $documentTypeId) { | |
| $documentTemplateTypeEmums[] = DocumentTemplateTypeEnum::getFromDocumentTypeId($documentTypeId); | |
| } | |
| $query->leftJoinRelationship('documentTemplate.documentTemplateType') | |
| ->leftJoinRelationship('document'); | |
| $query->where(function ($query) use ($documentTypeIds, $documentTemplateTypeEmums) { | |
| $query->orWhereIn('documents.document_type_id', $documentTypeIds) | |
| ->orWhereIn('document_template_types.name', $documentTemplateTypeEmums); | |
| }); | |
| } | |
| /** @var UserSignedDocument[]|Collection $userSignedDocuments */ | |
| $userSignedDocuments = $query->select([ | |
| 'user_signed_documents.id', | |
| 'user_signed_documents.uuid', | |
| 'user_signed_documents.user_id', | |
| 'user_signed_documents.document_id', | |
| 'user_signed_documents.document_template_id', | |
| 'user_signed_documents.fund_id', | |
| 'user_signed_documents.investment_id', | |
| 'user_signed_documents.name', | |
| 'user_signed_documents.provider_name', | |
| 'user_signed_documents.status', | |
| 'user_signed_documents.signed_at', | |
| 'user_signed_documents.signed_altix_at', | |
| 'user_signed_documents.path_signed', | |
| 'user_signed_documents.path_signed_altix', | |
| 'user_signed_documents.created_at', | |
| 'user_signed_documents.updated_at', | |
| ]) | |
| ->get(); | |
| return $userSignedDocuments; | |
| } | |
| /** | |
| * Retrieve the investments as a company into fund by user. | |
| * | |
| * @param User $user | |
| * @param int $fundId | |
| * @return EloquentBuilder[]|Collection | |
| */ | |
| protected function getUserFundInvestmentsAsCompany(User $user, int $fundId, array|string $columns = ['*']): array|Collection | |
| { | |
| return $this->getUserFundInvestmentsBuilder($user, $fundId) | |
| ->where('as_company', 1) | |
| ->get($columns); | |
| } | |
| /** | |
| * Retrieve query builder for investments as a company into fund by user. | |
| * | |
| * @param User $user | |
| * @param int $fundId | |
| * @return EloquentBuilder|Relation | |
| */ | |
| protected function getUserFundInvestmentsAsCompanyBuilder(User $user, int $fundId): EloquentBuilder|Relation | |
| { | |
| return $this->getUserFundInvestmentsBuilder($user, $fundId) | |
| ->where('as_company', 1); | |
| } | |
| /** | |
| * Retrieve query builder for investments into fund by user. | |
| * | |
| * @param User $user | |
| * @param int $fundId | |
| * @return EloquentBuilder|Relation | |
| */ | |
| protected function getUserFundInvestmentsBuilder(User $user, int $fundId): EloquentBuilder|Relation | |
| { | |
| return $user->investments() | |
| ->where([ | |
| 'fund_id' => $fundId, | |
| ]); | |
| } | |
| /** | |
| * Update a single attribute of the user fund record, possibly overwriting an existing value. | |
| * | |
| * @param int $userId | |
| * @param int $fundId | |
| * @param string $attribute | |
| * @param mixed $value | |
| * @return UserFund | |
| */ | |
| protected function updateUserFund(int $userId, int $fundId, string $attribute, mixed $value = null, bool $overwrite = false): UserFund | |
| { | |
| $userFund = $this->getOrCreateUserFund($userId, $fundId); | |
| $where = [ | |
| 'id' => $userFund->id, | |
| ]; | |
| if (! $overwrite) { | |
| if (null !== $userFund->{$attribute}) { | |
| return $userFund; | |
| } | |
| $where[$attribute] = null; | |
| } | |
| UserFund::query() | |
| ->where($where) | |
| ->update([$attribute => $value]); | |
| return $userFund->refresh(); | |
| } | |
| /** | |
| * Determine whether all information and contracts of a fund have been seen by a user. | |
| * | |
| * @param User $user | |
| * @param int $fundId | |
| * @return void | |
| */ | |
| protected function updateUserFundSeen(User $user, int $fundId): void | |
| { | |
| // create or retrieve user fund record | |
| $userFund = $this->getOrCreateUserFund($user->id, $fundId); | |
| // return if already registered everything | |
| if (null !== $userFund->seen_information_at && null !== $userFund->seen_contracts_at) { | |
| return; | |
| } | |
| // lock for update to ensure correctness of the seen dates | |
| $userFund = UserFund::lockForUpdate()->find($userFund->id); | |
| $documents = $this->getFundDocuments($fundId); | |
| $userFundDocuments = $this->getDownloadedDocuments($user, $fundId); | |
| if (! $userFundDocuments->isEmpty()) { | |
| // assume all documents have been seen | |
| $seenContracts = true; | |
| $seenInformation = true; | |
| foreach ($documents as $document) { | |
| // ignore fund NDA | |
| $documentType = $document->documentType; | |
| if (DocumentTypeId::NON_DISCLOSURE_AGREEMENT->value === $documentType->id) { | |
| continue; | |
| } | |
| // if the document has not been downloaded, set corresponding variable to false | |
| $userFundDocument = $userFundDocuments[$document->id] ?? null; | |
| $isDownloaded = null !== $userFundDocument?->downloaded_at; | |
| // if some document has not been seen, update the corresponding variable | |
| if (! $isDownloaded) { | |
| if ($documentType->require_unlock) { | |
| $seenContracts = false; | |
| } else { | |
| $seenInformation = false; | |
| } | |
| // return if neither have been seen | |
| if (! $seenContracts && ! $seenInformation) { | |
| return; | |
| } | |
| } | |
| } | |
| // update seen times, if still unset | |
| if ($seenContracts || $seenInformation) { | |
| $data = []; | |
| $now = now(); | |
| if ($seenInformation && null === $userFund->seen_information_at) { | |
| $data['seen_information_at'] = $now; | |
| } | |
| if ($seenContracts && null === $userFund->seen_contracts_at) { | |
| $data['seen_contracts_at'] = $now; | |
| } | |
| if (empty($data)) { | |
| return; | |
| } | |
| $userFund->update($data); | |
| } | |
| } | |
| } | |
| /** | |
| * Retrieve DocumentService instance. | |
| * | |
| * @return DocumentService | |
| */ | |
| protected function getDocumentService(): DocumentService | |
| { | |
| if (null === $this->documentService) { | |
| $this->documentService = resolve(DocumentService::class); | |
| } | |
| return $this->documentService; | |
| } | |
| /** | |
| * Retrieve DocumentTemplateService instance. | |
| * | |
| * @return DocumentTemplateService | |
| */ | |
| protected function getDocumentTemplateService(): DocumentTemplateService | |
| { | |
| if (null === $this->documentTemplateService) { | |
| $this->documentTemplateService = resolve(DocumentTemplateService::class); | |
| } | |
| return $this->documentTemplateService; | |
| } | |
| /** | |
| * Retrieve InvestmentService instance. | |
| * | |
| * @return InvestmentService | |
| */ | |
| protected function getInvestmentService(): InvestmentService | |
| { | |
| if (null === $this->investmentService) { | |
| $this->investmentService = resolve(InvestmentService::class); | |
| } | |
| return $this->investmentService; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment