207 lines
6.9 KiB
Dart
207 lines
6.9 KiB
Dart
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',
|
||
};
|
||
}
|