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:
Invoice Master
2026-02-04 20:14:34 +01:00
commit 05ea67144f
250 changed files with 50402 additions and 0 deletions

106
backend/.env.example Normal file
View 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

View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,6 @@
{
"sdk": {
"version": "8.0.100",
"rollForward": "latestFeature"
}
}

View File

@@ -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!);
}
}

View 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 });
}
}

View File

@@ -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"
});
}
}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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;
}
}

View 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>

View File

@@ -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);
}
}

View 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();

View 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"
}
}

View 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"
}
}

View File

@@ -0,0 +1,4 @@
// <autogenerated />
using System;
using System.Reflection;
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")]

View File

@@ -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.

View File

@@ -0,0 +1 @@
0b17e756d606e87e18da4b7ff18325a40b19e253e4e72e12aaf90ad0cae31d74

View File

@@ -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 =

View File

@@ -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;

View File

@@ -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"
}
}
}
}
}

View File

@@ -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>

View File

@@ -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>

File diff suppressed because it is too large Load Diff

View 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": []
}
]
}

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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");
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View 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);

View 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);

View 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);

View 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;
}
}

View File

@@ -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>

View 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;
}
}

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -0,0 +1,4 @@
// <autogenerated />
using System;
using System.Reflection;
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")]

View File

@@ -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.

View File

@@ -0,0 +1 @@
f6f8c7200df65f81badce9ed069db84dceea88391c8f2b18de6f45a3e2fdd4db

View File

@@ -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 =

View File

@@ -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;

View File

@@ -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"
}
}
}
}
}

View File

@@ -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>

View File

@@ -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" />

View 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."
}
]
}

View File

@@ -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": []
}
]
}

View File

@@ -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;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}

View 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;
}
}

View File

@@ -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);
}

View 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";
}

View 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);
}

View File

@@ -0,0 +1,6 @@
namespace InvoiceMaster.Core.Interfaces;
public interface IUnitOfWork : IDisposable
{
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
}

View File

@@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>InvoiceMaster.Core</AssemblyName>
<RootNamespace>InvoiceMaster.Core</RootNamespace>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,4 @@
// <autogenerated />
using System;
using System.Reflection;
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")]

View File

@@ -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.

View File

@@ -0,0 +1 @@
8c36b0fbd0cac0263b4d3fa3916563810323f0ab58fe8276eecae48c4cffb1b4

View File

@@ -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 =

View File

@@ -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;

View File

@@ -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"
}
}
}
}
}

View File

@@ -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>

View File

@@ -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" />

View 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."
}
]
}

View 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": []
}
]
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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>

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View 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; }
}

View File

@@ -0,0 +1,4 @@
// <autogenerated />
using System;
using System.Reflection;
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")]

View File

@@ -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.

View File

@@ -0,0 +1 @@
3dee7621ca5dc2dfbf843a3f5e185fcc9b57e0a9761aafbf40a4e3d73c447d65

View File

@@ -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 =

View File

@@ -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;

View File

@@ -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"
}
}
}
}
}

View File

@@ -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>

View File

@@ -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>

File diff suppressed because it is too large Load Diff

View 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": []
}
]
}

View File

@@ -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");
}
}

View File

@@ -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