Add pricing entry flow and platform admin foundations
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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.
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user