incentive; if (! $incentive) { return 0; } $added = 0; $candidates = User::query() ->where('m_sponsor', $participant->user_id) ->whereBetween('created_at', [ $incentive->qualification_start, $incentive->qualification_end->copy()->endOfDay(), ]) ->whereHas('shopping_orders', function ($q) { $q->wherePaidRegistrationIncludesStarterKit(); }) ->get(); foreach ($candidates as $partner) { if (IncentiveNewPartner::query() ->where('participant_id', $participant->id) ->where('user_id', $partner->id) ->exists()) { continue; } $order = ShoppingOrder::query() ->where('auth_user_id', $partner->id) ->wherePaidRegistrationIncludesStarterKit() ->orderBy('id') ->first(); if ($order) { IncentiveTracker::trackNewPartner($order); } if (! IncentiveNewPartner::query() ->where('participant_id', $participant->id) ->where('user_id', $partner->id) ->exists()) { $newPartner = IncentiveNewPartner::create([ 'participant_id' => $participant->id, 'user_id' => $partner->id, 'registered_at' => $partner->created_at, ]); IncentivePointsLog::create([ 'participant_id' => $participant->id, 'type' => 'partner', 'source_type' => User::class, 'source_id' => $partner->id, 'source_label' => $partner->getFullName() ?: $partner->email ?: ('User #'.$partner->id), 'month' => $partner->created_at->month, 'year' => $partner->created_at->year, 'points_onetime' => $incentive->points_partner_onetime, 'points_accumulated' => 0, 'incentive_new_partner_id' => $newPartner->id, ]); } if (IncentiveNewPartner::query() ->where('participant_id', $participant->id) ->where('user_id', $partner->id) ->exists()) { $added++; } } return $added; } /** * Fehlende Abo-Tracking-Zeilen anlegen (Kundenabo ot / Eigenabo me, wie IncentiveTracker). * Zuerst trackAboActivated ueber Erstbestellung; ohne UserAboOrder manuell wie Neuaufbau B. * * @return int Anzahl nachgezogener Abo-Trackings fuer diesen Teilnehmer */ public function syncMissingTrackingAbos(IncentiveParticipant $participant): int { $incentive = $participant->incentive; if (! $incentive) { return 0; } $added = 0; $qualEnd = $incentive->qualification_end->copy()->endOfDay(); $candidatesOt = UserAbo::query() ->where('is_for', 'ot') ->where('status', 2) ->where('member_id', $participant->user_id) ->whereBetween('created_at', [ $incentive->qualification_start, $qualEnd, ]) ->get(); $candidatesMe = UserAbo::query() ->where('is_for', 'me') ->where('status', 2) ->where('user_id', $participant->user_id) ->where(function ($q) use ($incentive, $qualEnd) { $q->whereBetween('created_at', [ $incentive->qualification_start, $qualEnd, ])->orWhere('created_at', '<', $incentive->qualification_start); }) ->get(); foreach ($candidatesOt->concat($candidatesMe) as $userAbo) { if (IncentiveNewAbo::query() ->where('participant_id', $participant->id) ->where('user_abo_id', $userAbo->id) ->exists()) { continue; } $order = UserAboOrder::query() ->where('user_abo_id', $userAbo->id) ->orderBy('id') ->with('shopping_order') ->first(); $shoppingOrder = $order?->shopping_order; if ($shoppingOrder) { IncentiveTracker::trackAboActivated($shoppingOrder); } if (! IncentiveNewAbo::query() ->where('participant_id', $participant->id) ->where('user_abo_id', $userAbo->id) ->exists()) { $qualStart = $incentive->qualification_start->copy()->startOfDay(); $activatedAt = $userAbo->created_at; $logMonth = (int) $userAbo->created_at->month; $logYear = (int) $userAbo->created_at->year; if ($userAbo->is_for === 'me' && $userAbo->created_at->lt($qualStart)) { $activatedAt = $qualStart->copy(); $logMonth = (int) $qualStart->month; $logYear = (int) $qualStart->year; } $newAbo = IncentiveNewAbo::create([ 'participant_id' => $participant->id, 'user_abo_id' => $userAbo->id, 'activated_at' => $activatedAt, ]); IncentivePointsLog::create([ 'participant_id' => $participant->id, 'type' => 'abo', 'source_type' => UserAbo::class, 'source_id' => $userAbo->id, 'source_label' => $userAbo->email ?: ('Abo #'.$userAbo->id), 'month' => $logMonth, 'year' => $logYear, 'points_onetime' => $incentive->points_abo_onetime, 'points_accumulated' => 0, 'incentive_new_abo_id' => $newAbo->id, ]); } if (IncentiveNewAbo::query() ->where('participant_id', $participant->id) ->where('user_abo_id', $userAbo->id) ->exists()) { $added++; } } return $added; } /** * Setzt fehlende incentive_new_partner_id / incentive_new_abo_id an bestehenden Log-Zeilen. * * @return array{partner_fk: int, abo_fk: int, onetime_partner_fk: int, onetime_abo_fk: int} */ public function repairForeignKeys(IncentiveParticipant $participant): array { $stats = [ 'partner_fk' => 0, 'abo_fk' => 0, 'onetime_partner_fk' => 0, 'onetime_abo_fk' => 0, ]; $newPartnerByUserId = $participant->newPartners()->get()->keyBy('user_id'); $newAboByUserAboId = $participant->newAbos()->get()->keyBy('user_abo_id'); foreach ($participant->pointsLog()->where('type', 'partner')->whereNull('incentive_new_partner_id')->cursor() as $log) { if ($log->source_type === User::class && $log->source_id) { $np = $newPartnerByUserId->get($log->source_id); if ($np) { $log->update(['incentive_new_partner_id' => $np->id]); $stats['onetime_partner_fk']++; } continue; } if ($log->user_sales_volume_id) { $sv = UserSalesVolume::find($log->user_sales_volume_id); if ($sv && $sv->user_id) { $np = $newPartnerByUserId->get($sv->user_id); if ($np) { $log->update(['incentive_new_partner_id' => $np->id]); $stats['partner_fk']++; } } } } $orderIdToUserAboId = []; foreach ($participant->pointsLog()->where('type', 'abo')->whereNull('incentive_new_abo_id')->cursor() as $log) { if ($log->source_type === UserAbo::class && $log->source_id) { $na = $newAboByUserAboId->get($log->source_id); if ($na) { $log->update(['incentive_new_abo_id' => $na->id]); $stats['onetime_abo_fk']++; } continue; } if ($log->user_sales_volume_id) { $sv = UserSalesVolume::find($log->user_sales_volume_id); if ($sv && $sv->shopping_order_id) { if (! isset($orderIdToUserAboId[$sv->shopping_order_id])) { $orderIdToUserAboId[$sv->shopping_order_id] = UserAboOrder::where('shopping_order_id', $sv->shopping_order_id)->value('user_abo_id'); } $userAboId = $orderIdToUserAboId[$sv->shopping_order_id] ?? null; if ($userAboId) { $na = $newAboByUserAboId->get($userAboId); if ($na) { $log->update(['incentive_new_abo_id' => $na->id]); $stats['abo_fk']++; } } } } } return $stats; } /** * Ruft IncentiveTracker::trackSalesVolume fuer Kandidaten-USVs auf, bei denen noch kein Log-Eintrag fuer diesen Teilnehmer existiert. * * @return int Anzahl neu angelegter Log-Zeilen (geschaetzt ueber Vorher/Nachher pro Teilnehmer) */ public function syncMissingSalesVolumeLogs(IncentiveParticipant $participant): int { $incentive = $participant->incentive; if (! $incentive) { return 0; } $synced = 0; $months = $incentive->getCalculationMonths(); $newPartnerUserIds = $participant->newPartners()->pluck('user_id')->filter()->values(); $trackedUserAboIds = $participant->newAbos()->pluck('user_abo_id')->filter()->values(); $orderIdsForAbos = $trackedUserAboIds->isNotEmpty() ? UserAboOrder::query()->whereIn('user_abo_id', $trackedUserAboIds)->pluck('shopping_order_id')->unique()->values() : collect(); foreach ($months as $period) { if ($newPartnerUserIds->isNotEmpty()) { $svs = UserSalesVolume::query() ->whereIn('user_id', $newPartnerUserIds) ->where('month', $period['month']) ->where('year', $period['year']) ->where('status', '!=', 6) ->get(); foreach ($svs as $sv) { if ((int) abs($sv->points ?? 0) <= 0) { continue; } if ($this->participantHasSalesVolumeLog($participant, $sv->id)) { continue; } IncentiveTracker::trackSalesVolume($sv); if ($this->participantHasSalesVolumeLog($participant, $sv->id)) { $synced++; } } } if ($orderIdsForAbos->isNotEmpty()) { $svs = UserSalesVolume::query() ->whereIn('shopping_order_id', $orderIdsForAbos) ->where('month', $period['month']) ->where('year', $period['year']) ->where('status', '!=', 6) ->get(); foreach ($svs as $sv) { if ((int) abs($sv->points ?? 0) <= 0) { continue; } if ($this->participantHasSalesVolumeLog($participant, $sv->id)) { continue; } IncentiveTracker::trackSalesVolume($sv); if ($this->participantHasSalesVolumeLog($participant, $sv->id)) { $synced++; } } } } return $synced; } private function participantHasSalesVolumeLog(IncentiveParticipant $participant, int $userSalesVolumeId): bool { return IncentivePointsLog::query() ->where('participant_id', $participant->id) ->where('user_sales_volume_id', $userSalesVolumeId) ->where('is_storno', false) ->exists(); } }