rayhan-erp/frontend/lib/screens/purchase_order_detail_scree...

226 lines
8.6 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:intl/intl.dart';
import '../models/purchase_order.dart';
import '../providers/achats_provider.dart';
class PurchaseOrderDetailScreen extends StatelessWidget {
final PurchaseOrder order;
const PurchaseOrderDetailScreen({super.key, required this.order});
@override
Widget build(BuildContext context) {
final fmt = NumberFormat.currency(locale: 'fr_TN', symbol: 'TND', decimalDigits: 3);
final statusColor = Color(order.statutColor);
return Scaffold(
backgroundColor: const Color(0xFFF5F7FA),
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
title: Text(order.reference ?? '',
style: const TextStyle(fontWeight: FontWeight.bold)),
actions: [
if (order.peutReceptionner)
Padding(
padding: const EdgeInsets.only(right: 12),
child: ElevatedButton.icon(
onPressed: () => _confirmReceive(context),
icon: const Icon(Icons.inventory_outlined, size: 18),
label: const Text('Réceptionner'),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF8B5CF6),
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
),
),
],
),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: statusColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: statusColor.withOpacity(0.3)),
),
child: Row(
children: [
Icon(Icons.circle, color: statusColor, size: 10),
const SizedBox(width: 8),
Text(order.statutLabel,
style: TextStyle(color: statusColor, fontWeight: FontWeight.bold, fontSize: 14)),
],
),
),
const SizedBox(height: 16),
_InfoCard(children: [
_InfoRow(label: 'Fournisseur', value: order.fournisseur?.raisonSociale ?? ''),
_InfoRow(label: 'Date commande', value: order.dateCommande),
if (order.dateLivraisonPrevue != null)
_InfoRow(label: 'Livraison prévue', value: order.dateLivraisonPrevue!),
if (order.notes != null && order.notes!.isNotEmpty)
_InfoRow(label: 'Notes', value: order.notes!),
]),
const SizedBox(height: 16),
const Text('Lignes de commande',
style: TextStyle(fontWeight: FontWeight.w700, fontSize: 14)),
const SizedBox(height: 8),
...order.lignes.map((l) => _LigneCard(ligne: l, fmt: fmt)),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 8, offset: const Offset(0, 2))],
),
child: Column(
children: [
_TotalRow(label: 'Total HT', value: fmt.format(order.totalHT)),
_TotalRow(label: 'TVA (19%)', value: fmt.format(order.totalTVA)),
const Divider(),
_TotalRow(label: 'Total TTC', value: fmt.format(order.totalTTC),
bold: true, color: const Color(0xFF8B5CF6)),
],
),
),
const SizedBox(height: 32),
],
),
);
}
void _confirmReceive(BuildContext context) {
showDialog(
context: context,
builder: (_) => AlertDialog(
title: const Text('Confirmer la réception ?'),
content: Text(
'Réceptionner toutes les lignes de ${order.reference} ?\n\nLe stock sera incrémenté automatiquement.'),
actions: [
TextButton(onPressed: () => Navigator.pop(context), child: const Text('Annuler')),
ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: const Color(0xFF8B5CF6)),
onPressed: () async {
Navigator.pop(context);
final err = await context.read<AchatsProvider>().receive(order.id!, order.lignes);
if (context.mounted) {
if (err == null) {
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text('Réception enregistrée — stock mis à jour'),
backgroundColor: Colors.green,
));
} else {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(err), backgroundColor: Colors.red));
}
}
},
child: const Text('Confirmer', style: TextStyle(color: Colors.white)),
),
],
),
);
}
}
class _InfoCard extends StatelessWidget {
final List<Widget> children;
const _InfoCard({required this.children});
@override
Widget build(BuildContext context) => Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.04), blurRadius: 8, offset: const Offset(0, 2))],
),
child: Column(children: children),
);
}
class _InfoRow extends StatelessWidget {
final String label;
final String value;
const _InfoRow({required this.label, required this.value});
@override
Widget build(BuildContext context) => Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(width: 130,
child: Text(label, style: TextStyle(color: Colors.grey[600], fontSize: 13))),
Expanded(child: Text(value,
style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 13))),
],
),
);
}
class _LigneCard extends StatelessWidget {
final PurchaseOrderLine ligne;
final NumberFormat fmt;
const _LigneCard({required this.ligne, required this.fmt});
@override
Widget build(BuildContext context) => Container(
margin: const EdgeInsets.only(bottom: 8),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.04), blurRadius: 6, offset: const Offset(0, 1))],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(ligne.article?.designation ?? '',
style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 13)),
const SizedBox(height: 4),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${ligne.quantiteCommandee} ${ligne.article?.uniteMesure ?? ''} × ${fmt.format(ligne.prixUnitaireHT)}',
style: TextStyle(color: Colors.grey[600], fontSize: 12),
),
Text(fmt.format(ligne.montantTTC ?? ligne.montantTTCCalc),
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 13, color: Color(0xFF8B5CF6))),
],
),
if (ligne.quantiteRecue > 0)
Text('Reçu : ${ligne.quantiteRecue}',
style: const TextStyle(color: Color(0xFF10B981), fontSize: 11)),
],
),
);
}
class _TotalRow extends StatelessWidget {
final String label;
final String value;
final bool bold;
final Color? color;
const _TotalRow({required this.label, required this.value, this.bold = false, this.color});
@override
Widget build(BuildContext context) => Padding(
padding: const EdgeInsets.symmetric(vertical: 3),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label, style: TextStyle(color: Colors.grey[600], fontSize: 13)),
Text(value, style: TextStyle(
fontWeight: bold ? FontWeight.bold : FontWeight.normal,
fontSize: bold ? 16 : 13,
color: color ?? const Color(0xFF374151))),
],
),
);
}