a8b54af0b8
Phase 1 of the messaging rework: one conversation/thread model (Inquiry) is the single source of truth, read perspectivally by both sides. - domain/inquiry: add kind (INQUIRY|OFFER), tenantOrgId (demand org), offeredPropertyIds - provider/service: InquiryFilters gains tenantOrgId+kind; new createInquiry (demand→supply) & createOffer (supply→demand) materialize threads; addMessage bumps unread; reply carries senderType (tenant vs supply_user) - hooks: useCreateInquiry, useCreateOffer; reply payload carries sender perspective - demand: InquiryQuickDialog → createInquiry via provider (session-based tenant/org, no hardcode); Anfragen.tsx reads via React Query (tenantOrgId) with Gesendet/Erhalten tabs, replies via provider, shows offered properties for OFFER threads; inquiryStore reduced to dialog-only (removes Zustand server-data island) - supply: OfferChatComposer send now materializes an OFFER conversation into the seeker's inbox (routed via latentNeed.tenantOrgId); Anfragencenter gains a "Gesendet" tab; ActiveInquiriesTab filters by perspective (org + kind) - seed: merge demand inquiries with tenantOrgId=org-mobimo + 2 OFFER seeds; provider seeds from both sets Closes the loop: a sent inquiry reaches the Verwaltung and a sent offer reaches the Nachfrager — both answerable in one thread. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
90 lines
2.5 KiB
TypeScript
90 lines
2.5 KiB
TypeScript
import { MockupInquiryProvider } from '../provider/MockupInquiryProvider'
|
|
import type { InquiryFilters, CreateInquiryInput, CreateOfferInput } from '../provider/IInquiryProvider'
|
|
import type { Inquiry, Attachment, InquiryMessage } from '../domain/inquiry'
|
|
import type { ListResponse, ItemResponse } from './types'
|
|
import { throwServiceError } from './errors'
|
|
|
|
const provider = MockupInquiryProvider
|
|
|
|
export interface InquiryReplyPayload {
|
|
subject?: string
|
|
body: string
|
|
attachments?: Attachment[]
|
|
senderType?: InquiryMessage['senderType'] // default 'supply_user'
|
|
senderName?: string
|
|
}
|
|
|
|
export const inquiryService = {
|
|
async getActiveInquiries(filters?: InquiryFilters): Promise<ListResponse<Inquiry>> {
|
|
try {
|
|
const data = await provider.getAll(filters)
|
|
return { data, meta: { total: data.length, page: 1, pageSize: data.length, hasMore: false } }
|
|
} catch (err) {
|
|
throwServiceError(err)
|
|
}
|
|
},
|
|
|
|
async getInquiryById(id: string): Promise<ItemResponse<Inquiry | null>> {
|
|
try {
|
|
const data = await provider.getById(id)
|
|
return { data }
|
|
} catch (err) {
|
|
throwServiceError(err)
|
|
}
|
|
},
|
|
|
|
async createInquiry(input: CreateInquiryInput): Promise<ItemResponse<Inquiry>> {
|
|
try {
|
|
const data = await provider.createInquiry(input)
|
|
return { data }
|
|
} catch (err) {
|
|
throwServiceError(err)
|
|
}
|
|
},
|
|
|
|
async createOffer(input: CreateOfferInput): Promise<ItemResponse<Inquiry>> {
|
|
try {
|
|
const data = await provider.createOffer(input)
|
|
return { data }
|
|
} catch (err) {
|
|
throwServiceError(err)
|
|
}
|
|
},
|
|
|
|
async sendInquiryReply(
|
|
inquiryId: string,
|
|
payload: InquiryReplyPayload,
|
|
): Promise<ItemResponse<Inquiry>> {
|
|
try {
|
|
const data = await provider.addMessage(inquiryId, {
|
|
senderType: payload.senderType ?? 'supply_user',
|
|
senderName: payload.senderName ?? 'Wincasa AG',
|
|
subject: payload.subject,
|
|
body: payload.body,
|
|
attachments: payload.attachments ?? [],
|
|
})
|
|
return { data }
|
|
} catch (err) {
|
|
throwServiceError(err)
|
|
}
|
|
},
|
|
|
|
async markThreadAsRead(id: string): Promise<ItemResponse<Inquiry>> {
|
|
try {
|
|
const data = await provider.markThreadAsRead(id)
|
|
return { data }
|
|
} catch (err) {
|
|
throwServiceError(err)
|
|
}
|
|
},
|
|
|
|
async getUnreadCount(filters?: InquiryFilters): Promise<ItemResponse<number>> {
|
|
try {
|
|
const data = await provider.getUnreadCount(filters)
|
|
return { data }
|
|
} catch (err) {
|
|
throwServiceError(err)
|
|
}
|
|
},
|
|
}
|