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