Add project files.

This commit is contained in:
Luca 2026-02-13 14:55:57 +01:00
parent 7fd695f5c4
commit de36f710df
36 changed files with 1137 additions and 0 deletions

25
OdooAPI.sln Normal file
View File

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.14.36623.8 d17.14
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OdooAPI", "OdooAPI\OdooAPI.csproj", "{AC50ACEB-8209-4330-B8BA-DB84922F8226}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{AC50ACEB-8209-4330-B8BA-DB84922F8226}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AC50ACEB-8209-4330-B8BA-DB84922F8226}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AC50ACEB-8209-4330-B8BA-DB84922F8226}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AC50ACEB-8209-4330-B8BA-DB84922F8226}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B1E1DA3F-BBFA-44B2-9C16-F05361CE4C27}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,7 @@
namespace OdooAPI.DataAccess.Odoo.Dtos;
public class OdooProductDto
{
public int Id { get; set; }
public string DefaultCode { get; set; } = "";
}

View File

@ -0,0 +1,10 @@
namespace OdooAPI.DataAccess.Odoo.Dtos;
public class OdooSupplierDto
{
public int Id { get; set; }
public int PartnerId { get; set; }
public string Name { get; set; } = "";
}

View File

@ -0,0 +1,63 @@
using System.Net.Http;
using System.Text;
using System.Text.Json;
namespace OdooAPI.DataAccess.Odoo;
public class OdooLogin
{
private const string Url = "https://odooapi1.odoo.com/jsonrpc";
private const string Database = "odooapi1";
private const string Username = "luca@corsie.nl";
private const string Password = "XS82ns23!";
private readonly HttpClient _http;
public OdooLogin(HttpClient http)
{
_http = http;
}
public async Task<int?> LoginAsync()
{
var payload = new
{
jsonrpc = "2.0",
method = "call",
id = 1,
@params = new
{
service = "common",
method = "login",
args = new object[]
{
Database,
Username,
Password
}
}
};
var content = new StringContent(
JsonSerializer.Serialize(payload),
Encoding.UTF8,
"application/json");
var response = await _http.PostAsync(Url, content);
var json = await response.Content.ReadAsStringAsync();
Console.WriteLine("LOGIN RESPONSE:");
Console.WriteLine(json);
using var doc = JsonDocument.Parse(json);
if (!doc.RootElement.TryGetProperty("result", out var result))
return null;
return result.ValueKind == JsonValueKind.Number
? result.GetInt32()
: null;
}
}

View File

