Add project files.
This commit is contained in:
30
.dockerignore
Normal file
30
.dockerignore
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
**/.classpath
|
||||||
|
**/.dockerignore
|
||||||
|
**/.env
|
||||||
|
**/.git
|
||||||
|
**/.gitignore
|
||||||
|
**/.project
|
||||||
|
**/.settings
|
||||||
|
**/.toolstarget
|
||||||
|
**/.vs
|
||||||
|
**/.vscode
|
||||||
|
**/*.*proj.user
|
||||||
|
**/*.dbmdl
|
||||||
|
**/*.jfm
|
||||||
|
**/azds.yaml
|
||||||
|
**/bin
|
||||||
|
**/charts
|
||||||
|
**/docker-compose*
|
||||||
|
**/Dockerfile*
|
||||||
|
**/node_modules
|
||||||
|
**/npm-debug.log
|
||||||
|
**/obj
|
||||||
|
**/secrets.dev.yaml
|
||||||
|
**/values.dev.yaml
|
||||||
|
LICENSE
|
||||||
|
README.md
|
||||||
|
!**/.gitignore
|
||||||
|
!.git/HEAD
|
||||||
|
!.git/config
|
||||||
|
!.git/packed-refs
|
||||||
|
!.git/refs/heads/**
|
||||||
2
.env
Normal file
2
.env
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
PAPERLESS_TOKEN=cfa320a52bec92cc7bef74415238ee000fc42a3c
|
||||||
|
PAPERLESS_API_URI=https://paperless.camcass.ca/api/
|
||||||
32
Dockerfile
Normal file
32
Dockerfile
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Stage 1: Runtime
|
||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
|
||||||
|
WORKDIR /app
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
# Stage 2: Build
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||||
|
WORKDIR /src
|
||||||
|
|
||||||
|
# Copy both .csproj files to restore dependencies first (better for Docker caching)
|
||||||
|
COPY ["paperless-ngx-export.service/paperless-ngx-export.service.csproj", "paperless-ngx-export.service/"]
|
||||||
|
COPY ["paperless-ngx-export/paperless-ngx-export.csproj", "paperless-ngx-export/"]
|
||||||
|
|
||||||
|
# Restore only the entry point project (it will automatically restore the dependency)
|
||||||
|
RUN dotnet restore "paperless-ngx-export.service/paperless-ngx-export.service.csproj"
|
||||||
|
|
||||||
|
# Copy the rest of the source code for both projects
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build the service
|
||||||
|
WORKDIR "/src/paperless-ngx-export.service"
|
||||||
|
RUN dotnet build "paperless-ngx-export.service.csproj" -c Release -o /app/build
|
||||||
|
|
||||||
|
# Stage 3: Publish
|
||||||
|
FROM build AS publish
|
||||||
|
RUN dotnet publish "paperless-ngx-export.service.csproj" -c Release -o /app/publish /p:UseAppHost=false
|
||||||
|
|
||||||
|
# Stage 4: Final Image
|
||||||
|
FROM base AS final
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=publish /app/publish .
|
||||||
|
ENTRYPOINT ["dotnet", "paperless-ngx-export.service.dll"]
|
||||||
5
Paperness-ngx-export.slnx
Normal file
5
Paperness-ngx-export.slnx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<Solution>
|
||||||
|
<Project Path="paperless-ngx-export.service/paperless-ngx-export.service.csproj" />
|
||||||
|
<Project Path="paperless-ngx-export.terminal/paperless-ngx-export.terminal.csproj" />
|
||||||
|
<Project Path="paperless-ngx-export/paperless-ngx-export.csproj" />
|
||||||
|
</Solution>
|
||||||
1
docker-build-image.sh
Normal file
1
docker-build-image.sh
Normal file
@@ -0,0 +1 @@
|
|||||||
|
docker build -t paperless-ngx-export-api .
|
||||||
9
docker-compose.yml
Normal file
9
docker-compose.yml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
services:
|
||||||
|
paperless-ngx-export-api-service:
|
||||||
|
image: paperless-ngx-export-api
|
||||||
|
container_name: paperless-ngx-export-api-service
|
||||||
|
ports:
|
||||||
|
- "8081:8080"
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
restart: unless-stopped
|
||||||
28
paperless-ngx-export.service/Dockerfile
Normal file
28
paperless-ngx-export.service/Dockerfile
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Use the official .NET 10 ASP.NET runtime
|
||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
|
||||||
|
WORKDIR /app
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
# Use the .NET 10 SDK for building
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||||
|
WORKDIR /src
|
||||||
|
|
||||||
|
# COPY both project files to restore dependencies
|
||||||
|
COPY ["paperless-ngx-export.service/paperless-ngx-export.service.csproj", "paperless-ngx-export.service/"]
|
||||||
|
COPY ["paperless-ngx-export/paperless-ngx-export.csproj", "paperless-ngx-export/"]
|
||||||
|
|
||||||
|
# Restore based on the main project
|
||||||
|
RUN dotnet restore "paperless-ngx-export.service/paperless-ngx-export.service.csproj"
|
||||||
|
|
||||||
|
# Copy everything else and build
|
||||||
|
COPY . .
|
||||||
|
WORKDIR "/src/paperless-ngx-export.service"
|
||||||
|
RUN dotnet build "paperless-ngx-export.service.csproj" -c Release -o /app/build
|
||||||
|
|
||||||
|
FROM build AS publish
|
||||||
|
RUN dotnet publish "paperless-ngx-export.service.csproj" -c Release -o /app/publish /p:UseAppHost=false
|
||||||
|
|
||||||
|
FROM base AS final
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=publish /app/publish .
|
||||||
|
ENTRYPOINT ["dotnet", "paperless-ngx-export.service.dll"]
|
||||||
80
paperless-ngx-export.service/Program.cs
Normal file
80
paperless-ngx-export.service/Program.cs
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using paperless_ngx_export;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection.Metadata.Ecma335;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace paperless_ngx_export.service
|
||||||
|
{
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
app.MapGet("/", (IConfiguration config) =>
|
||||||
|
#if DEBUG
|
||||||
|
{
|
||||||
|
return Results.Content("<html><body><a href=\"/calendar\">Calendar</a><br /><a href=\"/spreadsheet\">Spreadsheet</a></body></html>", "text/html");
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
"API"
|
||||||
|
#endif
|
||||||
|
);
|
||||||
|
|
||||||
|
app.MapGet("/calendar", async (IConfiguration config) =>
|
||||||
|
{
|
||||||
|
// Retrieve the environment variable
|
||||||
|
string? apiToken = config["PAPERLESS_TOKEN"];
|
||||||
|
string? apiBase = config["PAPERLESS_API_URI"];
|
||||||
|
|
||||||
|
if (apiToken == null)
|
||||||
|
{
|
||||||
|
return Results.Problem("PAPERLESS_TOKEN is missing from the environment.");
|
||||||
|
}
|
||||||
|
if (apiBase== null)
|
||||||
|
{
|
||||||
|
return Results.Problem("PAPERLESS_API_URI is missing from the environment.");
|
||||||
|
}
|
||||||
|
|
||||||
|
api.init(apiToken!, apiBase!);
|
||||||
|
|
||||||
|
var caldav = await api.getExpirationDatesCalDAVAsync();
|
||||||
|
return Results.Content(caldav, "text/calendar", Encoding.UTF8);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.MapGet("/spreadsheet", async (IConfiguration config) =>
|
||||||
|
{
|
||||||
|
#if DEBUG
|
||||||
|
string? apiToken = "cfa320a52bec92cc7bef74415238ee000fc42a3c";
|
||||||
|
string? apiBase = "https://paperless.camcass.ca/api/";
|
||||||
|
#else
|
||||||
|
// Retrieve the environment variable
|
||||||
|
string? apiToken = config["PAPERLESS_TOKEN"];
|
||||||
|
string? apiBase = config["PAPERLESS_API_URI"];
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (apiToken == null)
|
||||||
|
{
|
||||||
|
return Results.Problem("PAPERLESS_TOKEN is missing from the environment.");
|
||||||
|
}
|
||||||
|
if (apiBase == null)
|
||||||
|
{
|
||||||
|
return Results.Problem("PAPERLESS_API_URI is missing from the environment.");
|
||||||
|
}
|
||||||
|
|
||||||
|
api.init(apiToken!, apiBase!);
|
||||||
|
|
||||||
|
var content = await spreadsheet.GetSummarySpreadsheetAsByteArrayAsync();
|
||||||
|
|
||||||
|
return Results.File(
|
||||||
|
content,
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||||
|
"Report.xlsx");
|
||||||
|
});
|
||||||
|
|
||||||
|
app.Run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
24
paperless-ngx-export.service/Properties/launchSettings.json
Normal file
24
paperless-ngx-export.service/Properties/launchSettings.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"http": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
},
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"applicationUrl": "http://localhost:5066"
|
||||||
|
},
|
||||||
|
"Container (Dockerfile)": {
|
||||||
|
"commandName": "Docker",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_HTTP_PORTS": "8080"
|
||||||
|
},
|
||||||
|
"publishAllPorts": true,
|
||||||
|
"useSSL": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"$schema": "https://json.schemastore.org/launchsettings.json"
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
paperless-ngx-export.service/appsettings.json
Normal file
9
paperless-ngx-export.service/appsettings.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*"
|
||||||
|
}
|
||||||
9
paperless-ngx-export.service/docker-compose.yml
Normal file
9
paperless-ngx-export.service/docker-compose.yml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
services:
|
||||||
|
paperless-ngx-export-api-service:
|
||||||
|
image: paperless-ngx-export-api
|
||||||
|
ports:
|
||||||
|
- "8082:8080"
|
||||||
|
environment:
|
||||||
|
- PAPERLESS_TOKEN=cfa320a52bec92cc7bef74415238ee000fc42a3c
|
||||||
|
- PAPERLESS_API_URI=https://paperless.camcass.ca/api/
|
||||||
|
restart: unless-stopped
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<RootNamespace>paperness_ngx_export.service</RootNamespace>
|
||||||
|
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.23.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\paperless-ngx-export\paperless-ngx-export.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
21
paperless-ngx-export.terminal/TerminalProgram.cs
Normal file
21
paperless-ngx-export.terminal/TerminalProgram.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
namespace paperless_ngx_export.terminal
|
||||||
|
{
|
||||||
|
internal class TerminalProgram
|
||||||
|
{
|
||||||
|
static void Main(string[] args)
|
||||||
|
{
|
||||||
|
api.init("cfa320a52bec92cc7bef74415238ee000fc42a3c", "https://paperless.camcass.ca/api/");
|
||||||
|
|
||||||
|
var outputString = api.getExpirationDatesCalDAVAsync().Result;
|
||||||
|
|
||||||
|
using (var writer = new StreamWriter("Z:\\paperless_expirationdates.ics", false))
|
||||||
|
{
|
||||||
|
writer.Write(outputString);
|
||||||
|
writer.Flush();
|
||||||
|
writer.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("Hello, World!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<RootNamespace>paperless_ngx_export.terminal</RootNamespace>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\paperless-ngx-export\paperless-ngx-export.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
18
paperless-ngx-export/Models/Correspondent.cs
Normal file
18
paperless-ngx-export/Models/Correspondent.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace paperless_ngx_export.Models
|
||||||
|
{
|
||||||
|
public class Correspondent : ResultBase
|
||||||
|
{
|
||||||
|
public string slug { get; set; }
|
||||||
|
public string name { get; set; }
|
||||||
|
public int document_count { get; set; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
43
paperless-ngx-export/Models/CustomField.cs
Normal file
43
paperless-ngx-export/Models/CustomField.cs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace paperless_ngx_export.Models
|
||||||
|
{
|
||||||
|
public enum CustomField_DataType
|
||||||
|
{
|
||||||
|
select,
|
||||||
|
date,
|
||||||
|
@string
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CustomField_ExtraData
|
||||||
|
{
|
||||||
|
public CustomField_Select_Option[] select_options { get; set; }
|
||||||
|
public string? default_currency { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CustomField_Select_Option
|
||||||
|
{
|
||||||
|
public string id { get; set; }
|
||||||
|
public string label { get; set; }
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{label} ({id})";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CustomField : ResultBase
|
||||||
|
{
|
||||||
|
public string name { get; set; }
|
||||||
|
public CustomField_DataType data_type { get; set; }
|
||||||
|
public int? document_count { get; set; }
|
||||||
|
|
||||||
|
public CustomField_ExtraData? extra_data { get; set; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{name} ({data_type})";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
paperless-ngx-export/Models/CustomFieldKeyValuePair.cs
Normal file
17
paperless-ngx-export/Models/CustomFieldKeyValuePair.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace paperless_ngx_export.Models
|
||||||
|
{
|
||||||
|
public class CustomFieldKeyValuePair
|
||||||
|
{
|
||||||
|
public int field { get; set; }
|
||||||
|
public string value { get; set; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{field}: {value}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
66
paperless-ngx-export/Models/CustomFieldParsed.cs
Normal file
66
paperless-ngx-export/Models/CustomFieldParsed.cs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace paperless_ngx_export.Models
|
||||||
|
{
|
||||||
|
public class CustomFieldParsed
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public object? Value { get; set; }
|
||||||
|
public Type DataType { get; set; }
|
||||||
|
|
||||||
|
public string StringValue => (string)Value;
|
||||||
|
|
||||||
|
public DateTime DateValue => Value != null && DataType == typeof(DateTime) ? (DateTime)Value : DateTime.MinValue;
|
||||||
|
|
||||||
|
public static CustomFieldParsed FromCustomFieldKeyValuePair(CustomFieldKeyValuePair pair, IEnumerable<CustomField> customFields)
|
||||||
|
{
|
||||||
|
if (pair.value != null && customFields.FirstOrDefault(cf => cf.Id == pair.field) is CustomField field)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
switch (field.data_type)
|
||||||
|
{
|
||||||
|
case CustomField_DataType.date:
|
||||||
|
return new CustomFieldParsed
|
||||||
|
{
|
||||||
|
Name = field.name,
|
||||||
|
Value = DateTime.Parse(pair.value),
|
||||||
|
DataType = typeof(DateTime)
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case CustomField_DataType.select:
|
||||||
|
return new CustomFieldParsed
|
||||||
|
{
|
||||||
|
Name = field.name,
|
||||||
|
Value = field.extra_data.select_options.First(so => so.id == pair.value).label,
|
||||||
|
DataType = typeof(string)
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return new CustomFieldParsed
|
||||||
|
{
|
||||||
|
Name = field.name,
|
||||||
|
Value = pair.value,
|
||||||
|
DataType = typeof(string)
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{Name}: {Value} ({DataType.Name})";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
paperless-ngx-export/Models/Document.cs
Normal file
29
paperless-ngx-export/Models/Document.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace paperless_ngx_export.Models
|
||||||
|
{
|
||||||
|
public class Document : ResultBase
|
||||||
|
{
|
||||||
|
public int? correspondent { get; set; }
|
||||||
|
public int? document_type { get; set; }
|
||||||
|
public int? storage_path { get; set; }
|
||||||
|
public string title { get; set; }
|
||||||
|
public string content { get; set; }
|
||||||
|
public int[] tags { get; set; }
|
||||||
|
public DateTime created { get; set; }
|
||||||
|
public DateTime created_date { get; set; }
|
||||||
|
public DateTimeOffset modified { get; set; }
|
||||||
|
public DateTimeOffset added { get; set; }
|
||||||
|
public DateTimeOffset? deleted_at { get; set; }
|
||||||
|
public string? archive_serial_number { get; set; }
|
||||||
|
public string original_file_name { get; set; }
|
||||||
|
public string archived_file_name { get; set; }
|
||||||
|
public int owner { get; set; }
|
||||||
|
public string[] notes { get; set; }
|
||||||
|
public CustomFieldKeyValuePair[] custom_fields { get; set; }
|
||||||
|
public int? page_count { get; set; }
|
||||||
|
public string mime_type { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
65
paperless-ngx-export/Models/DocumentParsed.cs
Normal file
65
paperless-ngx-export/Models/DocumentParsed.cs
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace paperless_ngx_export.Models
|
||||||
|
{
|
||||||
|
public class DocumentParsed
|
||||||
|
{
|
||||||
|
#region static arrays
|
||||||
|
public static Tag[] _tags { get; set; } = new Tag[0];
|
||||||
|
public static Correspondent[] _correspondents { get; set; } = new Correspondent[0];
|
||||||
|
public static CustomField[] _customFields { get; set; } = new CustomField[0];
|
||||||
|
public static DocumentType[] _documentTypes { get; set; } = new DocumentType[0];
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public string Correspondent { get; protected set; }
|
||||||
|
public string DocumentType { get; protected set; } = "Unsorted";
|
||||||
|
public string Title { get; protected set; }
|
||||||
|
public string Content { get; protected set; }
|
||||||
|
public List<Tag> Tags { get; protected set; } = new List<Tag>();
|
||||||
|
public DateTime Created { get; protected set; }
|
||||||
|
public DateTimeOffset Modified { get; protected set; }
|
||||||
|
public DateTimeOffset Added { get; protected set; }
|
||||||
|
public DateTimeOffset? DeletedAt { get; protected set; }
|
||||||
|
public List<string> Notes{ get; protected set; }=new List<string>();
|
||||||
|
public string MIMEType { get; protected set; }
|
||||||
|
public int? PageCount { get; protected set; }
|
||||||
|
public List<CustomFieldParsed> CustomFields { get; protected set; } = new List<CustomFieldParsed>();
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{Correspondent} {DocumentType}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DocumentParsed FromDocument(Document doc)
|
||||||
|
{
|
||||||
|
DocumentParsed d = new DocumentParsed();
|
||||||
|
|
||||||
|
d.Correspondent = _correspondents.First(c => c.Id == doc.correspondent).name;
|
||||||
|
d.DocumentType = _documentTypes.FirstOrDefault(dt => dt.Id == doc.document_type)?.name ?? "Unsorted";
|
||||||
|
d.Title=doc.title.Trim();
|
||||||
|
d.Content = doc.content.Trim();
|
||||||
|
_tags.Where(_t=>doc.tags.Any(dt=>_t.Id == dt)).ToList().ForEach(t => d.Tags.Add(t));
|
||||||
|
d.Created=doc.created;
|
||||||
|
d.Modified=doc.modified;
|
||||||
|
d.Added=doc.added;
|
||||||
|
d.DeletedAt = doc.deleted_at;
|
||||||
|
d.Notes = doc.notes.ToList(); ;
|
||||||
|
d.MIMEType = doc.mime_type;
|
||||||
|
d.PageCount = doc.page_count;
|
||||||
|
_customFields.Where(_cf => doc.custom_fields.Any(dcf => _cf.Id == dcf.field)).ToList()
|
||||||
|
.ForEach(cf =>
|
||||||
|
{
|
||||||
|
var cfp = CustomFieldParsed.FromCustomFieldKeyValuePair(
|
||||||
|
doc.custom_fields.First(dcf => dcf.field == cf.Id),
|
||||||
|
_customFields);
|
||||||
|
if (!(cfp is null))
|
||||||
|
{
|
||||||
|
d.CustomFields.Add(cfp);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
paperless-ngx-export/Models/DocumentType.cs
Normal file
18
paperless-ngx-export/Models/DocumentType.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace paperless_ngx_export.Models
|
||||||
|
{
|
||||||
|
public class DocumentType : ResultBase
|
||||||
|
{
|
||||||
|
public string slug { get; set; }
|
||||||
|
public string name { get; set; }
|
||||||
|
public int? document_count { get; set; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
paperless-ngx-export/Models/ResultBase.cs
Normal file
11
paperless-ngx-export/Models/ResultBase.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace paperless_ngx_export.Models
|
||||||
|
{
|
||||||
|
public class ResultBase
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
12
paperless-ngx-export/Models/RootObject.cs
Normal file
12
paperless-ngx-export/Models/RootObject.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace paperless_ngx_export.Models
|
||||||
|
{
|
||||||
|
internal class RootObject<T> where T : ResultBase
|
||||||
|
{
|
||||||
|
public int Count { get; set; }
|
||||||
|
public List<T> Results { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
19
paperless-ngx-export/Models/Tag.cs
Normal file
19
paperless-ngx-export/Models/Tag.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace paperless_ngx_export.Models
|
||||||
|
{
|
||||||
|
public class Tag : ResultBase
|
||||||
|
{
|
||||||
|
public string slug { get; set; }
|
||||||
|
public string name { get; set; }
|
||||||
|
public string color { get; set; }
|
||||||
|
public string text_color { get; set; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{name} ({Id})";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
310
paperless-ngx-export/api.cs
Normal file
310
paperless-ngx-export/api.cs
Normal file
@@ -0,0 +1,310 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
using paperless_ngx_export.Models;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
|
||||||
|
namespace paperless_ngx_export
|
||||||
|
{
|
||||||
|
public static class api
|
||||||
|
{
|
||||||
|
public static bool isInitialized { get; private set; } = false;
|
||||||
|
static string paperless_api_base;
|
||||||
|
static string token;
|
||||||
|
|
||||||
|
public static void init(string token, string paperless_api_base)
|
||||||
|
{
|
||||||
|
api.paperless_api_base = paperless_api_base.Trim();
|
||||||
|
setToken(token.Trim());
|
||||||
|
|
||||||
|
isInitialized = !string.IsNullOrWhiteSpace(api.paperless_api_base) && !string.IsNullOrEmpty(api.token);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setToken(string token)
|
||||||
|
{
|
||||||
|
api.token = token.Trim().Trim('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HttpRequestMessage getHttpRequestMessage(string query, int page = -1)
|
||||||
|
{
|
||||||
|
if (page > 0)
|
||||||
|
{
|
||||||
|
var pagedQuery = query.Contains('?') ? $"{query}&page={page}" : $"{query}?page={page}";
|
||||||
|
return getHttpRequestMessage(pagedQuery, HttpMethod.Get);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return getHttpRequestMessage(query, HttpMethod.Get);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HttpRequestMessage getHttpRequestMessage(string query, HttpMethod httpMethod)
|
||||||
|
{
|
||||||
|
HttpRequestMessage message = new HttpRequestMessage(httpMethod, $"{api.paperless_api_base}{query}");
|
||||||
|
//message.Headers.Authorization = getAuthenticationHeaderValue();
|
||||||
|
message.Headers.Add("Authorization", $"Token {api.token}");
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AuthenticationHeaderValue getAuthenticationHeaderValue()
|
||||||
|
{
|
||||||
|
var header = new AuthenticationHeaderValue("Token", api.token);
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<IEnumerable<Tag>> getTagsAsync()
|
||||||
|
{
|
||||||
|
var query = "tags/";
|
||||||
|
var tags = new List<Tag>();
|
||||||
|
if (!isInitialized) { throw new NotSupportedException("API base URL or token not initialized"); }
|
||||||
|
using (HttpClient client = new HttpClient())
|
||||||
|
{
|
||||||
|
var count = -1;
|
||||||
|
var page = 1;
|
||||||
|
while (count != 0)
|
||||||
|
{
|
||||||
|
count = 0;
|
||||||
|
var response = await client.SendAsync(getHttpRequestMessage(query, page));
|
||||||
|
if (response != null && response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var responseString = await response.Content.ReadAsStringAsync();
|
||||||
|
var root = JsonConvert.DeserializeObject<RootObject<Tag>>(responseString);
|
||||||
|
if (root.Results.Any())
|
||||||
|
{
|
||||||
|
count = root.Results.Count();
|
||||||
|
tags.AddRange(root.Results);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
page++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<IEnumerable<Document>> getCurrentDocumentsAsync(bool includeNonExpirableDocuments = true)
|
||||||
|
{
|
||||||
|
var query = "documents/";
|
||||||
|
var excludedTagIds = (await getTagsAsync()).Where(x => (includeNonExpirableDocuments && x.name == "No Expiration") || x.name == "Action:Renewed" || x.name == "HR:Inactive").Select(x => x.Id).ToList();
|
||||||
|
excludedTagIds.ForEach(id => query += query.Contains('?') ? $"&tags__id__none={id}" : $"?tags__id__none={id}");
|
||||||
|
|
||||||
|
var documents = new List<Document>();
|
||||||
|
if (!isInitialized) { throw new NotSupportedException("API base URL or token not initialized"); }
|
||||||
|
using (HttpClient client = new HttpClient())
|
||||||
|
{
|
||||||
|
var count = -1;
|
||||||
|
var page = 1;
|
||||||
|
while (count != 0)
|
||||||
|
{
|
||||||
|
count = 0;
|
||||||
|
var response = await client.SendAsync(getHttpRequestMessage(query, page));
|
||||||
|
if (response != null && response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var responseString = await response.Content.ReadAsStringAsync();
|
||||||
|
var root = JsonConvert.DeserializeObject<RootObject<Document>>(responseString);
|
||||||
|
if (root.Results.Any())
|
||||||
|
{
|
||||||
|
count = root.Results.Count();
|
||||||
|
foreach (var document in root.Results)
|
||||||
|
{
|
||||||
|
bool isValid = true;
|
||||||
|
foreach (var t in document.tags)
|
||||||
|
if (excludedTagIds.Contains(t))
|
||||||
|
isValid = false;
|
||||||
|
|
||||||
|
if(isValid)
|
||||||
|
documents.Add(document);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
page++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return documents.OrderBy(d=>d.correspondent).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<IEnumerable<Document>> getAllDocumentsAsync()
|
||||||
|
{
|
||||||
|
var query = "documents/";
|
||||||
|
var documents = new List<Document>();
|
||||||
|
if (!isInitialized) { throw new NotSupportedException("API base URL or token not initialized"); }
|
||||||
|
using (HttpClient client = new HttpClient())
|
||||||
|
{
|
||||||
|
var count = -1;
|
||||||
|
var page = 1;
|
||||||
|
while (count != 0)
|
||||||
|
{
|
||||||
|
count = 0;
|
||||||
|
var response = await client.SendAsync(getHttpRequestMessage(query, page));
|
||||||
|
if (response != null && response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var responseString = await response.Content.ReadAsStringAsync();
|
||||||
|
var root = JsonConvert.DeserializeObject<RootObject<Document>>(responseString);
|
||||||
|
if (root.Results.Any())
|
||||||
|
{
|
||||||
|
count = root.Results.Count();
|
||||||
|
documents.AddRange(root.Results);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
page++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return documents;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<IEnumerable<Correspondent>> getCorrespondentsAsync()
|
||||||
|
{
|
||||||
|
var query = "correspondents/";
|
||||||
|
if (!isInitialized) { throw new NotSupportedException("API base URL or token not initialized"); }
|
||||||
|
var correspondents = new List<Correspondent>();
|
||||||
|
using (HttpClient client = new HttpClient())
|
||||||
|
{
|
||||||
|
var count = -1;
|
||||||
|
var page = 1;
|
||||||
|
while (count != 0)
|
||||||
|
{
|
||||||
|
count = 0;
|
||||||
|
var response = await client.SendAsync(getHttpRequestMessage(query, page));
|
||||||
|
if (response != null && response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var responseString = await response.Content.ReadAsStringAsync();
|
||||||
|
var root = JsonConvert.DeserializeObject<RootObject<Correspondent>>(responseString);
|
||||||
|
if (root.Results.Any())
|
||||||
|
{
|
||||||
|
count = root.Results.Count();
|
||||||
|
|
||||||
|
correspondents.AddRange(root.Results);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
page++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return correspondents;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<IEnumerable<CustomField>> getCustomFieldsAsync()
|
||||||
|
{
|
||||||
|
var query = "custom_fields/";
|
||||||
|
var fields = new List<CustomField>();
|
||||||
|
if (!isInitialized) { throw new NotSupportedException("API base URL or token not initialized"); }
|
||||||
|
using (HttpClient client = new HttpClient())
|
||||||
|
{
|
||||||
|
var count = -1;
|
||||||
|
var page = 1;
|
||||||
|
while (count != 0)
|
||||||
|
{
|
||||||
|
count = 0;
|
||||||
|
var response = await client.SendAsync(getHttpRequestMessage(query, page));
|
||||||
|
if (response != null && response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var responseString = await response.Content.ReadAsStringAsync();
|
||||||
|
var root = JsonConvert.DeserializeObject<RootObject<CustomField>>(responseString);
|
||||||
|
if (root.Results.Any())
|
||||||
|
{
|
||||||
|
count = root.Results.Count();
|
||||||
|
fields.AddRange(root.Results);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
page++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<IEnumerable<DocumentType>> getDocumentTypesAsync()
|
||||||
|
{
|
||||||
|
var query = "document_types/";
|
||||||
|
var doctypes = new List<DocumentType>();
|
||||||
|
if (!isInitialized) { throw new NotSupportedException("API base URL or token not initialized"); }
|
||||||
|
using (HttpClient client = new HttpClient())
|
||||||
|
{
|
||||||
|
var count = -1;
|
||||||
|
var page = 1;
|
||||||
|
while (count != 0)
|
||||||
|
{
|
||||||
|
count = 0;
|
||||||
|
var response = await client.SendAsync(getHttpRequestMessage(query, page));
|
||||||
|
if (response != null && response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var responseString = await response.Content.ReadAsStringAsync();
|
||||||
|
var root = JsonConvert.DeserializeObject<RootObject<DocumentType>>(responseString);
|
||||||
|
if (root.Results.Any())
|
||||||
|
{
|
||||||
|
count = root.Results.Count();
|
||||||
|
doctypes.AddRange(root.Results);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
page++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return doctypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<string> getExpirationDatesCalDAVAsync(bool onlyCurrentDocuments = true)
|
||||||
|
{
|
||||||
|
string caldav = @"BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
PRODID:-//CAMCASS//Expiring Documents Calendar//EN
|
||||||
|
X-WR-CALNAME:Expiring Documents
|
||||||
|
X-WR-CALDESC:Onboarded staff's document expiration dates
|
||||||
|
X-PUBLISHED-TTL:PT12H";
|
||||||
|
|
||||||
|
if (!isInitialized) { throw new NotSupportedException("API base URL or token not initialized"); }
|
||||||
|
|
||||||
|
var documents = (await getMergedDocumentMetadataAsync(onlyCurrentDocuments, false)).OrderBy(x=>x.Correspondent).ToList();
|
||||||
|
documents.Where(d => d.CustomFields.Any(f => f.Name.StartsWith("Expiration", StringComparison.OrdinalIgnoreCase)))
|
||||||
|
.ToList()
|
||||||
|
.ForEach(doc =>
|
||||||
|
{
|
||||||
|
var calDAVEvent = getCalDAVEvent(doc, doc.CustomFields.First(f => f.Name.StartsWith("expir", StringComparison.OrdinalIgnoreCase)).DateValue);
|
||||||
|
caldav += @$"
|
||||||
|
{calDAVEvent}";
|
||||||
|
});
|
||||||
|
|
||||||
|
caldav += @"
|
||||||
|
END:VCALENDAR";
|
||||||
|
return caldav;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string getCalDAVEvent(DocumentParsed document, DateTime date)
|
||||||
|
{
|
||||||
|
var nextDay = date.AddDays(1);
|
||||||
|
var DTSTAMP = $"{DateTime.UtcNow.Year:0000}{DateTime.UtcNow.Month:00}{DateTime.UtcNow.Day:00}T{DateTime.UtcNow.Hour:00}{DateTime.UtcNow.Minute:00}00Z";
|
||||||
|
return $@"BEGIN:VEVENT
|
||||||
|
UID:{Guid.NewGuid().ToString().Replace("-","")}
|
||||||
|
DTSTAMP:{DTSTAMP}
|
||||||
|
DTSTART;VALUE=DATE:{date.Year:0000}{date.Month:00}{date.Day:00}
|
||||||
|
DTEND;VALUE=DATE:{nextDay.Year:0000}{nextDay.Month:00}{nextDay.Day:00}
|
||||||
|
SUMMARY:{document.DocumentType} - {document.Correspondent}
|
||||||
|
END:VEVENT";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<IEnumerable<DocumentParsed>> getMergedDocumentMetadataAsync(bool onlyCurrentDocuments = true, bool includeNonExpirableDocuments=true)
|
||||||
|
{
|
||||||
|
if (!isInitialized) { throw new NotSupportedException("API base URL or token not initialized"); }
|
||||||
|
|
||||||
|
var docs = onlyCurrentDocuments ? await getCurrentDocumentsAsync(includeNonExpirableDocuments) : await getAllDocumentsAsync();
|
||||||
|
var tags = await getTagsAsync();
|
||||||
|
var correspondents = await getCorrespondentsAsync();
|
||||||
|
var customFields = await getCustomFieldsAsync();
|
||||||
|
var documenttypes = await getDocumentTypesAsync();
|
||||||
|
|
||||||
|
DocumentParsed._tags = tags.ToArray();
|
||||||
|
DocumentParsed._correspondents = correspondents.ToArray();
|
||||||
|
DocumentParsed._customFields = customFields.ToArray();
|
||||||
|
DocumentParsed._documentTypes = documenttypes.ToArray();
|
||||||
|
|
||||||
|
|
||||||
|
var documents = new List<DocumentParsed>();
|
||||||
|
docs.ToList().ForEach(document => documents.Add(DocumentParsed.FromDocument(document)));
|
||||||
|
|
||||||
|
return documents;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
paperless-ngx-export/paperless-ngx-export.csproj
Normal file
15
paperless-ngx-export/paperless-ngx-export.csproj
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<RootNamespace>paperless_ngx_export</RootNamespace>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="ClosedXML" Version="0.105.0" />
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
212
paperless-ngx-export/spreadsheet.cs
Normal file
212
paperless-ngx-export/spreadsheet.cs
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
using ClosedXML.Excel;
|
||||||
|
using DocumentFormat.OpenXml.Drawing.Charts;
|
||||||
|
using DocumentFormat.OpenXml.Spreadsheet;
|
||||||
|
using paperless_ngx_export.Models;
|
||||||
|
using RBush;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace paperless_ngx_export
|
||||||
|
{
|
||||||
|
public static class spreadsheet
|
||||||
|
{
|
||||||
|
public static async Task<byte[]> GetSummarySpreadsheetAsByteArrayAsync()
|
||||||
|
{
|
||||||
|
byte[] bytes = null;
|
||||||
|
|
||||||
|
using (var stream = new MemoryStream())
|
||||||
|
{
|
||||||
|
var workbook = await GetSummaryAsXLWorkbookAsync();
|
||||||
|
workbook.SaveAs(stream);
|
||||||
|
bytes = stream.ToArray();
|
||||||
|
stream.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes ?? new byte[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void PopulateDocumentTypeWorksheets(
|
||||||
|
IEnumerable<DocumentType> documentTypes,
|
||||||
|
IEnumerable<Tag> tags,
|
||||||
|
IEnumerable<CustomField> customFields,
|
||||||
|
IEnumerable<DocumentParsed> docs,
|
||||||
|
XLWorkbook workbook,
|
||||||
|
bool onlyCurrentDocuments = true)
|
||||||
|
{
|
||||||
|
var headings = new string[]
|
||||||
|
{
|
||||||
|
"Correspondent",
|
||||||
|
"Role",
|
||||||
|
"Specialty",
|
||||||
|
"Document Type",
|
||||||
|
"Expiration Date",
|
||||||
|
"Tags"
|
||||||
|
};
|
||||||
|
|
||||||
|
var documents = docs.Where(x =>
|
||||||
|
{
|
||||||
|
if (!x.CustomFields.Any(xx => xx.Name.Contains("expir", StringComparison.OrdinalIgnoreCase)))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return onlyCurrentDocuments ? !x.Tags.Any(xx => xx.name.Contains("renewed", StringComparison.OrdinalIgnoreCase)) : true;
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
foreach (var documentType in documentTypes.OrderBy(x=>!documents.Any(d=>d.DocumentType == x.name && d.CustomFields.Any(cf=>cf.Name.Contains("expir", StringComparison.OrdinalIgnoreCase)))).
|
||||||
|
ThenBy(x=>x.name))
|
||||||
|
{
|
||||||
|
var sheetName = documentType.name.Replace('/', '-');
|
||||||
|
var sheet = workbook.Worksheets.FirstOrDefault(x => x.Name == sheetName);
|
||||||
|
if (sheet == null)
|
||||||
|
{
|
||||||
|
sheet = workbook.Worksheets.Add(sheetName);
|
||||||
|
sheet.TabColor = XLColor.Green;
|
||||||
|
|
||||||
|
for (int i = 0; i < headings.Length; i++)
|
||||||
|
{
|
||||||
|
sheet.Cell(1, i + 1).Value = headings[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
sheet.SheetView.Freeze(1, 2);
|
||||||
|
sheet.SetAutoFilter(true);
|
||||||
|
sheet.Cells("A1:ZZ1").Style.Font.Bold = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var document in documents.Where(x => x.DocumentType == documentType.name).OrderBy(x => x.Correspondent))
|
||||||
|
{
|
||||||
|
var rowColour = XLColor.Transparent;
|
||||||
|
var expirationDate = document.CustomFields.FirstOrDefault(x => x.Name.Contains("expir", StringComparison.OrdinalIgnoreCase))?.DateValue;
|
||||||
|
|
||||||
|
if (expirationDate != null && expirationDate <= DateTime.Today)
|
||||||
|
{
|
||||||
|
rowColour = XLColor.Red;
|
||||||
|
}
|
||||||
|
else if (expirationDate != null && expirationDate?.AddDays(-30) < DateTime.Today)
|
||||||
|
{
|
||||||
|
rowColour = XLColor.Orange;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(sheet.TabColor == XLColor.Green
|
||||||
|
&& rowColour != XLColor.Transparent)
|
||||||
|
{
|
||||||
|
sheet.TabColor = sheet.TabColor == XLColor.Red ? XLColor.Red : rowColour;
|
||||||
|
}
|
||||||
|
|
||||||
|
var rowIndex = sheet.LastRowUsed()?.RowNumber() + 1 ?? 2;
|
||||||
|
sheet.Cell(rowIndex, 1).Value = document.Correspondent;
|
||||||
|
sheet.Cell(rowIndex, 2).Value = document.CustomFields.FirstOrDefault(x=>x.Name == "Role")?.StringValue;
|
||||||
|
sheet.Cell(rowIndex, 3).Value = document.CustomFields.FirstOrDefault(x => x.Name == "Specialty")?.StringValue;
|
||||||
|
sheet.Cell(rowIndex, 4).Value = document.DocumentType;
|
||||||
|
sheet.Cell(rowIndex, 5).Value = expirationDate == null ? Blank.Value : expirationDate;
|
||||||
|
sheet.Cell(rowIndex, 5).Style.Fill.BackgroundColor = rowColour == XLColor.Transparent ? sheet.Cell(rowIndex, 5).Style.Fill.BackgroundColor : rowColour;
|
||||||
|
sheet.Cell(rowIndex, 6).Value = String.Concat(document.Tags.Select(x=>x.name + ' ').ToArray()).Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
sheet.Columns().AdjustToContents();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void PopulateSummaryWorksheet(
|
||||||
|
IEnumerable<DocumentType> documentTypes,
|
||||||
|
IEnumerable<Tag> tags,
|
||||||
|
IEnumerable<CustomField> customFields,
|
||||||
|
IEnumerable<DocumentParsed> docs,
|
||||||
|
XLWorkbook workbook,
|
||||||
|
bool onlyCurrentDocuments = true)
|
||||||
|
{
|
||||||
|
var headers = new string[]
|
||||||
|
{
|
||||||
|
"Correspondent",
|
||||||
|
"Role (Specialty)",
|
||||||
|
"Professional Registration",
|
||||||
|
"Liability Insurance",
|
||||||
|
"ACLS",
|
||||||
|
"BLS",
|
||||||
|
"PALS",
|
||||||
|
"Vaccination Record",
|
||||||
|
"CV / Resumé",
|
||||||
|
"Misc Certificate",
|
||||||
|
"Credentialing / Privileges",
|
||||||
|
"Reference Letter"
|
||||||
|
};
|
||||||
|
|
||||||
|
var documents = docs.Where(x =>
|
||||||
|
{
|
||||||
|
if (!x.CustomFields.Any(xx => xx.Name.Contains("expir", StringComparison.OrdinalIgnoreCase)))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return onlyCurrentDocuments ? !x.Tags.Any(xx => xx.name.Contains("renewed", StringComparison.OrdinalIgnoreCase)) : true;
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
var summarySheet = workbook.Worksheets.Add("Summary");
|
||||||
|
summarySheet.SheetView.Freeze(1, 2); //Freeze header row and, correspondent and role columns
|
||||||
|
for (int i = 1; i <= headers.Length; i++)
|
||||||
|
{
|
||||||
|
summarySheet.Cell(1, i).Value = headers[i - 1];
|
||||||
|
}
|
||||||
|
summarySheet.Cells("A1:ZZ1").Style.Font.Bold = true;
|
||||||
|
summarySheet.SetAutoFilter();
|
||||||
|
|
||||||
|
var row = 2;
|
||||||
|
foreach (var correspondent in documents.Select(x => x.Correspondent).Distinct())
|
||||||
|
{
|
||||||
|
summarySheet.Cell(row, 1).Value = correspondent;
|
||||||
|
|
||||||
|
foreach (var doc in documents.Where(x => x.Correspondent == correspondent))
|
||||||
|
{
|
||||||
|
if (summarySheet.Cell(row, 2).Value.Type == XLDataType.Blank)
|
||||||
|
{
|
||||||
|
var role = doc.CustomFields.FirstOrDefault(x => x.Name == "Role")?.Value?.ToString() ?? "";
|
||||||
|
var specialty = doc.CustomFields.FirstOrDefault(x => x.Name == "Specialty")?.Value?.ToString() ?? "";
|
||||||
|
role = $"{role} {(!string.IsNullOrWhiteSpace(specialty) ? $"({specialty})" : "")}".Trim();
|
||||||
|
summarySheet.Cell(row, 2).Value = role;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headers.IndexOf(doc.DocumentType.ToString()) + 1 is int column && column > 0)
|
||||||
|
{
|
||||||
|
XLCellValue cellValue = summarySheet.Cell(row, column).Value;
|
||||||
|
|
||||||
|
if (!cellValue.IsBlank)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doc.DocumentType == "Reference Letter")
|
||||||
|
{
|
||||||
|
cellValue = documents.Where(x => x.Correspondent == correspondent && x.DocumentType == "Reference Letter").Count();
|
||||||
|
;
|
||||||
|
}
|
||||||
|
else if (doc.CustomFields.FirstOrDefault(x => x.Name == "Expiration Date") is CustomFieldParsed customFieldParsed &&
|
||||||
|
customFieldParsed.DateValue > DateTime.MinValue && customFieldParsed.DateValue < DateTime.MaxValue)
|
||||||
|
{
|
||||||
|
cellValue = XLCellValue.FromObject(customFieldParsed.DateValue);
|
||||||
|
}
|
||||||
|
else if (!doc.CustomFields.Any(x => x.Name == "Expiration Date"))
|
||||||
|
{
|
||||||
|
cellValue = "Present";
|
||||||
|
}
|
||||||
|
|
||||||
|
summarySheet.Cell(row, column).Value = cellValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
row++;
|
||||||
|
}
|
||||||
|
|
||||||
|
summarySheet.Columns().AdjustToContents();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<XLWorkbook> GetSummaryAsXLWorkbookAsync()
|
||||||
|
{
|
||||||
|
var documenttypes = await api.getDocumentTypesAsync();
|
||||||
|
var tags = await api.getTagsAsync();
|
||||||
|
var customFields = await api.getCustomFieldsAsync();
|
||||||
|
var documents = (await api.getMergedDocumentMetadataAsync(false, true)).Where(x => !x.CustomFields.Any(xx => xx.Name == "Action:Renewed")).OrderBy(x => x.Correspondent).ToList();
|
||||||
|
var workbook = new XLWorkbook();
|
||||||
|
|
||||||
|
PopulateSummaryWorksheet(documenttypes, tags, customFields, documents, workbook, true);
|
||||||
|
PopulateDocumentTypeWorksheets(documenttypes, tags, customFields, documents, workbook, true);
|
||||||
|
return workbook;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user