feat(plantare): schema v12 — bilateral signed columns + repo

Adds the deferred bilateral columns to Plantares (plantare-bilateral.md
§"Schema delta"), all nullable/defaulted so v1 local rows coexist
untouched:

- pledgeId (the shared id both parties key their row by), debtorKey /
  creditorKey, debtorSignature / creditorSignature, movementId (the
  shared hand-over Movement for the provenance DAG), remoteState
  (proposed/accepted/declined — the handshake, distinct from status),
  returnKind (similar/workHours/other, default similar) + workHours.

Repo: createPlantare grows optional bilateral params; plantareByPledgeId
looks a row up by the shared id; applyPlantareSignatures counter-stamps
an incoming accept; setPlantareRemoteState records a decline. The
JSON codec carries every new field so they ride backups.

Versioned migration is guarded/idempotent like the others; schema v12
dumped, test helper regenerated, migration test covers v1..v11 → v12.
db + data + services 295/295 green.
This commit is contained in:
vjrj 2026-07-14 10:59:35 +02:00
parent 516f1f50bc
commit bec0f78fa1
11 changed files with 5322 additions and 17 deletions

File diff suppressed because it is too large Load diff

View file

@ -230,6 +230,16 @@ class InventoryJsonCodec {
'status': p.status.name, 'status': p.status.name,
'settledOn': p.settledOn, 'settledOn': p.settledOn,
'note': p.note, 'note': p.note,
// Bilateral signed form (plantare-bilateral.md).
'pledgeId': p.pledgeId,
'debtorKey': p.debtorKey,
'creditorKey': p.creditorKey,
'debtorSignature': p.debtorSignature,
'creditorSignature': p.creditorSignature,
'movementId': p.movementId,
'remoteState': p.remoteState?.name,
'returnKind': p.returnKind.name,
'workHours': p.workHours,
}, },
], ],
'sales': [ 'sales': [
@ -484,6 +494,17 @@ class InventoryJsonCodec {
_enumOr(PlantareStatus.values, m['status'], PlantareStatus.open), _enumOr(PlantareStatus.values, m['status'], PlantareStatus.open),
settledOn: m['settledOn'] as int?, settledOn: m['settledOn'] as int?,
note: m['note'] as String?, note: m['note'] as String?,
pledgeId: m['pledgeId'] as String?,
debtorKey: m['debtorKey'] as String?,
creditorKey: m['creditorKey'] as String?,
debtorSignature: m['debtorSignature'] as String?,
creditorSignature: m['creditorSignature'] as String?,
movementId: m['movementId'] as String?,
remoteState:
_enumOrNull(PlantareRemoteState.values, m['remoteState']),
returnKind: _enumOr(PlantareReturnKind.values, m['returnKind'],
PlantareReturnKind.similar),
workHours: (m['workHours'] as num?)?.toDouble(),
); );
}), }),
sales: _rows(root, 'sales', (m) { sales: _rows(root, 'sales', (m) {

View file

@ -1867,14 +1867,27 @@ class VarietyRepository {
String? owedDescription, String? owedDescription,
int? dueBy, int? dueBy,
String? note, String? note,
// Bilateral signed form (plantare-bilateral.md) all optional so a plain
// local note stays a v1 row with these null.
String? id,
int? madeOn,
String? pledgeId,
String? debtorKey,
String? creditorKey,
String? debtorSignature,
String? creditorSignature,
String? movementId,
PlantareRemoteState? remoteState,
PlantareReturnKind returnKind = PlantareReturnKind.similar,
double? workHours,
}) async { }) async {
final (created, updated) = await _stamp(); final (created, updated) = await _stamp();
final id = idGen.newId(); final rowId = id ?? idGen.newId();
await _db await _db
.into(_db.plantares) .into(_db.plantares)
.insert( .insert(
PlantaresCompanion.insert( PlantaresCompanion.insert(
id: id, id: rowId,
createdAt: created, createdAt: created,
updatedAt: updated, updatedAt: updated,
lastAuthor: nodeId, lastAuthor: nodeId,
@ -1882,12 +1895,75 @@ class VarietyRepository {
varietyId: Value(varietyId), varietyId: Value(varietyId),
counterparty: Value(counterparty), counterparty: Value(counterparty),
owedDescription: Value(owedDescription), owedDescription: Value(owedDescription),
madeOn: created, madeOn: madeOn ?? created,
dueBy: Value(dueBy), dueBy: Value(dueBy),
note: Value(note), note: Value(note),
pledgeId: Value(pledgeId),
debtorKey: Value(debtorKey),
creditorKey: Value(creditorKey),
debtorSignature: Value(debtorSignature),
creditorSignature: Value(creditorSignature),
movementId: Value(movementId),
remoteState: Value(remoteState),
returnKind: Value(returnKind),
workHours: Value(workHours),
), ),
); );
return id; return rowId;
}
/// The local row holding the bilateral pledge [pledgeId], or null. Both parties
/// key their own row by the shared pledge id so accepts/declines reconcile.
Future<Plantare?> plantareByPledgeId(String pledgeId) =>
(_db.select(_db.plantares)
..where((p) => p.pledgeId.equals(pledgeId))
..limit(1))
.getSingleOrNull();
/// Applies a counterparty's stub(s) and the new handshake [remoteState] to the
/// row for [pledgeId] (e.g. on receiving an `accept`). No-op if unknown.
Future<void> applyPlantareSignatures({
required String pledgeId,
String? debtorSignature,
String? creditorSignature,
PlantareRemoteState? remoteState,
String? movementId,
}) async {
final (_, updated) = await _stamp();
await (_db.update(_db.plantares)
..where((p) => p.pledgeId.equals(pledgeId)))
.write(
PlantaresCompanion(
debtorSignature:
debtorSignature == null ? const Value.absent() : Value(debtorSignature),
creditorSignature: creditorSignature == null
? const Value.absent()
: Value(creditorSignature),
movementId:
movementId == null ? const Value.absent() : Value(movementId),
remoteState:
remoteState == null ? const Value.absent() : Value(remoteState),
updatedAt: Value(updated),
lastAuthor: Value(nodeId),
),
);
}
/// Moves the row for [pledgeId] to a new handshake [state] (e.g. `declined`).
Future<void> setPlantareRemoteState(
String pledgeId,
PlantareRemoteState state,
) async {
final (_, updated) = await _stamp();
await (_db.update(_db.plantares)
..where((p) => p.pledgeId.equals(pledgeId)))
.write(
PlantaresCompanion(
remoteState: Value(state),
updatedAt: Value(updated),
lastAuthor: Value(nodeId),
),
);
} }
/// All live commitments, newest first (for the Plantares screen). /// All live commitments, newest first (for the Plantares screen).

View file

@ -31,7 +31,7 @@ class AppDatabase extends _$AppDatabase {
/// Current schema version; also stamped into interchange exports so an /// Current schema version; also stamped into interchange exports so an
/// importer knows which app generation wrote the file (data-model §7). /// importer knows which app generation wrote the file (data-model §7).
static const int currentSchemaVersion = 11; static const int currentSchemaVersion = 12;
@override @override
int get schemaVersion => currentSchemaVersion; int get schemaVersion => currentSchemaVersion;
@ -163,6 +163,30 @@ class AppDatabase extends _$AppDatabase {
await m.addColumn(lots, lots.priceCurrency); await m.addColumn(lots, lots.priceCurrency);
} }
} }
// v12: the bilateral SIGNED Plantare (plantare-bilateral.md) keys, both
// signatures, the shared pledge id/Movement, the handshake state, and the
// structured return option. All nullable/defaulted so v1 local rows are
// untouched. Guarded (see the v7 note above).
if (from < 12) {
Future<void> addIfMissing(
String column,
GeneratedColumn<Object> definition,
) async {
if (!await _hasColumn('plantares', column)) {
await m.addColumn(plantares, definition);
}
}
await addIfMissing('pledge_id', plantares.pledgeId);
await addIfMissing('debtor_key', plantares.debtorKey);
await addIfMissing('creditor_key', plantares.creditorKey);
await addIfMissing('debtor_signature', plantares.debtorSignature);
await addIfMissing('creditor_signature', plantares.creditorSignature);
await addIfMissing('movement_id', plantares.movementId);
await addIfMissing('remote_state', plantares.remoteState);
await addIfMissing('return_kind', plantares.returnKind);
await addIfMissing('work_hours', plantares.workHours);
}
}, },
); );