@ -0,0 +1,249 @@
using System.Text;
using System.Text.Json;
namespace OdooAPI.DataAccess.Odoo.Repositories;
public class OdooProductRepository
{
private readonly HttpClient _http;
private const string Url = "https://odooapi1.odoo.com/jsonrpc";
private const string Database = "odooapi1";
private const string Username = "luca@corsie.nl";
private const string Password = "XS82ns23!";
private int _uid;
// ================================
// PROPERTY (fix voor jouw error)
// ================================
public int Uid => _uid;
public OdooProductRepository(HttpClient http)
{
_http = http;
}
// =========================================
// LOGIN
// =========================================
public async Task LoginAsync()
{
var result = await CallAsync(
"common",
"login",
new object[] { Database, Username, Password });
_uid = result.GetInt32();
}
// =========================================
// PRODUCTEN OPHALEN (fix voor jouw error)
// =========================================
public async Task<List<string>> GetAllProductsAsync()
{
var result = await CallAsync(
"object",
"execute_kw",
new object[]
{
Database, _uid, Password,
"product.product",
"search_read",
new object[] { new object[] { } }
},
new { fields = new[] { "default_code" } }
);
var list = new List<string>();
foreach (var p in result.EnumerateArray())
{
if (p.TryGetProperty("default_code", out var code) &&
code.ValueKind == JsonValueKind.String)
{
list.Add(code.GetString()!);
}
}
return list;
}
// =========================================
// UPSERT SUPPLIER (CREATE of UPDATE)
// =========================================
public async Task UpsertSupplierAsync(
string defaultCode,
string supplierName,
string leverancierProductCode,
decimal price,
int delay)
{
var partnerId = await GetPartnerIdAsync(supplierName);
if (partnerId == null)
throw new Exception($"Supplier '{supplierName}' niet gevonden.");
// product template ophalen (VERPLICHT in Odoo)
var tmplId = await GetProductTemplateIdAsync(defaultCode);
if (tmplId == null)
throw new Exception($"Product template niet gevonden voor {defaultCode}");
var existingId =
await GetExistingSupplierInfoId(partnerId.Value, leverancierProductCode);
var values = new
{
partner_id = partnerId,
product_tmpl_id = tmplId, // ⭐ BELANGRIJKSTE FIX
product_code = leverancierProductCode,
price = price,
delay = delay,
product_uom_id = 1
};
if (existingId != null)
{
Console.WriteLine(" ␦ UPDATE in Odoo");
await CallAsync(
"object",
"execute_kw",
new object[]
{
Database,_uid,Password,
"product.supplierinfo",
"write",
new object[] { new [] { existingId }, values }
});
}
else
{
Console.WriteLine(" ␦ CREATE in Odoo");
await CallAsync(
"object",
"execute_kw",
new object[]
{
Database,_uid,Password,
"product.supplierinfo",
"create",
new object[] { values }
});
}
}
// =========================================
// TEMPLATE ID (nieuw verplicht)
// =========================================
private async Task<int?> GetProductTemplateIdAsync(string defaultCode)
{
var result = await CallAsync(
"object",
"execute_kw",
new object[]
{
Database,_uid,Password,
"product.product",
"search_read",
new object[]
{
new object[]
{
new object[] { "default_code", "=", defaultCode }
}
}
},
new { fields = new[] { "product_tmpl_id" }, limit = 1 }
);
foreach (var r in result.EnumerateArray())
{
return r.GetProperty("product_tmpl_id")[0].GetInt32();
}
return null;
}
private async Task<int?> GetExistingSupplierInfoId(int partnerId, string code)
{
var result = await CallAsync(
"object",
"execute_kw",
new object[]
{
Database,_uid,Password,
"product.supplierinfo",
"search",
new object[]
{
new object[]
{
new object[] { "partner_id", "=", partnerId },
new object[] { "product_code", "=", code }
}
}
});
var ids = result.EnumerateArray().ToList();
return ids.Count == 0 ? null : ids[0].GetInt32();
}
private async Task<int?> GetPartnerIdAsync(string name)
{
var result = await CallAsync(
"object",
"execute_kw",
new object[]
{
Database,_uid,Password,
"res.partner",
"search",
new object[]
{
new object[]
{
new object[] { "name", "=", name }
}
}
});
var ids = result.EnumerateArray().ToList();
return ids.Count == 0 ? null : ids[0].GetInt32();
}
// =========================================
// CORE JSON RPC
// =========================================
private async Task<JsonElement> CallAsync(
string service,
string method,
object[] args,
object? kwargs = null)
{
var payload = new
{
jsonrpc = "2.0",
method = "call",
id = 1,
@params = new { service, method, args, kwargs }
};
var json = JsonSerializer.Serialize(payload);
var response = await _http.PostAsync(
Url,
new StringContent(json, Encoding.UTF8, "application/json"));
var content = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(content);
if (doc.RootElement.TryGetProperty("error", out var error))
throw new Exception(error.ToString());
return doc.RootElement.GetProperty("result").Clone();
}
}

View File

@ -0,0 +1,15 @@
using Microsoft.EntityFrameworkCore;
using OdooAPI.Database;
public class ArtikelRepository
{
public async Task<List<string>> GetSupplierNamesAsync(int artikelId)
{
using var db = new AppDbContext();
return await db.LevArtikelen
.Where(x => x.ArtikelId == artikelId)
.Select(x => x.Leverancier.Naam)
.ToListAsync();
}
}

View File

@ -0,0 +1,32 @@
using Microsoft.EntityFrameworkCore;
using OdooAPI.Database;
using OdooAPI.Database.Tabellen;
namespace OdooAPI.DataAccess.Repositories;
public class LevArtikelRepository
{
private readonly AppDbContext _db;
public LevArtikelRepository(AppDbContext db)
{
_db = db;
}
public Dictionary<(int, int), LevArtikel> GetAllLookup()
{
return _db.LevArtikelen
.AsNoTracking()
.ToDictionary(x => (x.LeverancierId, x.ArtikelId), x => x);
}
public void AddRange(List<LevArtikel> items)
{
_db.LevArtikelen.AddRange(items);
}
public void UpdateRange(List<LevArtikel> items)
{
_db.LevArtikelen.UpdateRange(items);
}
}

View File

