@@ -159,6 +159,39 @@ router.get('/metadata', async (req, res) => {
159159 * schema:
160160 * type: integer
161161 * default: 10
162+ * - in: query
163+ * name: search
164+ * schema:
165+ * type: string
166+ * description: Search by name, email, or company
167+ * - in: query
168+ * name: status
169+ * schema:
170+ * type: string
171+ * enum: [NEW, PENDING, CONTACTED, QUALIFIED, UNQUALIFIED, CONVERTED]
172+ * description: Filter by lead status
173+ * - in: query
174+ * name: leadSource
175+ * schema:
176+ * type: string
177+ * enum: [WEB, PHONE_INQUIRY, PARTNER_REFERRAL, COLD_CALL, TRADE_SHOW, EMPLOYEE_REFERRAL, ADVERTISEMENT, OTHER]
178+ * description: Filter by lead source
179+ * - in: query
180+ * name: industry
181+ * schema:
182+ * type: string
183+ * description: Filter by industry
184+ * - in: query
185+ * name: rating
186+ * schema:
187+ * type: string
188+ * enum: [Hot, Warm, Cold]
189+ * description: Filter by rating
190+ * - in: query
191+ * name: converted
192+ * schema:
193+ * type: boolean
194+ * description: Filter by conversion status
162195 * responses:
163196 * 200:
164197 * description: List of leads
@@ -179,10 +212,82 @@ router.get('/', async (req, res) => {
179212 const page = parseInt ( req . query . page ) || 1 ;
180213 const limit = parseInt ( req . query . limit ) || 10 ;
181214 const skip = ( page - 1 ) * limit ;
215+
216+ const {
217+ search,
218+ status,
219+ leadSource,
220+ industry,
221+ rating,
222+ converted
223+ } = req . query ;
224+
225+ // Build where clause for filtering
226+ let whereClause = {
227+ organizationId : req . organizationId
228+ } ;
229+
230+ // Add search filter (search in firstName, lastName, email, company)
231+ if ( search ) {
232+ whereClause . OR = [
233+ {
234+ firstName : {
235+ contains : search ,
236+ mode : 'insensitive'
237+ }
238+ } ,
239+ {
240+ lastName : {
241+ contains : search ,
242+ mode : 'insensitive'
243+ }
244+ } ,
245+ {
246+ email : {
247+ contains : search ,
248+ mode : 'insensitive'
249+ }
250+ } ,
251+ {
252+ company : {
253+ contains : search ,
254+ mode : 'insensitive'
255+ }
256+ }
257+ ] ;
258+ }
259+
260+ // Add status filter
261+ if ( status ) {
262+ whereClause . status = status ;
263+ }
264+
265+ // Add leadSource filter
266+ if ( leadSource ) {
267+ whereClause . leadSource = leadSource ;
268+ }
269+
270+ // Add industry filter
271+ if ( industry ) {
272+ whereClause . industry = {
273+ contains : industry ,
274+ mode : 'insensitive'
275+ } ;
276+ }
277+
278+ // Add rating filter
279+ if ( rating ) {
280+ whereClause . rating = rating ;
281+ }
282+
283+ // Add converted filter
284+ if ( converted !== undefined ) {
285+ whereClause . isConverted = converted === 'true' ;
286+ }
182287
183288 const [ leads , total ] = await Promise . all ( [
184289 prisma . lead . findMany ( {
185- where : { organizationId : req . organizationId } ,
290+ where : whereClause ,
186291 skip,
187292 take : limit ,
188293 orderBy : { createdAt : 'desc' } ,
@@ -193,17 +298,25 @@ router.get('/', async (req, res) => {
193298 }
194299 } ) ,
195300 prisma . lead . count ( {
196- where : { organizationId : req . organizationId }
301+ where : whereClause
197302 } )
198303 ] ) ;
199304
305+ // Calculate pagination info
306+ const totalPages = Math . ceil ( total / limit ) ;
307+ const hasNext = page < totalPages ;
308+ const hasPrev = page > 1 ;
309+
200310 res . json ( {
311+ success : true ,
201312 leads,
202313 pagination : {
203314 page,
204315 limit,
205316 total,
206- pages : Math . ceil ( total / limit )
317+ totalPages,
318+ hasNext,
319+ hasPrev
207320 }
208321 } ) ;
209322 } catch ( error ) {
@@ -297,10 +410,21 @@ router.get('/:id', async (req, res) => {
297410 * type: string
298411 * company:
299412 * type: string
413+ * title:
414+ * type: string
300415 * status:
301416 * type: string
417+ * enum: [NEW, PENDING, CONTACTED, QUALIFIED, UNQUALIFIED, CONVERTED]
302418 * leadSource:
303419 * type: string
420+ * enum: [WEB, PHONE_INQUIRY, PARTNER_REFERRAL, COLD_CALL, TRADE_SHOW, EMPLOYEE_REFERRAL, ADVERTISEMENT, OTHER]
421+ * industry:
422+ * type: string
423+ * rating:
424+ * type: string
425+ * enum: [Hot, Warm, Cold]
426+ * description:
427+ * type: string
304428 * responses:
305429 * 201:
306430 * description: Lead created successfully
@@ -309,21 +433,61 @@ router.get('/:id', async (req, res) => {
309433 */
310434router . post ( '/' , async ( req , res ) => {
311435 try {
312- const { firstName, lastName, email, phone, company, status, leadSource } = req . body ;
436+ const {
437+ firstName,
438+ lastName,
439+ email,
440+ phone,
441+ company,
442+ title,
443+ status,
444+ leadSource,
445+ industry,
446+ rating,
447+ description
448+ } = req . body ;
313449
314450 if ( ! firstName || ! lastName || ! email ) {
315451 return res . status ( 400 ) . json ( { error : 'First name, last name, and email are required' } ) ;
316452 }
317453
454+ // Validate email format
455+ const emailRegex = / ^ [ ^ \s @ ] + @ [ ^ \s @ ] + \. [ ^ \s @ ] + $ / ;
456+ if ( ! emailRegex . test ( email ) ) {
457+ return res . status ( 400 ) . json ( { error : 'Invalid email format' } ) ;
458+ }
459+
460+ // Validate status if provided
461+ const validStatuses = [ 'NEW' , 'PENDING' , 'CONTACTED' , 'QUALIFIED' , 'UNQUALIFIED' , 'CONVERTED' ] ;
462+ if ( status && ! validStatuses . includes ( status ) ) {
463+ return res . status ( 400 ) . json ( { error : 'Invalid status value' } ) ;
464+ }
465+
466+ // Validate leadSource if provided
467+ const validSources = [ 'WEB' , 'PHONE_INQUIRY' , 'PARTNER_REFERRAL' , 'COLD_CALL' , 'TRADE_SHOW' , 'EMPLOYEE_REFERRAL' , 'ADVERTISEMENT' , 'OTHER' ] ;
468+ if ( leadSource && ! validSources . includes ( leadSource ) ) {
469+ return res . status ( 400 ) . json ( { error : 'Invalid lead source value' } ) ;
470+ }
471+
472+ // Validate rating if provided
473+ const validRatings = [ 'Hot' , 'Warm' , 'Cold' ] ;
474+ if ( rating && ! validRatings . includes ( rating ) ) {
475+ return res . status ( 400 ) . json ( { error : 'Invalid rating value' } ) ;
476+ }
477+
318478 const lead = await prisma . lead . create ( {
319479 data : {
320- firstName,
321- lastName,
322- email,
323- phone,
324- company,
325- status : status || 'NEW' ,
326- leadSource,
480+ firstName : firstName . trim ( ) ,
481+ lastName : lastName . trim ( ) ,
482+ email : email . trim ( ) . toLowerCase ( ) ,
483+ phone : phone ?. trim ( ) || null ,
484+ company : company ?. trim ( ) || null ,
485+ title : title ?. trim ( ) || null ,
486+ status : status || 'PENDING' ,
487+ leadSource : leadSource || null ,
488+ industry : industry ?. trim ( ) || null ,
489+ rating : rating || null ,
490+ description : description ?. trim ( ) || null ,
327491 organizationId : req . organizationId ,
328492 ownerId : req . userId
329493 } ,
@@ -337,6 +501,9 @@ router.post('/', async (req, res) => {
337501 res . status ( 201 ) . json ( lead ) ;
338502 } catch ( error ) {
339503 console . error ( 'Create lead error:' , error ) ;
504+ if ( error . code === 'P2002' ) {
505+ return res . status ( 409 ) . json ( { error : 'A lead with this email already exists in this organization' } ) ;
506+ }
340507 res . status ( 500 ) . json ( { error : 'Internal server error' } ) ;
341508 }
342509} ) ;
0 commit comments