import { Controller, Get, Post, Patch, Delete, Body, Param, Query, ParseUUIDPipe, HttpCode, HttpStatus, UseInterceptors, UploadedFiles, } from "@nestjs/common"; import { AnyFilesInterceptor } from "@nestjs/platform-express"; import { ApiOperation, ApiTags, ApiConsumes } from "@nestjs/swagger"; import { CurrentUser } from "@edr/api-common"; import { FreightAdmin } from "../../common/booking-guards"; import { FilesService } from "../files/files.service"; import { CompaniesService } from "./companies.service"; import { CreateCompanyDto } from "./dto/create-company.dto"; import { UpdateCompanyDto } from "./dto/update-company.dto"; import { CreateExternalProfileDto } from "./dto/create-external-profile.dto"; import { CreateCompanyWithProfileDto } from "./dto/create-company-with-profile.dto"; import { AddCompanyProfilesDto } from "./dto/add-company-profiles.dto"; import { CreateCompanyProfileDto } from "./dto/create-company-profile.dto"; import { SetActiveModeDto } from "./dto/set-active-mode.dto"; import { SetOnboardingStepDto } from "./dto/set-onboarding-step.dto"; import { StartOnboardingDto } from "./dto/start-onboarding.dto"; import { DashboardQueryDto } from "./dto/dashboard-query.dto"; import { ResponseCompanyDto, ResponseCompanyProfileDto, } from "./dto/response-company.dto"; import { BusinessLicenseFile } from "./entities/company-profile.entity"; import { ResponseExternalProfileDto } from "./dto/response-external-profile.dto"; import { CompanyInfoResponseDto } from "./dto/company-info-response.dto"; import { UpdateProfileDto } from "./dto/update-profile.dto"; import { ProfileResponseDto } from "./dto/profile-response.dto"; import { DashboardSummaryResponseDto } from "./dto/dashboard-summary-response.dto"; import { ListCompaniesQueryDto } from "./dto/list-companies-query.dto"; import { CompanyStatsResponseDto } from "./dto/company-stats-response.dto"; import { UpdateCompanyProfileStatusDto } from "./dto/update-company-profile-status.dto"; import { FetchETradeDto } from "./dto/fetch-etrade.dto"; import { ETradeResponseDto } from "./dto/etrade-response.dto"; interface CurrentIamUser { id: string; name?: { en: string; am: string }; email?: string; phoneNumber?: string; } @ApiTags("Companies") @Controller("companies") export class CompaniesController { constructor( private readonly companiesService: CompaniesService, private readonly filesService: FilesService, ) { } @Get("getInfo") @ApiOperation({ summary: "Get company info for the current user" }) async getInfo( @CurrentUser() user: CurrentIamUser, ): Promise { const { profile, company } = await this.companiesService.getCompanyInfoByUserId(user.id); return new CompanyInfoResponseDto(profile, company); } @Get("profile") @ApiOperation({ summary: "Get flattened profile for the settings page" }) async getProfile( @CurrentUser() user: CurrentIamUser, ): Promise { const { profile, company } = await this.companiesService.getCompanyInfoByUserId(user.id); return new ProfileResponseDto(profile, company); } @Get("dashboard") @ApiOperation({ summary: "Get portal dashboard KPIs (delivered, spend, freight volume) for the current user", }) async getDashboard( @CurrentUser() user: CurrentIamUser, @Query() query: DashboardQueryDto, ): Promise { return this.companiesService.getDashboardSummary( user.id, query.companyProfileId, ); } @Post("fetch-etrade-info") @ApiOperation({ summary: "Fetch company info from eTrade by TIN" }) async fetchETradeInfo( @Body() dto: FetchETradeDto, ): Promise { const data = await this.companiesService.fetchETradeData(dto.tin); return new ETradeResponseDto(data); } @Patch("profile") @ApiOperation({ summary: "Update profile (flattened settings page)" }) async updateProfile( @CurrentUser() user: CurrentIamUser, @Body() dto: UpdateProfileDto, ): Promise { return this.companiesService.updateProfile(user.id, dto); } @Post("company-profiles") @ApiOperation({ summary: "Add operational profile(s) (importer/exporter/forwarder) to the current user's company", }) async addCompanyProfiles( @CurrentUser() user: CurrentIamUser, @Body() dto: AddCompanyProfilesDto, ): Promise { const profiles = await this.companiesService.addCompanyProfilesForUser( user.id, dto.types, ); return profiles.map((p) => new ResponseCompanyProfileDto(p)); } @Post("onboarding/start") @ApiOperation({ summary: "Begin onboarding: create a draft company + profile + role(s) so later steps can save incrementally", }) async startOnboarding( @CurrentUser() user: CurrentIamUser, @Body() dto: StartOnboardingDto, ): Promise { const nameParts = (user.name?.en ?? "").split(" "); const { profile, company } = await this.companiesService.startOnboarding( { userId: user.id, firstName: nameParts[0] || "", lastName: nameParts.slice(-1)[0] || "", email: user.email ?? "", phone: user.phoneNumber ?? "", }, dto.companyType, dto.roles, dto.nationality, ); return new CompanyInfoResponseDto(profile, company); } @Post("company-profile") @ApiOperation({ summary: "Create a single operational profile for the current user's company and make it the active mode", }) async createCompanyProfile( @CurrentUser() user: CurrentIamUser, @Body() dto: CreateCompanyProfileDto, ): Promise { const profile = await this.companiesService.createCompanyProfileForUser( user.id, dto.type, dto.businessLicense, ); return new ResponseCompanyProfileDto(profile); } @Post("company-profiles/:profileId/license") @UseInterceptors(AnyFilesInterceptor()) @ApiConsumes("multipart/form-data") @ApiOperation({ summary: "Upload business-license document(s) for one of the current user's company profiles", }) async uploadProfileLicense( @CurrentUser() user: CurrentIamUser, @Param("profileId", ParseUUIDPipe) profileId: string, @UploadedFiles() files: Array, ): Promise { return this.companiesService.uploadProfileLicenseFiles( user.id, profileId, files, ); } @Get("company-profiles/:profileId/license") @ApiOperation({ summary: "List business-license documents for a company profile", }) async listProfileLicense( @CurrentUser() user: CurrentIamUser, @Param("profileId", ParseUUIDPipe) profileId: string, ): Promise { return this.companiesService.listProfileLicenseFiles(user.id, profileId); } @Patch("active-mode") @ApiOperation({ summary: "Switch the current user's active operational mode (importer/exporter)", }) async setActiveMode( @CurrentUser() user: CurrentIamUser, @Body() dto: SetActiveModeDto, ): Promise { const { profile, company } = await this.companiesService.setActiveMode( user.id, dto.type, ); return new CompanyInfoResponseDto(profile, company); } @Patch("onboarding-step") @ApiOperation({ summary: "Persist the user's current onboarding wizard step" }) @HttpCode(HttpStatus.NO_CONTENT) async setOnboardingStep( @CurrentUser() user: CurrentIamUser, @Body() dto: SetOnboardingStepDto, ): Promise { await this.companiesService.setOnboardingStep(user.id, dto.step); } @Post("onboarding/complete") @ApiOperation({ summary: "Mark the current user's onboarding as complete" }) async completeOnboarding( @CurrentUser() user: CurrentIamUser, ): Promise { const { profile, company } = await this.companiesService.markOnboardingComplete(user.id); return new CompanyInfoResponseDto(profile, company); } // Used by portal @Post("create") @ApiOperation({ summary: "Create a company with its associated external profile (onboarding)", }) async createWithProfile( @CurrentUser() user: CurrentIamUser, @Body() dto: CreateCompanyWithProfileDto, ): Promise { const nameParts = (user.name?.en ?? "").split(" "); const { profile, company } = await this.companiesService.createCompanyWithProfile( { userId: user.id, firstName: nameParts[0] || "", lastName: nameParts.slice(-1)[0] || "", email: user.email ?? "", phone: user.phoneNumber ?? "", }, dto, ); return new CompanyInfoResponseDto(profile, company); } // Used by backoffice @Post() @FreightAdmin() @ApiOperation({ summary: "Create a new company (customer, freight_forwarder, dj_freight_forwarder, transporter)", }) async create(@Body() dto: CreateCompanyDto): Promise { const company = await this.companiesService.createCompany(dto); return new ResponseCompanyDto(company); } @Get("stats") @ApiOperation({ summary: "Company counts by status (KPI strip)" }) async getStats(): Promise { return this.companiesService.getCompanyStats(); } @Get() @ApiOperation({ summary: "List companies (paginated, filterable)" }) async findAll( @Query() query: ListCompaniesQueryDto, ): Promise<{ items: ResponseCompanyDto[]; total: number }> { const { items, total } = await this.companiesService.listCompanies(query); return { items: items.map((c) => new ResponseCompanyDto(c)), total }; } @Get(":id") @ApiOperation({ summary: "Get company by ID" }) async findById( @Param("id", ParseUUIDPipe) id: string, ): Promise { const company = await this.companiesService.findCompanyById(id); return new ResponseCompanyDto(company); } @Patch(":id") @FreightAdmin() @ApiOperation({ summary: "Update a company" }) async update( @Param("id", ParseUUIDPipe) id: string, @Body() dto: UpdateCompanyDto, ): Promise { const company = await this.companiesService.updateCompany(id, dto); return new ResponseCompanyDto(company); } @Delete(":id") @FreightAdmin() @ApiOperation({ summary: "Soft-delete a company" }) @HttpCode(HttpStatus.NO_CONTENT) async remove(@Param("id", ParseUUIDPipe) id: string): Promise { await this.companiesService.deleteCompany(id); } @Get(":companyId/documents") @ApiOperation({ summary: "List documents uploaded for a company" }) async listDocuments( @Param("companyId", ParseUUIDPipe) companyId: string, ) { const files = await this.filesService.findByResource(companyId, "companies"); return files.map((f) => ({ id: f.id, name: f.name, code: f.code, mimeType: f.mimeType, size: f.size, uploadedAt: f.createdAt, url: f.url, })); } @Post(":companyId/documents") @UseInterceptors(AnyFilesInterceptor()) @ApiConsumes("multipart/form-data") @ApiOperation({ summary: "Upload documents for a company (onboarding)" }) async uploadDocuments( @Param("companyId", ParseUUIDPipe) companyId: string, @UploadedFiles() files: Array, ) { return this.filesService.uploadMany(companyId, "companies", files); } @Patch("company-profiles/:profileId/status") @FreightAdmin() @ApiOperation({ summary: "Update a company profile's approval status" }) async updateCompanyProfileStatus( @Param("profileId", ParseUUIDPipe) profileId: string, @Body() dto: UpdateCompanyProfileStatusDto, ): Promise { const profile = await this.companiesService.setCompanyProfileStatus( profileId, dto.status, ); return new ResponseCompanyProfileDto(profile); } @Post(":companyId/profiles") @FreightAdmin() @ApiOperation({ summary: "Add a profile (employee) to a company" }) async createProfile( @Param("companyId", ParseUUIDPipe) companyId: string, @Body() dto: CreateExternalProfileDto, ): Promise { const profile = await this.companiesService.createProfile({ ...dto, companyId, }); return new ResponseExternalProfileDto(profile); } @Get(":companyId/profiles") @ApiOperation({ summary: "List profiles for a company" }) async listProfiles( @Param("companyId", ParseUUIDPipe) companyId: string, ): Promise { const profiles = await this.companiesService.findProfilesByCompany(companyId); return profiles.map((p) => new ResponseExternalProfileDto(p)); } @Get("profile/user/:userId") @ApiOperation({ summary: "Get profile by IAM user ID" }) async findProfileByUser( @Param("userId", ParseUUIDPipe) userId: string, ): Promise { const profile = await this.companiesService.findProfileByUserId(userId); return new ResponseExternalProfileDto(profile); } }