@ -0,0 +1,27 @@
using Microsoft.EntityFrameworkCore;
using OdooAPI.Database;
using OdooAPI.Database.Tabellen;
namespace OdooAPI.DataAccess.Repositories;
public class LeverancierRepository
{
private readonly AppDbContext _db;
public LeverancierRepository(AppDbContext db)
{
_db = db;
}
public Leverancier? GetByNaam(string naam)
{
return _db.Leveranciers.FirstOrDefault(x => x.Naam == naam);
}
public Leverancier Add(string naam)
{
var lev = new Leverancier { Naam = naam };
_db.Leveranciers.Add(lev);
return lev;
}
}

View File

@ -0,0 +1,10 @@
namespace OdooAPI.DataAccess.Xml;
public class XmlItemDto
{
public string VendorId { get; set; } = "";
public string Naam { get; set; } = "";
public decimal Prijs { get; set; }
public int Stock { get; set; }
public string? Ean { get; set; }
}

View File

@ -0,0 +1,32 @@
using OdooAPI.Database.Tabellen;
namespace OdooAPI.DataAccess.Xml;
public class XmlItemMapper
{
public Artikel MapToArtikel(XmlItemDto dto)
{
return new Artikel
{
ArtikelNummer = dto.VendorId,
Naam = dto.Naam,
Ean = dto.Ean,
InOdoo = false
};
}
public LevArtikel MapToLevArtikel(XmlItemDto dto, int leverancierId, int artikelId)
{
return new LevArtikel
{
LeverancierId = leverancierId,
ArtikelId = artikelId,
ArtikelNummerLev = dto.VendorId,
Inkoopprijs = dto.Prijs,
Levertijd = 1,
Omschrijving = "",
Leverbaar = dto.Stock > 0,
Korting = null
};
}
}

View File

@ -0,0 +1,38 @@
using System.Xml.Linq;
using OdooAPI.Services.Helpers;
namespace OdooAPI.DataAccess.Xml;
public class XmlSupplierReader
{
public List<XmlItemDto> ReadFolder(string supplierFolder)
{
var result = new List<XmlItemDto>();
var files = Directory.GetFiles(supplierFolder, "*.xml");
foreach (var file in files)
{
result.AddRange(ReadFile(file));
}
return result;
}
private IEnumerable<XmlItemDto> ReadFile(string filePath)
{
var doc = XDocument.Load(filePath);
foreach (var item in doc.Descendants("item"))
{
yield return new XmlItemDto
{
VendorId = item.Element("vendor_id")?.Value ?? "",
Naam = item.Element("long_desc")?.Value ?? "",
Prijs = DecimalHelper.Parse(item.Element("price")?.Value),
Stock = int.TryParse(item.Element("stock")?.Value, out var s) ? s : 0,
Ean = item.Element("EAN_code")?.Value
};
}
}
}

View File

@ -0,0 +1,41 @@
using Microsoft.EntityFrameworkCore;
using OdooAPI.Database.Tabellen;
namespace OdooAPI.Database;
public class AppDbContext : DbContext
{
public DbSet<Artikel> Artikelen => Set<Artikel>();
public DbSet<Leverancier> Leveranciers => Set<Leverancier>();
public DbSet<LevArtikel> LevArtikelen => Set<LevArtikel>();
public DbSet<Marge> Marges => Set<Marge>();
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
var path = Path.Combine(AppContext.BaseDirectory, "database.db");
options.UseSqlite($"Data Source={path}");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Artikel>()
.HasIndex(x => x.ArtikelNummer)
.IsUnique();
modelBuilder.Entity<LevArtikel>()
.HasIndex(x => new { x.LeverancierId, x.ArtikelId })
.IsUnique();
modelBuilder.Entity<LevArtikel>()
.HasOne(x => x.Artikel)
.WithMany(x => x.LevArtikelen)
.HasForeignKey(x => x.ArtikelId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<LevArtikel>()
.HasOne(x => x.Leverancier)
.WithMany(x => x.LevArtikelen)
.HasForeignKey(x => x.LeverancierId)
.OnDelete(DeleteBehavior.Restrict);
}
}

View File

@ -0,0 +1,21 @@
using Microsoft.EntityFrameworkCore;
namespace OdooAPI.Database;
public class ArtikelRepository
{
private readonly AppDbContext _db;
public ArtikelRepository(AppDbContext db)
{
_db = db;
}
public async Task<List<string>> GetAllArtikelNummersAsync()
{
return await _db.Artikelen
.Where(a => a.ArtikelNummer != null)
.Select(a => a.ArtikelNummer!)
.ToListAsync();
}
}

