| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589 |
- import 'package:flutter/material.dart';
- import 'package:flutter_riverpod/flutter_riverpod.dart';
- import 'package:intl/intl.dart';
- import '../../../providers/auth_provider.dart';
- import '../../../providers/order_provider.dart';
- import '../../../providers/database_provider.dart';
- import '../../../database/database.dart';
- import '../../../database/daos/listini_dao.dart';
- import '../../widgets/article_image.dart';
- class OrderEditPage extends ConsumerStatefulWidget {
- final int? id;
- final int? idCliente;
- const OrderEditPage({super.key, this.id, this.idCliente});
- @override
- ConsumerState<OrderEditPage> createState() => _OrderEditPageState();
- }
- class _OrderEditPageState extends ConsumerState<OrderEditPage>
- with SingleTickerProviderStateMixin {
- late TabController _tabController;
- final _artCtrl = TextEditingController();
- final _noteClienteCtrl = TextEditingController();
- final _noteOrdineCtrl = TextEditingController();
- final _riferimentoCtrl = TextEditingController();
- List<Articolo> _searchResults = [];
- bool _searching = false;
- List<Filiale> _filiali = [];
- List<Listino> _listini = [];
- List<Pagamento> _pagamenti = [];
- Cliente? _cliente;
- int? _selectedFiliale;
- int? _selectedListino;
- int? _selectedPagamento;
- bool _dataLoaded = false;
- @override
- void initState() {
- super.initState();
- _tabController = TabController(length: 2, vsync: this);
- WidgetsBinding.instance.addPostFrameCallback((_) => _init());
- }
- @override
- void dispose() {
- _tabController.dispose();
- _artCtrl.dispose();
- _noteClienteCtrl.dispose();
- _noteOrdineCtrl.dispose();
- _riferimentoCtrl.dispose();
- super.dispose();
- }
- Future<void> _init() async {
- final agente = ref.read(authProvider).agente;
- if (agente == null) return;
- if (widget.id != null) {
- await ref.read(orderProvider.notifier).loadOrder(widget.id!);
- } else if (widget.idCliente != null) {
- await ref
- .read(orderProvider.notifier)
- .createOrder(agente.idAgente, idCliente: widget.idCliente!);
- } else {
- await ref.read(orderProvider.notifier).createOrder(agente.idAgente);
- }
- final orderState = ref.read(orderProvider);
- final order = orderState.order;
- if (order == null) return;
- final cli = order.idCliente;
- final clientiDao = ref.read(clientiDaoProvider);
- final filialiDao = ref.read(filialiDaoProvider);
- final listiniDao = ref.read(listiniDaoProvider);
- final pagamentiDao = ref.read(pagamentiDaoProvider);
- if (cli != null) {
- _cliente = await clientiDao.getById(cli);
- _filiali = await filialiDao.getDestinationsForCustomer(cli);
- }
- _listini = await listiniDao.list();
- _pagamenti = await pagamentiDao.searchActive();
- setState(() {
- _selectedFiliale = order.idFiliale;
- _selectedListino = order.idListino ?? _cliente?.idListino;
- _selectedPagamento = order.idPagamento ?? _cliente?.idPagamento;
- _noteClienteCtrl.text = order.noteCliente ?? _cliente?.noteOrdine ?? '';
- _noteOrdineCtrl.text = order.noteOrdine ?? '';
- _riferimentoCtrl.text = order.riferimento ?? '';
- _dataLoaded = true;
- });
- }
- Future<void> _searchArticles(String q) async {
- if (q.length < 2) {
- setState(() {
- _searchResults = [];
- _searching = false;
- });
- return;
- }
- setState(() => _searching = true);
- final dao = ref.read(articoliDaoProvider);
- final results = await dao.getItems(q);
- setState(() {
- _searchResults = results;
- _searching = false;
- });
- }
- void _showArticlePicker(Articolo art) async {
- final agente = ref.read(authProvider).agente;
- final canEditPrice = agente?.modificaPrezzi == 1;
- final listini = await ref.read(articoliDaoProvider).getPriceCodes(art.idArticolo);
- final listiniDao = ref.read(listiniDaoProvider);
- if (!context.mounted) return;
- showModalBottomSheet(
- context: context,
- isScrollControlled: true,
- builder: (_) => _ArticlePickerSheet(
- articolo: art,
- listini: listini,
- listiniDao: listiniDao,
- canEditPrezzo: canEditPrice,
- idListinoDefault: _selectedListino,
- onConfirm: (colli, quantita, prezzo, listinoId) {
- _addArticleWithDetails(
- art: art,
- colli: colli,
- quantita: quantita,
- prezzo: prezzo,
- );
- },
- ),
- );
- }
- Future<void> _addArticleWithDetails({
- required Articolo art,
- required double colli,
- required double quantita,
- required double prezzo,
- }) async {
- final orderState = ref.read(orderProvider);
- final idOrdine = orderState.order?.idOrdine;
- if (idOrdine == null || idOrdine == 0) return;
- await ref.read(orderProvider.notifier).addLine(RigaOrdine(
- idRigaOrdine: 0,
- idOrdine: idOrdine,
- idArticolo: art.idArticolo,
- quantita: quantita,
- prezzoLordo: prezzo,
- prezzoNetto: prezzo,
- totale: prezzo * quantita,
- promozione: 0,
- rigaPromozione: 0,
- colli: colli,
- ));
- _artCtrl.clear();
- setState(() => _searchResults = []);
- }
- Future<void> _salva() async {
- final notifier = ref.read(orderProvider.notifier);
- notifier.setIdFiliale(_selectedFiliale);
- notifier.setIdListino(_selectedListino);
- notifier.setIdPagamento(_selectedPagamento);
- notifier.setNoteCliente(_noteClienteCtrl.text);
- notifier.setNoteOrdine(_noteOrdineCtrl.text);
- notifier.setRiferimento(_riferimentoCtrl.text);
- await notifier.saveOrder();
- if (context.mounted) Navigator.pop(context);
- }
- @override
- Widget build(BuildContext context) {
- final orderState = ref.watch(orderProvider);
- final theme = Theme.of(context);
- return Scaffold(
- appBar: AppBar(
- title:
- Text('Ordine #${orderState.order?.numeroOrdine ?? 'nuovo'}'),
- bottom: TabBar(
- controller: _tabController,
- tabs: const [
- Tab(text: 'Testata'),
- Tab(text: 'Righe'),
- ],
- ),
- ),
- body: orderState.isLoading || !_dataLoaded
- ? const Center(child: CircularProgressIndicator())
- : TabBarView(
- controller: _tabController,
- children: [
- _buildHeaderTab(theme),
- _buildLinesTab(theme, orderState),
- ],
- ),
- floatingActionButton: _tabController.index == 1 &&
- orderState.order?.idOrdine != null
- ? FloatingActionButton.extended(
- onPressed: _salva,
- icon: const Icon(Icons.save),
- label: const Text('Salva'),
- )
- : null,
- );
- }
- Widget _buildHeaderTab(ThemeData theme) {
- return SingleChildScrollView(
- padding: const EdgeInsets.all(16),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- DropdownButtonFormField<int>(
- value: _selectedFiliale,
- decoration: const InputDecoration(labelText: 'Filiale'),
- items: _filiali
- .map((f) => DropdownMenuItem(
- value: f.idFiliale,
- child: Text(f.descrizione ?? ''),
- ))
- .toList(),
- onChanged: (v) => setState(() => _selectedFiliale = v),
- ),
- const SizedBox(height: 16),
- DropdownButtonFormField<int>(
- value: _selectedListino,
- decoration: const InputDecoration(labelText: 'Listino'),
- items: _listini
- .map((l) => DropdownMenuItem(
- value: l.idListino,
- child: Text(l.descrizione ?? ''),
- ))
- .toList(),
- onChanged: (v) => setState(() => _selectedListino = v),
- ),
- const SizedBox(height: 16),
- DropdownButtonFormField<int>(
- value: _selectedPagamento,
- decoration: const InputDecoration(labelText: 'Pagamento'),
- items: _pagamenti
- .map((p) => DropdownMenuItem(
- value: p.idPagamento,
- child: Text(p.descrizione ?? ''),
- ))
- .toList(),
- onChanged: (v) => setState(() => _selectedPagamento = v),
- ),
- const SizedBox(height: 16),
- TextFormField(
- controller: _noteClienteCtrl,
- decoration: const InputDecoration(
- labelText: 'Note Cliente',
- alignLabelWithHint: true,
- ),
- maxLines: 3,
- ),
- const SizedBox(height: 16),
- TextFormField(
- controller: _noteOrdineCtrl,
- decoration: const InputDecoration(
- labelText: 'Note Ordine',
- alignLabelWithHint: true,
- ),
- maxLines: 3,
- ),
- const SizedBox(height: 16),
- TextFormField(
- controller: _riferimentoCtrl,
- decoration: const InputDecoration(labelText: 'Riferimento'),
- maxLines: 1,
- ),
- ],
- ),
- );
- }
- Widget _buildLinesTab(ThemeData theme, OrderState orderState) {
- return Column(
- children: [
- Padding(
- padding: const EdgeInsets.all(12),
- child: TextField(
- controller: _artCtrl,
- decoration: InputDecoration(
- hintText: 'Cerca articolo...',
- prefixIcon: const Icon(Icons.search),
- suffixIcon: _searching
- ? const SizedBox(
- width: 20,
- height: 20,
- child:
- CircularProgressIndicator(strokeWidth: 2))
- : null,
- ),
- onChanged: _searchArticles,
- ),
- ),
- if (_searchResults.isNotEmpty)
- SizedBox(
- height: 200,
- child: ListView.builder(
- itemCount: _searchResults.length,
- itemBuilder: (_, i) => ListTile(
- leading: ArticleImage(
- idArticolo: _searchResults[i].idArticolo,
- codArticolo: _searchResults[i].codArticolo),
- title: Text(_searchResults[i].descrizione ?? ''),
- subtitle: Text(_searchResults[i].codArticolo ?? ''),
- trailing: TextButton(
- onPressed: () => _showArticlePicker(_searchResults[i]),
- child: const Text('Aggiungi')),
- ),
- ),
- ),
- Expanded(
- child: orderState.lines.isEmpty
- ? const Center(child: Text('Nessun articolo'))
- : ListView.builder(
- itemCount: orderState.lines.length,
- itemBuilder: (_, i) => _LineCard(
- line: orderState.lines[i],
- onDelete: () => ref
- .read(orderProvider.notifier)
- .removeLine(orderState.lines[i].idRigaOrdine),
- theme: theme,
- ),
- ),
- ),
- Container(
- padding: const EdgeInsets.all(16),
- decoration: BoxDecoration(
- color: theme.colorScheme.surfaceContainerHighest,
- border: Border(
- top: BorderSide(color: theme.dividerColor)),
- ),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Text('Qtà: ${orderState.totQta.toStringAsFixed(1)}'),
- Text(
- 'Totale: ${NumberFormat.currency(locale: 'it', symbol: '€').format(orderState.totale)}',
- style: theme.textTheme.titleMedium
- ?.copyWith(fontWeight: FontWeight.bold),
- ),
- ],
- ),
- ),
- ],
- );
- }
- }
- class _LineCard extends StatelessWidget {
- final RigaOrdine line;
- final VoidCallback onDelete;
- final ThemeData theme;
- const _LineCard({required this.line, required this.onDelete, required this.theme});
- @override
- Widget build(BuildContext context) {
- final isPromo = line.rigaPromozione == 1;
- return Card(
- margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
- color: isPromo ? Colors.green.shade50 : null,
- child: ListTile(
- title: Text('Articolo #${line.idArticolo ?? ''}',
- style: TextStyle(
- fontWeight: isPromo ? FontWeight.bold : null)),
- subtitle: Text(
- 'Qtà: ${line.quantita?.toStringAsFixed(1) ?? "0"} Prezzo: ${line.prezzoNetto?.toStringAsFixed(2) ?? "0,00"}€'),
- trailing: Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- if (isPromo)
- Container(
- padding: const EdgeInsets.symmetric(
- horizontal: 6, vertical: 2),
- decoration: BoxDecoration(
- color: Colors.green,
- borderRadius: BorderRadius.circular(8)),
- child: const Text('PROMO',
- style: TextStyle(
- color: Colors.white, fontSize: 10)),
- ),
- if (!isPromo)
- IconButton(
- icon: const Icon(Icons.delete_outline, size: 20),
- onPressed: onDelete),
- ],
- ),
- ),
- );
- }
- }
- class _ArticlePickerSheet extends StatefulWidget {
- final Articolo articolo;
- final List<Listino> listini;
- final ListiniDao listiniDao;
- final bool canEditPrezzo;
- final int? idListinoDefault;
- final void Function(double colli, double quantita, double prezzo, int listinoId)
- onConfirm;
- const _ArticlePickerSheet({
- required this.articolo,
- required this.listini,
- required this.listiniDao,
- required this.canEditPrezzo,
- this.idListinoDefault,
- required this.onConfirm,
- });
- @override
- State<_ArticlePickerSheet> createState() => _ArticlePickerSheetState();
- }
- class _ArticlePickerSheetState extends State<_ArticlePickerSheet> {
- final _colliCtrl = TextEditingController();
- final _qtaCtrl = TextEditingController();
- final _prezzoCtrl = TextEditingController();
- int? _selectedListino;
- double _prezzo = 0;
- bool _updatingColli = false;
- bool _updatingQta = false;
- @override
- void initState() {
- super.initState();
- _selectedListino = widget.idListinoDefault;
- if (_selectedListino != null) {
- _loadPrezzo(_selectedListino!);
- }
- }
- @override
- void dispose() {
- _colliCtrl.dispose();
- _qtaCtrl.dispose();
- _prezzoCtrl.dispose();
- super.dispose();
- }
- Future<void> _loadPrezzo(int idListino) async {
- final lp = await widget.listiniDao.findListinoArticolo(
- idListino, widget.articolo.idArticolo);
- if (lp?.prezzo != null) {
- setState(() => _prezzo = lp!.prezzo!);
- _prezzoCtrl.text = _prezzo.toStringAsFixed(2);
- }
- }
- void _onColliChanged(String v) {
- if (_updatingColli) return;
- _updatingQta = true;
- final colli = double.tryParse(v) ?? 0;
- final pezzi = widget.articolo.pezziConfezione ?? 0;
- if (colli > 0 && pezzi > 0) {
- _qtaCtrl.text = (colli * pezzi).toStringAsFixed(1);
- } else if (colli == 0) {
- _qtaCtrl.text = '';
- }
- _updatingQta = false;
- }
- void _onQtaChanged(String v) {
- if (_updatingQta) return;
- _updatingColli = true;
- final qta = double.tryParse(v) ?? 0;
- if (qta > 0) {
- _colliCtrl.text = '0';
- } else if (qta == 0) {
- _colliCtrl.text = '';
- }
- _updatingColli = false;
- }
- void _confirm() {
- final colli = double.tryParse(_colliCtrl.text) ?? 0;
- final qta = double.tryParse(_qtaCtrl.text) ?? 0;
- final prezzo = double.tryParse(_prezzoCtrl.text) ?? 0;
- final listinoId = _selectedListino ?? 0;
- widget.onConfirm(colli, qta, prezzo, listinoId);
- Navigator.pop(context);
- }
- @override
- Widget build(BuildContext context) {
- final theme = Theme.of(context);
- return Padding(
- padding: EdgeInsets.only(
- bottom: MediaQuery.of(context).viewInsets.bottom,
- left: 16, right: 16, top: 16,
- ),
- child: SingleChildScrollView(
- child: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(widget.articolo.descrizione ?? '',
- style: theme.textTheme.titleMedium),
- const SizedBox(height: 4),
- Text(widget.articolo.codArticolo ?? '',
- style: theme.textTheme.bodySmall),
- if (widget.articolo.pezziConfezione != null)
- Text('Pezzi/confezione: ${widget.articolo.pezziConfezione}',
- style: theme.textTheme.bodySmall),
- const SizedBox(height: 16),
- DropdownButtonFormField<int>(
- value: _selectedListino,
- decoration: const InputDecoration(labelText: 'Listino'),
- isExpanded: true,
- items: widget.listini
- .map((l) => DropdownMenuItem(
- value: l.idListino,
- child: Text(l.descrizione ?? ''),
- ))
- .toList(),
- onChanged: (v) {
- setState(() => _selectedListino = v);
- if (v != null) _loadPrezzo(v);
- },
- ),
- const SizedBox(height: 12),
- Row(
- children: [
- Expanded(
- child: TextField(
- controller: _colliCtrl,
- decoration: const InputDecoration(
- labelText: 'Colli'),
- keyboardType: TextInputType.number,
- onChanged: _onColliChanged,
- ),
- ),
- const SizedBox(width: 12),
- Expanded(
- child: TextField(
- controller: _qtaCtrl,
- decoration: const InputDecoration(
- labelText: 'Quantità'),
- keyboardType: TextInputType.number,
- onChanged: _onQtaChanged,
- ),
- ),
- ],
- ),
- const SizedBox(height: 12),
- TextField(
- controller: _prezzoCtrl,
- decoration: const InputDecoration(labelText: 'Prezzo'),
- keyboardType:
- const TextInputType.numberWithOptions(decimal: true),
- enabled: widget.canEditPrezzo,
- ),
- const SizedBox(height: 20),
- SizedBox(
- width: double.infinity,
- child: ElevatedButton(
- onPressed: _confirm,
- child: const Text('Aggiungi'),
- ),
- ),
- const SizedBox(height: 8),
- ],
- ),
- ),
- );
- }
- }
|