feat: initial project setup
- Add .NET 8 backend with Clean Architecture - Add React + Vite + TypeScript frontend - Implement authentication with JWT - Implement Azure Blob Storage client - Implement OCR integration - Implement supplier matching service - Implement voucher generation - Implement Fortnox provider - Add unit and integration tests - Add Docker Compose configuration
This commit is contained in:
106
backend/.env.example
Normal file
106
backend/.env.example
Normal file
@@ -0,0 +1,106 @@
|
||||
# Backend Environment Variables
|
||||
# Copy this file to .env and fill in your values
|
||||
|
||||
# ==========================================
|
||||
# Application Configuration
|
||||
# ==========================================
|
||||
APP_NAME=Fortnox Invoice Integration
|
||||
APP_ENV=development
|
||||
DEBUG=true
|
||||
SECRET_KEY=change-this-to-a-random-secret-key-in-production
|
||||
|
||||
# ==========================================
|
||||
# Server Configuration
|
||||
# ==========================================
|
||||
HOST=0.0.0.0
|
||||
PORT=8000
|
||||
|
||||
# ==========================================
|
||||
# Database Configuration
|
||||
# ==========================================
|
||||
# Format: postgresql://user:password@host:port/database
|
||||
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/fortnox_invoice
|
||||
# For async SQLAlchemy
|
||||
ASYNC_DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/fortnox_invoice
|
||||
|
||||
# Database pool settings
|
||||
DB_POOL_SIZE=5
|
||||
DB_MAX_OVERFLOW=10
|
||||
DB_POOL_TIMEOUT=30
|
||||
|
||||
# ==========================================
|
||||
# Redis Configuration
|
||||
# ==========================================
|
||||
REDIS_URL=redis://localhost:6379/0
|
||||
REDIS_PASSWORD=
|
||||
|
||||
# ==========================================
|
||||
# Azure Blob Storage
|
||||
# ==========================================
|
||||
# Get this from Azure Portal > Storage Account > Access Keys
|
||||
AZURE_STORAGE_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=xxx;AccountKey=xxx;EndpointSuffix=core.windows.net
|
||||
AZURE_STORAGE_CONTAINER=documents
|
||||
AZURE_STORAGE_ACCOUNT_NAME=
|
||||
AZURE_STORAGE_ACCOUNT_KEY=
|
||||
|
||||
# ==========================================
|
||||
# Fortnox OAuth Configuration
|
||||
# ==========================================
|
||||
# Get these from Fortnox Developer Portal
|
||||
FORTNOX_CLIENT_ID=your-fortnox-client-id
|
||||
FORTNOX_CLIENT_SECRET=your-fortnox-client-secret
|
||||
FORTNOX_REDIRECT_URI=http://localhost:5173/fortnox/callback
|
||||
FORTNOX_AUTH_URL=https://apps.fortnox.se/oauth-v1/auth
|
||||
FORTNOX_TOKEN_URL=https://apps.fortnox.se/oauth-v1/token
|
||||
FORTNOX_API_BASE_URL=https://api.fortnox.se/3
|
||||
|
||||
# ==========================================
|
||||
# Invoice Master OCR API
|
||||
# ==========================================
|
||||
# URL of your existing invoice-master API
|
||||
OCR_API_URL=http://localhost:8000/api/v1
|
||||
OCR_API_KEY=your-ocr-api-key
|
||||
OCR_TIMEOUT=60
|
||||
|
||||
# ==========================================
|
||||
# JWT Configuration
|
||||
# ==========================================
|
||||
JWT_SECRET_KEY=your-jwt-secret-key-min-32-characters-long
|
||||
JWT_ALGORITHM=HS256
|
||||
JWT_ACCESS_TOKEN_EXPIRE_MINUTES=15
|
||||
JWT_REFRESH_TOKEN_EXPIRE_DAYS=7
|
||||
|
||||
# ==========================================
|
||||
# Encryption Configuration
|
||||
# ==========================================
|
||||
# 32-byte base64 encoded key for AES-256 encryption
|
||||
ENCRYPTION_KEY=your-32-byte-encryption-key-base64-encoded=
|
||||
|
||||
# ==========================================
|
||||
# Email Configuration (Optional)
|
||||
# ==========================================
|
||||
SMTP_HOST=
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=
|
||||
SMTP_PASSWORD=
|
||||
SMTP_FROM_EMAIL=noreply@example.com
|
||||
|
||||
# ==========================================
|
||||
# Logging Configuration
|
||||
# ==========================================
|
||||
LOG_LEVEL=INFO
|
||||
LOG_FORMAT=json
|
||||
|
||||
# ==========================================
|
||||
# Monitoring (Optional)
|
||||
# ==========================================
|
||||
APPLICATIONINSIGHTS_CONNECTION_STRING=
|
||||
SENTRY_DSN=
|
||||
|
||||
# ==========================================
|
||||
# Feature Flags
|
||||
# ==========================================
|
||||
ENABLE_AUTO_SUPPLIER_CREATE=false
|
||||
ENABLE_PDF_ATTACHMENT=true
|
||||
MAX_FILE_SIZE_MB=10
|
||||
ALLOWED_FILE_TYPES=pdf,jpg,jpeg,png
|
||||
18
backend/Directory.Build.props
Normal file
18
backend/Directory.Build.props
Normal file
@@ -0,0 +1,18 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)CodeAnalysis.ruleset</CodeAnalysisRuleSet>
|
||||
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
|
||||
<LangVersion>12.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
15
backend/Dockerfile
Normal file
15
backend/Dockerfile
Normal file
@@ -0,0 +1,15 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||
WORKDIR /src
|
||||
|
||||
COPY . .
|
||||
RUN dotnet restore
|
||||
RUN dotnet build -c Release --no-restore
|
||||
RUN dotnet publish src/InvoiceMaster.API/InvoiceMaster.API.csproj -c Release -o /app/publish --no-restore
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
|
||||
WORKDIR /app
|
||||
COPY --from=build /app/publish .
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
ENTRYPOINT ["dotnet", "InvoiceMaster.API.dll"]
|
||||
70
backend/InvoiceMaster.sln
Normal file
70
backend/InvoiceMaster.sln
Normal file
@@ -0,0 +1,70 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8A4623CB-AB3F-4A20-8A6E-3A33B65D6F5A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvoiceMaster.API", "src\InvoiceMaster.API\InvoiceMaster.API.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvoiceMaster.Core", "src\InvoiceMaster.Core\InvoiceMaster.Core.csproj", "{B2C3D4E5-F6A7-8901-BCDE-F23456789012}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvoiceMaster.Application", "src\InvoiceMaster.Application\InvoiceMaster.Application.csproj", "{C3D4E5F6-A7B8-9012-CDEF-345678901234}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvoiceMaster.Infrastructure", "src\InvoiceMaster.Infrastructure\InvoiceMaster.Infrastructure.csproj", "{D4E5F6A7-B8C9-0123-DEFA-456789012345}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvoiceMaster.Integrations", "src\InvoiceMaster.Integrations\InvoiceMaster.Integrations.csproj", "{E5F6A7B8-C9D0-1234-EFAB-567890123456}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{F6A7B8C9-D0E1-2345-FABC-678901234567}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvoiceMaster.UnitTests", "tests\InvoiceMaster.UnitTests\InvoiceMaster.UnitTests.csproj", "{A7B8C9D0-E1F2-3456-ABCD-789012345678}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvoiceMaster.IntegrationTests", "tests\InvoiceMaster.IntegrationTests\InvoiceMaster.IntegrationTests.csproj", "{B8C9D0E1-F2A3-4567-BCDE-890123456789}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B2C3D4E5-F6A7-8901-BCDE-F23456789012}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B2C3D4E5-F6A7-8901-BCDE-F23456789012}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B2C3D4E5-F6A7-8901-BCDE-F23456789012}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B2C3D4E5-F6A7-8901-BCDE-F23456789012}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C3D4E5F6-A7B8-9012-CDEF-345678901234}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C3D4E5F6-A7B8-9012-CDEF-345678901234}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C3D4E5F6-A7B8-9012-CDEF-345678901234}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C3D4E5F6-A7B8-9012-CDEF-345678901234}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{D4E5F6A7-B8C9-0123-DEFA-456789012345}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D4E5F6A7-B8C9-0123-DEFA-456789012345}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D4E5F6A7-B8C9-0123-DEFA-456789012345}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D4E5F6A7-B8C9-0123-DEFA-456789012345}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E5F6A7B8-C9D0-1234-EFAB-567890123456}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E5F6A7B8-C9D0-1234-EFAB-567890123456}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E5F6A7B8-C9D0-1234-EFAB-567890123456}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E5F6A7B8-C9D0-1234-EFAB-567890123456}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A7B8C9D0-E1F2-3456-ABCD-789012345678}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A7B8C9D0-E1F2-3456-ABCD-789012345678}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A7B8C9D0-E1F2-3456-ABCD-789012345678}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A7B8C9D0-E1F2-3456-ABCD-789012345678}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B8C9D0E1-F2A3-4567-BCDE-890123456789}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B8C9D0E1-F2A3-4567-BCDE-890123456789}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B8C9D0E1-F2A3-4567-BCDE-890123456789}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B8C9D0E1-F2A3-4567-BCDE-890123456789}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890} = {8A4623CB-AB3F-4A20-8A6E-3A33B65D6F5A}
|
||||
{B2C3D4E5-F6A7-8901-BCDE-F23456789012} = {8A4623CB-AB3F-4A20-8A6E-3A33B65D6F5A}
|
||||
{C3D4E5F6-A7B8-9012-CDEF-345678901234} = {8A4623CB-AB3F-4A20-8A6E-3A33B65D6F5A}
|
||||
{D4E5F6A7-B8C9-0123-DEFA-456789012345} = {8A4623CB-AB3F-4A20-8A6E-3A33B65D6F5A}
|
||||
{E5F6A7B8-C9D0-1234-EFAB-567890123456} = {8A4623CB-AB3F-4A20-8A6E-3A33B65D6F5A}
|
||||
{A7B8C9D0-E1F2-3456-ABCD-789012345678} = {F6A7B8C9-D0E1-2345-FABC-678901234567}
|
||||
{B8C9D0E1-F2A3-4567-BCDE-890123456789} = {F6A7B8C9-D0E1-2345-FABC-678901234567}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
44
backend/README.md
Normal file
44
backend/README.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Invoice Master - Backend
|
||||
|
||||
## Project Structure
|
||||
|
||||
This backend follows Clean Architecture with the following projects:
|
||||
|
||||
- **InvoiceMaster.Core** - Domain entities, interfaces, value objects
|
||||
- **InvoiceMaster.Application** - Business logic, CQRS commands/queries
|
||||
- **InvoiceMaster.Infrastructure** - EF Core, repositories, external services
|
||||
- **InvoiceMaster.Integrations** - Accounting system providers
|
||||
- **InvoiceMaster.API** - Web API entry point
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- .NET 8 SDK
|
||||
- PostgreSQL 15+
|
||||
- Redis 7+ (optional)
|
||||
|
||||
### Running Locally
|
||||
|
||||
```bash
|
||||
# Restore dependencies
|
||||
dotnet restore
|
||||
|
||||
# Run database migrations
|
||||
cd src/InvoiceMaster.Infrastructure
|
||||
dotnet ef database update --startup-project ../InvoiceMaster.API
|
||||
|
||||
# Run the API
|
||||
cd ../InvoiceMaster.API
|
||||
dotnet run
|
||||
```
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
dotnet test
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
See `src/InvoiceMaster.API/appsettings.Development.json` for configuration.
|
||||
6
backend/global.json
Normal file
6
backend/global.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "8.0.100",
|
||||
"rollForward": "latestFeature"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
using InvoiceMaster.Application.DTOs;
|
||||
using InvoiceMaster.Core.Entities;
|
||||
using InvoiceMaster.Core.Interfaces;
|
||||
using InvoiceMaster.Integrations.Accounting;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace InvoiceMaster.API.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/v1/accounting")]
|
||||
[Authorize]
|
||||
public class AccountingController : ControllerBase
|
||||
{
|
||||
private readonly IAccountingSystemFactory _factory;
|
||||
private readonly IRepository<AccountingConnection> _connectionRepository;
|
||||
private readonly IRepository<User> _userRepository;
|
||||
|
||||
public AccountingController(
|
||||
IAccountingSystemFactory factory,
|
||||
IRepository<AccountingConnection> connectionRepository,
|
||||
IRepository<User> userRepository)
|
||||
{
|
||||
_factory = factory;
|
||||
_connectionRepository = connectionRepository;
|
||||
_userRepository = userRepository;
|
||||
}
|
||||
|
||||
[HttpGet("providers")]
|
||||
public IActionResult GetProviders()
|
||||
{
|
||||
var userId = GetUserId();
|
||||
var connections = _connectionRepository.GetAllAsync().Result
|
||||
.Where(c => c.UserId == userId && c.IsActive)
|
||||
.ToList();
|
||||
|
||||
var providers = new[]
|
||||
{
|
||||
new ProviderInfoDto("fortnox", "Fortnox", "Swedish accounting software", true,
|
||||
connections.Any(c => c.Provider == "fortnox")),
|
||||
new ProviderInfoDto("visma", "Visma eAccounting", "Nordic accounting software", false, false),
|
||||
new ProviderInfoDto("hogia", "Hogia Smart", "Swedish accounting software", false, false)
|
||||
};
|
||||
|
||||
return Ok(new { success = true, data = new { providers } });
|
||||
}
|
||||
|
||||
[HttpGet("{provider}/auth/url")]
|
||||
public IActionResult GetAuthUrl(string provider)
|
||||
{
|
||||
var redirectUri = $"{Request.Scheme}://{Request.Host}/api/v1/accounting/{provider}/auth/callback";
|
||||
|
||||
var state = Guid.NewGuid().ToString("N");
|
||||
|
||||
string authUrl = provider.ToLower() switch
|
||||
{
|
||||
"fortnox" => $"https://apps.fortnox.se/oauth-v1/auth?client_id=&redirect_uri={Uri.EscapeDataString(redirectUri)}&scope=supplier+voucher+account&state={state}",
|
||||
_ => throw new NotSupportedException($"Provider '{provider}' is not supported")
|
||||
};
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
success = true,
|
||||
data = new { provider, authorizationUrl = authUrl, state }
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet("{provider}/auth/callback")]
|
||||
public async Task<IActionResult> AuthCallback(string provider, [FromQuery] string code, [FromQuery] string state)
|
||||
{
|
||||
var accounting = _factory.Create(provider);
|
||||
var result = await accounting.AuthenticateAsync(code);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
return BadRequest(new { success = false, error = result.ErrorMessage });
|
||||
}
|
||||
|
||||
var userId = GetUserId();
|
||||
var connection = AccountingConnection.Create(
|
||||
userId,
|
||||
provider,
|
||||
result.AccessToken ?? string.Empty,
|
||||
result.RefreshToken ?? string.Empty,
|
||||
result.ExpiresAt ?? DateTime.UtcNow.AddHours(1),
|
||||
result.Scope,
|
||||
result.CompanyInfo?.Name,
|
||||
result.CompanyInfo?.OrganisationNumber);
|
||||
|
||||
await _connectionRepository.AddAsync(connection);
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
success = true,
|
||||
data = new
|
||||
{
|
||||
provider,
|
||||
connected = true,
|
||||
companyName = result.CompanyInfo?.Name,
|
||||
companyOrgNumber = result.CompanyInfo?.OrganisationNumber,
|
||||
connectedAt = connection.CreatedAt
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet("connections")]
|
||||
public async Task<IActionResult> GetConnections()
|
||||
{
|
||||
var userId = GetUserId();
|
||||
var connections = (await _connectionRepository.GetAllAsync())
|
||||
.Where(c => c.UserId == userId && c.IsActive)
|
||||
.Select(c => new ConnectionDto(
|
||||
c.Provider,
|
||||
true,
|
||||
c.CompanyName,
|
||||
c.CompanyOrgNumber,
|
||||
c.Scope?.Split(' ').ToList(),
|
||||
c.ExpiresAt,
|
||||
new ConnectionSettingsDto(
|
||||
c.DefaultVoucherSeries,
|
||||
c.DefaultAccountCode,
|
||||
c.AutoAttachPdf,
|
||||
c.AutoCreateSupplier)))
|
||||
.ToList();
|
||||
|
||||
return Ok(new { success = true, data = new { connections } });
|
||||
}
|
||||
|
||||
[HttpDelete("connections/{provider}")]
|
||||
public async Task<IActionResult> Disconnect(string provider)
|
||||
{
|
||||
var userId = GetUserId();
|
||||
var connections = await _connectionRepository.GetAllAsync();
|
||||
var connection = connections.FirstOrDefault(c =>
|
||||
c.UserId == userId &&
|
||||
c.Provider.Equals(provider, StringComparison.OrdinalIgnoreCase) &&
|
||||
c.IsActive);
|
||||
|
||||
if (connection == null)
|
||||
{
|
||||
return NotFound(new { success = false, error = "Connection not found" });
|
||||
}
|
||||
|
||||
connection.Deactivate();
|
||||
return Ok(new { success = true });
|
||||
}
|
||||
|
||||
private Guid GetUserId()
|
||||
{
|
||||
var userId = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
|
||||
return Guid.Parse(userId!);
|
||||
}
|
||||
}
|
||||
108
backend/src/InvoiceMaster.API/Controllers/AuthController.cs
Normal file
108
backend/src/InvoiceMaster.API/Controllers/AuthController.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
using InvoiceMaster.Application.Commands.Auth;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace InvoiceMaster.API.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/v1/auth")]
|
||||
public class AuthController : ControllerBase
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
|
||||
public AuthController(IMediator mediator)
|
||||
{
|
||||
_mediator = mediator;
|
||||
}
|
||||
|
||||
[HttpPost("register")]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> Register([FromBody] RegisterCommand command)
|
||||
{
|
||||
var result = await _mediator.Send(command);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
return BadRequest(new
|
||||
{
|
||||
success = false,
|
||||
error = new { code = "REGISTRATION_FAILED", message = result.ErrorMessage }
|
||||
});
|
||||
}
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
success = true,
|
||||
data = new
|
||||
{
|
||||
user = result.User,
|
||||
tokens = result.Tokens
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost("login")]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> Login([FromBody] LoginCommand command)
|
||||
{
|
||||
var result = await _mediator.Send(command);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
return Unauthorized(new
|
||||
{
|
||||
success = false,
|
||||
error = new { code = "AUTHENTICATION_FAILED", message = result.ErrorMessage }
|
||||
});
|
||||
}
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
success = true,
|
||||
data = new
|
||||
{
|
||||
user = result.User,
|
||||
tokens = result.Tokens
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost("refresh")]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> Refresh([FromBody] RefreshTokenCommand command)
|
||||
{
|
||||
var result = await _mediator.Send(command);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
return Unauthorized(new
|
||||
{
|
||||
success = false,
|
||||
error = new { code = "INVALID_REFRESH_TOKEN", message = "Invalid or expired refresh token" }
|
||||
});
|
||||
}
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
success = true,
|
||||
data = new { tokens = result }
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost("logout")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> Logout()
|
||||
{
|
||||
var userId = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
|
||||
if (userId == null || !Guid.TryParse(userId, out var guid))
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var command = new LogoutCommand(guid);
|
||||
await _mediator.Send(command);
|
||||
|
||||
return Ok(new { success = true });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace InvoiceMaster.API.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/v1/health")]
|
||||
public class HealthController : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public IActionResult Get()
|
||||
{
|
||||
return Ok(new
|
||||
{
|
||||
status = "healthy",
|
||||
timestamp = DateTime.UtcNow.ToString("O"),
|
||||
version = "1.0.0"
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using InvoiceMaster.Application.Commands.Invoices;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace InvoiceMaster.API.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/v1/invoices")]
|
||||
[Authorize]
|
||||
public partial class InvoicesController : ControllerBase
|
||||
{
|
||||
[HttpPost("{id}/import")]
|
||||
public async Task<IActionResult> ImportInvoice(Guid id, [FromBody] ImportInvoiceRequest? request)
|
||||
{
|
||||
var userId = GetUserId();
|
||||
|
||||
var command = new ImportInvoiceCommand(
|
||||
id,
|
||||
userId,
|
||||
request?.CreateSupplier ?? false,
|
||||
request?.SupplierData != null
|
||||
? new AccountingSupplier(
|
||||
request.SupplierData.Name,
|
||||
request.SupplierData.Name,
|
||||
request.SupplierData.OrganisationNumber)
|
||||
: null);
|
||||
|
||||
var result = await _mediator.Send(command);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
return BadRequest(new { success = false, error = result.ErrorMessage });
|
||||
}
|
||||
|
||||
return Ok(new { success = true, data = result.Data });
|
||||
}
|
||||
|
||||
private Guid GetUserId()
|
||||
{
|
||||
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
return Guid.Parse(userId!);
|
||||
}
|
||||
}
|
||||
|
||||
public record ImportInvoiceRequest(
|
||||
bool CreateSupplier = false,
|
||||
SupplierDataRequest? SupplierData = null);
|
||||
|
||||
public record SupplierDataRequest(
|
||||
string Name,
|
||||
string OrganisationNumber);
|
||||
|
||||
public record AccountingSupplier(
|
||||
string SupplierNumber,
|
||||
string Name,
|
||||
string? OrganisationNumber = null,
|
||||
string? Address1 = null,
|
||||
string? Postcode = null,
|
||||
string? City = null,
|
||||
string? Phone = null,
|
||||
string? Email = null,
|
||||
string? BankgiroNumber = null,
|
||||
string? PlusgiroNumber = null);
|
||||
@@ -0,0 +1,67 @@
|
||||
using InvoiceMaster.Application.Commands.Invoices;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace InvoiceMaster.API.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/v1/invoices")]
|
||||
[Authorize]
|
||||
public class InvoicesController : ControllerBase
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
|
||||
public InvoicesController(IMediator mediator)
|
||||
{
|
||||
_mediator = mediator;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Consumes("multipart/form-data")]
|
||||
public async Task<IActionResult> UploadInvoice([FromForm] UploadInvoiceRequest request)
|
||||
{
|
||||
var userId = GetUserId();
|
||||
|
||||
if (request.File == null || request.File.Length == 0)
|
||||
{
|
||||
return BadRequest(new { success = false, error = "No file uploaded" });
|
||||
}
|
||||
|
||||
if (!request.File.ContentType.Equals("application/pdf", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return BadRequest(new { success = false, error = "Only PDF files are supported" });
|
||||
}
|
||||
|
||||
var command = new UploadInvoiceCommand(
|
||||
userId,
|
||||
request.Provider,
|
||||
request.File.FileName,
|
||||
request.File.OpenReadStream(),
|
||||
request.File.Length,
|
||||
request.File.ContentType);
|
||||
|
||||
var result = await _mediator.Send(command);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
return BadRequest(new { success = false, error = result.ErrorMessage });
|
||||
}
|
||||
|
||||
return Ok(new { success = true, data = result.Invoice });
|
||||
}
|
||||
|
||||
private Guid GetUserId()
|
||||
{
|
||||
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
return Guid.Parse(userId!);
|
||||
}
|
||||
}
|
||||
|
||||
public class UploadInvoiceRequest
|
||||
{
|
||||
public IFormFile? File { get; set; }
|
||||
public string Provider { get; set; } = "fortnox";
|
||||
public bool AutoProcess { get; set; } = false;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.Text;
|
||||
|
||||
namespace InvoiceMaster.API.Extensions;
|
||||
|
||||
public static class AuthenticationExtensions
|
||||
{
|
||||
public static IServiceCollection AddJwtAuthentication(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
var jwtSettings = configuration.GetSection("Jwt");
|
||||
var secretKey = jwtSettings["SecretKey"] ?? throw new InvalidOperationException("JWT SecretKey is not configured");
|
||||
|
||||
services.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
})
|
||||
.AddJwtBearer(options =>
|
||||
{
|
||||
options.TokenValidationParameters = new()
|
||||
{
|
||||
ValidateIssuer = true,
|
||||
ValidateAudience = true,
|
||||
ValidateLifetime = true,
|
||||
ValidateIssuerSigningKey = true,
|
||||
ValidIssuer = jwtSettings["Issuer"],
|
||||
ValidAudience = jwtSettings["Audience"],
|
||||
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey)),
|
||||
ClockSkew = TimeSpan.Zero
|
||||
};
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
21
backend/src/InvoiceMaster.API/InvoiceMaster.API.csproj
Normal file
21
backend/src/InvoiceMaster.API/InvoiceMaster.API.csproj
Normal file
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<AssemblyName>InvoiceMaster.API</AssemblyName>
|
||||
<RootNamespace>InvoiceMaster.API</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\InvoiceMaster.Application\InvoiceMaster.Application.csproj" />
|
||||
<ProjectReference Include="..\InvoiceMaster.Infrastructure\InvoiceMaster.Infrastructure.csproj" />
|
||||
<ProjectReference Include="..\InvoiceMaster.Integrations\InvoiceMaster.Integrations.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,77 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Net;
|
||||
|
||||
namespace InvoiceMaster.API.Middleware;
|
||||
|
||||
public class ExceptionHandlingMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly ILogger<ExceptionHandlingMiddleware> _logger;
|
||||
|
||||
public ExceptionHandlingMiddleware(RequestDelegate next, ILogger<ExceptionHandlingMiddleware> logger)
|
||||
{
|
||||
_next = next;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _next(context);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "An unhandled exception occurred");
|
||||
await HandleExceptionAsync(context, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
|
||||
{
|
||||
context.Response.ContentType = "application/json";
|
||||
|
||||
var (statusCode, errorCode, message) = exception switch
|
||||
{
|
||||
UnauthorizedAccessException _ => (
|
||||
(int)HttpStatusCode.Unauthorized,
|
||||
"UNAUTHORIZED",
|
||||
"Authentication required"
|
||||
),
|
||||
InvalidOperationException _ => (
|
||||
(int)HttpStatusCode.BadRequest,
|
||||
"INVALID_OPERATION",
|
||||
exception.Message
|
||||
),
|
||||
KeyNotFoundException _ => (
|
||||
(int)HttpStatusCode.NotFound,
|
||||
"NOT_FOUND",
|
||||
"Resource not found"
|
||||
),
|
||||
_ => (
|
||||
(int)HttpStatusCode.InternalServerError,
|
||||
"INTERNAL_ERROR",
|
||||
"An unexpected error occurred"
|
||||
)
|
||||
};
|
||||
|
||||
context.Response.StatusCode = statusCode;
|
||||
|
||||
var response = new
|
||||
{
|
||||
success = false,
|
||||
error = new
|
||||
{
|
||||
code = errorCode,
|
||||
message
|
||||
},
|
||||
meta = new
|
||||
{
|
||||
request_id = context.TraceIdentifier,
|
||||
timestamp = DateTime.UtcNow.ToString("O")
|
||||
}
|
||||
};
|
||||
|
||||
return context.Response.WriteAsJsonAsync(response);
|
||||
}
|
||||
}
|
||||
99
backend/src/InvoiceMaster.API/Program.cs
Normal file
99
backend/src/InvoiceMaster.API/Program.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using InvoiceMaster.API.Extensions;
|
||||
using InvoiceMaster.Application;
|
||||
using InvoiceMaster.Infrastructure.Data;
|
||||
using InvoiceMaster.Infrastructure.Extensions;
|
||||
using InvoiceMaster.Integrations.Extensions;
|
||||
using Serilog;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Configure Serilog
|
||||
builder.Host.UseSerilog((context, configuration) =>
|
||||
configuration.ReadFrom.Configuration(context.Configuration));
|
||||
|
||||
// Add services to the container
|
||||
builder.Services.AddControllers();
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen(options =>
|
||||
{
|
||||
options.SwaggerDoc("v1", new()
|
||||
{
|
||||
Title = "Invoice Master API",
|
||||
Version = "v1",
|
||||
Description = "Multi-accounting system invoice processing platform"
|
||||
});
|
||||
|
||||
// Add JWT authentication to Swagger
|
||||
options.AddSecurityDefinition("Bearer", new()
|
||||
{
|
||||
Description = "JWT Authorization header using the Bearer scheme. Example: \"Bearer {token}\"",
|
||||
Name = "Authorization",
|
||||
In = Microsoft.OpenApi.Models.ParameterLocation.Header,
|
||||
Type = Microsoft.OpenApi.Models.SecuritySchemeType.ApiKey,
|
||||
Scheme = "Bearer"
|
||||
});
|
||||
|
||||
options.AddSecurityRequirement(new()
|
||||
{
|
||||
{
|
||||
new()
|
||||
{
|
||||
Reference = new()
|
||||
{
|
||||
Type = Microsoft.OpenApi.Models.ReferenceType.SecurityScheme,
|
||||
Id = "Bearer"
|
||||
}
|
||||
},
|
||||
Array.Empty<string>()
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Add CORS
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy("AllowFrontend", policy =>
|
||||
{
|
||||
policy.WithOrigins(
|
||||
builder.Configuration.GetSection("Cors:AllowedOrigins").Get<string[]>()
|
||||
?? ["http://localhost:5173"])
|
||||
.AllowAnyHeader()
|
||||
.AllowAnyMethod()
|
||||
.AllowCredentials();
|
||||
});
|
||||
});
|
||||
|
||||
// Add application services
|
||||
builder.Services.AddApplicationServices();
|
||||
builder.Services.AddInfrastructureServices(builder.Configuration);
|
||||
builder.Services.AddIntegrationServices(builder.Configuration);
|
||||
|
||||
// Add JWT authentication
|
||||
builder.Services.AddJwtAuthentication(builder.Configuration);
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
}
|
||||
|
||||
app.UseSerilogRequestLogging();
|
||||
app.UseHttpsRedirection();
|
||||
app.UseCors("AllowFrontend");
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
app.UseMiddleware<InvoiceMaster.API.Middleware.ExceptionHandlingMiddleware>();
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
// Ensure database is created and migrated
|
||||
using (var scope = app.Services.CreateScope())
|
||||
{
|
||||
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
await context.Database.MigrateAsync();
|
||||
}
|
||||
|
||||
app.Run();
|
||||
20
backend/src/InvoiceMaster.API/appsettings.Development.json
Normal file
20
backend/src/InvoiceMaster.API/appsettings.Development.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"Serilog": {
|
||||
"MinimumLevel": {
|
||||
"Default": "Debug",
|
||||
"Override": {
|
||||
"Microsoft": "Information",
|
||||
"System": "Information"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Host=localhost;Port=5432;Database=invoice_master_dev;Username=postgres;Password=postgres"
|
||||
}
|
||||
}
|
||||
57
backend/src/InvoiceMaster.API/appsettings.json
Normal file
57
backend/src/InvoiceMaster.API/appsettings.json
Normal file
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"Serilog": {
|
||||
"Using": ["Serilog.Sinks.Console", "Serilog.Sinks.File"],
|
||||
"MinimumLevel": {
|
||||
"Default": "Information",
|
||||
"Override": {
|
||||
"Microsoft": "Warning",
|
||||
"System": "Warning"
|
||||
}
|
||||
},
|
||||
"WriteTo": [
|
||||
{
|
||||
"Name": "Console"
|
||||
},
|
||||
{
|
||||
"Name": "File",
|
||||
"Args": {
|
||||
"path": "logs/log-.txt",
|
||||
"rollingInterval": "Day"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"Cors": {
|
||||
"AllowedOrigins": ["http://localhost:5173", "https://localhost:5173"]
|
||||
},
|
||||
"Jwt": {
|
||||
"SecretKey": "your-super-secret-key-min-32-chars-long",
|
||||
"Issuer": "InvoiceMaster",
|
||||
"Audience": "InvoiceMaster.Client",
|
||||
"AccessTokenExpirationMinutes": 15,
|
||||
"RefreshTokenExpirationDays": 7
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Host=localhost;Port=5432;Database=invoice_master;Username=postgres;Password=postgres"
|
||||
},
|
||||
"AzureStorage": {
|
||||
"ConnectionString": "",
|
||||
"ContainerName": "documents"
|
||||
},
|
||||
"Ocr": {
|
||||
"ApiUrl": "http://localhost:8000/api/v1",
|
||||
"ApiKey": ""
|
||||
},
|
||||
"Fortnox": {
|
||||
"ClientId": "",
|
||||
"ClientSecret": "",
|
||||
"RedirectUri": "http://localhost:5173/accounting/fortnox/callback"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// <autogenerated />
|
||||
using System;
|
||||
using System.Reflection;
|
||||
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")]
|
||||
@@ -0,0 +1,22 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("InvoiceMaster.API")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("InvoiceMaster.API")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("InvoiceMaster.API")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
// Generated by the MSBuild WriteCodeFragment class.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
0b17e756d606e87e18da4b7ff18325a40b19e253e4e72e12aaf90ad0cae31d74
|
||||
@@ -0,0 +1,23 @@
|
||||
is_global = true
|
||||
build_property.TargetFramework = net8.0
|
||||
build_property.TargetFrameworkIdentifier = .NETCoreApp
|
||||
build_property.TargetFrameworkVersion = v8.0
|
||||
build_property.TargetPlatformMinVersion =
|
||||
build_property.UsingMicrosoftNETSdkWeb = true
|
||||
build_property.ProjectTypeGuids =
|
||||
build_property.InvariantGlobalization =
|
||||
build_property.PlatformNeutralAssembly =
|
||||
build_property.EnforceExtendedAnalyzerRules =
|
||||
build_property._SupportedPlatformList = Linux,macOS,Windows
|
||||
build_property.RootNamespace = InvoiceMaster.API
|
||||
build_property.RootNamespace = InvoiceMaster.API
|
||||
build_property.ProjectDir = C:\Users\yaoji\git\ColaCoder\accounting-system\backend\src\InvoiceMaster.API\
|
||||
build_property.EnableComHosting =
|
||||
build_property.EnableGeneratedComInterfaceComImportInterop =
|
||||
build_property.RazorLangVersion = 8.0
|
||||
build_property.SupportLocalizedComponentNames =
|
||||
build_property.GenerateRazorMetadataSourceChecksumAttributes =
|
||||
build_property.MSBuildProjectDirectory = C:\Users\yaoji\git\ColaCoder\accounting-system\backend\src\InvoiceMaster.API
|
||||
build_property._RazorSourceGeneratorDebug =
|
||||
build_property.EffectiveAnalysisLevelStyle = 8.0
|
||||
build_property.EnableCodeStyleSeverity =
|
||||
@@ -0,0 +1,17 @@
|
||||
// <auto-generated/>
|
||||
global using Microsoft.AspNetCore.Builder;
|
||||
global using Microsoft.AspNetCore.Hosting;
|
||||
global using Microsoft.AspNetCore.Http;
|
||||
global using Microsoft.AspNetCore.Routing;
|
||||
global using Microsoft.Extensions.Configuration;
|
||||
global using Microsoft.Extensions.DependencyInjection;
|
||||
global using Microsoft.Extensions.Hosting;
|
||||
global using Microsoft.Extensions.Logging;
|
||||
global using System;
|
||||
global using System.Collections.Generic;
|
||||
global using System.IO;
|
||||
global using System.Linq;
|
||||
global using System.Net.Http;
|
||||
global using System.Net.Http.Json;
|
||||
global using System.Threading;
|
||||
global using System.Threading.Tasks;
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,490 @@
|
||||
{
|
||||
"format": 1,
|
||||
"restore": {
|
||||
"C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj": {}
|
||||
},
|
||||
"projects": {
|
||||
"C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj": {
|
||||
"version": "1.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"projectName": "InvoiceMaster.API",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"packagesPath": "C:\\Users\\yaoji\\.nuget\\packages\\",
|
||||
"outputPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\obj\\",
|
||||
"projectStyle": "PackageReference",
|
||||
"fallbackFolders": [
|
||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
||||
],
|
||||
"configFilePaths": [
|
||||
"C:\\Users\\yaoji\\AppData\\Roaming\\NuGet\\NuGet.Config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
|
||||
],
|
||||
"originalTargetFrameworks": [
|
||||
"net8.0"
|
||||
],
|
||||
"sources": {
|
||||
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
|
||||
"https://api.nuget.org/v3/index.json": {},
|
||||
"https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"net8.0": {
|
||||
"targetAlias": "net8.0",
|
||||
"projectReferences": {
|
||||
"C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Application\\InvoiceMaster.Application.csproj": {
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Application\\InvoiceMaster.Application.csproj"
|
||||
},
|
||||
"C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj": {
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj"
|
||||
},
|
||||
"C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Integrations\\InvoiceMaster.Integrations.csproj": {
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Integrations\\InvoiceMaster.Integrations.csproj"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"warningProperties": {
|
||||
"allWarningsAsErrors": true,
|
||||
"warnAsError": [
|
||||
"NU1605"
|
||||
]
|
||||
},
|
||||
"restoreAuditProperties": {
|
||||
"enableAudit": "true",
|
||||
"auditLevel": "low",
|
||||
"auditMode": "direct"
|
||||
},
|
||||
"SdkAnalysisLevel": "10.0.100"
|
||||
},
|
||||
"frameworks": {
|
||||
"net8.0": {
|
||||
"targetAlias": "net8.0",
|
||||
"dependencies": {
|
||||
"Serilog.AspNetCore": {
|
||||
"target": "Package",
|
||||
"version": "[8.0.0, )"
|
||||
},
|
||||
"Serilog.Sinks.Console": {
|
||||
"target": "Package",
|
||||
"version": "[5.0.1, )"
|
||||
},
|
||||
"Serilog.Sinks.File": {
|
||||
"target": "Package",
|
||||
"version": "[5.0.0, )"
|
||||
},
|
||||
"StyleCop.Analyzers": {
|
||||
"include": "Runtime, Build, Native, ContentFiles, Analyzers",
|
||||
"suppressParent": "All",
|
||||
"target": "Package",
|
||||
"version": "[1.2.0-beta.556, )"
|
||||
},
|
||||
"Swashbuckle.AspNetCore": {
|
||||
"target": "Package",
|
||||
"version": "[6.5.0, )"
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"net461",
|
||||
"net462",
|
||||
"net47",
|
||||
"net471",
|
||||
"net472",
|
||||
"net48",
|
||||
"net481"
|
||||
],
|
||||
"assetTargetFallback": true,
|
||||
"warn": true,
|
||||
"frameworkReferences": {
|
||||
"Microsoft.AspNetCore.App": {
|
||||
"privateAssets": "none"
|
||||
},
|
||||
"Microsoft.NETCore.App": {
|
||||
"privateAssets": "all"
|
||||
}
|
||||
},
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.102/PortableRuntimeIdentifierGraph.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Application\\InvoiceMaster.Application.csproj": {
|
||||
"version": "1.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Application\\InvoiceMaster.Application.csproj",
|
||||
"projectName": "InvoiceMaster.Application",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Application\\InvoiceMaster.Application.csproj",
|
||||
"packagesPath": "C:\\Users\\yaoji\\.nuget\\packages\\",
|
||||
"outputPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Application\\obj\\",
|
||||
"projectStyle": "PackageReference",
|
||||
"fallbackFolders": [
|
||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
||||
],
|
||||
"configFilePaths": [
|
||||
"C:\\Users\\yaoji\\AppData\\Roaming\\NuGet\\NuGet.Config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
|
||||
],
|
||||
"originalTargetFrameworks": [
|
||||
"net8.0"
|
||||
],
|
||||
"sources": {
|
||||
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
|
||||
"https://api.nuget.org/v3/index.json": {},
|
||||
"https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"net8.0": {
|
||||
"targetAlias": "net8.0",
|
||||
"projectReferences": {
|
||||
"C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\InvoiceMaster.Core.csproj": {
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\InvoiceMaster.Core.csproj"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"warningProperties": {
|
||||
"allWarningsAsErrors": true,
|
||||
"warnAsError": [
|
||||
"NU1605"
|
||||
]
|
||||
},
|
||||
"restoreAuditProperties": {
|
||||
"enableAudit": "true",
|
||||
"auditLevel": "low",
|
||||
"auditMode": "direct"
|
||||
},
|
||||
"SdkAnalysisLevel": "10.0.100"
|
||||
},
|
||||
"frameworks": {
|
||||
"net8.0": {
|
||||
"targetAlias": "net8.0",
|
||||
"dependencies": {
|
||||
"AutoMapper": {
|
||||
"target": "Package",
|
||||
"version": "[12.0.1, )"
|
||||
},
|
||||
"FluentValidation": {
|
||||
"target": "Package",
|
||||
"version": "[11.8.1, )"
|
||||
},
|
||||
"MediatR": {
|
||||
"target": "Package",
|
||||
"version": "[12.2.0, )"
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": {
|
||||
"target": "Package",
|
||||
"version": "[8.0.0, )"
|
||||
},
|
||||
"StyleCop.Analyzers": {
|
||||
"include": "Runtime, Build, Native, ContentFiles, Analyzers",
|
||||
"suppressParent": "All",
|
||||
"target": "Package",
|
||||
"version": "[1.2.0-beta.556, )"
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"net461",
|
||||
"net462",
|
||||
"net47",
|
||||
"net471",
|
||||
"net472",
|
||||
"net48",
|
||||
"net481"
|
||||
],
|
||||
"assetTargetFallback": true,
|
||||
"warn": true,
|
||||
"frameworkReferences": {
|
||||
"Microsoft.NETCore.App": {
|
||||
"privateAssets": "all"
|
||||
}
|
||||
},
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.102/PortableRuntimeIdentifierGraph.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\InvoiceMaster.Core.csproj": {
|
||||
"version": "1.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\InvoiceMaster.Core.csproj",
|
||||
"projectName": "InvoiceMaster.Core",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\InvoiceMaster.Core.csproj",
|
||||
"packagesPath": "C:\\Users\\yaoji\\.nuget\\packages\\",
|
||||
"outputPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\obj\\",
|
||||
"projectStyle": "PackageReference",
|
||||
"fallbackFolders": [
|
||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
||||
],
|
||||
"configFilePaths": [
|
||||
"C:\\Users\\yaoji\\AppData\\Roaming\\NuGet\\NuGet.Config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
|
||||
],
|
||||
"originalTargetFrameworks": [
|
||||
"net8.0"
|
||||
],
|
||||
"sources": {
|
||||
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
|
||||
"https://api.nuget.org/v3/index.json": {},
|
||||
"https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"net8.0": {
|
||||
"targetAlias": "net8.0",
|
||||
"projectReferences": {}
|
||||
}
|
||||
},
|
||||
"warningProperties": {
|
||||
"allWarningsAsErrors": true,
|
||||
"warnAsError": [
|
||||
"NU1605"
|
||||
]
|
||||
},
|
||||
"restoreAuditProperties": {
|
||||
"enableAudit": "true",
|
||||
"auditLevel": "low",
|
||||
"auditMode": "direct"
|
||||
},
|
||||
"SdkAnalysisLevel": "10.0.100"
|
||||
},
|
||||
"frameworks": {
|
||||
"net8.0": {
|
||||
"targetAlias": "net8.0",
|
||||
"dependencies": {
|
||||
"StyleCop.Analyzers": {
|
||||
"include": "Runtime, Build, Native, ContentFiles, Analyzers",
|
||||
"suppressParent": "All",
|
||||
"target": "Package",
|
||||
"version": "[1.2.0-beta.556, )"
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"net461",
|
||||
"net462",
|
||||
"net47",
|
||||
"net471",
|
||||
"net472",
|
||||
"net48",
|
||||
"net481"
|
||||
],
|
||||
"assetTargetFallback": true,
|
||||
"warn": true,
|
||||
"frameworkReferences": {
|
||||
"Microsoft.NETCore.App": {
|
||||
"privateAssets": "all"
|
||||
}
|
||||
},
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.102/PortableRuntimeIdentifierGraph.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj": {
|
||||
"version": "1.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"projectName": "InvoiceMaster.Infrastructure",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"packagesPath": "C:\\Users\\yaoji\\.nuget\\packages\\",
|
||||
"outputPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\obj\\",
|
||||
"projectStyle": "PackageReference",
|
||||
"fallbackFolders": [
|
||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
||||
],
|
||||
"configFilePaths": [
|
||||
"C:\\Users\\yaoji\\AppData\\Roaming\\NuGet\\NuGet.Config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
|
||||
],
|
||||
"originalTargetFrameworks": [
|
||||
"net8.0"
|
||||
],
|
||||
"sources": {
|
||||
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
|
||||
"https://api.nuget.org/v3/index.json": {},
|
||||
"https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"net8.0": {
|
||||
"targetAlias": "net8.0",
|
||||
"projectReferences": {
|
||||
"C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Application\\InvoiceMaster.Application.csproj": {
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Application\\InvoiceMaster.Application.csproj"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"warningProperties": {
|
||||
"allWarningsAsErrors": true,
|
||||
"warnAsError": [
|
||||
"NU1605"
|
||||
]
|
||||
},
|
||||
"restoreAuditProperties": {
|
||||
"enableAudit": "true",
|
||||
"auditLevel": "low",
|
||||
"auditMode": "direct"
|
||||
},
|
||||
"SdkAnalysisLevel": "10.0.100"
|
||||
},
|
||||
"frameworks": {
|
||||
"net8.0": {
|
||||
"targetAlias": "net8.0",
|
||||
"dependencies": {
|
||||
"Azure.Storage.Blobs": {
|
||||
"target": "Package",
|
||||
"version": "[12.19.1, )"
|
||||
},
|
||||
"Microsoft.AspNetCore.Identity.EntityFrameworkCore": {
|
||||
"target": "Package",
|
||||
"version": "[8.0.0, )"
|
||||
},
|
||||
"Microsoft.EntityFrameworkCore": {
|
||||
"target": "Package",
|
||||
"version": "[8.0.0, )"
|
||||
},
|
||||
"Microsoft.EntityFrameworkCore.Tools": {
|
||||
"include": "Runtime, Build, Native, ContentFiles, Analyzers",
|
||||
"suppressParent": "All",
|
||||
"target": "Package",
|
||||
"version": "[8.0.0, )"
|
||||
},
|
||||
"Microsoft.Extensions.Http": {
|
||||
"target": "Package",
|
||||
"version": "[8.0.0, )"
|
||||
},
|
||||
"Npgsql.EntityFrameworkCore.PostgreSQL": {
|
||||
"target": "Package",
|
||||
"version": "[8.0.0, )"
|
||||
},
|
||||
"Polly": {
|
||||
"target": "Package",
|
||||
"version": "[8.2.0, )"
|
||||
},
|
||||
"Polly.Extensions.Http": {
|
||||
"target": "Package",
|
||||
"version": "[3.0.0, )"
|
||||
},
|
||||
"StyleCop.Analyzers": {
|
||||
"include": "Runtime, Build, Native, ContentFiles, Analyzers",
|
||||
"suppressParent": "All",
|
||||
"target": "Package",
|
||||
"version": "[1.2.0-beta.556, )"
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"net461",
|
||||
"net462",
|
||||
"net47",
|
||||
"net471",
|
||||
"net472",
|
||||
"net48",
|
||||
"net481"
|
||||
],
|
||||
"assetTargetFallback": true,
|
||||
"warn": true,
|
||||
"frameworkReferences": {
|
||||
"Microsoft.NETCore.App": {
|
||||
"privateAssets": "all"
|
||||
}
|
||||
},
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.102/PortableRuntimeIdentifierGraph.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Integrations\\InvoiceMaster.Integrations.csproj": {
|
||||
"version": "1.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Integrations\\InvoiceMaster.Integrations.csproj",
|
||||
"projectName": "InvoiceMaster.Integrations",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Integrations\\InvoiceMaster.Integrations.csproj",
|
||||
"packagesPath": "C:\\Users\\yaoji\\.nuget\\packages\\",
|
||||
"outputPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Integrations\\obj\\",
|
||||
"projectStyle": "PackageReference",
|
||||
"fallbackFolders": [
|
||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
||||
],
|
||||
"configFilePaths": [
|
||||
"C:\\Users\\yaoji\\AppData\\Roaming\\NuGet\\NuGet.Config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
|
||||
],
|
||||
"originalTargetFrameworks": [
|
||||
"net8.0"
|
||||
],
|
||||
"sources": {
|
||||
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
|
||||
"https://api.nuget.org/v3/index.json": {},
|
||||
"https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"net8.0": {
|
||||
"targetAlias": "net8.0",
|
||||
"projectReferences": {
|
||||
"C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\InvoiceMaster.Core.csproj": {
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\InvoiceMaster.Core.csproj"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"warningProperties": {
|
||||
"allWarningsAsErrors": true,
|
||||
"warnAsError": [
|
||||
"NU1605"
|
||||
]
|
||||
},
|
||||
"restoreAuditProperties": {
|
||||
"enableAudit": "true",
|
||||
"auditLevel": "low",
|
||||
"auditMode": "direct"
|
||||
},
|
||||
"SdkAnalysisLevel": "10.0.100"
|
||||
},
|
||||
"frameworks": {
|
||||
"net8.0": {
|
||||
"targetAlias": "net8.0",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Abstractions": {
|
||||
"target": "Package",
|
||||
"version": "[8.0.0, )"
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": {
|
||||
"target": "Package",
|
||||
"version": "[8.0.0, )"
|
||||
},
|
||||
"Microsoft.Extensions.Http": {
|
||||
"target": "Package",
|
||||
"version": "[8.0.0, )"
|
||||
},
|
||||
"Microsoft.Extensions.Logging.Abstractions": {
|
||||
"target": "Package",
|
||||
"version": "[8.0.0, )"
|
||||
},
|
||||
"StyleCop.Analyzers": {
|
||||
"include": "Runtime, Build, Native, ContentFiles, Analyzers",
|
||||
"suppressParent": "All",
|
||||
"target": "Package",
|
||||
"version": "[1.2.0-beta.556, )"
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"net461",
|
||||
"net462",
|
||||
"net47",
|
||||
"net471",
|
||||
"net472",
|
||||
"net48",
|
||||
"net481"
|
||||
],
|
||||
"assetTargetFallback": true,
|
||||
"warn": true,
|
||||
"frameworkReferences": {
|
||||
"Microsoft.NETCore.App": {
|
||||
"privateAssets": "all"
|
||||
}
|
||||
},
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.102/PortableRuntimeIdentifierGraph.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
|
||||
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
|
||||
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
|
||||
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
|
||||
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\yaoji\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages</NuGetPackageFolders>
|
||||
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
|
||||
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">7.0.0</NuGetToolVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<SourceRoot Include="C:\Users\yaoji\.nuget\packages\" />
|
||||
<SourceRoot Include="C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages\" />
|
||||
</ItemGroup>
|
||||
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<Import Project="$(NuGetPackageRoot)microsoft.extensions.apidescription.server\6.0.5\build\Microsoft.Extensions.ApiDescription.Server.props" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.apidescription.server\6.0.5\build\Microsoft.Extensions.ApiDescription.Server.props')" />
|
||||
<Import Project="$(NuGetPackageRoot)swashbuckle.aspnetcore\6.5.0\build\Swashbuckle.AspNetCore.props" Condition="Exists('$(NuGetPackageRoot)swashbuckle.aspnetcore\6.5.0\build\Swashbuckle.AspNetCore.props')" />
|
||||
<Import Project="$(NuGetPackageRoot)microsoft.entityframeworkcore\8.0.0\buildTransitive\net8.0\Microsoft.EntityFrameworkCore.props" Condition="Exists('$(NuGetPackageRoot)microsoft.entityframeworkcore\8.0.0\buildTransitive\net8.0\Microsoft.EntityFrameworkCore.props')" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<PkgMicrosoft_Extensions_ApiDescription_Server Condition=" '$(PkgMicrosoft_Extensions_ApiDescription_Server)' == '' ">C:\Users\yaoji\.nuget\packages\microsoft.extensions.apidescription.server\6.0.5</PkgMicrosoft_Extensions_ApiDescription_Server>
|
||||
<PkgStyleCop_Analyzers_Unstable Condition=" '$(PkgStyleCop_Analyzers_Unstable)' == '' ">C:\Users\yaoji\.nuget\packages\stylecop.analyzers.unstable\1.2.0.556</PkgStyleCop_Analyzers_Unstable>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<Import Project="$(NuGetPackageRoot)system.text.json\8.0.0\buildTransitive\net6.0\System.Text.Json.targets" Condition="Exists('$(NuGetPackageRoot)system.text.json\8.0.0\buildTransitive\net6.0\System.Text.Json.targets')" />
|
||||
<Import Project="$(NuGetPackageRoot)microsoft.extensions.apidescription.server\6.0.5\build\Microsoft.Extensions.ApiDescription.Server.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.apidescription.server\6.0.5\build\Microsoft.Extensions.ApiDescription.Server.targets')" />
|
||||
<Import Project="$(NuGetPackageRoot)microsoft.extensions.configuration.binder\8.0.0\buildTransitive\netstandard2.0\Microsoft.Extensions.Configuration.Binder.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.configuration.binder\8.0.0\buildTransitive\netstandard2.0\Microsoft.Extensions.Configuration.Binder.targets')" />
|
||||
<Import Project="$(NuGetPackageRoot)microsoft.extensions.options\8.0.0\buildTransitive\net6.0\Microsoft.Extensions.Options.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.options\8.0.0\buildTransitive\net6.0\Microsoft.Extensions.Options.targets')" />
|
||||
<Import Project="$(NuGetPackageRoot)microsoft.extensions.logging.abstractions\8.0.0\buildTransitive\net6.0\Microsoft.Extensions.Logging.Abstractions.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.logging.abstractions\8.0.0\buildTransitive\net6.0\Microsoft.Extensions.Logging.Abstractions.targets')" />
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
3351
backend/src/InvoiceMaster.API/obj/project.assets.json
Normal file
3351
backend/src/InvoiceMaster.API/obj/project.assets.json
Normal file
File diff suppressed because it is too large
Load Diff
235
backend/src/InvoiceMaster.API/obj/project.nuget.cache
Normal file
235
backend/src/InvoiceMaster.API/obj/project.nuget.cache
Normal file
@@ -0,0 +1,235 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "+5p9vl5fRd0=",
|
||||
"success": false,
|
||||
"projectFilePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"expectedPackageFiles": [
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\automapper\\12.0.1\\automapper.12.0.1.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\azure.core\\1.36.0\\azure.core.1.36.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\azure.storage.blobs\\12.19.1\\azure.storage.blobs.12.19.1.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\azure.storage.common\\12.18.1\\azure.storage.common.12.18.1.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\fluentvalidation\\11.8.1\\fluentvalidation.11.8.1.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\mediatr\\12.2.0\\mediatr.12.2.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\mediatr.contracts\\2.0.1\\mediatr.contracts.2.0.1.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.aspnetcore.cryptography.internal\\8.0.0\\microsoft.aspnetcore.cryptography.internal.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.aspnetcore.cryptography.keyderivation\\8.0.0\\microsoft.aspnetcore.cryptography.keyderivation.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.aspnetcore.identity.entityframeworkcore\\8.0.0\\microsoft.aspnetcore.identity.entityframeworkcore.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.bcl.asyncinterfaces\\1.1.1\\microsoft.bcl.asyncinterfaces.1.1.1.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.csharp\\4.7.0\\microsoft.csharp.4.7.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.entityframeworkcore\\8.0.0\\microsoft.entityframeworkcore.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.entityframeworkcore.abstractions\\8.0.0\\microsoft.entityframeworkcore.abstractions.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.entityframeworkcore.analyzers\\8.0.0\\microsoft.entityframeworkcore.analyzers.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.entityframeworkcore.relational\\8.0.0\\microsoft.entityframeworkcore.relational.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.apidescription.server\\6.0.5\\microsoft.extensions.apidescription.server.6.0.5.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.caching.abstractions\\8.0.0\\microsoft.extensions.caching.abstractions.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.caching.memory\\8.0.0\\microsoft.extensions.caching.memory.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.configuration\\8.0.0\\microsoft.extensions.configuration.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.configuration.abstractions\\8.0.0\\microsoft.extensions.configuration.abstractions.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.configuration.binder\\8.0.0\\microsoft.extensions.configuration.binder.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.dependencyinjection\\8.0.0\\microsoft.extensions.dependencyinjection.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.dependencyinjection.abstractions\\8.0.0\\microsoft.extensions.dependencyinjection.abstractions.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.dependencymodel\\8.0.0\\microsoft.extensions.dependencymodel.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.diagnostics\\8.0.0\\microsoft.extensions.diagnostics.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.diagnostics.abstractions\\8.0.0\\microsoft.extensions.diagnostics.abstractions.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.fileproviders.abstractions\\8.0.0\\microsoft.extensions.fileproviders.abstractions.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.hosting.abstractions\\8.0.0\\microsoft.extensions.hosting.abstractions.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.http\\8.0.0\\microsoft.extensions.http.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.identity.core\\8.0.0\\microsoft.extensions.identity.core.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.identity.stores\\8.0.0\\microsoft.extensions.identity.stores.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.logging\\8.0.0\\microsoft.extensions.logging.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.logging.abstractions\\8.0.0\\microsoft.extensions.logging.abstractions.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.options\\8.0.0\\microsoft.extensions.options.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.options.configurationextensions\\8.0.0\\microsoft.extensions.options.configurationextensions.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.primitives\\8.0.0\\microsoft.extensions.primitives.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.openapi\\1.2.3\\microsoft.openapi.1.2.3.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\npgsql\\8.0.0\\npgsql.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\npgsql.entityframeworkcore.postgresql\\8.0.0\\npgsql.entityframeworkcore.postgresql.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\polly\\8.2.0\\polly.8.2.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\polly.core\\8.2.0\\polly.core.8.2.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\polly.extensions.http\\3.0.0\\polly.extensions.http.3.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\serilog\\3.1.1\\serilog.3.1.1.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\serilog.aspnetcore\\8.0.0\\serilog.aspnetcore.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\serilog.extensions.hosting\\8.0.0\\serilog.extensions.hosting.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\serilog.extensions.logging\\8.0.0\\serilog.extensions.logging.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\serilog.formatting.compact\\2.0.0\\serilog.formatting.compact.2.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\serilog.settings.configuration\\8.0.0\\serilog.settings.configuration.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\serilog.sinks.console\\5.0.1\\serilog.sinks.console.5.0.1.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\serilog.sinks.debug\\2.0.0\\serilog.sinks.debug.2.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\serilog.sinks.file\\5.0.0\\serilog.sinks.file.5.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\stylecop.analyzers\\1.2.0-beta.556\\stylecop.analyzers.1.2.0-beta.556.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\stylecop.analyzers.unstable\\1.2.0.556\\stylecop.analyzers.unstable.1.2.0.556.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\swashbuckle.aspnetcore\\6.5.0\\swashbuckle.aspnetcore.6.5.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\swashbuckle.aspnetcore.swagger\\6.5.0\\swashbuckle.aspnetcore.swagger.6.5.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\swashbuckle.aspnetcore.swaggergen\\6.5.0\\swashbuckle.aspnetcore.swaggergen.6.5.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\swashbuckle.aspnetcore.swaggerui\\6.5.0\\swashbuckle.aspnetcore.swaggerui.6.5.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\system.diagnostics.diagnosticsource\\8.0.0\\system.diagnostics.diagnosticsource.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\system.io.hashing\\6.0.0\\system.io.hashing.6.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\system.memory.data\\1.0.2\\system.memory.data.1.0.2.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\system.numerics.vectors\\4.5.0\\system.numerics.vectors.4.5.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\system.text.encodings.web\\8.0.0\\system.text.encodings.web.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\system.text.json\\8.0.0\\system.text.json.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\system.threading.tasks.extensions\\4.5.4\\system.threading.tasks.extensions.4.5.4.nupkg.sha512"
|
||||
],
|
||||
"logs": [
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1900",
|
||||
"level": "Error",
|
||||
"message": "Warning As Error: Error occurred while getting package vulnerability data: Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.API\\InvoiceMaster.API.csproj",
|
||||
"targetGraphs": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using InvoiceMaster.Application.Commands.Auth;
|
||||
using InvoiceMaster.Application.Services;
|
||||
using MediatR;
|
||||
|
||||
namespace InvoiceMaster.Application.Commands.Auth.Handlers;
|
||||
|
||||
public class RegisterCommandHandler : IRequestHandler<RegisterCommand, AuthResultDto>
|
||||
{
|
||||
private readonly IAuthService _authService;
|
||||
|
||||
public RegisterCommandHandler(IAuthService authService)
|
||||
{
|
||||
_authService = authService;
|
||||
}
|
||||
|
||||
public async Task<AuthResultDto> Handle(RegisterCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _authService.RegisterAsync(request, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
public class LoginCommandHandler : IRequestHandler<LoginCommand, AuthResultDto>
|
||||
{
|
||||
private readonly IAuthService _authService;
|
||||
|
||||
public LoginCommandHandler(IAuthService authService)
|
||||
{
|
||||
_authService = authService;
|
||||
}
|
||||
|
||||
public async Task<AuthResultDto> Handle(LoginCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _authService.LoginAsync(request, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
public class RefreshTokenCommandHandler : IRequestHandler<RefreshTokenCommand, TokenResultDto?>
|
||||
{
|
||||
private readonly IAuthService _authService;
|
||||
|
||||
public RefreshTokenCommandHandler(IAuthService authService)
|
||||
{
|
||||
_authService = authService;
|
||||
}
|
||||
|
||||
public async Task<TokenResultDto?> Handle(RefreshTokenCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _authService.RefreshTokenAsync(request.RefreshToken, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
public class LogoutCommandHandler : IRequestHandler<LogoutCommand, bool>
|
||||
{
|
||||
private readonly IAuthService _authService;
|
||||
|
||||
public LogoutCommandHandler(IAuthService authService)
|
||||
{
|
||||
_authService = authService;
|
||||
}
|
||||
|
||||
public async Task<bool> Handle(LogoutCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _authService.LogoutAsync(request.UserId, cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using MediatR;
|
||||
|
||||
namespace InvoiceMaster.Application.Commands.Auth;
|
||||
|
||||
public record RegisterCommand(
|
||||
string Email,
|
||||
string Password,
|
||||
string? FullName) : IRequest<AuthResultDto>;
|
||||
|
||||
public record LoginCommand(
|
||||
string Email,
|
||||
string Password) : IRequest<AuthResultDto>;
|
||||
|
||||
public record RefreshTokenCommand(
|
||||
string RefreshToken) : IRequest<TokenResultDto>;
|
||||
|
||||
public record LogoutCommand(
|
||||
Guid UserId) : IRequest<bool>;
|
||||
|
||||
public record AuthResultDto(
|
||||
bool Success,
|
||||
string? ErrorMessage,
|
||||
UserDto? User,
|
||||
TokenResultDto? Tokens);
|
||||
|
||||
public record TokenResultDto(
|
||||
string AccessToken,
|
||||
string RefreshToken,
|
||||
int ExpiresIn);
|
||||
|
||||
public record UserDto(
|
||||
Guid Id,
|
||||
string Email,
|
||||
string? FullName,
|
||||
DateTime CreatedAt);
|
||||
@@ -0,0 +1,33 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace InvoiceMaster.Application.Commands.Auth;
|
||||
|
||||
public class RegisterCommandValidator : AbstractValidator<RegisterCommand>
|
||||
{
|
||||
public RegisterCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.Email)
|
||||
.NotEmpty().WithMessage("Email is required")
|
||||
.EmailAddress().WithMessage("Invalid email format");
|
||||
|
||||
RuleFor(x => x.Password)
|
||||
.NotEmpty().WithMessage("Password is required")
|
||||
.MinimumLength(8).WithMessage("Password must be at least 8 characters")
|
||||
.Matches("[A-Z]").WithMessage("Password must contain at least one uppercase letter")
|
||||
.Matches("[a-z]").WithMessage("Password must contain at least one lowercase letter")
|
||||
.Matches("[0-9]").WithMessage("Password must contain at least one digit");
|
||||
}
|
||||
}
|
||||
|
||||
public class LoginCommandValidator : AbstractValidator<LoginCommand>
|
||||
{
|
||||
public LoginCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.Email)
|
||||
.NotEmpty().WithMessage("Email is required")
|
||||
.EmailAddress().WithMessage("Invalid email format");
|
||||
|
||||
RuleFor(x => x.Password)
|
||||
.NotEmpty().WithMessage("Password is required");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
using InvoiceMaster.Application.DTOs;
|
||||
using InvoiceMaster.Core.Entities;
|
||||
using InvoiceMaster.Core.Interfaces;
|
||||
using InvoiceMaster.Integrations.Accounting;
|
||||
using MediatR;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace InvoiceMaster.Application.Commands.Invoices;
|
||||
|
||||
public record ImportInvoiceCommand(
|
||||
Guid InvoiceId,
|
||||
Guid UserId,
|
||||
bool CreateSupplier = false,
|
||||
AccountingSupplier? SupplierData = null) : IRequest<ImportInvoiceResult>;
|
||||
|
||||
public record ImportInvoiceResult(
|
||||
bool Success,
|
||||
string? ErrorMessage,
|
||||
ImportedInvoiceDto? Data);
|
||||
|
||||
public record ImportedInvoiceDto(
|
||||
Guid Id,
|
||||
string Status,
|
||||
string Provider,
|
||||
VoucherInfoDto? Voucher,
|
||||
SupplierInfoDto? Supplier,
|
||||
AttachmentInfoDto? Attachment,
|
||||
DateTime ImportedAt);
|
||||
|
||||
public record SupplierInfoDto(
|
||||
string Number,
|
||||
string Name);
|
||||
|
||||
public record AttachmentInfoDto(
|
||||
string Id,
|
||||
bool Uploaded);
|
||||
|
||||
public class ImportInvoiceCommandHandler : IRequestHandler<ImportInvoiceCommand, ImportInvoiceResult>
|
||||
{
|
||||
private readonly IRepository<Invoice> _invoiceRepository;
|
||||
private readonly IRepository<AccountingConnection> _connectionRepository;
|
||||
private readonly IAccountingSystemFactory _accountingFactory;
|
||||
private readonly IBlobStorageService _blobStorage;
|
||||
private readonly ISupplierMatchingService _supplierMatching;
|
||||
private readonly IVoucherGenerationService _voucherGeneration;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
|
||||
public ImportInvoiceCommandHandler(
|
||||
IRepository<Invoice> invoiceRepository,
|
||||
IRepository<AccountingConnection> connectionRepository,
|
||||
IAccountingSystemFactory accountingFactory,
|
||||
IBlobStorageService blobStorage,
|
||||
ISupplierMatchingService supplierMatching,
|
||||
IVoucherGenerationService voucherGeneration,
|
||||
IUnitOfWork unitOfWork)
|
||||
{
|
||||
_invoiceRepository = invoiceRepository;
|
||||
_connectionRepository = connectionRepository;
|
||||
_accountingFactory = accountingFactory;
|
||||
_blobStorage = blobStorage;
|
||||
_supplierMatching = supplierMatching;
|
||||
_voucherGeneration = voucherGeneration;
|
||||
_unitOfWork = unitOfWork;
|
||||
}
|
||||
|
||||
public async Task<ImportInvoiceResult> Handle(ImportInvoiceCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var invoice = await _invoiceRepository.GetByIdAsync(request.InvoiceId, cancellationToken);
|
||||
if (invoice == null)
|
||||
{
|
||||
return new ImportInvoiceResult(false, "Invoice not found", null);
|
||||
}
|
||||
|
||||
if (invoice.Status == InvoiceStatus.Imported)
|
||||
{
|
||||
return new ImportInvoiceResult(false, "Invoice already imported", null);
|
||||
}
|
||||
|
||||
var connection = await _connectionRepository.GetByIdAsync(invoice.ConnectionId, cancellationToken);
|
||||
if (connection == null || !connection.IsActive)
|
||||
{
|
||||
return new ImportInvoiceResult(false, "Connection not found or inactive", null);
|
||||
}
|
||||
|
||||
invoice.SetStatus(InvoiceStatus.Importing);
|
||||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||||
|
||||
var accounting = _accountingFactory.Create(connection.Provider);
|
||||
|
||||
string? supplierNumber = invoice.SupplierNumber;
|
||||
if (string.IsNullOrEmpty(supplierNumber))
|
||||
{
|
||||
if (request.CreateSupplier && request.SupplierData != null)
|
||||
{
|
||||
var createdSupplier = await accounting.CreateSupplierAsync(
|
||||
connection.AccessTokenEncrypted,
|
||||
request.SupplierData,
|
||||
cancellationToken);
|
||||
supplierNumber = createdSupplier.SupplierNumber;
|
||||
}
|
||||
else
|
||||
{
|
||||
var matchResult = await _supplierMatching.MatchSupplierAsync(
|
||||
connection.Id,
|
||||
invoice.ExtractedSupplierName,
|
||||
invoice.ExtractedSupplierOrgNumber,
|
||||
cancellationToken);
|
||||
|
||||
if (matchResult.Action == SupplierMatchAction.UseExisting)
|
||||
{
|
||||
supplierNumber = matchResult.SupplierNumber;
|
||||
}
|
||||
else if (matchResult.Action == SupplierMatchAction.CreateNew && connection.AutoCreateSupplier)
|
||||
{
|
||||
var newSupplier = new AccountingSupplier(
|
||||
"",
|
||||
invoice.ExtractedSupplierName ?? "Unknown",
|
||||
invoice.ExtractedSupplierOrgNumber);
|
||||
|
||||
var created = await accounting.CreateSupplierAsync(
|
||||
connection.AccessTokenEncrypted,
|
||||
newSupplier,
|
||||
cancellationToken);
|
||||
supplierNumber = created.SupplierNumber;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
invoice.SetSupplierMatch(
|
||||
supplierNumber ?? "",
|
||||
1.0m,
|
||||
SupplierMatchAction.UseExisting);
|
||||
|
||||
var voucher = await _voucherGeneration.GenerateVoucherAsync(
|
||||
invoice,
|
||||
connection,
|
||||
supplierNumber,
|
||||
cancellationToken);
|
||||
|
||||
var createdVoucher = await accounting.CreateVoucherAsync(
|
||||
connection.AccessTokenEncrypted,
|
||||
voucher,
|
||||
cancellationToken);
|
||||
|
||||
invoice.SetVoucher(
|
||||
createdVoucher.Series ?? connection.DefaultVoucherSeries,
|
||||
"",
|
||||
"",
|
||||
JsonSerializer.Serialize(createdVoucher.Rows));
|
||||
|
||||
string? attachmentId = null;
|
||||
if (connection.AutoAttachPdf)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var fileStream = await _blobStorage.DownloadAsync(invoice.StoragePath, cancellationToken);
|
||||
attachmentId = await accounting.UploadAttachmentAsync(
|
||||
connection.AccessTokenEncrypted,
|
||||
invoice.OriginalFilename,
|
||||
fileStream,
|
||||
cancellationToken);
|
||||
|
||||
invoice.SetAttachment(attachmentId, "");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Failed to upload attachment: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
invoice.SetStatus(InvoiceStatus.Imported);
|
||||
invoice.SetReviewed(request.UserId);
|
||||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||||
|
||||
var result = new ImportedInvoiceDto(
|
||||
invoice.Id,
|
||||
invoice.Status.ToString().ToLower(),
|
||||
invoice.Provider,
|
||||
new VoucherInfoDto(
|
||||
invoice.VoucherSeries,
|
||||
invoice.VoucherNumber,
|
||||
invoice.VoucherUrl),
|
||||
supplierNumber != null
|
||||
? new SupplierInfoDto(supplierNumber, invoice.ExtractedSupplierName ?? "Unknown")
|
||||
: null,
|
||||
attachmentId != null
|
||||
? new AttachmentInfoDto(attachmentId, true)
|
||||
: null,
|
||||
DateTime.UtcNow);
|
||||
|
||||
return new ImportInvoiceResult(true, null, result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var invoice = await _invoiceRepository.GetByIdAsync(request.InvoiceId, cancellationToken);
|
||||
if (invoice != null)
|
||||
{
|
||||
invoice.SetError("IMPORT_FAILED", ex.Message);
|
||||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
return new ImportInvoiceResult(false, ex.Message, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
using InvoiceMaster.Application.DTOs;
|
||||
using InvoiceMaster.Core.Entities;
|
||||
using InvoiceMaster.Core.Interfaces;
|
||||
using MediatR;
|
||||
|
||||
namespace InvoiceMaster.Application.Commands.Invoices;
|
||||
|
||||
public record UploadInvoiceCommand(
|
||||
Guid UserId,
|
||||
string Provider,
|
||||
string FileName,
|
||||
Stream FileStream,
|
||||
long FileSize,
|
||||
string ContentType) : IRequest<UploadInvoiceResult>;
|
||||
|
||||
public record UploadInvoiceResult(
|
||||
bool Success,
|
||||
string? ErrorMessage,
|
||||
InvoiceDetailDto? Invoice);
|
||||
|
||||
public class UploadInvoiceCommandHandler : IRequestHandler<UploadInvoiceCommand, UploadInvoiceResult>
|
||||
{
|
||||
private readonly IRepository<Invoice> _invoiceRepository;
|
||||
private readonly IRepository<AccountingConnection> _connectionRepository;
|
||||
private readonly IBlobStorageService _blobStorage;
|
||||
private readonly IOcrService _ocrService;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
|
||||
public UploadInvoiceCommandHandler(
|
||||
IRepository<Invoice> invoiceRepository,
|
||||
IRepository<AccountingConnection> connectionRepository,
|
||||
IBlobStorageService blobStorage,
|
||||
IOcrService ocrService,
|
||||
IUnitOfWork unitOfWork)
|
||||
{
|
||||
_invoiceRepository = invoiceRepository;
|
||||
_connectionRepository = connectionRepository;
|
||||
_blobStorage = blobStorage;
|
||||
_ocrService = ocrService;
|
||||
_unitOfWork = unitOfWork;
|
||||
}
|
||||
|
||||
public async Task<UploadInvoiceResult> Handle(UploadInvoiceCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var connections = await _connectionRepository.GetAllAsync(cancellationToken);
|
||||
var connection = connections.FirstOrDefault(c =>
|
||||
c.UserId == request.UserId &&
|
||||
c.Provider.Equals(request.Provider, StringComparison.OrdinalIgnoreCase) &&
|
||||
c.IsActive);
|
||||
|
||||
if (connection == null)
|
||||
{
|
||||
return new UploadInvoiceResult(false, $"No active connection found for provider '{request.Provider}'", null);
|
||||
}
|
||||
|
||||
var storagePath = await _blobStorage.UploadAsync(request.FileName, request.FileStream, request.ContentType, cancellationToken);
|
||||
|
||||
var invoice = Invoice.Create(
|
||||
connection.Id,
|
||||
request.Provider,
|
||||
request.FileName,
|
||||
storagePath,
|
||||
request.FileSize);
|
||||
|
||||
await _invoiceRepository.AddAsync(invoice, cancellationToken);
|
||||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||||
|
||||
request.FileStream.Position = 0;
|
||||
var ocrResult = await _ocrService.ExtractAsync(request.FileStream, request.FileName, cancellationToken);
|
||||
|
||||
if (ocrResult.Success && ocrResult.Data != null)
|
||||
{
|
||||
var data = ocrResult.Data;
|
||||
invoice.SetExtractionData(
|
||||
System.Text.Json.JsonSerializer.Serialize(data),
|
||||
ocrResult.Confidence,
|
||||
data.SupplierName,
|
||||
data.SupplierOrgNumber,
|
||||
data.InvoiceNumber,
|
||||
data.InvoiceDate,
|
||||
data.DueDate,
|
||||
data.AmountTotal,
|
||||
data.AmountVat,
|
||||
data.VatRate,
|
||||
data.OcrNumber,
|
||||
data.Bankgiro,
|
||||
data.Plusgiro,
|
||||
data.Currency);
|
||||
|
||||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
var dto = MapToDto(invoice);
|
||||
return new UploadInvoiceResult(true, null, dto);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new UploadInvoiceResult(false, ex.Message, null);
|
||||
}
|
||||
}
|
||||
|
||||
private static InvoiceDetailDto MapToDto(Invoice invoice)
|
||||
{
|
||||
return new InvoiceDetailDto(
|
||||
invoice.Id,
|
||||
invoice.Status.ToString().ToLower(),
|
||||
invoice.Provider,
|
||||
new FileInfoDto(
|
||||
invoice.OriginalFilename,
|
||||
invoice.FileSize,
|
||||
$"/api/v1/invoices/{invoice.Id}/file"),
|
||||
new ExtractionDataDto(
|
||||
invoice.ExtractedSupplierName,
|
||||
invoice.ExtractedSupplierOrgNumber,
|
||||
invoice.ExtractedInvoiceNumber,
|
||||
invoice.ExtractedInvoiceDate,
|
||||
invoice.ExtractedDueDate,
|
||||
invoice.ExtractedAmountTotal,
|
||||
invoice.ExtractedAmountVat,
|
||||
invoice.ExtractedVatRate,
|
||||
invoice.ExtractedOcrNumber,
|
||||
invoice.ExtractedBankgiro,
|
||||
invoice.ExtractedPlusgiro,
|
||||
invoice.ExtractedCurrency,
|
||||
invoice.ExtractionConfidence),
|
||||
invoice.SupplierNumber != null
|
||||
? new SupplierMatchDto(
|
||||
invoice.SupplierMatchAction?.ToString() ?? "USE_EXISTING",
|
||||
invoice.SupplierNumber,
|
||||
invoice.ExtractedSupplierName,
|
||||
invoice.SupplierMatchConfidence)
|
||||
: null,
|
||||
null,
|
||||
invoice.CreatedAt);
|
||||
}
|
||||
}
|
||||
17
backend/src/InvoiceMaster.Application/DTOs/AuthDtos.cs
Normal file
17
backend/src/InvoiceMaster.Application/DTOs/AuthDtos.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace InvoiceMaster.Application.DTOs;
|
||||
|
||||
public record UserDto(
|
||||
Guid Id,
|
||||
string Email,
|
||||
string? FullName,
|
||||
DateTime CreatedAt,
|
||||
bool IsActive);
|
||||
|
||||
public record AuthResponseDto(
|
||||
UserDto User,
|
||||
TokenDto Tokens);
|
||||
|
||||
public record TokenDto(
|
||||
string AccessToken,
|
||||
string RefreshToken,
|
||||
int ExpiresIn);
|
||||
23
backend/src/InvoiceMaster.Application/DTOs/ConnectionDtos.cs
Normal file
23
backend/src/InvoiceMaster.Application/DTOs/ConnectionDtos.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace InvoiceMaster.Application.DTOs;
|
||||
|
||||
public record ConnectionDto(
|
||||
string Provider,
|
||||
bool Connected,
|
||||
string? CompanyName,
|
||||
string? CompanyOrgNumber,
|
||||
List<string>? Scopes,
|
||||
DateTime? ExpiresAt,
|
||||
ConnectionSettingsDto? Settings);
|
||||
|
||||
public record ConnectionSettingsDto(
|
||||
string DefaultVoucherSeries,
|
||||
int DefaultAccountCode,
|
||||
bool AutoAttachPdf,
|
||||
bool AutoCreateSupplier);
|
||||
|
||||
public record ProviderInfoDto(
|
||||
string Id,
|
||||
string Name,
|
||||
string Description,
|
||||
bool Available,
|
||||
bool Connected);
|
||||
66
backend/src/InvoiceMaster.Application/DTOs/InvoiceDtos.cs
Normal file
66
backend/src/InvoiceMaster.Application/DTOs/InvoiceDtos.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using InvoiceMaster.Core.Entities;
|
||||
|
||||
namespace InvoiceMaster.Application.DTOs;
|
||||
|
||||
public record InvoiceListItemDto(
|
||||
Guid Id,
|
||||
string Status,
|
||||
string Provider,
|
||||
string FileName,
|
||||
string? SupplierName,
|
||||
decimal? AmountTotal,
|
||||
DateTime? InvoiceDate,
|
||||
VoucherInfoDto? Voucher,
|
||||
DateTime CreatedAt);
|
||||
|
||||
public record VoucherInfoDto(
|
||||
string? Series,
|
||||
string? Number,
|
||||
string? Url);
|
||||
|
||||
public record InvoiceDetailDto(
|
||||
Guid Id,
|
||||
string Status,
|
||||
string Provider,
|
||||
FileInfoDto File,
|
||||
ExtractionDataDto Extraction,
|
||||
SupplierMatchDto? SupplierMatch,
|
||||
VoucherPreviewDto? VoucherPreview,
|
||||
DateTime CreatedAt);
|
||||
|
||||
public record FileInfoDto(
|
||||
string Name,
|
||||
long? Size,
|
||||
string Url);
|
||||
|
||||
public record ExtractionDataDto(
|
||||
string? SupplierName,
|
||||
string? SupplierOrgNumber,
|
||||
string? InvoiceNumber,
|
||||
DateTime? InvoiceDate,
|
||||
DateTime? DueDate,
|
||||
decimal? AmountTotal,
|
||||
decimal? AmountVat,
|
||||
int? VatRate,
|
||||
string? OcrNumber,
|
||||
string? Bankgiro,
|
||||
string? Plusgiro,
|
||||
string Currency,
|
||||
decimal? Confidence);
|
||||
|
||||
public record SupplierMatchDto(
|
||||
string Action,
|
||||
string? SupplierNumber,
|
||||
string? SupplierName,
|
||||
decimal? Confidence);
|
||||
|
||||
public record VoucherPreviewDto(
|
||||
string? Series,
|
||||
List<VoucherRowDto> Rows);
|
||||
|
||||
public record VoucherRowDto(
|
||||
int Account,
|
||||
string? AccountName,
|
||||
decimal Debit,
|
||||
decimal Credit,
|
||||
string? Description);
|
||||
22
backend/src/InvoiceMaster.Application/DependencyInjection.cs
Normal file
22
backend/src/InvoiceMaster.Application/DependencyInjection.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using InvoiceMaster.Application.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace InvoiceMaster.Application;
|
||||
|
||||
public static class DependencyInjection
|
||||
{
|
||||
public static IServiceCollection AddApplicationServices(this IServiceCollection services)
|
||||
{
|
||||
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
|
||||
|
||||
services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(assembly));
|
||||
services.AddAutoMapper(assembly);
|
||||
services.AddValidatorsFromAssembly(assembly);
|
||||
|
||||
services.AddScoped<IAuthService, AuthService>();
|
||||
services.AddScoped<ISupplierMatchingService, SupplierMatchingService>();
|
||||
services.AddScoped<IVoucherGenerationService, VoucherGenerationService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<AssemblyName>InvoiceMaster.Application</AssemblyName>
|
||||
<RootNamespace>InvoiceMaster.Application</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MediatR" Version="12.2.0" />
|
||||
<PackageReference Include="AutoMapper" Version="12.0.1" />
|
||||
<PackageReference Include="FluentValidation" Version="11.8.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\InvoiceMaster.Core\InvoiceMaster.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
220
backend/src/InvoiceMaster.Application/Services/AuthService.cs
Normal file
220
backend/src/InvoiceMaster.Application/Services/AuthService.cs
Normal file
@@ -0,0 +1,220 @@
|
||||
using InvoiceMaster.Application.Commands.Auth;
|
||||
using InvoiceMaster.Core.Entities;
|
||||
using InvoiceMaster.Core.Interfaces;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace InvoiceMaster.Application.Services;
|
||||
|
||||
public interface IAuthService
|
||||
{
|
||||
Task<AuthResultDto> RegisterAsync(RegisterCommand command, CancellationToken cancellationToken = default);
|
||||
Task<AuthResultDto> LoginAsync(LoginCommand command, CancellationToken cancellationToken = default);
|
||||
Task<TokenResultDto?> RefreshTokenAsync(string refreshToken, CancellationToken cancellationToken = default);
|
||||
Task<bool> LogoutAsync(Guid userId, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public class AuthService : IAuthService
|
||||
{
|
||||
private readonly IRepository<User> _userRepository;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
public AuthService(
|
||||
IRepository<User> userRepository,
|
||||
IUnitOfWork unitOfWork,
|
||||
IConfiguration configuration)
|
||||
{
|
||||
_userRepository = userRepository;
|
||||
_unitOfWork = unitOfWork;
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
public async Task<AuthResultDto> RegisterAsync(RegisterCommand command, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var existingUser = await _userRepository.GetAllAsync(cancellationToken);
|
||||
if (existingUser.Any(u => u.Email.Equals(command.Email, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return new AuthResultDto(false, "Email already registered", null, null);
|
||||
}
|
||||
|
||||
var hashedPassword = HashPassword(command.Password);
|
||||
var user = new User
|
||||
{
|
||||
Email = command.Email,
|
||||
HashedPassword = hashedPassword,
|
||||
FullName = command.FullName
|
||||
};
|
||||
|
||||
await _userRepository.AddAsync(user, cancellationToken);
|
||||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||||
|
||||
var tokens = GenerateTokens(user);
|
||||
|
||||
return new AuthResultDto(
|
||||
true,
|
||||
null,
|
||||
new UserDto(user.Id, user.Email, user.FullName, user.CreatedAt),
|
||||
tokens);
|
||||
}
|
||||
|
||||
public async Task<AuthResultDto> LoginAsync(LoginCommand command, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var users = await _userRepository.GetAllAsync(cancellationToken);
|
||||
var user = users.FirstOrDefault(u =>
|
||||
u.Email.Equals(command.Email, StringComparison.OrdinalIgnoreCase) && u.IsActive);
|
||||
|
||||
if (user == null || !VerifyPassword(command.Password, user.HashedPassword))
|
||||
{
|
||||
return new AuthResultDto(false, "Invalid email or password", null, null);
|
||||
}
|
||||
|
||||
user.RecordLogin();
|
||||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||||
|
||||
var tokens = GenerateTokens(user);
|
||||
|
||||
return new AuthResultDto(
|
||||
true,
|
||||
null,
|
||||
new UserDto(user.Id, user.Email, user.FullName, user.CreatedAt),
|
||||
tokens);
|
||||
}
|
||||
|
||||
public async Task<TokenResultDto?> RefreshTokenAsync(string refreshToken, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var principal = GetPrincipalFromExpiredToken(refreshToken);
|
||||
if (principal == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var userId = principal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
if (userId == null || !Guid.TryParse(userId, out var guid))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var user = await _userRepository.GetByIdAsync(guid, cancellationToken);
|
||||
if (user == null || !user.IsActive)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return GenerateTokens(user);
|
||||
}
|
||||
|
||||
public async Task<bool> LogoutAsync(Guid userId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
private TokenResultDto GenerateTokens(User user)
|
||||
{
|
||||
var accessToken = GenerateAccessToken(user);
|
||||
var refreshToken = GenerateRefreshToken(user);
|
||||
var expiresIn = _configuration.GetValue<int>("Jwt:AccessTokenExpirationMinutes", 15);
|
||||
|
||||
return new TokenResultDto(accessToken, refreshToken, expiresIn * 60);
|
||||
}
|
||||
|
||||
private string GenerateAccessToken(User user)
|
||||
{
|
||||
var secretKey = _configuration["Jwt:SecretKey"] ?? throw new InvalidOperationException("JWT SecretKey not configured");
|
||||
var issuer = _configuration["Jwt:Issuer"] ?? "InvoiceMaster";
|
||||
var audience = _configuration["Jwt:Audience"] ?? "InvoiceMaster.Client";
|
||||
var expirationMinutes = _configuration.GetValue<int>("Jwt:AccessTokenExpirationMinutes", 15);
|
||||
|
||||
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey));
|
||||
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
|
||||
|
||||
var claims = new[]
|
||||
{
|
||||
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
|
||||
new Claim(ClaimTypes.Email, user.Email),
|
||||
new Claim(ClaimTypes.Name, user.FullName ?? user.Email)
|
||||
};
|
||||
|
||||
var token = new JwtSecurityToken(
|
||||
issuer: issuer,
|
||||
audience: audience,
|
||||
claims: claims,
|
||||
expires: DateTime.UtcNow.AddMinutes(expirationMinutes),
|
||||
signingCredentials: credentials);
|
||||
|
||||
return new JwtSecurityTokenHandler().WriteToken(token);
|
||||
}
|
||||
|
||||
private string GenerateRefreshToken(User user)
|
||||
{
|
||||
var secretKey = _configuration["Jwt:SecretKey"] ?? throw new InvalidOperationException("JWT SecretKey not configured");
|
||||
var issuer = _configuration["Jwt:Issuer"] ?? "InvoiceMaster";
|
||||
var audience = _configuration["Jwt:Audience"] ?? "InvoiceMaster.Client";
|
||||
var expirationDays = _configuration.GetValue<int>("Jwt:RefreshTokenExpirationDays", 7);
|
||||
|
||||
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey));
|
||||
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
|
||||
|
||||
var claims = new[]
|
||||
{
|
||||
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
|
||||
new Claim("token_type", "refresh")
|
||||
};
|
||||
|
||||
var token = new JwtSecurityToken(
|
||||
issuer: issuer,
|
||||
audience: audience,
|
||||
claims: claims,
|
||||
expires: DateTime.UtcNow.AddDays(expirationDays),
|
||||
signingCredentials: credentials);
|
||||
|
||||
return new JwtSecurityTokenHandler().WriteToken(token);
|
||||
}
|
||||
|
||||
private ClaimsPrincipal? GetPrincipalFromExpiredToken(string token)
|
||||
{
|
||||
var secretKey = _configuration["Jwt:SecretKey"] ?? throw new InvalidOperationException("JWT SecretKey not configured");
|
||||
var tokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidateAudience = true,
|
||||
ValidateIssuer = true,
|
||||
ValidateIssuerSigningKey = true,
|
||||
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey)),
|
||||
ValidateLifetime = false,
|
||||
ValidIssuer = _configuration["Jwt:Issuer"],
|
||||
ValidAudience = _configuration["Jwt:Audience"]
|
||||
};
|
||||
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
try
|
||||
{
|
||||
var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out var securityToken);
|
||||
if (securityToken is not JwtSecurityToken jwtSecurityToken ||
|
||||
!jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return principal;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static string HashPassword(string password)
|
||||
{
|
||||
using var sha256 = SHA256.Create();
|
||||
var hashedBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(password));
|
||||
return Convert.ToBase64String(hashedBytes);
|
||||
}
|
||||
|
||||
private static bool VerifyPassword(string password, string hashedPassword)
|
||||
{
|
||||
return HashPassword(password) == hashedPassword;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
using InvoiceMaster.Core.Entities;
|
||||
using InvoiceMaster.Core.Interfaces;
|
||||
using InvoiceMaster.Integrations.Accounting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace InvoiceMaster.Application.Services;
|
||||
|
||||
public interface ISupplierMatchingService
|
||||
{
|
||||
Task<SupplierMatchResult> MatchSupplierAsync(
|
||||
Guid connectionId,
|
||||
string? supplierName,
|
||||
string? orgNumber,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<List<SupplierCache>> GetCachedSuppliersAsync(
|
||||
Guid connectionId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task RefreshSupplierCacheAsync(
|
||||
Guid connectionId,
|
||||
string accessToken,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public class SupplierMatchingService : ISupplierMatchingService
|
||||
{
|
||||
private readonly IRepository<SupplierCache> _supplierCacheRepository;
|
||||
private readonly IRepository<AccountingConnection> _connectionRepository;
|
||||
private readonly IAccountingSystemFactory _accountingFactory;
|
||||
private readonly ILogger<SupplierMatchingService> _logger;
|
||||
|
||||
public SupplierMatchingService(
|
||||
IRepository<SupplierCache> supplierCacheRepository,
|
||||
IRepository<AccountingConnection> connectionRepository,
|
||||
IAccountingSystemFactory accountingFactory,
|
||||
ILogger<SupplierMatchingService> logger)
|
||||
{
|
||||
_supplierCacheRepository = supplierCacheRepository;
|
||||
_connectionRepository = connectionRepository;
|
||||
_accountingFactory = accountingFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<SupplierMatchResult> MatchSupplierAsync(
|
||||
Guid connectionId,
|
||||
string? supplierName,
|
||||
string? orgNumber,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var cachedSuppliers = await GetCachedSuppliersAsync(connectionId, cancellationToken);
|
||||
|
||||
if (!string.IsNullOrEmpty(orgNumber))
|
||||
{
|
||||
var normalizedOrgNumber = NormalizeOrgNumber(orgNumber);
|
||||
var exactMatch = cachedSuppliers.FirstOrDefault(s =>
|
||||
NormalizeOrgNumber(s.OrganisationNumber) == normalizedOrgNumber);
|
||||
|
||||
if (exactMatch != null)
|
||||
{
|
||||
return new SupplierMatchResult(
|
||||
SupplierMatchAction.UseExisting,
|
||||
exactMatch.SupplierNumber,
|
||||
exactMatch.Name,
|
||||
1.0m);
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(supplierName))
|
||||
{
|
||||
var bestMatch = FindBestNameMatch(supplierName, cachedSuppliers);
|
||||
if (bestMatch != null && bestMatch.Score > 0.8m)
|
||||
{
|
||||
return new SupplierMatchResult(
|
||||
SupplierMatchAction.UseExisting,
|
||||
bestMatch.Supplier.SupplierNumber,
|
||||
bestMatch.Supplier.Name,
|
||||
bestMatch.Score);
|
||||
}
|
||||
|
||||
if (bestMatch != null && bestMatch.Score > 0.6m)
|
||||
{
|
||||
return new SupplierMatchResult(
|
||||
SupplierMatchAction.SuggestMatch,
|
||||
bestMatch.Supplier.SupplierNumber,
|
||||
bestMatch.Supplier.Name,
|
||||
bestMatch.Score);
|
||||
}
|
||||
}
|
||||
|
||||
return new SupplierMatchResult(
|
||||
SupplierMatchAction.CreateNew,
|
||||
null,
|
||||
supplierName,
|
||||
0m);
|
||||
}
|
||||
|
||||
public async Task<List<SupplierCache>> GetCachedSuppliersAsync(
|
||||
Guid connectionId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var allCached = await _supplierCacheRepository.GetAllAsync(cancellationToken);
|
||||
return allCached.Where(s => s.ConnectionId == connectionId && !s.IsExpired()).ToList();
|
||||
}
|
||||
|
||||
public async Task RefreshSupplierCacheAsync(
|
||||
Guid connectionId,
|
||||
string accessToken,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var connection = await _connectionRepository.GetByIdAsync(connectionId, cancellationToken);
|
||||
if (connection == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Connection {connectionId} not found");
|
||||
}
|
||||
|
||||
var accounting = _accountingFactory.Create(connection.Provider);
|
||||
var suppliers = await accounting.GetSuppliersAsync(accessToken, cancellationToken);
|
||||
|
||||
var existingCache = await GetCachedSuppliersAsync(connectionId, cancellationToken);
|
||||
var existingDict = existingCache.ToDictionary(s => s.SupplierNumber);
|
||||
|
||||
foreach (var supplier in suppliers)
|
||||
{
|
||||
if (existingDict.TryGetValue(supplier.SupplierNumber, out var existing))
|
||||
{
|
||||
existing.UpdateDetails(
|
||||
supplier.Name,
|
||||
supplier.OrganisationNumber,
|
||||
supplier.Address1,
|
||||
null,
|
||||
supplier.Postcode,
|
||||
supplier.City,
|
||||
null,
|
||||
supplier.Phone,
|
||||
supplier.Email,
|
||||
supplier.BankgiroNumber,
|
||||
supplier.PlusgiroNumber);
|
||||
|
||||
await _supplierCacheRepository.UpdateAsync(existing, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
var newCache = SupplierCache.Create(
|
||||
connectionId,
|
||||
supplier.SupplierNumber,
|
||||
supplier.Name,
|
||||
supplier.OrganisationNumber,
|
||||
supplier.Address1,
|
||||
supplier.Postcode,
|
||||
supplier.City);
|
||||
|
||||
newCache.UpdateDetails(
|
||||
supplier.Name,
|
||||
supplier.OrganisationNumber,
|
||||
supplier.Address1,
|
||||
null,
|
||||
supplier.Postcode,
|
||||
supplier.City,
|
||||
null,
|
||||
supplier.Phone,
|
||||
supplier.Email,
|
||||
supplier.BankgiroNumber,
|
||||
supplier.PlusgiroNumber);
|
||||
|
||||
await _supplierCacheRepository.AddAsync(newCache, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation("Refreshed {Count} suppliers for connection {ConnectionId}",
|
||||
suppliers.Count, connectionId);
|
||||
}
|
||||
|
||||
private static string NormalizeOrgNumber(string? orgNumber)
|
||||
{
|
||||
if (string.IsNullOrEmpty(orgNumber)) return string.Empty;
|
||||
return orgNumber.Replace("-", "").Replace(" ", "").Trim();
|
||||
}
|
||||
|
||||
private static MatchResult? FindBestNameMatch(string input, List<SupplierCache> candidates)
|
||||
{
|
||||
MatchResult? bestMatch = null;
|
||||
|
||||
foreach (var candidate in candidates)
|
||||
{
|
||||
var score = CalculateSimilarity(input, candidate.Name);
|
||||
if (bestMatch == null || score > bestMatch.Score)
|
||||
{
|
||||
bestMatch = new MatchResult(candidate, score);
|
||||
}
|
||||
}
|
||||
|
||||
return bestMatch;
|
||||
}
|
||||
|
||||
private static decimal CalculateSimilarity(string s1, string s2)
|
||||
{
|
||||
var longer = s1.Length > s2.Length ? s1 : s2;
|
||||
var shorter = s1.Length > s2.Length ? s2 : s1;
|
||||
|
||||
if (longer.Length == 0) return 1.0m;
|
||||
|
||||
var distance = LevenshteinDistance(longer.ToLower(), shorter.ToLower());
|
||||
return 1.0m - (decimal)distance / longer.Length;
|
||||
}
|
||||
|
||||
private static int LevenshteinDistance(string s, string t)
|
||||
{
|
||||
var n = s.Length;
|
||||
var m = t.Length;
|
||||
var d = new int[n + 1, m + 1];
|
||||
|
||||
if (n == 0) return m;
|
||||
if (m == 0) return n;
|
||||
|
||||
for (var i = 0; i <= n; i++) d[i, 0] = i;
|
||||
for (var j = 0; j <= m; j++) d[0, j] = j;
|
||||
|
||||
for (var i = 1; i <= n; i++)
|
||||
{
|
||||
for (var j = 1; j <= m; j++)
|
||||
{
|
||||
var cost = (t[j - 1] == s[i - 1]) ? 0 : 1;
|
||||
d[i, j] = Math.Min(
|
||||
Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1),
|
||||
d[i - 1, j - 1] + cost);
|
||||
}
|
||||
}
|
||||
|
||||
return d[n, m];
|
||||
}
|
||||
}
|
||||
|
||||
public record SupplierMatchResult(
|
||||
SupplierMatchAction Action,
|
||||
string? SupplierNumber,
|
||||
string? SupplierName,
|
||||
decimal Confidence);
|
||||
|
||||
public record MatchResult(SupplierCache Supplier, decimal Score);
|
||||
@@ -0,0 +1,70 @@
|
||||
using InvoiceMaster.Core.Entities;
|
||||
using InvoiceMaster.Integrations.Accounting;
|
||||
|
||||
namespace InvoiceMaster.Application.Services;
|
||||
|
||||
public interface IVoucherGenerationService
|
||||
{
|
||||
Task<AccountingVoucher> GenerateVoucherAsync(
|
||||
Invoice invoice,
|
||||
AccountingConnection connection,
|
||||
string? supplierNumber,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public class VoucherGenerationService : IVoucherGenerationService
|
||||
{
|
||||
public Task<AccountingVoucher> GenerateVoucherAsync(
|
||||
Invoice invoice,
|
||||
AccountingConnection connection,
|
||||
string? supplierNumber,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var rows = new List<AccountingVoucherRow>();
|
||||
var amountTotal = invoice.ExtractedAmountTotal ?? 0;
|
||||
var vatRate = invoice.ExtractedVatRate ?? 25;
|
||||
var amountExclVat = amountTotal / (1 + vatRate / 100m);
|
||||
var vatAmount = amountTotal - amountExclVat;
|
||||
|
||||
var accountCode = connection.DefaultAccountCode;
|
||||
var description = $"{invoice.ExtractedSupplierName} - {invoice.ExtractedInvoiceNumber}";
|
||||
|
||||
rows.Add(new AccountingVoucherRow(
|
||||
accountCode,
|
||||
amountExclVat,
|
||||
0,
|
||||
description));
|
||||
|
||||
if (vatAmount > 0)
|
||||
{
|
||||
var vatAccount = vatRate switch
|
||||
{
|
||||
25 => 2610,
|
||||
12 => 2620,
|
||||
6 => 2630,
|
||||
_ => 2610
|
||||
};
|
||||
|
||||
rows.Add(new AccountingVoucherRow(
|
||||
vatAccount,
|
||||
vatAmount,
|
||||
0,
|
||||
$"Moms {vatRate}%"));
|
||||
}
|
||||
|
||||
rows.Add(new AccountingVoucherRow(
|
||||
2440,
|
||||
0,
|
||||
amountTotal,
|
||||
$"Faktura {invoice.ExtractedInvoiceNumber}",
|
||||
supplierNumber));
|
||||
|
||||
var voucher = new AccountingVoucher(
|
||||
connection.DefaultVoucherSeries,
|
||||
invoice.ExtractedInvoiceDate ?? DateTime.UtcNow,
|
||||
rows,
|
||||
description);
|
||||
|
||||
return Task.FromResult(voucher);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// <autogenerated />
|
||||
using System;
|
||||
using System.Reflection;
|
||||
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")]
|
||||
@@ -0,0 +1,22 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("InvoiceMaster.Application")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("InvoiceMaster.Application")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("InvoiceMaster.Application")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
// Generated by the MSBuild WriteCodeFragment class.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
f6f8c7200df65f81badce9ed069db84dceea88391c8f2b18de6f45a3e2fdd4db
|
||||
@@ -0,0 +1,17 @@
|
||||
is_global = true
|
||||
build_property.TargetFramework = net8.0
|
||||
build_property.TargetFrameworkIdentifier = .NETCoreApp
|
||||
build_property.TargetFrameworkVersion = v8.0
|
||||
build_property.TargetPlatformMinVersion =
|
||||
build_property.UsingMicrosoftNETSdkWeb =
|
||||
build_property.ProjectTypeGuids =
|
||||
build_property.InvariantGlobalization =
|
||||
build_property.PlatformNeutralAssembly =
|
||||
build_property.EnforceExtendedAnalyzerRules =
|
||||
build_property._SupportedPlatformList = Linux,macOS,Windows
|
||||
build_property.RootNamespace = InvoiceMaster.Application
|
||||
build_property.ProjectDir = C:\Users\yaoji\git\ColaCoder\accounting-system\backend\src\InvoiceMaster.Application\
|
||||
build_property.EnableComHosting =
|
||||
build_property.EnableGeneratedComInterfaceComImportInterop =
|
||||
build_property.EffectiveAnalysisLevelStyle = 8.0
|
||||
build_property.EnableCodeStyleSeverity =
|
||||
@@ -0,0 +1,8 @@
|
||||
// <auto-generated/>
|
||||
global using System;
|
||||
global using System.Collections.Generic;
|
||||
global using System.IO;
|
||||
global using System.Linq;
|
||||
global using System.Net.Http;
|
||||
global using System.Threading;
|
||||
global using System.Threading.Tasks;
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,178 @@
|
||||
{
|
||||
"format": 1,
|
||||
"restore": {
|
||||
"C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Application\\InvoiceMaster.Application.csproj": {}
|
||||
},
|
||||
"projects": {
|
||||
"C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Application\\InvoiceMaster.Application.csproj": {
|
||||
"version": "1.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Application\\InvoiceMaster.Application.csproj",
|
||||
"projectName": "InvoiceMaster.Application",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Application\\InvoiceMaster.Application.csproj",
|
||||
"packagesPath": "C:\\Users\\yaoji\\.nuget\\packages\\",
|
||||
"outputPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Application\\obj\\",
|
||||
"projectStyle": "PackageReference",
|
||||
"fallbackFolders": [
|
||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
||||
],
|
||||
"configFilePaths": [
|
||||
"C:\\Users\\yaoji\\AppData\\Roaming\\NuGet\\NuGet.Config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
|
||||
],
|
||||
"originalTargetFrameworks": [
|
||||
"net8.0"
|
||||
],
|
||||
"sources": {
|
||||
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
|
||||
"https://api.nuget.org/v3/index.json": {},
|
||||
"https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"net8.0": {
|
||||
"targetAlias": "net8.0",
|
||||
"projectReferences": {
|
||||
"C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\InvoiceMaster.Core.csproj": {
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\InvoiceMaster.Core.csproj"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"warningProperties": {
|
||||
"allWarningsAsErrors": true,
|
||||
"warnAsError": [
|
||||
"NU1605"
|
||||
]
|
||||
},
|
||||
"restoreAuditProperties": {
|
||||
"enableAudit": "true",
|
||||
"auditLevel": "low",
|
||||
"auditMode": "direct"
|
||||
},
|
||||
"SdkAnalysisLevel": "10.0.100"
|
||||
},
|
||||
"frameworks": {
|
||||
"net8.0": {
|
||||
"targetAlias": "net8.0",
|
||||
"dependencies": {
|
||||
"AutoMapper": {
|
||||
"target": "Package",
|
||||
"version": "[12.0.1, )"
|
||||
},
|
||||
"FluentValidation": {
|
||||
"target": "Package",
|
||||
"version": "[11.8.1, )"
|
||||
},
|
||||
"MediatR": {
|
||||
"target": "Package",
|
||||
"version": "[12.2.0, )"
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": {
|
||||
"target": "Package",
|
||||
"version": "[8.0.0, )"
|
||||
},
|
||||
"StyleCop.Analyzers": {
|
||||
"include": "Runtime, Build, Native, ContentFiles, Analyzers",
|
||||
"suppressParent": "All",
|
||||
"target": "Package",
|
||||
"version": "[1.2.0-beta.556, )"
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"net461",
|
||||
"net462",
|
||||
"net47",
|
||||
"net471",
|
||||
"net472",
|
||||
"net48",
|
||||
"net481"
|
||||
],
|
||||
"assetTargetFallback": true,
|
||||
"warn": true,
|
||||
"frameworkReferences": {
|
||||
"Microsoft.NETCore.App": {
|
||||
"privateAssets": "all"
|
||||
}
|
||||
},
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.102/PortableRuntimeIdentifierGraph.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\InvoiceMaster.Core.csproj": {
|
||||
"version": "1.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\InvoiceMaster.Core.csproj",
|
||||
"projectName": "InvoiceMaster.Core",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\InvoiceMaster.Core.csproj",
|
||||
"packagesPath": "C:\\Users\\yaoji\\.nuget\\packages\\",
|
||||
"outputPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\obj\\",
|
||||
"projectStyle": "PackageReference",
|
||||
"fallbackFolders": [
|
||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
||||
],
|
||||
"configFilePaths": [
|
||||
"C:\\Users\\yaoji\\AppData\\Roaming\\NuGet\\NuGet.Config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
|
||||
],
|
||||
"originalTargetFrameworks": [
|
||||
"net8.0"
|
||||
],
|
||||
"sources": {
|
||||
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
|
||||
"https://api.nuget.org/v3/index.json": {},
|
||||
"https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"net8.0": {
|
||||
"targetAlias": "net8.0",
|
||||
"projectReferences": {}
|
||||
}
|
||||
},
|
||||
"warningProperties": {
|
||||
"allWarningsAsErrors": true,
|
||||
"warnAsError": [
|
||||
"NU1605"
|
||||
]
|
||||
},
|
||||
"restoreAuditProperties": {
|
||||
"enableAudit": "true",
|
||||
"auditLevel": "low",
|
||||
"auditMode": "direct"
|
||||
},
|
||||
"SdkAnalysisLevel": "10.0.100"
|
||||
},
|
||||
"frameworks": {
|
||||
"net8.0": {
|
||||
"targetAlias": "net8.0",
|
||||
"dependencies": {
|
||||
"StyleCop.Analyzers": {
|
||||
"include": "Runtime, Build, Native, ContentFiles, Analyzers",
|
||||
"suppressParent": "All",
|
||||
"target": "Package",
|
||||
"version": "[1.2.0-beta.556, )"
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"net461",
|
||||
"net462",
|
||||
"net47",
|
||||
"net471",
|
||||
"net472",
|
||||
"net48",
|
||||
"net481"
|
||||
],
|
||||
"assetTargetFallback": true,
|
||||
"warn": true,
|
||||
"frameworkReferences": {
|
||||
"Microsoft.NETCore.App": {
|
||||
"privateAssets": "all"
|
||||
}
|
||||
},
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.102/PortableRuntimeIdentifierGraph.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
|
||||
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
|
||||
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
|
||||
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
|
||||
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\yaoji\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages</NuGetPackageFolders>
|
||||
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
|
||||
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">7.0.0</NuGetToolVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<SourceRoot Include="C:\Users\yaoji\.nuget\packages\" />
|
||||
<SourceRoot Include="C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages\" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<PkgStyleCop_Analyzers_Unstable Condition=" '$(PkgStyleCop_Analyzers_Unstable)' == '' ">C:\Users\yaoji\.nuget\packages\stylecop.analyzers.unstable\1.2.0.556</PkgStyleCop_Analyzers_Unstable>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" />
|
||||
466
backend/src/InvoiceMaster.Application/obj/project.assets.json
Normal file
466
backend/src/InvoiceMaster.Application/obj/project.assets.json
Normal file
@@ -0,0 +1,466 @@
|
||||
{
|
||||
"version": 3,
|
||||
"targets": {
|
||||
"net8.0": {
|
||||
"AutoMapper/12.0.1": {
|
||||
"type": "package",
|
||||
"dependencies": {
|
||||
"Microsoft.CSharp": "4.7.0"
|
||||
},
|
||||
"compile": {
|
||||
"lib/netstandard2.1/AutoMapper.dll": {
|
||||
"related": ".xml"
|
||||
}
|
||||
},
|
||||
"runtime": {
|
||||
"lib/netstandard2.1/AutoMapper.dll": {
|
||||
"related": ".xml"
|
||||
}
|
||||
}
|
||||
},
|
||||
"FluentValidation/11.8.1": {
|
||||
"type": "package",
|
||||
"compile": {
|
||||
"lib/net7.0/FluentValidation.dll": {
|
||||
"related": ".xml"
|
||||
}
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net7.0/FluentValidation.dll": {
|
||||
"related": ".xml"
|
||||
}
|
||||
}
|
||||
},
|
||||
"MediatR/12.2.0": {
|
||||
"type": "package",
|
||||
"dependencies": {
|
||||
"MediatR.Contracts": "[2.0.1, 3.0.0)",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0"
|
||||
},
|
||||
"compile": {
|
||||
"lib/net6.0/MediatR.dll": {
|
||||
"related": ".xml"
|
||||
}
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net6.0/MediatR.dll": {
|
||||
"related": ".xml"
|
||||
}
|
||||
}
|
||||
},
|
||||
"MediatR.Contracts/2.0.1": {
|
||||
"type": "package",
|
||||
"compile": {
|
||||
"lib/netstandard2.0/MediatR.Contracts.dll": {
|
||||
"related": ".xml"
|
||||
}
|
||||
},
|
||||
"runtime": {
|
||||
"lib/netstandard2.0/MediatR.Contracts.dll": {
|
||||
"related": ".xml"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.CSharp/4.7.0": {
|
||||
"type": "package",
|
||||
"compile": {
|
||||
"ref/netcoreapp2.0/_._": {}
|
||||
},
|
||||
"runtime": {
|
||||
"lib/netcoreapp2.0/_._": {}
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions/8.0.0": {
|
||||
"type": "package",
|
||||
"compile": {
|
||||
"lib/net8.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": {
|
||||
"related": ".xml"
|
||||
}
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net8.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": {
|
||||
"related": ".xml"
|
||||
}
|
||||
},
|
||||
"build": {
|
||||
"buildTransitive/net6.0/_._": {}
|
||||
}
|
||||
},
|
||||
"StyleCop.Analyzers/1.2.0-beta.556": {
|
||||
"type": "package",
|
||||
"dependencies": {
|
||||
"StyleCop.Analyzers.Unstable": "1.2.0.556"
|
||||
}
|
||||
},
|
||||
"StyleCop.Analyzers.Unstable/1.2.0.556": {
|
||||
"type": "package"
|
||||
},
|
||||
"InvoiceMaster.Core/1.0.0": {
|
||||
"type": "project",
|
||||
"framework": ".NETCoreApp,Version=v8.0",
|
||||
"compile": {
|
||||
"bin/placeholder/InvoiceMaster.Core.dll": {}
|
||||
},
|
||||
"runtime": {
|
||||
"bin/placeholder/InvoiceMaster.Core.dll": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"libraries": {
|
||||
"AutoMapper/12.0.1": {
|
||||
"sha512": "hvV62vl6Hp/WfQ24yzo3Co9+OPl8wH8hApwVtgWpiAynVJkUcs7xvehnSftawL8Pe8FrPffBRM3hwzLQqWDNjA==",
|
||||
"type": "package",
|
||||
"path": "automapper/12.0.1",
|
||||
"files": [
|
||||
".nupkg.metadata",
|
||||
".signature.p7s",
|
||||
"README.md",
|
||||
"automapper.12.0.1.nupkg.sha512",
|
||||
"automapper.nuspec",
|
||||
"icon.png",
|
||||
"lib/netstandard2.1/AutoMapper.dll",
|
||||
"lib/netstandard2.1/AutoMapper.xml"
|
||||
]
|
||||
},
|
||||
"FluentValidation/11.8.1": {
|
||||
"sha512": "N72rnlE99XYB7EGA1u9y7m7kNTTynqOPBhZqDE8zr1Y0aSR4t5si94LRA7UVdAV09GaXWCErW+EiFhfbg3DSbg==",
|
||||
"type": "package",
|
||||
"path": "fluentvalidation/11.8.1",
|
||||
"files": [
|
||||
".nupkg.metadata",
|
||||
".signature.p7s",
|
||||
"README.md",
|
||||
"fluent-validation-icon.png",
|
||||
"fluentvalidation.11.8.1.nupkg.sha512",
|
||||
"fluentvalidation.nuspec",
|
||||
"lib/net5.0/FluentValidation.dll",
|
||||
"lib/net5.0/FluentValidation.xml",
|
||||
"lib/net6.0/FluentValidation.dll",
|
||||
"lib/net6.0/FluentValidation.xml",
|
||||
"lib/net7.0/FluentValidation.dll",
|
||||
"lib/net7.0/FluentValidation.xml",
|
||||
"lib/netstandard2.0/FluentValidation.dll",
|
||||
"lib/netstandard2.0/FluentValidation.xml",
|
||||
"lib/netstandard2.1/FluentValidation.dll",
|
||||
"lib/netstandard2.1/FluentValidation.xml"
|
||||
]
|
||||
},
|
||||
"MediatR/12.2.0": {
|
||||
"sha512": "8TUFrHapKi6D74PhnSNEguRsH91HNGyP3R4ZQdgDorJgl9Wac5Prh0vA33QfrniAaS6L2xNNhc6vxzg+5AIbwA==",
|
||||
"type": "package",
|
||||
"path": "mediatr/12.2.0",
|
||||
"files": [
|
||||
".nupkg.metadata",
|
||||
".signature.p7s",
|
||||
"gradient_128x128.png",
|
||||
"lib/net6.0/MediatR.dll",
|
||||
"lib/net6.0/MediatR.xml",
|
||||
"lib/netstandard2.0/MediatR.dll",
|
||||
"lib/netstandard2.0/MediatR.xml",
|
||||
"mediatr.12.2.0.nupkg.sha512",
|
||||
"mediatr.nuspec"
|
||||
]
|
||||
},
|
||||
"MediatR.Contracts/2.0.1": {
|
||||
"sha512": "FYv95bNT4UwcNA+G/J1oX5OpRiSUxteXaUt2BJbRSdRNiIUNbggJF69wy6mnk2wYToaanpdXZdCwVylt96MpwQ==",
|
||||
"type": "package",
|
||||
"path": "mediatr.contracts/2.0.1",
|
||||
"files": [
|
||||
".nupkg.metadata",
|
||||
".signature.p7s",
|
||||
"gradient_128x128.png",
|
||||
"lib/netstandard2.0/MediatR.Contracts.dll",
|
||||
"lib/netstandard2.0/MediatR.Contracts.xml",
|
||||
"mediatr.contracts.2.0.1.nupkg.sha512",
|
||||
"mediatr.contracts.nuspec"
|
||||
]
|
||||
},
|
||||
"Microsoft.CSharp/4.7.0": {
|
||||
"sha512": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA==",
|
||||
"type": "package",
|
||||
"path": "microsoft.csharp/4.7.0",
|
||||
"files": [
|
||||
".nupkg.metadata",
|
||||
".signature.p7s",
|
||||
"LICENSE.TXT",
|
||||
"THIRD-PARTY-NOTICES.TXT",
|
||||
"lib/MonoAndroid10/_._",
|
||||
"lib/MonoTouch10/_._",
|
||||
"lib/net45/_._",
|
||||
"lib/netcore50/Microsoft.CSharp.dll",
|
||||
"lib/netcoreapp2.0/_._",
|
||||
"lib/netstandard1.3/Microsoft.CSharp.dll",
|
||||
"lib/netstandard2.0/Microsoft.CSharp.dll",
|
||||
"lib/netstandard2.0/Microsoft.CSharp.xml",
|
||||
"lib/portable-net45+win8+wp8+wpa81/_._",
|
||||
"lib/uap10.0.16299/_._",
|
||||
"lib/win8/_._",
|
||||
"lib/wp80/_._",
|
||||
"lib/wpa81/_._",
|
||||
"lib/xamarinios10/_._",
|
||||
"lib/xamarinmac20/_._",
|
||||
"lib/xamarintvos10/_._",
|
||||
"lib/xamarinwatchos10/_._",
|
||||
"microsoft.csharp.4.7.0.nupkg.sha512",
|
||||
"microsoft.csharp.nuspec",
|
||||
"ref/MonoAndroid10/_._",
|
||||
"ref/MonoTouch10/_._",
|
||||
"ref/net45/_._",
|
||||
"ref/netcore50/Microsoft.CSharp.dll",
|
||||
"ref/netcore50/Microsoft.CSharp.xml",
|
||||
"ref/netcore50/de/Microsoft.CSharp.xml",
|
||||
"ref/netcore50/es/Microsoft.CSharp.xml",
|
||||
"ref/netcore50/fr/Microsoft.CSharp.xml",
|
||||
"ref/netcore50/it/Microsoft.CSharp.xml",
|
||||
"ref/netcore50/ja/Microsoft.CSharp.xml",
|
||||
"ref/netcore50/ko/Microsoft.CSharp.xml",
|
||||
"ref/netcore50/ru/Microsoft.CSharp.xml",
|
||||
"ref/netcore50/zh-hans/Microsoft.CSharp.xml",
|
||||
"ref/netcore50/zh-hant/Microsoft.CSharp.xml",
|
||||
"ref/netcoreapp2.0/_._",
|
||||
"ref/netstandard1.0/Microsoft.CSharp.dll",
|
||||
"ref/netstandard1.0/Microsoft.CSharp.xml",
|
||||
"ref/netstandard1.0/de/Microsoft.CSharp.xml",
|
||||
"ref/netstandard1.0/es/Microsoft.CSharp.xml",
|
||||
"ref/netstandard1.0/fr/Microsoft.CSharp.xml",
|
||||
"ref/netstandard1.0/it/Microsoft.CSharp.xml",
|
||||
"ref/netstandard1.0/ja/Microsoft.CSharp.xml",
|
||||
"ref/netstandard1.0/ko/Microsoft.CSharp.xml",
|
||||
"ref/netstandard1.0/ru/Microsoft.CSharp.xml",
|
||||
"ref/netstandard1.0/zh-hans/Microsoft.CSharp.xml",
|
||||
"ref/netstandard1.0/zh-hant/Microsoft.CSharp.xml",
|
||||
"ref/netstandard2.0/Microsoft.CSharp.dll",
|
||||
"ref/netstandard2.0/Microsoft.CSharp.xml",
|
||||
"ref/portable-net45+win8+wp8+wpa81/_._",
|
||||
"ref/uap10.0.16299/_._",
|
||||
"ref/win8/_._",
|
||||
"ref/wp80/_._",
|
||||
"ref/wpa81/_._",
|
||||
"ref/xamarinios10/_._",
|
||||
"ref/xamarinmac20/_._",
|
||||
"ref/xamarintvos10/_._",
|
||||
"ref/xamarinwatchos10/_._",
|
||||
"useSharedDesignerContext.txt",
|
||||
"version.txt"
|
||||
]
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions/8.0.0": {
|
||||
"sha512": "cjWrLkJXK0rs4zofsK4bSdg+jhDLTaxrkXu4gS6Y7MAlCvRyNNgwY/lJi5RDlQOnSZweHqoyvgvbdvQsRIW+hg==",
|
||||
"type": "package",
|
||||
"path": "microsoft.extensions.dependencyinjection.abstractions/8.0.0",
|
||||
"files": [
|
||||
".nupkg.metadata",
|
||||
".signature.p7s",
|
||||
"Icon.png",
|
||||
"LICENSE.TXT",
|
||||
"PACKAGE.md",
|
||||
"THIRD-PARTY-NOTICES.TXT",
|
||||
"buildTransitive/net461/Microsoft.Extensions.DependencyInjection.Abstractions.targets",
|
||||
"buildTransitive/net462/_._",
|
||||
"buildTransitive/net6.0/_._",
|
||||
"buildTransitive/netcoreapp2.0/Microsoft.Extensions.DependencyInjection.Abstractions.targets",
|
||||
"lib/net462/Microsoft.Extensions.DependencyInjection.Abstractions.dll",
|
||||
"lib/net462/Microsoft.Extensions.DependencyInjection.Abstractions.xml",
|
||||
"lib/net6.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll",
|
||||
"lib/net6.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml",
|
||||
"lib/net7.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll",
|
||||
"lib/net7.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml",
|
||||
"lib/net8.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll",
|
||||
"lib/net8.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml",
|
||||
"lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll",
|
||||
"lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.Abstractions.xml",
|
||||
"lib/netstandard2.1/Microsoft.Extensions.DependencyInjection.Abstractions.dll",
|
||||
"lib/netstandard2.1/Microsoft.Extensions.DependencyInjection.Abstractions.xml",
|
||||
"microsoft.extensions.dependencyinjection.abstractions.8.0.0.nupkg.sha512",
|
||||
"microsoft.extensions.dependencyinjection.abstractions.nuspec",
|
||||
"useSharedDesignerContext.txt"
|
||||
]
|
||||
},
|
||||
"StyleCop.Analyzers/1.2.0-beta.556": {
|
||||
"sha512": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==",
|
||||
"type": "package",
|
||||
"path": "stylecop.analyzers/1.2.0-beta.556",
|
||||
"files": [
|
||||
".nupkg.metadata",
|
||||
".signature.p7s",
|
||||
"LICENSE",
|
||||
"THIRD-PARTY-NOTICES.txt",
|
||||
"stylecop.analyzers.1.2.0-beta.556.nupkg.sha512",
|
||||
"stylecop.analyzers.nuspec"
|
||||
]
|
||||
},
|
||||
"StyleCop.Analyzers.Unstable/1.2.0.556": {
|
||||
"sha512": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==",
|
||||
"type": "package",
|
||||
"path": "stylecop.analyzers.unstable/1.2.0.556",
|
||||
"hasTools": true,
|
||||
"files": [
|
||||
".nupkg.metadata",
|
||||
".signature.p7s",
|
||||
"LICENSE",
|
||||
"THIRD-PARTY-NOTICES.txt",
|
||||
"analyzers/dotnet/cs/StyleCop.Analyzers.CodeFixes.dll",
|
||||
"analyzers/dotnet/cs/StyleCop.Analyzers.dll",
|
||||
"analyzers/dotnet/cs/de-DE/StyleCop.Analyzers.resources.dll",
|
||||
"analyzers/dotnet/cs/en-GB/StyleCop.Analyzers.resources.dll",
|
||||
"analyzers/dotnet/cs/es-MX/StyleCop.Analyzers.resources.dll",
|
||||
"analyzers/dotnet/cs/fr-FR/StyleCop.Analyzers.resources.dll",
|
||||
"analyzers/dotnet/cs/pl-PL/StyleCop.Analyzers.resources.dll",
|
||||
"analyzers/dotnet/cs/pt-BR/StyleCop.Analyzers.resources.dll",
|
||||
"analyzers/dotnet/cs/ru-RU/StyleCop.Analyzers.resources.dll",
|
||||
"rulesets/StyleCopAnalyzersDefault.ruleset",
|
||||
"stylecop.analyzers.unstable.1.2.0.556.nupkg.sha512",
|
||||
"stylecop.analyzers.unstable.nuspec",
|
||||
"tools/install.ps1",
|
||||
"tools/uninstall.ps1"
|
||||
]
|
||||
},
|
||||
"InvoiceMaster.Core/1.0.0": {
|
||||
"type": "project",
|
||||
"path": "../InvoiceMaster.Core/InvoiceMaster.Core.csproj",
|
||||
"msbuildProject": "../InvoiceMaster.Core/InvoiceMaster.Core.csproj"
|
||||
}
|
||||
},
|
||||
"projectFileDependencyGroups": {
|
||||
"net8.0": [
|
||||
"AutoMapper >= 12.0.1",
|
||||
"FluentValidation >= 11.8.1",
|
||||
"InvoiceMaster.Core >= 1.0.0",
|
||||
"MediatR >= 12.2.0",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions >= 8.0.0",
|
||||
"StyleCop.Analyzers >= 1.2.0-beta.556"
|
||||
]
|
||||
},
|
||||
"packageFolders": {
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\": {},
|
||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages": {}
|
||||
},
|
||||
"project": {
|
||||
"version": "1.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Application\\InvoiceMaster.Application.csproj",
|
||||
"projectName": "InvoiceMaster.Application",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Application\\InvoiceMaster.Application.csproj",
|
||||
"packagesPath": "C:\\Users\\yaoji\\.nuget\\packages\\",
|
||||
"outputPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Application\\obj\\",
|
||||
"projectStyle": "PackageReference",
|
||||
"fallbackFolders": [
|
||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
||||
],
|
||||
"configFilePaths": [
|
||||
"C:\\Users\\yaoji\\AppData\\Roaming\\NuGet\\NuGet.Config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
|
||||
],
|
||||
"originalTargetFrameworks": [
|
||||
"net8.0"
|
||||
],
|
||||
"sources": {
|
||||
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
|
||||
"https://api.nuget.org/v3/index.json": {},
|
||||
"https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"net8.0": {
|
||||
"targetAlias": "net8.0",
|
||||
"projectReferences": {
|
||||
"C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\InvoiceMaster.Core.csproj": {
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\InvoiceMaster.Core.csproj"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"warningProperties": {
|
||||
"allWarningsAsErrors": true,
|
||||
"warnAsError": [
|
||||
"NU1605"
|
||||
]
|
||||
},
|
||||
"restoreAuditProperties": {
|
||||
"enableAudit": "true",
|
||||
"auditLevel": "low",
|
||||
"auditMode": "direct"
|
||||
},
|
||||
"SdkAnalysisLevel": "10.0.100"
|
||||
},
|
||||
"frameworks": {
|
||||
"net8.0": {
|
||||
"targetAlias": "net8.0",
|
||||
"dependencies": {
|
||||
"AutoMapper": {
|
||||
"target": "Package",
|
||||
"version": "[12.0.1, )"
|
||||
},
|
||||
"FluentValidation": {
|
||||
"target": "Package",
|
||||
"version": "[11.8.1, )"
|
||||
},
|
||||
"MediatR": {
|
||||
"target": "Package",
|
||||
"version": "[12.2.0, )"
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": {
|
||||
"target": "Package",
|
||||
"version": "[8.0.0, )"
|
||||
},
|
||||
"StyleCop.Analyzers": {
|
||||
"include": "Runtime, Build, Native, ContentFiles, Analyzers",
|
||||
"suppressParent": "All",
|
||||
"target": "Package",
|
||||
"version": "[1.2.0-beta.556, )"
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"net461",
|
||||
"net462",
|
||||
"net47",
|
||||
"net471",
|
||||
"net472",
|
||||
"net48",
|
||||
"net481"
|
||||
],
|
||||
"assetTargetFallback": true,
|
||||
"warn": true,
|
||||
"frameworkReferences": {
|
||||
"Microsoft.NETCore.App": {
|
||||
"privateAssets": "all"
|
||||
}
|
||||
},
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.102/PortableRuntimeIdentifierGraph.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"logs": [
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized)."
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized)."
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized)."
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized)."
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized)."
|
||||
},
|
||||
{
|
||||
"code": "NU1900",
|
||||
"level": "Error",
|
||||
"message": "Warning As Error: Error occurred while getting package vulnerability data: Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "azpiw38zbcw=",
|
||||
"success": false,
|
||||
"projectFilePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Application\\InvoiceMaster.Application.csproj",
|
||||
"expectedPackageFiles": [
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\automapper\\12.0.1\\automapper.12.0.1.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\fluentvalidation\\11.8.1\\fluentvalidation.11.8.1.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\mediatr\\12.2.0\\mediatr.12.2.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\mediatr.contracts\\2.0.1\\mediatr.contracts.2.0.1.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.csharp\\4.7.0\\microsoft.csharp.4.7.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.dependencyinjection.abstractions\\8.0.0\\microsoft.extensions.dependencyinjection.abstractions.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\stylecop.analyzers\\1.2.0-beta.556\\stylecop.analyzers.1.2.0-beta.556.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\stylecop.analyzers.unstable\\1.2.0.556\\stylecop.analyzers.unstable.1.2.0.556.nupkg.sha512"
|
||||
],
|
||||
"logs": [
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Application\\InvoiceMaster.Application.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Application\\InvoiceMaster.Application.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Application\\InvoiceMaster.Application.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Application\\InvoiceMaster.Application.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Application\\InvoiceMaster.Application.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Application\\InvoiceMaster.Application.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Application\\InvoiceMaster.Application.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Application\\InvoiceMaster.Application.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Application\\InvoiceMaster.Application.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Application\\InvoiceMaster.Application.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1900",
|
||||
"level": "Error",
|
||||
"message": "Warning As Error: Error occurred while getting package vulnerability data: Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Application\\InvoiceMaster.Application.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Application\\InvoiceMaster.Application.csproj",
|
||||
"targetGraphs": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
namespace InvoiceMaster.Core.Entities;
|
||||
|
||||
public class AccountingConnection : BaseEntity
|
||||
{
|
||||
public Guid UserId { get; private set; }
|
||||
public User User { get; private set; } = null!;
|
||||
|
||||
public string Provider { get; private set; } = string.Empty;
|
||||
|
||||
public string AccessTokenEncrypted { get; private set; } = string.Empty;
|
||||
public string RefreshTokenEncrypted { get; private set; } = string.Empty;
|
||||
public DateTime ExpiresAt { get; private set; }
|
||||
public string? Scope { get; private set; }
|
||||
|
||||
public string? CompanyName { get; private set; }
|
||||
public string? CompanyOrgNumber { get; private set; }
|
||||
|
||||
public string DefaultVoucherSeries { get; private set; } = "A";
|
||||
public int DefaultAccountCode { get; private set; } = 5460;
|
||||
public bool AutoAttachPdf { get; private set; } = true;
|
||||
public bool AutoCreateSupplier { get; private set; } = false;
|
||||
|
||||
public bool IsActive { get; private set; } = true;
|
||||
public DateTime? LastSyncAt { get; private set; }
|
||||
|
||||
private readonly List<Invoice> _invoices = [];
|
||||
public IReadOnlyCollection<Invoice> Invoices => _invoices.AsReadOnly();
|
||||
|
||||
public static AccountingConnection Create(
|
||||
Guid userId,
|
||||
string provider,
|
||||
string accessTokenEncrypted,
|
||||
string refreshTokenEncrypted,
|
||||
DateTime expiresAt,
|
||||
string? scope = null,
|
||||
string? companyName = null,
|
||||
string? companyOrgNumber = null)
|
||||
{
|
||||
return new AccountingConnection
|
||||
{
|
||||
UserId = userId,
|
||||
Provider = provider,
|
||||
AccessTokenEncrypted = accessTokenEncrypted,
|
||||
RefreshTokenEncrypted = refreshTokenEncrypted,
|
||||
ExpiresAt = expiresAt,
|
||||
Scope = scope,
|
||||
CompanyName = companyName,
|
||||
CompanyOrgNumber = companyOrgNumber
|
||||
};
|
||||
}
|
||||
|
||||
public void UpdateTokens(string accessTokenEncrypted, string refreshTokenEncrypted, DateTime expiresAt)
|
||||
{
|
||||
AccessTokenEncrypted = accessTokenEncrypted;
|
||||
RefreshTokenEncrypted = refreshTokenEncrypted;
|
||||
ExpiresAt = expiresAt;
|
||||
}
|
||||
|
||||
public void UpdateSettings(
|
||||
string? defaultVoucherSeries = null,
|
||||
int? defaultAccountCode = null,
|
||||
bool? autoAttachPdf = null,
|
||||
bool? autoCreateSupplier = null)
|
||||
{
|
||||
if (defaultVoucherSeries != null) DefaultVoucherSeries = defaultVoucherSeries;
|
||||
if (defaultAccountCode.HasValue) DefaultAccountCode = defaultAccountCode.Value;
|
||||
if (autoAttachPdf.HasValue) AutoAttachPdf = autoAttachPdf.Value;
|
||||
if (autoCreateSupplier.HasValue) AutoCreateSupplier = autoCreateSupplier.Value;
|
||||
}
|
||||
|
||||
public void RecordSync()
|
||||
{
|
||||
LastSyncAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public void Deactivate()
|
||||
{
|
||||
IsActive = false;
|
||||
}
|
||||
}
|
||||
13
backend/src/InvoiceMaster.Core/Entities/BaseEntity.cs
Normal file
13
backend/src/InvoiceMaster.Core/Entities/BaseEntity.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace InvoiceMaster.Core.Entities;
|
||||
|
||||
public abstract class BaseEntity
|
||||
{
|
||||
public Guid Id { get; protected set; } = Guid.NewGuid();
|
||||
public DateTime CreatedAt { get; protected set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; protected set; } = DateTime.UtcNow;
|
||||
|
||||
public void UpdateTimestamp()
|
||||
{
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
163
backend/src/InvoiceMaster.Core/Entities/Invoice.cs
Normal file
163
backend/src/InvoiceMaster.Core/Entities/Invoice.cs
Normal file
@@ -0,0 +1,163 @@
|
||||
namespace InvoiceMaster.Core.Entities;
|
||||
|
||||
public enum InvoiceStatus
|
||||
{
|
||||
Pending,
|
||||
Uploading,
|
||||
Processing,
|
||||
Preview,
|
||||
Importing,
|
||||
Imported,
|
||||
Failed
|
||||
}
|
||||
|
||||
public enum SupplierMatchAction
|
||||
{
|
||||
UseExisting,
|
||||
CreateNew,
|
||||
SuggestMatch
|
||||
}
|
||||
|
||||
public class Invoice : BaseEntity
|
||||
{
|
||||
public Guid ConnectionId { get; private set; }
|
||||
public AccountingConnection Connection { get; private set; } = null!;
|
||||
public string Provider { get; private set; } = string.Empty;
|
||||
|
||||
public string OriginalFilename { get; private set; } = string.Empty;
|
||||
public string StoragePath { get; private set; } = string.Empty;
|
||||
public long? FileSize { get; private set; }
|
||||
public string? FileHash { get; private set; }
|
||||
|
||||
public string? ExtractionData { get; private set; }
|
||||
public decimal? ExtractionConfidence { get; private set; }
|
||||
|
||||
public string? ExtractedSupplierName { get; private set; }
|
||||
public string? ExtractedSupplierOrgNumber { get; private set; }
|
||||
public string? ExtractedInvoiceNumber { get; private set; }
|
||||
public DateTime? ExtractedInvoiceDate { get; private set; }
|
||||
public DateTime? ExtractedDueDate { get; private set; }
|
||||
public decimal? ExtractedAmountTotal { get; private set; }
|
||||
public decimal? ExtractedAmountVat { get; private set; }
|
||||
public int? ExtractedVatRate { get; private set; }
|
||||
public string? ExtractedOcrNumber { get; private set; }
|
||||
public string? ExtractedBankgiro { get; private set; }
|
||||
public string? ExtractedPlusgiro { get; private set; }
|
||||
public string ExtractedCurrency { get; private set; } = "SEK";
|
||||
|
||||
public string? SupplierNumber { get; private set; }
|
||||
public decimal? SupplierMatchConfidence { get; private set; }
|
||||
public SupplierMatchAction? SupplierMatchAction { get; private set; }
|
||||
|
||||
public string? VoucherSeries { get; private set; }
|
||||
public string? VoucherNumber { get; private set; }
|
||||
public string? VoucherUrl { get; private set; }
|
||||
public string? VoucherRows { get; private set; }
|
||||
|
||||
public InvoiceStatus Status { get; private set; } = InvoiceStatus.Pending;
|
||||
public string? ErrorMessage { get; private set; }
|
||||
public string? ErrorCode { get; private set; }
|
||||
|
||||
public Guid? ReviewedBy { get; private set; }
|
||||
public DateTime? ReviewedAt { get; private set; }
|
||||
|
||||
public string? AttachmentId { get; private set; }
|
||||
public string? AttachmentUrl { get; private set; }
|
||||
public DateTime? ProcessedAt { get; private set; }
|
||||
|
||||
public static Invoice Create(
|
||||
Guid connectionId,
|
||||
string provider,
|
||||
string originalFilename,
|
||||
string storagePath,
|
||||
long? fileSize = null,
|
||||
string? fileHash = null)
|
||||
{
|
||||
return new Invoice
|
||||
{
|
||||
ConnectionId = connectionId,
|
||||
Provider = provider,
|
||||
OriginalFilename = originalFilename,
|
||||
StoragePath = storagePath,
|
||||
FileSize = fileSize,
|
||||
FileHash = fileHash,
|
||||
Status = InvoiceStatus.Uploading
|
||||
};
|
||||
}
|
||||
|
||||
public void SetExtractionData(
|
||||
string extractionData,
|
||||
decimal confidence,
|
||||
string? supplierName,
|
||||
string? supplierOrgNumber,
|
||||
string? invoiceNumber,
|
||||
DateTime? invoiceDate,
|
||||
DateTime? dueDate,
|
||||
decimal? amountTotal,
|
||||
decimal? amountVat,
|
||||
int? vatRate,
|
||||
string? ocrNumber,
|
||||
string? bankgiro,
|
||||
string? plusgiro,
|
||||
string currency = "SEK")
|
||||
{
|
||||
ExtractionData = extractionData;
|
||||
ExtractionConfidence = confidence;
|
||||
ExtractedSupplierName = supplierName;
|
||||
ExtractedSupplierOrgNumber = supplierOrgNumber;
|
||||
ExtractedInvoiceNumber = invoiceNumber;
|
||||
ExtractedInvoiceDate = invoiceDate;
|
||||
ExtractedDueDate = dueDate;
|
||||
ExtractedAmountTotal = amountTotal;
|
||||
ExtractedAmountVat = amountVat;
|
||||
ExtractedVatRate = vatRate;
|
||||
ExtractedOcrNumber = ocrNumber;
|
||||
ExtractedBankgiro = bankgiro;
|
||||
ExtractedPlusgiro = plusgiro;
|
||||
ExtractedCurrency = currency;
|
||||
Status = InvoiceStatus.Preview;
|
||||
}
|
||||
|
||||
public void SetSupplierMatch(string supplierNumber, decimal confidence, SupplierMatchAction action)
|
||||
{
|
||||
SupplierNumber = supplierNumber;
|
||||
SupplierMatchConfidence = confidence;
|
||||
SupplierMatchAction = action;
|
||||
}
|
||||
|
||||
public void SetVoucher(string series, string number, string url, string rows)
|
||||
{
|
||||
VoucherSeries = series;
|
||||
VoucherNumber = number;
|
||||
VoucherUrl = url;
|
||||
VoucherRows = rows;
|
||||
}
|
||||
|
||||
public void SetStatus(InvoiceStatus status)
|
||||
{
|
||||
Status = status;
|
||||
if (status == InvoiceStatus.Imported)
|
||||
{
|
||||
ProcessedAt = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetError(string errorCode, string errorMessage)
|
||||
{
|
||||
ErrorCode = errorCode;
|
||||
ErrorMessage = errorMessage;
|
||||
Status = InvoiceStatus.Failed;
|
||||
}
|
||||
|
||||
public void SetReviewed(Guid reviewedBy)
|
||||
{
|
||||
ReviewedBy = reviewedBy;
|
||||
ReviewedAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public void SetAttachment(string attachmentId, string attachmentUrl)
|
||||
{
|
||||
AttachmentId = attachmentId;
|
||||
AttachmentUrl = attachmentUrl;
|
||||
}
|
||||
}
|
||||
76
backend/src/InvoiceMaster.Core/Entities/SupplierCache.cs
Normal file
76
backend/src/InvoiceMaster.Core/Entities/SupplierCache.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
namespace InvoiceMaster.Core.Entities;
|
||||
|
||||
public class SupplierCache : BaseEntity
|
||||
{
|
||||
public Guid ConnectionId { get; private set; }
|
||||
public AccountingConnection Connection { get; private set; } = null!;
|
||||
|
||||
public string SupplierNumber { get; private set; } = string.Empty;
|
||||
public string Name { get; private set; } = string.Empty;
|
||||
public string? OrganisationNumber { get; private set; }
|
||||
public string? Address1 { get; private set; }
|
||||
public string? Address2 { get; private set; }
|
||||
public string? Postcode { get; private set; }
|
||||
public string? City { get; private set; }
|
||||
public string? Country { get; private set; }
|
||||
public string? Phone { get; private set; }
|
||||
public string? Email { get; private set; }
|
||||
public string? BankgiroNumber { get; private set; }
|
||||
public string? PlusgiroNumber { get; private set; }
|
||||
|
||||
public DateTime CachedAt { get; private set; } = DateTime.UtcNow;
|
||||
public DateTime ExpiresAt { get; private set; } = DateTime.UtcNow.AddHours(1);
|
||||
|
||||
public static SupplierCache Create(
|
||||
Guid connectionId,
|
||||
string supplierNumber,
|
||||
string name,
|
||||
string? organisationNumber = null,
|
||||
string? address1 = null,
|
||||
string? postcode = null,
|
||||
string? city = null)
|
||||
{
|
||||
return new SupplierCache
|
||||
{
|
||||
ConnectionId = connectionId,
|
||||
SupplierNumber = supplierNumber,
|
||||
Name = name,
|
||||
OrganisationNumber = organisationNumber,
|
||||
Address1 = address1,
|
||||
Postcode = postcode,
|
||||
City = city,
|
||||
CachedAt = DateTime.UtcNow,
|
||||
ExpiresAt = DateTime.UtcNow.AddHours(1)
|
||||
};
|
||||
}
|
||||
|
||||
public void UpdateDetails(
|
||||
string name,
|
||||
string? organisationNumber = null,
|
||||
string? address1 = null,
|
||||
string? address2 = null,
|
||||
string? postcode = null,
|
||||
string? city = null,
|
||||
string? country = null,
|
||||
string? phone = null,
|
||||
string? email = null,
|
||||
string? bankgiroNumber = null,
|
||||
string? plusgiroNumber = null)
|
||||
{
|
||||
Name = name;
|
||||
OrganisationNumber = organisationNumber;
|
||||
Address1 = address1;
|
||||
Address2 = address2;
|
||||
Postcode = postcode;
|
||||
City = city;
|
||||
Country = country;
|
||||
Phone = phone;
|
||||
Email = email;
|
||||
BankgiroNumber = bankgiroNumber;
|
||||
PlusgiroNumber = plusgiroNumber;
|
||||
CachedAt = DateTime.UtcNow;
|
||||
ExpiresAt = DateTime.UtcNow.AddHours(1);
|
||||
}
|
||||
|
||||
public bool IsExpired() => DateTime.UtcNow > ExpiresAt;
|
||||
}
|
||||
29
backend/src/InvoiceMaster.Core/Entities/User.cs
Normal file
29
backend/src/InvoiceMaster.Core/Entities/User.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
namespace InvoiceMaster.Core.Entities;
|
||||
|
||||
public class User : BaseEntity
|
||||
{
|
||||
public required string Email { get; set; }
|
||||
public required string HashedPassword { get; set; }
|
||||
public string? FullName { get; set; }
|
||||
public bool IsActive { get; set; } = true;
|
||||
public bool IsSuperuser { get; set; } = false;
|
||||
public DateTime? LastLoginAt { get; set; }
|
||||
|
||||
private readonly List<AccountingConnection> _connections = [];
|
||||
public IReadOnlyCollection<AccountingConnection> Connections => _connections.AsReadOnly();
|
||||
|
||||
public void AddConnection(AccountingConnection connection)
|
||||
{
|
||||
_connections.Add(connection);
|
||||
}
|
||||
|
||||
public void RemoveConnection(AccountingConnection connection)
|
||||
{
|
||||
_connections.Remove(connection);
|
||||
}
|
||||
|
||||
public void RecordLogin()
|
||||
{
|
||||
LastLoginAt = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace InvoiceMaster.Core.Interfaces;
|
||||
|
||||
public interface IBlobStorageService
|
||||
{
|
||||
Task<string> UploadAsync(string fileName, Stream content, string contentType, CancellationToken cancellationToken = default);
|
||||
Task<Stream> DownloadAsync(string blobName, CancellationToken cancellationToken = default);
|
||||
Task DeleteAsync(string blobName, CancellationToken cancellationToken = default);
|
||||
Task<bool> ExistsAsync(string blobName, CancellationToken cancellationToken = default);
|
||||
string GetBlobUrl(string blobName);
|
||||
}
|
||||
30
backend/src/InvoiceMaster.Core/Interfaces/IOcrService.cs
Normal file
30
backend/src/InvoiceMaster.Core/Interfaces/IOcrService.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
namespace InvoiceMaster.Core.Interfaces;
|
||||
|
||||
public interface IOcrService
|
||||
{
|
||||
Task<OcrResult> ExtractAsync(Stream fileStream, string fileName, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public class OcrResult
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public string? ErrorMessage { get; set; }
|
||||
public InvoiceData? Data { get; set; }
|
||||
public decimal Confidence { get; set; }
|
||||
}
|
||||
|
||||
public class InvoiceData
|
||||
{
|
||||
public string? SupplierName { get; set; }
|
||||
public string? SupplierOrgNumber { get; set; }
|
||||
public string? InvoiceNumber { get; set; }
|
||||
public DateTime? InvoiceDate { get; set; }
|
||||
public DateTime? DueDate { get; set; }
|
||||
public decimal? AmountTotal { get; set; }
|
||||
public decimal? AmountVat { get; set; }
|
||||
public int? VatRate { get; set; }
|
||||
public string? OcrNumber { get; set; }
|
||||
public string? Bankgiro { get; set; }
|
||||
public string? Plusgiro { get; set; }
|
||||
public string Currency { get; set; } = "SEK";
|
||||
}
|
||||
10
backend/src/InvoiceMaster.Core/Interfaces/IRepository.cs
Normal file
10
backend/src/InvoiceMaster.Core/Interfaces/IRepository.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace InvoiceMaster.Core.Interfaces;
|
||||
|
||||
public interface IRepository<T> where T : class
|
||||
{
|
||||
Task<T?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default);
|
||||
Task<IReadOnlyList<T>> GetAllAsync(CancellationToken cancellationToken = default);
|
||||
Task<T> AddAsync(T entity, CancellationToken cancellationToken = default);
|
||||
Task UpdateAsync(T entity, CancellationToken cancellationToken = default);
|
||||
Task DeleteAsync(T entity, CancellationToken cancellationToken = default);
|
||||
}
|
||||
6
backend/src/InvoiceMaster.Core/Interfaces/IUnitOfWork.cs
Normal file
6
backend/src/InvoiceMaster.Core/Interfaces/IUnitOfWork.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace InvoiceMaster.Core.Interfaces;
|
||||
|
||||
public interface IUnitOfWork : IDisposable
|
||||
{
|
||||
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
8
backend/src/InvoiceMaster.Core/InvoiceMaster.Core.csproj
Normal file
8
backend/src/InvoiceMaster.Core/InvoiceMaster.Core.csproj
Normal file
@@ -0,0 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<AssemblyName>InvoiceMaster.Core</AssemblyName>
|
||||
<RootNamespace>InvoiceMaster.Core</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,4 @@
|
||||
// <autogenerated />
|
||||
using System;
|
||||
using System.Reflection;
|
||||
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")]
|
||||
@@ -0,0 +1,22 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("InvoiceMaster.Core")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("InvoiceMaster.Core")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("InvoiceMaster.Core")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
// Generated by the MSBuild WriteCodeFragment class.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
8c36b0fbd0cac0263b4d3fa3916563810323f0ab58fe8276eecae48c4cffb1b4
|
||||
@@ -0,0 +1,17 @@
|
||||
is_global = true
|
||||
build_property.TargetFramework = net8.0
|
||||
build_property.TargetFrameworkIdentifier = .NETCoreApp
|
||||
build_property.TargetFrameworkVersion = v8.0
|
||||
build_property.TargetPlatformMinVersion =
|
||||
build_property.UsingMicrosoftNETSdkWeb =
|
||||
build_property.ProjectTypeGuids =
|
||||
build_property.InvariantGlobalization =
|
||||
build_property.PlatformNeutralAssembly =
|
||||
build_property.EnforceExtendedAnalyzerRules =
|
||||
build_property._SupportedPlatformList = Linux,macOS,Windows
|
||||
build_property.RootNamespace = InvoiceMaster.Core
|
||||
build_property.ProjectDir = C:\Users\yaoji\git\ColaCoder\accounting-system\backend\src\InvoiceMaster.Core\
|
||||
build_property.EnableComHosting =
|
||||
build_property.EnableGeneratedComInterfaceComImportInterop =
|
||||
build_property.EffectiveAnalysisLevelStyle = 8.0
|
||||
build_property.EnableCodeStyleSeverity =
|
||||
@@ -0,0 +1,8 @@
|
||||
// <auto-generated/>
|
||||
global using System;
|
||||
global using System.Collections.Generic;
|
||||
global using System.IO;
|
||||
global using System.Linq;
|
||||
global using System.Net.Http;
|
||||
global using System.Threading;
|
||||
global using System.Threading.Tasks;
|
||||
Binary file not shown.
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"format": 1,
|
||||
"restore": {
|
||||
"C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\InvoiceMaster.Core.csproj": {}
|
||||
},
|
||||
"projects": {
|
||||
"C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\InvoiceMaster.Core.csproj": {
|
||||
"version": "1.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\InvoiceMaster.Core.csproj",
|
||||
"projectName": "InvoiceMaster.Core",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\InvoiceMaster.Core.csproj",
|
||||
"packagesPath": "C:\\Users\\yaoji\\.nuget\\packages\\",
|
||||
"outputPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\obj\\",
|
||||
"projectStyle": "PackageReference",
|
||||
"fallbackFolders": [
|
||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
||||
],
|
||||
"configFilePaths": [
|
||||
"C:\\Users\\yaoji\\AppData\\Roaming\\NuGet\\NuGet.Config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
|
||||
],
|
||||
"originalTargetFrameworks": [
|
||||
"net8.0"
|
||||
],
|
||||
"sources": {
|
||||
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
|
||||
"https://api.nuget.org/v3/index.json": {},
|
||||
"https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"net8.0": {
|
||||
"targetAlias": "net8.0",
|
||||
"projectReferences": {}
|
||||
}
|
||||
},
|
||||
"warningProperties": {
|
||||
"allWarningsAsErrors": true,
|
||||
"warnAsError": [
|
||||
"NU1605"
|
||||
]
|
||||
},
|
||||
"restoreAuditProperties": {
|
||||
"enableAudit": "true",
|
||||
"auditLevel": "low",
|
||||
"auditMode": "direct"
|
||||
},
|
||||
"SdkAnalysisLevel": "10.0.100"
|
||||
},
|
||||
"frameworks": {
|
||||
"net8.0": {
|
||||
"targetAlias": "net8.0",
|
||||
"dependencies": {
|
||||
"StyleCop.Analyzers": {
|
||||
"include": "Runtime, Build, Native, ContentFiles, Analyzers",
|
||||
"suppressParent": "All",
|
||||
"target": "Package",
|
||||
"version": "[1.2.0-beta.556, )"
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"net461",
|
||||
"net462",
|
||||
"net47",
|
||||
"net471",
|
||||
"net472",
|
||||
"net48",
|
||||
"net481"
|
||||
],
|
||||
"assetTargetFallback": true,
|
||||
"warn": true,
|
||||
"frameworkReferences": {
|
||||
"Microsoft.NETCore.App": {
|
||||
"privateAssets": "all"
|
||||
}
|
||||
},
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.102/PortableRuntimeIdentifierGraph.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
|
||||
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
|
||||
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
|
||||
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
|
||||
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\yaoji\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages</NuGetPackageFolders>
|
||||
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
|
||||
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">7.0.0</NuGetToolVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<SourceRoot Include="C:\Users\yaoji\.nuget\packages\" />
|
||||
<SourceRoot Include="C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages\" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<PkgStyleCop_Analyzers_Unstable Condition=" '$(PkgStyleCop_Analyzers_Unstable)' == '' ">C:\Users\yaoji\.nuget\packages\stylecop.analyzers.unstable\1.2.0.556</PkgStyleCop_Analyzers_Unstable>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" />
|
||||
168
backend/src/InvoiceMaster.Core/obj/project.assets.json
Normal file
168
backend/src/InvoiceMaster.Core/obj/project.assets.json
Normal file
@@ -0,0 +1,168 @@
|
||||
{
|
||||
"version": 3,
|
||||
"targets": {
|
||||
"net8.0": {
|
||||
"StyleCop.Analyzers/1.2.0-beta.556": {
|
||||
"type": "package",
|
||||
"dependencies": {
|
||||
"StyleCop.Analyzers.Unstable": "1.2.0.556"
|
||||
}
|
||||
},
|
||||
"StyleCop.Analyzers.Unstable/1.2.0.556": {
|
||||
"type": "package"
|
||||
}
|
||||
}
|
||||
},
|
||||
"libraries": {
|
||||
"StyleCop.Analyzers/1.2.0-beta.556": {
|
||||
"sha512": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==",
|
||||
"type": "package",
|
||||
"path": "stylecop.analyzers/1.2.0-beta.556",
|
||||
"files": [
|
||||
".nupkg.metadata",
|
||||
".signature.p7s",
|
||||
"LICENSE",
|
||||
"THIRD-PARTY-NOTICES.txt",
|
||||
"stylecop.analyzers.1.2.0-beta.556.nupkg.sha512",
|
||||
"stylecop.analyzers.nuspec"
|
||||
]
|
||||
},
|
||||
"StyleCop.Analyzers.Unstable/1.2.0.556": {
|
||||
"sha512": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==",
|
||||
"type": "package",
|
||||
"path": "stylecop.analyzers.unstable/1.2.0.556",
|
||||
"hasTools": true,
|
||||
"files": [
|
||||
".nupkg.metadata",
|
||||
".signature.p7s",
|
||||
"LICENSE",
|
||||
"THIRD-PARTY-NOTICES.txt",
|
||||
"analyzers/dotnet/cs/StyleCop.Analyzers.CodeFixes.dll",
|
||||
"analyzers/dotnet/cs/StyleCop.Analyzers.dll",
|
||||
"analyzers/dotnet/cs/de-DE/StyleCop.Analyzers.resources.dll",
|
||||
"analyzers/dotnet/cs/en-GB/StyleCop.Analyzers.resources.dll",
|
||||
"analyzers/dotnet/cs/es-MX/StyleCop.Analyzers.resources.dll",
|
||||
"analyzers/dotnet/cs/fr-FR/StyleCop.Analyzers.resources.dll",
|
||||
"analyzers/dotnet/cs/pl-PL/StyleCop.Analyzers.resources.dll",
|
||||
"analyzers/dotnet/cs/pt-BR/StyleCop.Analyzers.resources.dll",
|
||||
"analyzers/dotnet/cs/ru-RU/StyleCop.Analyzers.resources.dll",
|
||||
"rulesets/StyleCopAnalyzersDefault.ruleset",
|
||||
"stylecop.analyzers.unstable.1.2.0.556.nupkg.sha512",
|
||||
"stylecop.analyzers.unstable.nuspec",
|
||||
"tools/install.ps1",
|
||||
"tools/uninstall.ps1"
|
||||
]
|
||||
}
|
||||
},
|
||||
"projectFileDependencyGroups": {
|
||||
"net8.0": [
|
||||
"StyleCop.Analyzers >= 1.2.0-beta.556"
|
||||
]
|
||||
},
|
||||
"packageFolders": {
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\": {},
|
||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages": {}
|
||||
},
|
||||
"project": {
|
||||
"version": "1.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\InvoiceMaster.Core.csproj",
|
||||
"projectName": "InvoiceMaster.Core",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\InvoiceMaster.Core.csproj",
|
||||
"packagesPath": "C:\\Users\\yaoji\\.nuget\\packages\\",
|
||||
"outputPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\obj\\",
|
||||
"projectStyle": "PackageReference",
|
||||
"fallbackFolders": [
|
||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
||||
],
|
||||
"configFilePaths": [
|
||||
"C:\\Users\\yaoji\\AppData\\Roaming\\NuGet\\NuGet.Config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
|
||||
],
|
||||
"originalTargetFrameworks": [
|
||||
"net8.0"
|
||||
],
|
||||
"sources": {
|
||||
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
|
||||
"https://api.nuget.org/v3/index.json": {},
|
||||
"https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"net8.0": {
|
||||
"targetAlias": "net8.0",
|
||||
"projectReferences": {}
|
||||
}
|
||||
},
|
||||
"warningProperties": {
|
||||
"allWarningsAsErrors": true,
|
||||
"warnAsError": [
|
||||
"NU1605"
|
||||
]
|
||||
},
|
||||
"restoreAuditProperties": {
|
||||
"enableAudit": "true",
|
||||
"auditLevel": "low",
|
||||
"auditMode": "direct"
|
||||
},
|
||||
"SdkAnalysisLevel": "10.0.100"
|
||||
},
|
||||
"frameworks": {
|
||||
"net8.0": {
|
||||
"targetAlias": "net8.0",
|
||||
"dependencies": {
|
||||
"StyleCop.Analyzers": {
|
||||
"include": "Runtime, Build, Native, ContentFiles, Analyzers",
|
||||
"suppressParent": "All",
|
||||
"target": "Package",
|
||||
"version": "[1.2.0-beta.556, )"
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"net461",
|
||||
"net462",
|
||||
"net47",
|
||||
"net471",
|
||||
"net472",
|
||||
"net48",
|
||||
"net481"
|
||||
],
|
||||
"assetTargetFallback": true,
|
||||
"warn": true,
|
||||
"frameworkReferences": {
|
||||
"Microsoft.NETCore.App": {
|
||||
"privateAssets": "all"
|
||||
}
|
||||
},
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.102/PortableRuntimeIdentifierGraph.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"logs": [
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized)."
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized)."
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized)."
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized)."
|
||||
},
|
||||
{
|
||||
"code": "NU1900",
|
||||
"level": "Error",
|
||||
"message": "Warning As Error: Error occurred while getting package vulnerability data: Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json."
|
||||
}
|
||||
]
|
||||
}
|
||||
52
backend/src/InvoiceMaster.Core/obj/project.nuget.cache
Normal file
52
backend/src/InvoiceMaster.Core/obj/project.nuget.cache
Normal file
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "SGe3B4fOk7A=",
|
||||
"success": false,
|
||||
"projectFilePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\InvoiceMaster.Core.csproj",
|
||||
"expectedPackageFiles": [
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\stylecop.analyzers\\1.2.0-beta.556\\stylecop.analyzers.1.2.0-beta.556.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\stylecop.analyzers.unstable\\1.2.0.556\\stylecop.analyzers.unstable.1.2.0.556.nupkg.sha512"
|
||||
],
|
||||
"logs": [
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\InvoiceMaster.Core.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\InvoiceMaster.Core.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\InvoiceMaster.Core.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\InvoiceMaster.Core.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\InvoiceMaster.Core.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\InvoiceMaster.Core.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\InvoiceMaster.Core.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\InvoiceMaster.Core.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1900",
|
||||
"level": "Error",
|
||||
"message": "Warning As Error: Error occurred while getting package vulnerability data: Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\InvoiceMaster.Core.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\InvoiceMaster.Core.csproj",
|
||||
"targetGraphs": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using InvoiceMaster.Core.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace InvoiceMaster.Infrastructure.Data;
|
||||
|
||||
public class ApplicationDbContext : DbContext
|
||||
{
|
||||
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
|
||||
{
|
||||
}
|
||||
|
||||
public DbSet<User> Users => Set<User>();
|
||||
public DbSet<AccountingConnection> AccountingConnections => Set<AccountingConnection>();
|
||||
public DbSet<Invoice> Invoices => Set<Invoice>();
|
||||
public DbSet<SupplierCache> SupplierCaches => Set<SupplierCache>();
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
modelBuilder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using InvoiceMaster.Core.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace InvoiceMaster.Infrastructure.Data.Configurations;
|
||||
|
||||
public class AccountingConnectionConfiguration : IEntityTypeConfiguration<AccountingConnection>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<AccountingConnection> builder)
|
||||
{
|
||||
builder.ToTable("accounting_connections");
|
||||
|
||||
builder.HasKey(e => e.Id);
|
||||
|
||||
builder.Property(e => e.Provider)
|
||||
.IsRequired()
|
||||
.HasMaxLength(50);
|
||||
|
||||
builder.Property(e => e.AccessTokenEncrypted)
|
||||
.IsRequired();
|
||||
|
||||
builder.Property(e => e.RefreshTokenEncrypted)
|
||||
.IsRequired();
|
||||
|
||||
builder.Property(e => e.CompanyName)
|
||||
.HasMaxLength(255);
|
||||
|
||||
builder.Property(e => e.CompanyOrgNumber)
|
||||
.HasMaxLength(20);
|
||||
|
||||
builder.Property(e => e.DefaultVoucherSeries)
|
||||
.HasMaxLength(10)
|
||||
.HasDefaultValue("A");
|
||||
|
||||
builder.Property(e => e.Scope);
|
||||
|
||||
builder.HasIndex(e => new { e.UserId, e.Provider })
|
||||
.IsUnique();
|
||||
|
||||
builder.HasIndex(e => e.Provider);
|
||||
builder.HasIndex(e => e.IsActive);
|
||||
builder.HasIndex(e => e.ExpiresAt);
|
||||
|
||||
builder.HasOne(e => e.User)
|
||||
.WithMany(u => u.Connections)
|
||||
.HasForeignKey(e => e.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
using InvoiceMaster.Core.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace InvoiceMaster.Infrastructure.Data.Configurations;
|
||||
|
||||
public class InvoiceConfiguration : IEntityTypeConfiguration<Invoice>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Invoice> builder)
|
||||
{
|
||||
builder.ToTable("invoices");
|
||||
|
||||
builder.HasKey(e => e.Id);
|
||||
|
||||
builder.Property(e => e.Provider)
|
||||
.IsRequired()
|
||||
.HasMaxLength(50);
|
||||
|
||||
builder.Property(e => e.OriginalFilename)
|
||||
.IsRequired()
|
||||
.HasMaxLength(255);
|
||||
|
||||
builder.Property(e => e.StoragePath)
|
||||
.IsRequired();
|
||||
|
||||
builder.Property(e => e.FileHash)
|
||||
.HasMaxLength(64);
|
||||
|
||||
builder.Property(e => e.ExtractedSupplierName)
|
||||
.HasMaxLength(255);
|
||||
|
||||
builder.Property(e => e.ExtractedSupplierOrgNumber)
|
||||
.HasMaxLength(20);
|
||||
|
||||
builder.Property(e => e.ExtractedInvoiceNumber)
|
||||
.HasMaxLength(100);
|
||||
|
||||
builder.Property(e => e.ExtractedOcrNumber)
|
||||
.HasMaxLength(50);
|
||||
|
||||
builder.Property(e => e.ExtractedBankgiro)
|
||||
.HasMaxLength(50);
|
||||
|
||||
builder.Property(e => e.ExtractedPlusgiro)
|
||||
.HasMaxLength(50);
|
||||
|
||||
builder.Property(e => e.ExtractedCurrency)
|
||||
.HasMaxLength(3)
|
||||
.HasDefaultValue("SEK");
|
||||
|
||||
builder.Property(e => e.SupplierNumber)
|
||||
.HasMaxLength(50);
|
||||
|
||||
builder.Property(e => e.VoucherSeries)
|
||||
.HasMaxLength(10);
|
||||
|
||||
builder.Property(e => e.VoucherNumber)
|
||||
.HasMaxLength(50);
|
||||
|
||||
builder.Property(e => e.ErrorCode)
|
||||
.HasMaxLength(50);
|
||||
|
||||
builder.HasIndex(e => e.ConnectionId);
|
||||
builder.HasIndex(e => e.Provider);
|
||||
builder.HasIndex(e => e.Status);
|
||||
builder.HasIndex(e => e.CreatedAt);
|
||||
builder.HasIndex(e => e.FileHash);
|
||||
builder.HasIndex(e => e.ExtractedSupplierOrgNumber);
|
||||
|
||||
builder.HasOne(e => e.Connection)
|
||||
.WithMany(c => c.Invoices)
|
||||
.HasForeignKey(e => e.ConnectionId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using InvoiceMaster.Core.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace InvoiceMaster.Infrastructure.Data.Configurations;
|
||||
|
||||
public class SupplierCacheConfiguration : IEntityTypeConfiguration<SupplierCache>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<SupplierCache> builder)
|
||||
{
|
||||
builder.ToTable("supplier_cache");
|
||||
|
||||
builder.HasKey(e => e.Id);
|
||||
|
||||
builder.Property(e => e.SupplierNumber)
|
||||
.IsRequired()
|
||||
.HasMaxLength(50);
|
||||
|
||||
builder.Property(e => e.Name)
|
||||
.IsRequired()
|
||||
.HasMaxLength(255);
|
||||
|
||||
builder.Property(e => e.OrganisationNumber)
|
||||
.HasMaxLength(20);
|
||||
|
||||
builder.Property(e => e.Address1)
|
||||
.HasMaxLength(255);
|
||||
|
||||
builder.Property(e => e.Address2)
|
||||
.HasMaxLength(255);
|
||||
|
||||
builder.Property(e => e.Postcode)
|
||||
.HasMaxLength(20);
|
||||
|
||||
builder.Property(e => e.City)
|
||||
.HasMaxLength(100);
|
||||
|
||||
builder.Property(e => e.Country)
|
||||
.HasMaxLength(100);
|
||||
|
||||
builder.Property(e => e.Phone)
|
||||
.HasMaxLength(50);
|
||||
|
||||
builder.Property(e => e.Email)
|
||||
.HasMaxLength(255);
|
||||
|
||||
builder.Property(e => e.BankgiroNumber)
|
||||
.HasMaxLength(50);
|
||||
|
||||
builder.Property(e => e.PlusgiroNumber)
|
||||
.HasMaxLength(50);
|
||||
|
||||
builder.HasIndex(e => new { e.ConnectionId, e.SupplierNumber })
|
||||
.IsUnique();
|
||||
|
||||
builder.HasIndex(e => e.ConnectionId);
|
||||
builder.HasIndex(e => e.OrganisationNumber);
|
||||
builder.HasIndex(e => e.Name);
|
||||
builder.HasIndex(e => e.ExpiresAt);
|
||||
|
||||
builder.HasOne(e => e.Connection)
|
||||
.WithMany()
|
||||
.HasForeignKey(e => e.ConnectionId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using InvoiceMaster.Core.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace InvoiceMaster.Infrastructure.Data.Configurations;
|
||||
|
||||
public class UserConfiguration : IEntityTypeConfiguration<User>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<User> builder)
|
||||
{
|
||||
builder.ToTable("users");
|
||||
|
||||
builder.HasKey(e => e.Id);
|
||||
|
||||
builder.Property(e => e.Email)
|
||||
.IsRequired()
|
||||
.HasMaxLength(255);
|
||||
|
||||
builder.HasIndex(e => e.Email)
|
||||
.IsUnique();
|
||||
|
||||
builder.Property(e => e.HashedPassword)
|
||||
.IsRequired()
|
||||
.HasMaxLength(255);
|
||||
|
||||
builder.Property(e => e.FullName)
|
||||
.HasMaxLength(255);
|
||||
|
||||
builder.Property(e => e.IsActive)
|
||||
.HasDefaultValue(true);
|
||||
|
||||
builder.Property(e => e.IsSuperuser)
|
||||
.HasDefaultValue(false);
|
||||
|
||||
builder.HasIndex(e => e.IsActive);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using InvoiceMaster.Core.Interfaces;
|
||||
using InvoiceMaster.Infrastructure.Data;
|
||||
using InvoiceMaster.Infrastructure.Repositories;
|
||||
using InvoiceMaster.Infrastructure.Services;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace InvoiceMaster.Infrastructure.Extensions;
|
||||
|
||||
public static class DependencyInjection
|
||||
{
|
||||
public static IServiceCollection AddInfrastructureServices(
|
||||
this IServiceCollection services,
|
||||
IConfiguration configuration)
|
||||
{
|
||||
services.AddDbContext<ApplicationDbContext>(options =>
|
||||
{
|
||||
options.UseNpgsql(
|
||||
configuration.GetConnectionString("DefaultConnection"),
|
||||
b => b.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName));
|
||||
});
|
||||
|
||||
services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
|
||||
services.AddScoped<IUnitOfWork, UnitOfWork>();
|
||||
services.AddSingleton<IBlobStorageService, AzureBlobStorageService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<AssemblyName>InvoiceMaster.Infrastructure</AssemblyName>
|
||||
<RootNamespace>InvoiceMaster.Infrastructure</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.0" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.0" />
|
||||
<PackageReference Include="Azure.Storage.Blobs" Version="12.19.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||
<PackageReference Include="Polly" Version="8.2.0" />
|
||||
<PackageReference Include="Polly.Extensions.Http" Version="3.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\InvoiceMaster.Application\InvoiceMaster.Application.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,45 @@
|
||||
using InvoiceMaster.Core.Interfaces;
|
||||
using InvoiceMaster.Infrastructure.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace InvoiceMaster.Infrastructure.Repositories;
|
||||
|
||||
public class Repository<T> : IRepository<T> where T : class
|
||||
{
|
||||
protected readonly ApplicationDbContext _context;
|
||||
protected readonly DbSet<T> _dbSet;
|
||||
|
||||
public Repository(ApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
_dbSet = context.Set<T>();
|
||||
}
|
||||
|
||||
public virtual async Task<T?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _dbSet.FindAsync(new object[] { id }, cancellationToken);
|
||||
}
|
||||
|
||||
public virtual async Task<IReadOnlyList<T>> GetAllAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _dbSet.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public virtual async Task<T> AddAsync(T entity, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await _dbSet.AddAsync(entity, cancellationToken);
|
||||
return entity;
|
||||
}
|
||||
|
||||
public virtual Task UpdateAsync(T entity, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_dbSet.Update(entity);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public virtual Task DeleteAsync(T entity, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_dbSet.Remove(entity);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using InvoiceMaster.Core.Interfaces;
|
||||
using InvoiceMaster.Infrastructure.Data;
|
||||
|
||||
namespace InvoiceMaster.Infrastructure.Repositories;
|
||||
|
||||
public class UnitOfWork : IUnitOfWork
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
|
||||
public UnitOfWork(ApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_context.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
using Azure.Storage.Blobs;
|
||||
using Azure.Storage.Blobs.Models;
|
||||
using InvoiceMaster.Core.Interfaces;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace InvoiceMaster.Infrastructure.Services;
|
||||
|
||||
public class AzureBlobStorageService : IBlobStorageService
|
||||
{
|
||||
private readonly BlobContainerClient _containerClient;
|
||||
private readonly ILogger<AzureBlobStorageService> _logger;
|
||||
|
||||
public AzureBlobStorageService(IConfiguration configuration, ILogger<AzureBlobStorageService> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
var connectionString = configuration["AzureStorage:ConnectionString"];
|
||||
var containerName = configuration["AzureStorage:ContainerName"] ?? "documents";
|
||||
|
||||
if (string.IsNullOrEmpty(connectionString))
|
||||
{
|
||||
_logger.LogWarning("Azure Storage connection string not configured. Using development mode.");
|
||||
_containerClient = null!;
|
||||
}
|
||||
else
|
||||
{
|
||||
_containerClient = new BlobContainerClient(connectionString, containerName);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> UploadAsync(string fileName, Stream content, string contentType, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_containerClient == null)
|
||||
{
|
||||
var localPath = Path.Combine("uploads", fileName);
|
||||
Directory.CreateDirectory("uploads");
|
||||
using var fileStream = File.Create(localPath);
|
||||
await content.CopyToAsync(fileStream, cancellationToken);
|
||||
return localPath;
|
||||
}
|
||||
|
||||
await _containerClient.CreateIfNotExistsAsync(PublicAccessType.None, cancellationToken: cancellationToken);
|
||||
|
||||
var blobName = $"{DateTime.UtcNow:yyyy/MM/dd}/{Guid.NewGuid()}_{fileName}";
|
||||
var blobClient = _containerClient.GetBlobClient(blobName);
|
||||
|
||||
var blobHttpHeaders = new BlobHttpHeaders { ContentType = contentType };
|
||||
await blobClient.UploadAsync(content, new BlobUploadOptions { HttpHeaders = blobHttpHeaders }, cancellationToken);
|
||||
|
||||
_logger.LogInformation("File uploaded successfully: {BlobName}", blobName);
|
||||
return blobName;
|
||||
}
|
||||
|
||||
public async Task<Stream> DownloadAsync(string blobName, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_containerClient == null)
|
||||
{
|
||||
return File.OpenRead(blobName);
|
||||
}
|
||||
|
||||
var blobClient = _containerClient.GetBlobClient(blobName);
|
||||
var response = await blobClient.DownloadAsync(cancellationToken);
|
||||
return response.Value.Content;
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(string blobName, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_containerClient == null)
|
||||
{
|
||||
if (File.Exists(blobName))
|
||||
{
|
||||
File.Delete(blobName);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var blobClient = _containerClient.GetBlobClient(blobName);
|
||||
await blobClient.DeleteIfExistsAsync(cancellationToken: cancellationToken);
|
||||
_logger.LogInformation("File deleted: {BlobName}", blobName);
|
||||
}
|
||||
|
||||
public async Task<bool> ExistsAsync(string blobName, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_containerClient == null)
|
||||
{
|
||||
return File.Exists(blobName);
|
||||
}
|
||||
|
||||
var blobClient = _containerClient.GetBlobClient(blobName);
|
||||
return await blobClient.ExistsAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public string GetBlobUrl(string blobName)
|
||||
{
|
||||
if (_containerClient == null)
|
||||
{
|
||||
return $"/uploads/{Path.GetFileName(blobName)}";
|
||||
}
|
||||
|
||||
var blobClient = _containerClient.GetBlobClient(blobName);
|
||||
return blobClient.Uri.ToString();
|
||||
}
|
||||
}
|
||||
154
backend/src/InvoiceMaster.Infrastructure/Services/OcrService.cs
Normal file
154
backend/src/InvoiceMaster.Infrastructure/Services/OcrService.cs
Normal file
@@ -0,0 +1,154 @@
|
||||
using InvoiceMaster.Core.Interfaces;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace InvoiceMaster.Infrastructure.Services;
|
||||
|
||||
public class OcrService : IOcrService
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly ILogger<OcrService> _logger;
|
||||
private readonly string _apiUrl;
|
||||
private readonly string? _apiKey;
|
||||
|
||||
public OcrService(IHttpClientFactory httpClientFactory, IConfiguration configuration, ILogger<OcrService> logger)
|
||||
{
|
||||
_httpClient = httpClientFactory.CreateClient();
|
||||
_logger = logger;
|
||||
_apiUrl = configuration["Ocr:ApiUrl"] ?? "http://localhost:8000/api/v1";
|
||||
_apiKey = configuration["Ocr:ApiKey"];
|
||||
}
|
||||
|
||||
public async Task<OcrResult> ExtractAsync(Stream fileStream, string fileName, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var content = new MultipartFormDataContent();
|
||||
var streamContent = new StreamContent(fileStream);
|
||||
streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
|
||||
content.Add(streamContent, "file", fileName);
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, $"{_apiUrl}/infer")
|
||||
{
|
||||
Content = content
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(_apiKey))
|
||||
{
|
||||
request.Headers.Add("X-API-Key", _apiKey);
|
||||
}
|
||||
|
||||
var response = await _httpClient.SendAsync(request, cancellationToken);
|
||||
var responseContent = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
_logger.LogError("OCR API error: {StatusCode} - {Content}", response.StatusCode, responseContent);
|
||||
return new OcrResult
|
||||
{
|
||||
Success = false,
|
||||
ErrorMessage = $"OCR API returned {response.StatusCode}"
|
||||
};
|
||||
}
|
||||
|
||||
var result = JsonSerializer.Deserialize<OcrApiResponse>(responseContent, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
});
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
return new OcrResult
|
||||
{
|
||||
Success = false,
|
||||
ErrorMessage = "Invalid OCR response"
|
||||
};
|
||||
}
|
||||
|
||||
return new OcrResult
|
||||
{
|
||||
Success = result.Success,
|
||||
Data = MapToInvoiceData(result.Fields),
|
||||
Confidence = result.Confidence,
|
||||
ErrorMessage = result.Error
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error calling OCR API");
|
||||
return new OcrResult
|
||||
{
|
||||
Success = false,
|
||||
ErrorMessage = ex.Message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static InvoiceData MapToInvoiceData(Dictionary<string, OcrField>? fields)
|
||||
{
|
||||
if (fields == null)
|
||||
{
|
||||
return new InvoiceData();
|
||||
}
|
||||
|
||||
return new InvoiceData
|
||||
{
|
||||
SupplierName = GetFieldValue(fields, "supplier_name"),
|
||||
SupplierOrgNumber = GetFieldValue(fields, "supplier_org_number"),
|
||||
InvoiceNumber = GetFieldValue(fields, "invoice_number"),
|
||||
InvoiceDate = ParseDate(GetFieldValue(fields, "invoice_date")),
|
||||
DueDate = ParseDate(GetFieldValue(fields, "due_date")),
|
||||
AmountTotal = ParseDecimal(GetFieldValue(fields, "amount_total")),
|
||||
AmountVat = ParseDecimal(GetFieldValue(fields, "amount_vat")),
|
||||
VatRate = ParseInt(GetFieldValue(fields, "vat_rate")),
|
||||
OcrNumber = GetFieldValue(fields, "ocr_number"),
|
||||
Bankgiro = GetFieldValue(fields, "bankgiro"),
|
||||
Plusgiro = GetFieldValue(fields, "plusgiro"),
|
||||
Currency = GetFieldValue(fields, "currency") ?? "SEK"
|
||||
};
|
||||
}
|
||||
|
||||
private static string? GetFieldValue(Dictionary<string, OcrField> fields, string key)
|
||||
{
|
||||
return fields.TryGetValue(key, out var field) ? field.Value : null;
|
||||
}
|
||||
|
||||
private static DateTime? ParseDate(string? value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value)) return null;
|
||||
if (DateTime.TryParse(value, out var date)) return date;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static decimal? ParseDecimal(string? value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value)) return null;
|
||||
value = value.Replace(",", "").Replace(" ", "");
|
||||
if (decimal.TryParse(value, out var result)) return result;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int? ParseInt(string? value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value)) return null;
|
||||
if (int.TryParse(value, out var result)) return result;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public class OcrApiResponse
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public string? Error { get; set; }
|
||||
public Dictionary<string, OcrField>? Fields { get; set; }
|
||||
public decimal Confidence { get; set; }
|
||||
}
|
||||
|
||||
public class OcrField
|
||||
{
|
||||
public string? Value { get; set; }
|
||||
public decimal Confidence { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// <autogenerated />
|
||||
using System;
|
||||
using System.Reflection;
|
||||
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")]
|
||||
@@ -0,0 +1,22 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("InvoiceMaster.Infrastructure")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("InvoiceMaster.Infrastructure")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("InvoiceMaster.Infrastructure")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
// Generated by the MSBuild WriteCodeFragment class.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
3dee7621ca5dc2dfbf843a3f5e185fcc9b57e0a9761aafbf40a4e3d73c447d65
|
||||
@@ -0,0 +1,17 @@
|
||||
is_global = true
|
||||
build_property.TargetFramework = net8.0
|
||||
build_property.TargetFrameworkIdentifier = .NETCoreApp
|
||||
build_property.TargetFrameworkVersion = v8.0
|
||||
build_property.TargetPlatformMinVersion =
|
||||
build_property.UsingMicrosoftNETSdkWeb =
|
||||
build_property.ProjectTypeGuids =
|
||||
build_property.InvariantGlobalization =
|
||||
build_property.PlatformNeutralAssembly =
|
||||
build_property.EnforceExtendedAnalyzerRules =
|
||||
build_property._SupportedPlatformList = Linux,macOS,Windows
|
||||
build_property.RootNamespace = InvoiceMaster.Infrastructure
|
||||
build_property.ProjectDir = C:\Users\yaoji\git\ColaCoder\accounting-system\backend\src\InvoiceMaster.Infrastructure\
|
||||
build_property.EnableComHosting =
|
||||
build_property.EnableGeneratedComInterfaceComImportInterop =
|
||||
build_property.EffectiveAnalysisLevelStyle = 8.0
|
||||
build_property.EnableCodeStyleSeverity =
|
||||
@@ -0,0 +1,8 @@
|
||||
// <auto-generated/>
|
||||
global using System;
|
||||
global using System.Collections.Generic;
|
||||
global using System.IO;
|
||||
global using System.Linq;
|
||||
global using System.Net.Http;
|
||||
global using System.Threading;
|
||||
global using System.Threading.Tasks;
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,291 @@
|
||||
{
|
||||
"format": 1,
|
||||
"restore": {
|
||||
"C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj": {}
|
||||
},
|
||||
"projects": {
|
||||
"C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Application\\InvoiceMaster.Application.csproj": {
|
||||
"version": "1.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Application\\InvoiceMaster.Application.csproj",
|
||||
"projectName": "InvoiceMaster.Application",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Application\\InvoiceMaster.Application.csproj",
|
||||
"packagesPath": "C:\\Users\\yaoji\\.nuget\\packages\\",
|
||||
"outputPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Application\\obj\\",
|
||||
"projectStyle": "PackageReference",
|
||||
"fallbackFolders": [
|
||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
||||
],
|
||||
"configFilePaths": [
|
||||
"C:\\Users\\yaoji\\AppData\\Roaming\\NuGet\\NuGet.Config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
|
||||
],
|
||||
"originalTargetFrameworks": [
|
||||
"net8.0"
|
||||
],
|
||||
"sources": {
|
||||
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
|
||||
"https://api.nuget.org/v3/index.json": {},
|
||||
"https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"net8.0": {
|
||||
"targetAlias": "net8.0",
|
||||
"projectReferences": {
|
||||
"C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\InvoiceMaster.Core.csproj": {
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\InvoiceMaster.Core.csproj"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"warningProperties": {
|
||||
"allWarningsAsErrors": true,
|
||||
"warnAsError": [
|
||||
"NU1605"
|
||||
]
|
||||
},
|
||||
"restoreAuditProperties": {
|
||||
"enableAudit": "true",
|
||||
"auditLevel": "low",
|
||||
"auditMode": "direct"
|
||||
},
|
||||
"SdkAnalysisLevel": "10.0.100"
|
||||
},
|
||||
"frameworks": {
|
||||
"net8.0": {
|
||||
"targetAlias": "net8.0",
|
||||
"dependencies": {
|
||||
"AutoMapper": {
|
||||
"target": "Package",
|
||||
"version": "[12.0.1, )"
|
||||
},
|
||||
"FluentValidation": {
|
||||
"target": "Package",
|
||||
"version": "[11.8.1, )"
|
||||
},
|
||||
"MediatR": {
|
||||
"target": "Package",
|
||||
"version": "[12.2.0, )"
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": {
|
||||
"target": "Package",
|
||||
"version": "[8.0.0, )"
|
||||
},
|
||||
"StyleCop.Analyzers": {
|
||||
"include": "Runtime, Build, Native, ContentFiles, Analyzers",
|
||||
"suppressParent": "All",
|
||||
"target": "Package",
|
||||
"version": "[1.2.0-beta.556, )"
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"net461",
|
||||
"net462",
|
||||
"net47",
|
||||
"net471",
|
||||
"net472",
|
||||
"net48",
|
||||
"net481"
|
||||
],
|
||||
"assetTargetFallback": true,
|
||||
"warn": true,
|
||||
"frameworkReferences": {
|
||||
"Microsoft.NETCore.App": {
|
||||
"privateAssets": "all"
|
||||
}
|
||||
},
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.102/PortableRuntimeIdentifierGraph.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\InvoiceMaster.Core.csproj": {
|
||||
"version": "1.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\InvoiceMaster.Core.csproj",
|
||||
"projectName": "InvoiceMaster.Core",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\InvoiceMaster.Core.csproj",
|
||||
"packagesPath": "C:\\Users\\yaoji\\.nuget\\packages\\",
|
||||
"outputPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Core\\obj\\",
|
||||
"projectStyle": "PackageReference",
|
||||
"fallbackFolders": [
|
||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
||||
],
|
||||
"configFilePaths": [
|
||||
"C:\\Users\\yaoji\\AppData\\Roaming\\NuGet\\NuGet.Config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
|
||||
],
|
||||
"originalTargetFrameworks": [
|
||||
"net8.0"
|
||||
],
|
||||
"sources": {
|
||||
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
|
||||
"https://api.nuget.org/v3/index.json": {},
|
||||
"https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"net8.0": {
|
||||
"targetAlias": "net8.0",
|
||||
"projectReferences": {}
|
||||
}
|
||||
},
|
||||
"warningProperties": {
|
||||
"allWarningsAsErrors": true,
|
||||
"warnAsError": [
|
||||
"NU1605"
|
||||
]
|
||||
},
|
||||
"restoreAuditProperties": {
|
||||
"enableAudit": "true",
|
||||
"auditLevel": "low",
|
||||
"auditMode": "direct"
|
||||
},
|
||||
"SdkAnalysisLevel": "10.0.100"
|
||||
},
|
||||
"frameworks": {
|
||||
"net8.0": {
|
||||
"targetAlias": "net8.0",
|
||||
"dependencies": {
|
||||
"StyleCop.Analyzers": {
|
||||
"include": "Runtime, Build, Native, ContentFiles, Analyzers",
|
||||
"suppressParent": "All",
|
||||
"target": "Package",
|
||||
"version": "[1.2.0-beta.556, )"
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"net461",
|
||||
"net462",
|
||||
"net47",
|
||||
"net471",
|
||||
"net472",
|
||||
"net48",
|
||||
"net481"
|
||||
],
|
||||
"assetTargetFallback": true,
|
||||
"warn": true,
|
||||
"frameworkReferences": {
|
||||
"Microsoft.NETCore.App": {
|
||||
"privateAssets": "all"
|
||||
}
|
||||
},
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.102/PortableRuntimeIdentifierGraph.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj": {
|
||||
"version": "1.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"projectName": "InvoiceMaster.Infrastructure",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"packagesPath": "C:\\Users\\yaoji\\.nuget\\packages\\",
|
||||
"outputPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\obj\\",
|
||||
"projectStyle": "PackageReference",
|
||||
"fallbackFolders": [
|
||||
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
|
||||
],
|
||||
"configFilePaths": [
|
||||
"C:\\Users\\yaoji\\AppData\\Roaming\\NuGet\\NuGet.Config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config",
|
||||
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
|
||||
],
|
||||
"originalTargetFrameworks": [
|
||||
"net8.0"
|
||||
],
|
||||
"sources": {
|
||||
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
|
||||
"https://api.nuget.org/v3/index.json": {},
|
||||
"https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"net8.0": {
|
||||
"targetAlias": "net8.0",
|
||||
"projectReferences": {
|
||||
"C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Application\\InvoiceMaster.Application.csproj": {
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Application\\InvoiceMaster.Application.csproj"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"warningProperties": {
|
||||
"allWarningsAsErrors": true,
|
||||
"warnAsError": [
|
||||
"NU1605"
|
||||
]
|
||||
},
|
||||
"restoreAuditProperties": {
|
||||
"enableAudit": "true",
|
||||
"auditLevel": "low",
|
||||
"auditMode": "direct"
|
||||
},
|
||||
"SdkAnalysisLevel": "10.0.100"
|
||||
},
|
||||
"frameworks": {
|
||||
"net8.0": {
|
||||
"targetAlias": "net8.0",
|
||||
"dependencies": {
|
||||
"Azure.Storage.Blobs": {
|
||||
"target": "Package",
|
||||
"version": "[12.19.1, )"
|
||||
},
|
||||
"Microsoft.AspNetCore.Identity.EntityFrameworkCore": {
|
||||
"target": "Package",
|
||||
"version": "[8.0.0, )"
|
||||
},
|
||||
"Microsoft.EntityFrameworkCore": {
|
||||
"target": "Package",
|
||||
"version": "[8.0.0, )"
|
||||
},
|
||||
"Microsoft.EntityFrameworkCore.Tools": {
|
||||
"include": "Runtime, Build, Native, ContentFiles, Analyzers",
|
||||
"suppressParent": "All",
|
||||
"target": "Package",
|
||||
"version": "[8.0.0, )"
|
||||
},
|
||||
"Microsoft.Extensions.Http": {
|
||||
"target": "Package",
|
||||
"version": "[8.0.0, )"
|
||||
},
|
||||
"Npgsql.EntityFrameworkCore.PostgreSQL": {
|
||||
"target": "Package",
|
||||
"version": "[8.0.0, )"
|
||||
},
|
||||
"Polly": {
|
||||
"target": "Package",
|
||||
"version": "[8.2.0, )"
|
||||
},
|
||||
"Polly.Extensions.Http": {
|
||||
"target": "Package",
|
||||
"version": "[3.0.0, )"
|
||||
},
|
||||
"StyleCop.Analyzers": {
|
||||
"include": "Runtime, Build, Native, ContentFiles, Analyzers",
|
||||
"suppressParent": "All",
|
||||
"target": "Package",
|
||||
"version": "[1.2.0-beta.556, )"
|
||||
}
|
||||
},
|
||||
"imports": [
|
||||
"net461",
|
||||
"net462",
|
||||
"net47",
|
||||
"net471",
|
||||
"net472",
|
||||
"net48",
|
||||
"net481"
|
||||
],
|
||||
"assetTargetFallback": true,
|
||||
"warn": true,
|
||||
"frameworkReferences": {
|
||||
"Microsoft.NETCore.App": {
|
||||
"privateAssets": "all"
|
||||
}
|
||||
},
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.102/PortableRuntimeIdentifierGraph.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
|
||||
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
|
||||
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
|
||||
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
|
||||
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\yaoji\.nuget\packages\;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages</NuGetPackageFolders>
|
||||
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
|
||||
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">7.0.0</NuGetToolVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<SourceRoot Include="C:\Users\yaoji\.nuget\packages\" />
|
||||
<SourceRoot Include="C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages\" />
|
||||
</ItemGroup>
|
||||
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<Import Project="$(NuGetPackageRoot)microsoft.entityframeworkcore\8.0.0\buildTransitive\net8.0\Microsoft.EntityFrameworkCore.props" Condition="Exists('$(NuGetPackageRoot)microsoft.entityframeworkcore\8.0.0\buildTransitive\net8.0\Microsoft.EntityFrameworkCore.props')" />
|
||||
<Import Project="$(NuGetPackageRoot)microsoft.entityframeworkcore.design\8.0.0\build\net8.0\Microsoft.EntityFrameworkCore.Design.props" Condition="Exists('$(NuGetPackageRoot)microsoft.entityframeworkcore.design\8.0.0\build\net8.0\Microsoft.EntityFrameworkCore.Design.props')" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<PkgStyleCop_Analyzers_Unstable Condition=" '$(PkgStyleCop_Analyzers_Unstable)' == '' ">C:\Users\yaoji\.nuget\packages\stylecop.analyzers.unstable\1.2.0.556</PkgStyleCop_Analyzers_Unstable>
|
||||
<PkgMicrosoft_CodeAnalysis_Analyzers Condition=" '$(PkgMicrosoft_CodeAnalysis_Analyzers)' == '' ">C:\Users\yaoji\.nuget\packages\microsoft.codeanalysis.analyzers\3.3.3</PkgMicrosoft_CodeAnalysis_Analyzers>
|
||||
<PkgMicrosoft_EntityFrameworkCore_Tools Condition=" '$(PkgMicrosoft_EntityFrameworkCore_Tools)' == '' ">C:\Users\yaoji\.nuget\packages\microsoft.entityframeworkcore.tools\8.0.0</PkgMicrosoft_EntityFrameworkCore_Tools>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<Import Project="$(NuGetPackageRoot)system.text.json\8.0.0\buildTransitive\net6.0\System.Text.Json.targets" Condition="Exists('$(NuGetPackageRoot)system.text.json\8.0.0\buildTransitive\net6.0\System.Text.Json.targets')" />
|
||||
<Import Project="$(NuGetPackageRoot)microsoft.extensions.logging.abstractions\8.0.0\buildTransitive\net6.0\Microsoft.Extensions.Logging.Abstractions.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.logging.abstractions\8.0.0\buildTransitive\net6.0\Microsoft.Extensions.Logging.Abstractions.targets')" />
|
||||
<Import Project="$(NuGetPackageRoot)microsoft.extensions.options\8.0.0\buildTransitive\net6.0\Microsoft.Extensions.Options.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.options\8.0.0\buildTransitive\net6.0\Microsoft.Extensions.Options.targets')" />
|
||||
<Import Project="$(NuGetPackageRoot)microsoft.extensions.configuration.binder\8.0.0\buildTransitive\netstandard2.0\Microsoft.Extensions.Configuration.Binder.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.configuration.binder\8.0.0\buildTransitive\netstandard2.0\Microsoft.Extensions.Configuration.Binder.targets')" />
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
3758
backend/src/InvoiceMaster.Infrastructure/obj/project.assets.json
Normal file
3758
backend/src/InvoiceMaster.Infrastructure/obj/project.assets.json
Normal file
File diff suppressed because it is too large
Load Diff
248
backend/src/InvoiceMaster.Infrastructure/obj/project.nuget.cache
Normal file
248
backend/src/InvoiceMaster.Infrastructure/obj/project.nuget.cache
Normal file
@@ -0,0 +1,248 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "8okLAOOEnqI=",
|
||||
"success": false,
|
||||
"projectFilePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"expectedPackageFiles": [
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\automapper\\12.0.1\\automapper.12.0.1.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\azure.core\\1.36.0\\azure.core.1.36.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\azure.storage.blobs\\12.19.1\\azure.storage.blobs.12.19.1.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\azure.storage.common\\12.18.1\\azure.storage.common.12.18.1.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\fluentvalidation\\11.8.1\\fluentvalidation.11.8.1.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\humanizer.core\\2.14.1\\humanizer.core.2.14.1.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\mediatr\\12.2.0\\mediatr.12.2.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\mediatr.contracts\\2.0.1\\mediatr.contracts.2.0.1.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.aspnetcore.cryptography.internal\\8.0.0\\microsoft.aspnetcore.cryptography.internal.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.aspnetcore.cryptography.keyderivation\\8.0.0\\microsoft.aspnetcore.cryptography.keyderivation.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.aspnetcore.identity.entityframeworkcore\\8.0.0\\microsoft.aspnetcore.identity.entityframeworkcore.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.bcl.asyncinterfaces\\6.0.0\\microsoft.bcl.asyncinterfaces.6.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.codeanalysis.analyzers\\3.3.3\\microsoft.codeanalysis.analyzers.3.3.3.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.codeanalysis.common\\4.5.0\\microsoft.codeanalysis.common.4.5.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.codeanalysis.csharp\\4.5.0\\microsoft.codeanalysis.csharp.4.5.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.codeanalysis.csharp.workspaces\\4.5.0\\microsoft.codeanalysis.csharp.workspaces.4.5.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.codeanalysis.workspaces.common\\4.5.0\\microsoft.codeanalysis.workspaces.common.4.5.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.csharp\\4.7.0\\microsoft.csharp.4.7.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.entityframeworkcore\\8.0.0\\microsoft.entityframeworkcore.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.entityframeworkcore.abstractions\\8.0.0\\microsoft.entityframeworkcore.abstractions.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.entityframeworkcore.analyzers\\8.0.0\\microsoft.entityframeworkcore.analyzers.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.entityframeworkcore.design\\8.0.0\\microsoft.entityframeworkcore.design.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.entityframeworkcore.relational\\8.0.0\\microsoft.entityframeworkcore.relational.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.entityframeworkcore.tools\\8.0.0\\microsoft.entityframeworkcore.tools.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.caching.abstractions\\8.0.0\\microsoft.extensions.caching.abstractions.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.caching.memory\\8.0.0\\microsoft.extensions.caching.memory.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.configuration\\8.0.0\\microsoft.extensions.configuration.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.configuration.abstractions\\8.0.0\\microsoft.extensions.configuration.abstractions.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.configuration.binder\\8.0.0\\microsoft.extensions.configuration.binder.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.dependencyinjection\\8.0.0\\microsoft.extensions.dependencyinjection.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.dependencyinjection.abstractions\\8.0.0\\microsoft.extensions.dependencyinjection.abstractions.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.dependencymodel\\8.0.0\\microsoft.extensions.dependencymodel.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.diagnostics\\8.0.0\\microsoft.extensions.diagnostics.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.diagnostics.abstractions\\8.0.0\\microsoft.extensions.diagnostics.abstractions.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.http\\8.0.0\\microsoft.extensions.http.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.identity.core\\8.0.0\\microsoft.extensions.identity.core.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.identity.stores\\8.0.0\\microsoft.extensions.identity.stores.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.logging\\8.0.0\\microsoft.extensions.logging.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.logging.abstractions\\8.0.0\\microsoft.extensions.logging.abstractions.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.options\\8.0.0\\microsoft.extensions.options.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.options.configurationextensions\\8.0.0\\microsoft.extensions.options.configurationextensions.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\microsoft.extensions.primitives\\8.0.0\\microsoft.extensions.primitives.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\mono.texttemplating\\2.2.1\\mono.texttemplating.2.2.1.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\npgsql\\8.0.0\\npgsql.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\npgsql.entityframeworkcore.postgresql\\8.0.0\\npgsql.entityframeworkcore.postgresql.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\polly\\8.2.0\\polly.8.2.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\polly.core\\8.2.0\\polly.core.8.2.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\polly.extensions.http\\3.0.0\\polly.extensions.http.3.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\stylecop.analyzers\\1.2.0-beta.556\\stylecop.analyzers.1.2.0-beta.556.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\stylecop.analyzers.unstable\\1.2.0.556\\stylecop.analyzers.unstable.1.2.0.556.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\system.codedom\\4.4.0\\system.codedom.4.4.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\system.collections.immutable\\6.0.0\\system.collections.immutable.6.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\system.composition\\6.0.0\\system.composition.6.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\system.composition.attributedmodel\\6.0.0\\system.composition.attributedmodel.6.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\system.composition.convention\\6.0.0\\system.composition.convention.6.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\system.composition.hosting\\6.0.0\\system.composition.hosting.6.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\system.composition.runtime\\6.0.0\\system.composition.runtime.6.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\system.composition.typedparts\\6.0.0\\system.composition.typedparts.6.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\system.diagnostics.diagnosticsource\\8.0.0\\system.diagnostics.diagnosticsource.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\system.io.hashing\\6.0.0\\system.io.hashing.6.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\system.io.pipelines\\6.0.3\\system.io.pipelines.6.0.3.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\system.memory.data\\1.0.2\\system.memory.data.1.0.2.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\system.numerics.vectors\\4.5.0\\system.numerics.vectors.4.5.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\system.reflection.metadata\\6.0.1\\system.reflection.metadata.6.0.1.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\system.runtime.compilerservices.unsafe\\6.0.0\\system.runtime.compilerservices.unsafe.6.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\system.text.encoding.codepages\\6.0.0\\system.text.encoding.codepages.6.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\system.text.encodings.web\\8.0.0\\system.text.encodings.web.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\system.text.json\\8.0.0\\system.text.json.8.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\system.threading.channels\\6.0.0\\system.threading.channels.6.0.0.nupkg.sha512",
|
||||
"C:\\Users\\yaoji\\.nuget\\packages\\system.threading.tasks.extensions\\4.5.4\\system.threading.tasks.extensions.4.5.4.nupkg.sha512"
|
||||
],
|
||||
"logs": [
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1301",
|
||||
"level": "Error",
|
||||
"message": "Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.\r\n Response status code does not indicate success: 401 (Unauthorized).",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"targetGraphs": []
|
||||
},
|
||||
{
|
||||
"code": "NU1900",
|
||||
"level": "Error",
|
||||
"message": "Warning As Error: Error occurred while getting package vulnerability data: Unable to load the service index for source https://pkgs.dev.azure.com/billodev/2c2b8bbf-61f2-43f4-b4bb-2017cef20a2c/_packaging/BilloFeed/nuget/v3/index.json.",
|
||||
"projectPath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"filePath": "C:\\Users\\yaoji\\git\\ColaCoder\\accounting-system\\backend\\src\\InvoiceMaster.Infrastructure\\InvoiceMaster.Infrastructure.csproj",
|
||||
"targetGraphs": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using InvoiceMaster.Integrations.Accounting;
|
||||
|
||||
namespace InvoiceMaster.Integrations;
|
||||
|
||||
public class AccountingSystemFactory : IAccountingSystemFactory
|
||||
{
|
||||
private readonly Dictionary<string, IAccountingSystem> _providers = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public AccountingSystemFactory(IEnumerable<IAccountingSystem> providers)
|
||||
{
|
||||
foreach (var provider in providers)
|
||||
{
|
||||
_providers[provider.ProviderName] = provider;
|
||||
}
|
||||
}
|
||||
|
||||
public IAccountingSystem Create(string providerName)
|
||||
{
|
||||
if (_providers.TryGetValue(providerName, out var provider))
|
||||
{
|
||||
return provider;
|
||||
}
|
||||
|
||||
throw new NotSupportedException($"Accounting provider '{providerName}' is not supported");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
namespace InvoiceMaster.Integrations.Accounting;
|
||||
|
||||
public interface IAccountingSystem
|
||||
{
|
||||
string ProviderName { get; }
|
||||
|
||||
Task<AuthResult> AuthenticateAsync(string code, CancellationToken cancellationToken = default);
|
||||
Task<AuthResult> RefreshTokenAsync(string refreshToken, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<List<AccountingSupplier>> GetSuppliersAsync(string accessToken, CancellationToken cancellationToken = default);
|
||||
Task<AccountingSupplier> CreateSupplierAsync(string accessToken, AccountingSupplier supplier, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<AccountingVoucher> CreateVoucherAsync(string accessToken, AccountingVoucher voucher, CancellationToken cancellationToken = default);
|
||||
Task<List<AccountingAccount>> GetAccountsAsync(string accessToken, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<string> UploadAttachmentAsync(string accessToken, string fileName, Stream fileStream, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<CompanyInfo> GetCompanyInfoAsync(string accessToken, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public interface IAccountingSystemFactory
|
||||
{
|
||||
IAccountingSystem Create(string providerName);
|
||||
}
|
||||
|
||||
public record AuthResult(
|
||||
bool Success,
|
||||
string? ErrorMessage,
|
||||
string? AccessToken,
|
||||
string? RefreshToken,
|
||||
DateTime? ExpiresAt,
|
||||
string? Scope,
|
||||
CompanyInfo? CompanyInfo);
|
||||
|
||||
public record CompanyInfo(
|
||||
string Name,
|
||||
string? OrganisationNumber);
|
||||
|
||||
public record AccountingSupplier(
|
||||
string SupplierNumber,
|
||||
string Name,
|
||||
string? OrganisationNumber = null,
|
||||
string? Address1 = null,
|
||||
string? Postcode = null,
|
||||
string? City = null,
|
||||
string? Phone = null,
|
||||
string? Email = null,
|
||||
string? BankgiroNumber = null,
|
||||
string? PlusgiroNumber = null);
|
||||
|
||||
public record AccountingVoucher(
|
||||
string? Series,
|
||||
DateTime? TransactionDate,
|
||||
List<AccountingVoucherRow> Rows,
|
||||
string? Description = null);
|
||||
|
||||
public record AccountingVoucherRow(
|
||||
int Account,
|
||||
decimal? Debit,
|
||||
decimal? Credit,
|
||||
string? Description = null,
|
||||
string? SupplierNumber = null);
|
||||
|
||||
public record AccountingAccount(
|
||||
int Code,
|
||||
string Name,
|
||||
string Type);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user