Add pricing entry flow and platform admin foundations

This commit is contained in:
egecankomur
2026-06-20 18:24:40 +03:00
parent 1d36ccdf30
commit ac42681f7e
44 changed files with 6567 additions and 1419 deletions
+28 -12
View File
@@ -20,9 +20,9 @@ class FinanceService {
if (amount <= 0) return;
final existing = await _pb.collection('finance_entries').getFullList(
filter: 'job_id = "$jobId"',
batch: 200,
);
filter: 'job_id = "$jobId"',
batch: 200,
);
await _upsertEntry(
existing: existing,
@@ -47,11 +47,27 @@ class FinanceService {
);
}
Future<void> markJobPaid(String jobId) async {
Future<void> reportJobPayment(String jobId) async {
final existing = await _pb.collection('finance_entries').getFullList(
filter: 'job_id = "$jobId"',
batch: 200,
);
filter: 'job_id = "$jobId"',
batch: 200,
);
for (final record in existing) {
await _pb.collection('finance_entries').update(
record.id,
body: {
'status': 'reported',
'paid_at': null,
},
);
}
}
Future<void> confirmJobPayment(String jobId) async {
final existing = await _pb.collection('finance_entries').getFullList(
filter: 'job_id = "$jobId"',
batch: 200,
);
final paidAt = DateTime.now().toIso8601String();
for (final record in existing) {
await _pb.collection('finance_entries').update(
@@ -66,9 +82,10 @@ class FinanceService {
Future<void> deletePendingEntriesForJob(String jobId) async {
final existing = await _pb.collection('finance_entries').getFullList(
filter: 'job_id = "$jobId" && status = "pending"',
batch: 200,
);
filter:
'job_id = "$jobId" && (status = "pending" || status = "reported")',
batch: 200,
);
for (final record in existing) {
await _pb.collection('finance_entries').delete(record.id);
}
@@ -88,8 +105,7 @@ class FinanceService {
try {
match = existing.firstWhere(
(record) =>
record.data['tenant_id'] == tenantId &&
record.data['type'] == type,
record.data['tenant_id'] == tenantId && record.data['type'] == type,
);
} catch (_) {
match = null;
+27 -17
View File
@@ -19,6 +19,7 @@ class JobHistoryEntry {
enum JobHistoryAction {
accepted,
stepCompleted,
handedToClinic,
approved,
revisionRequested,
@@ -29,6 +30,7 @@ enum JobHistoryAction {
extension JobHistoryActionExt on JobHistoryAction {
String get value => switch (this) {
JobHistoryAction.accepted => 'accepted',
JobHistoryAction.stepCompleted => 'step_completed',
JobHistoryAction.handedToClinic => 'handed_to_clinic',
JobHistoryAction.approved => 'approved',
JobHistoryAction.revisionRequested => 'revision_requested',
@@ -43,21 +45,21 @@ class JobHistoryService {
PocketBase get _pb => PocketBaseClient.instance.pb;
String get _currentUserId =>
(_pb.authStore.record?.id) ?? (_pb.authStore.model as dynamic)?.id as String? ?? '';
String get _currentUserId => _pb.authStore.record?.id ?? '';
Future<List<JobHistoryEntry>> listForJob(String jobId) async {
try {
final result = await _pb.collection('job_status_history').getList(
filter: 'job_id = "$jobId"',
perPage: 200,
);
filter: 'job_id = "$jobId"',
perPage: 200,
);
return (result.items.map((r) {
final j = r.toJson();
String? str(dynamic v) {
final s = v as String?;
return (s == null || s.isEmpty) ? null : s;
}
return JobHistoryEntry(
id: j['id'] as String,
action: _parseAction(j['action_type'] as String? ?? ''),
@@ -65,30 +67,38 @@ class JobHistoryService {
note: str(j['note']),
createdAt: DateTime.parse(j['created'] as String),
);
}).toList()..sort((a, b) => a.createdAt.compareTo(b.createdAt)));
}).toList()
..sort((a, b) => a.createdAt.compareTo(b.createdAt)));
} catch (_) {
return [];
}
}
static JobHistoryAction _parseAction(String s) => switch (s) {
'accepted' => JobHistoryAction.accepted,
'handed_to_clinic' => JobHistoryAction.handedToClinic,
'approved' => JobHistoryAction.approved,
'accepted' => JobHistoryAction.accepted,
'step_completed' => JobHistoryAction.stepCompleted,
'handed_to_clinic' => JobHistoryAction.handedToClinic,
'approved' => JobHistoryAction.approved,
'revision_requested' => JobHistoryAction.revisionRequested,
'delivered' => JobHistoryAction.delivered,
_ => JobHistoryAction.cancelled,
'delivered' => JobHistoryAction.delivered,
_ => JobHistoryAction.cancelled,
};
static JobStep _parseStep(String s) => switch (s) {
'olcu_kontrol' => JobStep.olcuKontrol,
'dijital_tasarim' => JobStep.dijitalTasarim,
'model_hazirlik' => JobStep.modelHazirlik,
'alt_yapi_prova' => JobStep.altYapiProva,
'ust_yapi_prova' => JobStep.ustYapiProva,
'mum_prova' => JobStep.mumProva,
'disler_prova' => JobStep.dislerProva,
'dayanak_prova' => JobStep.dayanakProva,
'kron_prova' => JobStep.kronProva,
'cila_bitim' => JobStep.cilaBitim,
_ => JobStep.olcu,
'mum_prova' => JobStep.mumProva,
'disler_prova' => JobStep.dislerProva,
'dayanak_prova' => JobStep.dayanakProva,
'kron_prova' => JobStep.kronProva,
'fotograf_onay' => JobStep.fotografOnay,
'kalite_kontrol' => JobStep.kaliteKontrol,
'teslim_oncesi_kontrol' => JobStep.teslimOncesiKontrol,
'cila_bitim' => JobStep.cilaBitim,
_ => JobStep.olcu,
};
Future<void> append({
+17 -5
View File
@@ -16,21 +16,33 @@ class RealtimeService {
required void Function(RecordSubscriptionEvent) onEvent,
}) {
UnsubFn? cancel;
bool disposeRequested = false;
bool disposed = false;
_pb.collection(collection).subscribe(topic, onEvent, filter: filter).then((fn) {
_pb
.collection(collection)
.subscribe(topic, onEvent, filter: filter)
.then((fn) async {
if (disposeRequested) {
disposed = true;
await fn();
return;
}
cancel = fn;
});
}).catchError((_) {});
return () async {
if (disposed) return;
disposeRequested = true;
try {
final fn = cancel;
if (fn != null) {
disposed = true;
await fn();
} else {
await _pb.collection(collection).unsubscribe(topic);
}
} catch (_) {
await _pb.collection(collection).unsubscribe(topic);
// swallow — never globally unsubscribe the topic here, because
// other screens may still be subscribed to the same collection/topic.
}
};
}