From cd51347f3b4d959e443c0cc8997f66c9eb170634 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96ZT=C3=9CRK?= <76204082+iamsedatozturk@users.noreply.github.com> Date: Fri, 15 Aug 2025 14:26:03 +0300 Subject: [PATCH] Report App Service --- .../Reports/ReportAppService.cs | 127 ++++++++++++----- ....cs => 20250815110914_Reports.Designer.cs} | 2 +- ...9_Reports.cs => 20250815110914_Reports.cs} | 0 ui/src/proxy/reports/models.ts | 134 ++++++++++++++---- ui/src/services/reports.service.ts | 57 ++++---- 5 files changed, 233 insertions(+), 87 deletions(-) rename api/src/Kurs.Platform.EntityFrameworkCore/Migrations/{20250815090839_Reports.Designer.cs => 20250815110914_Reports.Designer.cs} (99%) rename api/src/Kurs.Platform.EntityFrameworkCore/Migrations/{20250815090839_Reports.cs => 20250815110914_Reports.cs} (100%) diff --git a/api/src/Kurs.Platform.Application/Reports/ReportAppService.cs b/api/src/Kurs.Platform.Application/Reports/ReportAppService.cs index 49e4eea8..8621e681 100644 --- a/api/src/Kurs.Platform.Application/Reports/ReportAppService.cs +++ b/api/src/Kurs.Platform.Application/Reports/ReportAppService.cs @@ -4,23 +4,24 @@ using System.Linq; using System.Text.Json; using System.Threading.Tasks; using Kurs.Platform.Entities; -using Kurs.Platform.Repositories; using Microsoft.AspNetCore.Authorization; using Volo.Abp.Application.Dtos; using Volo.Abp.Domain.Repositories; +using System.Linq.Dynamic.Core; +using Microsoft.EntityFrameworkCore; namespace Kurs.Platform.Reports; [Authorize()] public class ReportAppService : PlatformAppService, IReportAppService { - private readonly IReportTemplateRepository _reportTemplateRepository; - private readonly IGeneratedReportRepository _generatedReportRepository; + private readonly IRepository _reportTemplateRepository; + private readonly IRepository _generatedReportRepository; private readonly IRepository _reportParameterRepository; public ReportAppService( - IReportTemplateRepository reportTemplateRepository, - IGeneratedReportRepository generatedReportRepository, + IRepository reportTemplateRepository, + IRepository generatedReportRepository, IRepository reportParameterRepository) { _reportTemplateRepository = reportTemplateRepository; @@ -30,22 +31,50 @@ public class ReportAppService : PlatformAppService, IReportAppService public async Task> GetTemplatesAsync(GetReportTemplatesInput input) { - var totalCount = await _reportTemplateRepository.GetCountAsync(input.Filter, input.Category); - var templates = await _reportTemplateRepository.GetListAsync( - input.SkipCount, - input.MaxResultCount, - input.Sorting, - input.Filter, - input.Category); + // IQueryable başlat + var query = await _reportTemplateRepository.GetQueryableAsync(); + // Filtreleme + if (!string.IsNullOrWhiteSpace(input.Filter)) + { + query = query.Where(x => + x.Name.Contains(input.Filter) || + x.Description.Contains(input.Filter) || + x.Category.Contains(input.Filter) + ); + } + + if (!string.IsNullOrWhiteSpace(input.Category)) + { + query = query.Where(x => x.Category == input.Category); + } + + // Toplam kayıt sayısı + var totalCount = await AsyncExecuter.CountAsync(query); + + // Sıralama (ABP default olarak sorting null ise Id'ye göre sıralar) + query = query.OrderBy(input.Sorting ?? nameof(ReportTemplate.Name)); + + // Sayfalama + var templates = await AsyncExecuter.ToListAsync( + query + .Skip(input.SkipCount) + .Take(input.MaxResultCount) + ); + + // DTO dönüşümü var templateDtos = templates.Select(MapToReportTemplateDto).ToList(); - return new PagedResultDto(totalCount, templateDtos); + return new PagedResultDto( + totalCount, + templateDtos + ); } public async Task GetTemplateAsync(Guid id) { - var template = await _reportTemplateRepository.GetByIdWithParametersAsync(id); + var template = await _reportTemplateRepository.GetAsync(id); + return MapToReportTemplateDto(template); } @@ -56,9 +85,10 @@ public class ReportAppService : PlatformAppService, IReportAppService input.Name, input.Description, input.HtmlContent, - input.Category ?? "Genel"); - - template.Tags = JsonSerializer.Serialize(input.Tags); + input.Category ?? "Genel") + { + Tags = JsonSerializer.Serialize(input.Tags) + }; template = await _reportTemplateRepository.InsertAsync(template, true); @@ -71,10 +101,11 @@ public class ReportAppService : PlatformAppService, IReportAppService paramDto.Name, paramDto.Placeholder, (Entities.ReportParameterType)paramDto.Type, - paramDto.Required); - - parameter.DefaultValue = paramDto.DefaultValue; - parameter.Description = paramDto.Description; + paramDto.Required) + { + DefaultValue = paramDto.DefaultValue, + Description = paramDto.Description + }; await _reportParameterRepository.InsertAsync(parameter); } @@ -84,7 +115,7 @@ public class ReportAppService : PlatformAppService, IReportAppService public async Task UpdateTemplateAsync(Guid id, UpdateReportTemplateDto input) { - var template = await _reportTemplateRepository.GetByIdWithParametersAsync(id); + var template = await _reportTemplateRepository.GetAsync(id); template.Name = input.Name; template.Description = input.Description; @@ -128,14 +159,41 @@ public class ReportAppService : PlatformAppService, IReportAppService public async Task> GetGeneratedReportsAsync(GetGeneratedReportsInput input) { - var totalCount = await _generatedReportRepository.GetCountAsync(input.Filter, input.TemplateId); - var reports = await _generatedReportRepository.GetListAsync( - input.SkipCount, - input.MaxResultCount, - input.Sorting, - input.Filter, - input.TemplateId); + var query = await _generatedReportRepository.GetQueryableAsync(); + // Okuma senaryosu: tracking gerekmiyor + Template'ı eager load edelim + query = query.AsNoTracking() + .Include(x => x.Template); + + // Filtre + if (!string.IsNullOrWhiteSpace(input.Filter)) + { + query = query.Where(x => + x.TemplateName.Contains(input.Filter) || + x.GeneratedContent.Contains(input.Filter) + ); + } + + if (input.TemplateId.HasValue) + { + query = query.Where(x => x.TemplateId == input.TemplateId.Value); + } + + // Toplam kayıt + var totalCount = await AsyncExecuter.CountAsync(query); + + // Sıralama + if (!string.IsNullOrWhiteSpace(input.Sorting)) + query = query.OrderBy(input.Sorting); // ör. "generatedAt DESC" veya "templateName" + else + query = query.OrderByDescending(x => x.GeneratedAt); + + // Sayfalama + var reports = await AsyncExecuter.ToListAsync( + query.Skip(input.SkipCount).Take(input.MaxResultCount) + ); + + // DTO map var reportDtos = reports.Select(MapToGeneratedReportDto).ToList(); return new PagedResultDto(totalCount, reportDtos); @@ -143,13 +201,14 @@ public class ReportAppService : PlatformAppService, IReportAppService public async Task GetGeneratedReportAsync(Guid id) { - var report = await _generatedReportRepository.GetByIdWithTemplateAsync(id); + var report = await _generatedReportRepository.GetAsync(id); + return MapToGeneratedReportDto(report); } public async Task GenerateReportAsync(GenerateReportDto input) { - var template = await _reportTemplateRepository.GetByIdWithParametersAsync(input.TemplateId); + var template = await _reportTemplateRepository.GetAsync(input.TemplateId); if (template == null) { throw new ArgumentException("Template not found"); @@ -214,7 +273,7 @@ public class ReportAppService : PlatformAppService, IReportAppService try { dto.Tags = string.IsNullOrEmpty(template.Tags) - ? new List() + ? [] : JsonSerializer.Deserialize>(template.Tags); } catch @@ -233,7 +292,7 @@ public class ReportAppService : PlatformAppService, IReportAppService DefaultValue = p.DefaultValue, Required = p.Required, Description = p.Description - }).ToList() ?? new List(); + }).ToList() ?? []; return dto; } @@ -262,7 +321,7 @@ public class ReportAppService : PlatformAppService, IReportAppService } catch { - dto.Parameters = new Dictionary(); + dto.Parameters = []; } // Template mapping diff --git a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250815090839_Reports.Designer.cs b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250815110914_Reports.Designer.cs similarity index 99% rename from api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250815090839_Reports.Designer.cs rename to api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250815110914_Reports.Designer.cs index 3229c0a5..753c3a52 100644 --- a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250815090839_Reports.Designer.cs +++ b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250815110914_Reports.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace Kurs.Platform.Migrations { [DbContext(typeof(PlatformDbContext))] - [Migration("20250815090839_Reports")] + [Migration("20250815110914_Reports")] partial class Reports { /// diff --git a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250815090839_Reports.cs b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250815110914_Reports.cs similarity index 100% rename from api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250815090839_Reports.cs rename to api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250815110914_Reports.cs diff --git a/ui/src/proxy/reports/models.ts b/ui/src/proxy/reports/models.ts index e78aa1c2..4fe82a9a 100644 --- a/ui/src/proxy/reports/models.ts +++ b/ui/src/proxy/reports/models.ts @@ -1,32 +1,118 @@ -export interface ReportTemplateDto { - id: string; - name: string; - description: string; - htmlContent: string; - parameters: ReportParameterDto[]; - creationTime: Date; - lastModificationTime: Date; - category: string; - tags: string[]; +// reports.models.ts + +/** Enum, backend ile birebir (0..4) */ +export enum ReportParameterType { + Text = 0, + Number = 1, + Date = 2, + Email = 3, + Url = 4, } -export type ReportParameterType = "text" | "number" | "date" | "email" | "url"; +/** ---- API MODELLERİ (Raw JSON) ---- */ +/** Not: Tarihler API’den ISO string olarak gelir (Date değil) */ export interface ReportParameterDto { - id: string; - name: string; - placeholder: string; - type: ReportParameterType; - defaultValue?: string; - required: boolean; - description?: string; + id: string + reportTemplateId: string + name: string + placeholder?: string + type: ReportParameterType // enum (0..4) + defaultValue?: string + required: boolean + description?: string +} + +export interface ReportTemplateDto { + id: string + name: string + description?: string + htmlContent: string + category?: string + tags: string[] + parameters: ReportParameterDto[] + + // FullAuditedEntityDto alanları + creationTime: string // ISO + lastModificationTime?: string // ISO | undefined + creatorId?: string + lastModifierId?: string } export interface GeneratedReportDto { - id: string; - templateId: string; - templateName: string; - generatedContent: string; - parameters: Record; - generatedAt: Date; + id: string + templateId?: string | null + templateName: string + generatedContent: string + parameters: Record + generatedAt: string // ISO + + // FullAuditedEntityDto alanları + creationTime: string // ISO + lastModificationTime?: string // ISO | undefined + creatorId?: string + lastModifierId?: string + + template?: ReportTemplateDto // dolu gelebilir +} + +/** Create / Update input’ları */ +export interface CreateReportParameterDto { + name: string + placeholder?: string + type: ReportParameterType + defaultValue?: string + required: boolean + description?: string +} + +export interface UpdateReportParameterDto extends CreateReportParameterDto { + id?: string // opsiyonel +} + +export interface CreateReportTemplateDto { + name: string + description?: string + htmlContent: string + category?: string + tags?: string[] + parameters: CreateReportParameterDto[] +} + +export interface UpdateReportTemplateDto { + name: string + description?: string + htmlContent: string + category?: string + tags?: string[] + parameters: UpdateReportParameterDto[] +} + +/** Generate input’u */ +export interface GenerateReportDto { + templateId: string + parameters: Record +} + +/** List input’ları (query string) */ +export interface GetReportTemplatesInput { + skipCount?: number + maxResultCount?: number + sorting?: string + filter?: string + category?: string +} + +export interface GetGeneratedReportsInput { + skipCount?: number + maxResultCount?: number + sorting?: string + filter?: string + templateId?: string +} + +/** (Opsiyonel) Paged wrapper — projende zaten varsa bunu kullanmana gerek yok */ +export interface PagedResultDto { + items: T[] + totalCount: number } diff --git a/ui/src/services/reports.service.ts b/ui/src/services/reports.service.ts index 922fe7d2..ecd27770 100644 --- a/ui/src/services/reports.service.ts +++ b/ui/src/services/reports.service.ts @@ -1,5 +1,11 @@ -import { GeneratedReportDto, ReportTemplateDto } from '@/proxy/reports/models' -import apiService, { Config } from './api.service' +import { + ReportTemplateDto, + GeneratedReportDto, + CreateReportTemplateDto, + UpdateReportTemplateDto, + GenerateReportDto, // backend'deki GenerateReportDto (templateId + parameters) +} from '@/proxy/reports/models' +import apiService from './api.service' import { PagedAndSortedResultRequestDto, PagedResultDto } from '@/proxy' export interface ReportsData { @@ -7,20 +13,15 @@ export interface ReportsData { generatedReports: GeneratedReportDto[] } -export interface GenerateReportRequestDto { - templateId: string - parameters: Record -} - export class ReportsService { apiName = 'Default' - // Template operations + // TEMPLATES getTemplates = (input: PagedAndSortedResultRequestDto) => apiService.fetchData, PagedAndSortedResultRequestDto>( { method: 'GET', - url: '/api/app/reports/templates', + url: '/api/app/report/templates', // ✔ Swagger: GET /api/app/report/templates params: { sorting: input.sorting, skipCount: input.skipCount, @@ -34,27 +35,27 @@ export class ReportsService { apiService.fetchData( { method: 'GET', - url: `/api/app/reports/templates/${id}`, + url: `/api/app/report/${id}/template`, // ✔ Swagger: GET /api/app/report/{id}/template }, { apiName: this.apiName }, ) - createTemplate = (input: ReportTemplateDto) => - apiService.fetchData( + createTemplate = (input: CreateReportTemplateDto) => + apiService.fetchData( { method: 'POST', - url: '/api/app/reports/templates', + url: '/api/app/report/template', // ✔ Swagger: POST /api/app/report/template data: input, }, { apiName: this.apiName }, ) - updateTemplate = (id: string, input: ReportTemplateDto) => - apiService.fetchData( + updateTemplate = (id: string, input: UpdateReportTemplateDto) => + apiService.fetchData( { method: 'PUT', - url: `/api/app/reports/templates/${id}`, - data: input as any, + url: `/api/app/report/${id}/template`, // ✔ Swagger: PUT /api/app/report/{id}/template + data: input, }, { apiName: this.apiName }, ) @@ -63,17 +64,17 @@ export class ReportsService { apiService.fetchData( { method: 'DELETE', - url: `/api/app/reports/templates/${id}`, + url: `/api/app/report/${id}/template`, // ✔ Swagger: DELETE /api/app/report/{id}/template }, { apiName: this.apiName }, ) - // Generated Reports operations + // GENERATED REPORTS getGeneratedReports = (input: PagedAndSortedResultRequestDto) => apiService.fetchData, PagedAndSortedResultRequestDto>( { method: 'GET', - url: '/api/app/reports/generated', + url: '/api/app/report/generated-reports', // ✔ Swagger: GET /api/app/report/generated-reports params: { sorting: input.sorting, skipCount: input.skipCount, @@ -87,17 +88,17 @@ export class ReportsService { apiService.fetchData( { method: 'GET', - url: `/api/app/reports/generated/${id}`, + url: `/api/app/report/${id}/generated-report`, // ✔ Swagger: GET /api/app/report/{id}/generated-report }, { apiName: this.apiName }, ) - generateReport = (input: GeneratedReportDto) => - apiService.fetchData( + generateReport = (input: GenerateReportDto) => + apiService.fetchData( { method: 'POST', - url: '/api/app/reports/generate', - data: input as any, + url: '/api/app/report/generate-report', // ✔ Swagger: POST /api/app/report/generate-report + data: input, }, { apiName: this.apiName }, ) @@ -106,17 +107,17 @@ export class ReportsService { apiService.fetchData( { method: 'DELETE', - url: `/api/app/reports/generated/${id}`, + url: `/api/app/report/${id}/generated-report`, // ✔ Swagger: DELETE /api/app/report/{id}/generated-report }, { apiName: this.apiName }, ) - // Bulk operations + // BULK getAllData = () => apiService.fetchData( { method: 'GET', - url: '/api/app/reports/all', + url: '/api/app/report/data', // ✔ Swagger: GET /api/app/report/data }, { apiName: this.apiName }, )