Files
hub/app/Services/ContactService.php
Jon Leopard 7e5ea2bc10 checkpoint: marketplace and operational features complete, ready for Week 4 data migration
Summary of completed work:
- Complete buyer portal (browse, cart, checkout, orders, invoices)
- Complete seller portal (orders, manifests, fleet, picking)
- Business onboarding wizards (buyer 4-step, seller 5-step)
- Email verification and registration flows
- Notification system for buyers and sellers
- Payment term surcharges and pickup/delivery workflows
- Filament admin resources (Business, Brand, Product, Order, Invoice, User)
- 51 migrations executed successfully

Renamed Company -> Business throughout codebase for consistency.
Unified authentication flows with password reset.
Added audit trail and telescope for debugging.

Next: Week 4 Data Migration (Days 22-28)
- Product migration (883 products from cannabrands_crm)
- Company migration (81 buyer companies)
- User migration (preserve password hashes)
- Order history migration
2025-10-15 11:17:15 -07:00

223 lines
7.0 KiB
PHP

<?php
namespace App\Services;
use App\Models\Contact;
use App\Models\Company;
use App\Models\User;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\DB;
class ContactService
{
/**
* Get all contacts for a company
*/
public function getCompanyContacts(Company $company): Collection
{
return $company->contacts()
->with('user.roles')
->orderBy('is_primary', 'desc')
->orderBy('last_name')
->get();
}
/**
* Create a new contact
*/
public function createContact(Company $company, array $data): Contact
{
return DB::transaction(function () use ($company, $data) {
$contact = $company->contacts()->create([
'first_name' => $data['first_name'],
'last_name' => $data['last_name'],
'email' => $data['email'] ?? null,
'phone' => $data['phone'] ?? null,
'mobile' => $data['mobile'] ?? null,
'position' => $data['position'] ?? null,
'department' => $data['department'] ?? null,
'contact_type' => $data['contact_type'] ?? null,
'is_primary' => $data['is_primary'] ?? false,
'is_active' => true,
'can_approve_orders' => $data['can_approve_orders'] ?? false,
'can_place_orders' => $data['can_place_orders'] ?? false,
'can_receive_invoices' => $data['can_receive_invoices'] ?? false,
'receive_notifications' => $data['receive_notifications'] ?? true,
'preferred_contact_method' => $data['preferred_contact_method'] ?? 'email',
'notes' => $data['notes'] ?? null,
]);
// If marked as primary, ensure no other contacts are primary
if ($contact->is_primary) {
$this->ensureSinglePrimary($company, $contact);
}
return $contact;
});
}
/**
* Update an existing contact
*/
public function updateContact(Contact $contact, array $data): Contact
{
return DB::transaction(function () use ($contact, $data) {
$contact->update([
'first_name' => $data['first_name'] ?? $contact->first_name,
'last_name' => $data['last_name'] ?? $contact->last_name,
'email' => $data['email'] ?? $contact->email,
'phone' => $data['phone'] ?? $contact->phone,
'mobile' => $data['mobile'] ?? $contact->mobile,
'position' => $data['position'] ?? $contact->position,
'department' => $data['department'] ?? $contact->department,
'contact_type' => $data['contact_type'] ?? $contact->contact_type,
'is_primary' => $data['is_primary'] ?? $contact->is_primary,
'can_approve_orders' => $data['can_approve_orders'] ?? $contact->can_approve_orders,
'can_place_orders' => $data['can_place_orders'] ?? $contact->can_place_orders,
'can_receive_invoices' => $data['can_receive_invoices'] ?? $contact->can_receive_invoices,
'receive_notifications' => $data['receive_notifications'] ?? $contact->receive_notifications,
'preferred_contact_method' => $data['preferred_contact_method'] ?? $contact->preferred_contact_method,
'notes' => $data['notes'] ?? $contact->notes,
]);
// If marked as primary, ensure no other contacts are primary
if ($contact->is_primary) {
$this->ensureSinglePrimary($contact->company, $contact);
}
return $contact->fresh();
});
}
/**
* Delete a contact (soft delete)
*/
public function deleteContact(Contact $contact): bool
{
// Prevent deleting if contact has a linked user
if ($contact->user_id) {
throw new \Exception('Cannot delete contact with linked user account. Unlink the user first.');
}
return $contact->delete();
}
/**
* Archive a contact
*/
public function archiveContact(Contact $contact, string $reason = null, User $archivedBy = null): void
{
$contact->archive($reason, $archivedBy);
}
/**
* Restore an archived contact
*/
public function restoreContact(Contact $contact, User $restoredBy = null): void
{
$contact->restore($restoredBy);
}
/**
* Set a contact as primary
*/
public function setPrimary(Contact $contact): void
{
$contact->makePrimary();
}
/**
* Ensure only one primary contact per company
*/
private function ensureSinglePrimary(Company $company, Contact $primaryContact): void
{
$company->contacts()
->where('id', '!=', $primaryContact->id)
->where('is_primary', true)
->update(['is_primary' => false]);
}
/**
* Link a contact to a user account
*/
public function linkToUser(Contact $contact, User $user): void
{
if ($contact->user_id) {
throw new \Exception('Contact already linked to a user');
}
$contact->update(['user_id' => $user->id]);
}
/**
* Unlink a contact from a user account
*/
public function unlinkFromUser(Contact $contact): void
{
$contact->update(['user_id' => null]);
}
/**
* Search contacts by name or email
*/
public function searchContacts(Company $company, string $query): Collection
{
return $company->contacts()
->where(function ($q) use ($query) {
$q->where('first_name', 'like', "%{$query}%")
->orWhere('last_name', 'like', "%{$query}%")
->orWhere('email', 'like', "%{$query}%")
->orWhere('phone', 'like', "%{$query}%");
})
->with('user.roles')
->get();
}
/**
* Get contacts by type
*/
public function getContactsByType(Company $company, string $type): Collection
{
return $company->contacts()
->byType($type)
->with('user.roles')
->get();
}
/**
* Get contacts that can approve orders
*/
public function getApprovers(Company $company): Collection
{
return $company->contacts()
->canApproveOrders()
->active()
->get();
}
/**
* Get contacts that can place orders
*/
public function getBuyers(Company $company): Collection
{
return $company->contacts()
->canPlaceOrders()
->active()
->get();
}
/**
* Check if email already exists for this company
*/
public function emailExists(Company $company, string $email, ?int $excludeContactId = null): bool
{
$query = $company->contacts()->where('email', $email);
if ($excludeContactId) {
$query->where('id', '!=', $excludeContactId);
}
return $query->exists();
}
}