All files / services inquiry.service.ts

96.15% Statements 25/26
90.9% Branches 20/22
100% Functions 3/3
96.15% Lines 25/26

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97                                                            16x     12x     12x 7x 1x       6x 2x 2x 1x       5x 5x     5x                               5x       12x 12x 1x   11x 1x   10x 10x 1x       9x 1x   8x 1x            
// Inquiry Service - Business Logic Layer
// Trimmed to create-only — pro/admin management removed (CRM replaces them).
// Marketplace inquiry submission still uses create().
import type { Dal } from "../dal";
import type { Inquiry } from "../db/schema";
import { INQUIRY_TYPES } from "../db/schema";
import { NotFoundError, ValidationError } from "../lib/errors";
import { parseIndianPhone } from "@interioring/utils/validation/phone";
import {
	validateCustomerName,
	customerNameErrorMessage,
	normalizeCustomerName,
} from "@interioring/utils/validation/customer-name";
import { isValidEnum } from "../lib/utils";
export type CreateInquiryInput = {
	proId: string;
	projectId?: string;
	type: string;
	customerName: string;
	customerPhone: string;
	customerEmail?: string;
	customerLocation?: string;
	requirement?: string;
	notes?: string;
	sourcePage?: string;
	requirementType?: string;
	sourceType?: string;
};
 
export class InquiryService {
	constructor(private dal: Dal) {}
 
	async create(input: CreateInquiryInput): Promise<Inquiry> {
		this.validateInput(input);
 
		// Verify pro exists
		const pro = await this.dal.pros.findById(input.proId);
		if (!pro) {
			throw new NotFoundError("Pro", input.proId);
		}
 
		// Verify project if provided
		if (input.projectId) {
			const project = await this.dal.projects.findById(input.projectId);
			if (!project) {
				throw new NotFoundError("Project", input.projectId);
			}
		}
 
		const parsedPhone = parseIndianPhone(input.customerPhone);
		Iif (!parsedPhone) {
			throw new ValidationError("Invalid Indian phone number");
		}
		const inquiry = await this.dal.inquiries.create({
			proId: input.proId,
			projectId: input.projectId,
			type: input.type as Inquiry["type"],
			status: "new",
			customerName: normalizeCustomerName(input.customerName),
			customerPhone: parsedPhone.e164,
			customerEmail: input.customerEmail?.trim(),
			customerLocation: input.customerLocation?.trim(),
			requirement: input.requirement?.trim(),
			notes: input.notes?.trim(),
			sourcePage: input.sourcePage,
			requirementType: input.requirementType,
			sourceType: input.sourceType,
		});
 
		return inquiry;
	}
 
	private validateInput(input: CreateInquiryInput): void {
		const nameError = validateCustomerName(input.customerName ?? "");
		if (nameError) {
			throw new ValidationError(customerNameErrorMessage(nameError));
		}
		if (!input.customerPhone?.trim()) {
			throw new ValidationError("Customer phone is required");
		}
		const parsed = parseIndianPhone(input.customerPhone);
		if (!parsed || parsed.type !== "mobile") {
			throw new ValidationError(
				"Customer phone must be a valid 10-digit Indian mobile number",
			);
		}
		if (!input.type) {
			throw new ValidationError("Inquiry type is required");
		}
		if (!isValidEnum(input.type, INQUIRY_TYPES)) {
			throw new ValidationError(
				`Invalid inquiry type. Must be one of: ${INQUIRY_TYPES.join(", ")}`,
			);
		}
	}
}