View File

@ -0,0 +1,10 @@
namespace OdooAPI.Database;
public static class DbInitializer
{
public static void Initialize()
{
using var db = new AppDbContext();
db.Database.EnsureCreated();
}
}

View File

@ -0,0 +1,16 @@
namespace OdooAPI.Database.Tabellen;
public class Artikel
{
public int Id { get; set; }
public string Naam { get; set; } = "";
public string ArtikelNummer { get; set; } = "";
public string? Ean { get; set; }
public bool InOdoo { get; set; }
public List<LevArtikel> LevArtikelen { get; set; } = new();
}

View File

@ -0,0 +1,24 @@
namespace OdooAPI.Database.Tabellen;
public class LevArtikel
{
public int Id { get; set; }
public int LeverancierId { get; set; }
public Leverancier Leverancier { get; set; } = null!;
public int ArtikelId { get; set; }
public Artikel Artikel { get; set; } = null!;
public string ArtikelNummerLev { get; set; } = "";
public decimal Inkoopprijs { get; set; }
public int Levertijd { get; set; } = 1;
public string Omschrijving { get; set; } = "";
public bool Leverbaar { get; set; }
public decimal? Korting { get; set; }
}

View File

@ -0,0 +1,10 @@
namespace OdooAPI.Database.Tabellen;
public class Leverancier
{
public int Id { get; set; }
public string Naam { get; set; } = "";
public List<LevArtikel> LevArtikelen { get; set; } = new();
}

View File

@ -0,0 +1,10 @@
namespace OdooAPI.Database.Tabellen;
public class Marge
{
public int Id { get; set; }
public int ArtikelId { get; set; }
public decimal Percentage { get; set; }
}

View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OdooAPI.Import
{
internal class OdooSycnProcessor
{
}
}

View File

@ -0,0 +1,119 @@
using OdooAPI.Database;
using OdooAPI.Database.Tabellen;
using OdooAPI.DataAccess.Xml;
namespace OdooAPI.Import;
public class SupplierImportProcessor
{
private readonly AppDbContext _db;
public SupplierImportProcessor(AppDbContext db)
{
_db = db;
}
public void Process(string supplierFolder)
{
var supplierName = Path.GetFileName(supplierFolder);
Console.WriteLine($"Start import: {supplierName}");
var leverancier = _db.Leveranciers
.FirstOrDefault(x => x.Naam == supplierName);
if (leverancier == null)
{
leverancier = new Leverancier { Naam = supplierName };
_db.Leveranciers.Add(leverancier);
_db.SaveChanges();
Console.WriteLine("Nieuwe leverancier aangemaakt");
}
var reader = new XmlSupplierReader();
var items = reader.ReadFolder(supplierFolder);
Console.WriteLine($"XML items gevonden: {items.Count}");
var bestaandeArtikelen = _db.Artikelen
.ToDictionary(x => x.ArtikelNummer);
int nieuweArtikelen = 0;
foreach (var item in items)
{
if (!bestaandeArtikelen.ContainsKey(item.VendorId))
{
var artikel = new Artikel
{
Naam = item.Naam,
ArtikelNummer = item.VendorId,
Ean = item.Ean,
InOdoo = false
};
_db.Artikelen.Add(artikel);
bestaandeArtikelen[item.VendorId] = artikel;
nieuweArtikelen++;
}
}
_db.SaveChanges();
Console.WriteLine($"Nieuwe artikelen: {nieuweArtikelen}");
var bestaandeLevArtikelen = _db.LevArtikelen
.Where(x => x.LeverancierId == leverancier.Id)
.ToDictionary(x => x.ArtikelId);
var gezienArtikelIds = new HashSet<int>();
int nieuweLev = 0;
int updates = 0;
foreach (var item in items)
{
var artikel = bestaandeArtikelen[item.VendorId];
if (!bestaandeLevArtikelen.TryGetValue(artikel.Id, out var levArtikel))
{
levArtikel = new LevArtikel
{
LeverancierId = leverancier.Id,
ArtikelId = artikel.Id,
ArtikelNummerLev = item.VendorId,
Inkoopprijs = item.Prijs,
Levertijd = 1,
Leverbaar = true,
Omschrijving = "",
Korting = null
};
_db.LevArtikelen.Add(levArtikel);
nieuweLev++;
}
else
{
levArtikel.Inkoopprijs = item.Prijs;
levArtikel.Leverbaar = true;
updates++;
}
gezienArtikelIds.Add(artikel.Id);
}
foreach (var lev in bestaandeLevArtikelen.Values)
{
if (!gezienArtikelIds.Contains(lev.ArtikelId))
lev.Leverbaar = false;
}
_db.SaveChanges();
Console.WriteLine($"Nieuwe leverancier-artikelen: {nieuweLev}");
Console.WriteLine($"Updates: {updates}");
Console.WriteLine($"Klaar met: {supplierName}");
Console.WriteLine();
}
}

