fix macos pdf
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flux/core/blocs/session/session_cubit.dart';
|
||||
import 'package:flux/features/documents/data/tickets_shipment_repository.dart';
|
||||
import 'package:flux/features/documents/models/shipment_document_model.dart';
|
||||
import 'package:flux/features/tickets/data/tickets_shipment_repository.dart';
|
||||
import 'package:flux/features/tickets/models/shipment_document_model.dart';
|
||||
import 'package:flux/features/master_data/providers/models/provider_location_model.dart';
|
||||
import 'package:flux/features/master_data/providers/models/provider_model.dart';
|
||||
import 'package:flux/features/settings/document_sequence/data/document_sequence_repository.dart';
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flux/core/blocs/session/session_cubit.dart';
|
||||
import 'package:flux/features/settings/document_sequence/data/document_sequence_repository.dart';
|
||||
import 'package:flux/features/settings/document_sequence/models/document_sequence_model.dart';
|
||||
import 'package:flux/features/tickets/models/ticket_model.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
class TicketRepository {
|
||||
final SupabaseClient _supabase = GetIt.I.get<SupabaseClient>();
|
||||
final DocumentSequenceRepository _documentSequenceRepository = GetIt.I
|
||||
.get<DocumentSequenceRepository>();
|
||||
|
||||
TicketRepository();
|
||||
|
||||
@@ -192,29 +196,29 @@ class TicketRepository {
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> generateTicketReference(String companyId) async {
|
||||
final response = await Supabase.instance.client.rpc(
|
||||
'get_next_document_number',
|
||||
params: {'p_company_id': companyId, 'p_doc_type': 'ticket'},
|
||||
);
|
||||
// Future<String> generateTicketReference(String companyId) async {
|
||||
// final response = await Supabase.instance.client.rpc(
|
||||
// 'get_next_document_number',
|
||||
// params: {'p_company_id': companyId, 'p_doc_type': 'ticket'},
|
||||
// );
|
||||
|
||||
// Estraiamo i dati dal JSON
|
||||
final int nextValue = response['next_value'];
|
||||
final String prefix = response['prefix'] ?? '';
|
||||
// // Estraiamo i dati dal JSON
|
||||
// final int nextValue = response['next_value'];
|
||||
// final String prefix = response['prefix'] ?? '';
|
||||
|
||||
final year = DateTime.now().year; // 2026
|
||||
// final year = DateTime.now().year; // 2026
|
||||
|
||||
// Formattazione con zeri iniziali (es. 000125)
|
||||
final paddedNumber = nextValue.toString().padLeft(6, '0');
|
||||
// // Formattazione con zeri iniziali (es. 000125)
|
||||
// final paddedNumber = nextValue.toString().padLeft(6, '0');
|
||||
|
||||
// Costruiamo la stringa. Se c'è un prefisso mette "TCK-2026-000125",
|
||||
// altrimenti solo "2026-000125"
|
||||
if (prefix.isNotEmpty) {
|
||||
return '$prefix-$year-$paddedNumber';
|
||||
} else {
|
||||
return '$year-$paddedNumber';
|
||||
}
|
||||
}
|
||||
// // Costruiamo la stringa. Se c'è un prefisso mette "TCK-2026-000125",
|
||||
// // altrimenti solo "2026-000125"
|
||||
// if (prefix.isNotEmpty) {
|
||||
// return '$prefix-$year-$paddedNumber';
|
||||
// } else {
|
||||
// return '$year-$paddedNumber';
|
||||
// }
|
||||
// }
|
||||
|
||||
/// Salva il ticket
|
||||
Future<TicketModel> insertTicket(TicketModel ticket) async {
|
||||
@@ -223,7 +227,9 @@ class TicketRepository {
|
||||
}
|
||||
try {
|
||||
final ticketToSave = ticket.copyWith(
|
||||
referenceId: await generateTicketReference(ticket.companyId),
|
||||
referenceId: await _documentSequenceRepository.getNextDocumentNumber(
|
||||
DocumentType.ticket.name,
|
||||
),
|
||||
);
|
||||
final response = await _supabase
|
||||
.from(_tableName)
|
||||
|
||||
50
lib/features/tickets/data/tickets_shipment_repository.dart
Normal file
50
lib/features/tickets/data/tickets_shipment_repository.dart
Normal file
@@ -0,0 +1,50 @@
|
||||
import 'package:flux/features/tickets/models/shipment_document_model.dart';
|
||||
import 'package:flux/features/master_data/providers/models/provider_model.dart';
|
||||
import 'package:flux/features/master_data/providers/models/provider_role.dart';
|
||||
import 'package:flux/features/tickets/models/ticket_model.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
class TicketsShipmentRepository {
|
||||
final _supabase = GetIt.I.get<SupabaseClient>();
|
||||
|
||||
Future<List<ProviderModel>> fetchRepairCenters() async {
|
||||
try {
|
||||
final response = await _supabase
|
||||
.from('provider')
|
||||
.select('*, provider_locations (*)')
|
||||
.eq('is_active', true)
|
||||
.order('name');
|
||||
|
||||
final allProviders = (response as List)
|
||||
.map((row) => ProviderModel.fromMap(row as Map<String, dynamic>))
|
||||
.toList();
|
||||
|
||||
// Filtriamo lato client per prendere SOLO i repairCenter
|
||||
return allProviders
|
||||
.where((p) => p.roles.contains(ProviderRole.repairCenter))
|
||||
.toList();
|
||||
} catch (e) {
|
||||
throw ('Errore caricamento laboratori: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// NUOVO METODO: Salva il DDT e aggiorna i Ticket
|
||||
Future<void> createShipmentDocument({
|
||||
required ShipmentDocumentModel document,
|
||||
required TicketStatus newTicketStatus, // es: 'shipped' o 'inExternalLab'
|
||||
}) async {
|
||||
try {
|
||||
// 1. Inseriamo il singolo Documento di Trasporto
|
||||
await _supabase.from('shipment_documents').insert(document.toMap());
|
||||
|
||||
// 2. Aggiorniamo lo stato di TUTTI i ticket inclusi nel DDT
|
||||
await _supabase
|
||||
.from('ticket')
|
||||
.update({'ticket_status': newTicketStatus.value})
|
||||
.inFilter('id', document.ticketIds);
|
||||
} catch (e) {
|
||||
throw ('Errore durante la creazione della spedizione: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
93
lib/features/tickets/models/shipment_document_model.dart
Normal file
93
lib/features/tickets/models/shipment_document_model.dart
Normal file
@@ -0,0 +1,93 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
class ShipmentDocumentModel extends Equatable {
|
||||
final String? id;
|
||||
final String companyId;
|
||||
final List<String> ticketIds;
|
||||
final String providerId;
|
||||
final String destinationLocationId;
|
||||
final String docNumber;
|
||||
final DateTime docDate;
|
||||
final int packageCount;
|
||||
final double? weight;
|
||||
final String shippingReason;
|
||||
final String? notes;
|
||||
|
||||
const ShipmentDocumentModel({
|
||||
this.id,
|
||||
required this.companyId,
|
||||
required this.ticketIds,
|
||||
required this.providerId,
|
||||
required this.destinationLocationId,
|
||||
required this.docNumber,
|
||||
required this.docDate,
|
||||
this.packageCount = 1,
|
||||
this.weight,
|
||||
this.shippingReason = 'Riparazione esterna',
|
||||
this.notes,
|
||||
});
|
||||
|
||||
ShipmentDocumentModel copyWith({
|
||||
String? id,
|
||||
String? companyId,
|
||||
List<String>? ticketIds,
|
||||
String? providerId,
|
||||
String? destinationLocationId,
|
||||
String? docNumber,
|
||||
DateTime? docDate,
|
||||
int? packageCount,
|
||||
double? weight,
|
||||
String? shippingReason,
|
||||
String? notes,
|
||||
}) {
|
||||
return ShipmentDocumentModel(
|
||||
id: id ?? this.id,
|
||||
companyId: companyId ?? this.companyId,
|
||||
ticketIds: ticketIds ?? this.ticketIds,
|
||||
providerId: providerId ?? this.providerId,
|
||||
destinationLocationId:
|
||||
destinationLocationId ?? this.destinationLocationId,
|
||||
docNumber: docNumber ?? this.docNumber,
|
||||
docDate: docDate ?? this.docDate,
|
||||
packageCount: packageCount ?? this.packageCount,
|
||||
weight: weight ?? this.weight,
|
||||
shippingReason: shippingReason ?? this.shippingReason,
|
||||
notes: notes ?? this.notes,
|
||||
);
|
||||
}
|
||||
|
||||
factory ShipmentDocumentModel.fromMap(Map<String, dynamic> map) {
|
||||
return ShipmentDocumentModel(
|
||||
id: map['id'],
|
||||
companyId: map['company_id'],
|
||||
ticketIds: List<String>.from(map['ticket_ids']),
|
||||
providerId: map['provider_id'],
|
||||
destinationLocationId: map['destination_location_id'],
|
||||
docNumber: map['doc_number'],
|
||||
docDate: DateTime.parse(map['doc_date']),
|
||||
packageCount: map['package_count'] ?? 1,
|
||||
weight: map['weight'] != null ? (map['weight'] as num).toDouble() : null,
|
||||
shippingReason: map['shipping_reason'] ?? 'Riparazione esterna',
|
||||
notes: map['notes'],
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
if (id != null) 'id': id,
|
||||
'company_id': companyId,
|
||||
'ticket_ids': ticketIds,
|
||||
'provider_id': providerId,
|
||||
'destination_location_id': destinationLocationId,
|
||||
'doc_number': docNumber,
|
||||
'doc_date': docDate.toIso8601String(),
|
||||
'package_count': packageCount,
|
||||
'weight': weight,
|
||||
'shipping_reason': shippingReason,
|
||||
'notes': notes,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [id, docNumber, ticketIds];
|
||||
}
|
||||
@@ -213,8 +213,12 @@ class _TicketFormScreenState extends State<TicketFormScreen> {
|
||||
.generateTicketReceipt(ticket);
|
||||
final bytes = await doc.save();
|
||||
final fileName = 'Ricevuta_${ticket.referenceId}.pdf';
|
||||
await Printing.layoutPdf(
|
||||
onLayout: (format) async => bytes,
|
||||
name: fileName,
|
||||
);
|
||||
|
||||
if (kIsWeb || Platform.isMacOS) {
|
||||
/* if (kIsWeb || Platform.isMacOS) {
|
||||
// Forza il download/salvataggio senza passare per il print spooler
|
||||
await Printing.sharePdf(
|
||||
bytes: bytes,
|
||||
@@ -222,11 +226,8 @@ class _TicketFormScreenState extends State<TicketFormScreen> {
|
||||
);
|
||||
} else {
|
||||
// Su Android/iOS continuiamo a usare la stampa diretta che funziona
|
||||
await Printing.layoutPdf(
|
||||
onLayout: (format) async => bytes,
|
||||
name: fileName,
|
||||
);
|
||||
}
|
||||
|
||||
} */
|
||||
},
|
||||
),
|
||||
if (company.labelFormat != LabelFormat.none)
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import 'package:flutter/foundation.dart' show kIsWeb, defaultTargetPlatform;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flux/core/blocs/session/session_cubit.dart';
|
||||
import 'package:flux/features/documents/models/shipment_document_model.dart';
|
||||
import 'package:flux/features/tickets/models/shipment_document_model.dart';
|
||||
import 'package:flux/features/master_data/providers/models/provider_location_model.dart';
|
||||
import 'package:flux/features/master_data/providers/models/provider_model.dart';
|
||||
import 'package:flux/features/tickets/blocs/ticket_list_cubit.dart';
|
||||
@@ -148,24 +147,10 @@ class TicketList extends StatelessWidget {
|
||||
document: result.document,
|
||||
tickets: state.selectedTickets.toList(),
|
||||
);
|
||||
|
||||
// 4. LANCIAMO LA STAMPA (Anteprima nativa / Browser)
|
||||
// Check sicuro: se NON siamo sul web E la piattaforma nativa è macOS
|
||||
if (!kIsWeb &&
|
||||
defaultTargetPlatform == TargetPlatform.macOS) {
|
||||
// Scialuppa di salvataggio per il Mac
|
||||
await Printing.sharePdf(
|
||||
bytes: pdfBytes,
|
||||
filename: 'ddt_${result.document.docNumber}.pdf',
|
||||
);
|
||||
} else {
|
||||
// Per Web, Windows, Linux, Android e iOS... diamo spettacolo!
|
||||
await Printing.layoutPdf(
|
||||
onLayout: (PdfPageFormat format) async =>
|
||||
pdfBytes,
|
||||
name: 'ddt_${result.document.docNumber}.pdf',
|
||||
);
|
||||
}
|
||||
await Printing.layoutPdf(
|
||||
onLayout: (PdfPageFormat format) async => pdfBytes,
|
||||
name: 'ddt_${result.document.docNumber}.pdf',
|
||||
);
|
||||
|
||||
// 5. Pulizia finale: Deselezioniamo tutti i ticket e ricarichiamo la lista
|
||||
ticketListCubit.clearSelection();
|
||||
|
||||
@@ -185,7 +185,7 @@ class _TicketShippingModalState extends State<TicketShippingModal> {
|
||||
TicketShippingState state,
|
||||
) {
|
||||
return DropdownButtonFormField<String>(
|
||||
value: state.document.providerId.isEmpty
|
||||
initialValue: state.document.providerId.isEmpty
|
||||
? null
|
||||
: state.document.providerId,
|
||||
decoration: const InputDecoration(
|
||||
@@ -207,7 +207,7 @@ class _TicketShippingModalState extends State<TicketShippingModal> {
|
||||
TicketShippingState state,
|
||||
) {
|
||||
return DropdownButtonFormField<String>(
|
||||
value: state.document.destinationLocationId.isEmpty
|
||||
initialValue: state.document.destinationLocationId.isEmpty
|
||||
? null
|
||||
: state.document.destinationLocationId,
|
||||
decoration: const InputDecoration(
|
||||
@@ -280,6 +280,7 @@ class _TicketShippingModalState extends State<TicketShippingModal> {
|
||||
),
|
||||
trailing: const Icon(Icons.calendar_month),
|
||||
onTap: () async {
|
||||
final ticketShippingCubit = context.read<TicketShippingCubit>();
|
||||
final newDate = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: date,
|
||||
@@ -287,7 +288,7 @@ class _TicketShippingModalState extends State<TicketShippingModal> {
|
||||
lastDate: DateTime.now().add(const Duration(days: 365)),
|
||||
);
|
||||
if (newDate != null) {
|
||||
context.read<TicketShippingCubit>().updateDocument(docDate: newDate);
|
||||
ticketShippingCubit.updateDocument(docDate: newDate);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'dart:typed_data';
|
||||
import 'package:flux/features/company/models/company_model.dart';
|
||||
import 'package:flux/features/documents/models/shipment_document_model.dart';
|
||||
import 'package:flux/features/tickets/models/shipment_document_model.dart';
|
||||
import 'package:flux/features/master_data/providers/models/provider_location_model.dart';
|
||||
import 'package:flux/features/master_data/providers/models/provider_model.dart';
|
||||
import 'package:flux/features/tickets/models/ticket_model.dart';
|
||||
|
||||
Reference in New Issue
Block a user