|
|
@@ -1,12 +1,16 @@
|
|
|
import 'package:flutter/material.dart';
|
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
import 'package:intl/intl.dart';
|
|
|
+import 'package:ristoris/core/credit_score_color.dart';
|
|
|
+import '../../../database/daos/sconti_dao.dart';
|
|
|
import '../../../providers/auth_provider.dart';
|
|
|
+import '../../widgets/cod_desc_dropdown_item.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';
|
|
|
+import 'selezione_cliente_page.dart';
|
|
|
|
|
|
class OrderEditPage extends ConsumerStatefulWidget {
|
|
|
final int? id;
|
|
|
@@ -28,6 +32,7 @@ class _OrderEditPageState extends ConsumerState<OrderEditPage>
|
|
|
|
|
|
List<Articolo> _searchResults = [];
|
|
|
bool _searching = false;
|
|
|
+ bool _usaListinoCliente = true;
|
|
|
|
|
|
List<Filiale> _filiali = [];
|
|
|
List<Listino> _listini = [];
|
|
|
@@ -44,6 +49,11 @@ class _OrderEditPageState extends ConsumerState<OrderEditPage>
|
|
|
void initState() {
|
|
|
super.initState();
|
|
|
_tabController = TabController(length: 2, vsync: this);
|
|
|
+ _tabController.addListener(() {
|
|
|
+ if (_tabController.index == 1 && _searchResults.isEmpty && _artCtrl.text.isEmpty) {
|
|
|
+ _searchArticles('');
|
|
|
+ }
|
|
|
+ });
|
|
|
WidgetsBinding.instance.addPostFrameCallback((_) => _init());
|
|
|
}
|
|
|
|
|
|
@@ -100,16 +110,25 @@ class _OrderEditPageState extends ConsumerState<OrderEditPage>
|
|
|
}
|
|
|
|
|
|
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);
|
|
|
+ final order = ref.read(orderProvider).order;
|
|
|
+ final dataOrdine = order?.dataOrdine ?? DateTime.now().toIso8601String();
|
|
|
+ List<Articolo> results;
|
|
|
+ if (_usaListinoCliente && _cliente != null) {
|
|
|
+ results = await dao.getArticoliPerListinoCliente(_cliente!.idCliente, dataOrdine);
|
|
|
+ } else {
|
|
|
+ results = await dao.getArticoliPerListinoAttivo(dataOrdine);
|
|
|
+ }
|
|
|
+ if (q.isNotEmpty) {
|
|
|
+ final lower = q.toLowerCase();
|
|
|
+ results = results.where((a) {
|
|
|
+ final cod = (a.codArticolo ?? '').toLowerCase();
|
|
|
+ final desc = (a.descrizione ?? '').toLowerCase();
|
|
|
+ final ean = (a.ean ?? '').toLowerCase();
|
|
|
+ return cod.contains(lower) || desc.contains(lower) || ean.contains(lower);
|
|
|
+ }).toList();
|
|
|
+ }
|
|
|
setState(() {
|
|
|
_searchResults = results;
|
|
|
_searching = false;
|
|
|
@@ -119,8 +138,13 @@ class _OrderEditPageState extends ConsumerState<OrderEditPage>
|
|
|
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 order = ref.read(orderProvider).order;
|
|
|
+ final dataOrdine = order?.dataOrdine ?? DateTime.now().toIso8601String();
|
|
|
+ final listini = await ref
|
|
|
+ .read(listiniDaoProvider)
|
|
|
+ .getListiniPerArticolo(art.idArticolo, dataOrdine);
|
|
|
final listiniDao = ref.read(listiniDaoProvider);
|
|
|
+ final scontiDao = ref.read(scontiDaoProvider);
|
|
|
if (!context.mounted) return;
|
|
|
|
|
|
showModalBottomSheet(
|
|
|
@@ -130,14 +154,34 @@ class _OrderEditPageState extends ConsumerState<OrderEditPage>
|
|
|
articolo: art,
|
|
|
listini: listini,
|
|
|
listiniDao: listiniDao,
|
|
|
+ scontiDao: scontiDao,
|
|
|
+ dataOrdine: dataOrdine,
|
|
|
canEditPrezzo: canEditPrice,
|
|
|
idListinoDefault: _selectedListino,
|
|
|
- onConfirm: (colli, quantita, prezzo, listinoId) {
|
|
|
+ onConfirm: (
|
|
|
+ colli,
|
|
|
+ quantita,
|
|
|
+ prezzoLordo,
|
|
|
+ prezzoNetto,
|
|
|
+ listinoId, {
|
|
|
+ double sconto1 = 0,
|
|
|
+ double sconto2 = 0,
|
|
|
+ double sconto3 = 0,
|
|
|
+ double sconto4 = 0,
|
|
|
+ double sconto5 = 0,
|
|
|
+ }) {
|
|
|
_addArticleWithDetails(
|
|
|
art: art,
|
|
|
+ idListino: listinoId,
|
|
|
colli: colli,
|
|
|
quantita: quantita,
|
|
|
- prezzo: prezzo,
|
|
|
+ prezzoLordo: prezzoLordo,
|
|
|
+ prezzoNetto: prezzoNetto,
|
|
|
+ sconto1: sconto1,
|
|
|
+ sconto2: sconto2,
|
|
|
+ sconto3: sconto3,
|
|
|
+ sconto4: sconto4,
|
|
|
+ sconto5: sconto5,
|
|
|
);
|
|
|
},
|
|
|
),
|
|
|
@@ -146,30 +190,79 @@ class _OrderEditPageState extends ConsumerState<OrderEditPage>
|
|
|
|
|
|
Future<void> _addArticleWithDetails({
|
|
|
required Articolo art,
|
|
|
+ required int idListino,
|
|
|
required double colli,
|
|
|
required double quantita,
|
|
|
- required double prezzo,
|
|
|
+ required double prezzoLordo,
|
|
|
+ required double prezzoNetto,
|
|
|
+ double? sconto1 = 0,
|
|
|
+ double? sconto2 = 0,
|
|
|
+ double? sconto3 = 0,
|
|
|
+ double? sconto4 = 0,
|
|
|
+ double? sconto5 = 0,
|
|
|
}) 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,
|
|
|
- ));
|
|
|
+ var idOrdine = orderState.order?.idOrdine;
|
|
|
+ if (idOrdine == null || idOrdine == 0) {
|
|
|
+ await ref.read(orderProvider.notifier).saveOrder();
|
|
|
+ idOrdine = ref.read(orderProvider).order?.idOrdine;
|
|
|
+ if (idOrdine == null || idOrdine == 0) return;
|
|
|
+ }
|
|
|
+
|
|
|
+ await ref
|
|
|
+ .read(orderProvider.notifier)
|
|
|
+ .addLine(
|
|
|
+ RigaOrdine(
|
|
|
+ idRigaOrdine: 0,
|
|
|
+ idOrdine: idOrdine,
|
|
|
+ idArticolo: art.idArticolo,
|
|
|
+ idListino: idListino,
|
|
|
+ quantita: quantita,
|
|
|
+ prezzoLordo: prezzoLordo,
|
|
|
+ prezzoNetto: prezzoNetto,
|
|
|
+ sconto1: sconto1,
|
|
|
+ sconto2: sconto2,
|
|
|
+ sconto3: sconto3,
|
|
|
+ sconto4: sconto4,
|
|
|
+ sconto5: sconto5,
|
|
|
+ totale: prezzoNetto * quantita,
|
|
|
+ promozione: 0,
|
|
|
+ rigaPromozione: 0,
|
|
|
+ colli: colli,
|
|
|
+ ),
|
|
|
+ );
|
|
|
_artCtrl.clear();
|
|
|
setState(() => _searchResults = []);
|
|
|
}
|
|
|
|
|
|
+ Future<void> _selezionaCliente() async {
|
|
|
+ final agente = ref.read(authProvider).agente;
|
|
|
+ if (agente == null) return;
|
|
|
+ final cliente = await Navigator.push<Cliente>(
|
|
|
+ context,
|
|
|
+ MaterialPageRoute(
|
|
|
+ builder: (_) => SelezioneClientePage(idAgente: agente.idAgente),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ if (cliente != null) _onClienteSelezionato(cliente);
|
|
|
+ }
|
|
|
+
|
|
|
+ void _onClienteSelezionato(Cliente cliente) async {
|
|
|
+ ref.read(orderProvider.notifier).setIdCliente(cliente.idCliente);
|
|
|
+ final filialiDao = ref.read(filialiDaoProvider);
|
|
|
+ final filiali = await filialiDao.getDestinationsForCustomer(
|
|
|
+ cliente.idCliente,
|
|
|
+ );
|
|
|
+ setState(() {
|
|
|
+ _cliente = cliente;
|
|
|
+ _selectedFiliale = null;
|
|
|
+ _selectedPagamento = cliente.idPagamento;
|
|
|
+ _selectedListino = cliente.idListino;
|
|
|
+ _filiali = filiali;
|
|
|
+ _noteClienteCtrl.text = cliente.noteOrdine ?? '';
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
Future<void> _salva() async {
|
|
|
final notifier = ref.read(orderProvider.notifier);
|
|
|
notifier.setIdFiliale(_selectedFiliale);
|
|
|
@@ -190,8 +283,7 @@ class _OrderEditPageState extends ConsumerState<OrderEditPage>
|
|
|
|
|
|
return Scaffold(
|
|
|
appBar: AppBar(
|
|
|
- title:
|
|
|
- Text('Ordine #${orderState.order?.numeroOrdine ?? 'nuovo'}'),
|
|
|
+ title: Text('Ordine #${orderState.order?.numeroOrdine ?? 'nuovo'}'),
|
|
|
bottom: TabBar(
|
|
|
controller: _tabController,
|
|
|
tabs: const [
|
|
|
@@ -200,17 +292,19 @@ class _OrderEditPageState extends ConsumerState<OrderEditPage>
|
|
|
],
|
|
|
),
|
|
|
),
|
|
|
- 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
|
|
|
+ body: SafeArea(
|
|
|
+ child: 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),
|
|
|
@@ -221,43 +315,141 @@ class _OrderEditPageState extends ConsumerState<OrderEditPage>
|
|
|
}
|
|
|
|
|
|
Widget _buildHeaderTab(ThemeData theme) {
|
|
|
+ final cs = theme.colorScheme;
|
|
|
+ final bgColor = creditScoreBg(_cliente?.creditScoreColor);
|
|
|
+
|
|
|
return SingleChildScrollView(
|
|
|
padding: const EdgeInsets.all(16),
|
|
|
child: Column(
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
children: [
|
|
|
+ Card(
|
|
|
+ elevation: 0,
|
|
|
+ color: Colors.white,
|
|
|
+ shape: RoundedRectangleBorder(
|
|
|
+ borderRadius: BorderRadius.circular(12),
|
|
|
+ side: BorderSide(color: cs.outlineVariant),
|
|
|
+ ),
|
|
|
+ child: Padding(
|
|
|
+ padding: const EdgeInsets.all(12),
|
|
|
+ child: Row(
|
|
|
+ children: [
|
|
|
+ Expanded(
|
|
|
+ child: Column(
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
+ children: [
|
|
|
+ Text(
|
|
|
+ 'Cliente',
|
|
|
+ style: TextStyle(
|
|
|
+ fontSize: 12,
|
|
|
+ fontWeight: FontWeight.w600,
|
|
|
+ color: cs.onSurfaceVariant,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ const SizedBox(height: 4),
|
|
|
+ if (_cliente != null)
|
|
|
+ Row(
|
|
|
+ children: [
|
|
|
+ Container(
|
|
|
+ padding: const EdgeInsets.symmetric(
|
|
|
+ horizontal: 8,
|
|
|
+ vertical: 4,
|
|
|
+ ),
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ color: bgColor ?? cs.surfaceVariant,
|
|
|
+ borderRadius: BorderRadius.circular(4),
|
|
|
+ ),
|
|
|
+ child: Text(
|
|
|
+ _cliente!.codCliente ?? '',
|
|
|
+ style: TextStyle(
|
|
|
+ fontSize: 12,
|
|
|
+ height: 16 / 12,
|
|
|
+ fontWeight: FontWeight.w500,
|
|
|
+ color: bgColor != null
|
|
|
+ ? Colors.white
|
|
|
+ : cs.onSurfaceVariant,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ const SizedBox(width: 8),
|
|
|
+ Expanded(
|
|
|
+ child: Text(
|
|
|
+ _cliente!.ragioneSociale ?? '',
|
|
|
+ style: theme.textTheme.bodyMedium?.copyWith(
|
|
|
+ fontWeight: FontWeight.w600,
|
|
|
+ ),
|
|
|
+ maxLines: 1,
|
|
|
+ overflow: TextOverflow.ellipsis,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ )
|
|
|
+ else
|
|
|
+ Text(
|
|
|
+ 'Nessun cliente selezionato',
|
|
|
+ style: theme.textTheme.bodyMedium?.copyWith(
|
|
|
+ color: cs.onSurfaceVariant,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ IconButton(
|
|
|
+ icon: Icon(Icons.search, color: cs.primary),
|
|
|
+ onPressed: _selezionaCliente,
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ const SizedBox(height: 16),
|
|
|
DropdownButtonFormField<int>(
|
|
|
value: _selectedFiliale,
|
|
|
decoration: const InputDecoration(labelText: 'Filiale'),
|
|
|
items: _filiali
|
|
|
- .map((f) => DropdownMenuItem(
|
|
|
- value: f.idFiliale,
|
|
|
- child: Text(f.descrizione ?? ''),
|
|
|
- ))
|
|
|
+ .map(
|
|
|
+ (f) => DropdownMenuItem(
|
|
|
+ value: f.idFiliale,
|
|
|
+ child: CodDescDropdownItem(
|
|
|
+ codice: f.codFiliale,
|
|
|
+ descrizione: f.descrizione,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ )
|
|
|
.toList(),
|
|
|
onChanged: (v) => setState(() => _selectedFiliale = v),
|
|
|
),
|
|
|
const SizedBox(height: 16),
|
|
|
DropdownButtonFormField<int>(
|
|
|
- value: _selectedListino,
|
|
|
+ initialValue: _selectedListino,
|
|
|
decoration: const InputDecoration(labelText: 'Listino'),
|
|
|
items: _listini
|
|
|
- .map((l) => DropdownMenuItem(
|
|
|
- value: l.idListino,
|
|
|
- child: Text(l.descrizione ?? ''),
|
|
|
- ))
|
|
|
+ .map(
|
|
|
+ (l) => DropdownMenuItem(
|
|
|
+ value: l.idListino,
|
|
|
+ child: CodDescDropdownItem(
|
|
|
+ codice: l.codListino,
|
|
|
+ descrizione: l.descrizione,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ )
|
|
|
.toList(),
|
|
|
onChanged: (v) => setState(() => _selectedListino = v),
|
|
|
),
|
|
|
const SizedBox(height: 16),
|
|
|
DropdownButtonFormField<int>(
|
|
|
- value: _selectedPagamento,
|
|
|
+ initialValue: _selectedPagamento,
|
|
|
decoration: const InputDecoration(labelText: 'Pagamento'),
|
|
|
items: _pagamenti
|
|
|
- .map((p) => DropdownMenuItem(
|
|
|
- value: p.idPagamento,
|
|
|
- child: Text(p.descrizione ?? ''),
|
|
|
- ))
|
|
|
+ .map(
|
|
|
+ (p) => DropdownMenuItem(
|
|
|
+ value: p.idPagamento,
|
|
|
+ child: CodDescDropdownItem(
|
|
|
+ codice: p.codPagamento,
|
|
|
+ descrizione: p.descrizione,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ )
|
|
|
.toList(),
|
|
|
onChanged: (v) => setState(() => _selectedPagamento = v),
|
|
|
),
|
|
|
@@ -295,20 +487,35 @@ class _OrderEditPageState extends ConsumerState<OrderEditPage>
|
|
|
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,
|
|
|
+ child: Row(
|
|
|
+ children: [
|
|
|
+ Expanded(
|
|
|
+ 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,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ const SizedBox(width: 8),
|
|
|
+ FilterChip(
|
|
|
+ label: const Text('Listino cliente'),
|
|
|
+ selected: _usaListinoCliente,
|
|
|
+ onSelected: (v) {
|
|
|
+ setState(() => _usaListinoCliente = v);
|
|
|
+ _searchArticles(_artCtrl.text);
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ ],
|
|
|
),
|
|
|
),
|
|
|
if (_searchResults.isNotEmpty)
|
|
|
@@ -318,13 +525,15 @@ class _OrderEditPageState extends ConsumerState<OrderEditPage>
|
|
|
itemCount: _searchResults.length,
|
|
|
itemBuilder: (_, i) => ListTile(
|
|
|
leading: ArticleImage(
|
|
|
- idArticolo: _searchResults[i].idArticolo,
|
|
|
- codArticolo: _searchResults[i].codArticolo),
|
|
|
+ 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')),
|
|
|
+ onPressed: () => _showArticlePicker(_searchResults[i]),
|
|
|
+ child: const Text('Aggiungi'),
|
|
|
+ ),
|
|
|
),
|
|
|
),
|
|
|
),
|
|
|
@@ -346,8 +555,7 @@ class _OrderEditPageState extends ConsumerState<OrderEditPage>
|
|
|
padding: const EdgeInsets.all(16),
|
|
|
decoration: BoxDecoration(
|
|
|
color: theme.colorScheme.surfaceContainerHighest,
|
|
|
- border: Border(
|
|
|
- top: BorderSide(color: theme.dividerColor)),
|
|
|
+ border: Border(top: BorderSide(color: theme.dividerColor)),
|
|
|
),
|
|
|
child: Row(
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
@@ -355,8 +563,9 @@ class _OrderEditPageState extends ConsumerState<OrderEditPage>
|
|
|
Text('Qtà: ${orderState.totQta.toStringAsFixed(1)}'),
|
|
|
Text(
|
|
|
'Totale: ${NumberFormat.currency(locale: 'it', symbol: '€').format(orderState.totale)}',
|
|
|
- style: theme.textTheme.titleMedium
|
|
|
- ?.copyWith(fontWeight: FontWeight.bold),
|
|
|
+ style: theme.textTheme.titleMedium?.copyWith(
|
|
|
+ fontWeight: FontWeight.bold,
|
|
|
+ ),
|
|
|
),
|
|
|
],
|
|
|
),
|
|
|
@@ -371,7 +580,11 @@ class _LineCard extends StatelessWidget {
|
|
|
final VoidCallback onDelete;
|
|
|
final ThemeData theme;
|
|
|
|
|
|
- const _LineCard({required this.line, required this.onDelete, required this.theme});
|
|
|
+ const _LineCard({
|
|
|
+ required this.line,
|
|
|
+ required this.onDelete,
|
|
|
+ required this.theme,
|
|
|
+ });
|
|
|
|
|
|
@override
|
|
|
Widget build(BuildContext context) {
|
|
|
@@ -380,29 +593,33 @@ class _LineCard extends StatelessWidget {
|
|
|
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)),
|
|
|
+ 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"}€'),
|
|
|
+ '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),
|
|
|
+ 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)),
|
|
|
+ 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),
|
|
|
+ icon: const Icon(Icons.delete_outline, size: 20),
|
|
|
+ onPressed: onDelete,
|
|
|
+ ),
|
|
|
],
|
|
|
),
|
|
|
),
|
|
|
@@ -414,15 +631,30 @@ class _ArticlePickerSheet extends StatefulWidget {
|
|
|
final Articolo articolo;
|
|
|
final List<Listino> listini;
|
|
|
final ListiniDao listiniDao;
|
|
|
+ final ScontiDao scontiDao;
|
|
|
+ final String dataOrdine;
|
|
|
final bool canEditPrezzo;
|
|
|
final int? idListinoDefault;
|
|
|
- final void Function(double colli, double quantita, double prezzo, int listinoId)
|
|
|
- onConfirm;
|
|
|
+ final void Function(
|
|
|
+ double colli,
|
|
|
+ double quantita,
|
|
|
+ double prezzoLordo,
|
|
|
+ double prezzoNetto,
|
|
|
+ int listinoId, {
|
|
|
+ double sconto1,
|
|
|
+ double sconto2,
|
|
|
+ double sconto3,
|
|
|
+ double sconto4,
|
|
|
+ double sconto5,
|
|
|
+ })
|
|
|
+ onConfirm;
|
|
|
|
|
|
const _ArticlePickerSheet({
|
|
|
required this.articolo,
|
|
|
required this.listini,
|
|
|
required this.listiniDao,
|
|
|
+ required this.scontiDao,
|
|
|
+ required this.dataOrdine,
|
|
|
required this.canEditPrezzo,
|
|
|
this.idListinoDefault,
|
|
|
required this.onConfirm,
|
|
|
@@ -436,9 +668,20 @@ class _ArticlePickerSheetState extends State<_ArticlePickerSheet> {
|
|
|
final _colliCtrl = TextEditingController();
|
|
|
final _qtaCtrl = TextEditingController();
|
|
|
final _prezzoCtrl = TextEditingController();
|
|
|
+ final _sconto1Ctrl = TextEditingController();
|
|
|
+ final _sconto2Ctrl = TextEditingController();
|
|
|
+ final _sconto3Ctrl = TextEditingController();
|
|
|
+ final _sconto4Ctrl = TextEditingController();
|
|
|
+ final _sconto5Ctrl = TextEditingController();
|
|
|
|
|
|
int? _selectedListino;
|
|
|
double _prezzo = 0;
|
|
|
+ double _prezzoNetto = 0;
|
|
|
+ double _sconto1 = 0;
|
|
|
+ double _sconto2 = 0;
|
|
|
+ double _sconto3 = 0;
|
|
|
+ double _sconto4 = 0;
|
|
|
+ double _sconto5 = 0;
|
|
|
|
|
|
bool _updatingColli = false;
|
|
|
bool _updatingQta = false;
|
|
|
@@ -457,18 +700,94 @@ class _ArticlePickerSheetState extends State<_ArticlePickerSheet> {
|
|
|
_colliCtrl.dispose();
|
|
|
_qtaCtrl.dispose();
|
|
|
_prezzoCtrl.dispose();
|
|
|
+ _sconto1Ctrl.dispose();
|
|
|
+ _sconto2Ctrl.dispose();
|
|
|
+ _sconto3Ctrl.dispose();
|
|
|
+ _sconto4Ctrl.dispose();
|
|
|
+ _sconto5Ctrl.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);
|
|
|
+ idListino,
|
|
|
+ widget.articolo.idArticolo,
|
|
|
+ );
|
|
|
+ if (lp != null) {
|
|
|
+ if (lp.prezzo != null) {
|
|
|
+ setState(() => _prezzo = lp.prezzo!);
|
|
|
+ _prezzoCtrl.text = _prezzo.toStringAsFixed(2);
|
|
|
+ }
|
|
|
+ if (lp.idSconto != null) {
|
|
|
+ final sconto = await widget.scontiDao.findById(lp.idSconto!);
|
|
|
+ setState(() {
|
|
|
+ _sconto1 = sconto?.sconto1 ?? 0;
|
|
|
+ _sconto2 = sconto?.sconto2 ?? 0;
|
|
|
+ _sconto3 = sconto?.sconto3 ?? 0;
|
|
|
+ _sconto4 = sconto?.sconto4 ?? 0;
|
|
|
+ _sconto5 = sconto?.sconto5 ?? 0;
|
|
|
+ });
|
|
|
+ _sconto1Ctrl.text = _sconto1 > 0 ? _sconto1.toStringAsFixed(1) : '';
|
|
|
+ _sconto2Ctrl.text = _sconto2 > 0 ? _sconto2.toStringAsFixed(1) : '';
|
|
|
+ _sconto3Ctrl.text = _sconto3 > 0 ? _sconto3.toStringAsFixed(1) : '';
|
|
|
+ _sconto4Ctrl.text = _sconto4 > 0 ? _sconto4.toStringAsFixed(1) : '';
|
|
|
+ _sconto5Ctrl.text = _sconto5 > 0 ? _sconto5.toStringAsFixed(1) : '';
|
|
|
+ } else {
|
|
|
+ _resetSconti();
|
|
|
+ }
|
|
|
+ _calcolaPrezzoNetto();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ void _resetSconti() {
|
|
|
+ setState(() {
|
|
|
+ _sconto1 = 0;
|
|
|
+ _sconto2 = 0;
|
|
|
+ _sconto3 = 0;
|
|
|
+ _sconto4 = 0;
|
|
|
+ _sconto5 = 0;
|
|
|
+ });
|
|
|
+ _sconto1Ctrl.clear();
|
|
|
+ _sconto2Ctrl.clear();
|
|
|
+ _sconto3Ctrl.clear();
|
|
|
+ _sconto4Ctrl.clear();
|
|
|
+ _sconto5Ctrl.clear();
|
|
|
+ }
|
|
|
+
|
|
|
+ void _calcolaPrezzoNetto() {
|
|
|
+ double n = _prezzo;
|
|
|
+ n *= (100 - _sconto1) / 100;
|
|
|
+ n *= (100 - _sconto2) / 100;
|
|
|
+ n *= (100 - _sconto3) / 100;
|
|
|
+ n *= (100 - _sconto4) / 100;
|
|
|
+ n *= (100 - _sconto5) / 100;
|
|
|
+ setState(() => _prezzoNetto = n);
|
|
|
+ }
|
|
|
+
|
|
|
+ void _onPrezzoChanged(String v) {
|
|
|
+ setState(() => _prezzo = double.tryParse(v) ?? 0);
|
|
|
+ _calcolaPrezzoNetto();
|
|
|
+ }
|
|
|
+
|
|
|
+ void _onScontoChanged(int index, String v) {
|
|
|
+ final val = double.tryParse(v) ?? 0;
|
|
|
+ setState(() {
|
|
|
+ switch (index) {
|
|
|
+ case 1:
|
|
|
+ _sconto1 = val;
|
|
|
+ case 2:
|
|
|
+ _sconto2 = val;
|
|
|
+ case 3:
|
|
|
+ _sconto3 = val;
|
|
|
+ case 4:
|
|
|
+ _sconto4 = val;
|
|
|
+ case 5:
|
|
|
+ _sconto5 = val;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ _calcolaPrezzoNetto();
|
|
|
+ }
|
|
|
+
|
|
|
void _onColliChanged(String v) {
|
|
|
if (_updatingColli) return;
|
|
|
_updatingQta = true;
|
|
|
@@ -497,9 +816,20 @@ class _ArticlePickerSheetState extends State<_ArticlePickerSheet> {
|
|
|
void _confirm() {
|
|
|
final colli = double.tryParse(_colliCtrl.text) ?? 0;
|
|
|
final qta = double.tryParse(_qtaCtrl.text) ?? 0;
|
|
|
- final prezzo = double.tryParse(_prezzoCtrl.text) ?? 0;
|
|
|
+ final prezzoLordo = double.tryParse(_prezzoCtrl.text) ?? 0;
|
|
|
final listinoId = _selectedListino ?? 0;
|
|
|
- widget.onConfirm(colli, qta, prezzo, listinoId);
|
|
|
+ widget.onConfirm(
|
|
|
+ colli,
|
|
|
+ qta,
|
|
|
+ prezzoLordo,
|
|
|
+ _prezzoNetto,
|
|
|
+ listinoId,
|
|
|
+ sconto1: _sconto1,
|
|
|
+ sconto2: _sconto2,
|
|
|
+ sconto3: _sconto3,
|
|
|
+ sconto4: _sconto4,
|
|
|
+ sconto5: _sconto5,
|
|
|
+ );
|
|
|
Navigator.pop(context);
|
|
|
}
|
|
|
|
|
|
@@ -509,31 +839,44 @@ class _ArticlePickerSheetState extends State<_ArticlePickerSheet> {
|
|
|
return Padding(
|
|
|
padding: EdgeInsets.only(
|
|
|
bottom: MediaQuery.of(context).viewInsets.bottom,
|
|
|
- left: 16, right: 16, top: 16,
|
|
|
+ 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),
|
|
|
+ Text(
|
|
|
+ widget.articolo.descrizione ?? '',
|
|
|
+ style: theme.textTheme.titleMedium,
|
|
|
+ ),
|
|
|
const SizedBox(height: 4),
|
|
|
- Text(widget.articolo.codArticolo ?? '',
|
|
|
- style: theme.textTheme.bodySmall),
|
|
|
+ Text(
|
|
|
+ widget.articolo.codArticolo ?? '',
|
|
|
+ style: theme.textTheme.bodySmall,
|
|
|
+ ),
|
|
|
if (widget.articolo.pezziConfezione != null)
|
|
|
- Text('Pezzi/confezione: ${widget.articolo.pezziConfezione}',
|
|
|
- style: theme.textTheme.bodySmall),
|
|
|
+ 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 ?? ''),
|
|
|
- ))
|
|
|
+ .map(
|
|
|
+ (l) => DropdownMenuItem(
|
|
|
+ value: l.idListino,
|
|
|
+ child: CodDescDropdownItem(
|
|
|
+ codice: l.codListino,
|
|
|
+ descrizione: l.descrizione,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ )
|
|
|
.toList(),
|
|
|
onChanged: (v) {
|
|
|
setState(() => _selectedListino = v);
|
|
|
@@ -541,13 +884,98 @@ class _ArticlePickerSheetState extends State<_ArticlePickerSheet> {
|
|
|
},
|
|
|
),
|
|
|
const SizedBox(height: 12),
|
|
|
+ TextField(
|
|
|
+ controller: _prezzoCtrl,
|
|
|
+ decoration: const InputDecoration(labelText: 'Prezzo lordo'),
|
|
|
+ keyboardType: const TextInputType.numberWithOptions(
|
|
|
+ decimal: true,
|
|
|
+ ),
|
|
|
+ enabled: widget.canEditPrezzo,
|
|
|
+ onChanged: _onPrezzoChanged,
|
|
|
+ ),
|
|
|
+ const SizedBox(height: 12),
|
|
|
+ Row(
|
|
|
+ children: [
|
|
|
+ Expanded(
|
|
|
+ child: TextField(
|
|
|
+ controller: _sconto1Ctrl,
|
|
|
+ decoration: const InputDecoration(labelText: 'Sconto 1 %'),
|
|
|
+ keyboardType: const TextInputType.numberWithOptions(
|
|
|
+ decimal: true,
|
|
|
+ ),
|
|
|
+ onChanged: (v) => _onScontoChanged(1, v),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ const SizedBox(width: 12),
|
|
|
+ Expanded(
|
|
|
+ child: TextField(
|
|
|
+ controller: _sconto2Ctrl,
|
|
|
+ decoration: const InputDecoration(labelText: 'Sconto 2 %'),
|
|
|
+ keyboardType: const TextInputType.numberWithOptions(
|
|
|
+ decimal: true,
|
|
|
+ ),
|
|
|
+ onChanged: (v) => _onScontoChanged(2, v),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ const SizedBox(height: 12),
|
|
|
+ Row(
|
|
|
+ children: [
|
|
|
+ Expanded(
|
|
|
+ child: TextField(
|
|
|
+ controller: _sconto3Ctrl,
|
|
|
+ decoration: const InputDecoration(labelText: 'Sconto 3 %'),
|
|
|
+ keyboardType: const TextInputType.numberWithOptions(
|
|
|
+ decimal: true,
|
|
|
+ ),
|
|
|
+ onChanged: (v) => _onScontoChanged(3, v),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ const SizedBox(width: 12),
|
|
|
+ Expanded(
|
|
|
+ child: TextField(
|
|
|
+ controller: _sconto4Ctrl,
|
|
|
+ decoration: const InputDecoration(labelText: 'Sconto 4 %'),
|
|
|
+ keyboardType: const TextInputType.numberWithOptions(
|
|
|
+ decimal: true,
|
|
|
+ ),
|
|
|
+ onChanged: (v) => _onScontoChanged(4, v),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ const SizedBox(height: 12),
|
|
|
+ SizedBox(
|
|
|
+ width: 140,
|
|
|
+ child: TextField(
|
|
|
+ controller: _sconto5Ctrl,
|
|
|
+ decoration: const InputDecoration(labelText: 'Sconto 5 %'),
|
|
|
+ keyboardType: const TextInputType.numberWithOptions(
|
|
|
+ decimal: true,
|
|
|
+ ),
|
|
|
+ onChanged: (v) => _onScontoChanged(5, v),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ const SizedBox(height: 16),
|
|
|
+ Row(
|
|
|
+ children: [
|
|
|
+ Text('Prezzo netto: ', style: theme.textTheme.bodyLarge),
|
|
|
+ Text(
|
|
|
+ _prezzoNetto.toStringAsFixed(2),
|
|
|
+ style: theme.textTheme.bodyLarge?.copyWith(
|
|
|
+ fontWeight: FontWeight.bold,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ const SizedBox(height: 12),
|
|
|
Row(
|
|
|
children: [
|
|
|
Expanded(
|
|
|
child: TextField(
|
|
|
controller: _colliCtrl,
|
|
|
- decoration: const InputDecoration(
|
|
|
- labelText: 'Colli'),
|
|
|
+ decoration: const InputDecoration(labelText: 'Colli'),
|
|
|
keyboardType: TextInputType.number,
|
|
|
onChanged: _onColliChanged,
|
|
|
),
|
|
|
@@ -556,22 +984,13 @@ class _ArticlePickerSheetState extends State<_ArticlePickerSheet> {
|
|
|
Expanded(
|
|
|
child: TextField(
|
|
|
controller: _qtaCtrl,
|
|
|
- decoration: const InputDecoration(
|
|
|
- labelText: 'Quantità'),
|
|
|
+ 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,
|