View file

@ -8787,6 +8787,103 @@ class $PlantaresTable extends Plantares
type: DriftSqlType.string, type: DriftSqlType.string,
requiredDuringInsert: false, requiredDuringInsert: false,
); );
static const VerificationMeta _pledgeIdMeta = const VerificationMeta(
'pledgeId',
);
@override
late final GeneratedColumn<String> pledgeId = GeneratedColumn<String>(
'pledge_id',
aliasedName,
true,
type: DriftSqlType.string,
requiredDuringInsert: false,
);
static const VerificationMeta _debtorKeyMeta = const VerificationMeta(
'debtorKey',
);
@override
late final GeneratedColumn<String> debtorKey = GeneratedColumn<String>(
'debtor_key',
aliasedName,
true,
type: DriftSqlType.string,
requiredDuringInsert: false,
);
static const VerificationMeta _creditorKeyMeta = const VerificationMeta(
'creditorKey',
);
@override
late final GeneratedColumn<String> creditorKey = GeneratedColumn<String>(
'creditor_key',
aliasedName,
true,
type: DriftSqlType.string,
requiredDuringInsert: false,
);
static const VerificationMeta _debtorSignatureMeta = const VerificationMeta(
'debtorSignature',
);
@override
late final GeneratedColumn<String> debtorSignature = GeneratedColumn<String>(
'debtor_signature',
aliasedName,
true,
type: DriftSqlType.string,
requiredDuringInsert: false,
);
static const VerificationMeta _creditorSignatureMeta = const VerificationMeta(
'creditorSignature',
);
@override
late final GeneratedColumn<String> creditorSignature =
GeneratedColumn<String>(
'creditor_signature',
aliasedName,
true,
type: DriftSqlType.string,
requiredDuringInsert: false,
);
static const VerificationMeta _movementIdMeta = const VerificationMeta(
'movementId',
);
@override
late final GeneratedColumn<String> movementId = GeneratedColumn<String>(
'movement_id',
aliasedName,
true,
type: DriftSqlType.string,
requiredDuringInsert: false,
);
@override
late final GeneratedColumnWithTypeConverter<PlantareRemoteState?, String>
remoteState = GeneratedColumn<String>(
'remote_state',
aliasedName,
true,
type: DriftSqlType.string,
requiredDuringInsert: false,
).withConverter<PlantareRemoteState?>($PlantaresTable.$converterremoteStaten);
@override
late final GeneratedColumnWithTypeConverter<PlantareReturnKind, String>
returnKind = GeneratedColumn<String>(
'return_kind',
aliasedName,
false,
type: DriftSqlType.string,
requiredDuringInsert: false,
defaultValue: const Constant('similar'),
).withConverter<PlantareReturnKind>($PlantaresTable.$converterreturnKind);
static const VerificationMeta _workHoursMeta = const VerificationMeta(
'workHours',
);
@override
late final GeneratedColumn<double> workHours = GeneratedColumn<double>(
'work_hours',
aliasedName,
true,
type: DriftSqlType.double,
requiredDuringInsert: false,
);
@override @override
List<GeneratedColumn> get $columns => [ List<GeneratedColumn> get $columns => [
id, id,
@ -8804,6 +8901,15 @@ class $PlantaresTable extends Plantares
status, status,
settledOn, settledOn,
note, note,
pledgeId,
debtorKey,
creditorKey,
debtorSignature,
creditorSignature,
movementId,
remoteState,
returnKind,
workHours,
]; ];
@override @override
String get aliasedName => _alias ?? actualTableName; String get aliasedName => _alias ?? actualTableName;
@ -8911,6 +9017,57 @@ class $PlantaresTable extends Plantares
note.isAcceptableOrUnknown(data['note']!, _noteMeta), note.isAcceptableOrUnknown(data['note']!, _noteMeta),
); );
} }
if (data.containsKey('pledge_id')) {
context.handle(
_pledgeIdMeta,
pledgeId.isAcceptableOrUnknown(data['pledge_id']!, _pledgeIdMeta),
);
}
if (data.containsKey('debtor_key')) {
context.handle(
_debtorKeyMeta,
debtorKey.isAcceptableOrUnknown(data['debtor_key']!, _debtorKeyMeta),
);
}
if (data.containsKey('creditor_key')) {
context.handle(
_creditorKeyMeta,
creditorKey.isAcceptableOrUnknown(
data['creditor_key']!,
_creditorKeyMeta,
),
);
}
if (data.containsKey('debtor_signature')) {
context.handle(
_debtorSignatureMeta,
debtorSignature.isAcceptableOrUnknown(
data['debtor_signature']!,
_debtorSignatureMeta,
),
);
}
if (data.containsKey('creditor_signature')) {
context.handle(
_creditorSignatureMeta,
creditorSignature.isAcceptableOrUnknown(
data['creditor_signature']!,
_creditorSignatureMeta,
),
);
}
if (data.containsKey('movement_id')) {
context.handle(
_movementIdMeta,
movementId.isAcceptableOrUnknown(data['movement_id']!, _movementIdMeta),
);
}
if (data.containsKey('work_hours')) {
context.handle(
_workHoursMeta,
workHours.isAcceptableOrUnknown(data['work_hours']!, _workHoursMeta),
);
}
return context; return context;
} }
@ -8984,6 +9141,46 @@ class $PlantaresTable extends Plantares
DriftSqlType.string, DriftSqlType.string,
data['${effectivePrefix}note'], data['${effectivePrefix}note'],
), ),
pledgeId: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}pledge_id'],
),
debtorKey: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}debtor_key'],
),
creditorKey: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}creditor_key'],
),
debtorSignature: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}debtor_signature'],
),
creditorSignature: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}creditor_signature'],
),
movementId: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}movement_id'],
),
remoteState: $PlantaresTable.$converterremoteStaten.fromSql(
attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}remote_state'],
),
),
returnKind: $PlantaresTable.$converterreturnKind.fromSql(
attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}return_kind'],
)!,
),
workHours: attachedDatabase.typeMapping.read(
DriftSqlType.double,
data['${effectivePrefix}work_hours'],
),
); );
} }
@ -8998,6 +9195,16 @@ class $PlantaresTable extends Plantares
); );
static JsonTypeConverter2<PlantareStatus, String, String> $converterstatus = static JsonTypeConverter2<PlantareStatus, String, String> $converterstatus =
const EnumNameConverter<PlantareStatus>(PlantareStatus.values); const EnumNameConverter<PlantareStatus>(PlantareStatus.values);
static JsonTypeConverter2<PlantareRemoteState, String, String>
$converterremoteState = const EnumNameConverter<PlantareRemoteState>(
PlantareRemoteState.values,
);
static JsonTypeConverter2<PlantareRemoteState?, String?, String?>
$converterremoteStaten = JsonTypeConverter2.asNullable($converterremoteState);
static JsonTypeConverter2<PlantareReturnKind, String, String>
$converterreturnKind = const EnumNameConverter<PlantareReturnKind>(
PlantareReturnKind.values,
);
} }
class Plantare extends DataClass implements Insertable<Plantare> { class Plantare extends DataClass implements Insertable<Plantare> {
@ -9033,6 +9240,35 @@ class Plantare extends DataClass implements Insertable<Plantare> {
/// When it was returned or forgiven (ms since epoch). /// When it was returned or forgiven (ms since epoch).
final int? settledOn; final int? settledOn;
final String? note; final String? note;
/// The shared pledge id both parties agree on (the proposer's id). Distinct
/// from [id] (each side keeps its own local row). Null for a v1 local note.
final String? pledgeId;
/// Pubkey (hex) of who received the seed and owes a return.
final String? debtorKey;
/// Pubkey (hex) of who gave the seed.
final String? creditorKey;
/// The debtor's Schnorr stub over the canonical pledge core.
final String? debtorSignature;
/// The creditor's Schnorr stub over the canonical pledge core.
final String? creditorSignature;
/// The shared hand-over `Movement` this Plantare accompanies (provenance DAG).
final String? movementId;
/// The handshake state (proposed/accepted/declined), null for a v1 local row.
final PlantareRemoteState? remoteState;
/// How it's promised back (default `similar`: open-pollinated · non-GMO ·
/// organically grown), mirroring the paper form's checkboxes.
final PlantareReturnKind returnKind;
/// Hours for the `workHours` return option (time-as-currency). Null otherwise.
final double? workHours;
const Plantare({ const Plantare({
required this.id, required this.id,
required this.createdAt, required this.createdAt,
@ -9049,6 +9285,15 @@ class Plantare extends DataClass implements Insertable<Plantare> {
required this.status, required this.status,
this.settledOn, this.settledOn,
this.note, this.note,
this.pledgeId,
this.debtorKey,
this.creditorKey,
this.debtorSignature,
this.creditorSignature,
this.movementId,
this.remoteState,
required this.returnKind,
this.workHours,
}); });
@override @override
Map<String, Expression> toColumns(bool nullToAbsent) { Map<String, Expression> toColumns(bool nullToAbsent) {
@ -9088,6 +9333,37 @@ class Plantare extends DataClass implements Insertable<Plantare> {
if (!nullToAbsent || note != null) { if (!nullToAbsent || note != null) {
map['note'] = Variable<String>(note); map['note'] = Variable<String>(note);
} }
if (!nullToAbsent || pledgeId != null) {
map['pledge_id'] = Variable<String>(pledgeId);
}
if (!nullToAbsent || debtorKey != null) {
map['debtor_key'] = Variable<String>(debtorKey);
}
if (!nullToAbsent || creditorKey != null) {
map['creditor_key'] = Variable<String>(creditorKey);
}
if (!nullToAbsent || debtorSignature != null) {
map['debtor_signature'] = Variable<String>(debtorSignature);
}
if (!nullToAbsent || creditorSignature != null) {
map['creditor_signature'] = Variable<String>(creditorSignature);
}
if (!nullToAbsent || movementId != null) {
map['movement_id'] = Variable<String>(movementId);
}
if (!nullToAbsent || remoteState != null) {
map['remote_state'] = Variable<String>(
$PlantaresTable.$converterremoteStaten.toSql(remoteState),
);
}
{
map['return_kind'] = Variable<String>(
$PlantaresTable.$converterreturnKind.toSql(returnKind),
);
}
if (!nullToAbsent || workHours != null) {
map['work_hours'] = Variable<double>(workHours);
}
return map; return map;
} }
@ -9118,6 +9394,31 @@ class Plantare extends DataClass implements Insertable<Plantare> {
? const Value.absent() ? const Value.absent()
: Value(settledOn), : Value(settledOn),
note: note == null && nullToAbsent ? const Value.absent() : Value(note), note: note == null && nullToAbsent ? const Value.absent() : Value(note),
pledgeId: pledgeId == null && nullToAbsent
? const Value.absent()
: Value(pledgeId),
debtorKey: debtorKey == null && nullToAbsent
? const Value.absent()
: Value(debtorKey),
creditorKey: creditorKey == null && nullToAbsent
? const Value.absent()
: Value(creditorKey),
debtorSignature: debtorSignature == null && nullToAbsent
? const Value.absent()
: Value(debtorSignature),
creditorSignature: creditorSignature == null && nullToAbsent
? const Value.absent()
: Value(creditorSignature),
movementId: movementId == null && nullToAbsent
? const Value.absent()
: Value(movementId),
remoteState: remoteState == null && nullToAbsent
? const Value.absent()
: Value(remoteState),
returnKind: Value(returnKind),
workHours: workHours == null && nullToAbsent
? const Value.absent()
: Value(workHours),
); );
} }
@ -9146,6 +9447,21 @@ class Plantare extends DataClass implements Insertable<Plantare> {
), ),
settledOn: serializer.fromJson<int?>(json['settledOn']), settledOn: serializer.fromJson<int?>(json['settledOn']),
note: serializer.fromJson<String?>(json['note']), note: serializer.fromJson<String?>(json['note']),
pledgeId: serializer.fromJson<String?>(json['pledgeId']),
debtorKey: serializer.fromJson<String?>(json['debtorKey']),
creditorKey: serializer.fromJson<String?>(json['creditorKey']),
debtorSignature: serializer.fromJson<String?>(json['debtorSignature']),
creditorSignature: serializer.fromJson<String?>(
json['creditorSignature'],
),
movementId: serializer.fromJson<String?>(json['movementId']),
remoteState: $PlantaresTable.$converterremoteStaten.fromJson(
serializer.fromJson<String?>(json['remoteState']),
),
returnKind: $PlantaresTable.$converterreturnKind.fromJson(
serializer.fromJson<String>(json['returnKind']),
),
workHours: serializer.fromJson<double?>(json['workHours']),
); );
} }
@override @override
@ -9171,6 +9487,19 @@ class Plantare extends DataClass implements Insertable<Plantare> {
), ),
'settledOn': serializer.toJson<int?>(settledOn), 'settledOn': serializer.toJson<int?>(settledOn),
'note': serializer.toJson<String?>(note), 'note': serializer.toJson<String?>(note),
'pledgeId': serializer.toJson<String?>(pledgeId),
'debtorKey': serializer.toJson<String?>(debtorKey),
'creditorKey': serializer.toJson<String?>(creditorKey),
'debtorSignature': serializer.toJson<String?>(debtorSignature),
'creditorSignature': serializer.toJson<String?>(creditorSignature),
'movementId': serializer.toJson<String?>(movementId),
'remoteState': serializer.toJson<String?>(
$PlantaresTable.$converterremoteStaten.toJson(remoteState),
),
'returnKind': serializer.toJson<String>(
$PlantaresTable.$converterreturnKind.toJson(returnKind),
),
'workHours': serializer.toJson<double?>(workHours),
}; };
} }
@ -9190,6 +9519,15 @@ class Plantare extends DataClass implements Insertable<Plantare> {
PlantareStatus? status, PlantareStatus? status,
Value<int?> settledOn = const Value.absent(), Value<int?> settledOn = const Value.absent(),
Value<String?> note = const Value.absent(), Value<String?> note = const Value.absent(),
Value<String?> pledgeId = const Value.absent(),
Value<String?> debtorKey = const Value.absent(),
Value<String?> creditorKey = const Value.absent(),
Value<String?> debtorSignature = const Value.absent(),
Value<String?> creditorSignature = const Value.absent(),
Value<String?> movementId = const Value.absent(),
Value<PlantareRemoteState?> remoteState = const Value.absent(),
PlantareReturnKind? returnKind,
Value<double?> workHours = const Value.absent(),
}) => Plantare( }) => Plantare(
id: id ?? this.id, id: id ?? this.id,
createdAt: createdAt ?? this.createdAt, createdAt: createdAt ?? this.createdAt,
@ -9208,6 +9546,19 @@ class Plantare extends DataClass implements Insertable<Plantare> {
status: status ?? this.status, status: status ?? this.status,
settledOn: settledOn.present ? settledOn.value : this.settledOn, settledOn: settledOn.present ? settledOn.value : this.settledOn,
note: note.present ? note.value : this.note, note: note.present ? note.value : this.note,
pledgeId: pledgeId.present ? pledgeId.value : this.pledgeId,
debtorKey: debtorKey.present ? debtorKey.value : this.debtorKey,
creditorKey: creditorKey.present ? creditorKey.value : this.creditorKey,
debtorSignature: debtorSignature.present
? debtorSignature.value
: this.debtorSignature,
creditorSignature: creditorSignature.present
? creditorSignature.value
: this.creditorSignature,
movementId: movementId.present ? movementId.value : this.movementId,
remoteState: remoteState.present ? remoteState.value : this.remoteState,
returnKind: returnKind ?? this.returnKind,
workHours: workHours.present ? workHours.value : this.workHours,
); );
Plantare copyWithCompanion(PlantaresCompanion data) { Plantare copyWithCompanion(PlantaresCompanion data) {
return Plantare( return Plantare(
@ -9234,6 +9585,27 @@ class Plantare extends DataClass implements Insertable<Plantare> {
status: data.status.present ? data.status.value : this.status, status: data.status.present ? data.status.value : this.status,
settledOn: data.settledOn.present ? data.settledOn.value : this.settledOn, settledOn: data.settledOn.present ? data.settledOn.value : this.settledOn,
note: data.note.present ? data.note.value : this.note, note: data.note.present ? data.note.value : this.note,
pledgeId: data.pledgeId.present ? data.pledgeId.value : this.pledgeId,
debtorKey: data.debtorKey.present ? data.debtorKey.value : this.debtorKey,
creditorKey: data.creditorKey.present
? data.creditorKey.value
: this.creditorKey,
debtorSignature: data.debtorSignature.present
? data.debtorSignature.value
: this.debtorSignature,
creditorSignature: data.creditorSignature.present
? data.creditorSignature.value
: this.creditorSignature,
movementId: data.movementId.present
? data.movementId.value
: this.movementId,
remoteState: data.remoteState.present
? data.remoteState.value
: this.remoteState,
returnKind: data.returnKind.present
? data.returnKind.value
: this.returnKind,
workHours: data.workHours.present ? data.workHours.value : this.workHours,
); );
} }
@ -9254,13 +9626,22 @@ class Plantare extends DataClass implements Insertable<Plantare> {
..write('dueBy: $dueBy, ') ..write('dueBy: $dueBy, ')
..write('status: $status, ') ..write('status: $status, ')
..write('settledOn: $settledOn, ') ..write('settledOn: $settledOn, ')
..write('note: $note') ..write('note: $note, ')
..write('pledgeId: $pledgeId, ')
..write('debtorKey: $debtorKey, ')
..write('creditorKey: $creditorKey, ')
..write('debtorSignature: $debtorSignature, ')
..write('creditorSignature: $creditorSignature, ')
..write('movementId: $movementId, ')
..write('remoteState: $remoteState, ')
..write('returnKind: $returnKind, ')
..write('workHours: $workHours')
..write(')')) ..write(')'))
.toString(); .toString();
} }
@override @override
int get hashCode => Object.hash( int get hashCode => Object.hashAll([
id, id,
createdAt, createdAt,
updatedAt, updatedAt,
@ -9276,7 +9657,16 @@ class Plantare extends DataClass implements Insertable<Plantare> {
status, status,
settledOn, settledOn,
note, note,
); pledgeId,
debtorKey,
creditorKey,
debtorSignature,
creditorSignature,
movementId,
remoteState,
returnKind,
workHours,
]);
@override @override
bool operator ==(Object other) => bool operator ==(Object other) =>
identical(this, other) || identical(this, other) ||
@ -9295,7 +9685,16 @@ class Plantare extends DataClass implements Insertable<Plantare> {
other.dueBy == this.dueBy && other.dueBy == this.dueBy &&
other.status == this.status && other.status == this.status &&
other.settledOn == this.settledOn && other.settledOn == this.settledOn &&
other.note == this.note); other.note == this.note &&
other.pledgeId == this.pledgeId &&
other.debtorKey == this.debtorKey &&
other.creditorKey == this.creditorKey &&
other.debtorSignature == this.debtorSignature &&
other.creditorSignature == this.creditorSignature &&
other.movementId == this.movementId &&
other.remoteState == this.remoteState &&
other.returnKind == this.returnKind &&
other.workHours == this.workHours);
} }
class PlantaresCompanion extends UpdateCompanion<Plantare> { class PlantaresCompanion extends UpdateCompanion<Plantare> {
@ -9314,6 +9713,15 @@ class PlantaresCompanion extends UpdateCompanion<Plantare> {
final Value<PlantareStatus> status; final Value<PlantareStatus> status;
final Value<int?> settledOn; final Value<int?> settledOn;
final Value<String?> note; final Value<String?> note;
final Value<String?> pledgeId;
final Value<String?> debtorKey;
final Value<String?> creditorKey;
final Value<String?> debtorSignature;
final Value<String?> creditorSignature;
final Value<String?> movementId;
final Value<PlantareRemoteState?> remoteState;
final Value<PlantareReturnKind> returnKind;
final Value<double?> workHours;
final Value<int> rowid; final Value<int> rowid;
const PlantaresCompanion({ const PlantaresCompanion({
this.id = const Value.absent(), this.id = const Value.absent(),
@ -9331,6 +9739,15 @@ class PlantaresCompanion extends UpdateCompanion<Plantare> {
this.status = const Value.absent(), this.status = const Value.absent(),
this.settledOn = const Value.absent(), this.settledOn = const Value.absent(),
this.note = const Value.absent(), this.note = const Value.absent(),
this.pledgeId = const Value.absent(),
this.debtorKey = const Value.absent(),
this.creditorKey = const Value.absent(),
this.debtorSignature = const Value.absent(),
this.creditorSignature = const Value.absent(),
this.movementId = const Value.absent(),
this.remoteState = const Value.absent(),
this.returnKind = const Value.absent(),
this.workHours = const Value.absent(),
this.rowid = const Value.absent(), this.rowid = const Value.absent(),
}); });
PlantaresCompanion.insert({ PlantaresCompanion.insert({
@ -9349,6 +9766,15 @@ class PlantaresCompanion extends UpdateCompanion<Plantare> {
this.status = const Value.absent(), this.status = const Value.absent(),
this.settledOn = const Value.absent(), this.settledOn = const Value.absent(),
this.note = const Value.absent(), this.note = const Value.absent(),
this.pledgeId = const Value.absent(),
this.debtorKey = const Value.absent(),
this.creditorKey = const Value.absent(),
this.debtorSignature = const Value.absent(),
this.creditorSignature = const Value.absent(),
this.movementId = const Value.absent(),
this.remoteState = const Value.absent(),
this.returnKind = const Value.absent(),
this.workHours = const Value.absent(),
this.rowid = const Value.absent(), this.rowid = const Value.absent(),
}) : id = Value(id), }) : id = Value(id),
createdAt = Value(createdAt), createdAt = Value(createdAt),
@ -9372,6 +9798,15 @@ class PlantaresCompanion extends UpdateCompanion<Plantare> {
Expression<String>? status, Expression<String>? status,
Expression<int>? settledOn, Expression<int>? settledOn,
Expression<String>? note, Expression<String>? note,
Expression<String>? pledgeId,
Expression<String>? debtorKey,
Expression<String>? creditorKey,
Expression<String>? debtorSignature,
Expression<String>? creditorSignature,
Expression<String>? movementId,
Expression<String>? remoteState,
Expression<String>? returnKind,
Expression<double>? workHours,
Expression<int>? rowid, Expression<int>? rowid,
}) { }) {
return RawValuesInsertable({ return RawValuesInsertable({
@ -9390,6 +9825,15 @@ class PlantaresCompanion extends UpdateCompanion<Plantare> {
if (status != null) 'status': status, if (status != null) 'status': status,
if (settledOn != null) 'settled_on': settledOn, if (settledOn != null) 'settled_on': settledOn,
if (note != null) 'note': note, if (note != null) 'note': note,
if (pledgeId != null) 'pledge_id': pledgeId,
if (debtorKey != null) 'debtor_key': debtorKey,
if (creditorKey != null) 'creditor_key': creditorKey,
if (debtorSignature != null) 'debtor_signature': debtorSignature,
if (creditorSignature != null) 'creditor_signature': creditorSignature,
if (movementId != null) 'movement_id': movementId,
if (remoteState != null) 'remote_state': remoteState,
if (returnKind != null) 'return_kind': returnKind,
if (workHours != null) 'work_hours': workHours,
if (rowid != null) 'rowid': rowid, if (rowid != null) 'rowid': rowid,
}); });
} }
@ -9410,6 +9854,15 @@ class PlantaresCompanion extends UpdateCompanion<Plantare> {
Value<PlantareStatus>? status, Value<PlantareStatus>? status,
Value<int?>? settledOn, Value<int?>? settledOn,
Value<String?>? note, Value<String?>? note,
Value<String?>? pledgeId,
Value<String?>? debtorKey,
Value<String?>? creditorKey,
Value<String?>? debtorSignature,
Value<String?>? creditorSignature,
Value<String?>? movementId,
Value<PlantareRemoteState?>? remoteState,
Value<PlantareReturnKind>? returnKind,
Value<double?>? workHours,
Value<int>? rowid, Value<int>? rowid,
}) { }) {
return PlantaresCompanion( return PlantaresCompanion(
@ -9428,6 +9881,15 @@ class PlantaresCompanion extends UpdateCompanion<Plantare> {
status: status ?? this.status, status: status ?? this.status,
settledOn: settledOn ?? this.settledOn, settledOn: settledOn ?? this.settledOn,
note: note ?? this.note, note: note ?? this.note,
pledgeId: pledgeId ?? this.pledgeId,
debtorKey: debtorKey ?? this.debtorKey,
creditorKey: creditorKey ?? this.creditorKey,
debtorSignature: debtorSignature ?? this.debtorSignature,
creditorSignature: creditorSignature ?? this.creditorSignature,
movementId: movementId ?? this.movementId,
remoteState: remoteState ?? this.remoteState,
returnKind: returnKind ?? this.returnKind,
workHours: workHours ?? this.workHours,
rowid: rowid ?? this.rowid, rowid: rowid ?? this.rowid,
); );
} }
@ -9484,6 +9946,37 @@ class PlantaresCompanion extends UpdateCompanion<Plantare> {
if (note.present) { if (note.present) {
map['note'] = Variable<String>(note.value); map['note'] = Variable<String>(note.value);
} }
if (pledgeId.present) {
map['pledge_id'] = Variable<String>(pledgeId.value);
}
if (debtorKey.present) {
map['debtor_key'] = Variable<String>(debtorKey.value);
}
if (creditorKey.present) {
map['creditor_key'] = Variable<String>(creditorKey.value);
}
if (debtorSignature.present) {
map['debtor_signature'] = Variable<String>(debtorSignature.value);
}
if (creditorSignature.present) {
map['creditor_signature'] = Variable<String>(creditorSignature.value);
}
if (movementId.present) {
map['movement_id'] = Variable<String>(movementId.value);
}
if (remoteState.present) {
map['remote_state'] = Variable<String>(
$PlantaresTable.$converterremoteStaten.toSql(remoteState.value),
);
}
if (returnKind.present) {
map['return_kind'] = Variable<String>(
$PlantaresTable.$converterreturnKind.toSql(returnKind.value),
);
}
if (workHours.present) {
map['work_hours'] = Variable<double>(workHours.value);
}
if (rowid.present) { if (rowid.present) {
map['rowid'] = Variable<int>(rowid.value); map['rowid'] = Variable<int>(rowid.value);
} }
@ -9508,6 +10001,15 @@ class PlantaresCompanion extends UpdateCompanion<Plantare> {
..write('status: $status, ') ..write('status: $status, ')
..write('settledOn: $settledOn, ') ..write('settledOn: $settledOn, ')
..write('note: $note, ') ..write('note: $note, ')
..write('pledgeId: $pledgeId, ')
..write('debtorKey: $debtorKey, ')
..write('creditorKey: $creditorKey, ')
..write('debtorSignature: $debtorSignature, ')
..write('creditorSignature: $creditorSignature, ')
..write('movementId: $movementId, ')
..write('remoteState: $remoteState, ')
..write('returnKind: $returnKind, ')
..write('workHours: $workHours, ')
..write('rowid: $rowid') ..write('rowid: $rowid')
..write(')')) ..write(')'))
.toString(); .toString();
@ -14358,6 +14860,15 @@ typedef $$PlantaresTableCreateCompanionBuilder =
Value<PlantareStatus> status, Value<PlantareStatus> status,
Value<int?> settledOn, Value<int?> settledOn,
Value<String?> note, Value<String?> note,
Value<String?> pledgeId,
Value<String?> debtorKey,
Value<String?> creditorKey,
Value<String?> debtorSignature,
Value<String?> creditorSignature,
Value<String?> movementId,
Value<PlantareRemoteState?> remoteState,
Value<PlantareReturnKind> returnKind,
Value<double?> workHours,
Value<int> rowid, Value<int> rowid,
}); });
typedef $$PlantaresTableUpdateCompanionBuilder = typedef $$PlantaresTableUpdateCompanionBuilder =
@ -14377,6 +14888,15 @@ typedef $$PlantaresTableUpdateCompanionBuilder =
Value<PlantareStatus> status, Value<PlantareStatus> status,
Value<int?> settledOn, Value<int?> settledOn,
Value<String?> note, Value<String?> note,
Value<String?> pledgeId,
Value<String?> debtorKey,
Value<String?> creditorKey,
Value<String?> debtorSignature,
Value<String?> creditorSignature,
Value<String?> movementId,
Value<PlantareRemoteState?> remoteState,
Value<PlantareReturnKind> returnKind,
Value<double?> workHours,
Value<int> rowid, Value<int> rowid,
}); });
@ -14465,6 +14985,57 @@ class $$PlantaresTableFilterComposer
column: $table.note, column: $table.note,
builder: (column) => ColumnFilters(column), builder: (column) => ColumnFilters(column),
); );
ColumnFilters<String> get pledgeId => $composableBuilder(
column: $table.pledgeId,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<String> get debtorKey => $composableBuilder(
column: $table.debtorKey,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<String> get creditorKey => $composableBuilder(
column: $table.creditorKey,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<String> get debtorSignature => $composableBuilder(
column: $table.debtorSignature,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<String> get creditorSignature => $composableBuilder(
column: $table.creditorSignature,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<String> get movementId => $composableBuilder(
column: $table.movementId,
builder: (column) => ColumnFilters(column),
);
ColumnWithTypeConverterFilters<
PlantareRemoteState?,
PlantareRemoteState,
String
>
get remoteState => $composableBuilder(
column: $table.remoteState,
builder: (column) => ColumnWithTypeConverterFilters(column),
);
ColumnWithTypeConverterFilters<PlantareReturnKind, PlantareReturnKind, String>
get returnKind => $composableBuilder(
column: $table.returnKind,
builder: (column) => ColumnWithTypeConverterFilters(column),
);
ColumnFilters<double> get workHours => $composableBuilder(
column: $table.workHours,
builder: (column) => ColumnFilters(column),
);
} }
class $$PlantaresTableOrderingComposer class $$PlantaresTableOrderingComposer
@ -14550,6 +15121,51 @@ class $$PlantaresTableOrderingComposer
column: $table.note, column: $table.note,
builder: (column) => ColumnOrderings(column), builder: (column) => ColumnOrderings(column),
); );
ColumnOrderings<String> get pledgeId => $composableBuilder(
column: $table.pledgeId,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<String> get debtorKey => $composableBuilder(
column: $table.debtorKey,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<String> get creditorKey => $composableBuilder(
column: $table.creditorKey,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<String> get debtorSignature => $composableBuilder(
column: $table.debtorSignature,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<String> get creditorSignature => $composableBuilder(
column: $table.creditorSignature,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<String> get movementId => $composableBuilder(
column: $table.movementId,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<String> get remoteState => $composableBuilder(
column: $table.remoteState,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<String> get returnKind => $composableBuilder(
column: $table.returnKind,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<double> get workHours => $composableBuilder(
column: $table.workHours,
builder: (column) => ColumnOrderings(column),
);
} }
class $$PlantaresTableAnnotationComposer class $$PlantaresTableAnnotationComposer
@ -14613,6 +15229,47 @@ class $$PlantaresTableAnnotationComposer
GeneratedColumn<String> get note => GeneratedColumn<String> get note =>
$composableBuilder(column: $table.note, builder: (column) => column); $composableBuilder(column: $table.note, builder: (column) => column);
GeneratedColumn<String> get pledgeId =>
$composableBuilder(column: $table.pledgeId, builder: (column) => column);
GeneratedColumn<String> get debtorKey =>
$composableBuilder(column: $table.debtorKey, builder: (column) => column);
GeneratedColumn<String> get creditorKey => $composableBuilder(
column: $table.creditorKey,
builder: (column) => column,
);
GeneratedColumn<String> get debtorSignature => $composableBuilder(
column: $table.debtorSignature,
builder: (column) => column,
);
GeneratedColumn<String> get creditorSignature => $composableBuilder(
column: $table.creditorSignature,
builder: (column) => column,
);
GeneratedColumn<String> get movementId => $composableBuilder(
column: $table.movementId,
builder: (column) => column,
);
GeneratedColumnWithTypeConverter<PlantareRemoteState?, String>
get remoteState => $composableBuilder(
column: $table.remoteState,
builder: (column) => column,
);
GeneratedColumnWithTypeConverter<PlantareReturnKind, String> get returnKind =>
$composableBuilder(
column: $table.returnKind,
builder: (column) => column,
);
GeneratedColumn<double> get workHours =>
$composableBuilder(column: $table.workHours, builder: (column) => column);
} }
class $$PlantaresTableTableManager class $$PlantaresTableTableManager
@ -14658,6 +15315,15 @@ class $$PlantaresTableTableManager
Value<PlantareStatus> status = const Value.absent(), Value<PlantareStatus> status = const Value.absent(),
Value<int?> settledOn = const Value.absent(), Value<int?> settledOn = const Value.absent(),
Value<String?> note = const Value.absent(), Value<String?> note = const Value.absent(),
Value<String?> pledgeId = const Value.absent(),
Value<String?> debtorKey = const Value.absent(),
Value<String?> creditorKey = const Value.absent(),
Value<String?> debtorSignature = const Value.absent(),
Value<String?> creditorSignature = const Value.absent(),
Value<String?> movementId = const Value.absent(),
Value<PlantareRemoteState?> remoteState = const Value.absent(),
Value<PlantareReturnKind> returnKind = const Value.absent(),
Value<double?> workHours = const Value.absent(),
Value<int> rowid = const Value.absent(), Value<int> rowid = const Value.absent(),
}) => PlantaresCompanion( }) => PlantaresCompanion(
id: id, id: id,
@ -14675,6 +15341,15 @@ class $$PlantaresTableTableManager
status: status, status: status,
settledOn: settledOn, settledOn: settledOn,
note: note, note: note,
pledgeId: pledgeId,
debtorKey: debtorKey,
creditorKey: creditorKey,
debtorSignature: debtorSignature,
creditorSignature: creditorSignature,
movementId: movementId,
remoteState: remoteState,
returnKind: returnKind,
workHours: workHours,
rowid: rowid, rowid: rowid,
), ),
createCompanionCallback: createCompanionCallback:
@ -14694,6 +15369,15 @@ class $$PlantaresTableTableManager
Value<PlantareStatus> status = const Value.absent(), Value<PlantareStatus> status = const Value.absent(),
Value<int?> settledOn = const Value.absent(), Value<int?> settledOn = const Value.absent(),
Value<String?> note = const Value.absent(), Value<String?> note = const Value.absent(),
Value<String?> pledgeId = const Value.absent(),
Value<String?> debtorKey = const Value.absent(),
Value<String?> creditorKey = const Value.absent(),
Value<String?> debtorSignature = const Value.absent(),
Value<String?> creditorSignature = const Value.absent(),
Value<String?> movementId = const Value.absent(),
Value<PlantareRemoteState?> remoteState = const Value.absent(),
Value<PlantareReturnKind> returnKind = const Value.absent(),
Value<double?> workHours = const Value.absent(),
Value<int> rowid = const Value.absent(), Value<int> rowid = const Value.absent(),
}) => PlantaresCompanion.insert( }) => PlantaresCompanion.insert(
id: id, id: id,
@ -14711,6 +15395,15 @@ class $$PlantaresTableTableManager
status: status, status: status,
settledOn: settledOn, settledOn: settledOn,
note: note, note: note,
pledgeId: pledgeId,
debtorKey: debtorKey,
creditorKey: creditorKey,
debtorSignature: debtorSignature,
creditorSignature: creditorSignature,
movementId: movementId,
remoteState: remoteState,
returnKind: returnKind,
workHours: workHours,
rowid: rowid, rowid: rowid,
), ),
withReferenceMapper: (p0) => p0 withReferenceMapper: (p0) => p0

