feat-ultimi_servizi-contratti_in_scadenza #12
111
lib/features/attachments/models/attachment_model.dart
Normal file
111
lib/features/attachments/models/attachment_model.dart
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
class AttachmentModel extends Equatable {
|
||||||
|
final String? id;
|
||||||
|
final DateTime? createdAt;
|
||||||
|
final String? customerId;
|
||||||
|
final String? operationId;
|
||||||
|
final String name;
|
||||||
|
final String extension;
|
||||||
|
final String storagePath;
|
||||||
|
final int fileSize;
|
||||||
|
final Uint8List? localBytes;
|
||||||
|
final String companyId;
|
||||||
|
|
||||||
|
const AttachmentModel({
|
||||||
|
this.id,
|
||||||
|
this.createdAt,
|
||||||
|
this.customerId,
|
||||||
|
this.operationId,
|
||||||
|
required this.name,
|
||||||
|
required this.extension,
|
||||||
|
required this.storagePath,
|
||||||
|
required this.fileSize,
|
||||||
|
this.localBytes,
|
||||||
|
required this.companyId,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
id,
|
||||||
|
createdAt,
|
||||||
|
customerId,
|
||||||
|
operationId,
|
||||||
|
name,
|
||||||
|
extension,
|
||||||
|
storagePath,
|
||||||
|
fileSize,
|
||||||
|
localBytes,
|
||||||
|
companyId,
|
||||||
|
];
|
||||||
|
|
||||||
|
bool get isLocal => localBytes != null;
|
||||||
|
|
||||||
|
bool get isPdf => extension.toLowerCase().replaceAll('.', '') == 'pdf';
|
||||||
|
|
||||||
|
String get sizeFormatted {
|
||||||
|
if (fileSize <= 0) return "0 B";
|
||||||
|
const suffixes = ["B", "KB", "MB", "GB", "TB"];
|
||||||
|
var i = (fileSize.toString().length - 1) ~/ 3;
|
||||||
|
if (i >= suffixes.length) i = suffixes.length - 1;
|
||||||
|
double num = fileSize / (1 << (i * 10));
|
||||||
|
return "${num.toStringAsFixed(i == 0 ? 0 : 1)} ${suffixes[i]}";
|
||||||
|
}
|
||||||
|
|
||||||
|
AttachmentModel copyWith({
|
||||||
|
String? id,
|
||||||
|
DateTime? createdAt,
|
||||||
|
String? customerId,
|
||||||
|
String? operationId,
|
||||||
|
String? name,
|
||||||
|
String? extension,
|
||||||
|
String? storagePath,
|
||||||
|
int? fileSize,
|
||||||
|
Uint8List? localBytes,
|
||||||
|
String? companyId,
|
||||||
|
}) => AttachmentModel(
|
||||||
|
id: id ?? this.id,
|
||||||
|
createdAt: createdAt ?? this.createdAt,
|
||||||
|
customerId: customerId ?? this.customerId,
|
||||||
|
operationId: operationId ?? this.operationId,
|
||||||
|
name: name ?? this.name,
|
||||||
|
extension: extension ?? this.extension,
|
||||||
|
storagePath: storagePath ?? this.storagePath,
|
||||||
|
fileSize: fileSize ?? this.fileSize,
|
||||||
|
localBytes: localBytes ?? this.localBytes,
|
||||||
|
companyId: companyId ?? this.companyId,
|
||||||
|
);
|
||||||
|
|
||||||
|
factory AttachmentModel.fromMap(Map<String, dynamic> map) {
|
||||||
|
return AttachmentModel(
|
||||||
|
id: map['id'] as String,
|
||||||
|
createdAt: map['created_at'] != null
|
||||||
|
? DateTime.parse(map['created_at'])
|
||||||
|
: null,
|
||||||
|
customerId: map['customer_id'] as String?,
|
||||||
|
operationId: map['operation_id'] as String?,
|
||||||
|
name: map['name'] as String,
|
||||||
|
extension: map['extension'] as String,
|
||||||
|
storagePath: map['storage_path'] as String,
|
||||||
|
fileSize: map['file_size'] is int
|
||||||
|
? map['file_size']
|
||||||
|
: int.tryParse(map['file_size']?.toString() ?? '0') ?? 0,
|
||||||
|
companyId: map['company_id'] as String,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() {
|
||||||
|
return {
|
||||||
|
if (id != null) 'id': id,
|
||||||
|
'name': name,
|
||||||
|
'extension': extension,
|
||||||
|
'storage_path': storagePath,
|
||||||
|
'customer_id': customerId,
|
||||||
|
'operation_id': operationId,
|
||||||
|
'file_size': fileSize,
|
||||||
|
'company_id': companyId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -135,8 +135,8 @@ class CustomerCubit extends Cubit<CustomerState> {
|
|||||||
String? email,
|
String? email,
|
||||||
}) async {
|
}) async {
|
||||||
final newCustomer = CustomerModel(
|
final newCustomer = CustomerModel(
|
||||||
nome: name,
|
name: name,
|
||||||
telefono: phone ?? '',
|
phoneNumber: phone ?? '',
|
||||||
email: email ?? '',
|
email: email ?? '',
|
||||||
companyId: _sessionCubit.state.company!.id!,
|
companyId: _sessionCubit.state.company!.id!,
|
||||||
note: '',
|
note: '',
|
||||||
|
|||||||
@@ -1,74 +1,74 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flux/core/utils/extensions.dart';
|
import 'package:flux/core/utils/extensions.dart';
|
||||||
import 'package:flux/features/customers/models/customer_file_model.dart';
|
import 'package:flux/features/attachments/models/attachment_model.dart';
|
||||||
|
|
||||||
class CustomerModel extends Equatable {
|
class CustomerModel extends Equatable {
|
||||||
final String? id; // Bigint in SQL
|
final String? id; // Bigint in SQL
|
||||||
final DateTime? createdAt;
|
final DateTime? createdAt;
|
||||||
final String nome;
|
final String name;
|
||||||
final String telefono;
|
final String phoneNumber;
|
||||||
final String email;
|
final String email;
|
||||||
final String note;
|
final String note;
|
||||||
final DateTime? dataUltimoContatto;
|
final DateTime? lastContactDate;
|
||||||
final bool nonDisturbare;
|
final bool doNotDisturb;
|
||||||
final String companyId; // UUID
|
final String companyId; // UUID
|
||||||
final bool isActive;
|
final bool isActive;
|
||||||
final List<CustomerFileModel> files;
|
final List<AttachmentModel> attachments;
|
||||||
|
|
||||||
const CustomerModel({
|
const CustomerModel({
|
||||||
this.id,
|
this.id,
|
||||||
this.createdAt,
|
this.createdAt,
|
||||||
required this.nome,
|
required this.name,
|
||||||
required this.telefono,
|
required this.phoneNumber,
|
||||||
required this.email,
|
required this.email,
|
||||||
required this.note,
|
required this.note,
|
||||||
this.dataUltimoContatto,
|
this.lastContactDate,
|
||||||
this.nonDisturbare = false,
|
this.doNotDisturb = false,
|
||||||
required this.companyId,
|
required this.companyId,
|
||||||
this.isActive = true,
|
this.isActive = true,
|
||||||
this.files = const [],
|
this.attachments = const [],
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [
|
List<Object?> get props => [
|
||||||
id,
|
id,
|
||||||
createdAt,
|
createdAt,
|
||||||
nome,
|
name,
|
||||||
telefono,
|
phoneNumber,
|
||||||
email,
|
email,
|
||||||
note,
|
note,
|
||||||
dataUltimoContatto,
|
lastContactDate,
|
||||||
nonDisturbare,
|
doNotDisturb,
|
||||||
companyId,
|
companyId,
|
||||||
isActive,
|
isActive,
|
||||||
files,
|
attachments,
|
||||||
];
|
];
|
||||||
|
|
||||||
CustomerModel copyWith({
|
CustomerModel copyWith({
|
||||||
String? id,
|
String? id,
|
||||||
DateTime? createdAt,
|
DateTime? createdAt,
|
||||||
String? nome,
|
String? name,
|
||||||
String? telefono,
|
String? phoneNumber,
|
||||||
String? email,
|
String? email,
|
||||||
String? note,
|
String? note,
|
||||||
DateTime? dataUltimoContatto,
|
DateTime? lastContactDate,
|
||||||
bool? nonDisturbare,
|
bool? doNotDisturb,
|
||||||
String? companyId,
|
String? companyId,
|
||||||
bool? isActive,
|
bool? isActive,
|
||||||
List<CustomerFileModel>? files,
|
List<AttachmentModel>? attachments,
|
||||||
}) {
|
}) {
|
||||||
return CustomerModel(
|
return CustomerModel(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
createdAt: createdAt ?? this.createdAt,
|
createdAt: createdAt ?? this.createdAt,
|
||||||
nome: nome ?? this.nome,
|
name: name ?? this.name,
|
||||||
telefono: telefono ?? this.telefono,
|
phoneNumber: phoneNumber ?? this.phoneNumber,
|
||||||
email: email ?? this.email,
|
email: email ?? this.email,
|
||||||
note: note ?? this.note,
|
note: note ?? this.note,
|
||||||
dataUltimoContatto: dataUltimoContatto ?? this.dataUltimoContatto,
|
lastContactDate: lastContactDate ?? this.lastContactDate,
|
||||||
nonDisturbare: nonDisturbare ?? this.nonDisturbare,
|
doNotDisturb: doNotDisturb ?? this.doNotDisturb,
|
||||||
companyId: companyId ?? this.companyId,
|
companyId: companyId ?? this.companyId,
|
||||||
isActive: isActive ?? this.isActive,
|
isActive: isActive ?? this.isActive,
|
||||||
files: files ?? this.files,
|
attachments: attachments ?? this.attachments,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,34 +78,29 @@ class CustomerModel extends Equatable {
|
|||||||
createdAt: map['created_at'] != null
|
createdAt: map['created_at'] != null
|
||||||
? DateTime.parse(map['created_at'])
|
? DateTime.parse(map['created_at'])
|
||||||
: null,
|
: null,
|
||||||
nome: (map['nome'] as String).myFormat(),
|
name: (map['name'] as String).myFormat(),
|
||||||
telefono: map['telefono'],
|
phoneNumber: map['phone_number'],
|
||||||
email: map['email'],
|
email: map['email'],
|
||||||
note: map['note'] ?? '',
|
note: map['note'] ?? '',
|
||||||
dataUltimoContatto: map['data_ultimo_contatto'] != null
|
lastContactDate: map['last_contact_date'] != null
|
||||||
? DateTime.parse(map['data_ultimo_contatto'])
|
? DateTime.parse(map['last_contact_date'])
|
||||||
: null,
|
: null,
|
||||||
nonDisturbare: map['non_disturbare'] ?? false,
|
doNotDisturb: map['do_not_disturb'] ?? false,
|
||||||
companyId: map['company_id'] as String,
|
companyId: map['company_id'] as String,
|
||||||
isActive: map['is_active'] ?? true,
|
isActive: map['is_active'] ?? true,
|
||||||
files:
|
|
||||||
(map['customer_file'] as List?)
|
|
||||||
?.map((x) => CustomerFileModel.fromMap(x))
|
|
||||||
.toList() ??
|
|
||||||
const [],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
return {
|
return {
|
||||||
if (id != null) 'id': id,
|
if (id != null) 'id': id,
|
||||||
'nome': nome.toLowerCase().trim(),
|
'name': name.toLowerCase().trim(),
|
||||||
'telefono': telefono,
|
'phone_number': phoneNumber,
|
||||||
'email': email.toLowerCase().trim(),
|
'email': email.toLowerCase().trim(),
|
||||||
'note': note,
|
'note': note,
|
||||||
if (dataUltimoContatto != null)
|
if (lastContactDate != null)
|
||||||
'data_ultimo_contatto': dataUltimoContatto!.toIso8601String(),
|
'last_contact_date': lastContactDate!.toIso8601String(),
|
||||||
'non_disturbare': nonDisturbare,
|
'do_not_disturb': doNotDisturb,
|
||||||
'company_id': companyId,
|
'company_id': companyId,
|
||||||
'is_active': isActive,
|
'is_active': isActive,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ class _CustomerDetailScreenState extends State<CustomerDetailScreen> {
|
|||||||
backgroundColor: context.background,
|
backgroundColor: context.background,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(
|
title: Text(
|
||||||
widget.customer.nome,
|
widget.customer.name,
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
backgroundColor: context.background,
|
backgroundColor: context.background,
|
||||||
@@ -103,7 +103,7 @@ class _CustomerDetailScreenState extends State<CustomerDetailScreen> {
|
|||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_infoTile(Icons.phone_android, "Telefono", widget.customer.telefono),
|
_infoTile(Icons.phone_android, "Telefono", widget.customer.phoneNumber),
|
||||||
_infoTile(
|
_infoTile(
|
||||||
Icons.email_outlined,
|
Icons.email_outlined,
|
||||||
"Email",
|
"Email",
|
||||||
@@ -117,7 +117,7 @@ class _CustomerDetailScreenState extends State<CustomerDetailScreen> {
|
|||||||
: widget.customer.note,
|
: widget.customer.note,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
if (widget.customer.nonDisturbare)
|
if (widget.customer.doNotDisturb)
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@@ -191,8 +191,8 @@ class _CustomerDetailScreenState extends State<CustomerDetailScreen> {
|
|||||||
context: context,
|
context: context,
|
||||||
builder: (context) => QrUploadDialog(
|
builder: (context) => QrUploadDialog(
|
||||||
deepLinkUrl:
|
deepLinkUrl:
|
||||||
'fluxapp://customer/${widget.customer.id}/upload?name=${Uri.encodeComponent(widget.customer.nome)}',
|
'fluxapp://customer/${widget.customer.id}/upload?name=${Uri.encodeComponent(widget.customer.name)}',
|
||||||
title: 'Scatta per ${widget.customer.nome}',
|
title: 'Scatta per ${widget.customer.name}',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -30,15 +30,15 @@ class _CustomerFormState extends State<CustomerForm> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
// Se widget.customer è null, i campi saranno vuoti
|
// Se widget.customer è null, i campi saranno vuoti
|
||||||
_nomeController = TextEditingController(text: widget.customer?.nome ?? '');
|
_nomeController = TextEditingController(text: widget.customer?.name ?? '');
|
||||||
_telefonoController = TextEditingController(
|
_telefonoController = TextEditingController(
|
||||||
text: widget.customer?.telefono ?? '',
|
text: widget.customer?.phoneNumber ?? '',
|
||||||
);
|
);
|
||||||
_emailController = TextEditingController(
|
_emailController = TextEditingController(
|
||||||
text: widget.customer?.email ?? '',
|
text: widget.customer?.email ?? '',
|
||||||
);
|
);
|
||||||
_noteController = TextEditingController(text: widget.customer?.note ?? '');
|
_noteController = TextEditingController(text: widget.customer?.note ?? '');
|
||||||
_nonDisturbare = widget.customer?.nonDisturbare ?? false;
|
_nonDisturbare = widget.customer?.doNotDisturb ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -56,19 +56,19 @@ class _CustomerFormState extends State<CustomerForm> {
|
|||||||
// o creandone uno da zero, preservando l'ID in caso di modifica.
|
// o creandone uno da zero, preservando l'ID in caso di modifica.
|
||||||
final updatedCustomer =
|
final updatedCustomer =
|
||||||
widget.customer?.copyWith(
|
widget.customer?.copyWith(
|
||||||
nome: _nomeController.text.trim(),
|
name: _nomeController.text.trim(),
|
||||||
telefono: _telefonoController.text.trim(),
|
phoneNumber: _telefonoController.text.trim(),
|
||||||
email: _emailController.text.trim(),
|
email: _emailController.text.trim(),
|
||||||
note: _noteController.text.trim(),
|
note: _noteController.text.trim(),
|
||||||
nonDisturbare: _nonDisturbare,
|
doNotDisturb: _nonDisturbare,
|
||||||
) ??
|
) ??
|
||||||
CustomerModel(
|
CustomerModel(
|
||||||
// Caso nuovo cliente
|
// Caso nuovo cliente
|
||||||
nome: _nomeController.text.trim(),
|
name: _nomeController.text.trim(),
|
||||||
telefono: _telefonoController.text.trim(),
|
phoneNumber: _telefonoController.text.trim(),
|
||||||
email: _emailController.text.trim(),
|
email: _emailController.text.trim(),
|
||||||
note: _noteController.text.trim(),
|
note: _noteController.text.trim(),
|
||||||
nonDisturbare: _nonDisturbare,
|
doNotDisturb: _nonDisturbare,
|
||||||
companyId: '', // Verrà iniettato dal Bloc o dal chiamante
|
companyId: '', // Verrà iniettato dal Bloc o dal chiamante
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ class _CustomerSearchSheetState extends State<CustomerSearchSheet> {
|
|||||||
if (nuovoCliente != null) {
|
if (nuovoCliente != null) {
|
||||||
operationsCubit.updateField(
|
operationsCubit.updateField(
|
||||||
customerId: nuovoCliente.id,
|
customerId: nuovoCliente.id,
|
||||||
customerDisplayName: nuovoCliente.nome,
|
customerDisplayName: nuovoCliente.name,
|
||||||
);
|
);
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -151,7 +151,7 @@ class _CustomerSearchSheetState extends State<CustomerSearchSheet> {
|
|||||||
final customer = state.customers[index];
|
final customer = state.customers[index];
|
||||||
// Assumo che il tuo CustomerModel abbia le proprietà name e surname.
|
// Assumo che il tuo CustomerModel abbia le proprietà name e surname.
|
||||||
// Adatta queste variabili al tuo modello reale!
|
// Adatta queste variabili al tuo modello reale!
|
||||||
final displayName = customer.nome.trim();
|
final displayName = customer.name.trim();
|
||||||
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ class _CustomerTile extends StatelessWidget {
|
|||||||
radius: 24,
|
radius: 24,
|
||||||
backgroundColor: context.accent.withValues(alpha: 0.1),
|
backgroundColor: context.accent.withValues(alpha: 0.1),
|
||||||
child: Text(
|
child: Text(
|
||||||
customer.nome.isNotEmpty ? customer.nome[0].toUpperCase() : '?',
|
customer.name.isNotEmpty ? customer.name[0].toUpperCase() : '?',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: context.accent,
|
color: context.accent,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
@@ -174,7 +174,7 @@ class _CustomerTile extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
customer.nome,
|
customer.name,
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
|
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
|
||||||
),
|
),
|
||||||
subtitle: Padding(
|
subtitle: Padding(
|
||||||
@@ -184,7 +184,7 @@ class _CustomerTile extends StatelessWidget {
|
|||||||
Icon(Icons.phone_android, size: 14, color: context.secondaryText),
|
Icon(Icons.phone_android, size: 14, color: context.secondaryText),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Text(
|
Text(
|
||||||
customer.telefono,
|
customer.phoneNumber,
|
||||||
style: TextStyle(color: context.secondaryText),
|
style: TextStyle(color: context.secondaryText),
|
||||||
),
|
),
|
||||||
if (customer.email.isNotEmpty) ...[
|
if (customer.email.isNotEmpty) ...[
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:file_picker/file_picker.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flux/core/blocs/session/session_cubit.dart';
|
import 'package:flux/core/blocs/session/session_cubit.dart';
|
||||||
import 'package:flux/core/utils/extensions.dart';
|
import 'package:flux/core/utils/extensions.dart';
|
||||||
|
import 'package:flux/features/attachments/models/attachment_model.dart';
|
||||||
import 'package:flux/features/customers/data/customer_repository.dart';
|
import 'package:flux/features/customers/data/customer_repository.dart';
|
||||||
import 'package:flux/features/customers/models/customer_file_model.dart';
|
import 'package:flux/features/customers/models/customer_file_model.dart';
|
||||||
import 'package:flux/features/operations/models/operation_file_model.dart';
|
import 'package:flux/features/operations/models/operation_file_model.dart';
|
||||||
@@ -21,11 +22,8 @@ class OperationsRepository {
|
|||||||
.from('operation')
|
.from('operation')
|
||||||
.select('''
|
.select('''
|
||||||
*,
|
*,
|
||||||
customer(nome),
|
customer(name),
|
||||||
energy_operation(*),
|
staff_member(name)
|
||||||
fin_operation(*),
|
|
||||||
entertainment_operation(*),
|
|
||||||
operation_file(*)
|
|
||||||
''')
|
''')
|
||||||
.eq('id', id)
|
.eq('id', id)
|
||||||
.single();
|
.single();
|
||||||
@@ -45,16 +43,13 @@ class OperationsRepository {
|
|||||||
DateTimeRange? dateRange,
|
DateTimeRange? dateRange,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
// Nota: 'customer(name, surname)' serve per il display name nella card
|
|
||||||
var query = _supabase
|
var query = _supabase
|
||||||
.from('operation')
|
.from('operation')
|
||||||
.select('''
|
.select('''
|
||||||
*,
|
*,
|
||||||
customer(nome),
|
customer(name),
|
||||||
energy_operation(*),
|
staff_member(name),
|
||||||
fin_operation(*),
|
attachments(*)
|
||||||
entertainment_operation(*),
|
|
||||||
operation_file(*)
|
|
||||||
''')
|
''')
|
||||||
.eq('company_id', companyId);
|
.eq('company_id', companyId);
|
||||||
|
|
||||||
@@ -68,7 +63,7 @@ class OperationsRepository {
|
|||||||
if (searchTerm != null && searchTerm.isNotEmpty) {
|
if (searchTerm != null && searchTerm.isNotEmpty) {
|
||||||
// Filtra sui campi della tabella principale O su quelli della tabella joinata
|
// Filtra sui campi della tabella principale O su quelli della tabella joinata
|
||||||
query = query.or(
|
query = query.or(
|
||||||
'number.ilike.%$searchTerm%,note.ilike.%$searchTerm%,customer.nome.ilike.%$searchTerm%',
|
'reference.ilike.%$searchTerm%,note.ilike.%$searchTerm%,customer.name.ilike.%$searchTerm%',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +75,7 @@ class OperationsRepository {
|
|||||||
.map((map) => OperationModel.fromMap(map))
|
.map((map) => OperationModel.fromMap(map))
|
||||||
.toList();
|
.toList();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw Exception('Errore nel caricamento servizi: $e');
|
throw Exception('$e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,66 +107,9 @@ class OperationsRepository {
|
|||||||
|
|
||||||
final String newId = operationData['id'];
|
final String newId = operationData['id'];
|
||||||
|
|
||||||
// 2. MODIFICA: Pulizia atomica dei figli
|
|
||||||
// Se stiamo modificando (id != null), resettiamo le tabelle collegate
|
|
||||||
if (operation.id != null) {
|
|
||||||
await Future.wait([
|
|
||||||
_supabase.from('energy_operation').delete().eq('operation_id', newId),
|
|
||||||
_supabase.from('fin_operation').delete().eq('operation_id', newId),
|
|
||||||
_supabase
|
|
||||||
.from('entertainment_operation')
|
|
||||||
.delete()
|
|
||||||
.eq('operation_id', newId),
|
|
||||||
// Aggiungi qui eventuali altre tabelle pivot o file
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Inserimento dei moduli in parallelo per velocità
|
|
||||||
final List<Future> insertTasks = [];
|
|
||||||
|
|
||||||
if (operation.energyOperations.isNotEmpty) {
|
|
||||||
insertTasks.add(
|
|
||||||
_supabase
|
|
||||||
.from('energy_operation')
|
|
||||||
.insert(
|
|
||||||
operation.energyOperations
|
|
||||||
.map((item) => item.copyWith(operationId: newId).toMap())
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (operation.finOperations.isNotEmpty) {
|
|
||||||
insertTasks.add(
|
|
||||||
_supabase
|
|
||||||
.from('fin_operation')
|
|
||||||
.insert(
|
|
||||||
operation.finOperations
|
|
||||||
.map((item) => item.copyWith(operationId: newId).toMap())
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (operation.entertainmentOperations.isNotEmpty) {
|
|
||||||
insertTasks.add(
|
|
||||||
_supabase
|
|
||||||
.from('entertainment_operation')
|
|
||||||
.insert(
|
|
||||||
operation.entertainmentOperations
|
|
||||||
.map((item) => item.copyWith(operationId: newId).toMap())
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (insertTasks.isNotEmpty) {
|
|
||||||
await Future.wait(insertTasks);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. UPLOAD DEI FILE LOCALI (Nuovi)
|
// 4. UPLOAD DEI FILE LOCALI (Nuovi)
|
||||||
// Filtriamo solo i file che non hanno ancora un ID (quindi sono locali)
|
// Filtriamo solo i file che non hanno ancora un ID (quindi sono locali)
|
||||||
final localFilesToUpload = operation.files
|
final localFilesToUpload = operation.attachments
|
||||||
.where((f) => f.id == null)
|
.where((f) => f.id == null)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
@@ -202,7 +140,7 @@ class OperationsRepository {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// B. Inserimento riga nel DB relazionale
|
// B. Inserimento riga nel DB relazionale
|
||||||
await _supabase.from('operation_file').insert(fileToSave.toMap());
|
await _supabase.from('attachment').insert(fileToSave.toMap());
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadTasks.add(uploadAndLink());
|
uploadTasks.add(uploadAndLink());
|
||||||
@@ -219,10 +157,9 @@ class OperationsRepository {
|
|||||||
.from('operation')
|
.from('operation')
|
||||||
.select('''
|
.select('''
|
||||||
*,
|
*,
|
||||||
energy_operation(*),
|
staff_member(name),
|
||||||
fin_operation(*),
|
customer(name),
|
||||||
entertainment_operation(*),
|
attachments(*)
|
||||||
operation_file(*)
|
|
||||||
''')
|
''')
|
||||||
.eq('id', newId)
|
.eq('id', newId)
|
||||||
.single();
|
.single();
|
||||||
@@ -230,7 +167,7 @@ class OperationsRepository {
|
|||||||
return OperationModel.fromMap(updatedOperationData);
|
return OperationModel.fromMap(updatedOperationData);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Qui potresti aggiungere una logica di "rollback manuale" se necessario
|
// Qui potresti aggiungere una logica di "rollback manuale" se necessario
|
||||||
throw Exception('Errore durante il salvataggio corazzato: $e');
|
throw Exception('$e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,7 +176,7 @@ class OperationsRepository {
|
|||||||
try {
|
try {
|
||||||
await _supabase.from('operation').delete().eq('id', id);
|
await _supabase.from('operation').delete().eq('id', id);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw Exception('Errore durante l\'eliminazione: $e');
|
throw Exception('$e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,16 +186,17 @@ class OperationsRepository {
|
|||||||
// Cerchiamo i tipi più frequenti associati ai servizi di questa company
|
// Cerchiamo i tipi più frequenti associati ai servizi di questa company
|
||||||
// Nota: dobbiamo passare attraverso la tabella 'operation' per filtrare per company_id
|
// Nota: dobbiamo passare attraverso la tabella 'operation' per filtrare per company_id
|
||||||
final response = await _supabase
|
final response = await _supabase
|
||||||
.from('entertainment_operation')
|
.from('operation')
|
||||||
.select('type, operation!inner(store!inner(company_id))')
|
.select('description')
|
||||||
.eq('operation.store.company_id', companyId)
|
.eq('company_id', companyId)
|
||||||
.limit(100); // Prendiamo un campione
|
.eq('type', 'Entertainment')
|
||||||
|
.limit(50); // Prendiamo un campione
|
||||||
|
|
||||||
// Logica rapida per contare le occorrenze e prendere i primi 5
|
// Logica rapida per contare le occorrenze e prendere i primi 5
|
||||||
final Map<String, int> counts = {};
|
final Map<String, int> counts = {};
|
||||||
for (var item in (response as List)) {
|
for (var item in (response as List)) {
|
||||||
final type = item['type'] as String;
|
final description = item['description'] as String;
|
||||||
counts[type] = (counts[type] ?? 0) + 1;
|
counts[description] = (counts[description] ?? 0) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
var sortedKeys = counts.keys.toList()
|
var sortedKeys = counts.keys.toList()
|
||||||
@@ -276,19 +214,19 @@ class OperationsRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Ascolta in tempo reale i file caricati per una pratica
|
/// Ascolta in tempo reale i file caricati per una pratica
|
||||||
Stream<List<OperationFileModel>> getOperationFilesStream(String operationId) {
|
Stream<List<AttachmentModel>> getOperationFilesStream(String operationId) {
|
||||||
return _supabase
|
return _supabase
|
||||||
.from('operation_file')
|
.from('attachment')
|
||||||
.stream(primaryKey: ['id'])
|
.stream(primaryKey: ['id'])
|
||||||
.eq('operation_id', operationId)
|
.eq('operation_id', operationId)
|
||||||
.order('created_at', ascending: false)
|
.order('created_at', ascending: false)
|
||||||
.map(
|
.map(
|
||||||
(listOfMaps) =>
|
(listOfMaps) =>
|
||||||
listOfMaps.map((map) => OperationFileModel.fromMap(map)).toList(),
|
listOfMaps.map((map) => AttachmentModel.fromMap(map)).toList(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<OperationFileModel> uploadAndRegisterOperationFile({
|
Future<AttachmentModel> uploadAndRegisterOperationFile({
|
||||||
required String operationId,
|
required String operationId,
|
||||||
required PlatformFile pickedFile,
|
required PlatformFile pickedFile,
|
||||||
}) async {
|
}) async {
|
||||||
@@ -299,7 +237,8 @@ class OperationsRepository {
|
|||||||
final storagePath =
|
final storagePath =
|
||||||
'$companyId/operations/$operationId/${DateTime.now().millisecondsSinceEpoch}_$cleanFileName';
|
'$companyId/operations/$operationId/${DateTime.now().millisecondsSinceEpoch}_$cleanFileName';
|
||||||
final int fileSize = pickedFile.size;
|
final int fileSize = pickedFile.size;
|
||||||
final fileToSave = OperationFileModel(
|
final fileToSave = AttachmentModel(
|
||||||
|
companyId: GetIt.I.get<SessionCubit>().state.company!.id!,
|
||||||
operationId: operationId,
|
operationId: operationId,
|
||||||
name: cleanFileName.fileNameWithoutExtension(),
|
name: cleanFileName.fileNameWithoutExtension(),
|
||||||
extension: cleanFileName.fileExtension(),
|
extension: cleanFileName.fileExtension(),
|
||||||
@@ -327,12 +266,12 @@ class OperationsRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final response = await _supabase
|
final response = await _supabase
|
||||||
.from('operation_file')
|
.from('attachment')
|
||||||
.insert(fileToSave.toMap())
|
.insert(fileToSave.toMap())
|
||||||
.select()
|
.select()
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
return OperationFileModel.fromMap(response);
|
return AttachmentModel.fromMap(response);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw 'Errore durante l\'upload: $e';
|
throw 'Errore durante l\'upload: $e';
|
||||||
}
|
}
|
||||||
@@ -342,17 +281,13 @@ class OperationsRepository {
|
|||||||
required OperationFileModel file,
|
required OperationFileModel file,
|
||||||
required String customerId,
|
required String customerId,
|
||||||
}) async {
|
}) async {
|
||||||
CustomerFileModel fileToCopy = CustomerFileModel(
|
await _supabase
|
||||||
customerId: customerId,
|
.from('attachment')
|
||||||
name: file.name,
|
.update({'customer_id': customerId})
|
||||||
storagePath: file.storagePath,
|
.eq('id', file.id!);
|
||||||
extension: file.extension,
|
|
||||||
fileSize: file.fileSize,
|
|
||||||
);
|
|
||||||
await _customerRepository.saveFileReference(fileToCopy);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteOperationFiles(List<OperationFileModel> files) async {
|
Future<void> deleteOperationFiles(List<AttachmentModel> files) async {
|
||||||
if (files.isEmpty) return;
|
if (files.isEmpty) return;
|
||||||
// 1. Prepariamo le liste di ID e di Percorsi
|
// 1. Prepariamo le liste di ID e di Percorsi
|
||||||
final List<String> idsToDelete = files.map((f) => f.id!).toList();
|
final List<String> idsToDelete = files.map((f) => f.id!).toList();
|
||||||
@@ -360,18 +295,14 @@ class OperationsRepository {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await _supabase
|
await _supabase
|
||||||
.from('operation_file')
|
.from('attachment')
|
||||||
.delete()
|
.update({'operation_id': null})
|
||||||
.inFilter('id', idsToDelete);
|
.inFilter('id', idsToDelete);
|
||||||
|
|
||||||
await _supabase.storage.from('documents').remove(storagePaths);
|
await _supabase.storage.from('documents').remove(storagePaths);
|
||||||
|
|
||||||
debugPrint("Eliminati con successo ${files.length} file.");
|
|
||||||
} on PostgrestException catch (e) {
|
} on PostgrestException catch (e) {
|
||||||
debugPrint("Errore DB: ${e.message}");
|
|
||||||
throw 'Errore database: ${e.message}';
|
throw 'Errore database: ${e.message}';
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint("Errore generico: $e");
|
|
||||||
throw 'Errore durante l\'eliminazione dei file: $e';
|
throw 'Errore durante l\'eliminazione dei file: $e';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,200 +1,200 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flux/core/utils/extensions.dart';
|
import 'package:flux/features/attachments/models/attachment_model.dart';
|
||||||
import 'package:flux/features/operations/models/energy_operation_model.dart';
|
|
||||||
import 'package:flux/features/operations/models/entertainment_operation_model.dart';
|
enum OperationStatus {
|
||||||
import 'package:flux/features/operations/models/fin_operation_model.dart';
|
ok('ok'),
|
||||||
import 'package:flux/features/operations/models/operation_file_model.dart'; // <-- Aggiunto Import
|
waitingforaction('waiting_for_action'),
|
||||||
|
waitingforsupport('waiting_for_support'),
|
||||||
|
waitingfordeployment('waiting_for_deployment'),
|
||||||
|
ko('ko'),
|
||||||
|
draft('draft'),
|
||||||
|
canceled('canceled');
|
||||||
|
|
||||||
|
static OperationStatus fromString(String value) {
|
||||||
|
final normalizedValue = value.replaceAll('_', '').toLowerCase();
|
||||||
|
return OperationStatus.values.firstWhere(
|
||||||
|
(e) => e.name.toLowerCase() == normalizedValue,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final String supabaseName;
|
||||||
|
|
||||||
|
const OperationStatus(this.supabaseName);
|
||||||
|
}
|
||||||
|
|
||||||
class OperationModel extends Equatable {
|
class OperationModel extends Equatable {
|
||||||
final String? id;
|
final String? id;
|
||||||
final DateTime? createdAt;
|
final DateTime? createdAt;
|
||||||
final String storeId;
|
final String type;
|
||||||
final String? employeeId;
|
final String? providerId;
|
||||||
final String? customerId;
|
final String? modelId;
|
||||||
final String number;
|
final String? description;
|
||||||
final bool isBozza;
|
final DateTime? expirationDate;
|
||||||
final String note;
|
final String note;
|
||||||
final bool resultOk;
|
final bool showInDashboard;
|
||||||
final String? customerDisplayName;
|
final String batchUuid;
|
||||||
final String companyId;
|
final String companyId;
|
||||||
|
final String storeId;
|
||||||
// Telefonia
|
final int quantity;
|
||||||
final int al;
|
final String? staffId;
|
||||||
final int mnp;
|
final String staffDisplayName;
|
||||||
final int nip;
|
final String? lastCampaignId;
|
||||||
final int unica;
|
final OperationStatus status;
|
||||||
final int telepass;
|
final String? customerId;
|
||||||
|
final String customerDisplayName;
|
||||||
// Moduli (Liste)
|
final String reference;
|
||||||
final List<EnergyOperationModel> energyOperations;
|
|
||||||
final List<FinOperationModel> finOperations;
|
|
||||||
final List<EntertainmentOperationModel> entertainmentOperations;
|
|
||||||
|
|
||||||
// ALLEGATI (Aggiunto)
|
// ALLEGATI (Aggiunto)
|
||||||
final List<OperationFileModel> files;
|
final List<AttachmentModel> attachments;
|
||||||
|
|
||||||
const OperationModel({
|
const OperationModel({
|
||||||
this.id,
|
this.id,
|
||||||
this.createdAt,
|
this.createdAt,
|
||||||
required this.storeId,
|
this.type = '',
|
||||||
this.employeeId,
|
this.providerId,
|
||||||
this.customerId,
|
this.modelId,
|
||||||
required this.number,
|
this.description,
|
||||||
this.isBozza = true,
|
this.expirationDate,
|
||||||
this.note = '',
|
this.note = '',
|
||||||
this.resultOk = true,
|
this.showInDashboard = true,
|
||||||
this.al = 0,
|
this.batchUuid = '',
|
||||||
this.mnp = 0,
|
|
||||||
this.nip = 0,
|
|
||||||
this.unica = 0,
|
|
||||||
this.telepass = 0,
|
|
||||||
this.energyOperations = const [],
|
|
||||||
this.finOperations = const [],
|
|
||||||
this.entertainmentOperations = const [],
|
|
||||||
this.files = const [], // <-- Aggiunto default vuoto
|
|
||||||
this.customerDisplayName,
|
|
||||||
required this.companyId,
|
required this.companyId,
|
||||||
|
this.storeId = '',
|
||||||
|
this.quantity = 1,
|
||||||
|
this.staffId,
|
||||||
|
this.staffDisplayName = '',
|
||||||
|
this.lastCampaignId,
|
||||||
|
this.status = OperationStatus.draft,
|
||||||
|
this.customerId,
|
||||||
|
this.customerDisplayName = '',
|
||||||
|
this.reference = '',
|
||||||
|
this.attachments = const [],
|
||||||
});
|
});
|
||||||
|
|
||||||
OperationModel copyWith({
|
OperationModel copyWith({
|
||||||
String? id,
|
String? id,
|
||||||
DateTime? createdAt,
|
DateTime? createdAt,
|
||||||
String? storeId,
|
String? type,
|
||||||
String? employeeId,
|
String? providerId,
|
||||||
String? customerId,
|
String? modelId,
|
||||||
String? number,
|
String? description,
|
||||||
bool? isBozza,
|
DateTime? expirationDate,
|
||||||
String? note,
|
String? note,
|
||||||
bool? resultOk,
|
bool? showInDashboard,
|
||||||
int? al,
|
String? batchUuid,
|
||||||
int? mnp,
|
|
||||||
int? nip,
|
|
||||||
int? unica,
|
|
||||||
int? telepass,
|
|
||||||
List<EnergyOperationModel>? energyOperations,
|
|
||||||
List<FinOperationModel>? finOperations,
|
|
||||||
List<EntertainmentOperationModel>? entertainmentOperations,
|
|
||||||
List<OperationFileModel>? files, // <-- Aggiunto
|
|
||||||
String? customerDisplayName,
|
|
||||||
String? companyId,
|
String? companyId,
|
||||||
}) {
|
String? storeId,
|
||||||
return OperationModel(
|
int? quantity,
|
||||||
|
String? staffId,
|
||||||
|
String? staffDisplayName,
|
||||||
|
String? lastCampaignId,
|
||||||
|
OperationStatus? status,
|
||||||
|
String? customerId,
|
||||||
|
String? customerDisplayName,
|
||||||
|
String? reference,
|
||||||
|
List<AttachmentModel>? attachments,
|
||||||
|
}) => OperationModel(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
createdAt: createdAt ?? this.createdAt,
|
createdAt: createdAt ?? this.createdAt,
|
||||||
storeId: storeId ?? this.storeId,
|
type: type ?? this.type,
|
||||||
employeeId: employeeId ?? this.employeeId,
|
providerId: providerId ?? this.providerId,
|
||||||
customerId: customerId ?? this.customerId,
|
modelId: modelId ?? this.modelId,
|
||||||
number: number ?? this.number,
|
description: description ?? this.description,
|
||||||
isBozza: isBozza ?? this.isBozza,
|
expirationDate: expirationDate ?? this.expirationDate,
|
||||||
note: note ?? this.note,
|
note: note ?? this.note,
|
||||||
resultOk: resultOk ?? this.resultOk,
|
showInDashboard: showInDashboard ?? this.showInDashboard,
|
||||||
al: al ?? this.al,
|
batchUuid: batchUuid ?? this.batchUuid,
|
||||||
mnp: mnp ?? this.mnp,
|
|
||||||
nip: nip ?? this.nip,
|
|
||||||
unica: unica ?? this.unica,
|
|
||||||
telepass: telepass ?? this.telepass,
|
|
||||||
energyOperations: energyOperations ?? this.energyOperations,
|
|
||||||
finOperations: finOperations ?? this.finOperations,
|
|
||||||
entertainmentOperations:
|
|
||||||
entertainmentOperations ?? this.entertainmentOperations,
|
|
||||||
files: files ?? this.files, // <-- Aggiunto
|
|
||||||
customerDisplayName: customerDisplayName ?? this.customerDisplayName,
|
|
||||||
companyId: companyId ?? this.companyId,
|
companyId: companyId ?? this.companyId,
|
||||||
|
storeId: storeId ?? this.storeId,
|
||||||
|
quantity: quantity ?? this.quantity,
|
||||||
|
staffId: staffId ?? this.staffId,
|
||||||
|
staffDisplayName: staffDisplayName ?? this.staffDisplayName,
|
||||||
|
lastCampaignId: lastCampaignId ?? this.lastCampaignId,
|
||||||
|
status: status ?? this.status,
|
||||||
|
customerId: customerId ?? this.customerId,
|
||||||
|
customerDisplayName: customerDisplayName ?? this.customerDisplayName,
|
||||||
|
reference: reference ?? this.reference,
|
||||||
|
attachments: attachments ?? this.attachments,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [
|
List<Object?> get props => [
|
||||||
id,
|
id,
|
||||||
createdAt,
|
createdAt,
|
||||||
storeId,
|
type,
|
||||||
employeeId,
|
providerId,
|
||||||
customerId,
|
modelId,
|
||||||
number,
|
description,
|
||||||
isBozza,
|
expirationDate,
|
||||||
note,
|
note,
|
||||||
resultOk,
|
showInDashboard,
|
||||||
al,
|
batchUuid,
|
||||||
mnp,
|
|
||||||
nip,
|
|
||||||
unica,
|
|
||||||
telepass,
|
|
||||||
energyOperations,
|
|
||||||
finOperations,
|
|
||||||
entertainmentOperations,
|
|
||||||
files, // <-- Aggiunto
|
|
||||||
customerDisplayName,
|
|
||||||
companyId,
|
companyId,
|
||||||
|
storeId,
|
||||||
|
quantity,
|
||||||
|
staffId,
|
||||||
|
staffDisplayName,
|
||||||
|
lastCampaignId,
|
||||||
|
status,
|
||||||
|
customerId,
|
||||||
|
customerDisplayName,
|
||||||
|
reference,
|
||||||
|
attachments,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
factory OperationModel.empty({required String companyId}) {
|
||||||
|
return OperationModel(id: null, createdAt: null, companyId: companyId);
|
||||||
|
}
|
||||||
|
|
||||||
factory OperationModel.fromMap(Map<String, dynamic> map) {
|
factory OperationModel.fromMap(Map<String, dynamic> map) {
|
||||||
return OperationModel(
|
return OperationModel(
|
||||||
id: map['id'].toString(),
|
id: map['id'],
|
||||||
createdAt: map['created_at'] != null
|
createdAt: map['created_at'] != null
|
||||||
? DateTime.parse(map['created_at'])
|
? DateTime.parse(map['created_at'])
|
||||||
: DateTime.now(),
|
: null,
|
||||||
storeId: map['store_id'] ?? '',
|
type: map['type'] as String? ?? '',
|
||||||
employeeId: map['employee_id']?.toString(),
|
providerId: map['provider_id'] as String? ?? '',
|
||||||
customerId: map['customer_id']?.toString(),
|
modelId: map['model_id'] as String? ?? '',
|
||||||
number: map['number']?.toString() ?? '',
|
description: map['description'] as String? ?? '',
|
||||||
isBozza: map['bozza'] ?? true,
|
expirationDate: map['expiration_date'] != null
|
||||||
note: map['note'] ?? '',
|
? DateTime.parse(map['expiration_date'])
|
||||||
resultOk: map['result_ok'] ?? true,
|
: null,
|
||||||
al: map['al'] ?? 0,
|
note: map['note'] as String? ?? '',
|
||||||
mnp: map['mnp'] ?? 0,
|
showInDashboard: map['show_in_dashboard'] as bool,
|
||||||
nip: map['nip'] ?? 0,
|
batchUuid: map['batch_uuid'] as String,
|
||||||
unica: map['unica'] ?? 0,
|
|
||||||
telepass: map['telepass'] ?? 0,
|
|
||||||
|
|
||||||
// Estrazione sicura liste collegate
|
|
||||||
energyOperations:
|
|
||||||
(map['energy_operation'] as List?)
|
|
||||||
?.map((x) => EnergyOperationModel.fromMap(x))
|
|
||||||
.toList() ??
|
|
||||||
const [],
|
|
||||||
finOperations:
|
|
||||||
(map['fin_operation'] as List?)
|
|
||||||
?.map((x) => FinOperationModel.fromMap(x))
|
|
||||||
.toList() ??
|
|
||||||
const [],
|
|
||||||
entertainmentOperations:
|
|
||||||
(map['entertainment_operation'] as List?)
|
|
||||||
?.map((x) => EntertainmentOperationModel.fromMap(x))
|
|
||||||
.toList() ??
|
|
||||||
const [],
|
|
||||||
|
|
||||||
// I FILE! (Assicurati che la foreign key su Supabase usi esattamente questo nome)
|
|
||||||
files:
|
|
||||||
(map['operation_file'] as List?)
|
|
||||||
?.map((x) => OperationFileModel.fromMap(x))
|
|
||||||
.toList() ??
|
|
||||||
const [],
|
|
||||||
|
|
||||||
// Display name del cliente con fallback
|
|
||||||
customerDisplayName: map['customer'] != null
|
|
||||||
? "${map['customer']['nome'] ?? ''}".myFormat()
|
|
||||||
: "Cliente non assegnato",
|
|
||||||
companyId: map['company_id'] as String,
|
companyId: map['company_id'] as String,
|
||||||
|
storeId: map['store_id'] as String? ?? '',
|
||||||
|
quantity: map['quantity'] is int
|
||||||
|
? map['quantity']
|
||||||
|
: int.tryParse(map['quantity']?.toString() ?? '0') ?? 0,
|
||||||
|
staffId: map['staff_id'] as String? ?? '',
|
||||||
|
lastCampaignId: map['last_campaign_id'] as String? ?? '',
|
||||||
|
status: OperationStatus.fromString(map['status']),
|
||||||
|
customerId: map['customer_id'] as String? ?? '',
|
||||||
|
reference: map['reference'] as String? ?? '',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> toMap() {
|
Map<String, dynamic> toMap() {
|
||||||
return {
|
return {
|
||||||
if (id != null) 'id': id,
|
if (id != null) 'id': id,
|
||||||
'store_id': storeId,
|
'type': type,
|
||||||
'employee_id': employeeId,
|
'provider_id': providerId,
|
||||||
'customer_id': customerId,
|
'model_id': modelId,
|
||||||
'number': number,
|
'description': description,
|
||||||
'bozza': isBozza,
|
if (expirationDate != null)
|
||||||
|
'expiration_date': expirationDate!.toIso8601String(),
|
||||||
'note': note,
|
'note': note,
|
||||||
'result_ok': resultOk,
|
'show_in_dashboard': showInDashboard,
|
||||||
'al': al,
|
'batch_uuid': batchUuid,
|
||||||
'mnp': mnp,
|
|
||||||
'nip': nip,
|
|
||||||
'unica': unica,
|
|
||||||
'telepass': telepass,
|
|
||||||
'company_id': companyId,
|
'company_id': companyId,
|
||||||
// Le liste non le mettiamo qui perché vanno in tabelle diverse!
|
'store_id': storeId,
|
||||||
|
'quantity': quantity,
|
||||||
|
if (staffId != null) 'staff_id': staffId,
|
||||||
|
if (lastCampaignId != null) 'last_campaign_id': lastCampaignId,
|
||||||
|
'status': status.supabaseName,
|
||||||
|
if (customerId != null) 'customer_id': customerId,
|
||||||
|
'reference': reference,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user