View File

@ -0,0 +1,19 @@
using OdooAPI.Database;
namespace OdooAPI.Import;
public class XmlImportRunner
{
public void Run()
{
using var db = new AppDbContext();
db.Database.EnsureCreated();
var importer = new XmlImporter(db);
importer.Run();
}
}

View File

@ -0,0 +1,41 @@
using OdooAPI.Database;
using OdooAPI.Services;
using System.IO;
namespace OdooAPI.Import;
public class XmlImporter
{
private readonly AppDbContext _db;
public XmlImporter(AppDbContext db)
{
_db = db;
}
public void Run()
{
var baseDir = Path.Combine(AppContext.BaseDirectory, "Leveranciers");
Console.WriteLine($"Zoeken naar leveranciers in: {baseDir}");
if (!Directory.Exists(baseDir))
{
Console.WriteLine("Leveranciers map niet gevonden");
return;
}
var scanner = new LeverancierScanner();
var supplierFolders = scanner.GetSupplierFolders(baseDir);
foreach (var folder in supplierFolders)
{
var name = Path.GetFileName(folder);
Console.WriteLine($"Leverancier gevonden: {name}");
var processor = new SupplierImportProcessor(_db);
processor.Process(folder);
}
}
}

20
OdooAPI/OdooAPI.csproj Normal file
View File

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.23" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.23" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.23">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.1" />
</ItemGroup>
</Project>

44
OdooAPI/Program.cs Normal file
View File

@ -0,0 +1,44 @@
using OdooAPI.Import;
using OdooAPI.Services;
internal class Program
{
static async Task Main(string[] args)
{
try
{
// =====================================
// 1⃣ XML IMPORT
// =====================================
Console.WriteLine("=================================");
Console.WriteLine("XML IMPORT START");
Console.WriteLine("=================================");
var xml = new XmlImportRunner();
xml.Run();
Console.WriteLine("XML IMPORT KLAAR\n");
// =====================================
// 2⃣ ODOO SYNC
// =====================================
Console.WriteLine("=================================");
Console.WriteLine("ODOO SYNC START");
Console.WriteLine("=================================");
var app = new OdooSyncApp();
await app.RunAsync();
Console.WriteLine("\nKlaar.");
}
catch (Exception ex)
{
Console.WriteLine("\nFOUT:");
Console.WriteLine(ex);
}
Console.WriteLine("\nDruk op een toets om te sluiten...");
Console.ReadKey();
}
}

View File

@ -0,0 +1,13 @@
namespace OdooAPI.Services;
public class ArtikelMatcher
{
public bool IsMatch(string artikelNummerA, string artikelNummerB)
{
return string.Equals(
artikelNummerA?.Trim(),
artikelNummerB?.Trim(),
StringComparison.OrdinalIgnoreCase
);
}
}

View File

@ -0,0 +1,9 @@
namespace OdooAPI.Services;
public static class AvailabilityRules
{
public static bool IsAvailable(int stock)
{
return stock > 0;
}
}

View File

@ -0,0 +1,12 @@
namespace OdooAPI.Services;
public static class EanValidator
{
public static bool IsValid(string? ean)
{
if (string.IsNullOrWhiteSpace(ean))
return false;
return ean.All(char.IsDigit);
}
}

View File

@ -0,0 +1,18 @@
using System.Globalization;
namespace OdooAPI.Services.Helpers;
public static class DecimalHelper
{
public static decimal Parse(string? value)
{
if (string.IsNullOrWhiteSpace(value))
return 0m;
value = value.Replace(",", ".");
decimal.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result);
return result;
}
}

View File

@ -0,0 +1,9 @@
namespace OdooAPI.Services.Helpers;
public static class Keybuilder
{
public static string Build(string leverancier, string artikelNummer)
{
return $"{leverancier}_{artikelNummer}".ToLowerInvariant();
}
}