View file

@ -102,6 +102,19 @@ enum PlantareDirection {
/// `forgiven` (not "defaulted") when it's let go. /// `forgiven` (not "defaulted") when it's let go.
enum PlantareStatus { open, returned, forgiven } enum PlantareStatus { open, returned, forgiven }
/// State of the bilateral SIGNED handshake, distinct from [PlantareStatus]
/// (which tracks the *promise*, not the *agreement*). A v1 local-only row leaves
/// this null. `proposed` = I sent/received a proposal awaiting the other stub;
/// `accepted` = both signed (a provably closed deal); `declined` = the other
/// party turned my proposal down. Mirrors commons_core `PlantareMessageKind`.
enum PlantareRemoteState { proposed, accepted, declined }
/// How a Plantare is promised back, mirroring the paper form's checkboxes and
/// commons_core `ReturnKind`. `similar` (the default) means open-pollinated ·
/// non-GMO · organically grown; `workHours` is time-as-currency; `other` is
/// free text in `owedDescription`.
enum PlantareReturnKind { similar, workHours, other }
/// Direction of a recorded seed Sale, seen from this app's owner. A sale is a /// Direction of a recorded seed Sale, seen from this app's owner. A sale is a
/// SEPARATE model from a gift or a Plantare seed for money (any currency: /// SEPARATE model from a gift or a Plantare seed for money (any currency:
/// , Ğ1, a local/time currency). No commission is ever taken on seeds. /// , Ğ1, a local/time currency). No commission is ever taken on seeds.

View file

@ -221,6 +221,39 @@ class Plantares extends Table with SyncColumns {
IntColumn get settledOn => integer().nullable()(); IntColumn get settledOn => integer().nullable()();
TextColumn get note => text().nullable()(); TextColumn get note => text().nullable()();
// --- Bilateral signed form (plantare-bilateral.md). Nullable so v1 local rows
// (both keys/signatures null, remoteState null) coexist untouched. ---
/// The shared pledge id both parties agree on (the proposer's id). Distinct
/// from [id] (each side keeps its own local row). Null for a v1 local note.
TextColumn get pledgeId => text().nullable()();
/// Pubkey (hex) of who received the seed and owes a return.
TextColumn get debtorKey => text().nullable()();
/// Pubkey (hex) of who gave the seed.
TextColumn get creditorKey => text().nullable()();
/// The debtor's Schnorr stub over the canonical pledge core.
TextColumn get debtorSignature => text().nullable()();
/// The creditor's Schnorr stub over the canonical pledge core.
TextColumn get creditorSignature => text().nullable()();
/// The shared hand-over `Movement` this Plantare accompanies (provenance DAG).
TextColumn get movementId => text().nullable()(); // Movement
/// The handshake state (proposed/accepted/declined), null for a v1 local row.
TextColumn get remoteState => textEnum<PlantareRemoteState>().nullable()();
/// How it's promised back (default `similar`: open-pollinated · non-GMO ·
/// organically grown), mirroring the paper form's checkboxes.
TextColumn get returnKind =>
textEnum<PlantareReturnKind>().withDefault(const Constant('similar'))();
/// Hours for the `workHours` return option (time-as-currency). Null otherwise.
RealColumn get workHours => real().nullable()();
} }
/// A recorded seed Sale seed for money (data-model §2.8/sharing-model §6). A /// A recorded seed Sale seed for money (data-model §2.8/sharing-model §6). A

