mirror of https://github.com/ghostfolio/ghostfolio
13 changed files with 593 additions and 7 deletions
@ -0,0 +1,27 @@ |
|||
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator'; |
|||
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; |
|||
import { permissions } from '@ghostfolio/common/permissions'; |
|||
import type { RequestWithUser } from '@ghostfolio/common/types'; |
|||
|
|||
import { |
|||
Body, |
|||
Controller, |
|||
Delete, |
|||
Get, |
|||
Inject, |
|||
Put, |
|||
Query, |
|||
UseGuards |
|||
} from '@nestjs/common'; |
|||
import { REQUEST } from '@nestjs/core'; |
|||
import { AuthGuard } from '@nestjs/passport'; |
|||
|
|||
import { CellMappingService } from './cell-mapping.service'; |
|||
|
|||
@Controller('cell-mapping') |
|||
export class CellMappingController { |
|||
public constructor( |
|||
private readonly cellMappingService: CellMappingService, |
|||
@Inject(REQUEST) private readonly request: RequestWithUser |
|||
) {} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module'; |
|||
|
|||
import { Module } from '@nestjs/common'; |
|||
|
|||
import { CellMappingController } from './cell-mapping.controller'; |
|||
import { CellMappingService } from './cell-mapping.service'; |
|||
|
|||
@Module({ |
|||
controllers: [CellMappingController], |
|||
exports: [CellMappingService], |
|||
imports: [PrismaModule], |
|||
providers: [CellMappingService] |
|||
}) |
|||
export class CellMappingModule {} |
|||
@ -0,0 +1,181 @@ |
|||
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; |
|||
|
|||
import { Injectable, OnModuleInit } from '@nestjs/common'; |
|||
|
|||
/** Default IRS K-1 (Form 1065) cell mappings */ |
|||
const IRS_DEFAULT_MAPPINGS: Array<{ |
|||
boxNumber: string; |
|||
label: string; |
|||
description: string; |
|||
sortOrder: number; |
|||
}> = [ |
|||
{ boxNumber: '1', label: 'Ordinary business income (loss)', description: 'IRS Schedule K-1 Box 1', sortOrder: 1 }, |
|||
{ boxNumber: '2', label: 'Net rental real estate income (loss)', description: 'IRS Schedule K-1 Box 2', sortOrder: 2 }, |
|||
{ boxNumber: '3', label: 'Other net rental income (loss)', description: 'IRS Schedule K-1 Box 3', sortOrder: 3 }, |
|||
{ boxNumber: '4', label: 'Guaranteed payments for services', description: 'IRS Schedule K-1 Box 4', sortOrder: 4 }, |
|||
{ boxNumber: '4a', label: 'Guaranteed payments for capital', description: 'IRS Schedule K-1 Box 4a', sortOrder: 5 }, |
|||
{ boxNumber: '4b', label: 'Total guaranteed payments', description: 'IRS Schedule K-1 Box 4b', sortOrder: 6 }, |
|||
{ boxNumber: '5', label: 'Interest income', description: 'IRS Schedule K-1 Box 5', sortOrder: 7 }, |
|||
{ boxNumber: '6a', label: 'Ordinary dividends', description: 'IRS Schedule K-1 Box 6a', sortOrder: 8 }, |
|||
{ boxNumber: '6b', label: 'Qualified dividends', description: 'IRS Schedule K-1 Box 6b', sortOrder: 9 }, |
|||
{ boxNumber: '6c', label: 'Dividend equivalents', description: 'IRS Schedule K-1 Box 6c', sortOrder: 10 }, |
|||
{ boxNumber: '7', label: 'Royalties', description: 'IRS Schedule K-1 Box 7', sortOrder: 11 }, |
|||
{ boxNumber: '8', label: 'Net short-term capital gain (loss)', description: 'IRS Schedule K-1 Box 8', sortOrder: 12 }, |
|||
{ boxNumber: '9a', label: 'Net long-term capital gain (loss)', description: 'IRS Schedule K-1 Box 9a', sortOrder: 13 }, |
|||
{ boxNumber: '9b', label: 'Collectibles (28%) gain (loss)', description: 'IRS Schedule K-1 Box 9b', sortOrder: 14 }, |
|||
{ boxNumber: '9c', label: 'Unrecaptured section 1250 gain', description: 'IRS Schedule K-1 Box 9c', sortOrder: 15 }, |
|||
{ boxNumber: '10', label: 'Net section 1231 gain (loss)', description: 'IRS Schedule K-1 Box 10', sortOrder: 16 }, |
|||
{ boxNumber: '11', label: 'Other income (loss)', description: 'IRS Schedule K-1 Box 11', sortOrder: 17 }, |
|||
{ boxNumber: '12', label: 'Section 179 deduction', description: 'IRS Schedule K-1 Box 12', sortOrder: 18 }, |
|||
{ boxNumber: '13', label: 'Other deductions', description: 'IRS Schedule K-1 Box 13', sortOrder: 19 }, |
|||
{ boxNumber: '14', label: 'Self-employment earnings (loss)', description: 'IRS Schedule K-1 Box 14', sortOrder: 20 }, |
|||
{ boxNumber: '15', label: 'Credits', description: 'IRS Schedule K-1 Box 15', sortOrder: 21 }, |
|||
{ boxNumber: '16', label: 'Foreign transactions', description: 'IRS Schedule K-1 Box 16', sortOrder: 22 }, |
|||
{ boxNumber: '17', label: 'Alternative minimum tax (AMT) items', description: 'IRS Schedule K-1 Box 17', sortOrder: 23 }, |
|||
{ boxNumber: '18', label: 'Tax-exempt income and nondeductible expenses', description: 'IRS Schedule K-1 Box 18', sortOrder: 24 }, |
|||
{ boxNumber: '19a', label: 'Distributions — Cash and marketable securities', description: 'IRS Schedule K-1 Box 19a', sortOrder: 25 }, |
|||
{ boxNumber: '19b', label: 'Distributions — Other property', description: 'IRS Schedule K-1 Box 19b', sortOrder: 26 }, |
|||
{ boxNumber: '20', label: 'Other information', description: 'IRS Schedule K-1 Box 20', sortOrder: 27 }, |
|||
{ boxNumber: '21', label: 'Foreign taxes paid or accrued', description: 'IRS Schedule K-1 Box 21', sortOrder: 28 } |
|||
]; |
|||
|
|||
/** Default aggregation rules */ |
|||
const DEFAULT_AGGREGATION_RULES: Array<{ |
|||
name: string; |
|||
operation: string; |
|||
sourceCells: string[]; |
|||
sortOrder: number; |
|||
}> = [ |
|||
{ |
|||
name: 'Total Ordinary Income', |
|||
operation: 'SUM', |
|||
sourceCells: ['1'], |
|||
sortOrder: 1 |
|||
}, |
|||
{ |
|||
name: 'Total Capital Gains', |
|||
operation: 'SUM', |
|||
sourceCells: ['8', '9a', '9b', '9c', '10'], |
|||
sortOrder: 2 |
|||
}, |
|||
{ |
|||
name: 'Total Deductions', |
|||
operation: 'SUM', |
|||
sourceCells: ['12', '13'], |
|||
sortOrder: 3 |
|||
} |
|||
]; |
|||
|
|||
@Injectable() |
|||
export class CellMappingService implements OnModuleInit { |
|||
public constructor(private readonly prismaService: PrismaService) {} |
|||
|
|||
public async onModuleInit() { |
|||
await this.seedDefaultMappings(); |
|||
await this.seedDefaultAggregationRules(); |
|||
} |
|||
|
|||
/** |
|||
* Seed default IRS cell mappings (partnershipId = null) if they don't exist |
|||
*/ |
|||
public async seedDefaultMappings() { |
|||
const existingCount = await this.prismaService.cellMapping.count({ |
|||
where: { partnershipId: null } |
|||
}); |
|||
|
|||
if (existingCount > 0) { |
|||
return; |
|||
} |
|||
|
|||
await this.prismaService.cellMapping.createMany({ |
|||
data: IRS_DEFAULT_MAPPINGS.map((mapping) => ({ |
|||
...mapping, |
|||
partnershipId: null, |
|||
isCustom: false |
|||
})) |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* Seed default aggregation rules (partnershipId = null) if they don't exist |
|||
*/ |
|||
public async seedDefaultAggregationRules() { |
|||
const existingCount = await this.prismaService.cellAggregationRule.count({ |
|||
where: { partnershipId: null } |
|||
}); |
|||
|
|||
if (existingCount > 0) { |
|||
return; |
|||
} |
|||
|
|||
await this.prismaService.cellAggregationRule.createMany({ |
|||
data: DEFAULT_AGGREGATION_RULES.map((rule) => ({ |
|||
...rule, |
|||
partnershipId: null |
|||
})) |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* Get cell mappings for a partnership (with global defaults for unmapped boxes) |
|||
*/ |
|||
public async getMappings(partnershipId?: string) { |
|||
if (!partnershipId) { |
|||
return this.prismaService.cellMapping.findMany({ |
|||
where: { partnershipId: null }, |
|||
orderBy: { sortOrder: 'asc' } |
|||
}); |
|||
} |
|||
|
|||
// Get partnership-specific mappings
|
|||
const partnershipMappings = await this.prismaService.cellMapping.findMany({ |
|||
where: { partnershipId }, |
|||
orderBy: { sortOrder: 'asc' } |
|||
}); |
|||
|
|||
// Get global defaults for any boxes not overridden
|
|||
const globalMappings = await this.prismaService.cellMapping.findMany({ |
|||
where: { partnershipId: null }, |
|||
orderBy: { sortOrder: 'asc' } |
|||
}); |
|||
|
|||
const partnershipBoxNumbers = new Set( |
|||
partnershipMappings.map((m) => m.boxNumber) |
|||
); |
|||
|
|||
const mergedMappings = [ |
|||
...partnershipMappings, |
|||
...globalMappings.filter((g) => !partnershipBoxNumbers.has(g.boxNumber)) |
|||
]; |
|||
|
|||
return mergedMappings.sort((a, b) => a.sortOrder - b.sortOrder); |
|||
} |
|||
|
|||
/** |
|||
* Get aggregation rules for a partnership (with global defaults) |
|||
*/ |
|||
public async getAggregationRules(partnershipId?: string) { |
|||
if (!partnershipId) { |
|||
return this.prismaService.cellAggregationRule.findMany({ |
|||
where: { partnershipId: null }, |
|||
orderBy: { sortOrder: 'asc' } |
|||
}); |
|||
} |
|||
|
|||
const partnershipRules = |
|||
await this.prismaService.cellAggregationRule.findMany({ |
|||
where: { partnershipId }, |
|||
orderBy: { sortOrder: 'asc' } |
|||
}); |
|||
|
|||
if (partnershipRules.length > 0) { |
|||
return partnershipRules; |
|||
} |
|||
|
|||
// Fall back to global defaults
|
|||
return this.prismaService.cellAggregationRule.findMany({ |
|||
where: { partnershipId: null }, |
|||
orderBy: { sortOrder: 'asc' } |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
import type { K1ExtractionResult } from '@ghostfolio/common/interfaces'; |
|||
|
|||
/** |
|||
* Interface for K-1 PDF data extractors. |
|||
* Each extractor implements a different extraction strategy |
|||
* (pdf-parse for digital PDFs, Azure DI for scanned, tesseract as fallback). |
|||
*/ |
|||
export interface K1Extractor { |
|||
/** |
|||
* Extract structured K-1 data from a PDF buffer. |
|||
* @param buffer - The PDF file content as a Buffer |
|||
* @param fileName - Original filename of the uploaded PDF |
|||
* @returns Extracted K-1 fields with confidence scores |
|||
*/ |
|||
extract(buffer: Buffer, fileName: string): Promise<K1ExtractionResult>; |
|||
|
|||
/** |
|||
* Check if this extractor is available/configured. |
|||
* For example, Azure extractor requires API keys to be configured. |
|||
*/ |
|||
isAvailable(): boolean; |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator'; |
|||
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; |
|||
import { permissions } from '@ghostfolio/common/permissions'; |
|||
import type { RequestWithUser } from '@ghostfolio/common/types'; |
|||
|
|||
import { |
|||
Body, |
|||
Controller, |
|||
Get, |
|||
Inject, |
|||
Param, |
|||
Post, |
|||
Put, |
|||
Query, |
|||
UploadedFile, |
|||
UseGuards, |
|||
UseInterceptors |
|||
} from '@nestjs/common'; |
|||
import { REQUEST } from '@nestjs/core'; |
|||
import { AuthGuard } from '@nestjs/passport'; |
|||
import { FileInterceptor } from '@nestjs/platform-express'; |
|||
|
|||
import { K1ImportService } from './k1-import.service'; |
|||
|
|||
@Controller('k1-import') |
|||
export class K1ImportController { |
|||
public constructor( |
|||
private readonly k1ImportService: K1ImportService, |
|||
@Inject(REQUEST) private readonly request: RequestWithUser |
|||
) {} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module'; |
|||
|
|||
import { Module } from '@nestjs/common'; |
|||
|
|||
import { CellMappingModule } from '../cell-mapping/cell-mapping.module'; |
|||
import { UploadModule } from '../upload/upload.module'; |
|||
import { K1ImportController } from './k1-import.controller'; |
|||
import { K1ImportService } from './k1-import.service'; |
|||
import { K1AggregationService } from './k1-aggregation.service'; |
|||
import { K1AllocationService } from './k1-allocation.service'; |
|||
import { K1ConfidenceService } from './k1-confidence.service'; |
|||
import { K1FieldMapperService } from './k1-field-mapper.service'; |
|||
import { AzureExtractor } from './extractors/azure-extractor'; |
|||
import { PdfParseExtractor } from './extractors/pdf-parse-extractor'; |
|||
import { TesseractExtractor } from './extractors/tesseract-extractor'; |
|||
|
|||
@Module({ |
|||
controllers: [K1ImportController], |
|||
exports: [K1ImportService], |
|||
imports: [CellMappingModule, PrismaModule, UploadModule], |
|||
providers: [ |
|||
AzureExtractor, |
|||
K1AggregationService, |
|||
K1AllocationService, |
|||
K1ConfidenceService, |
|||
K1FieldMapperService, |
|||
K1ImportService, |
|||
PdfParseExtractor, |
|||
TesseractExtractor |
|||
] |
|||
}) |
|||
export class K1ImportModule {} |
|||
@ -0,0 +1,30 @@ |
|||
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; |
|||
import { UploadService } from '../upload/upload.service'; |
|||
import { CellMappingService } from '../cell-mapping/cell-mapping.service'; |
|||
import { K1FieldMapperService } from './k1-field-mapper.service'; |
|||
import { K1ConfidenceService } from './k1-confidence.service'; |
|||
import { K1AllocationService } from './k1-allocation.service'; |
|||
import { K1AggregationService } from './k1-aggregation.service'; |
|||
import { PdfParseExtractor } from './extractors/pdf-parse-extractor'; |
|||
import { AzureExtractor } from './extractors/azure-extractor'; |
|||
import { TesseractExtractor } from './extractors/tesseract-extractor'; |
|||
|
|||
import { HttpException, Injectable } from '@nestjs/common'; |
|||
import { K1ImportStatus } from '@prisma/client'; |
|||
import { StatusCodes, getReasonPhrase } from 'http-status-codes'; |
|||
|
|||
@Injectable() |
|||
export class K1ImportService { |
|||
public constructor( |
|||
private readonly prismaService: PrismaService, |
|||
private readonly uploadService: UploadService, |
|||
private readonly cellMappingService: CellMappingService, |
|||
private readonly fieldMapperService: K1FieldMapperService, |
|||
private readonly confidenceService: K1ConfidenceService, |
|||
private readonly allocationService: K1AllocationService, |
|||
private readonly aggregationService: K1AggregationService, |
|||
private readonly pdfParseExtractor: PdfParseExtractor, |
|||
private readonly azureExtractor: AzureExtractor, |
|||
private readonly tesseractExtractor: TesseractExtractor |
|||
) {} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
import { AuthGuard } from '@ghostfolio/client/core/auth.guard'; |
|||
|
|||
import { Routes } from '@angular/router'; |
|||
|
|||
export const routes: Routes = [ |
|||
{ |
|||
canActivate: [AuthGuard], |
|||
loadComponent: () => |
|||
import('./cell-mapping-page.component').then( |
|||
(c) => c.CellMappingPageComponent |
|||
), |
|||
path: '', |
|||
title: 'Cell Mapping' |
|||
} |
|||
]; |
|||
@ -0,0 +1,15 @@ |
|||
import { AuthGuard } from '@ghostfolio/client/core/auth.guard'; |
|||
|
|||
import { Routes } from '@angular/router'; |
|||
|
|||
export const routes: Routes = [ |
|||
{ |
|||
canActivate: [AuthGuard], |
|||
loadComponent: () => |
|||
import('./k1-import-page.component').then( |
|||
(c) => c.K1ImportPageComponent |
|||
), |
|||
path: '', |
|||
title: 'K-1 Import' |
|||
} |
|||
]; |
|||
@ -0,0 +1,201 @@ |
|||
import type { |
|||
K1ExtractionResult, |
|||
K1ImportSessionSummary, |
|||
K1AggregationResult |
|||
} from '@ghostfolio/common/interfaces'; |
|||
import type { |
|||
ConfirmK1ImportDto, |
|||
VerifyK1ImportDto |
|||
} from '@ghostfolio/common/dtos'; |
|||
|
|||
import { HttpClient, HttpParams } from '@angular/common/http'; |
|||
import { Injectable } from '@angular/core'; |
|||
import { Observable } from 'rxjs'; |
|||
|
|||
@Injectable({ |
|||
providedIn: 'root' |
|||
}) |
|||
export class K1ImportDataService { |
|||
public constructor(private http: HttpClient) {} |
|||
|
|||
// ── K1 Import Endpoints ──────────────────────────────────────────
|
|||
|
|||
/** |
|||
* Upload a K-1 PDF and initiate extraction. |
|||
* POST /api/v1/k1-import/upload |
|||
*/ |
|||
public uploadK1(formData: FormData): Observable<any> { |
|||
return this.http.post('/api/v1/k1-import/upload', formData); |
|||
} |
|||
|
|||
/** |
|||
* Get the current state of an import session. |
|||
* GET /api/v1/k1-import/:id |
|||
*/ |
|||
public fetchImportSession(sessionId: string): Observable<any> { |
|||
return this.http.get(`/api/v1/k1-import/${sessionId}`); |
|||
} |
|||
|
|||
/** |
|||
* Submit user-verified extraction data. |
|||
* PUT /api/v1/k1-import/:id/verify |
|||
*/ |
|||
public verifyImportSession( |
|||
sessionId: string, |
|||
data: VerifyK1ImportDto |
|||
): Observable<any> { |
|||
return this.http.put(`/api/v1/k1-import/${sessionId}/verify`, data); |
|||
} |
|||
|
|||
/** |
|||
* Confirm verified data and trigger auto-creation of model objects. |
|||
* POST /api/v1/k1-import/:id/confirm |
|||
*/ |
|||
public confirmImportSession( |
|||
sessionId: string, |
|||
data: ConfirmK1ImportDto |
|||
): Observable<any> { |
|||
return this.http.post(`/api/v1/k1-import/${sessionId}/confirm`, data); |
|||
} |
|||
|
|||
/** |
|||
* Cancel an import session. |
|||
* POST /api/v1/k1-import/:id/cancel |
|||
*/ |
|||
public cancelImportSession(sessionId: string): Observable<any> { |
|||
return this.http.post(`/api/v1/k1-import/${sessionId}/cancel`, {}); |
|||
} |
|||
|
|||
/** |
|||
* List import sessions for a partnership. |
|||
* GET /api/v1/k1-import/history |
|||
*/ |
|||
public fetchImportHistory(params: { |
|||
partnershipId: string; |
|||
taxYear?: number; |
|||
}): Observable<K1ImportSessionSummary[]> { |
|||
let httpParams = new HttpParams().set( |
|||
'partnershipId', |
|||
params.partnershipId |
|||
); |
|||
|
|||
if (params.taxYear) { |
|||
httpParams = httpParams.set('taxYear', params.taxYear.toString()); |
|||
} |
|||
|
|||
return this.http.get<K1ImportSessionSummary[]>( |
|||
'/api/v1/k1-import/history', |
|||
{ params: httpParams } |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* Re-run extraction on a previously uploaded PDF. |
|||
* POST /api/v1/k1-import/:id/reprocess |
|||
*/ |
|||
public reprocessImportSession(sessionId: string): Observable<any> { |
|||
return this.http.post(`/api/v1/k1-import/${sessionId}/reprocess`, {}); |
|||
} |
|||
|
|||
// ── Cell Mapping Endpoints ───────────────────────────────────────
|
|||
|
|||
/** |
|||
* Get cell mappings for a partnership (with global defaults). |
|||
* GET /api/v1/cell-mapping |
|||
*/ |
|||
public fetchCellMappings(partnershipId?: string): Observable<any[]> { |
|||
let httpParams = new HttpParams(); |
|||
|
|||
if (partnershipId) { |
|||
httpParams = httpParams.set('partnershipId', partnershipId); |
|||
} |
|||
|
|||
return this.http.get<any[]>('/api/v1/cell-mapping', { |
|||
params: httpParams |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* Update or create cell mappings for a partnership. |
|||
* PUT /api/v1/cell-mapping |
|||
*/ |
|||
public updateCellMappings(data: { |
|||
partnershipId: string; |
|||
mappings: Array<{ |
|||
boxNumber: string; |
|||
label: string; |
|||
description?: string; |
|||
isCustom: boolean; |
|||
}>; |
|||
}): Observable<any[]> { |
|||
return this.http.put<any[]>('/api/v1/cell-mapping', data); |
|||
} |
|||
|
|||
/** |
|||
* Reset a partnership's cell mappings to IRS defaults. |
|||
* DELETE /api/v1/cell-mapping/reset |
|||
*/ |
|||
public resetCellMappings(partnershipId: string): Observable<void> { |
|||
const httpParams = new HttpParams().set('partnershipId', partnershipId); |
|||
|
|||
return this.http.delete<void>('/api/v1/cell-mapping/reset', { |
|||
params: httpParams |
|||
}); |
|||
} |
|||
|
|||
// ── Aggregation Rule Endpoints ───────────────────────────────────
|
|||
|
|||
/** |
|||
* Get aggregation rules for a partnership. |
|||
* GET /api/v1/cell-mapping/aggregation-rules |
|||
*/ |
|||
public fetchAggregationRules(partnershipId?: string): Observable<any[]> { |
|||
let httpParams = new HttpParams(); |
|||
|
|||
if (partnershipId) { |
|||
httpParams = httpParams.set('partnershipId', partnershipId); |
|||
} |
|||
|
|||
return this.http.get<any[]>('/api/v1/cell-mapping/aggregation-rules', { |
|||
params: httpParams |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* Create or update aggregation rules for a partnership. |
|||
* PUT /api/v1/cell-mapping/aggregation-rules |
|||
*/ |
|||
public updateAggregationRules(data: { |
|||
partnershipId: string; |
|||
rules: Array<{ |
|||
name: string; |
|||
operation: string; |
|||
sourceCells: string[]; |
|||
}>; |
|||
}): Observable<any[]> { |
|||
return this.http.put<any[]>( |
|||
'/api/v1/cell-mapping/aggregation-rules', |
|||
data |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* Compute aggregation values for a specific KDocument. |
|||
* GET /api/v1/cell-mapping/aggregation-rules/compute |
|||
*/ |
|||
public computeAggregations(params: { |
|||
kDocumentId: string; |
|||
partnershipId?: string; |
|||
}): Observable<K1AggregationResult[]> { |
|||
let httpParams = new HttpParams().set('kDocumentId', params.kDocumentId); |
|||
|
|||
if (params.partnershipId) { |
|||
httpParams = httpParams.set('partnershipId', params.partnershipId); |
|||
} |
|||
|
|||
return this.http.get<K1AggregationResult[]>( |
|||
'/api/v1/cell-mapping/aggregation-rules/compute', |
|||
{ params: httpParams } |
|||
); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue