using Microsoft.Data.Sqlite;
using OpenNest.Data;
namespace OpenNest.Server;
// The ledger item codes each nest's current archive is coded with, kept beside the record at
// every archive write so listing the codes in use never reopens an archive.
public sealed partial class NestDatabase
{
// v14: one row per distinct code of a nest's current archive.
private const string StockCodeSchema = """
CREATE TABLE IF NOT EXISTS nest_stock_codes (
id TEXT NOT NULL,
code TEXT NOT NULL,
PRIMARY KEY (id, code)
);
CREATE INDEX IF NOT EXISTS ix_nest_stock_codes_code ON nest_stock_codes(code);
""";
///
/// The item codes on the current archives of every non-deleted Draft, Quote or To Be Cut
/// nest except , ordered by code then nest name.
///
public IReadOnlyList StockCodesInUse(Guid? excludeId)
{
lock (_sync)
{
using var command = _connection.CreateCommand();
command.CommandText = """
SELECT c.code, n.id, n.name, n.status FROM nest_stock_codes c
JOIN nests n ON n.id = c.id
WHERE n.deleted = 0 AND n.status IN ('Draft', 'Quote', 'ToBeCut') AND n.id <> $exclude
ORDER BY c.code, n.name COLLATE NOCASE, n.id
""";
command.Parameters.AddWithValue("$exclude", (excludeId ?? Guid.Empty).ToString());
var uses = new List();
using var reader = command.ExecuteReader();
while (reader.Read())
{
uses.Add(new NestStockCodeUse
{
ItemCode = reader.GetString(0),
NestId = Guid.Parse(reader.GetString(1)),
NestName = reader.GetString(2),
Status = Enum.TryParse(reader.GetString(3), true, out var status) ? status : NestStatus.Quote,
});
}
return uses;
}
}
// Called only under _sync with the caller's transaction, whenever a nest's current archive changes.
private void WriteStockCodes(Guid id, byte[] file, SqliteTransaction transaction)
{
using var command = _connection.CreateCommand();
command.Transaction = transaction;
command.CommandText = "DELETE FROM nest_stock_codes WHERE id = $id";
command.Parameters.AddWithValue("$id", id.ToString());
command.ExecuteNonQuery();
InsertStockCodes(command, id, NestArchive.ReadStockItemCodes(file));
}
private static void InsertStockCodes(SqliteCommand command, Guid id, IEnumerable codes)
{
command.CommandText = "INSERT OR IGNORE INTO nest_stock_codes (id, code) VALUES ($id, $code)";
command.Parameters.Clear();
command.Parameters.AddWithValue("$id", id.ToString());
var code = command.Parameters.Add("$code", SqliteType.Text);
foreach (var value in codes)
{
code.Value = value;
command.ExecuteNonQuery();
}
command.Parameters.Clear();
}
// v14: reads each current archive once, deleted ones too so a recovered nest keeps its codes.
private static void AddStockCodes(SqliteCommand command)
{
command.CommandText = StockCodeSchema;
command.ExecuteNonQuery();
var codes = new List<(Guid Id, IReadOnlyList Codes)>();
command.CommandText = "SELECT id, file FROM nests";
using (var reader = command.ExecuteReader())
{
while (reader.Read())
codes.Add((Guid.Parse(reader.GetString(0)), NestArchive.ReadStockItemCodes((byte[])reader.GetValue(1))));
}
foreach (var (id, nestCodes) in codes.Where(c => c.Codes.Count > 0))
InsertStockCodes(command, id, nestCodes);
command.CommandText = "PRAGMA user_version=14";
command.ExecuteNonQuery();
}
}