View file

@ -89,6 +89,70 @@ void main() {
expect((await repo.watchPlantares().first).single.dueBy, due); expect((await repo.watchPlantares().first).single.dueBy, due);
}); });
test('a bilateral pledge stores keys/signatures and looks up by pledgeId',
() async {
final repo = newTestRepository(db);
await repo.createPlantare(
direction: PlantareDirection.iReturn,
counterparty: 'Ana',
pledgeId: 'pledge-1',
debtorKey: 'dkey',
creditorKey: 'ckey',
creditorSignature: 'csig',
remoteState: PlantareRemoteState.proposed,
returnKind: PlantareReturnKind.workHours,
workHours: 3,
);
final row = await repo.plantareByPledgeId('pledge-1');
expect(row, isNotNull);
expect(row!.debtorKey, 'dkey');
expect(row.creditorKey, 'ckey');
expect(row.creditorSignature, 'csig');
expect(row.debtorSignature, isNull);
expect(row.remoteState, PlantareRemoteState.proposed);
expect(row.returnKind, PlantareReturnKind.workHours);
expect(row.workHours, 3);
expect(await repo.plantareByPledgeId('nope'), isNull);
});
test('accepting a proposal adds the counter-stub and flips to accepted',
() async {
final repo = newTestRepository(db);
await repo.createPlantare(
direction: PlantareDirection.owedToMe,
pledgeId: 'pledge-2',
debtorKey: 'dkey',
creditorKey: 'ckey',
creditorSignature: 'csig',
remoteState: PlantareRemoteState.proposed,
);
await repo.applyPlantareSignatures(
pledgeId: 'pledge-2',
debtorSignature: 'dsig',
remoteState: PlantareRemoteState.accepted,
movementId: 'mov-1',
);
final row = await repo.plantareByPledgeId('pledge-2');
expect(row!.debtorSignature, 'dsig');
expect(row.creditorSignature, 'csig'); // untouched
expect(row.remoteState, PlantareRemoteState.accepted);
expect(row.movementId, 'mov-1');
});
test('a v1 local note keeps the bilateral columns null (defaults)', () async {
final repo = newTestRepository(db);
final id = await repo.createPlantare(direction: PlantareDirection.iReturn);
final row = (await repo.watchPlantares().first).single;
expect(row.id, id);
expect(row.pledgeId, isNull);
expect(row.debtorKey, isNull);
expect(row.remoteState, isNull);
expect(row.returnKind, PlantareReturnKind.similar); // default
});
test('commitments survive a backup round-trip (in exportInventory/import)', test('commitments survive a backup round-trip (in exportInventory/import)',
() async { () async {
final repoA = newTestRepository(db); final repoA = newTestRepository(db);
@ -98,6 +162,12 @@ void main() {
varietyId: vid, varietyId: vid,
counterparty: 'Colectivo semillero', counterparty: 'Colectivo semillero',
owedDescription: 'una mazorca', owedDescription: 'una mazorca',
pledgeId: 'pledge-3',
debtorKey: 'dkey',
creditorKey: 'ckey',
debtorSignature: 'dsig',
creditorSignature: 'csig',
remoteState: PlantareRemoteState.accepted,
); );
final snapshot = await repoA.exportInventory(); final snapshot = await repoA.exportInventory();
@ -110,5 +180,10 @@ void main() {
final onB = await repoB.watchPlantares().first; final onB = await repoB.watchPlantares().first;
expect(onB.single.counterparty, 'Colectivo semillero'); expect(onB.single.counterparty, 'Colectivo semillero');
expect(onB.single.owedDescription, 'una mazorca'); expect(onB.single.owedDescription, 'una mazorca');
// The bilateral form rides the backup too.
expect(onB.single.pledgeId, 'pledge-3');
expect(onB.single.debtorSignature, 'dsig');
expect(onB.single.creditorSignature, 'csig');
expect(onB.single.remoteState, PlantareRemoteState.accepted);
}); });
} }

