import { demoVisits, type DemoVisit } from '@/lib/mock-data'; import type { TravelPlan } from '@/lib/travel-planner'; export const OPS_STORAGE_KEY = 'ajet360.ops.v1'; export const OPS_STORAGE_EVENT = 'ajet360:ops-storage'; export type VisitTarget = { owner: string; period: string; target: number; setBy: 'self' | 'manager'; setAt: string; }; export type ContractType = 'Komisyon' | 'Garanti' | 'Özel Anlaşma'; export type ContractStatus = | 'Taslak' | 'İç İmzada' | 'Acente İmzasında' | 'Yürürlükte' | 'Süresi Doldu' | 'Arşiv'; export type SignerRole = 'Satış Müdürü' | 'Genel Müdür' | 'Muhasebe' | 'Acente'; export type InternalSignerRole = Exclude; export type ContractSignature = { role: SignerRole; signerName: string; signedAt: string; ip?: string; }; export type ContractEventAction = | 'created' | 'sent_internal' | 'signed_internal' | 'sent_external' | 'signed_external' | 'activated' | 'archived'; export type ContractEvent = { at: string; actor: string; action: ContractEventAction; note?: string; }; export type Contract = { id: string; agencyCode: string; agencyName: string; type: ContractType; startDate: string; endDate: string; commissionPct: number; status: ContractStatus; internalOrder: InternalSignerRole[]; signatures: ContractSignature[]; events: ContractEvent[]; createdAt: string; createdBy: string; }; export const INTERNAL_SIGNER_ORDER: InternalSignerRole[] = ['Satış Müdürü', 'Genel Müdür', 'Muhasebe']; export const INTERNAL_SIGNER_NAMES: Record = { 'Satış Müdürü': 'Mehmet Yılmaz', 'Genel Müdür': 'Ayşe Kaya', 'Muhasebe': 'Burak Demir', }; export type OpsStorageState = { visits: DemoVisit[]; targets?: VisitTarget[]; lastTravelPlan?: TravelPlan; contracts?: Contract[]; }; const EMPTY_STATE: OpsStorageState = { visits: [], targets: [], contracts: [], }; function canUseStorage() { return typeof window !== 'undefined' && Boolean(window.localStorage); } function readState(): OpsStorageState { if (!canUseStorage()) { return EMPTY_STATE; } const raw = window.localStorage.getItem(OPS_STORAGE_KEY); if (!raw) { return EMPTY_STATE; } try { const parsed = JSON.parse(raw) as Partial; return { ...parsed, visits: Array.isArray(parsed.visits) ? parsed.visits : [], targets: Array.isArray(parsed.targets) ? parsed.targets : [], contracts: Array.isArray(parsed.contracts) ? parsed.contracts : [], }; } catch { return EMPTY_STATE; } } function writeState(state: OpsStorageState) { if (!canUseStorage()) { return; } window.localStorage.setItem(OPS_STORAGE_KEY, JSON.stringify(state)); window.dispatchEvent(new Event(OPS_STORAGE_EVENT)); } function getNextTripBaseDate() { const date = new Date(); date.setDate(date.getDate() + 1); date.setHours(9, 30, 0, 0); return date; } function getTravelPlanBaseDate(plan: TravelPlan) { if (!plan.input.startDate) { return getNextTripBaseDate(); } const date = new Date(`${plan.input.startDate}T09:30:00`); return Number.isNaN(date.getTime()) ? getNextTripBaseDate() : date; } function planTimestamp(plan: TravelPlan) { return plan.generatedAt.replace(/\D/g, '').slice(0, 14); } export function getStoredVisits() { return readState().visits; } export function getAllVisits() { return [...demoVisits, ...getStoredVisits()].sort( (a, b) => new Date(a.date).getTime() - new Date(b.date).getTime(), ); } export function saveTravelPlanVisits(plan: TravelPlan) { const state = readState(); const baseDate = getTravelPlanBaseDate(plan); const timestamp = planTimestamp(plan); const participants = plan.participants.length > 0 ? plan.participants : plan.input.participants; const owner = participants[0] ?? `${plan.input.city} Saha Ekibi`; const participantsNote = participants.length > 0 ? ` Katilimcilar: ${participants.join(', ')}.` : ''; const createdVisits: DemoVisit[] = plan.stops.map((stop) => { const date = new Date(baseDate); const dayOffset = stop.day - 1; const hourOffset = (stop.order - 1) * 2; date.setDate(baseDate.getDate() + dayOffset); date.setHours(9 + hourOffset, 30, 0, 0); return { id: `travel-${timestamp}-${stop.day}-${stop.order}-${stop.agencyCode}`, agencyCode: stop.agencyCode, agencyName: stop.agencyName, city: plan.input.city, owner, date: date.toISOString(), type: 'Yuz yuze', status: 'Planli', note: `Seyahat plani: Gun ${stop.day}, sira ${stop.order}. ${stop.reason}.${participantsNote}`, }; }); const createdIds = new Set(createdVisits.map((visit) => visit.id)); const visits = [...state.visits.filter((visit) => !createdIds.has(visit.id)), ...createdVisits]; writeState({ ...state, visits, lastTravelPlan: plan, }); return createdVisits; } export function addVisit(visit: Omit): DemoVisit { const id = `local-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`; const created: DemoVisit = { ...visit, id }; const state = readState(); writeState({ ...state, visits: [...state.visits, created] }); return created; } export function updateVisit(id: string, patch: Partial): void { const state = readState(); const idx = state.visits.findIndex((v) => v.id === id); if (idx < 0) { const fromDemo = demoVisits.find((v) => v.id === id); if (!fromDemo) return; writeState({ ...state, visits: [...state.visits, { ...fromDemo, ...patch, id }] }); return; } const current = state.visits[idx]!; const next = [...state.visits]; next[idx] = { ...current, ...patch }; writeState({ ...state, visits: next }); } export function getVisitById(id: string): DemoVisit | undefined { const stored = readState().visits.find((v) => v.id === id); if (stored) return stored; return demoVisits.find((v) => v.id === id); } export function listVisitTargets(): VisitTarget[] { return readState().targets ?? []; } export function getVisitTarget(owner: string, period: string): VisitTarget | undefined { return listVisitTargets().find((t) => t.owner === owner && t.period === period); } export function setVisitTarget(input: Omit): VisitTarget { const state = readState(); const targets = state.targets ?? []; const next: VisitTarget = { ...input, setAt: new Date().toISOString() }; const idx = targets.findIndex((t) => t.owner === input.owner && t.period === input.period); const merged = idx >= 0 ? targets.map((t, i) => (i === idx ? next : t)) : [...targets, next]; writeState({ ...state, targets: merged }); return next; } export function periodKey(date: Date = new Date()): string { return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`; } export function buildVisitSummaryMailto(visit: DemoVisit): string { const recipient = `${visit.agencyCode.toLowerCase()}@acente.ajet.com.tr`; const subject = `AJet · Toplantı Özeti — ${visit.agencyName}`; const date = new Date(visit.date).toLocaleString('tr-TR', { dateStyle: 'long', timeStyle: 'short', }); const sentimentLine = visit.sentiment ? `\nGörüşme tonu: ${visit.sentiment === 'POZITIF' ? 'Pozitif' : visit.sentiment === 'NEGATIF' ? 'Negatif' : 'Nötr'}\n` : ''; const actions = (visit.nextActions ?? []).filter(Boolean); const actionsBlock = actions.length ? `\nSonraki Adımlar:\n${actions.map((a) => `• ${a}`).join('\n')}\n` : ''; const body = [ `Sayın ${visit.agencyName} ekibi,`, '', `${date} tarihinde gerçekleştirdiğimiz görüşmenin özetini bilgilerinize sunuyoruz.${sentimentLine}`, visit.postNote || visit.note || 'Görüşme özeti hazırlanıyor.', actionsBlock, 'Her türlü soru ve geri dönüşünüz için ulaşabilirsiniz.', '', 'Saygılarımızla,', 'AJet Acente İlişkileri', ].join('\n'); return `mailto:${recipient}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`; } const DEMO_CONTRACTS: Contract[] = [ { id: 'demo-contract-001', agencyCode: 'AJ-IST-001', agencyName: 'Istanbul Jet Travel', type: 'Komisyon', startDate: '2026-01-01T00:00:00.000Z', endDate: '2026-12-31T00:00:00.000Z', commissionPct: 14, status: 'Acente İmzasında', internalOrder: ['Satış Müdürü', 'Genel Müdür', 'Muhasebe'], signatures: [ { role: 'Satış Müdürü', signerName: 'Mehmet Yılmaz', signedAt: '2026-01-02T09:15:00.000Z', ip: 'demo-127.0.0.1' }, { role: 'Genel Müdür', signerName: 'Ayşe Kaya', signedAt: '2026-01-02T11:30:00.000Z', ip: 'demo-127.0.0.1' }, { role: 'Muhasebe', signerName: 'Burak Demir', signedAt: '2026-01-03T08:45:00.000Z', ip: 'demo-127.0.0.1' }, ], events: [ { at: '2026-01-02T09:00:00.000Z', actor: 'Sistem Kullanıcısı', action: 'created', note: 'Sözleşme oluşturuldu (Komisyon, %14 komisyon)' }, { at: '2026-01-02T09:15:00.000Z', actor: 'Mehmet Yılmaz', action: 'signed_internal', note: 'Satış Müdürü (Mehmet Yılmaz) e-imza ile imzaladı' }, { at: '2026-01-02T11:30:00.000Z', actor: 'Ayşe Kaya', action: 'signed_internal', note: 'Genel Müdür (Ayşe Kaya) e-imza ile imzaladı' }, { at: '2026-01-03T08:45:00.000Z', actor: 'Burak Demir', action: 'signed_internal', note: 'Muhasebe (Burak Demir) e-imza ile imzaladı' }, { at: '2026-01-03T09:00:00.000Z', actor: 'Sistem Kullanıcısı', action: 'sent_external', note: 'Acenteye e-imza linki gönderildi (Istanbul Jet Travel)' }, ], createdAt: '2026-01-02T09:00:00.000Z', createdBy: 'Sistem Kullanıcısı', }, { id: 'demo-contract-002', agencyCode: 'AJ-ANK-001', agencyName: 'Ankara Tur Merkezi', type: 'Garanti', startDate: '2025-07-01T00:00:00.000Z', endDate: '2026-06-30T00:00:00.000Z', commissionPct: 10, status: 'Yürürlükte', internalOrder: ['Satış Müdürü', 'Genel Müdür', 'Muhasebe'], signatures: [ { role: 'Satış Müdürü', signerName: 'Mehmet Yılmaz', signedAt: '2025-06-28T10:00:00.000Z', ip: 'demo-127.0.0.1' }, { role: 'Genel Müdür', signerName: 'Ayşe Kaya', signedAt: '2025-06-28T14:00:00.000Z', ip: 'demo-127.0.0.1' }, { role: 'Muhasebe', signerName: 'Burak Demir', signedAt: '2025-06-29T09:30:00.000Z', ip: 'demo-127.0.0.1' }, { role: 'Acente', signerName: 'Ankara Tur Merkezi Yetkilisi', signedAt: '2025-07-01T08:00:00.000Z', ip: 'demo-127.0.0.1' }, ], events: [ { at: '2025-06-27T09:00:00.000Z', actor: 'Sistem Kullanıcısı', action: 'created', note: 'Sözleşme oluşturuldu (Garanti, %10 komisyon)' }, { at: '2025-06-28T10:00:00.000Z', actor: 'Mehmet Yılmaz', action: 'signed_internal', note: 'Satış Müdürü (Mehmet Yılmaz) e-imza ile imzaladı' }, { at: '2025-06-28T14:00:00.000Z', actor: 'Ayşe Kaya', action: 'signed_internal', note: 'Genel Müdür (Ayşe Kaya) e-imza ile imzaladı' }, { at: '2025-06-29T09:30:00.000Z', actor: 'Burak Demir', action: 'signed_internal', note: 'Muhasebe (Burak Demir) e-imza ile imzaladı' }, { at: '2025-06-30T10:00:00.000Z', actor: 'Sistem Kullanıcısı', action: 'sent_external', note: 'Acenteye e-imza linki gönderildi (Ankara Tur Merkezi)' }, { at: '2025-07-01T08:00:00.000Z', actor: 'Ankara Tur Merkezi Yetkilisi', action: 'signed_external', note: 'Ankara Tur Merkezi (Ankara Tur Merkezi Yetkilisi) e-imza ile imzaladı' }, { at: '2025-07-01T08:01:00.000Z', actor: 'Sistem', action: 'activated', note: 'Sözleşme yürürlüğe girdi' }, ], createdAt: '2025-06-27T09:00:00.000Z', createdBy: 'Sistem Kullanıcısı', }, ]; export function listContracts(): Contract[] { return readState().contracts ?? []; } export function getAllContracts(): Contract[] { const stored = listContracts(); const storedIds = new Set(stored.map((c) => c.id)); return [...DEMO_CONTRACTS.filter((c) => !storedIds.has(c.id)), ...stored].sort( (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), ); } export function getContractById(id: string): Contract | undefined { const stored = listContracts().find((c) => c.id === id); if (stored) return stored; return DEMO_CONTRACTS.find((c) => c.id === id); } export type CreateContractInput = { agencyCode: string; agencyName: string; type: ContractType; startDate: string; endDate: string; commissionPct: number; createdBy?: string; }; export function createContract(input: CreateContractInput): Contract { const state = readState(); const contracts = state.contracts ?? []; const now = new Date().toISOString(); const ts = now.replace(/\D/g, '').slice(0, 14); const createdBy = input.createdBy ?? 'Sistem Kullanıcısı'; const contract: Contract = { id: `contract-${ts}-${input.agencyCode}`, agencyCode: input.agencyCode, agencyName: input.agencyName, type: input.type, startDate: input.startDate, endDate: input.endDate, commissionPct: input.commissionPct, status: 'Taslak', internalOrder: [...INTERNAL_SIGNER_ORDER], signatures: [], events: [ { at: now, actor: createdBy, action: 'created', note: `Sözleşme oluşturuldu (${input.type}, ${input.commissionPct}% komisyon)`, }, ], createdAt: now, createdBy, }; writeState({ ...state, contracts: [contract, ...contracts] }); return contract; } function nextStatusAfterSign(contract: Contract): ContractStatus { const internalCount = contract.signatures.filter((s) => s.role !== 'Acente').length; const externalSigned = contract.signatures.some((s) => s.role === 'Acente'); if (externalSigned) return 'Yürürlükte'; if (internalCount >= contract.internalOrder.length) return 'Acente İmzasında'; if (internalCount > 0) return 'İç İmzada'; return 'Taslak'; } export function getNextSigner(contract: Contract): SignerRole | null { const signedRoles = new Set(contract.signatures.map((s) => s.role)); for (const role of contract.internalOrder) { if (!signedRoles.has(role)) return role; } if (!signedRoles.has('Acente')) return 'Acente'; return null; } export function signContract( id: string, role: SignerRole, signerName: string, ): Contract | undefined { const state = readState(); const contracts = state.contracts ?? []; const idx = contracts.findIndex((c) => c.id === id); if (idx < 0) return undefined; const contract = contracts[idx]!; if (contract.signatures.some((s) => s.role === role)) return contract; const expected = getNextSigner(contract); if (expected !== role) return contract; const now = new Date().toISOString(); const signature: ContractSignature = { role, signerName, signedAt: now, ip: 'demo-127.0.0.1', }; const events: ContractEvent[] = [ ...contract.events, { at: now, actor: signerName, action: role === 'Acente' ? 'signed_external' : 'signed_internal', note: role === 'Acente' ? `${contract.agencyName} (${signerName}) e-imza ile imzaladı` : `${role} (${signerName}) e-imza ile imzaladı`, }, ]; const next: Contract = { ...contract, signatures: [...contract.signatures, signature], events, }; next.status = nextStatusAfterSign(next); if (next.status === 'Yürürlükte' && !next.events.some((e) => e.action === 'activated')) { next.events = [ ...next.events, { at: now, actor: 'Sistem', action: 'activated', note: 'Sözleşme yürürlüğe girdi', }, ]; } const arr = [...contracts]; arr[idx] = next; writeState({ ...state, contracts: arr }); return next; } export function markContractSentExternal(id: string, actor: string): Contract | undefined { const state = readState(); const contracts = state.contracts ?? []; const idx = contracts.findIndex((c) => c.id === id); if (idx < 0) return undefined; const contract = contracts[idx]!; const now = new Date().toISOString(); const next: Contract = { ...contract, events: [ ...contract.events, { at: now, actor, action: 'sent_external', note: `Acenteye e-imza linki gönderildi (${contract.agencyName})`, }, ], }; const arr = [...contracts]; arr[idx] = next; writeState({ ...state, contracts: arr }); return next; } export function archiveContract(id: string, actor: string): Contract | undefined { const state = readState(); const contracts = state.contracts ?? []; const idx = contracts.findIndex((c) => c.id === id); if (idx < 0) return undefined; const contract = contracts[idx]!; const now = new Date().toISOString(); const next: Contract = { ...contract, status: 'Arşiv', events: [ ...contract.events, { at: now, actor, action: 'archived', note: 'Sözleşme arşive alındı' }, ], }; const arr = [...contracts]; arr[idx] = next; writeState({ ...state, contracts: arr }); return next; } export function buildContractMailto(contract: Contract): string { const recipient = `${contract.agencyCode.toLowerCase()}@acente.ajet.com.tr`; const subject = `AJet · Sözleşme E-İmza Talebi — ${contract.agencyName}`; const startTr = new Date(contract.startDate).toLocaleDateString('tr-TR'); const endTr = new Date(contract.endDate).toLocaleDateString('tr-TR'); const signLink = `https://demo.ajet360.local/sozlesmeler/${contract.id}?as=acente`; const body = [ `Sayın ${contract.agencyName} ekibi,`, '', 'AJet ile aranızda imzalanmak üzere hazırlanan sözleşme, tarafımızdaki tüm yetkililer tarafından e-imza ile imzalanmıştır. Sürecin tamamlanması için sizin de e-imza onayınızı bekliyoruz.', '', `Sözleşme tipi: ${contract.type}`, `Geçerlilik: ${startTr} → ${endTr}`, `Komisyon oranı: %${contract.commissionPct}`, '', 'Aşağıdaki bağlantı üzerinden tek tıkla e-imza atabilirsiniz:', signLink, '', 'Saygılarımızla,', 'AJet Acente İlişkileri', ].join('\n'); return `mailto:${recipient}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`; } export function resetOpsStorage() { if (!canUseStorage()) { return; } window.localStorage.removeItem(OPS_STORAGE_KEY); window.dispatchEvent(new Event(OPS_STORAGE_EVENT)); }