Files
2026-06-20 18:24:40 +03:00

207 lines
6.9 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'job.dart';
enum TenantKind { lab, clinic }
enum TenantRole {
owner,
admin,
technician, // lab: işler + ürünler
delivery, // lab: işler
finance, // lab+clinic: finans
doctor, // clinic: işler + hastalar
member, // legacy — full access
;
String get value => name;
String get label => switch (this) {
TenantRole.owner => 'Sahibi',
TenantRole.admin => 'Yönetici',
TenantRole.technician => 'Teknisyen',
TenantRole.delivery => 'Teslimat Elemanı',
TenantRole.finance => 'Finans Elemanı',
TenantRole.doctor => 'Hekim',
TenantRole.member => 'Üye',
};
}
enum TenantPlan { starter, pro, enterprise }
class Tenant {
const Tenant({
required this.id,
required this.kind,
required this.memberNumber,
required this.companyName,
this.companyAddress,
this.city,
this.district,
this.latitude,
this.longitude,
this.logo,
this.defaultCurrency = 'TRY',
this.status = 'active',
this.plan,
this.maxMembers,
this.workflowOverrideStepKeys = const [],
});
final String id;
final TenantKind kind;
final String memberNumber;
final String companyName;
final String? companyAddress;
final String? city;
final String? district;
final double? latitude;
final double? longitude;
final String? logo;
final String defaultCurrency;
final String status;
final TenantPlan? plan;
final int? maxMembers;
final List<String> workflowOverrideStepKeys;
bool get isLab => kind == TenantKind.lab;
bool get isClinic => kind == TenantKind.clinic;
bool get hasLocation => latitude != null && longitude != null;
List<JobStep> get workflowOverrideSteps => workflowOverrideStepKeys
.map(Job.parseStepValue)
.where((step) => step.isLabOptional)
.toList();
String get locationLabel {
final parts = [
if ((district ?? '').trim().isNotEmpty) district!.trim(),
if ((city ?? '').trim().isNotEmpty) city!.trim(),
];
if (parts.isNotEmpty) return parts.join(' / ');
return (companyAddress ?? '').trim();
}
factory Tenant.fromJson(Map<String, dynamic> j) => Tenant(
id: j['id'] as String,
kind: j['kind'] == 'lab' ? TenantKind.lab : TenantKind.clinic,
memberNumber: (j['member_number'] as String?) ?? '',
companyName: j['company_name'] as String,
companyAddress: j['company_address'] as String?,
city: j['city'] as String?,
district: j['district'] as String?,
latitude: (j['latitude'] as num?)?.toDouble(),
longitude: (j['longitude'] as num?)?.toDouble(),
logo: j['logo'] as String?,
defaultCurrency: (j['default_currency'] as String?) ?? 'TRY',
status: (j['status'] as String?) ?? 'active',
plan: _parsePlan(j['plan'] as String?),
maxMembers: (j['max_members'] as num?)?.toInt(),
workflowOverrideStepKeys: j['workflow_overrides'] is List
? (j['workflow_overrides'] as List)
.map((e) => e.toString())
.toList()
: const [],
);
static TenantPlan? _parsePlan(String? p) => switch (p) {
'starter' => TenantPlan.starter,
'pro' => TenantPlan.pro,
'enterprise' => TenantPlan.enterprise,
_ => null,
};
static TenantPlan parsePlanValue(String? value) =>
_parsePlan(value) ?? TenantPlan.starter;
}
class TenantMembership {
const TenantMembership({
required this.id,
required this.tenant,
required this.role,
});
final String id;
final Tenant tenant;
final TenantRole role;
// ── Access helpers ────────────────────────────────────────────────────────
bool get isOwner => role == TenantRole.owner;
bool get isAdmin => role == TenantRole.admin || role == TenantRole.owner;
bool get canManageUsers =>
role == TenantRole.owner || role == TenantRole.admin;
bool get canManageJobs => role != TenantRole.finance;
bool get canManageFinance =>
role == TenantRole.owner ||
role == TenantRole.admin ||
role == TenantRole.finance ||
role == TenantRole.member;
bool get canManageProducts =>
role == TenantRole.owner ||
role == TenantRole.admin ||
role == TenantRole.technician ||
role == TenantRole.member;
bool get canViewPatients =>
role == TenantRole.owner ||
role == TenantRole.admin ||
role == TenantRole.doctor ||
role == TenantRole.member;
bool get canManageConnections =>
role == TenantRole.owner ||
role == TenantRole.admin ||
role == TenantRole.member;
// ── Fine-grained job actions ──────────────────────────────────────────────
/// Can create new jobs (clinic side: owner/admin/doctor/member; not delivery/finance)
bool get canCreateJobs =>
role != TenantRole.delivery && role != TenantRole.finance;
/// Can confirm physical delivery (delivery role + supervisors)
bool get canDeliverJobs => role != TenantRole.finance;
/// Can cancel or fully manage job lifecycle (not delivery-only or finance)
bool get canCancelJobs =>
role == TenantRole.owner ||
role == TenantRole.admin ||
role == TenantRole.member ||
role == TenantRole.doctor;
/// Primary focus is delivery — restrict to delivery-relevant UI
bool get isDeliveryOnly => role == TenantRole.delivery;
// ── Nav visibility ────────────────────────────────────────────────────────
bool get showDashboard => true;
bool get showJobs => canManageJobs;
bool get showProducts => tenant.isLab && canManageProducts;
bool get showPatients => tenant.isClinic && canViewPatients;
bool get showConnections => canManageConnections;
bool get showFinance => canManageFinance;
factory TenantMembership.fromJson(Map<String, dynamic> j) {
final expand = j['expand'] as Map<String, dynamic>?;
final tenantData = expand?['tenant_id'] as Map<String, dynamic>?;
return TenantMembership(
id: j['id'] as String,
tenant: Tenant.fromJson(tenantData!),
role: parseRole(j['role'] as String),
);
}
static TenantRole parseRole(String r) => switch (r) {
'owner' => TenantRole.owner,
'admin' => TenantRole.admin,
'technician' => TenantRole.technician,
'delivery' => TenantRole.delivery,
'finance' => TenantRole.finance,
'doctor' => TenantRole.doctor,
_ => TenantRole.member,
};
String get roleLabel => switch (role) {
TenantRole.owner => 'Sahibi',
TenantRole.admin => 'Yönetici',
TenantRole.technician => 'Teknisyen',
TenantRole.delivery => 'Teslimat Elemanı',
TenantRole.finance => 'Finans Elemanı',
TenantRole.doctor => 'Hekim',
TenantRole.member => 'Üye',
};
}