View file

@ -11,19 +11,19 @@ void main() {
verifier = SchemaVerifier(GeneratedHelper()); verifier = SchemaVerifier(GeneratedHelper());
}); });
test('freshly created database matches the exported schema v11', () async { test('freshly created database matches the exported schema v12', () async {
final schema = await verifier.schemaAt(11); final schema = await verifier.schemaAt(12);
final db = AppDatabase(schema.newConnection()); final db = AppDatabase(schema.newConnection());
await verifier.migrateAndValidate(db, 11); await verifier.migrateAndValidate(db, 12);
await db.close(); await db.close();
}); });
// Every historical version upgrades cleanly to the current schema (v11). // Every historical version upgrades cleanly to the current schema (v12).
for (var from = 1; from <= 10; from++) { for (var from = 1; from <= 11; from++) {
test('upgrades v$from → v11 and matches the fresh schema', () async { test('upgrades v$from → v12 and matches the fresh schema', () async {
final connection = await verifier.startAt(from); final connection = await verifier.startAt(from);
final db = AppDatabase(connection); final db = AppDatabase(connection);
await verifier.migrateAndValidate(db, 11); await verifier.migrateAndValidate(db, 12);
await db.close(); await db.close();
}); });
} }

View file

@ -15,6 +15,7 @@ import 'schema_v8.dart' as v8;
import 'schema_v9.dart' as v9; import 'schema_v9.dart' as v9;
import 'schema_v10.dart' as v10; import 'schema_v10.dart' as v10;
import 'schema_v11.dart' as v11; import 'schema_v11.dart' as v11;
import 'schema_v12.dart' as v12;
class GeneratedHelper implements SchemaInstantiationHelper { class GeneratedHelper implements SchemaInstantiationHelper {
@override @override
@ -42,10 +43,12 @@ class GeneratedHelper implements SchemaInstantiationHelper {
return v10.DatabaseAtV10(db); return v10.DatabaseAtV10(db);
case 11: case 11:
return v11.DatabaseAtV11(db); return v11.DatabaseAtV11(db);
case 12:
return v12.DatabaseAtV12(db);
default: default:
throw MissingSchemaException(version, versions); throw MissingSchemaException(version, versions);
} }
} }
static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
} }

File diff suppressed because it is too large Load diff