View File

@ -0,0 +1,12 @@
namespace OdooAPI.Services.Helpers;
public static class Normalizer
{
public static string Clean(string? value)
{
if (string.IsNullOrWhiteSpace(value))
return string.Empty;
return value.Trim();
}
}

View File

@ -0,0 +1,14 @@
using System.IO;
namespace OdooAPI.Services;
public class LeverancierScanner
{
public IEnumerable<string> GetSupplierFolders(string baseDir)
{
if (!Directory.Exists(baseDir))
return Enumerable.Empty<string>();
return Directory.GetDirectories(baseDir);
}
}

View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace OdooAPI.Services
{
internal class MatchResult
{
}
}

View File

@ -0,0 +1,57 @@
using OdooAPI.DataAccess.Odoo.Repositories;
using OdooAPI.Database;
namespace OdooAPI.Services;
public class OdooSyncApp
{
public async Task RunAsync()
{
Console.WriteLine("Inloggen in Odoo...");
var http = new HttpClient();
var repo = new OdooProductRepository(http);
await repo.LoginAsync();
Console.WriteLine($"Ingelogd bij Odoo (UID = {repo.Uid})");
// =========================================
// PRODUCTEN OPHALEN
// =========================================
Console.WriteLine("\nProducten ophalen...");
var odooProducts = await repo.GetAllProductsAsync(); // List<string>
Console.WriteLine($"Odoo producten: {odooProducts.Count}");
var db = new AppDbContext();
var localCodes = db.LevArtikelen
.Select(x => x.ArtikelNummerLev)
.Distinct()
.ToList();
Console.WriteLine($"Database producten: {localCodes.Count}");
// ✅ BELANGRIJK: strings vergelijken (GEEN DefaultCode meer!)
var matches = odooProducts
.Where(code => localCodes.Contains(code))
.ToList();
Console.WriteLine($"Matches: {matches.Count}");
// =========================================
// SUPPLIER MATCHING
// =========================================
Console.WriteLine("\nSUPPLIER MATCHING");
var supplierMatch = new SupplierMatchService(db, repo);
await supplierMatch.RunAsync(matches);
Console.WriteLine("Klaar.");
}
}

View File

@ -0,0 +1,27 @@
using OdooAPI.DataAccess.Odoo.Dtos;
namespace OdooAPI.Services;
public class OdooXmlMatchService
{
public List<string> GetMatchedCodes(
List<OdooProductDto> odooProducts,
List<string> localCodes)
{
var localSet = new HashSet<string>(localCodes);
var matches = odooProducts
.Where(p =>
!string.IsNullOrEmpty(p.DefaultCode) &&
localSet.Contains(p.DefaultCode))
.Select(p => p.DefaultCode)
.ToList();
Console.WriteLine($"Aantal matches: {matches.Count}");
foreach (var m in matches)
Console.WriteLine($"MATCH: {m}");
return matches;
}
}

View File

@ -0,0 +1,9 @@
namespace OdooAPI.Services;
public static class PriceComparer
{
public static bool HasChanged(decimal oldPrice, decimal newPrice)
{
return oldPrice != newPrice;
}
}

View File

@ -0,0 +1,50 @@
using Microsoft.EntityFrameworkCore;
using OdooAPI.Database;
using OdooAPI.DataAccess.Odoo.Repositories;
namespace OdooAPI.Services;
public class SupplierMatchService
{
private readonly AppDbContext _db;
private readonly OdooProductRepository _repo;
public SupplierMatchService(AppDbContext db, OdooProductRepository repo)
{
_db = db;
_repo = repo;
}
public async Task RunAsync(List<string> matchedCodes)
{
Console.WriteLine("SUPPLIER MATCHING");
foreach (var code in matchedCodes)
{
Console.WriteLine($"\nProduct: {code}");
var localSuppliers = await _db.LevArtikelen
.Where(x => x.ArtikelNummerLev == code)
.Select(x => new
{
Naam = x.Leverancier.Naam,
Code = x.ArtikelNummerLev,
Prijs = x.Inkoopprijs,
Delay = x.Leverbaar ? 1 : 0
})
.ToListAsync();
foreach (var s in localSuppliers)
{
Console.WriteLine($"UPSERT {s.Naam}");
await _repo.UpsertSupplierAsync(
code,
s.Naam,
s.Code,
s.Prijs,
s.Delay);
}
}
}
}