ticket labels e ticket receipt

This commit is contained in:
2026-05-10 14:09:57 +02:00
parent 385c3da0a5
commit 5c86483563
20 changed files with 1024 additions and 157 deletions

View File

@@ -0,0 +1,70 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/features/settings/document_sequence/models/document_sequence_model.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
class DocumentSequenceState {
final List<DocumentSequence> sequences;
final bool isLoading;
final String? error;
DocumentSequenceState({
this.sequences = const [],
this.isLoading = false,
this.error,
});
}
class DocumentSequenceCubit extends Cubit<DocumentSequenceState> {
final String companyId;
final _supabase = Supabase.instance.client;
DocumentSequenceCubit(this.companyId) : super(DocumentSequenceState());
Future<void> loadSequences() async {
emit(DocumentSequenceState(isLoading: true));
try {
final data = await _supabase
.from('document_sequences')
.select()
.eq('company_id', companyId);
final list = (data as List)
.map((e) => DocumentSequence.fromMap(e))
.toList();
emit(DocumentSequenceState(sequences: list));
} catch (e) {
emit(DocumentSequenceState(error: e.toString()));
}
}
void updateLocalSequence(String docType, {String? prefix, int? nextValue}) {
final newList = state.sequences.map((s) {
if (s.docType == docType) {
return s.copyWith(prefix: prefix, nextValue: nextValue);
}
return s;
}).toList();
emit(DocumentSequenceState(sequences: newList));
}
Future<void> saveSequences() async {
try {
for (var seq in state.sequences) {
await _supabase.from('document_sequences').upsert({
'company_id': companyId,
'doc_type': seq.docType,
'next_value': seq.nextValue,
'prefix': seq.prefix,
});
}
// Opzionale: mostra un feedback di successo
} catch (e) {
emit(
DocumentSequenceState(
sequences: state.sequences,
error: "Errore nel salvataggio",
),
);
}
}
}

View File

@@ -0,0 +1,30 @@
import 'package:flux/features/settings/document_sequence/models/document_sequence_model.dart';
import 'package:get_it/get_it.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
class DocumentSequenceRepository {
final _supabase = GetIt.I.get<SupabaseClient>();
Future<List<DocumentSequence>> getDocumentSequences(String companyId) async {
final response = await _supabase
.from('document_sequences')
.select()
.eq('company_id', companyId);
return (response as List).map((e) => DocumentSequence.fromMap(e)).toList();
}
Future<void> updateSequence({
required String companyId,
required String docType,
required int nextValue,
required String prefix,
}) async {
await _supabase.from('document_sequences').upsert({
'company_id': companyId,
'doc_type': docType,
'next_value': nextValue,
'prefix': prefix,
});
}
}

View File

@@ -0,0 +1,29 @@
enum DocumentType { ticket, ddt, invoice }
class DocumentSequence {
final String docType;
final int nextValue;
final String prefix;
DocumentSequence({
required this.docType,
required this.nextValue,
required this.prefix,
});
DocumentSequence copyWith({int? nextValue, String? prefix}) {
return DocumentSequence(
docType: docType,
nextValue: nextValue ?? this.nextValue,
prefix: prefix ?? this.prefix,
);
}
factory DocumentSequence.fromMap(Map<String, dynamic> map) {
return DocumentSequence(
docType: map['doc_type'],
nextValue: map['next_value'],
prefix: map['prefix'] ?? '',
);
}
}

View File

@@ -0,0 +1,139 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/features/settings/document_sequence/blocs/document_sequence_cubit.dart';
class DocumentSequenceSection extends StatelessWidget {
const DocumentSequenceSection({super.key});
@override
Widget build(BuildContext context) {
final year = DateTime.now().year;
return BlocBuilder<DocumentSequenceCubit, DocumentSequenceState>(
builder: (context, state) {
if (state.isLoading) {
return const Center(child: CircularProgressIndicator());
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Text(
"Protocolli e Numerazione",
style: Theme.of(
context,
).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold),
),
),
...state.sequences.map((seq) {
// Anteprima dinamica
final preview =
"${seq.prefix.isNotEmpty ? '${seq.prefix}-' : ''}$year-${seq.nextValue.toString().padLeft(6, '0')}";
return Card(
margin: const EdgeInsets.only(bottom: 12),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
seq.docType.toUpperCase(),
style: const TextStyle(
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
flex: 2,
child: TextFormField(
initialValue: seq.prefix,
decoration: const InputDecoration(
labelText: 'Prefisso',
hintText: 'es. TCK',
),
onChanged: (val) => context
.read<DocumentSequenceCubit>()
.updateLocalSequence(
seq.docType,
prefix: val,
),
),
),
const SizedBox(width: 16),
Expanded(
flex: 3,
child: TextFormField(
initialValue: seq.nextValue.toString(),
keyboardType: TextInputType.number,
decoration: const InputDecoration(
labelText: 'Prossimo Numero',
),
onChanged: (val) => context
.read<DocumentSequenceCubit>()
.updateLocalSequence(
seq.docType,
nextValue: int.tryParse(val) ?? 1,
),
),
),
],
),
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
const Icon(
Icons.visibility,
size: 16,
color: Colors.grey,
),
const SizedBox(width: 8),
Text(
"Anteprima prossimo: ",
style: TextStyle(
color: Colors.grey.shade700,
fontSize: 12,
),
),
Text(
preview,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontFamily: 'monospace',
),
),
],
),
),
],
),
),
);
}),
const SizedBox(height: 16),
SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
onPressed: () =>
context.read<DocumentSequenceCubit>().saveSequences(),
icon: const Icon(Icons.save),
label: const Text("SALVA PROTOCOLLI"),
),
),
],
);
},
);
}
}