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
223 lines
7.0 KiB
PHP
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();
|
|
}
|
|
}
|