361 lines
21 KiB
C#
361 lines
21 KiB
C#
using System.Globalization;
|
|
using MijnBesluit.Application.Services;
|
|
using MijnBesluit.Domain.Enums;
|
|
|
|
namespace MijnBesluit.SelfCheck;
|
|
|
|
/// <summary>
|
|
/// Adverteren: de rekensom, de weg van indienen naar draaien, en wie er wat mag.
|
|
///
|
|
/// Zie docs/Adverteren.md. De twee dingen die hier echt bewaakt worden: dat er niets draait
|
|
/// waarvoor niet is betaald én naar gekeken, en dat alleen een beheerder die knoppen heeft.
|
|
/// </summary>
|
|
public static class AdvertentieChecks
|
|
{
|
|
public static void Register(Runner runner)
|
|
{
|
|
runner.TestAsync("Adverteren: een code voor adverteerders is geen inlogcode", async () =>
|
|
{
|
|
// De belangrijkste controle van het hele adverteerdersgedeelte. Een adverteerder is
|
|
// geen gebruiker van MijnBesluit: zijn code hoort nooit toegang tot een kluis te
|
|
// geven, en de inlogcode van een gebruiker hoort niet in het adverteerdersgedeelte
|
|
// te werken. Ze staan in dezelfde tabel, dus het verschil zit in het doel - en
|
|
// precies zo'n verschil is er een om vast te leggen.
|
|
var w = new World();
|
|
await w.MaakGebruikerAsync("jan@example.nl", "Jan", "Jansen");
|
|
|
|
// Een code voor adverteren, aangevraagd op het adres van een echte gebruiker.
|
|
Assert.True((await w.Accounts.VraagAdverteerderscodeAanAsync("jan@example.nl", "Kwik-Bit", "Jan")).Succeeded,
|
|
"de code voor adverteren gaat de deur uit");
|
|
var reclamecode = w.LaatsteCodeUitMail("jan@example.nl");
|
|
|
|
var binnen = await w.Accounts.LogInMetCodeAsync("jan@example.nl", reclamecode);
|
|
Assert.True(!binnen.Succeeded,
|
|
"met een adverteerderscode kom je niet in de kluis van die gebruiker");
|
|
|
|
// En andersom.
|
|
w.Clock.Advance(TimeSpan.FromSeconds(31));
|
|
Assert.True((await w.Accounts.VraagInlogcodeAanAsync("jan@example.nl")).Succeeded, "nu een gewone inlogcode");
|
|
var inlogcode = w.LaatsteCodeUitMail("jan@example.nl");
|
|
|
|
var reclame = await w.Accounts.ControleerAdverteerderscodeAsync("jan@example.nl", inlogcode);
|
|
Assert.True(!reclame.Succeeded, "en met een inlogcode kom je niet in het adverteerdersgedeelte");
|
|
});
|
|
|
|
runner.TestAsync("Adverteren: de bedrijfsnaam reist mee met de code", async () =>
|
|
{
|
|
// Wie nieuw is vult zijn bedrijfsnaam in vóórdat hij de code krijgt. Die naam wordt
|
|
// bij de code bewaard en pas gebruikt zodra de code klopt - zo kan niemand zich een
|
|
// bedrijfsnaam aanmeten zonder eerst bij dat e-mailadres te kunnen.
|
|
var w = new World();
|
|
|
|
Assert.True((await w.Accounts.VraagAdverteerderscodeAanAsync(
|
|
"info@kwik-bit.nl", "Kwik-Bit", "Jonathan")).Succeeded, "de code gaat de deur uit");
|
|
|
|
var code = w.LaatsteCodeUitMail("info@kwik-bit.nl");
|
|
var controle = await w.Accounts.ControleerAdverteerderscodeAsync("info@kwik-bit.nl", code);
|
|
|
|
Assert.True(controle.Succeeded, $"de code klopt ({controle.Error})");
|
|
Assert.True(controle.Value!.Bedrijfsnaam == "Kwik-Bit", "met de bedrijfsnaam erbij");
|
|
Assert.True(controle.Value.Contactpersoon == "Jonathan", "en de contactpersoon");
|
|
|
|
var account = await w.Adverteren.MeldAanAsync(controle.Value.Bedrijfsnaam,
|
|
controle.Value.Contactpersoon, "info@kwik-bit.nl");
|
|
Assert.True(account.Succeeded, $"en het account wordt aangemaakt ({account.Error})");
|
|
|
|
// Dezelfde code werkt geen tweede keer.
|
|
var nogmaals = await w.Accounts.ControleerAdverteerderscodeAsync("info@kwik-bit.nl", code);
|
|
Assert.True(!nogmaals.Succeeded, "een code werkt maar één keer");
|
|
});
|
|
|
|
runner.Test("Adverteren: de betaallink klopt, ook op een Nederlandse server", () =>
|
|
{
|
|
var w = new World();
|
|
w.Adverteren.BunqMe = "https://bunq.me/KwikBit";
|
|
|
|
var vorige = CultureInfo.CurrentCulture;
|
|
try
|
|
{
|
|
// Precies de server waar dit op draait: een taalinstelling die 30,00 met een
|
|
// komma schrijft. In de link hoort een punt te staan - bunq leest in een komma
|
|
// een ander bedrag, of helemaal geen.
|
|
//
|
|
// Een zelfgemaakte instelling en niet new CultureInfo("nl-NL"): niet elke
|
|
// omgeving heeft de landgegevens bij zich (globalization-invariant mode), en dan
|
|
// valt deze controle om op iets wat er niet toe doet. Waar het om gaat is de
|
|
// komma, en die zetten we hier zelf.
|
|
var metKomma = (CultureInfo)CultureInfo.InvariantCulture.Clone();
|
|
metKomma.NumberFormat.NumberDecimalSeparator = ",";
|
|
CultureInfo.CurrentCulture = metKomma;
|
|
|
|
Assert.True((30.00m).ToString("0.00") == "30,00",
|
|
"de instelling van deze controle schrijft zelf wél een komma");
|
|
|
|
var link = w.Adverteren.Betaallink(3000, "MB-00012");
|
|
Assert.True(link == "https://bunq.me/KwikBit?amount=30.00&description=MB-00012",
|
|
$"bedrag met een punt en het kenmerk erin ({link})");
|
|
}
|
|
finally { CultureInfo.CurrentCulture = vorige; }
|
|
|
|
// Een afsluitende schuine streep in de instelling hoort geen dubbele op te leveren.
|
|
w.Adverteren.BunqMe = "https://bunq.me/KwikBit/";
|
|
var netjes = w.Adverteren.Betaallink(1000, "MB-00001");
|
|
Assert.True(netjes == "https://bunq.me/KwikBit?amount=10.00&description=MB-00001",
|
|
$"een streep te veel wordt opgeruimd ({netjes})");
|
|
|
|
// Op het scherm hoort juist een komma te staan; daar leest een mens het. Er stond
|
|
// "€ 10.00" op de adverteerderspagina, en dat is niet hoe je hier een bedrag schrijft.
|
|
Assert.True(AdvertentieService.Euro(3000) == "\u20ac 30,00",
|
|
$"een bedrag op het scherm heeft een komma ({AdvertentieService.Euro(3000)})");
|
|
Assert.True(AdvertentieService.Euro(1000) == "\u20ac 10,00", "ook een rond bedrag");
|
|
|
|
// Niets ingesteld: dan liever geen knop dan een knop naar de verkeerde rekening.
|
|
w.Adverteren.BunqMe = "";
|
|
Assert.True(w.Adverteren.Betaallink(1000, "MB-00001") is null,
|
|
"zonder ingesteld adres komt er geen betaallink");
|
|
});
|
|
|
|
runner.Test("Adverteren: de prijs is dagen maal vertoningen per uur maal tarief", () =>
|
|
{
|
|
var w = new World();
|
|
|
|
// Vaste getallen, zodat deze controle blijft kloppen als het tarief in de instellingen
|
|
// wijzigt. Dat is immers de bedoeling van dat tarief.
|
|
w.Adverteren.Tarieven.TariefCenten = 20;
|
|
w.Adverteren.Tarieven.MinimumCenten = 1000;
|
|
|
|
// Dertig dagen, vijf per uur: 150 eenheden, 3000 cent.
|
|
Assert.True(w.Adverteren.BerekenBedrag(30, 5) == 3000, "dertig dagen, vijf per uur is dertig euro");
|
|
|
|
// Begin- en einddag tellen allebei mee: van de 1e tot en met de 30e is dertig dagen.
|
|
var eerste = new DateOnly(2026, 10, 1);
|
|
Assert.True(w.Adverteren.BerekenBedrag(eerste, eerste.AddDays(29), 5) == 3000,
|
|
"met datums gerekend komt er hetzelfde uit");
|
|
Assert.True(w.Adverteren.BerekenBedrag(eerste, eerste, 1) == w.Adverteren.BerekenBedrag(1, 1),
|
|
"van de eerste tot en met de eerste is één dag");
|
|
|
|
// Onder de bodem betaal je de bodem: een kleine boeking kost aan kijken, afvinken en
|
|
// terugmailen meer dan hij opbrengt.
|
|
Assert.True(w.Adverteren.BerekenBedrag(1, 1) == 1000, "het kleinste bedrag is de bodem");
|
|
Assert.True(w.Adverteren.BerekenBedrag(7, 2) == 1000, "zeven dagen, twee per uur haalt de bodem niet");
|
|
|
|
Assert.True(w.Adverteren.BerekenBedrag(0, 5) == 0 && w.Adverteren.BerekenBedrag(30, 0) == 0,
|
|
"een campagne zonder dagen of zonder vertoningen kost niets");
|
|
});
|
|
|
|
runner.TestAsync("Adverteren: een campagne wordt pas getoond na betalen én goedkeuren", async () =>
|
|
{
|
|
var w = new World();
|
|
var beheerder = await w.MaakGebruikerAsync("beheer@example.nl", "Bea", "Beheer");
|
|
w.Store.Users.Single(u => u.Id == beheerder.Id).IsBeheerder = true;
|
|
|
|
var bedrijf = (await w.Adverteren.MeldAanAsync("Uitvaartzorg Noord", "Piet", "piet@uitvaart.nl")).Value!;
|
|
|
|
var vanaf = DateOnly.FromDateTime(w.Clock.UtcNow);
|
|
var tot = vanaf.AddDays(29);
|
|
|
|
var ingediend = await w.Adverteren.DienInAsync(bedrijf.Id, "Persoonlijk afscheid, dichtbij",
|
|
"https://uitvaartnoord.nl", vanaf, tot, 5);
|
|
Assert.True(ingediend.Succeeded, $"indienen lukt ({ingediend.Error})");
|
|
|
|
var campagne = ingediend.Value!;
|
|
Assert.True(campagne.Stand == CampagneStand.WachtOpBetaling, "hij wacht op de betaling");
|
|
Assert.True(campagne.BedragCenten == w.Adverteren.BerekenBedrag(30, 5), "met het berekende bedrag");
|
|
Assert.True(campagne.Betaalkenmerk is { Length: > 0 }, "en een kenmerk om de betaling mee terug te vinden");
|
|
|
|
// De beheerder hoort het te weten, want vanaf nu ligt het bij hem.
|
|
Assert.True(w.Email.Sent.Any(m => m.To == "beheer@example.nl"), "de beheerder krijgt bericht");
|
|
|
|
// Nog niets te zien: niet betaald, niet gekeurd.
|
|
Assert.True((await w.Adverteren.WachtOpBeheerderAsync()).Count == 1, "hij staat op de lijst van de beheerder");
|
|
Assert.True(!campagne.DraaitOp(vanaf), "en draait nog niet");
|
|
|
|
// Goedkeuren vóór betalen mag niet. Anders draait er iets waarvoor niet betaald is,
|
|
// en dat merk je pas als het voorbij is.
|
|
var tevroeg = await w.Adverteren.GoedkeurenAsync(campagne.Id, beheerder.Id);
|
|
Assert.True(!tevroeg.Succeeded, "goedkeuren kan niet zolang er niet betaald is");
|
|
|
|
Assert.True((await w.Adverteren.BetaaldAsync(campagne.Id, beheerder.Id)).Succeeded, "betaling afvinken");
|
|
Assert.True(campagne.Stand == CampagneStand.WachtOpGoedkeuring, "nu wacht hij op de inhoudelijke blik");
|
|
Assert.True(!campagne.DraaitOp(vanaf), "betaald is nog niet goedgekeurd");
|
|
|
|
Assert.True((await w.Adverteren.GoedkeurenAsync(campagne.Id, beheerder.Id)).Succeeded, "goedkeuren");
|
|
Assert.True(campagne.Stand == CampagneStand.Loopt, "hij loopt");
|
|
Assert.True(campagne.DraaitOp(vanaf) && campagne.DraaitOp(tot), "op de eerste en de laatste dag");
|
|
Assert.True(!campagne.DraaitOp(tot.AddDays(1)), "en een dag later niet meer");
|
|
|
|
Assert.True((await w.Adverteren.WachtOpBeheerderAsync()).Count == 0, "er wacht niets meer");
|
|
Assert.True(w.Email.Sent.Any(m => m.To == "piet@uitvaart.nl"), "de adverteerder hoort dat hij is goedgekeurd");
|
|
});
|
|
|
|
runner.TestAsync("Adverteren: wie het verst achterloopt is aan de beurt", async () =>
|
|
{
|
|
// Twee campagnes tegelijk, met een verschillend aantal per uur. Wie meer boekt hoort
|
|
// meer te krijgen, zonder dat de een de ander verdringt en zonder dat iemand over
|
|
// zijn ingekochte aantal heen gaat.
|
|
var w = new World();
|
|
var beheerder = await w.MaakGebruikerAsync("beheer@example.nl", "Bea", "Beheer");
|
|
w.Store.Users.Single(u => u.Id == beheerder.Id).IsBeheerder = true;
|
|
|
|
var groot = await LopendAsync(w, beheerder, "Groot BV", "groot@example.nl", perUur: 3);
|
|
var klein = await LopendAsync(w, beheerder, "Klein BV", "klein@example.nl", perUur: 1);
|
|
|
|
// Vier keer kiezen binnen hetzelfde klokuur. Groot heeft er drie gekocht, Klein één,
|
|
// dus de verdeling hoort 3 om 1 te zijn.
|
|
var nu = w.Clock.UtcNow;
|
|
var beurten = new List<long>();
|
|
for (var i = 0; i < 4; i++)
|
|
{
|
|
var gekozen = await w.Adverteren.KiesAsync(nu);
|
|
Assert.True(gekozen is not null, $"er is een advertentie bij beurt {i + 1}");
|
|
beurten.Add(gekozen!.Id);
|
|
await w.Adverteren.TelVertoningAsync(gekozen.Id, nu);
|
|
}
|
|
|
|
Assert.True(beurten.Count(id => id == groot.Id) == 3, "Groot komt drie keer aan bod");
|
|
Assert.True(beurten.Count(id => id == klein.Id) == 1, "Klein één keer");
|
|
|
|
// Allebei op hun aantal: dan verschijnt er niets. Geen opvulling, geen herhaling.
|
|
Assert.True(await w.Adverteren.KiesAsync(nu) is null, "daarna is het uur op");
|
|
|
|
// Het volgende klokuur begint het opnieuw.
|
|
var volgendUur = nu.AddHours(1);
|
|
Assert.True(await w.Adverteren.KiesAsync(volgendUur) is not null, "een uur later mag het weer");
|
|
|
|
// En wat er werkelijk is getoond staat klaar voor de adverteerder.
|
|
var totalen = await w.Adverteren.TotalenAsync(groot.Id);
|
|
Assert.True(totalen.Vertoningen == 3 && totalen.Kliks == 0, "de tellers kloppen");
|
|
|
|
await w.Adverteren.TelKlikAsync(groot.Id, nu);
|
|
Assert.True((await w.Adverteren.TotalenAsync(groot.Id)).Kliks == 1, "en een klik telt apart");
|
|
});
|
|
|
|
runner.TestAsync("Adverteren: buiten de periode of zonder goedkeuring komt er niets", async () =>
|
|
{
|
|
var w = new World();
|
|
var beheerder = await w.MaakGebruikerAsync("beheer@example.nl", "Bea", "Beheer");
|
|
w.Store.Users.Single(u => u.Id == beheerder.Id).IsBeheerder = true;
|
|
|
|
var nu = w.Clock.UtcNow;
|
|
var vandaag = DateOnly.FromDateTime(nu);
|
|
|
|
// Betaald en goedgekeurd, maar hij begint pas morgen.
|
|
var straks = (await w.Adverteren.MeldAanAsync("Later BV", "L", "later@example.nl")).Value!;
|
|
var morgen = (await w.Adverteren.DienInAsync(straks.Id, "Binnenkort", "https://later.nl",
|
|
vandaag.AddDays(1), vandaag.AddDays(8), 5)).Value!;
|
|
await w.Adverteren.BetaaldAsync(morgen.Id, beheerder.Id);
|
|
await w.Adverteren.GoedkeurenAsync(morgen.Id, beheerder.Id);
|
|
|
|
Assert.True(await w.Adverteren.KiesAsync(nu) is null, "een campagne die morgen begint draait vandaag niet");
|
|
Assert.True(await w.Adverteren.KiesAsync(nu.AddDays(1)) is not null, "morgen wel");
|
|
|
|
// Betaald maar niet goedgekeurd: dat hoort niets te tonen. Dit is de belangrijkste
|
|
// van allemaal - hier zou een niet-gekeurde advertentie langs de beheerder glippen.
|
|
var wacht = (await w.Adverteren.MeldAanAsync("Wacht BV", "W", "wacht@example.nl")).Value!;
|
|
var ongekeurd = (await w.Adverteren.DienInAsync(wacht.Id, "Nog niet gezien", "https://wacht.nl",
|
|
vandaag, vandaag.AddDays(8), 5)).Value!;
|
|
await w.Adverteren.BetaaldAsync(ongekeurd.Id, beheerder.Id);
|
|
|
|
Assert.True(await w.Adverteren.KiesAsync(nu) is null, "betaald zonder goedkeuring komt er niet in");
|
|
|
|
// En een stopgezette campagne verdwijnt meteen.
|
|
await w.Adverteren.GoedkeurenAsync(ongekeurd.Id, beheerder.Id);
|
|
Assert.True(await w.Adverteren.KiesAsync(nu) is not null, "na goedkeuren wel");
|
|
|
|
await w.Adverteren.StopzettenAsync(ongekeurd.Id, beheerder.Id);
|
|
Assert.True(await w.Adverteren.KiesAsync(nu) is null, "stopzetten haalt hem er meteen uit");
|
|
});
|
|
|
|
runner.TestAsync("Adverteren: alleen een beheerder mag afvinken en goedkeuren", async () =>
|
|
{
|
|
var w = new World();
|
|
var gewoon = await w.MaakGebruikerAsync("jan@example.nl", "Jan", "Jansen");
|
|
var bedrijf = (await w.Adverteren.MeldAanAsync("Bloemen Zo", "Riet", "riet@bloemen.nl")).Value!;
|
|
|
|
var vanaf = DateOnly.FromDateTime(w.Clock.UtcNow);
|
|
var campagne = (await w.Adverteren.DienInAsync(bedrijf.Id, "Rouwbloemen",
|
|
"https://bloemenzo.nl", vanaf, vanaf.AddDays(6), 2)).Value!;
|
|
|
|
var betalen = await w.Adverteren.BetaaldAsync(campagne.Id, gewoon.Id);
|
|
Assert.True(!betalen.Succeeded, "een gewone gebruiker vinkt niets af");
|
|
|
|
// Dezelfde melding als "bestaat niet": wie geen beheerder is, hoort niet te weten dat
|
|
// hier iets te beheren valt.
|
|
Assert.True(betalen.Error == "Niet gevonden.", "en krijgt niet te horen dat het aan zijn rechten ligt");
|
|
|
|
Assert.True(!(await w.Adverteren.GoedkeurenAsync(campagne.Id, gewoon.Id)).Succeeded, "en keurt niets goed");
|
|
Assert.True(campagne.Stand == CampagneStand.WachtOpBetaling, "er is niets veranderd");
|
|
});
|
|
|
|
runner.TestAsync("Adverteren: afwijzen kan niet zonder reden", async () =>
|
|
{
|
|
var w = new World();
|
|
var beheerder = await w.MaakGebruikerAsync("beheer@example.nl", "Bea", "Beheer");
|
|
w.Store.Users.Single(u => u.Id == beheerder.Id).IsBeheerder = true;
|
|
|
|
var bedrijf = (await w.Adverteren.MeldAanAsync("Iets Anders", "Kees", "kees@anders.nl")).Value!;
|
|
var vanaf = DateOnly.FromDateTime(w.Clock.UtcNow);
|
|
var campagne = (await w.Adverteren.DienInAsync(bedrijf.Id, "Koop nu",
|
|
"https://anders.nl", vanaf, vanaf.AddDays(6), 2)).Value!;
|
|
|
|
Assert.True(!(await w.Adverteren.AfwijzenAsync(campagne.Id, beheerder.Id, " ")).Succeeded,
|
|
"zonder reden wijs je niets af");
|
|
|
|
Assert.True((await w.Adverteren.AfwijzenAsync(campagne.Id, beheerder.Id, "Past niet bij dit product")).Succeeded,
|
|
"met een reden wel");
|
|
Assert.True(campagne.Stand == CampagneStand.Afgewezen, "hij staat als afgewezen");
|
|
Assert.True(campagne.AfwijzingReden == "Past niet bij dit product", "met de reden erbij");
|
|
|
|
// De adverteerder hoort te weten waaróm. Anders dient hij hetzelfde nog een keer in.
|
|
var bericht = w.Email.Sent.Last(m => m.To == "kees@anders.nl");
|
|
Assert.True(bericht.Body.Contains("Past niet bij dit product"), "en krijgt die reden te lezen");
|
|
});
|
|
|
|
runner.TestAsync("Adverteren: wat er niet in mag", async () =>
|
|
{
|
|
var w = new World();
|
|
var bedrijf = (await w.Adverteren.MeldAanAsync("Test BV", "Tester", "test@example.nl")).Value!;
|
|
var vanaf = new DateOnly(2026, 10, 1);
|
|
|
|
// Een link die code uitvoert bij de lezer. Dit is de reden dat er op het schema wordt
|
|
// gecontroleerd en niet alleen op "is het gevuld".
|
|
var script = await w.Adverteren.DienInAsync(bedrijf.Id, "Klik hier",
|
|
"javascript:alert(1)", vanaf, vanaf.AddDays(6), 2);
|
|
Assert.True(!script.Succeeded, "een javascript-link komt er niet in");
|
|
|
|
var geenAdres = await w.Adverteren.DienInAsync(bedrijf.Id, "Klik hier",
|
|
"www.voorbeeld.nl", vanaf, vanaf.AddDays(6), 2);
|
|
Assert.True(!geenAdres.Succeeded, "en een adres zonder http:// ook niet");
|
|
|
|
var omgekeerd = await w.Adverteren.DienInAsync(bedrijf.Id, "Klik hier",
|
|
"https://voorbeeld.nl", vanaf.AddDays(6), vanaf, 2);
|
|
Assert.True(!omgekeerd.Succeeded, "de einddatum mag niet voor de begindatum liggen");
|
|
|
|
var teveel = await w.Adverteren.DienInAsync(bedrijf.Id, "Klik hier",
|
|
"https://voorbeeld.nl", vanaf, vanaf.AddDays(6), w.Adverteren.Tarieven.MaxPerUur + 1);
|
|
Assert.True(!teveel.Succeeded, "en niemand koopt alle ruimte van een uur op");
|
|
|
|
Assert.True(w.Store.Campagnes.Count == 0, "er is niets van dit alles opgeslagen");
|
|
|
|
// Hetzelfde e-mailadres kan geen tweede account: daarmee logt hij in.
|
|
var nogeens = await w.Adverteren.MeldAanAsync("Test BV 2", "Tester", "test@example.nl");
|
|
Assert.True(!nogeens.Succeeded, "eenzelfde e-mailadres kan geen tweede account");
|
|
});
|
|
}
|
|
|
|
/// <summary>Een adverteerder met één campagne die vanaf vandaag draait: betaald en goedgekeurd.</summary>
|
|
private static async Task<MijnBesluit.Domain.Entities.Campagne> LopendAsync(
|
|
World w, MijnBesluit.Domain.Entities.User beheerder, string bedrijf, string email, int perUur)
|
|
{
|
|
var adverteerder = (await w.Adverteren.MeldAanAsync(bedrijf, "Contact", email)).Value!;
|
|
var vandaag = DateOnly.FromDateTime(w.Clock.UtcNow);
|
|
|
|
var campagne = (await w.Adverteren.DienInAsync(adverteerder.Id, $"Advertentie van {bedrijf}",
|
|
"https://voorbeeld.nl", vandaag, vandaag.AddDays(30), perUur)).Value!;
|
|
|
|
await w.Adverteren.BetaaldAsync(campagne.Id, beheerder.Id);
|
|
await w.Adverteren.GoedkeurenAsync(campagne.Id, beheerder.Id);
|
|
return campagne;
|
|
}
|
|
}
|