start numbers implementation
This commit is contained in:
@@ -9,10 +9,10 @@ public partial class RavenOperations : IRavenOperations
|
||||
public virtual Task<Result<T>> Create<T>(T model, Func<T, Task<ValidationResult>> validate = null, Action<IZeroDocumentSession> onAfterStore = null) where T : ZeroIdEntity, new() => Save(model, validate, onAfterStore);
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual Task<Result<T>> Update<T>(T model, Func<T, Task<ValidationResult>> validate = null, Action<IZeroDocumentSession> onAfterStore = null) where T : ZeroIdEntity, new() => Save(model, validate, onAfterStore);
|
||||
public virtual Task<Result<T>> Update<T>(T model, Func<T, Task<ValidationResult>> validate = null, Action<IZeroDocumentSession> onAfterStore = null) where T : ZeroIdEntity, new() => Save(model, validate, onAfterStore, true);
|
||||
|
||||
/// <inheritdoc />
|
||||
protected virtual async Task<Result<T>> Save<T>(T model, Func<T, Task<ValidationResult>> validate = null, Action<IZeroDocumentSession> onAfterStore = null) where T : ZeroIdEntity, new()
|
||||
protected virtual async Task<Result<T>> Save<T>(T model, Func<T, Task<ValidationResult>> validate = null, Action<IZeroDocumentSession> onAfterStore = null, bool update = false) where T : ZeroIdEntity, new()
|
||||
{
|
||||
if (model == null)
|
||||
{
|
||||
@@ -30,10 +30,14 @@ public partial class RavenOperations : IRavenOperations
|
||||
previousModel = await session.LoadAsync<T>(model.Id);
|
||||
}
|
||||
|
||||
if (previousModel == null)
|
||||
if (update && previousModel == null)
|
||||
{
|
||||
return Result<T>.Fail("@errors.onsave.noidmatch");
|
||||
}
|
||||
else if (!update && previousModel != null)
|
||||
{
|
||||
return Result<T>.Fail("@errors.oncreate.idmismatch");
|
||||
}
|
||||
|
||||
isUpdate = true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace zero.Numbers;
|
||||
|
||||
public interface IZeroNumberStoreDbProvider
|
||||
{
|
||||
Task<T> Load<T>(string id, CancellationToken ct = default) where T : ZeroEntity, new();
|
||||
|
||||
Task<T> Find<T>(Expression<Func<T, bool>> expression, CancellationToken ct = default) where T : ZeroEntity;
|
||||
|
||||
Task<IList<T>> FindAll<T>(Expression<Func<T, bool>> expression, CancellationToken ct = default) where T : ZeroEntity;
|
||||
|
||||
Task<Result<T>> Create<T>(T model, CancellationToken ct = default) where T : ZeroEntity, new();
|
||||
|
||||
Task<Result<T>> Update<T>(T model, CancellationToken ct = default) where T : ZeroEntity, new();
|
||||
|
||||
Task<Result<T>> Delete<T>(T model, CancellationToken ct = default) where T : ZeroEntity, new();
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
namespace zero.Numbers;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration of number generation
|
||||
/// Template can use the following placeholders:
|
||||
/// {number} for the current number
|
||||
/// {random} for a random number (default length is 5)
|
||||
/// {random:7} for a random number with the specified length
|
||||
/// {year} for the date in yyyy
|
||||
/// {date} for the date in dd-mm-yyyy
|
||||
/// {date:dmy} for the date in a custom format, where dmy is a nested placeholder for the format
|
||||
/// </summary>
|
||||
public class Number : ZeroEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// The template to build the number.
|
||||
/// The number will reset to StartNumber once a new year is reached.
|
||||
/// This is only applicable if the template contains the placeholder {year}.
|
||||
/// Example: {year}-{number} will result in 2020-01, 2020-02, ... 2021-01, 2021-02, ...
|
||||
/// </summary>
|
||||
public string Template { get; set; } = "{number}";
|
||||
|
||||
/// <summary>
|
||||
/// Where to start, lol
|
||||
/// </summary>
|
||||
public long StartNumber { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum length of the number (append 0's when it is too short)
|
||||
/// </summary>
|
||||
public int MinLength { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Store current counters
|
||||
/// </summary>
|
||||
public List<NumberCounter> Counters { get; set; } = new();
|
||||
}
|
||||
|
||||
|
||||
public class NumberCounter
|
||||
{
|
||||
public string Key { get; set; }
|
||||
|
||||
public string Value { get; set; }
|
||||
|
||||
public long Count { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using Raven.Client.Documents;
|
||||
|
||||
namespace zero.Commerce.Numbers;
|
||||
|
||||
public class NumberInterceptor : Interceptor<Number>
|
||||
{
|
||||
protected IZeroStore Store { get; private set; }
|
||||
|
||||
|
||||
public NumberInterceptor(IZeroStore store)
|
||||
{
|
||||
Store = store;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<InterceptorResult<Number>> Creating(InterceptorParameters args, Number model) => Saving(args, model, false);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<InterceptorResult<Number>> Updating(InterceptorParameters args, Number model) => Saving(args, model, true);
|
||||
|
||||
|
||||
protected async Task<InterceptorResult<Number>> Saving(InterceptorParameters args, Number model, bool update)
|
||||
{
|
||||
string flavor = model.Flavor;
|
||||
string existingId = await args.Operations.Session.Query<Number>().Where(x => x.Flavor == flavor).Select(x => x.Id).FirstOrDefaultAsync();
|
||||
|
||||
if (!existingId.IsNullOrEmpty() && existingId != model.Id)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Result = Result<Number>.Fail("@shop.errors.number.multiplenotallowed")
|
||||
};
|
||||
}
|
||||
|
||||
model.Alias = flavor;
|
||||
model.IsActive = true;
|
||||
model.Name = null;
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace zero.Numbers;
|
||||
|
||||
public class NumberOptions : List<NumberOptionsItem>
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public class NumberOptionsItem
|
||||
{
|
||||
public string Alias { get; set; }
|
||||
|
||||
public string Template { get; set; } = "{number}";
|
||||
|
||||
public long StartNumber { get; set; } = 1;
|
||||
|
||||
public int MinLength { get; set; } = 1;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using zero.Numbers;
|
||||
|
||||
namespace zero.Numbers;
|
||||
|
||||
public class NumberService : INumberService
|
||||
{
|
||||
protected IZeroNumberStoreDbProvider Db { get; set; }
|
||||
|
||||
const string DEFAULT_COUNTER = "Default";
|
||||
Regex templateRegex = new("{(date|number|year|random):?([a-zA-Z0-9-_]*)}", RegexOptions.IgnoreCase);
|
||||
Regex containsYearRegex = new("{(date|year|date:([^}]*y+[^}]*))}", RegexOptions.IgnoreCase);
|
||||
Random random = new();
|
||||
|
||||
|
||||
public NumberService(IZeroNumberStoreDbProvider db, IZeroOptions options)
|
||||
{
|
||||
Db = db;
|
||||
//RegisteredTypes = options.For<FlavorOptions>().GetAll<Number>().Select(x => x as NumberType);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public interface INumberService
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the next available number for the specified template
|
||||
/// </summary>
|
||||
Task<string> Next(string alias, DateTimeOffset? date = null, bool store = true);
|
||||
|
||||
/// <summary>
|
||||
/// Resets the current counter for the specified template
|
||||
/// </summary>
|
||||
Task Reset(string alias);
|
||||
|
||||
/// <summary>
|
||||
/// Resets all counters for the specified template
|
||||
/// </summary>
|
||||
Task ResetAll(string alias);
|
||||
|
||||
/// <summary>
|
||||
/// Renders the final output from the template and the value
|
||||
/// </summary>
|
||||
string Compile(string alias, long value, DateTimeOffset? date = null);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace zero.Numbers;
|
||||
|
||||
public class NumberValidator : ZeroValidator<Number>
|
||||
{
|
||||
public NumberValidator()
|
||||
{
|
||||
//if (ctx.Context == null)
|
||||
//{
|
||||
// throw new ArgumentNullException("ctx.Context", "NumberValidator requires access to ");
|
||||
//}
|
||||
|
||||
RuleFor(x => x.Template).NotEmpty().Length(2, 60);
|
||||
RuleFor(x => x.Template).Must(x => x != null && x.Contains("{number}")).WithMessage("@shop.number.errors.invalid_template");
|
||||
RuleFor(x => x.StartNumber).GreaterThanOrEqualTo(0);
|
||||
RuleFor(x => x.MinLength).ExclusiveBetween(1, 32);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace zero.Commerce.Numbers;
|
||||
|
||||
internal class ZeroCommerceNumberModule : ZeroModule
|
||||
{
|
||||
public override void ConfigureServices(IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddScoped<INumberStore, NumberStore>();
|
||||
services.AddScoped<INumberService, NumberService>();
|
||||
services.AddSingleton<IPermissionProvider, NumberPermissions>();
|
||||
services.AddScoped<IInterceptor, NumberInterceptor>();
|
||||
services.AddScoped<IValidator<Number>, NumberValidator>();
|
||||
|
||||
services.Configure<FlavorOptions>(opts =>
|
||||
{
|
||||
opts.Configure<Number>(cfg =>
|
||||
{
|
||||
cfg.CanUseWithoutFlavors = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace zero.Numbers;
|
||||
|
||||
internal class ZeroNumberModule : ZeroModule
|
||||
{
|
||||
public override void ConfigureServices(IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddOptions<NumberOptions>().Bind(configuration.GetSection("Zero:Numbers"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,379 @@
|
||||
using Raven.Client.Documents;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace zero.Commerce.Numbers;
|
||||
|
||||
public class _NumberService : I_NumberService
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<NumberType> RegisteredTypes { get; private set; }
|
||||
|
||||
protected INumberStore Store { get; set; }
|
||||
|
||||
const string DEFAULT_COUNTER = "Default";
|
||||
Regex templateRegex = new("{(date|number|year|random):?([a-zA-Z0-9-_]*)}", RegexOptions.IgnoreCase);
|
||||
Regex containsYearRegex = new("{(date|year|date:([^}]*y+[^}]*))}", RegexOptions.IgnoreCase);
|
||||
Random random = new();
|
||||
|
||||
|
||||
public _NumberService(INumberStore store, IZeroOptions options)
|
||||
{
|
||||
Store = store;
|
||||
RegisteredTypes = options.For<FlavorOptions>().GetAll<Number>().Select(x => x as NumberType);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<Number> Get(string alias)
|
||||
{
|
||||
NumberType type = RegisteredTypes.FirstOrDefault(x => x.Alias.Equals(alias, StringComparison.InvariantCultureIgnoreCase));
|
||||
return await Load(type, () => type.Construct(type) as Number);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<T> Get<T>(string alias = null) where T : Number, new()
|
||||
{
|
||||
NumberType type = RegisteredTypes.FirstOrDefault(x => x.FlavorType == typeof(T) && (alias.IsNullOrEmpty() || x.Alias.Equals(alias, StringComparison.InvariantCultureIgnoreCase)));
|
||||
return await Load<T>(type);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<Paged<NumberCounter>> GetCounters(string alias) => await GetCounters(await Get(alias));
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<Paged<NumberCounter>> GetCounters<T>(string alias = null) where T : Number, new() => await GetCounters(await Get<T>(alias));
|
||||
|
||||
|
||||
protected async Task<Paged<NumberCounter>> GetCounters(Number number)
|
||||
{
|
||||
if (number == null || number.Hash.IsNullOrEmpty())
|
||||
{
|
||||
return new(0, 1, 1);
|
||||
}
|
||||
|
||||
List<NumberCounter> items = new();
|
||||
Dictionary<string, long?> counters = await Store.Session.CountersFor(number).GetAllAsync();
|
||||
|
||||
foreach (var counter in counters)
|
||||
{
|
||||
long value = counter.Value ?? 0;
|
||||
|
||||
int.TryParse(counter.Key, out int year);
|
||||
bool isPassed = year > 0 && year < DateTimeOffset.Now.Year;
|
||||
|
||||
items.Add(new()
|
||||
{
|
||||
Count = value,
|
||||
Key = counter.Key,
|
||||
Value = isPassed ? "@shop.number.fields.counterlist.yearpassed" : Compile(number, value + 1)
|
||||
});
|
||||
}
|
||||
|
||||
return new(items, items.Count, 1, items.Count);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<Paged<Number>> GetAll(ListQuery<Number> query = default)
|
||||
{
|
||||
query ??= new();
|
||||
|
||||
List<Number> result = new();
|
||||
List<Number> numbers = await Store.LoadAll();
|
||||
|
||||
foreach (NumberType type in RegisteredTypes)
|
||||
{
|
||||
Number number = numbers.FirstOrDefault(x => x.Flavor == type.Alias);
|
||||
|
||||
if (number == null)
|
||||
{
|
||||
number = type.Construct(type) as Number;
|
||||
number.Flavor = type.Alias;
|
||||
}
|
||||
|
||||
number.Name = type.Name;
|
||||
number.IsActive = true;
|
||||
|
||||
result.Add(number);
|
||||
}
|
||||
|
||||
return new(result.Paging(query.Page, query.PageSize).ToList(), result.Count, query.Page, query.PageSize);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<string> Next<T>(string alias = null, DateTimeOffset? date = null) where T : Number, new()
|
||||
{
|
||||
T preset = await Get<T>(alias);
|
||||
|
||||
if (!IsStored(preset))
|
||||
{
|
||||
NumberType type = RegisteredTypes.FirstOrDefault(x => x.FlavorType == typeof(T) && (alias.IsNullOrEmpty() || x.Alias.Equals(alias, StringComparison.InvariantCultureIgnoreCase)));
|
||||
preset = await Create<T>(type);
|
||||
}
|
||||
|
||||
string counterName = GetCounterName(preset);
|
||||
|
||||
// get a matching counter
|
||||
var counters = Store.Session.CountersFor(preset);
|
||||
long? storedCount = await counters.GetAsync(counterName);
|
||||
|
||||
// calculate new value
|
||||
bool hasValue = storedCount.HasValue;
|
||||
long oldValue = hasValue ? storedCount.Value : preset.StartNumber;
|
||||
long incrementBy = hasValue ? 1 : 0;
|
||||
long newValue = oldValue + incrementBy;
|
||||
|
||||
// increment the value and store it in database
|
||||
counters.Increment(counterName, hasValue ? incrementBy : newValue);
|
||||
await Store.Session.SaveChangesAsync();
|
||||
|
||||
// compiles the template and returns the rendered result
|
||||
return Compile(preset, newValue, date);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Reset(string alias) => await Reset(await Get(alias));
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Reset<T>(string alias = null) where T : Number, new() => await Reset(await Get<T>(alias));
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
protected async Task Reset(Number preset)
|
||||
{
|
||||
if (!IsStored(preset))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string counterName = GetCounterName(preset);
|
||||
|
||||
// get a matching counter
|
||||
var counters = Store.Session.CountersFor(preset);
|
||||
|
||||
// delete the counter
|
||||
counters.Delete(counterName);
|
||||
await Store.Session.SaveChangesAsync();
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<Result<Number>> Update(Number preset)
|
||||
{
|
||||
return IsStored(preset) ? await Store.Update(preset) : await Store.Create(preset);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Delete(string alias)
|
||||
{
|
||||
Number preset = await Get(alias);
|
||||
|
||||
if (IsStored(preset))
|
||||
{
|
||||
await Store.Delete(preset);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Delete<T>(string alias = null) where T : Number, new()
|
||||
{
|
||||
T preset = await Get<T>(alias);
|
||||
|
||||
if (IsStored(preset))
|
||||
{
|
||||
await Store.Delete(preset);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Compile<T>(T number, long value, DateTimeOffset? date = null) where T : Number
|
||||
{
|
||||
string output = number.Template;
|
||||
MatchCollection matches = templateRegex.Matches(output);
|
||||
DateTimeOffset usedDate = date ?? DateTimeOffset.Now;
|
||||
|
||||
foreach (Match match in matches)
|
||||
{
|
||||
if (!match.Success)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string original = match.Value;
|
||||
string type = match.Groups[1].Value;
|
||||
string options = match.Groups[2].Value;
|
||||
string result = String.Empty;
|
||||
|
||||
if (type == "year")
|
||||
{
|
||||
result = usedDate.Year.ToString();
|
||||
}
|
||||
else if (type == "date")
|
||||
{
|
||||
result = usedDate.ToString(options.IsNullOrWhiteSpace() ? "dd-MM-yyyy" : options);
|
||||
}
|
||||
else if (type == "number")
|
||||
{
|
||||
int minLength = number.MinLength > 0 && number.MinLength <= 16 ? number.MinLength : 1;
|
||||
result = value.ToString("D" + minLength);
|
||||
}
|
||||
else if (type == "random")
|
||||
{
|
||||
int length = 5;
|
||||
if (!options.IsNullOrWhiteSpace() && Int32.TryParse(options, out int oLength) && oLength > 0 && oLength <= 10)
|
||||
{
|
||||
length = oLength;
|
||||
}
|
||||
result = random.Next(0, Int32.MaxValue).ToString("D10").Substring(0, length);
|
||||
}
|
||||
|
||||
output = output.ReplaceFirst(original, result);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get integration data from database
|
||||
/// </summary>
|
||||
protected async Task<T> Load<T>(NumberType type, Func<T> createInstance = null) where T : Number, new()
|
||||
{
|
||||
if (type == null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
T entity = await Store.Session.Query<T>().FirstOrDefaultAsync(x => x.Flavor == type.Alias && x.IsActive);
|
||||
|
||||
if (entity == null)
|
||||
{
|
||||
entity = createInstance != null ? createInstance() : new T();
|
||||
}
|
||||
|
||||
entity.Name = type.Name;
|
||||
entity.IsActive = true;
|
||||
entity.Flavor = type.Alias;
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Saves a new number to the database
|
||||
/// </summary>
|
||||
protected async Task<T> Create<T>(NumberType type, Action<T> modify = null) where T : Number, new()
|
||||
{
|
||||
T entity = new();
|
||||
entity.Flavor = type.Alias;
|
||||
entity.Name = null;
|
||||
entity.IsActive = true;
|
||||
|
||||
modify?.Invoke(entity);
|
||||
|
||||
Result<Number> result = await Store.Create(entity);
|
||||
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
throw new Exception("Could not store number in database"); // TODO add details for logging
|
||||
}
|
||||
|
||||
return (T)result.Model;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get the name for the current counter
|
||||
/// </summary>
|
||||
protected virtual string GetCounterName<T>(T number) where T : Number
|
||||
{
|
||||
if (!number.ResetNumberPerYear || !containsYearRegex.IsMatch(number.Template))
|
||||
{
|
||||
return DEFAULT_COUNTER;
|
||||
}
|
||||
|
||||
return DateTimeOffset.Now.Year.ToString();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Determines if this number comes from the database
|
||||
/// </summary>
|
||||
protected virtual bool IsStored<T>(T number) where T : Number
|
||||
{
|
||||
return number != null && !number.Hash.IsNullOrEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public interface I_NumberService
|
||||
{
|
||||
/// <summary>
|
||||
/// Get a number config by an alias
|
||||
/// </summary>
|
||||
Task<Number> Get(string alias);
|
||||
|
||||
/// <summary>
|
||||
/// Get the number config of a certain type with an optional alias
|
||||
/// </summary>
|
||||
Task<T> Get<T>(string alias = null) where T : Number, new();
|
||||
|
||||
/// <summary>
|
||||
/// Get all counters for a number
|
||||
/// </summary>
|
||||
Task<Paged<NumberCounter>> GetCounters(string alias);
|
||||
|
||||
/// <summary>
|
||||
/// Get all counters for a number
|
||||
/// </summary>
|
||||
Task<Paged<NumberCounter>> GetCounters<T>(string alias = null) where T : Number, new();
|
||||
|
||||
/// <summary>
|
||||
/// Get all numbers
|
||||
/// </summary>
|
||||
Task<Paged<Number>> GetAll(ListQuery<Number> query = default);
|
||||
|
||||
/// <summary>
|
||||
/// Get the next available number for the specified template
|
||||
/// </summary>
|
||||
Task<string> Next<T>(string alias = null, DateTimeOffset? date = null) where T : Number, new();
|
||||
|
||||
/// <summary>
|
||||
/// Resets the current counter for the specified template
|
||||
/// </summary>
|
||||
Task Reset(string alias);
|
||||
|
||||
/// <summary>
|
||||
/// Resets the current counter for the specified template
|
||||
/// </summary>
|
||||
Task Reset<T>(string alias = null) where T : Number, new();
|
||||
|
||||
/// <summary>
|
||||
/// Updates number properties
|
||||
/// </summary>
|
||||
Task<Result<Number>> Update(Number preset);
|
||||
|
||||
/// <summary>
|
||||
/// Resets the whole number configuration including changes and all attached counters
|
||||
/// </summary>
|
||||
Task Delete(string alias);
|
||||
|
||||
/// <summary>
|
||||
/// Resets the whole number configuration including changes and all attached counters
|
||||
/// </summary>
|
||||
Task Delete<T>(string alias = null) where T : Number, new();
|
||||
|
||||
/// <summary>
|
||||
/// Renders the final output from the template and the value
|
||||
/// </summary>
|
||||
string Compile<T>(T number, long value, DateTimeOffset? date = null) where T : Number;
|
||||
}
|
||||
Reference in New Issue
Block a user