diff --git a/api/Erp.Platform.sln b/api/Erp.Platform.sln index e8dba0e3..a9b23d0d 100644 --- a/api/Erp.Platform.sln +++ b/api/Erp.Platform.sln @@ -92,6 +92,18 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Erp.SqlQueryManager.Applica EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Erp.SqlQueryManager.EntityFrameworkCore", "modules\Erp.SqlQueryManager\Erp.SqlQueryManager.EntityFrameworkCore\Erp.SqlQueryManager.EntityFrameworkCore.csproj", "{1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Erp.Reports", "Erp.Reports", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Erp.Reports.Application", "modules\Erp.Reports\Erp.Reports.Application\Erp.Reports.Application.csproj", "{3E1C9BC6-90C2-20F1-567F-2BA043D81721}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Erp.Reports.Application.Contracts", "modules\Erp.Reports\Erp.Reports.Application.Contracts\Erp.Reports.Application.Contracts.csproj", "{6E1A7691-CD09-860C-C6B3-86FDFDD3E372}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Erp.Reports.Domain", "modules\Erp.Reports\Erp.Reports.Domain\Erp.Reports.Domain.csproj", "{0924ACE7-6A32-F683-9F4D-A15B07D14A5E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Erp.Reports.Domain.Shared", "modules\Erp.Reports\Erp.Reports.Domain.Shared\Erp.Reports.Domain.Shared.csproj", "{E65E10EE-41CC-B0E2-1004-E40D0CD26011}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Erp.Reports.EntityFrameworkCore", "modules\Erp.Reports\Erp.Reports.EntityFrameworkCore\Erp.Reports.EntityFrameworkCore.csproj", "{02E91CDA-E54C-9D5C-76AB-B07BE6D3E7FF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -230,6 +242,26 @@ Global {1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}.Debug|Any CPU.Build.0 = Debug|Any CPU {1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}.Release|Any CPU.ActiveCfg = Release|Any CPU {1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}.Release|Any CPU.Build.0 = Release|Any CPU + {3E1C9BC6-90C2-20F1-567F-2BA043D81721}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3E1C9BC6-90C2-20F1-567F-2BA043D81721}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3E1C9BC6-90C2-20F1-567F-2BA043D81721}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3E1C9BC6-90C2-20F1-567F-2BA043D81721}.Release|Any CPU.Build.0 = Release|Any CPU + {6E1A7691-CD09-860C-C6B3-86FDFDD3E372}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6E1A7691-CD09-860C-C6B3-86FDFDD3E372}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6E1A7691-CD09-860C-C6B3-86FDFDD3E372}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6E1A7691-CD09-860C-C6B3-86FDFDD3E372}.Release|Any CPU.Build.0 = Release|Any CPU + {0924ACE7-6A32-F683-9F4D-A15B07D14A5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0924ACE7-6A32-F683-9F4D-A15B07D14A5E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0924ACE7-6A32-F683-9F4D-A15B07D14A5E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0924ACE7-6A32-F683-9F4D-A15B07D14A5E}.Release|Any CPU.Build.0 = Release|Any CPU + {E65E10EE-41CC-B0E2-1004-E40D0CD26011}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E65E10EE-41CC-B0E2-1004-E40D0CD26011}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E65E10EE-41CC-B0E2-1004-E40D0CD26011}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E65E10EE-41CC-B0E2-1004-E40D0CD26011}.Release|Any CPU.Build.0 = Release|Any CPU + {02E91CDA-E54C-9D5C-76AB-B07BE6D3E7FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {02E91CDA-E54C-9D5C-76AB-B07BE6D3E7FF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {02E91CDA-E54C-9D5C-76AB-B07BE6D3E7FF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {02E91CDA-E54C-9D5C-76AB-B07BE6D3E7FF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -274,6 +306,12 @@ Global {B45A3E8B-286B-4A74-9602-FC192ACEE8C4} = {2889482E-64CA-4A25-91D8-5B963D83681B} {ED9C639A-A706-4ECB-9638-A15B3681BDEC} = {2889482E-64CA-4A25-91D8-5B963D83681B} {1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6} = {2889482E-64CA-4A25-91D8-5B963D83681B} + {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {03E1C8DA-035E-4882-AF81-F392139FCF38} + {3E1C9BC6-90C2-20F1-567F-2BA043D81721} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {6E1A7691-CD09-860C-C6B3-86FDFDD3E372} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {0924ACE7-6A32-F683-9F4D-A15B07D14A5E} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {E65E10EE-41CC-B0E2-1004-E40D0CD26011} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {02E91CDA-E54C-9D5C-76AB-B07BE6D3E7FF} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {28315BFD-90E7-4E14-A2EA-F3D23AF4126F} diff --git a/api/modules/Erp.Reports/Erp.Reports.Application.Contracts/Erp.Reports.Application.Contracts.csproj b/api/modules/Erp.Reports/Erp.Reports.Application.Contracts/Erp.Reports.Application.Contracts.csproj new file mode 100644 index 00000000..7f8c933e --- /dev/null +++ b/api/modules/Erp.Reports/Erp.Reports.Application.Contracts/Erp.Reports.Application.Contracts.csproj @@ -0,0 +1,18 @@ + + + + net9.0 + enable + enable + Erp.Reports + + + + + + + + + + + diff --git a/api/modules/Erp.Reports/Erp.Reports.Application.Contracts/ErpReportsApplicationContractsModule.cs b/api/modules/Erp.Reports/Erp.Reports.Application.Contracts/ErpReportsApplicationContractsModule.cs new file mode 100644 index 00000000..ee72d765 --- /dev/null +++ b/api/modules/Erp.Reports/Erp.Reports.Application.Contracts/ErpReportsApplicationContractsModule.cs @@ -0,0 +1,12 @@ +using Volo.Abp.Application; +using Volo.Abp.Modularity; + +namespace Erp.Reports; + +[DependsOn( + typeof(ErpReportsDomainSharedModule), + typeof(AbpDddApplicationContractsModule) +)] +public class ErpReportsApplicationContractsModule : AbpModule +{ +} diff --git a/api/modules/Erp.Reports/Erp.Reports.Application.Contracts/ReportDefinitions/CreateReportDefinitionDto.cs b/api/modules/Erp.Reports/Erp.Reports.Application.Contracts/ReportDefinitions/CreateReportDefinitionDto.cs new file mode 100644 index 00000000..96bc1e89 --- /dev/null +++ b/api/modules/Erp.Reports/Erp.Reports.Application.Contracts/ReportDefinitions/CreateReportDefinitionDto.cs @@ -0,0 +1,17 @@ +using System.ComponentModel.DataAnnotations; + +namespace Erp.Reports.ReportDefinitions; + +public class CreateReportDefinitionDto +{ + [Required] + [StringLength(256)] + public string Name { get; set; } + + [Required] + [StringLength(512)] + public string DisplayName { get; set; } + + [Required] + public byte[] Content { get; set; } +} diff --git a/api/modules/Erp.Reports/Erp.Reports.Application.Contracts/ReportDefinitions/IReportDefinitionAppService.cs b/api/modules/Erp.Reports/Erp.Reports.Application.Contracts/ReportDefinitions/IReportDefinitionAppService.cs new file mode 100644 index 00000000..51134e24 --- /dev/null +++ b/api/modules/Erp.Reports/Erp.Reports.Application.Contracts/ReportDefinitions/IReportDefinitionAppService.cs @@ -0,0 +1,13 @@ +using Volo.Abp.Application.Services; + +namespace Erp.Reports.ReportDefinitions; + +public interface IReportDefinitionAppService : IApplicationService +{ + Task GetAsync(Guid id); + Task GetByNameAsync(string name); + Task GetContentAsync(string name); + Task CreateAsync(CreateReportDefinitionDto input); + Task UpdateAsync(Guid id, UpdateReportDefinitionDto input); + Task DeleteAsync(Guid id); +} diff --git a/api/modules/Erp.Reports/Erp.Reports.Application.Contracts/ReportDefinitions/ReportDefinitionDto.cs b/api/modules/Erp.Reports/Erp.Reports.Application.Contracts/ReportDefinitions/ReportDefinitionDto.cs new file mode 100644 index 00000000..6ad8a60b --- /dev/null +++ b/api/modules/Erp.Reports/Erp.Reports.Application.Contracts/ReportDefinitions/ReportDefinitionDto.cs @@ -0,0 +1,9 @@ +using Volo.Abp.Application.Dtos; + +namespace Erp.Reports.ReportDefinitions; + +public class ReportDefinitionDto : FullAuditedEntityDto +{ + public string Name { get; set; } + public string DisplayName { get; set; } +} diff --git a/api/modules/Erp.Reports/Erp.Reports.Application.Contracts/ReportDefinitions/UpdateReportDefinitionDto.cs b/api/modules/Erp.Reports/Erp.Reports.Application.Contracts/ReportDefinitions/UpdateReportDefinitionDto.cs new file mode 100644 index 00000000..73d71594 --- /dev/null +++ b/api/modules/Erp.Reports/Erp.Reports.Application.Contracts/ReportDefinitions/UpdateReportDefinitionDto.cs @@ -0,0 +1,11 @@ +using System.ComponentModel.DataAnnotations; + +namespace Erp.Reports.ReportDefinitions; + +public class UpdateReportDefinitionDto +{ + [StringLength(512)] + public string DisplayName { get; set; } + + public byte[] Content { get; set; } +} diff --git a/api/modules/Erp.Reports/Erp.Reports.Application/Erp.Reports.Application.csproj b/api/modules/Erp.Reports/Erp.Reports.Application/Erp.Reports.Application.csproj new file mode 100644 index 00000000..9002073a --- /dev/null +++ b/api/modules/Erp.Reports/Erp.Reports.Application/Erp.Reports.Application.csproj @@ -0,0 +1,20 @@ + + + + net9.0 + enable + enable + Erp.Reports + + + + + + + + + + + + + diff --git a/api/modules/Erp.Reports/Erp.Reports.Application/ErpReportsApplicationAutoMapperProfile.cs b/api/modules/Erp.Reports/Erp.Reports.Application/ErpReportsApplicationAutoMapperProfile.cs new file mode 100644 index 00000000..42530c50 --- /dev/null +++ b/api/modules/Erp.Reports/Erp.Reports.Application/ErpReportsApplicationAutoMapperProfile.cs @@ -0,0 +1,12 @@ +using AutoMapper; +using Erp.Reports.ReportDefinitions; + +namespace Erp.Reports; + +public class ErpReportsApplicationAutoMapperProfile : Profile +{ + public ErpReportsApplicationAutoMapperProfile() + { + CreateMap(); + } +} diff --git a/api/modules/Erp.Reports/Erp.Reports.Application/ErpReportsApplicationModule.cs b/api/modules/Erp.Reports/Erp.Reports.Application/ErpReportsApplicationModule.cs new file mode 100644 index 00000000..c2e49c3f --- /dev/null +++ b/api/modules/Erp.Reports/Erp.Reports.Application/ErpReportsApplicationModule.cs @@ -0,0 +1,22 @@ +using Volo.Abp.Application; +using Volo.Abp.AutoMapper; +using Volo.Abp.Modularity; + +namespace Erp.Reports; + +[DependsOn( + typeof(ErpReportsDomainModule), + typeof(ErpReportsApplicationContractsModule), + typeof(AbpDddApplicationModule), + typeof(AbpAutoMapperModule) +)] +public class ErpReportsApplicationModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.AddMaps(); + }); + } +} diff --git a/api/modules/Erp.Reports/Erp.Reports.Application/ReportDefinitionAppService.cs b/api/modules/Erp.Reports/Erp.Reports.Application/ReportDefinitionAppService.cs new file mode 100644 index 00000000..3eb4b023 --- /dev/null +++ b/api/modules/Erp.Reports/Erp.Reports.Application/ReportDefinitionAppService.cs @@ -0,0 +1,76 @@ +using Volo.Abp.Application.Services; +using Volo.Abp.Domain.Repositories; + +namespace Erp.Reports.ReportDefinitions; + +public class ReportDefinitionAppService : ApplicationService, IReportDefinitionAppService +{ + private readonly IRepository _reportDefinitionRepository; + + public ReportDefinitionAppService(IRepository reportDefinitionRepository) + { + _reportDefinitionRepository = reportDefinitionRepository; + } + + public async Task GetAsync(Guid id) + { + var report = await _reportDefinitionRepository.GetAsync(id); + return ObjectMapper.Map(report); + } + + public async Task GetByNameAsync(string name) + { + var report = await _reportDefinitionRepository.FirstOrDefaultAsync(x => x.Name == name); + if (report == null) + { + throw new Volo.Abp.UserFriendlyException($"Report '{name}' not found"); + } + return ObjectMapper.Map(report); + } + + public async Task GetContentAsync(string name) + { + var report = await _reportDefinitionRepository.FirstOrDefaultAsync(x => x.Name == name); + if (report == null) + { + throw new Volo.Abp.UserFriendlyException($"Report '{name}' not found"); + } + return report.Content; + } + + public async Task CreateAsync(CreateReportDefinitionDto input) + { + var report = new ReportDefinition( + GuidGenerator.Create(), + input.Name, + input.DisplayName, + input.Content + ); + + await _reportDefinitionRepository.InsertAsync(report); + return ObjectMapper.Map(report); + } + + public async Task UpdateAsync(Guid id, UpdateReportDefinitionDto input) + { + var report = await _reportDefinitionRepository.GetAsync(id); + + if (!string.IsNullOrEmpty(input.DisplayName)) + { + report.DisplayName = input.DisplayName; + } + + if (input.Content != null && input.Content.Length > 0) + { + report.UpdateContent(input.Content); + } + + await _reportDefinitionRepository.UpdateAsync(report); + return ObjectMapper.Map(report); + } + + public async Task DeleteAsync(Guid id) + { + await _reportDefinitionRepository.DeleteAsync(id); + } +} diff --git a/api/modules/Erp.Reports/Erp.Reports.Domain.Shared/Erp.Reports.Domain.Shared.csproj b/api/modules/Erp.Reports/Erp.Reports.Domain.Shared/Erp.Reports.Domain.Shared.csproj new file mode 100644 index 00000000..da280b1c --- /dev/null +++ b/api/modules/Erp.Reports/Erp.Reports.Domain.Shared/Erp.Reports.Domain.Shared.csproj @@ -0,0 +1,15 @@ + + + + net9.0 + enable + enable + Erp.Reports + + + + + + + + diff --git a/api/modules/Erp.Reports/Erp.Reports.Domain.Shared/ErpReportsDomainSharedModule.cs b/api/modules/Erp.Reports/Erp.Reports.Domain.Shared/ErpReportsDomainSharedModule.cs new file mode 100644 index 00000000..bbfa9a6e --- /dev/null +++ b/api/modules/Erp.Reports/Erp.Reports.Domain.Shared/ErpReportsDomainSharedModule.cs @@ -0,0 +1,15 @@ +using Volo.Abp.Modularity; +using Volo.Abp.VirtualFileSystem; + +namespace Erp.Reports; + +public class ErpReportsDomainSharedModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.FileSets.AddEmbedded(); + }); + } +} diff --git a/api/modules/Erp.Reports/Erp.Reports.Domain.Shared/ReportsConsts.cs b/api/modules/Erp.Reports/Erp.Reports.Domain.Shared/ReportsConsts.cs new file mode 100644 index 00000000..f1ccb381 --- /dev/null +++ b/api/modules/Erp.Reports/Erp.Reports.Domain.Shared/ReportsConsts.cs @@ -0,0 +1,7 @@ +namespace Erp.Reports; + +public static class ReportsConsts +{ + public const string DbTablePrefix = "Erp"; + public const string DbSchema = null; +} diff --git a/api/modules/Erp.Reports/Erp.Reports.Domain/Erp.Reports.Domain.csproj b/api/modules/Erp.Reports/Erp.Reports.Domain/Erp.Reports.Domain.csproj new file mode 100644 index 00000000..5e0b875c --- /dev/null +++ b/api/modules/Erp.Reports/Erp.Reports.Domain/Erp.Reports.Domain.csproj @@ -0,0 +1,18 @@ + + + + net9.0 + enable + enable + Erp.Reports + + + + + + + + + + + diff --git a/api/modules/Erp.Reports/Erp.Reports.Domain/ErpReportsDbProperties.cs b/api/modules/Erp.Reports/Erp.Reports.Domain/ErpReportsDbProperties.cs new file mode 100644 index 00000000..d6b7d72e --- /dev/null +++ b/api/modules/Erp.Reports/Erp.Reports.Domain/ErpReportsDbProperties.cs @@ -0,0 +1,18 @@ +namespace Erp.Reports.Domain; + +public static class Prefix +{ + public static string MenuPrefix { get; set; } = "Plat"; + public static string HostPrefix { get; set; } = "H"; + public static string? DbSchema { get; set; } = null; + + public const string ConnectionStringName = "Reports"; +} + +public static class TablePrefix +{ + public static string ByName(string tableName) + { + return $"{Prefix.MenuPrefix}_{Prefix.HostPrefix}_{tableName}"; + } +} diff --git a/api/modules/Erp.Reports/Erp.Reports.Domain/ErpReportsDomainModule.cs b/api/modules/Erp.Reports/Erp.Reports.Domain/ErpReportsDomainModule.cs new file mode 100644 index 00000000..0d0f349a --- /dev/null +++ b/api/modules/Erp.Reports/Erp.Reports.Domain/ErpReportsDomainModule.cs @@ -0,0 +1,16 @@ +using Volo.Abp.Domain; +using Volo.Abp.Modularity; + +namespace Erp.Reports; + +[DependsOn( + typeof(AbpDddDomainModule), + typeof(ErpReportsDomainSharedModule) +)] +public class ErpReportsDomainModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + // Domain services configuration can be added here + } +} diff --git a/api/modules/Erp.Reports/Erp.Reports.Domain/ReportDefinitions/ReportDefinition.cs b/api/modules/Erp.Reports/Erp.Reports.Domain/ReportDefinitions/ReportDefinition.cs new file mode 100644 index 00000000..9378362b --- /dev/null +++ b/api/modules/Erp.Reports/Erp.Reports.Domain/ReportDefinitions/ReportDefinition.cs @@ -0,0 +1,23 @@ +using Volo.Abp.Domain.Entities.Auditing; + +namespace Erp.Reports.ReportDefinitions; + +public class ReportDefinition : FullAuditedEntity +{ + public string Name { get; set; } + public string DisplayName { get; set; } + public byte[] Content { get; set; } + + public ReportDefinition(Guid id, string name, string displayName, byte[] content) + : base(id) + { + Name = name; + DisplayName = displayName; + Content = content; + } + + public void UpdateContent(byte[] content) + { + Content = content; + } +} diff --git a/api/modules/Erp.Reports/Erp.Reports.EntityFrameworkCore/Erp.Reports.EntityFrameworkCore.csproj b/api/modules/Erp.Reports/Erp.Reports.EntityFrameworkCore/Erp.Reports.EntityFrameworkCore.csproj new file mode 100644 index 00000000..4b56e317 --- /dev/null +++ b/api/modules/Erp.Reports/Erp.Reports.EntityFrameworkCore/Erp.Reports.EntityFrameworkCore.csproj @@ -0,0 +1,24 @@ + + + + net9.0 + enable + enable + Erp.Reports.EntityFrameworkCore + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + diff --git a/api/modules/Erp.Reports/Erp.Reports.EntityFrameworkCore/ErpReportsDbContext.cs b/api/modules/Erp.Reports/Erp.Reports.EntityFrameworkCore/ErpReportsDbContext.cs new file mode 100644 index 00000000..f8455934 --- /dev/null +++ b/api/modules/Erp.Reports/Erp.Reports.EntityFrameworkCore/ErpReportsDbContext.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Volo.Abp.Data; +using Volo.Abp.EntityFrameworkCore; +using Erp.Reports.ReportDefinitions; +using Erp.Reports.Domain; + +namespace Erp.Reports.EntityFrameworkCore; + +[ConnectionStringName(Prefix.ConnectionStringName)] +public class ErpReportsDbContext : AbpDbContext +{ + public DbSet ReportDefinitions { get; set; } + + public ErpReportsDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + builder.ConfigureReports(); + } +} diff --git a/api/modules/Erp.Reports/Erp.Reports.EntityFrameworkCore/ErpReportsDbContextFactory.cs b/api/modules/Erp.Reports/Erp.Reports.EntityFrameworkCore/ErpReportsDbContextFactory.cs new file mode 100644 index 00000000..8d110fae --- /dev/null +++ b/api/modules/Erp.Reports/Erp.Reports.EntityFrameworkCore/ErpReportsDbContextFactory.cs @@ -0,0 +1,27 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; +using Microsoft.Extensions.Configuration; + +namespace Erp.Reports.EntityFrameworkCore; + +public class ErpReportsDbContextFactory : IDesignTimeDbContextFactory +{ + public ErpReportsDbContext CreateDbContext(string[] args) + { + var configuration = BuildConfiguration(); + + var builder = new DbContextOptionsBuilder() + .UseSqlServer(configuration.GetConnectionString("SqlServer")); + + return new ErpReportsDbContext(builder.Options); + } + + private static IConfigurationRoot BuildConfiguration() + { + var builder = new ConfigurationBuilder() + .SetBasePath(Path.Combine(Directory.GetCurrentDirectory(), "../Erp.Reports.HttpApi.Host/")) + .AddJsonFile("appsettings.json", optional: false); + + return builder.Build(); + } +} diff --git a/api/modules/Erp.Reports/Erp.Reports.EntityFrameworkCore/ErpReportsDbContextModelCreatingExtensions.cs b/api/modules/Erp.Reports/Erp.Reports.EntityFrameworkCore/ErpReportsDbContextModelCreatingExtensions.cs new file mode 100644 index 00000000..2338aeaa --- /dev/null +++ b/api/modules/Erp.Reports/Erp.Reports.EntityFrameworkCore/ErpReportsDbContextModelCreatingExtensions.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore; +using Volo.Abp; +using Volo.Abp.EntityFrameworkCore.Modeling; +using Erp.Reports.ReportDefinitions; +using Erp.Reports.Domain; + +namespace Erp.Reports.EntityFrameworkCore; + +public static class ErpReportsDbContextModelCreatingExtensions +{ + public static void ConfigureReports( + this ModelBuilder builder) + { + Check.NotNull(builder, nameof(builder)); + + builder.Entity(b => + { + b.ToTable(TablePrefix.ByName(nameof(ReportDefinition)), Prefix.DbSchema); + b.ConfigureByConvention(); + + b.Property(x => x.Name).IsRequired().HasMaxLength(256); + b.Property(x => x.DisplayName).IsRequired().HasMaxLength(512); + b.Property(x => x.Content).IsRequired(); + + b.HasIndex(x => x.Name); + }); + } +} diff --git a/api/modules/Erp.Reports/Erp.Reports.EntityFrameworkCore/ErpReportsEntityFrameworkCoreModule.cs b/api/modules/Erp.Reports/Erp.Reports.EntityFrameworkCore/ErpReportsEntityFrameworkCoreModule.cs new file mode 100644 index 00000000..6b6189ee --- /dev/null +++ b/api/modules/Erp.Reports/Erp.Reports.EntityFrameworkCore/ErpReportsEntityFrameworkCoreModule.cs @@ -0,0 +1,26 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore.SqlServer; +using Volo.Abp.Modularity; + +namespace Erp.Reports.EntityFrameworkCore; + +[DependsOn( + typeof(ErpReportsDomainModule), + typeof(AbpEntityFrameworkCoreSqlServerModule) +)] +public class ErpReportsEntityFrameworkCoreModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddAbpDbContext(options => + { + options.AddDefaultRepositories(includeAllEntities: true); + }); + + Configure(options => + { + options.UseSqlServer(); + }); + } +} diff --git a/api/src/Erp.Platform.Application.Contracts/Erp.Platform.Application.Contracts.csproj b/api/src/Erp.Platform.Application.Contracts/Erp.Platform.Application.Contracts.csproj index 67f11d41..421309b8 100644 --- a/api/src/Erp.Platform.Application.Contracts/Erp.Platform.Application.Contracts.csproj +++ b/api/src/Erp.Platform.Application.Contracts/Erp.Platform.Application.Contracts.csproj @@ -11,6 +11,7 @@ + diff --git a/api/src/Erp.Platform.Application.Contracts/PlatformApplicationContractsModule.cs b/api/src/Erp.Platform.Application.Contracts/PlatformApplicationContractsModule.cs index 7700a58e..83caab29 100644 --- a/api/src/Erp.Platform.Application.Contracts/PlatformApplicationContractsModule.cs +++ b/api/src/Erp.Platform.Application.Contracts/PlatformApplicationContractsModule.cs @@ -1,5 +1,6 @@ using Erp.Languages; using Erp.Notifications.Application; +using Erp.Reports; using Erp.Settings; using Volo.Abp.Account; using Volo.Abp.FeatureManagement; @@ -21,7 +22,8 @@ namespace Erp.Platform; typeof(AbpObjectExtendingModule), typeof(LanguagesApplicationContractsModule), typeof(SettingsApplicationContractsModule), - typeof(NotificationApplicationContractsModule) + typeof(NotificationApplicationContractsModule), + typeof(ErpReportsApplicationContractsModule) )] public class PlatformApplicationContractsModule : AbpModule { diff --git a/api/src/Erp.Platform.Application/Erp.Platform.Application.csproj b/api/src/Erp.Platform.Application/Erp.Platform.Application.csproj index c5557c5b..197aadae 100644 --- a/api/src/Erp.Platform.Application/Erp.Platform.Application.csproj +++ b/api/src/Erp.Platform.Application/Erp.Platform.Application.csproj @@ -15,6 +15,7 @@ + diff --git a/api/src/Erp.Platform.Application/PlatformApplicationModule.cs b/api/src/Erp.Platform.Application/PlatformApplicationModule.cs index 5121c36f..224c6d7d 100644 --- a/api/src/Erp.Platform.Application/PlatformApplicationModule.cs +++ b/api/src/Erp.Platform.Application/PlatformApplicationModule.cs @@ -1,5 +1,6 @@ using Erp.Languages; using Erp.Notifications.Application; +using Erp.Reports; using Erp.Settings; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Account; @@ -23,7 +24,8 @@ namespace Erp.Platform; typeof(AbpFeatureManagementApplicationModule), typeof(LanguagesApplicationModule), typeof(SettingsApplicationModule), - typeof(NotificationApplicationModule) + typeof(NotificationApplicationModule), + typeof(ErpReportsApplicationModule) )] public class PlatformApplicationModule : AbpModule { diff --git a/api/src/Erp.Platform.DbMigrator/Seeds/MenusData.json b/api/src/Erp.Platform.DbMigrator/Seeds/MenusData.json index cd247dfe..75ea0af3 100644 --- a/api/src/Erp.Platform.DbMigrator/Seeds/MenusData.json +++ b/api/src/Erp.Platform.DbMigrator/Seeds/MenusData.json @@ -454,6 +454,13 @@ "componentPath": "@/views/coordinator/ExamInterface/PDFTestInterface", "routeType": "protected", "authority": ["App.Coordinator.Tests"] + }, + { + "key": "admin.devexpressReportView", + "path": "/admin/reports/reportviewer", + "componentPath": "@/views/report/DevexpressReportViewer", + "routeType": "protected", + "authority": [] } ], "Menus": [ diff --git a/api/src/Erp.Platform.Domain.Shared/Erp.Platform.Domain.Shared.csproj b/api/src/Erp.Platform.Domain.Shared/Erp.Platform.Domain.Shared.csproj index 9a635bdc..b7868197 100644 --- a/api/src/Erp.Platform.Domain.Shared/Erp.Platform.Domain.Shared.csproj +++ b/api/src/Erp.Platform.Domain.Shared/Erp.Platform.Domain.Shared.csproj @@ -30,6 +30,7 @@ + diff --git a/api/src/Erp.Platform.Domain.Shared/PlatformDomainSharedModule.cs b/api/src/Erp.Platform.Domain.Shared/PlatformDomainSharedModule.cs index 01d4db7a..ca0d3cd4 100644 --- a/api/src/Erp.Platform.Domain.Shared/PlatformDomainSharedModule.cs +++ b/api/src/Erp.Platform.Domain.Shared/PlatformDomainSharedModule.cs @@ -1,5 +1,6 @@ using Erp.Languages; using Erp.Notifications.Domain; +using Erp.Reports; using Erp.Platform.Localization; using Erp.Settings; using Volo.Abp.AuditLogging; @@ -27,7 +28,8 @@ namespace Erp.Platform; typeof(AbpTenantManagementDomainSharedModule), typeof(LanguagesDomainSharedModule), typeof(SettingsDomainSharedModule), - typeof(NotificationDomainSharedModule) + typeof(NotificationDomainSharedModule), + typeof(ErpReportsDomainSharedModule) )] public class PlatformDomainSharedModule : AbpModule { diff --git a/api/src/Erp.Platform.Domain/Erp.Platform.Domain.csproj b/api/src/Erp.Platform.Domain/Erp.Platform.Domain.csproj index c12cf031..a53f8293 100644 --- a/api/src/Erp.Platform.Domain/Erp.Platform.Domain.csproj +++ b/api/src/Erp.Platform.Domain/Erp.Platform.Domain.csproj @@ -13,6 +13,7 @@ + diff --git a/api/src/Erp.Platform.Domain/PlatformDomainModule.cs b/api/src/Erp.Platform.Domain/PlatformDomainModule.cs index 1218f1fd..68d200a7 100644 --- a/api/src/Erp.Platform.Domain/PlatformDomainModule.cs +++ b/api/src/Erp.Platform.Domain/PlatformDomainModule.cs @@ -17,6 +17,7 @@ using Volo.Abp.TenantManagement; using Volo.Abp.BlobStoring; using Volo.Abp.BlobStoring.FileSystem; using Volo.Abp.Timing; +using Erp.Reports; namespace Erp.Platform; @@ -35,6 +36,7 @@ namespace Erp.Platform; typeof(SettingsDomainModule), typeof(ErpMailQueueModule), typeof(NotificationDomainModule), + typeof(ErpReportsDomainModule), typeof(AbpBlobStoringModule), typeof(AbpBlobStoringFileSystemModule) )] diff --git a/api/src/Erp.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs b/api/src/Erp.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs index 32080161..68ee5350 100644 --- a/api/src/Erp.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs +++ b/api/src/Erp.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs @@ -27,6 +27,7 @@ using static Erp.Platform.PlatformConsts; using static Erp.Settings.SettingsConsts; using Erp.Platform.Enums; using Erp.SqlQueryManager.EntityFrameworkCore; +using Erp.Reports.EntityFrameworkCore; namespace Erp.Platform.EntityFrameworkCore; @@ -351,6 +352,7 @@ public class PlatformDbContext : builder.ConfigureMailQueue(); builder.ConfigureNotification(); builder.ConfigureSqlQueryManager(); + builder.ConfigureReports(); //Saas builder.Entity(b => diff --git a/api/src/Erp.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformEntityFrameworkCoreModule.cs b/api/src/Erp.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformEntityFrameworkCoreModule.cs index 725e2d7c..15925cd9 100644 --- a/api/src/Erp.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformEntityFrameworkCoreModule.cs +++ b/api/src/Erp.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformEntityFrameworkCoreModule.cs @@ -1,6 +1,7 @@ using System; using Erp.Languages.EntityFrameworkCore; using Erp.Notifications.EntityFrameworkCore; +using Erp.Reports.EntityFrameworkCore; using Erp.Settings.EntityFrameworkCore; using Erp.SqlQueryManager.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; @@ -36,7 +37,8 @@ namespace Erp.Platform.EntityFrameworkCore; typeof(LanguagesEntityFrameworkCoreModule), typeof(SettingsEntityFrameworkCoreModule), typeof(NotificationEntityFrameworkCoreModule), - typeof(SqlQueryManagerEntityFrameworkCoreModule) + typeof(SqlQueryManagerEntityFrameworkCoreModule), + typeof(ErpReportsEntityFrameworkCoreModule) )] public class PlatformEntityFrameworkCoreModule : AbpModule { diff --git a/api/src/Erp.Platform.EntityFrameworkCore/Erp.Platform.EntityFrameworkCore.csproj b/api/src/Erp.Platform.EntityFrameworkCore/Erp.Platform.EntityFrameworkCore.csproj index 36f011bf..7676d08f 100644 --- a/api/src/Erp.Platform.EntityFrameworkCore/Erp.Platform.EntityFrameworkCore.csproj +++ b/api/src/Erp.Platform.EntityFrameworkCore/Erp.Platform.EntityFrameworkCore.csproj @@ -27,6 +27,7 @@ + diff --git a/api/src/Erp.Platform.EntityFrameworkCore/Migrations/20251214195617_Initial.Designer.cs b/api/src/Erp.Platform.EntityFrameworkCore/Migrations/20260106110136_Initial.Designer.cs similarity index 99% rename from api/src/Erp.Platform.EntityFrameworkCore/Migrations/20251214195617_Initial.Designer.cs rename to api/src/Erp.Platform.EntityFrameworkCore/Migrations/20260106110136_Initial.Designer.cs index 46f9209f..6beac0df 100644 --- a/api/src/Erp.Platform.EntityFrameworkCore/Migrations/20251214195617_Initial.Designer.cs +++ b/api/src/Erp.Platform.EntityFrameworkCore/Migrations/20260106110136_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace Erp.Platform.Migrations { [DbContext(typeof(PlatformDbContext))] - [Migration("20251214195617_Initial")] + [Migration("20260106110136_Initial")] partial class Initial { /// @@ -17773,6 +17773,63 @@ namespace Erp.Platform.Migrations b.ToTable("Sas_H_ForumTopic", (string)null); }); + modelBuilder.Entity("Erp.Reports.ReportDefinitions.ReportDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Content") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("CreationTime") + .HasColumnType("datetime2") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uniqueidentifier") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uniqueidentifier") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("datetime2") + .HasColumnName("DeletionTime"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("nvarchar(512)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("datetime2") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uniqueidentifier") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("Name"); + + b.ToTable("Plat_H_ReportDefinition", (string)null); + }); + modelBuilder.Entity("Erp.Settings.Entities.SettingDefinition", b => { b.Property("Id") diff --git a/api/src/Erp.Platform.EntityFrameworkCore/Migrations/20251214195617_Initial.cs b/api/src/Erp.Platform.EntityFrameworkCore/Migrations/20260106110136_Initial.cs similarity index 99% rename from api/src/Erp.Platform.EntityFrameworkCore/Migrations/20251214195617_Initial.cs rename to api/src/Erp.Platform.EntityFrameworkCore/Migrations/20260106110136_Initial.cs index 5a6e10e3..a3f2e145 100644 --- a/api/src/Erp.Platform.EntityFrameworkCore/Migrations/20251214195617_Initial.cs +++ b/api/src/Erp.Platform.EntityFrameworkCore/Migrations/20260106110136_Initial.cs @@ -2009,6 +2009,27 @@ namespace Erp.Platform.Migrations table.PrimaryKey("PK_Plat_H_NotificationRule", x => x.Id); }); + migrationBuilder.CreateTable( + name: "Plat_H_ReportDefinition", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + DisplayName = table.Column(type: "nvarchar(512)", maxLength: 512, nullable: false), + Content = table.Column(type: "varbinary(max)", nullable: false), + CreationTime = table.Column(type: "datetime2", nullable: false), + CreatorId = table.Column(type: "uniqueidentifier", nullable: true), + LastModificationTime = table.Column(type: "datetime2", nullable: true), + LastModifierId = table.Column(type: "uniqueidentifier", nullable: true), + IsDeleted = table.Column(type: "bit", nullable: false, defaultValue: false), + DeleterId = table.Column(type: "uniqueidentifier", nullable: true), + DeletionTime = table.Column(type: "datetime2", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Plat_H_ReportDefinition", x => x.Id); + }); + migrationBuilder.CreateTable( name: "Plat_H_SettingDefinition", columns: table => new @@ -9505,6 +9526,11 @@ namespace Erp.Platform.Migrations table: "Plat_H_Notification", column: "NotificationRuleId"); + migrationBuilder.CreateIndex( + name: "IX_Plat_H_ReportDefinition_Name", + table: "Plat_H_ReportDefinition", + column: "Name"); + migrationBuilder.CreateIndex( name: "IX_Prj_T_ProjectPhase_CategoryId", table: "Prj_T_ProjectPhase", @@ -10675,6 +10701,9 @@ namespace Erp.Platform.Migrations migrationBuilder.DropTable( name: "Plat_H_Notification"); + migrationBuilder.DropTable( + name: "Plat_H_ReportDefinition"); + migrationBuilder.DropTable( name: "Plat_H_SettingDefinition"); diff --git a/api/src/Erp.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs b/api/src/Erp.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs index f8f86f8b..047d0422 100644 --- a/api/src/Erp.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs +++ b/api/src/Erp.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs @@ -17770,6 +17770,63 @@ namespace Erp.Platform.Migrations b.ToTable("Sas_H_ForumTopic", (string)null); }); + modelBuilder.Entity("Erp.Reports.ReportDefinitions.ReportDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Content") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("CreationTime") + .HasColumnType("datetime2") + .HasColumnName("CreationTime"); + + b.Property("CreatorId") + .HasColumnType("uniqueidentifier") + .HasColumnName("CreatorId"); + + b.Property("DeleterId") + .HasColumnType("uniqueidentifier") + .HasColumnName("DeleterId"); + + b.Property("DeletionTime") + .HasColumnType("datetime2") + .HasColumnName("DeletionTime"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("nvarchar(512)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false) + .HasColumnName("IsDeleted"); + + b.Property("LastModificationTime") + .HasColumnType("datetime2") + .HasColumnName("LastModificationTime"); + + b.Property("LastModifierId") + .HasColumnType("uniqueidentifier") + .HasColumnName("LastModifierId"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("Name"); + + b.ToTable("Plat_H_ReportDefinition", (string)null); + }); + modelBuilder.Entity("Erp.Settings.Entities.SettingDefinition", b => { b.Property("Id") diff --git a/api/src/Erp.Platform.HttpApi.Host/Controllers/CustomWebDocumentViewerController.cs b/api/src/Erp.Platform.HttpApi.Host/Controllers/CustomWebDocumentViewerController.cs new file mode 100644 index 00000000..08ef7e63 --- /dev/null +++ b/api/src/Erp.Platform.HttpApi.Host/Controllers/CustomWebDocumentViewerController.cs @@ -0,0 +1,15 @@ +using DevExpress.AspNetCore.Reporting.WebDocumentViewer; +using DevExpress.AspNetCore.Reporting.WebDocumentViewer.Native.Services; +using Microsoft.AspNetCore.Mvc; + +namespace Erp.Platform.Controllers; + +[Route("DXXRDV")] +[ApiExplorerSettings(IgnoreApi = true)] +public class CustomWebDocumentViewerController : WebDocumentViewerController +{ + public CustomWebDocumentViewerController(IWebDocumentViewerMvcControllerService controllerService) + : base(controllerService) + { + } +} diff --git a/api/src/Erp.Platform.HttpApi.Host/Data/LegacyReportDbContext.cs b/api/src/Erp.Platform.HttpApi.Host/Data/LegacyReportDbContext.cs new file mode 100644 index 00000000..6484326c --- /dev/null +++ b/api/src/Erp.Platform.HttpApi.Host/Data/LegacyReportDbContext.cs @@ -0,0 +1,67 @@ +using System.Linq; +using Microsoft.EntityFrameworkCore; + +namespace Erp.Reports.HttpApi.Host.Data; + +public class SqlDataConnectionDescription : DataConnection { } +public class JsonDataConnectionDescription : DataConnection { } + +public abstract class DataConnection +{ + public int Id { get; set; } + public string Name { get; set; } + public string DisplayName { get; set; } + public string ConnectionString { get; set; } +} + +public class LegacyReportDbContext : DbContext +{ + public DbSet JsonDataConnections { get; set; } + public DbSet SqlDataConnections { get; set; } + + public LegacyReportDbContext(DbContextOptions options) : base(options) + { + } + + public void InitializeDatabase() + { + Database.EnsureCreated(); + + var nwindJsonDataConnectionName = "NWindProductsJson"; + if (!JsonDataConnections.Any(x => x.Name == nwindJsonDataConnectionName)) + { + var newData = new JsonDataConnectionDescription + { + Name = nwindJsonDataConnectionName, + DisplayName = "Northwind Products (JSON)", + ConnectionString = "Uri=Data/nwind.json" + }; + JsonDataConnections.Add(newData); + } + + var nwindSqlDataConnectionName = "NWindConnectionString"; + if (!SqlDataConnections.Any(x => x.Name == nwindSqlDataConnectionName)) + { + var newData = new SqlDataConnectionDescription + { + Name = nwindSqlDataConnectionName, + DisplayName = "Northwind Data Connection", + ConnectionString = "XpoProvider=SQLite;Data Source=|DataDirectory|Data/nwind.db" + }; + SqlDataConnections.Add(newData); + } + + var reportsDataConnectionName = "ReportsDataSqlite"; + if (!SqlDataConnections.Any(x => x.Name == reportsDataConnectionName)) + { + var newData = new SqlDataConnectionDescription + { + Name = reportsDataConnectionName, + DisplayName = "Reports Data (Demo)", + ConnectionString = "XpoProvider=SQLite;Data Source=|DataDirectory|Data/reportsData.db" + }; + SqlDataConnections.Add(newData); + } + SaveChanges(); + } +} diff --git a/api/src/Erp.Platform.HttpApi.Host/Data/nwind.db b/api/src/Erp.Platform.HttpApi.Host/Data/nwind.db new file mode 100644 index 00000000..ef5dea89 Binary files /dev/null and b/api/src/Erp.Platform.HttpApi.Host/Data/nwind.db differ diff --git a/api/src/Erp.Platform.HttpApi.Host/Data/nwind.json b/api/src/Erp.Platform.HttpApi.Host/Data/nwind.json new file mode 100644 index 00000000..49aa018c --- /dev/null +++ b/api/src/Erp.Platform.HttpApi.Host/Data/nwind.json @@ -0,0 +1,1005 @@ +{ + "Products": [ + { + "ProductID": 1, + "ProductName": "Chai", + "SupplierID": 1, + "CategoryID": 1, + "QuantityPerUnit": "10 boxes x 20 bags", + "UnitPrice": 18, + "UnitsInStock": 39, + "UnitsOnOrder": 0, + "ReorderLevel": 10, + "Discontinued": 0, + "EAN13": "070684900001" + }, + { + "ProductID": 2, + "ProductName": "Chang", + "SupplierID": 1, + "CategoryID": 1, + "QuantityPerUnit": "24 - 12 oz bottles", + "UnitPrice": 19, + "UnitsInStock": 17, + "UnitsOnOrder": 40, + "ReorderLevel": 25, + "Discontinued": 0, + "EAN13": "070684900002" + }, + { + "ProductID": 3, + "ProductName": "Aniseed Syrup", + "SupplierID": 1, + "CategoryID": 2, + "QuantityPerUnit": "12 - 550 ml bottles", + "UnitPrice": 10, + "UnitsInStock": 13, + "UnitsOnOrder": 70, + "ReorderLevel": 25, + "Discontinued": 0, + "EAN13": "070684900003" + }, + { + "ProductID": 4, + "ProductName": "Chef Anton's Cajun Seasoning", + "SupplierID": 2, + "CategoryID": 2, + "QuantityPerUnit": "48 - 6 oz jars", + "UnitPrice": 22, + "UnitsInStock": 53, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900004" + }, + { + "ProductID": 5, + "ProductName": "Chef Anton's Gumbo Mix", + "SupplierID": 2, + "CategoryID": 2, + "QuantityPerUnit": "36 boxes", + "UnitPrice": 21.35, + "UnitsInStock": 0, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 1, + "EAN13": "070684900005" + }, + { + "ProductID": 6, + "ProductName": "Grandma's Boysenberry Spread", + "SupplierID": 3, + "CategoryID": 2, + "QuantityPerUnit": "12 - 8 oz jars", + "UnitPrice": 25, + "UnitsInStock": 120, + "UnitsOnOrder": 0, + "ReorderLevel": 25, + "Discontinued": 0, + "EAN13": "070684900006" + }, + { + "ProductID": 7, + "ProductName": "Uncle Bob's Organic Dried Pears", + "SupplierID": 3, + "CategoryID": 7, + "QuantityPerUnit": "12 - 1 lb pkgs.", + "UnitPrice": 30, + "UnitsInStock": 15, + "UnitsOnOrder": 0, + "ReorderLevel": 10, + "Discontinued": 0, + "EAN13": "070684900007" + }, + { + "ProductID": 8, + "ProductName": "Northwoods Cranberry Sauce", + "SupplierID": 3, + "CategoryID": 2, + "QuantityPerUnit": "12 - 12 oz jars", + "UnitPrice": 40, + "UnitsInStock": 6, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900008" + }, + { + "ProductID": 9, + "ProductName": "Mishi Kobe Niku", + "SupplierID": 4, + "CategoryID": 6, + "QuantityPerUnit": "18 - 500 g pkgs.", + "UnitPrice": 97, + "UnitsInStock": 29, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 1, + "EAN13": "070684900009" + }, + { + "ProductID": 10, + "ProductName": "Ikura", + "SupplierID": 4, + "CategoryID": 8, + "QuantityPerUnit": "12 - 200 ml jars", + "UnitPrice": 31, + "UnitsInStock": 31, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900010" + }, + { + "ProductID": 11, + "ProductName": "Queso Cabrales", + "SupplierID": 5, + "CategoryID": 4, + "QuantityPerUnit": "1 kg pkg.", + "UnitPrice": 21, + "UnitsInStock": 22, + "UnitsOnOrder": 30, + "ReorderLevel": 30, + "Discontinued": 0, + "EAN13": "070684900011" + }, + { + "ProductID": 12, + "ProductName": "Queso Manchego La Pastora", + "SupplierID": 5, + "CategoryID": 4, + "QuantityPerUnit": "10 - 500 g pkgs.", + "UnitPrice": 38, + "UnitsInStock": 86, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900012" + }, + { + "ProductID": 13, + "ProductName": "Konbu", + "SupplierID": 6, + "CategoryID": 8, + "QuantityPerUnit": "2 kg box", + "UnitPrice": 6, + "UnitsInStock": 24, + "UnitsOnOrder": 0, + "ReorderLevel": 5, + "Discontinued": 0, + "EAN13": "070684900013" + }, + { + "ProductID": 14, + "ProductName": "Tofu", + "SupplierID": 6, + "CategoryID": 7, + "QuantityPerUnit": "40 - 100 g pkgs.", + "UnitPrice": 23.25, + "UnitsInStock": 35, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900014" + }, + { + "ProductID": 15, + "ProductName": "Genen Shouyu", + "SupplierID": 6, + "CategoryID": 2, + "QuantityPerUnit": "24 - 250 ml bottles", + "UnitPrice": 15.5, + "UnitsInStock": 39, + "UnitsOnOrder": 0, + "ReorderLevel": 5, + "Discontinued": 0, + "EAN13": "070684900015" + }, + { + "ProductID": 16, + "ProductName": "Pavlova", + "SupplierID": 7, + "CategoryID": 3, + "QuantityPerUnit": "32 - 500 g boxes", + "UnitPrice": 17.45, + "UnitsInStock": 29, + "UnitsOnOrder": 0, + "ReorderLevel": 10, + "Discontinued": 0, + "EAN13": "070684900016" + }, + { + "ProductID": 17, + "ProductName": "Alice Mutton", + "SupplierID": 7, + "CategoryID": 6, + "QuantityPerUnit": "20 - 1 kg tins", + "UnitPrice": 39, + "UnitsInStock": 0, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 1, + "EAN13": "070684900017" + }, + { + "ProductID": 18, + "ProductName": "Carnarvon Tigers", + "SupplierID": 7, + "CategoryID": 8, + "QuantityPerUnit": "16 kg pkg.", + "UnitPrice": 62.5, + "UnitsInStock": 42, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900018" + }, + { + "ProductID": 19, + "ProductName": "Teatime Chocolate Biscuits", + "SupplierID": 8, + "CategoryID": 3, + "QuantityPerUnit": "10 boxes x 12 pieces", + "UnitPrice": 9.2, + "UnitsInStock": 25, + "UnitsOnOrder": 0, + "ReorderLevel": 5, + "Discontinued": 0, + "EAN13": "070684900019" + }, + { + "ProductID": 20, + "ProductName": "Sir Rodney's Marmalade", + "SupplierID": 8, + "CategoryID": 3, + "QuantityPerUnit": "30 gift boxes", + "UnitPrice": 81, + "UnitsInStock": 40, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900020" + }, + { + "ProductID": 21, + "ProductName": "Sir Rodney's Scones", + "SupplierID": 8, + "CategoryID": 3, + "QuantityPerUnit": "24 pkgs. x 4 pieces", + "UnitPrice": 10, + "UnitsInStock": 3, + "UnitsOnOrder": 40, + "ReorderLevel": 5, + "Discontinued": 0, + "EAN13": "070684900021" + }, + { + "ProductID": 22, + "ProductName": "Gustaf's Knäckebröd", + "SupplierID": 9, + "CategoryID": 5, + "QuantityPerUnit": "24 - 500 g pkgs.", + "UnitPrice": 21, + "UnitsInStock": 104, + "UnitsOnOrder": 0, + "ReorderLevel": 25, + "Discontinued": 0, + "EAN13": "070684900022" + }, + { + "ProductID": 23, + "ProductName": "Tunnbröd", + "SupplierID": 9, + "CategoryID": 5, + "QuantityPerUnit": "12 - 250 g pkgs.", + "UnitPrice": 9, + "UnitsInStock": 61, + "UnitsOnOrder": 0, + "ReorderLevel": 25, + "Discontinued": 0, + "EAN13": "070684900023" + }, + { + "ProductID": 24, + "ProductName": "Guaraná Fantástica", + "SupplierID": 10, + "CategoryID": 1, + "QuantityPerUnit": "12 - 355 ml cans", + "UnitPrice": 4.5, + "UnitsInStock": 20, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 1, + "EAN13": "070684900024" + }, + { + "ProductID": 25, + "ProductName": "NuNuCa Nuß-Nougat-Creme", + "SupplierID": 11, + "CategoryID": 3, + "QuantityPerUnit": "20 - 450 g glasses", + "UnitPrice": 14, + "UnitsInStock": 76, + "UnitsOnOrder": 0, + "ReorderLevel": 30, + "Discontinued": 0, + "EAN13": "070684900025" + }, + { + "ProductID": 26, + "ProductName": "Gumbär Gummibärchen", + "SupplierID": 11, + "CategoryID": 3, + "QuantityPerUnit": "100 - 250 g bags", + "UnitPrice": 31.23, + "UnitsInStock": 15, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900026" + }, + { + "ProductID": 27, + "ProductName": "Schoggi Schokolade", + "SupplierID": 11, + "CategoryID": 3, + "QuantityPerUnit": "100 - 100 g pieces", + "UnitPrice": 43.9, + "UnitsInStock": 49, + "UnitsOnOrder": 0, + "ReorderLevel": 30, + "Discontinued": 0, + "EAN13": "070684900027" + }, + { + "ProductID": 28, + "ProductName": "Rössle Sauerkraut", + "SupplierID": 12, + "CategoryID": 7, + "QuantityPerUnit": "25 - 825 g cans", + "UnitPrice": 45.6, + "UnitsInStock": 26, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 1, + "EAN13": "070684900028" + }, + { + "ProductID": 29, + "ProductName": "Thüringer Rostbratwurst", + "SupplierID": 12, + "CategoryID": 6, + "QuantityPerUnit": "50 bags x 30 sausgs.", + "UnitPrice": 123.79, + "UnitsInStock": 0, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 1, + "EAN13": "070684900029" + }, + { + "ProductID": 30, + "ProductName": "Nord-Ost Matjeshering", + "SupplierID": 13, + "CategoryID": 8, + "QuantityPerUnit": "10 - 200 g glasses", + "UnitPrice": 25.89, + "UnitsInStock": 10, + "UnitsOnOrder": 0, + "ReorderLevel": 15, + "Discontinued": 0, + "EAN13": "070684900030" + }, + { + "ProductID": 31, + "ProductName": "Gorgonzola Telino", + "SupplierID": 14, + "CategoryID": 4, + "QuantityPerUnit": "12 - 100 g pkgs", + "UnitPrice": 12.5, + "UnitsInStock": 0, + "UnitsOnOrder": 70, + "ReorderLevel": 20, + "Discontinued": 0, + "EAN13": "070684900031" + }, + { + "ProductID": 32, + "ProductName": "Mascarpone Fabioli", + "SupplierID": 14, + "CategoryID": 4, + "QuantityPerUnit": "24 - 200 g pkgs.", + "UnitPrice": 32, + "UnitsInStock": 9, + "UnitsOnOrder": 40, + "ReorderLevel": 25, + "Discontinued": 0, + "EAN13": "070684900032" + }, + { + "ProductID": 33, + "ProductName": "Geitost", + "SupplierID": 15, + "CategoryID": 4, + "QuantityPerUnit": "500 g", + "UnitPrice": 2.5, + "UnitsInStock": 112, + "UnitsOnOrder": 0, + "ReorderLevel": 20, + "Discontinued": 0, + "EAN13": "070684900033" + }, + { + "ProductID": 34, + "ProductName": "Sasquatch Ale", + "SupplierID": 16, + "CategoryID": 1, + "QuantityPerUnit": "24 - 12 oz bottles", + "UnitPrice": 14, + "UnitsInStock": 111, + "UnitsOnOrder": 0, + "ReorderLevel": 15, + "Discontinued": 0, + "EAN13": "070684900034" + }, + { + "ProductID": 35, + "ProductName": "Steeleye Stout", + "SupplierID": 16, + "CategoryID": 1, + "QuantityPerUnit": "24 - 12 oz bottles", + "UnitPrice": 18, + "UnitsInStock": 20, + "UnitsOnOrder": 0, + "ReorderLevel": 15, + "Discontinued": 0, + "EAN13": "070684900035" + }, + { + "ProductID": 36, + "ProductName": "Inlagd Sill", + "SupplierID": 17, + "CategoryID": 8, + "QuantityPerUnit": "24 - 250 g jars", + "UnitPrice": 19, + "UnitsInStock": 112, + "UnitsOnOrder": 0, + "ReorderLevel": 20, + "Discontinued": 0, + "EAN13": "070684900036" + }, + { + "ProductID": 37, + "ProductName": "Gravad lax", + "SupplierID": 17, + "CategoryID": 8, + "QuantityPerUnit": "12 - 500 g pkgs.", + "UnitPrice": 26, + "UnitsInStock": 11, + "UnitsOnOrder": 50, + "ReorderLevel": 25, + "Discontinued": 0, + "EAN13": "070684900037" + }, + { + "ProductID": 38, + "ProductName": "Côte de Blaye", + "SupplierID": 18, + "CategoryID": 1, + "QuantityPerUnit": "12 - 75 cl bottles", + "UnitPrice": 263.5, + "UnitsInStock": 17, + "UnitsOnOrder": 0, + "ReorderLevel": 15, + "Discontinued": 0, + "EAN13": "070684900038" + }, + { + "ProductID": 39, + "ProductName": "Chartreuse verte", + "SupplierID": 18, + "CategoryID": 1, + "QuantityPerUnit": "750 cc per bottle", + "UnitPrice": 18, + "UnitsInStock": 69, + "UnitsOnOrder": 0, + "ReorderLevel": 5, + "Discontinued": 0, + "EAN13": "070684900039" + }, + { + "ProductID": 40, + "ProductName": "Boston Crab Meat", + "SupplierID": 19, + "CategoryID": 8, + "QuantityPerUnit": "24 - 4 oz tins", + "UnitPrice": 18.4, + "UnitsInStock": 123, + "UnitsOnOrder": 0, + "ReorderLevel": 30, + "Discontinued": 0, + "EAN13": "070684900040" + }, + { + "ProductID": 41, + "ProductName": "Jack's New England Clam Chowder", + "SupplierID": 19, + "CategoryID": 8, + "QuantityPerUnit": "12 - 12 oz cans", + "UnitPrice": 9.65, + "UnitsInStock": 85, + "UnitsOnOrder": 0, + "ReorderLevel": 10, + "Discontinued": 0, + "EAN13": "070684900041" + }, + { + "ProductID": 42, + "ProductName": "Singaporean Hokkien Fried Mee", + "SupplierID": 20, + "CategoryID": 5, + "QuantityPerUnit": "32 - 1 kg pkgs.", + "UnitPrice": 14, + "UnitsInStock": 26, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 1, + "EAN13": "070684900042" + }, + { + "ProductID": 43, + "ProductName": "Ipoh Coffee", + "SupplierID": 20, + "CategoryID": 1, + "QuantityPerUnit": "16 - 500 g tins", + "UnitPrice": 46, + "UnitsInStock": 17, + "UnitsOnOrder": 10, + "ReorderLevel": 25, + "Discontinued": 0, + "EAN13": "070684900043" + }, + { + "ProductID": 44, + "ProductName": "Gula Malacca", + "SupplierID": 20, + "CategoryID": 2, + "QuantityPerUnit": "20 - 2 kg bags", + "UnitPrice": 19.45, + "UnitsInStock": 27, + "UnitsOnOrder": 0, + "ReorderLevel": 15, + "Discontinued": 0, + "EAN13": "070684900044" + }, + { + "ProductID": 45, + "ProductName": "Rogede sild", + "SupplierID": 21, + "CategoryID": 8, + "QuantityPerUnit": "1k pkg.", + "UnitPrice": 9.5, + "UnitsInStock": 5, + "UnitsOnOrder": 70, + "ReorderLevel": 15, + "Discontinued": 0, + "EAN13": "070684900045" + }, + { + "ProductID": 46, + "ProductName": "Spegesild", + "SupplierID": 21, + "CategoryID": 8, + "QuantityPerUnit": "4 - 450 g glasses", + "UnitPrice": 12, + "UnitsInStock": 95, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900046" + }, + { + "ProductID": 47, + "ProductName": "Zaanse koeken", + "SupplierID": 22, + "CategoryID": 3, + "QuantityPerUnit": "10 - 4 oz boxes", + "UnitPrice": 9.5, + "UnitsInStock": 36, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900047" + }, + { + "ProductID": 48, + "ProductName": "Chocolade", + "SupplierID": 22, + "CategoryID": 3, + "QuantityPerUnit": "10 pkgs.", + "UnitPrice": 12.75, + "UnitsInStock": 15, + "UnitsOnOrder": 70, + "ReorderLevel": 25, + "Discontinued": 0, + "EAN13": "070684900048" + }, + { + "ProductID": 49, + "ProductName": "Maxilaku", + "SupplierID": 23, + "CategoryID": 3, + "QuantityPerUnit": "24 - 50 g pkgs.", + "UnitPrice": 20, + "UnitsInStock": 10, + "UnitsOnOrder": 60, + "ReorderLevel": 15, + "Discontinued": 0, + "EAN13": "070684900049" + }, + { + "ProductID": 50, + "ProductName": "Valkoinen suklaa", + "SupplierID": 23, + "CategoryID": 3, + "QuantityPerUnit": "12 - 100 g bars", + "UnitPrice": 16.25, + "UnitsInStock": 65, + "UnitsOnOrder": 0, + "ReorderLevel": 30, + "Discontinued": 0, + "EAN13": "070684900050" + }, + { + "ProductID": 51, + "ProductName": "Manjimup Dried Apples", + "SupplierID": 24, + "CategoryID": 7, + "QuantityPerUnit": "50 - 300 g pkgs.", + "UnitPrice": 53, + "UnitsInStock": 20, + "UnitsOnOrder": 0, + "ReorderLevel": 10, + "Discontinued": 0, + "EAN13": "070684900051" + }, + { + "ProductID": 52, + "ProductName": "Filo Mix", + "SupplierID": 24, + "CategoryID": 5, + "QuantityPerUnit": "16 - 2 kg boxes", + "UnitPrice": 7, + "UnitsInStock": 38, + "UnitsOnOrder": 0, + "ReorderLevel": 25, + "Discontinued": 0, + "EAN13": "070684900052" + }, + { + "ProductID": 53, + "ProductName": "Perth Pasties", + "SupplierID": 24, + "CategoryID": 6, + "QuantityPerUnit": "48 pieces", + "UnitPrice": 32.8, + "UnitsInStock": 0, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 1, + "EAN13": "070684900053" + }, + { + "ProductID": 54, + "ProductName": "Tourtière", + "SupplierID": 25, + "CategoryID": 6, + "QuantityPerUnit": "16 pies", + "UnitPrice": 7.45, + "UnitsInStock": 21, + "UnitsOnOrder": 0, + "ReorderLevel": 10, + "Discontinued": 0, + "EAN13": "070684900054" + }, + { + "ProductID": 55, + "ProductName": "Pâté chinois", + "SupplierID": 25, + "CategoryID": 6, + "QuantityPerUnit": "24 boxes x 2 pies", + "UnitPrice": 24, + "UnitsInStock": 115, + "UnitsOnOrder": 0, + "ReorderLevel": 20, + "Discontinued": 0, + "EAN13": "070684900055" + }, + { + "ProductID": 56, + "ProductName": "Gnocchi di nonna Alice", + "SupplierID": 26, + "CategoryID": 5, + "QuantityPerUnit": "24 - 250 g pkgs.", + "UnitPrice": 38, + "UnitsInStock": 21, + "UnitsOnOrder": 10, + "ReorderLevel": 30, + "Discontinued": 0, + "EAN13": "070684900056" + }, + { + "ProductID": 57, + "ProductName": "Ravioli Angelo", + "SupplierID": 26, + "CategoryID": 5, + "QuantityPerUnit": "24 - 250 g pkgs.", + "UnitPrice": 19.5, + "UnitsInStock": 36, + "UnitsOnOrder": 0, + "ReorderLevel": 20, + "Discontinued": 0, + "EAN13": "070684900057" + }, + { + "ProductID": 58, + "ProductName": "Escargots de Bourgogne", + "SupplierID": 27, + "CategoryID": 8, + "QuantityPerUnit": "24 pieces", + "UnitPrice": 13.25, + "UnitsInStock": 62, + "UnitsOnOrder": 0, + "ReorderLevel": 20, + "Discontinued": 0, + "EAN13": "070684900058" + }, + { + "ProductID": 59, + "ProductName": "Raclette Courdavault", + "SupplierID": 28, + "CategoryID": 4, + "QuantityPerUnit": "5 kg pkg.", + "UnitPrice": 55, + "UnitsInStock": 79, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900059" + }, + { + "ProductID": 60, + "ProductName": "Camembert Pierrot", + "SupplierID": 28, + "CategoryID": 4, + "QuantityPerUnit": "15 - 300 g rounds", + "UnitPrice": 34, + "UnitsInStock": 19, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900060" + }, + { + "ProductID": 61, + "ProductName": "Sirop d'érable", + "SupplierID": 29, + "CategoryID": 2, + "QuantityPerUnit": "24 - 500 ml bottles", + "UnitPrice": 28.5, + "UnitsInStock": 113, + "UnitsOnOrder": 0, + "ReorderLevel": 25, + "Discontinued": 0, + "EAN13": "070684900061" + }, + { + "ProductID": 62, + "ProductName": "Tarte au sucre", + "SupplierID": 29, + "CategoryID": 3, + "QuantityPerUnit": "48 pies", + "UnitPrice": 49.3, + "UnitsInStock": 17, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900062" + }, + { + "ProductID": 63, + "ProductName": "Vegie-spread", + "SupplierID": 7, + "CategoryID": 2, + "QuantityPerUnit": "15 - 625 g jars", + "UnitPrice": 43.9, + "UnitsInStock": 24, + "UnitsOnOrder": 0, + "ReorderLevel": 5, + "Discontinued": 0, + "EAN13": "070684900063" + }, + { + "ProductID": 64, + "ProductName": "Wimmers gute Semmelknödel", + "SupplierID": 12, + "CategoryID": 5, + "QuantityPerUnit": "20 bags x 4 pieces", + "UnitPrice": 33.25, + "UnitsInStock": 22, + "UnitsOnOrder": 80, + "ReorderLevel": 30, + "Discontinued": 0, + "EAN13": "070684900064" + }, + { + "ProductID": 65, + "ProductName": "Louisiana Fiery Hot Pepper Sauce", + "SupplierID": 2, + "CategoryID": 2, + "QuantityPerUnit": "32 - 8 oz bottles", + "UnitPrice": 21.05, + "UnitsInStock": 76, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900065" + }, + { + "ProductID": 66, + "ProductName": "Louisiana Hot Spiced Okra", + "SupplierID": 2, + "CategoryID": 2, + "QuantityPerUnit": "24 - 8 oz jars", + "UnitPrice": 17, + "UnitsInStock": 4, + "UnitsOnOrder": 100, + "ReorderLevel": 20, + "Discontinued": 0, + "EAN13": "070684900066" + }, + { + "ProductID": 67, + "ProductName": "Laughing Lumberjack Lager", + "SupplierID": 16, + "CategoryID": 1, + "QuantityPerUnit": "24 - 12 oz bottles", + "UnitPrice": 14, + "UnitsInStock": 52, + "UnitsOnOrder": 0, + "ReorderLevel": 10, + "Discontinued": 0, + "EAN13": "070684900067" + }, + { + "ProductID": 68, + "ProductName": "Scottish Longbreads", + "SupplierID": 8, + "CategoryID": 3, + "QuantityPerUnit": "10 boxes x 8 pieces", + "UnitPrice": 12.5, + "UnitsInStock": 6, + "UnitsOnOrder": 10, + "ReorderLevel": 15, + "Discontinued": 0, + "EAN13": "070684900068" + }, + { + "ProductID": 69, + "ProductName": "Gudbrandsdalsost", + "SupplierID": 15, + "CategoryID": 4, + "QuantityPerUnit": "10 kg pkg.", + "UnitPrice": 36, + "UnitsInStock": 26, + "UnitsOnOrder": 0, + "ReorderLevel": 15, + "Discontinued": 0, + "EAN13": "070684900069" + }, + { + "ProductID": 70, + "ProductName": "Outback Lager", + "SupplierID": 7, + "CategoryID": 1, + "QuantityPerUnit": "24 - 355 ml bottles", + "UnitPrice": 15, + "UnitsInStock": 15, + "UnitsOnOrder": 10, + "ReorderLevel": 30, + "Discontinued": 0, + "EAN13": "070684900070" + }, + { + "ProductID": 71, + "ProductName": "Flotemysost", + "SupplierID": 15, + "CategoryID": 4, + "QuantityPerUnit": "10 - 500 g pkgs.", + "UnitPrice": 21.5, + "UnitsInStock": 26, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900071" + }, + { + "ProductID": 72, + "ProductName": "Mozzarella di Giovanni", + "SupplierID": 14, + "CategoryID": 4, + "QuantityPerUnit": "24 - 200 g pkgs.", + "UnitPrice": 34.8, + "UnitsInStock": 14, + "UnitsOnOrder": 0, + "ReorderLevel": 0, + "Discontinued": 0, + "EAN13": "070684900072" + }, + { + "ProductID": 73, + "ProductName": "Röd Kaviar", + "SupplierID": 17, + "CategoryID": 8, + "QuantityPerUnit": "24 - 150 g jars", + "UnitPrice": 15, + "UnitsInStock": 101, + "UnitsOnOrder": 0, + "ReorderLevel": 5, + "Discontinued": 0, + "EAN13": "070684900073" + }, + { + "ProductID": 74, + "ProductName": "Longlife Tofu", + "SupplierID": 4, + "CategoryID": 7, + "QuantityPerUnit": "5 kg pkg.", + "UnitPrice": 10, + "UnitsInStock": 4, + "UnitsOnOrder": 20, + "ReorderLevel": 5, + "Discontinued": 0, + "EAN13": "070684900074" + }, + { + "ProductID": 75, + "ProductName": "Rhönbräu Klosterbier", + "SupplierID": 12, + "CategoryID": 1, + "QuantityPerUnit": "24 - 0.5 l bottles", + "UnitPrice": 7.75, + "UnitsInStock": 125, + "UnitsOnOrder": 0, + "ReorderLevel": 25, + "Discontinued": 0, + "EAN13": "070684900075" + }, + { + "ProductID": 76, + "ProductName": "Lakkalikööri", + "SupplierID": 23, + "CategoryID": 1, + "QuantityPerUnit": "500 ml", + "UnitPrice": 18, + "UnitsInStock": 57, + "UnitsOnOrder": 0, + "ReorderLevel": 20, + "Discontinued": 0, + "EAN13": "070684900076" + }, + { + "ProductID": 77, + "ProductName": "Original Frankfurter grüne Soße", + "SupplierID": 12, + "CategoryID": 2, + "QuantityPerUnit": "12 boxes", + "UnitPrice": 13, + "UnitsInStock": 32, + "UnitsOnOrder": 0, + "ReorderLevel": 15, + "Discontinued": 0, + "EAN13": "070684900077" + } + ] +} diff --git a/api/src/Erp.Platform.HttpApi.Host/Data/reportsData.db b/api/src/Erp.Platform.HttpApi.Host/Data/reportsData.db new file mode 100644 index 00000000..7a11360f Binary files /dev/null and b/api/src/Erp.Platform.HttpApi.Host/Data/reportsData.db differ diff --git a/api/src/Erp.Platform.HttpApi.Host/Erp.Platform.HttpApi.Host.csproj b/api/src/Erp.Platform.HttpApi.Host/Erp.Platform.HttpApi.Host.csproj index 4905e2ac..8fe09174 100644 --- a/api/src/Erp.Platform.HttpApi.Host/Erp.Platform.HttpApi.Host.csproj +++ b/api/src/Erp.Platform.HttpApi.Host/Erp.Platform.HttpApi.Host.csproj @@ -10,6 +10,8 @@ + + @@ -42,5 +44,20 @@ + + + PreserveNewest + + + PreserveNewest + + + + + + XtraReport + + + diff --git a/api/src/Erp.Platform.HttpApi.Host/PlatformHttpApiHostModule.cs b/api/src/Erp.Platform.HttpApi.Host/PlatformHttpApiHostModule.cs index 960fe1f9..3e91cb4c 100644 --- a/api/src/Erp.Platform.HttpApi.Host/PlatformHttpApiHostModule.cs +++ b/api/src/Erp.Platform.HttpApi.Host/PlatformHttpApiHostModule.cs @@ -9,6 +9,7 @@ using Erp.MailQueue; using Erp.Notifications.Application; using Erp.Platform.Classrooms; using Erp.Platform.EntityFrameworkCore; +using Erp.Reports; using Erp.SqlQueryManager; using Erp.Platform.Extensions; using Erp.Platform.FileManagement; @@ -55,6 +56,10 @@ using Erp.Platform.DynamicServices; using static Erp.Platform.PlatformConsts; using static Erp.Settings.SettingsConsts; using Hangfire.SqlServer; +using DevExpress.AspNetCore; +using DevExpress.AspNetCore.Reporting; +using DevExpress.XtraReports.Web.Extensions; +using Erp.Platform.ReportServices; namespace Erp.Platform; @@ -69,7 +74,8 @@ namespace Erp.Platform; typeof(AbpAspNetCoreSerilogModule), typeof(AbpSwashbuckleModule), typeof(AbpBackgroundWorkersHangfireModule), - typeof(SqlQueryManagerApplicationModule) + typeof(SqlQueryManagerApplicationModule), + typeof(ErpReportsApplicationModule) )] public class PlatformHttpApiHostModule : AbpModule { @@ -127,6 +133,7 @@ public class PlatformHttpApiHostModule : AbpModule ConfigureAuditing(); ConfigureDynamicServices(context); + ConfigureDevExpressReporting(context); context.Services.AddSignalR(); @@ -207,6 +214,7 @@ public class PlatformHttpApiHostModule : AbpModule options.ConventionalControllers.Create(typeof(ErpMailQueueModule).Assembly); options.ConventionalControllers.Create(typeof(NotificationApplicationModule).Assembly); options.ConventionalControllers.Create(typeof(SqlQueryManagerApplicationModule).Assembly); + options.ConventionalControllers.Create(typeof(ErpReportsApplicationModule).Assembly); options.ChangeControllerModelApiExplorerGroupName = false; options.ConventionalControllers.FormBodyBindingIgnoredTypes.Add(typeof(PlatformUpdateProfileDto)); options.ConventionalControllers.FormBodyBindingIgnoredTypes.Add(typeof(UploadFileDto)); @@ -386,6 +394,30 @@ public class PlatformHttpApiHostModule : AbpModule context.Services.AddSingleton(); } + private void ConfigureDevExpressReporting(ServiceConfigurationContext context) + { + context.Services.AddDevExpressControls(); + context.Services.ConfigureReportingServices(configurator => + { + configurator.ConfigureReportDesigner(designerConfigurator => + { + }); + configurator.ConfigureWebDocumentViewer(viewerConfigurator => + { + viewerConfigurator.UseCachedReportSourceBuilder(); + }); + }); + + // Register report storage extension + context.Services.AddScoped(); + + // Register custom data connection providers + context.Services.AddScoped(); + context.Services.AddScoped(); + context.Services.AddScoped(); + context.Services.AddScoped(); + } + public override void OnApplicationInitialization(ApplicationInitializationContext context) { var app = context.GetApplicationBuilder(); diff --git a/api/src/Erp.Platform.HttpApi.Host/PredefinedReports/ReportsFactory.cs b/api/src/Erp.Platform.HttpApi.Host/PredefinedReports/ReportsFactory.cs new file mode 100644 index 00000000..50779f8c --- /dev/null +++ b/api/src/Erp.Platform.HttpApi.Host/PredefinedReports/ReportsFactory.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using DevExpress.XtraReports.UI; + +namespace Erp.Reports.PredefinedReports; + +public static class ReportsFactory +{ + public static Dictionary> Reports = new Dictionary>() + { + ["TestReport"] = () => new TestReport() + }; +} diff --git a/api/src/Erp.Platform.HttpApi.Host/PredefinedReports/TestReport.Designer.cs b/api/src/Erp.Platform.HttpApi.Host/PredefinedReports/TestReport.Designer.cs new file mode 100644 index 00000000..e9a8fad3 --- /dev/null +++ b/api/src/Erp.Platform.HttpApi.Host/PredefinedReports/TestReport.Designer.cs @@ -0,0 +1,559 @@ +namespace Erp.Reports.PredefinedReports +{ + partial class TestReport + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if(disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() { + this.components = new System.ComponentModel.Container(); + DevExpress.DataAccess.Sql.SelectQuery selectQuery1 = new DevExpress.DataAccess.Sql.SelectQuery(); + DevExpress.DataAccess.Sql.Column column1 = new DevExpress.DataAccess.Sql.Column(); + DevExpress.DataAccess.Sql.ColumnExpression columnExpression1 = new DevExpress.DataAccess.Sql.ColumnExpression(); + DevExpress.DataAccess.Sql.Table table4 = new DevExpress.DataAccess.Sql.Table(); + DevExpress.DataAccess.Sql.Column column2 = new DevExpress.DataAccess.Sql.Column(); + DevExpress.DataAccess.Sql.ColumnExpression columnExpression2 = new DevExpress.DataAccess.Sql.ColumnExpression(); + DevExpress.DataAccess.Sql.Column column3 = new DevExpress.DataAccess.Sql.Column(); + DevExpress.DataAccess.Sql.ColumnExpression columnExpression3 = new DevExpress.DataAccess.Sql.ColumnExpression(); + DevExpress.DataAccess.Sql.Column column4 = new DevExpress.DataAccess.Sql.Column(); + DevExpress.DataAccess.Sql.ColumnExpression columnExpression4 = new DevExpress.DataAccess.Sql.ColumnExpression(); + DevExpress.DataAccess.Sql.Column column5 = new DevExpress.DataAccess.Sql.Column(); + DevExpress.DataAccess.Sql.ColumnExpression columnExpression5 = new DevExpress.DataAccess.Sql.ColumnExpression(); + DevExpress.DataAccess.Sql.Column column6 = new DevExpress.DataAccess.Sql.Column(); + DevExpress.DataAccess.Sql.ColumnExpression columnExpression6 = new DevExpress.DataAccess.Sql.ColumnExpression(); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(TestReport)); + this.TopMargin = new DevExpress.XtraReports.UI.TopMarginBand(); + this.BottomMargin = new DevExpress.XtraReports.UI.BottomMarginBand(); + this.pageInfo1 = new DevExpress.XtraReports.UI.XRPageInfo(); + this.pageInfo2 = new DevExpress.XtraReports.UI.XRPageInfo(); + this.ReportHeader = new DevExpress.XtraReports.UI.ReportHeaderBand(); + this.label1 = new DevExpress.XtraReports.UI.XRLabel(); + this.GroupHeader1 = new DevExpress.XtraReports.UI.GroupHeaderBand(); + this.table1 = new DevExpress.XtraReports.UI.XRTable(); + this.tableRow1 = new DevExpress.XtraReports.UI.XRTableRow(); + this.tableCell1 = new DevExpress.XtraReports.UI.XRTableCell(); + this.tableCell2 = new DevExpress.XtraReports.UI.XRTableCell(); + this.GroupHeader2 = new DevExpress.XtraReports.UI.GroupHeaderBand(); + this.table2 = new DevExpress.XtraReports.UI.XRTable(); + this.tableRow2 = new DevExpress.XtraReports.UI.XRTableRow(); + this.tableCell3 = new DevExpress.XtraReports.UI.XRTableCell(); + this.tableCell4 = new DevExpress.XtraReports.UI.XRTableCell(); + this.tableCell5 = new DevExpress.XtraReports.UI.XRTableCell(); + this.tableCell6 = new DevExpress.XtraReports.UI.XRTableCell(); + this.tableCell7 = new DevExpress.XtraReports.UI.XRTableCell(); + this.Detail = new DevExpress.XtraReports.UI.DetailBand(); + this.table3 = new DevExpress.XtraReports.UI.XRTable(); + this.tableRow3 = new DevExpress.XtraReports.UI.XRTableRow(); + this.tableCell8 = new DevExpress.XtraReports.UI.XRTableCell(); + this.tableCell9 = new DevExpress.XtraReports.UI.XRTableCell(); + this.tableCell10 = new DevExpress.XtraReports.UI.XRTableCell(); + this.pictureBox1 = new DevExpress.XtraReports.UI.XRPictureBox(); + this.tableCell11 = new DevExpress.XtraReports.UI.XRTableCell(); + this.pictureBox2 = new DevExpress.XtraReports.UI.XRPictureBox(); + this.tableCell12 = new DevExpress.XtraReports.UI.XRTableCell(); + this.pictureBox3 = new DevExpress.XtraReports.UI.XRPictureBox(); + this.GroupFooter1 = new DevExpress.XtraReports.UI.GroupFooterBand(); + this.label2 = new DevExpress.XtraReports.UI.XRLabel(); + this.sqlDataSource1 = new DevExpress.DataAccess.Sql.SqlDataSource(this.components); + this.Title = new DevExpress.XtraReports.UI.XRControlStyle(); + this.GroupCaption1 = new DevExpress.XtraReports.UI.XRControlStyle(); + this.GroupData1 = new DevExpress.XtraReports.UI.XRControlStyle(); + this.DetailCaption1 = new DevExpress.XtraReports.UI.XRControlStyle(); + this.DetailData1 = new DevExpress.XtraReports.UI.XRControlStyle(); + this.GroupFooterBackground3 = new DevExpress.XtraReports.UI.XRControlStyle(); + this.DetailData3_Odd = new DevExpress.XtraReports.UI.XRControlStyle(); + this.PageInfo = new DevExpress.XtraReports.UI.XRControlStyle(); + ((System.ComponentModel.ISupportInitialize)(this.table1)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.table2)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.table3)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this)).BeginInit(); + // + // TopMargin + // + this.TopMargin.Name = "TopMargin"; + // + // BottomMargin + // + this.BottomMargin.Controls.AddRange(new DevExpress.XtraReports.UI.XRControl[] { + this.pageInfo1, + this.pageInfo2}); + this.BottomMargin.Name = "BottomMargin"; + // + // pageInfo1 + // + this.pageInfo1.LocationFloat = new DevExpress.Utils.PointFloat(0F, 0F); + this.pageInfo1.Name = "pageInfo1"; + this.pageInfo1.PageInfo = DevExpress.XtraPrinting.PageInfo.DateTime; + this.pageInfo1.SizeF = new System.Drawing.SizeF(325F, 23F); + this.pageInfo1.StyleName = "PageInfo"; + // + // pageInfo2 + // + this.pageInfo2.LocationFloat = new DevExpress.Utils.PointFloat(325F, 0F); + this.pageInfo2.Name = "pageInfo2"; + this.pageInfo2.SizeF = new System.Drawing.SizeF(325F, 23F); + this.pageInfo2.StyleName = "PageInfo"; + this.pageInfo2.TextAlignment = DevExpress.XtraPrinting.TextAlignment.TopRight; + this.pageInfo2.TextFormatString = "Page {0} of {1}"; + // + // ReportHeader + // + this.ReportHeader.Controls.AddRange(new DevExpress.XtraReports.UI.XRControl[] { + this.label1}); + this.ReportHeader.HeightF = 60F; + this.ReportHeader.Name = "ReportHeader"; + // + // label1 + // + this.label1.LocationFloat = new DevExpress.Utils.PointFloat(0F, 0F); + this.label1.Name = "label1"; + this.label1.SizeF = new System.Drawing.SizeF(650F, 24.19433F); + this.label1.StyleName = "Title"; + this.label1.Text = "Report"; + // + // GroupHeader1 + // + this.GroupHeader1.Controls.AddRange(new DevExpress.XtraReports.UI.XRControl[] { + this.table1}); + this.GroupHeader1.GroupFields.AddRange(new DevExpress.XtraReports.UI.GroupField[] { + new DevExpress.XtraReports.UI.GroupField("CategoryID", DevExpress.XtraReports.UI.XRColumnSortOrder.Ascending)}); + this.GroupHeader1.GroupUnion = DevExpress.XtraReports.UI.GroupUnion.WithFirstDetail; + this.GroupHeader1.HeightF = 27F; + this.GroupHeader1.Level = 1; + this.GroupHeader1.Name = "GroupHeader1"; + // + // table1 + // + this.table1.LocationFloat = new DevExpress.Utils.PointFloat(0F, 2F); + this.table1.Name = "table1"; + this.table1.Rows.AddRange(new DevExpress.XtraReports.UI.XRTableRow[] { + this.tableRow1}); + this.table1.SizeF = new System.Drawing.SizeF(650F, 25F); + // + // tableRow1 + // + this.tableRow1.Cells.AddRange(new DevExpress.XtraReports.UI.XRTableCell[] { + this.tableCell1, + this.tableCell2}); + this.tableRow1.Name = "tableRow1"; + this.tableRow1.Weight = 1D; + // + // tableCell1 + // + this.tableCell1.Name = "tableCell1"; + this.tableCell1.StyleName = "GroupCaption1"; + this.tableCell1.Text = "CATEGORY ID"; + this.tableCell1.Weight = 1434852.375D; + // + // tableCell2 + // + this.tableCell2.ExpressionBindings.AddRange(new DevExpress.XtraReports.UI.ExpressionBinding[] { + new DevExpress.XtraReports.UI.ExpressionBinding("BeforePrint", "Text", "[CategoryID]")}); + this.tableCell2.Name = "tableCell2"; + this.tableCell2.StyleName = "GroupData1"; + this.tableCell2.Weight = 9214747D; + // + // GroupHeader2 + // + this.GroupHeader2.Controls.AddRange(new DevExpress.XtraReports.UI.XRControl[] { + this.table2}); + this.GroupHeader2.GroupUnion = DevExpress.XtraReports.UI.GroupUnion.WithFirstDetail; + this.GroupHeader2.HeightF = 28F; + this.GroupHeader2.Level = 2; + this.GroupHeader2.Name = "GroupHeader2"; + // + // table2 + // + this.table2.LocationFloat = new DevExpress.Utils.PointFloat(0F, 0F); + this.table2.Name = "table2"; + this.table2.Rows.AddRange(new DevExpress.XtraReports.UI.XRTableRow[] { + this.tableRow2}); + this.table2.SizeF = new System.Drawing.SizeF(650F, 28F); + // + // tableRow2 + // + this.tableRow2.Cells.AddRange(new DevExpress.XtraReports.UI.XRTableCell[] { + this.tableCell3, + this.tableCell4, + this.tableCell5, + this.tableCell6, + this.tableCell7}); + this.tableRow2.Name = "tableRow2"; + this.tableRow2.Weight = 1D; + // + // tableCell3 + // + this.tableCell3.Borders = DevExpress.XtraPrinting.BorderSide.None; + this.tableCell3.Name = "tableCell3"; + this.tableCell3.StyleName = "DetailCaption1"; + this.tableCell3.StylePriority.UseBorders = false; + this.tableCell3.Text = "Category Name"; + this.tableCell3.Weight = 0.29834482046274041D; + // + // tableCell4 + // + this.tableCell4.Name = "tableCell4"; + this.tableCell4.StyleName = "DetailCaption1"; + this.tableCell4.Text = "Description"; + this.tableCell4.Weight = 0.2344280066856971D; + // + // tableCell5 + // + this.tableCell5.Name = "tableCell5"; + this.tableCell5.StyleName = "DetailCaption1"; + this.tableCell5.Text = "Picture"; + this.tableCell5.Weight = 0.1609015596829928D; + // + // tableCell6 + // + this.tableCell6.Name = "tableCell6"; + this.tableCell6.StyleName = "DetailCaption1"; + this.tableCell6.Text = "Icon17"; + this.tableCell6.Weight = 0.153162841796875D; + // + // tableCell7 + // + this.tableCell7.Name = "tableCell7"; + this.tableCell7.StyleName = "DetailCaption1"; + this.tableCell7.Text = "Icon25"; + this.tableCell7.Weight = 0.15316274789663462D; + // + // Detail + // + this.Detail.Controls.AddRange(new DevExpress.XtraReports.UI.XRControl[] { + this.table3}); + this.Detail.HeightF = 25F; + this.Detail.Name = "Detail"; + // + // table3 + // + this.table3.LocationFloat = new DevExpress.Utils.PointFloat(0F, 0F); + this.table3.Name = "table3"; + this.table3.OddStyleName = "DetailData3_Odd"; + this.table3.Rows.AddRange(new DevExpress.XtraReports.UI.XRTableRow[] { + this.tableRow3}); + this.table3.SizeF = new System.Drawing.SizeF(650F, 25F); + // + // tableRow3 + // + this.tableRow3.Cells.AddRange(new DevExpress.XtraReports.UI.XRTableCell[] { + this.tableCell8, + this.tableCell9, + this.tableCell10, + this.tableCell11, + this.tableCell12}); + this.tableRow3.Name = "tableRow3"; + this.tableRow3.Weight = 11.5D; + // + // tableCell8 + // + this.tableCell8.Borders = DevExpress.XtraPrinting.BorderSide.None; + this.tableCell8.ExpressionBindings.AddRange(new DevExpress.XtraReports.UI.ExpressionBinding[] { + new DevExpress.XtraReports.UI.ExpressionBinding("BeforePrint", "Text", "[CategoryName]")}); + this.tableCell8.Name = "tableCell8"; + this.tableCell8.StyleName = "DetailData1"; + this.tableCell8.StylePriority.UseBorders = false; + this.tableCell8.Weight = 0.29834482046274041D; + // + // tableCell9 + // + this.tableCell9.ExpressionBindings.AddRange(new DevExpress.XtraReports.UI.ExpressionBinding[] { + new DevExpress.XtraReports.UI.ExpressionBinding("BeforePrint", "Text", "[Description]")}); + this.tableCell9.Name = "tableCell9"; + this.tableCell9.StyleName = "DetailData1"; + this.tableCell9.Weight = 0.2344280066856971D; + // + // tableCell10 + // + this.tableCell10.Controls.AddRange(new DevExpress.XtraReports.UI.XRControl[] { + this.pictureBox1}); + this.tableCell10.Name = "tableCell10"; + this.tableCell10.StyleName = "DetailData1"; + this.tableCell10.Weight = 0.1609015596829928D; + // + // pictureBox1 + // + this.pictureBox1.AnchorHorizontal = ((DevExpress.XtraReports.UI.HorizontalAnchorStyles)((DevExpress.XtraReports.UI.HorizontalAnchorStyles.Left | DevExpress.XtraReports.UI.HorizontalAnchorStyles.Right))); + this.pictureBox1.AnchorVertical = ((DevExpress.XtraReports.UI.VerticalAnchorStyles)((DevExpress.XtraReports.UI.VerticalAnchorStyles.Top | DevExpress.XtraReports.UI.VerticalAnchorStyles.Bottom))); + this.pictureBox1.ExpressionBindings.AddRange(new DevExpress.XtraReports.UI.ExpressionBinding[] { + new DevExpress.XtraReports.UI.ExpressionBinding("BeforePrint", "ImageSource", "[Picture]")}); + this.pictureBox1.LocationFloat = new DevExpress.Utils.PointFloat(2.083333F, 0F); + this.pictureBox1.Name = "pictureBox1"; + this.pictureBox1.SizeF = new System.Drawing.SizeF(102.5027F, 25F); + this.pictureBox1.Sizing = DevExpress.XtraPrinting.ImageSizeMode.ZoomImage; + // + // tableCell11 + // + this.tableCell11.Controls.AddRange(new DevExpress.XtraReports.UI.XRControl[] { + this.pictureBox2}); + this.tableCell11.Name = "tableCell11"; + this.tableCell11.StyleName = "DetailData1"; + this.tableCell11.Weight = 0.153162841796875D; + // + // pictureBox2 + // + this.pictureBox2.AnchorHorizontal = ((DevExpress.XtraReports.UI.HorizontalAnchorStyles)((DevExpress.XtraReports.UI.HorizontalAnchorStyles.Left | DevExpress.XtraReports.UI.HorizontalAnchorStyles.Right))); + this.pictureBox2.AnchorVertical = ((DevExpress.XtraReports.UI.VerticalAnchorStyles)((DevExpress.XtraReports.UI.VerticalAnchorStyles.Top | DevExpress.XtraReports.UI.VerticalAnchorStyles.Bottom))); + this.pictureBox2.ExpressionBindings.AddRange(new DevExpress.XtraReports.UI.ExpressionBinding[] { + new DevExpress.XtraReports.UI.ExpressionBinding("BeforePrint", "ImageSource", "[Icon17]")}); + this.pictureBox2.LocationFloat = new DevExpress.Utils.PointFloat(2.083333F, 0F); + this.pictureBox2.Name = "pictureBox2"; + this.pictureBox2.SizeF = new System.Drawing.SizeF(97.47251F, 25F); + this.pictureBox2.Sizing = DevExpress.XtraPrinting.ImageSizeMode.ZoomImage; + // + // tableCell12 + // + this.tableCell12.Controls.AddRange(new DevExpress.XtraReports.UI.XRControl[] { + this.pictureBox3}); + this.tableCell12.Name = "tableCell12"; + this.tableCell12.StyleName = "DetailData1"; + this.tableCell12.Weight = 0.15316274789663462D; + // + // pictureBox3 + // + this.pictureBox3.AnchorHorizontal = ((DevExpress.XtraReports.UI.HorizontalAnchorStyles)((DevExpress.XtraReports.UI.HorizontalAnchorStyles.Left | DevExpress.XtraReports.UI.HorizontalAnchorStyles.Right))); + this.pictureBox3.AnchorVertical = ((DevExpress.XtraReports.UI.VerticalAnchorStyles)((DevExpress.XtraReports.UI.VerticalAnchorStyles.Top | DevExpress.XtraReports.UI.VerticalAnchorStyles.Bottom))); + this.pictureBox3.ExpressionBindings.AddRange(new DevExpress.XtraReports.UI.ExpressionBinding[] { + new DevExpress.XtraReports.UI.ExpressionBinding("BeforePrint", "ImageSource", "[Icon25]")}); + this.pictureBox3.LocationFloat = new DevExpress.Utils.PointFloat(2.083333F, 0F); + this.pictureBox3.Name = "pictureBox3"; + this.pictureBox3.SizeF = new System.Drawing.SizeF(97.47245F, 25F); + this.pictureBox3.Sizing = DevExpress.XtraPrinting.ImageSizeMode.ZoomImage; + // + // GroupFooter1 + // + this.GroupFooter1.Controls.AddRange(new DevExpress.XtraReports.UI.XRControl[] { + this.label2}); + this.GroupFooter1.GroupUnion = DevExpress.XtraReports.UI.GroupFooterUnion.WithLastDetail; + this.GroupFooter1.HeightF = 6F; + this.GroupFooter1.Name = "GroupFooter1"; + // + // label2 + // + this.label2.Borders = DevExpress.XtraPrinting.BorderSide.None; + this.label2.LocationFloat = new DevExpress.Utils.PointFloat(0F, 0F); + this.label2.Name = "label2"; + this.label2.SizeF = new System.Drawing.SizeF(650F, 2.08F); + this.label2.StyleName = "GroupFooterBackground3"; + this.label2.StylePriority.UseBorders = false; + // + // sqlDataSource1 + // + this.sqlDataSource1.ConnectionName = "NWindConnectionString"; + this.sqlDataSource1.Name = "sqlDataSource1"; + columnExpression1.ColumnName = "CategoryID"; + table4.Name = "Categories"; + columnExpression1.Table = table4; + column1.Expression = columnExpression1; + columnExpression2.ColumnName = "CategoryName"; + columnExpression2.Table = table4; + column2.Expression = columnExpression2; + columnExpression3.ColumnName = "Description"; + columnExpression3.Table = table4; + column3.Expression = columnExpression3; + columnExpression4.ColumnName = "Picture"; + columnExpression4.Table = table4; + column4.Expression = columnExpression4; + columnExpression5.ColumnName = "Icon17"; + columnExpression5.Table = table4; + column5.Expression = columnExpression5; + columnExpression6.ColumnName = "Icon25"; + columnExpression6.Table = table4; + column6.Expression = columnExpression6; + selectQuery1.Columns.Add(column1); + selectQuery1.Columns.Add(column2); + selectQuery1.Columns.Add(column3); + selectQuery1.Columns.Add(column4); + selectQuery1.Columns.Add(column5); + selectQuery1.Columns.Add(column6); + selectQuery1.Name = "Categories"; + selectQuery1.Tables.Add(table4); + this.sqlDataSource1.Queries.AddRange(new DevExpress.DataAccess.Sql.SqlQuery[] { + selectQuery1}); + this.sqlDataSource1.ResultSchemaSerializable = resources.GetString("sqlDataSource1.ResultSchemaSerializable"); + // + // Title + // + this.Title.BackColor = System.Drawing.Color.Transparent; + this.Title.BorderColor = System.Drawing.Color.Black; + this.Title.Borders = DevExpress.XtraPrinting.BorderSide.None; + this.Title.BorderWidth = 1F; + this.Title.Font = new DevExpress.Drawing.DXFont("Arial", 14.25F); + this.Title.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(70)))), ((int)(((byte)(80))))); + this.Title.Name = "Title"; + this.Title.Padding = new DevExpress.XtraPrinting.PaddingInfo(6, 6, 0, 0, 100F); + // + // GroupCaption1 + // + this.GroupCaption1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(181)))), ((int)(((byte)(211)))), ((int)(((byte)(142))))); + this.GroupCaption1.BorderColor = System.Drawing.Color.White; + this.GroupCaption1.Borders = DevExpress.XtraPrinting.BorderSide.Bottom; + this.GroupCaption1.BorderWidth = 2F; + this.GroupCaption1.Font = new DevExpress.Drawing.DXFont("Arial", 8.25F, DevExpress.Drawing.DXFontStyle.Bold); + this.GroupCaption1.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(228)))), ((int)(((byte)(228)))), ((int)(((byte)(228))))); + this.GroupCaption1.Name = "GroupCaption1"; + this.GroupCaption1.Padding = new DevExpress.XtraPrinting.PaddingInfo(6, 2, 0, 0, 100F); + this.GroupCaption1.TextAlignment = DevExpress.XtraPrinting.TextAlignment.MiddleLeft; + // + // GroupData1 + // + this.GroupData1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(181)))), ((int)(((byte)(211)))), ((int)(((byte)(142))))); + this.GroupData1.BorderColor = System.Drawing.Color.White; + this.GroupData1.Borders = DevExpress.XtraPrinting.BorderSide.Bottom; + this.GroupData1.BorderWidth = 2F; + this.GroupData1.Font = new DevExpress.Drawing.DXFont("Arial", 8.25F, DevExpress.Drawing.DXFontStyle.Bold); + this.GroupData1.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(70)))), ((int)(((byte)(80))))); + this.GroupData1.Name = "GroupData1"; + this.GroupData1.Padding = new DevExpress.XtraPrinting.PaddingInfo(6, 2, 0, 0, 100F); + this.GroupData1.TextAlignment = DevExpress.XtraPrinting.TextAlignment.MiddleLeft; + // + // DetailCaption1 + // + this.DetailCaption1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(181)))), ((int)(((byte)(211)))), ((int)(((byte)(142))))); + this.DetailCaption1.BorderColor = System.Drawing.Color.White; + this.DetailCaption1.Borders = DevExpress.XtraPrinting.BorderSide.Left; + this.DetailCaption1.BorderWidth = 2F; + this.DetailCaption1.Font = new DevExpress.Drawing.DXFont("Arial", 8.25F, DevExpress.Drawing.DXFontStyle.Bold); + this.DetailCaption1.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(70)))), ((int)(((byte)(80))))); + this.DetailCaption1.Name = "DetailCaption1"; + this.DetailCaption1.Padding = new DevExpress.XtraPrinting.PaddingInfo(6, 6, 0, 0, 100F); + this.DetailCaption1.TextAlignment = DevExpress.XtraPrinting.TextAlignment.MiddleLeft; + // + // DetailData1 + // + this.DetailData1.BorderColor = System.Drawing.Color.Transparent; + this.DetailData1.Borders = DevExpress.XtraPrinting.BorderSide.Left; + this.DetailData1.BorderWidth = 2F; + this.DetailData1.Font = new DevExpress.Drawing.DXFont("Arial", 8.25F); + this.DetailData1.ForeColor = System.Drawing.Color.Black; + this.DetailData1.Name = "DetailData1"; + this.DetailData1.Padding = new DevExpress.XtraPrinting.PaddingInfo(6, 6, 0, 0, 100F); + this.DetailData1.TextAlignment = DevExpress.XtraPrinting.TextAlignment.MiddleLeft; + // + // GroupFooterBackground3 + // + this.GroupFooterBackground3.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(109)))), ((int)(((byte)(117)))), ((int)(((byte)(129))))); + this.GroupFooterBackground3.BorderColor = System.Drawing.Color.White; + this.GroupFooterBackground3.Borders = DevExpress.XtraPrinting.BorderSide.Bottom; + this.GroupFooterBackground3.BorderWidth = 2F; + this.GroupFooterBackground3.Font = new DevExpress.Drawing.DXFont("Arial", 8.25F, DevExpress.Drawing.DXFontStyle.Bold); + this.GroupFooterBackground3.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(228)))), ((int)(((byte)(228)))), ((int)(((byte)(228))))); + this.GroupFooterBackground3.Name = "GroupFooterBackground3"; + this.GroupFooterBackground3.Padding = new DevExpress.XtraPrinting.PaddingInfo(6, 2, 0, 0, 100F); + this.GroupFooterBackground3.TextAlignment = DevExpress.XtraPrinting.TextAlignment.MiddleLeft; + // + // DetailData3_Odd + // + this.DetailData3_Odd.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(243)))), ((int)(((byte)(245)))), ((int)(((byte)(248))))); + this.DetailData3_Odd.BorderColor = System.Drawing.Color.Transparent; + this.DetailData3_Odd.Borders = DevExpress.XtraPrinting.BorderSide.None; + this.DetailData3_Odd.BorderWidth = 1F; + this.DetailData3_Odd.Font = new DevExpress.Drawing.DXFont("Arial", 8.25F); + this.DetailData3_Odd.ForeColor = System.Drawing.Color.Black; + this.DetailData3_Odd.Name = "DetailData3_Odd"; + this.DetailData3_Odd.Padding = new DevExpress.XtraPrinting.PaddingInfo(6, 6, 0, 0, 100F); + this.DetailData3_Odd.TextAlignment = DevExpress.XtraPrinting.TextAlignment.MiddleLeft; + // + // PageInfo + // + this.PageInfo.Font = new DevExpress.Drawing.DXFont("Arial", 8.25F, DevExpress.Drawing.DXFontStyle.Bold); + this.PageInfo.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(70)))), ((int)(((byte)(80))))); + this.PageInfo.Name = "PageInfo"; + this.PageInfo.Padding = new DevExpress.XtraPrinting.PaddingInfo(6, 6, 0, 0, 100F); + // + // TestReport + // + this.Bands.AddRange(new DevExpress.XtraReports.UI.Band[] { + this.TopMargin, + this.BottomMargin, + this.ReportHeader, + this.GroupHeader1, + this.GroupHeader2, + this.Detail, + this.GroupFooter1}); + this.ComponentStorage.AddRange(new System.ComponentModel.IComponent[] { + this.sqlDataSource1}); + this.DataMember = "Categories"; + this.DataSource = this.sqlDataSource1; + this.Font = new DevExpress.Drawing.DXFont("Arial", 9.75F); + this.StyleSheet.AddRange(new DevExpress.XtraReports.UI.XRControlStyle[] { + this.Title, + this.GroupCaption1, + this.GroupData1, + this.DetailCaption1, + this.DetailData1, + this.GroupFooterBackground3, + this.DetailData3_Odd, + this.PageInfo}); + this.Version = "22.2"; + ((System.ComponentModel.ISupportInitialize)(this.table1)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.table2)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.table3)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this)).EndInit(); + + } + + #endregion + + private DevExpress.XtraReports.UI.TopMarginBand TopMargin; + private DevExpress.XtraReports.UI.BottomMarginBand BottomMargin; + private DevExpress.XtraReports.UI.XRPageInfo pageInfo1; + private DevExpress.XtraReports.UI.XRPageInfo pageInfo2; + private DevExpress.XtraReports.UI.ReportHeaderBand ReportHeader; + private DevExpress.XtraReports.UI.XRLabel label1; + private DevExpress.XtraReports.UI.GroupHeaderBand GroupHeader1; + private DevExpress.XtraReports.UI.XRTable table1; + private DevExpress.XtraReports.UI.XRTableRow tableRow1; + private DevExpress.XtraReports.UI.XRTableCell tableCell1; + private DevExpress.XtraReports.UI.XRTableCell tableCell2; + private DevExpress.XtraReports.UI.GroupHeaderBand GroupHeader2; + private DevExpress.XtraReports.UI.XRTable table2; + private DevExpress.XtraReports.UI.XRTableRow tableRow2; + private DevExpress.XtraReports.UI.XRTableCell tableCell3; + private DevExpress.XtraReports.UI.XRTableCell tableCell4; + private DevExpress.XtraReports.UI.XRTableCell tableCell5; + private DevExpress.XtraReports.UI.XRTableCell tableCell6; + private DevExpress.XtraReports.UI.XRTableCell tableCell7; + private DevExpress.XtraReports.UI.DetailBand Detail; + private DevExpress.XtraReports.UI.XRTable table3; + private DevExpress.XtraReports.UI.XRTableRow tableRow3; + private DevExpress.XtraReports.UI.XRTableCell tableCell8; + private DevExpress.XtraReports.UI.XRTableCell tableCell9; + private DevExpress.XtraReports.UI.XRTableCell tableCell10; + private DevExpress.XtraReports.UI.XRPictureBox pictureBox1; + private DevExpress.XtraReports.UI.XRTableCell tableCell11; + private DevExpress.XtraReports.UI.XRPictureBox pictureBox2; + private DevExpress.XtraReports.UI.XRTableCell tableCell12; + private DevExpress.XtraReports.UI.XRPictureBox pictureBox3; + private DevExpress.XtraReports.UI.GroupFooterBand GroupFooter1; + private DevExpress.XtraReports.UI.XRLabel label2; + private DevExpress.DataAccess.Sql.SqlDataSource sqlDataSource1; + private DevExpress.XtraReports.UI.XRControlStyle Title; + private DevExpress.XtraReports.UI.XRControlStyle GroupCaption1; + private DevExpress.XtraReports.UI.XRControlStyle GroupData1; + private DevExpress.XtraReports.UI.XRControlStyle DetailCaption1; + private DevExpress.XtraReports.UI.XRControlStyle DetailData1; + private DevExpress.XtraReports.UI.XRControlStyle GroupFooterBackground3; + private DevExpress.XtraReports.UI.XRControlStyle DetailData3_Odd; + private DevExpress.XtraReports.UI.XRControlStyle PageInfo; + } +} diff --git a/api/src/Erp.Platform.HttpApi.Host/PredefinedReports/TestReport.cs b/api/src/Erp.Platform.HttpApi.Host/PredefinedReports/TestReport.cs new file mode 100644 index 00000000..6278b7f7 --- /dev/null +++ b/api/src/Erp.Platform.HttpApi.Host/PredefinedReports/TestReport.cs @@ -0,0 +1,9 @@ +namespace Erp.Reports.PredefinedReports; + +public partial class TestReport : DevExpress.XtraReports.UI.XtraReport +{ + public TestReport() + { + InitializeComponent(); + } +} diff --git a/api/src/Erp.Platform.HttpApi.Host/PredefinedReports/TestReport.resx b/api/src/Erp.Platform.HttpApi.Host/PredefinedReports/TestReport.resx new file mode 100644 index 00000000..e36faffa --- /dev/null +++ b/api/src/Erp.Platform.HttpApi.Host/PredefinedReports/TestReport.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + PERhdGFTZXQgTmFtZT0ic3FsRGF0YVNvdXJjZTEiPjxWaWV3IE5hbWU9IkNhdGVnb3JpZXMiPjxGaWVsZCBOYW1lPSJDYXRlZ29yeUlEIiBUeXBlPSJJbnQ2NCIgLz48RmllbGQgTmFtZT0iQ2F0ZWdvcnlOYW1lIiBUeXBlPSJTdHJpbmciIC8+PEZpZWxkIE5hbWU9IkRlc2NyaXB0aW9uIiBUeXBlPSJTdHJpbmciIC8+PEZpZWxkIE5hbWU9IlBpY3R1cmUiIFR5cGU9IkJ5dGVBcnJheSIgLz48RmllbGQgTmFtZT0iSWNvbjE3IiBUeXBlPSJCeXRlQXJyYXkiIC8+PEZpZWxkIE5hbWU9Ikljb24yNSIgVHlwZT0iQnl0ZUFycmF5IiAvPjwvVmlldz48L0RhdGFTZXQ+ + + \ No newline at end of file diff --git a/api/src/Erp.Platform.HttpApi.Host/ReportServices/CustomDataSourceWizardJsonDataConnectionStorage.cs b/api/src/Erp.Platform.HttpApi.Host/ReportServices/CustomDataSourceWizardJsonDataConnectionStorage.cs new file mode 100644 index 00000000..85223fe3 --- /dev/null +++ b/api/src/Erp.Platform.HttpApi.Host/ReportServices/CustomDataSourceWizardJsonDataConnectionStorage.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using DevExpress.DataAccess.Json; +using DevExpress.DataAccess.Web; +using DevExpress.DataAccess.Wizard.Services; +using Erp.Reports.HttpApi.Host.Data; + +namespace Erp.Platform.ReportServices; + +public class CustomDataSourceWizardJsonDataConnectionStorage : IDataSourceWizardJsonConnectionStorage +{ + protected LegacyReportDbContext DbContext { get; } + + public CustomDataSourceWizardJsonDataConnectionStorage(LegacyReportDbContext dbContext) + { + DbContext = dbContext; + } + + public List GetConnections() + { + return DbContext.JsonDataConnections.ToList(); + } + + bool IJsonConnectionStorageService.CanSaveConnection { get { return DbContext.JsonDataConnections != null; } } + + bool IJsonConnectionStorageService.ContainsConnection(string connectionName) + { + return GetConnections().Any(x => x.Name == connectionName); + } + + IEnumerable IJsonConnectionStorageService.GetConnections() + { + return GetConnections().Select(x => CreateJsonDataConnectionFromString(x)); + } + + JsonDataConnection IJsonDataConnectionProviderService.GetJsonDataConnection(string name) + { + var connection = GetConnections().FirstOrDefault(x => x.Name == name); + if (connection == null) + throw new InvalidOperationException(); + return CreateJsonDataConnectionFromString(connection); + } + + void IJsonConnectionStorageService.SaveConnection(string connectionName, JsonDataConnection dataConnection, bool saveCredentials) + { + var connections = GetConnections(); + var connectionString = dataConnection.CreateConnectionString(); + foreach (var connection in connections) + { + if (connection.Name == connectionName) + { + connection.ConnectionString = connectionString; + DbContext.SaveChanges(); + return; + } + } + DbContext.JsonDataConnections.Add(new JsonDataConnectionDescription() { Name = connectionName, ConnectionString = connectionString }); + DbContext.SaveChanges(); + } + + public static JsonDataConnection CreateJsonDataConnectionFromString(DataConnection dataConnection) + { + return new JsonDataConnection(dataConnection.ConnectionString) { StoreConnectionNameOnly = true, Name = dataConnection.Name }; + } +} diff --git a/api/src/Erp.Platform.HttpApi.Host/ReportServices/CustomJsonDataConnectionProviderFactory.cs b/api/src/Erp.Platform.HttpApi.Host/ReportServices/CustomJsonDataConnectionProviderFactory.cs new file mode 100644 index 00000000..ba035e98 --- /dev/null +++ b/api/src/Erp.Platform.HttpApi.Host/ReportServices/CustomJsonDataConnectionProviderFactory.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using DevExpress.DataAccess.Json; +using DevExpress.DataAccess.Web; +using Erp.Reports.HttpApi.Host.Data; + +namespace Erp.Platform.ReportServices; + +public class CustomJsonDataConnectionProviderFactory : IJsonDataConnectionProviderFactory +{ + protected LegacyReportDbContext DbContext { get; } + + public CustomJsonDataConnectionProviderFactory(LegacyReportDbContext dbContext) + { + DbContext = dbContext; + } + + public IJsonDataConnectionProviderService Create() + { + return new WebDocumentViewerJsonDataConnectionProvider(DbContext.JsonDataConnections.ToList()); + } +} + +public class WebDocumentViewerJsonDataConnectionProvider : IJsonDataConnectionProviderService +{ + readonly IEnumerable jsonDataConnections; + + public WebDocumentViewerJsonDataConnectionProvider(IEnumerable jsonDataConnections) + { + this.jsonDataConnections = jsonDataConnections; + } + + public JsonDataConnection GetJsonDataConnection(string name) + { + var connection = jsonDataConnections.FirstOrDefault(x => x.Name == name); + if (connection == null) + throw new InvalidOperationException(); + return CustomDataSourceWizardJsonDataConnectionStorage.CreateJsonDataConnectionFromString(connection); + } +} diff --git a/api/src/Erp.Platform.HttpApi.Host/ReportServices/CustomReportStorageWebExtension.cs b/api/src/Erp.Platform.HttpApi.Host/ReportServices/CustomReportStorageWebExtension.cs new file mode 100644 index 00000000..7ee5a2b1 --- /dev/null +++ b/api/src/Erp.Platform.HttpApi.Host/ReportServices/CustomReportStorageWebExtension.cs @@ -0,0 +1,83 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using DevExpress.XtraReports.UI; +using Erp.Reports.EntityFrameworkCore; +using System; +using Erp.Reports.PredefinedReports; + +namespace Erp.Platform.ReportServices; + +public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extensions.ReportStorageWebExtension +{ + protected ErpReportsDbContext DbContext { get; set; } + + public CustomReportStorageWebExtension(ErpReportsDbContext dbContext) + { + this.DbContext = dbContext; + } + + public override bool CanSetData(string url) + { + return true; + } + + public override bool IsValidUrl(string url) + { + return true; + } + + public override byte[] GetData(string url) + { + var reportData = DbContext.ReportDefinitions.FirstOrDefault(x => x.Name == url); + if (reportData != null) + return reportData.Content; + + if (ReportsFactory.Reports.ContainsKey(url)) + { + using var ms = new MemoryStream(); + using XtraReport report = ReportsFactory.Reports[url](); + report.SaveLayoutToXml(ms); + return ms.ToArray(); + } + throw new DevExpress.XtraReports.Web.ClientControls.FaultException($"Could not find report '{url}'."); + } + + public override Dictionary GetUrls() + { + return DbContext.ReportDefinitions + .ToList() + .Select(x => x.Name) + .Union(ReportsFactory.Reports.Select(x => x.Key)) + .ToDictionary(x => x); + } + + public override void SetData(XtraReport report, string url) + { + using var stream = new MemoryStream(); + report.SaveLayoutToXml(stream); + + var reportData = DbContext.ReportDefinitions.FirstOrDefault(x => x.Name == url); + if (reportData == null) + { + var newReport = new Erp.Reports.ReportDefinitions.ReportDefinition( + Guid.NewGuid(), + url, + url, + stream.ToArray() + ); + DbContext.ReportDefinitions.Add(newReport); + } + else + { + reportData.UpdateContent(stream.ToArray()); + } + DbContext.SaveChanges(); + } + + public override string SetNewData(XtraReport report, string defaultUrl) + { + SetData(report, defaultUrl); + return defaultUrl; + } +} diff --git a/api/src/Erp.Platform.HttpApi.Host/ReportServices/CustomSqlDataConnectionProviderFactory.cs b/api/src/Erp.Platform.HttpApi.Host/ReportServices/CustomSqlDataConnectionProviderFactory.cs new file mode 100644 index 00000000..257c5419 --- /dev/null +++ b/api/src/Erp.Platform.HttpApi.Host/ReportServices/CustomSqlDataConnectionProviderFactory.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using DevExpress.DataAccess.ConnectionParameters; +using DevExpress.DataAccess.Sql; +using DevExpress.DataAccess.Web; +using DevExpress.DataAccess.Wizard.Services; +using Erp.Reports.HttpApi.Host.Data; + +namespace Erp.Platform.ReportServices; + +public class CustomSqlDataConnectionProviderFactory : IConnectionProviderFactory +{ + private readonly LegacyReportDbContext reportDbContext; + + public CustomSqlDataConnectionProviderFactory(LegacyReportDbContext reportDbContext) + { + this.reportDbContext = reportDbContext; + } + + public IConnectionProviderService Create() + { + return new CustomSqlConnectionProviderService(reportDbContext.SqlDataConnections.ToList()); + } +} + +public class CustomSqlConnectionProviderService : IConnectionProviderService +{ + readonly IEnumerable sqlDataConnections; + + public CustomSqlConnectionProviderService(IEnumerable sqlDataConnections) + { + this.sqlDataConnections = sqlDataConnections; + } + + public SqlDataConnection LoadConnection(string connectionName) + { + var sqlDataConnectionData = sqlDataConnections.FirstOrDefault(x => x.Name == connectionName); + if (sqlDataConnectionData == null) + throw new InvalidOperationException(); + + if (string.IsNullOrEmpty(sqlDataConnectionData.ConnectionString)) + throw new KeyNotFoundException($"Connection string '{connectionName}' not found."); + + var connectionParameters = new CustomStringConnectionParameters(sqlDataConnectionData.ConnectionString); + return new SqlDataConnection(connectionName, connectionParameters); + } +} diff --git a/api/src/Erp.Platform.HttpApi.Host/ReportServices/CustomSqlDataSourceWizardConnectionStringsProvider.cs b/api/src/Erp.Platform.HttpApi.Host/ReportServices/CustomSqlDataSourceWizardConnectionStringsProvider.cs new file mode 100644 index 00000000..96a4d39d --- /dev/null +++ b/api/src/Erp.Platform.HttpApi.Host/ReportServices/CustomSqlDataSourceWizardConnectionStringsProvider.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using System.Linq; +using DevExpress.DataAccess.ConnectionParameters; +using DevExpress.DataAccess.Web; +using Erp.Reports.HttpApi.Host.Data; + +namespace Erp.Platform.ReportServices; + +public class CustomSqlDataSourceWizardConnectionStringsProvider : IDataSourceWizardConnectionStringsProvider +{ + readonly LegacyReportDbContext reportDataContext; + + public CustomSqlDataSourceWizardConnectionStringsProvider(LegacyReportDbContext reportDataContext) + { + this.reportDataContext = reportDataContext; + } + + Dictionary IDataSourceWizardConnectionStringsProvider.GetConnectionDescriptions() + { + return reportDataContext.SqlDataConnections.ToDictionary(x => x.Name, x => x.DisplayName); + } + + DataConnectionParametersBase IDataSourceWizardConnectionStringsProvider.GetDataConnectionParameters(string name) + { + return null; + } +} diff --git a/ui/package-lock.json b/ui/package-lock.json index c8147f16..fb300f97 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -29,6 +29,7 @@ "axios": "^1.7.9", "classnames": "^2.5.1", "dayjs": "^1.11.13", + "devexpress-reporting-react": "25.1.7", "devextreme": "25.1.7", "devextreme-dist": "25.1.7", "devextreme-react": "25.1.7", @@ -1714,6 +1715,22 @@ "node": ">=6.9.0" } }, + "node_modules/@devexpress/analytics-core": { + "version": "25.1.7", + "resolved": "https://registry.npmjs.org/@devexpress/analytics-core/-/analytics-core-25.1.7.tgz", + "integrity": "sha512-3H98zZe4uylgiBdIi65z4TIJQa8owrpDVR6o6ACxjH0PtjCVgZr5MgExGEobYrjqK8GUCglUCFwsUDgRVTDlcA==", + "license": "SEE LICENSE IN README.md", + "peer": true, + "dependencies": { + "@types/jquery": "^3.1.1", + "ace-builds": "^1.4.13", + "jquery": "^3.1.1", + "knockout": "~3.5.0" + }, + "peerDependencies": { + "devextreme": "25.1.7" + } + }, "node_modules/@devexpress/utils": { "version": "1.4.8", "resolved": "https://registry.npmjs.org/@devexpress/utils/-/utils-1.4.8.tgz", @@ -3552,6 +3569,16 @@ "@types/react": "*" } }, + "node_modules/@types/jquery": { + "version": "3.5.33", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.33.tgz", + "integrity": "sha512-SeyVJXlCZpEki5F0ghuYe+L+PprQta6nRZqhONt9F13dWBtR/ftoaIbdRQ7cis7womE+X2LKhsDdDtkkDhJS6g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/sizzle": "*" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -3596,9 +3623,9 @@ "license": "MIT" }, "node_modules/@types/prop-types": { - "version": "15.7.15", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", - "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "version": "15.7.13", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", + "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", "license": "MIT" }, "node_modules/@types/raf": { @@ -3622,7 +3649,6 @@ "version": "18.3.7", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", - "devOptional": true, "license": "MIT", "peerDependencies": { "@types/react": "^18.0.0" @@ -3692,6 +3718,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/sizzle": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.10.tgz", + "integrity": "sha512-TC0dmN0K8YcWEAEfiPi5gJP14eJe30TTGjkvek3iM/1NdHHsdCA/Td6GvNndMOo/iSnIsZ4HuuhrYPDAmbxzww==", + "license": "MIT", + "peer": true + }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", @@ -4206,6 +4239,13 @@ "node": ">=6.5" } }, + "node_modules/ace-builds": { + "version": "1.43.5", + "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.43.5.tgz", + "integrity": "sha512-iH5FLBKdB7SVn9GR37UgA/tpQS8OTWIxWAuq3Ofaw+Qbc69FfPXsXd9jeW7KRG2xKpKMqBDnu0tHBrCWY5QI7A==", + "license": "BSD-3-Clause", + "peer": true + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -5762,6 +5802,40 @@ "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", "license": "0BSD" }, + "node_modules/devexpress-reporting": { + "version": "25.1.7", + "resolved": "https://registry.npmjs.org/devexpress-reporting/-/devexpress-reporting-25.1.7.tgz", + "integrity": "sha512-4Ffvj6bgzII63hAfq3u53w0VypDd4HRJHQnQv4yqWm5VyW+7V7ztLLeNQRerbyqjzdhfkN2qyXqFV+3YxZwoEg==", + "license": "SEE LICENSE IN README.md", + "peer": true, + "dependencies": { + "ace-builds": "^1.4.13", + "jquery": "^3.1.1", + "knockout": "~3.5.0" + }, + "peerDependencies": { + "@devexpress/analytics-core": "25.1.7" + } + }, + "node_modules/devexpress-reporting-react": { + "version": "25.1.7", + "resolved": "https://registry.npmjs.org/devexpress-reporting-react/-/devexpress-reporting-react-25.1.7.tgz", + "integrity": "sha512-2PbTbqsAFfZec+BgGSHqU/SBpyyXWps9qx+UD1eC09s7sIHdBvooGMwPI9Vb2+FUiqq1vcDKPxH1hLUqAJYZvg==", + "license": "SEE LICENSE IN README.md", + "dependencies": { + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "@devexpress/analytics-core": "25.1.7", + "@types/prop-types": "15.7.13", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "devexpress-reporting": "25.1.7", + "devextreme-react": "25.1.7", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, "node_modules/devextreme": { "version": "25.1.7", "resolved": "https://registry.npmjs.org/devextreme/-/devextreme-25.1.7.tgz", @@ -8691,6 +8765,13 @@ "jiti": "bin/jiti.js" } }, + "node_modules/jquery": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", + "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", + "license": "MIT", + "peer": true + }, "node_modules/js-sha3": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", @@ -8914,6 +8995,13 @@ "json-buffer": "3.0.1" } }, + "node_modules/knockout": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/knockout/-/knockout-3.5.1.tgz", + "integrity": "sha512-wRJ9I4az0QcsH7A4v4l0enUpkS++MBx0BnL/68KaLzJg7x1qmbjSlwEoCNol7KTYZ+pmtI7Eh2J0Nu6/2Z5J/Q==", + "license": "MIT", + "peer": true + }, "node_modules/language-subtag-registry": { "version": "0.3.23", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", diff --git a/ui/package.json b/ui/package.json index 653df566..44e9dc2a 100644 --- a/ui/package.json +++ b/ui/package.json @@ -40,6 +40,7 @@ "devextreme": "25.1.7", "devextreme-dist": "25.1.7", "devextreme-react": "25.1.7", + "devexpress-reporting-react": "25.1.7", "easy-peasy": "^6.0.5", "emoji-picker-react": "^4.14.1", "exceljs": "^4.4.0", diff --git a/ui/src/views/report/DevexpressReportViewer.tsx b/ui/src/views/report/DevexpressReportViewer.tsx new file mode 100644 index 00000000..76beb61b --- /dev/null +++ b/ui/src/views/report/DevexpressReportViewer.tsx @@ -0,0 +1,29 @@ +import React from 'react' +import { Container } from '@/components/shared' +import { Helmet } from 'react-helmet' +import ReportViewer, { RequestOptions } from 'devexpress-reporting-react/dx-report-viewer' +import { useLocalization } from '@/utils/hooks/useLocalization' +import 'devextreme/dist/css/dx.light.css' +import '@devexpress/analytics-core/dist/css/dx-analytics.common.css' +import '@devexpress/analytics-core/dist/css/dx-analytics.light.css' +import 'devexpress-reporting/dist/css/dx-webdocumentviewer.css' + +const DevexpressReportViewer: React.FC = () => { + const { translate } = useLocalization() + + return ( + + + + + + + + ) +} + +export default DevexpressReportViewer