@@ -320,6 +320,128 @@ export const routes = (router: KoaRouter) => {
320320 }
321321 } )
322322
323+ /**
324+ * @swagger
325+ * /leases/export:
326+ * get:
327+ * summary: Export leases to Excel
328+ * tags:
329+ * - Lease service
330+ * description: Export lease search results to Excel file. Uses same filters as /leases/search but without pagination.
331+ * parameters:
332+ * - in: query
333+ * name: q
334+ * schema:
335+ * type: string
336+ * description: Free-text search (contract ID, tenant name, PNR, contact code, address)
337+ * - in: query
338+ * name: objectType
339+ * schema:
340+ * type: array
341+ * items:
342+ * type: string
343+ * description: Object types (e.g., residence, parking)
344+ * - in: query
345+ * name: status
346+ * schema:
347+ * type: array
348+ * items:
349+ * type: string
350+ * enum: ['0', '1', '2', '3']
351+ * description: Contract status filter (0=Current, 1=Upcoming, 2=AboutToEnd, 3=Ended)
352+ * - in: query
353+ * name: startDateFrom
354+ * schema:
355+ * type: string
356+ * format: date
357+ * description: Minimum start date (YYYY-MM-DD)
358+ * - in: query
359+ * name: startDateTo
360+ * schema:
361+ * type: string
362+ * format: date
363+ * description: Maximum start date (YYYY-MM-DD)
364+ * - in: query
365+ * name: endDateFrom
366+ * schema:
367+ * type: string
368+ * format: date
369+ * description: Minimum end date (YYYY-MM-DD)
370+ * - in: query
371+ * name: endDateTo
372+ * schema:
373+ * type: string
374+ * format: date
375+ * description: Maximum end date (YYYY-MM-DD)
376+ * - in: query
377+ * name: property
378+ * schema:
379+ * type: array
380+ * items:
381+ * type: string
382+ * description: Property/estate names
383+ * - in: query
384+ * name: buildingCodes
385+ * schema:
386+ * type: array
387+ * items:
388+ * type: string
389+ * description: Building codes
390+ * - in: query
391+ * name: areaCodes
392+ * schema:
393+ * type: array
394+ * items:
395+ * type: string
396+ * description: Area codes (Område)
397+ * - in: query
398+ * name: districtNames
399+ * schema:
400+ * type: array
401+ * items:
402+ * type: string
403+ * description: District names
404+ * - in: query
405+ * name: buildingManager
406+ * schema:
407+ * type: array
408+ * items:
409+ * type: string
410+ * description: Building manager names (Kvartersvärd)
411+ * produces:
412+ * - application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
413+ * responses:
414+ * 200:
415+ * description: Excel file download
416+ * 500:
417+ * description: Internal server error
418+ * security:
419+ * - bearerAuth: []
420+ */
421+ router . get ( '/leases/export' , async ( ctx ) => {
422+ const metadata = generateRouteMetadata ( ctx )
423+
424+ try {
425+ const result = await leasingAdapter . exportLeasesToExcel ( ctx . query )
426+
427+ if ( ! result . ok ) {
428+ logger . error ( { err : result . err , metadata } , 'Lease export failed' )
429+ ctx . status = 500
430+ ctx . body = { error : 'Internal server error' , ...metadata }
431+ return
432+ }
433+
434+ ctx . set ( 'Content-Type' , result . data . contentType )
435+ ctx . set ( 'Content-Disposition' , result . data . contentDisposition )
436+ ctx . status = 200
437+ ctx . body = result . data . data
438+ } catch ( error ) {
439+ logger . error ( { error, metadata } , 'Error exporting leases to Excel' )
440+ ctx . status = 500
441+ ctx . body = { error : 'Internal server error' , ...metadata }
442+ }
443+ } )
444+
323445 /**
324446 * @swagger
325447 * /leases/by-rental-property-id/{rentalPropertyId}:
0 commit comments