
Refactors the reception process to provide clearer control over data synchronization and validation. - Replaces the `isDraft` property with `synchronized` in `StockPickingRecordEntity` to accurately represent the local data's sync status with the backend. - Introduces a new API endpoint and corresponding logic for `validateStockPicking`, allowing finalization of receptions. - Splits the "Save" action into "Synchronize data" and "Validate reception" on the details page. "Validate" now implicitly performs a synchronization first. - Updates UI elements (cards, quick actions) to reflect the new synchronization state and expose the new actions. - Corrects a typo in the `updateAllMoveLineOnStockPicking` API method name. - Improves local data update logic for scanned items, marking them as unsynchronized.
218 lines
9.7 KiB
Dart
218 lines
9.7 KiB
Dart
import 'package:e_scan/backend/objectbox/entities/stock_picking/stock_picking_record_entity.dart';
|
|
import 'package:e_scan/components/components.dart';
|
|
import 'package:e_scan/pages/operation/reception/reception_details_page_model.dart';
|
|
import 'package:e_scan/router/go_secure_router_builder.dart';
|
|
import 'package:e_scan/themes/app_theme.dart';
|
|
import 'package:e_scan/utils/utils.dart';
|
|
import 'package:flutter/scheduler.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
|
class ReceptionDetailsPage extends ConsumerStatefulWidget {
|
|
const ReceptionDetailsPage({super.key, required this.receptionId});
|
|
final int receptionId;
|
|
@override
|
|
ConsumerState<ReceptionDetailsPage> createState() =>
|
|
_ReceptionDetailsPageState();
|
|
}
|
|
|
|
class _ReceptionDetailsPageState extends ConsumerState<ReceptionDetailsPage> {
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
SchedulerBinding.instance.addPostFrameCallback((_) {
|
|
ref
|
|
.read(receptionDetailsPageModelProvider.notifier)
|
|
.getReceptionById(id: widget.receptionId);
|
|
});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final state = ref.watch(receptionDetailsPageModelProvider);
|
|
final reception = state.reception;
|
|
return Scaffold(
|
|
backgroundColor: AppTheme.of(context).primaryBackground,
|
|
appBar: MainAppbarComponent(
|
|
leading: BackButton(color: AppTheme.of(context).white),
|
|
title: "Réceptions",
|
|
subTitle: "Opérations d'Entrepôt",
|
|
),
|
|
body: state.loading
|
|
? Center(child: LoadingProgressComponent())
|
|
: RefreshIndicator(
|
|
color: AppTheme.of(context).white,
|
|
backgroundColor: AppTheme.of(context).primary,
|
|
onRefresh: () async {
|
|
await ref
|
|
.read(receptionDetailsPageModelProvider.notifier)
|
|
.getReceptionById(id: widget.receptionId);
|
|
},
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
child: ListView(
|
|
children: [
|
|
const SizedBox(height: 16),
|
|
if (reception?.isDone == false) ...[
|
|
QuickActionComponent(
|
|
onTapValidateReception: reception?.synchronized == false
|
|
? () {
|
|
ref
|
|
.read(
|
|
receptionDetailsPageModelProvider
|
|
.notifier,
|
|
)
|
|
.validate(
|
|
receptionId: widget.receptionId,
|
|
onSuccess: () {
|
|
Toast.showSuccess(
|
|
'Réception validée avec succès.',
|
|
);
|
|
},
|
|
onError: () {
|
|
Toast.showError(
|
|
'Connexion impossible. Les données seront synchronisées plus tard.',
|
|
);
|
|
},
|
|
);
|
|
}
|
|
: null,
|
|
validateReceptionLoading: state.validateLoading,
|
|
syncReceptionLoading: state.saveLoading,
|
|
onTapSyncReception: reception?.synchronized == false
|
|
? () {
|
|
ref
|
|
.read(
|
|
receptionDetailsPageModelProvider
|
|
.notifier,
|
|
)
|
|
.save(
|
|
receptionId: widget.receptionId,
|
|
onSuccess: () {
|
|
Toast.showSuccess(
|
|
'Les données sont synchronisées.',
|
|
);
|
|
},
|
|
onError: () {
|
|
Toast.showError(
|
|
'Connexion impossible. Les données seront synchronisées plus tard.',
|
|
);
|
|
},
|
|
);
|
|
}
|
|
: null,
|
|
onTapScan: () {
|
|
final id = reception?.id;
|
|
if (id == null) return;
|
|
ReceptionScanRoute(receptionId: id)
|
|
.push(context)
|
|
.then(
|
|
(v) => ref
|
|
.read(
|
|
receptionDetailsPageModelProvider
|
|
.notifier,
|
|
)
|
|
.getReceptionById(id: widget.receptionId),
|
|
);
|
|
},
|
|
),
|
|
const SizedBox(height: 16),
|
|
],
|
|
Text(
|
|
'Détails réception',
|
|
style: AppTheme.of(
|
|
context,
|
|
).bodyMedium.copyWith(fontWeight: FontWeight.bold),
|
|
),
|
|
SizedBox(height: 10),
|
|
StockPickingCard(
|
|
synchronized: reception?.synchronized == true,
|
|
isDone: reception?.isDone == true,
|
|
margin: EdgeInsets.symmetric(horizontal: 5),
|
|
reference: reception?.name ?? '',
|
|
from: reception?.locationId.target?.completeName,
|
|
to: reception?.locationDestId.target?.completeName,
|
|
contact: reception?.partnerId.target?.displayName,
|
|
origin: reception?.origin,
|
|
status: reception?.state,
|
|
),
|
|
SizedBox(height: 20),
|
|
Text(
|
|
'Produits',
|
|
style: AppTheme.of(
|
|
context,
|
|
).bodyMedium.copyWith(fontWeight: FontWeight.bold),
|
|
),
|
|
SizedBox(height: 10),
|
|
...reception?.moveIdsWithoutPackage
|
|
.map(
|
|
(move) => Card(
|
|
color: AppTheme.of(context).primaryBackground,
|
|
child: ListTile(
|
|
title: Text(
|
|
move.productId.target?.displayName ?? '',
|
|
style: TextStyle(color: Colors.black),
|
|
),
|
|
subtitle: Wrap(
|
|
spacing: 5,
|
|
children: [
|
|
Chip(
|
|
backgroundColor: AppTheme.of(
|
|
context,
|
|
).secondaryBackground,
|
|
label: Text(
|
|
"Qté demandée: ${move.productUomQty}",
|
|
),
|
|
),
|
|
Chip(
|
|
backgroundColor: AppTheme.of(
|
|
context,
|
|
).secondaryBackground,
|
|
label: Text(
|
|
"Qté reçue: ${move.quantity}",
|
|
),
|
|
),
|
|
Chip(
|
|
backgroundColor: AppTheme.of(
|
|
context,
|
|
).secondaryBackground,
|
|
label: Text(
|
|
"Ecart: ${move.ecart}",
|
|
style: TextStyle(
|
|
color: Colors.green,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
)
|
|
.toList() ??
|
|
[],
|
|
],
|
|
),
|
|
),
|
|
),
|
|
floatingActionButton: reception?.isDone == false
|
|
? FloatingActionButton(
|
|
backgroundColor: AppTheme.of(context).primary,
|
|
onPressed: () {
|
|
final id = reception?.id;
|
|
if (id == null) return;
|
|
ReceptionScanRoute(receptionId: id)
|
|
.push(context)
|
|
.then(
|
|
(v) => ref
|
|
.read(receptionDetailsPageModelProvider.notifier)
|
|
.getReceptionById(id: widget.receptionId),
|
|
);
|
|
},
|
|
child: Icon(Icons.qr_code, color: AppTheme.of(context).white),
|
|
)
|
|
: null,
|
|
);
|
|
}
|
|
}
|