Merge develop into master (squash): ticket #102 NIR harmonisation
- Backend: DTO NIR 15 car 2A/2B, validation format+clé, warning cohérence - BDD: nir_chiffre NOT NULL, migration pour bases existantes - Seeds: 02 nir_chiffre, 03 Marie 2A / Fatima 99 - Frontend: nir_utils, nir_text_field, formulaire pro, mock inscription AM Made-with: Cursor
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
// Définition de l'enum pour les styles de couleur/fond
|
||||
@@ -30,6 +31,7 @@ class CustomAppTextField extends StatefulWidget {
|
||||
final Iterable<String>? autofillHints;
|
||||
final TextInputAction? textInputAction;
|
||||
final ValueChanged<String>? onFieldSubmitted;
|
||||
final List<TextInputFormatter>? inputFormatters;
|
||||
|
||||
const CustomAppTextField({
|
||||
super.key,
|
||||
@@ -54,6 +56,7 @@ class CustomAppTextField extends StatefulWidget {
|
||||
this.autofillHints,
|
||||
this.textInputAction,
|
||||
this.onFieldSubmitted,
|
||||
this.inputFormatters,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -114,6 +117,7 @@ class _CustomAppTextFieldState extends State<CustomAppTextField> {
|
||||
focusNode: widget.focusNode,
|
||||
obscureText: widget.obscureText,
|
||||
keyboardType: widget.keyboardType,
|
||||
inputFormatters: widget.inputFormatters,
|
||||
autofillHints: widget.autofillHints,
|
||||
textInputAction: widget.textInputAction,
|
||||
onFieldSubmitted: widget.onFieldSubmitted,
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../utils/nir_utils.dart';
|
||||
import 'custom_app_text_field.dart';
|
||||
|
||||
/// Champ de saisie dédié au NIR (Numéro d'Inscription au Répertoire – 15 caractères).
|
||||
/// Format affiché : 1 12 34 56 789 012-34 ou 1 12 34 2A 789 012-34 pour la Corse.
|
||||
/// La valeur envoyée au [controller] est formatée ; utiliser [normalizeNir](controller.text) à la soumission.
|
||||
class NirTextField extends StatelessWidget {
|
||||
final TextEditingController controller;
|
||||
final String labelText;
|
||||
final String hintText;
|
||||
final String? Function(String?)? validator;
|
||||
final double fieldWidth;
|
||||
final double fieldHeight;
|
||||
final double labelFontSize;
|
||||
final double inputFontSize;
|
||||
final bool enabled;
|
||||
final bool readOnly;
|
||||
final CustomAppTextFieldStyle style;
|
||||
|
||||
const NirTextField({
|
||||
super.key,
|
||||
required this.controller,
|
||||
this.labelText = 'N° Sécurité Sociale (NIR)',
|
||||
this.hintText = '15 car. (ex. 1 12 34 56 789 012-34 ou 2A Corse)',
|
||||
this.validator,
|
||||
this.fieldWidth = double.infinity,
|
||||
this.fieldHeight = 53.0,
|
||||
this.labelFontSize = 18.0,
|
||||
this.inputFontSize = 18.0,
|
||||
this.enabled = true,
|
||||
this.readOnly = false,
|
||||
this.style = CustomAppTextFieldStyle.beige,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CustomAppTextField(
|
||||
controller: controller,
|
||||
labelText: labelText,
|
||||
hintText: hintText,
|
||||
fieldWidth: fieldWidth,
|
||||
fieldHeight: fieldHeight,
|
||||
labelFontSize: labelFontSize,
|
||||
inputFontSize: inputFontSize,
|
||||
keyboardType: TextInputType.text,
|
||||
validator: validator ?? validateNir,
|
||||
inputFormatters: [NirInputFormatter()],
|
||||
enabled: enabled,
|
||||
readOnly: readOnly,
|
||||
style: style,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
@@ -6,7 +7,9 @@ import 'dart:math' as math;
|
||||
import 'dart:io';
|
||||
import '../models/card_assets.dart';
|
||||
import '../config/display_config.dart';
|
||||
import '../utils/nir_utils.dart';
|
||||
import 'custom_app_text_field.dart';
|
||||
import 'nir_text_field.dart';
|
||||
import 'form_field_wrapper.dart';
|
||||
import 'app_custom_checkbox.dart';
|
||||
import 'hover_relief_widget.dart';
|
||||
@@ -97,7 +100,8 @@ class _ProfessionalInfoFormScreenState extends State<ProfessionalInfoFormScreen>
|
||||
: '';
|
||||
_birthCityController.text = data.birthCity;
|
||||
_birthCountryController.text = data.birthCountry;
|
||||
_nirController.text = data.nir;
|
||||
final nirRaw = nirToRaw(data.nir);
|
||||
_nirController.text = nirRaw.length == 15 ? formatNir(nirRaw) : data.nir;
|
||||
_agrementController.text = data.agrementNumber;
|
||||
_capacityController.text = data.capacity?.toString() ?? '';
|
||||
_photoPathFramework = data.photoPath;
|
||||
@@ -161,7 +165,7 @@ class _ProfessionalInfoFormScreenState extends State<ProfessionalInfoFormScreen>
|
||||
dateOfBirth: _selectedDate,
|
||||
birthCity: _birthCityController.text,
|
||||
birthCountry: _birthCountryController.text,
|
||||
nir: _nirController.text,
|
||||
nir: normalizeNir(_nirController.text),
|
||||
agrementNumber: _agrementController.text,
|
||||
capacity: int.tryParse(_capacityController.text),
|
||||
);
|
||||
@@ -499,7 +503,7 @@ class _ProfessionalInfoFormScreenState extends State<ProfessionalInfoFormScreen>
|
||||
children: [
|
||||
Expanded(flex: 2, child: _buildReadonlyField('Date de naissance', _dateOfBirthController.text)),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(flex: 3, child: _buildReadonlyField('NIR', _nirController.text)),
|
||||
Expanded(flex: 3, child: _buildReadonlyField('NIR', _formatNirForDisplay(_nirController.text))),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
@@ -525,6 +529,12 @@ class _ProfessionalInfoFormScreenState extends State<ProfessionalInfoFormScreen>
|
||||
);
|
||||
}
|
||||
|
||||
/// NIR formaté pour affichage (1 12 34 56 789 012-34 ou 2A pour la Corse).
|
||||
String _formatNirForDisplay(String value) {
|
||||
final raw = nirToRaw(value);
|
||||
return raw.length == 15 ? formatNir(raw) : value;
|
||||
}
|
||||
|
||||
/// Helper pour champ Readonly style "Beige"
|
||||
Widget _buildReadonlyField(String label, String value) {
|
||||
return Column(
|
||||
@@ -609,18 +619,12 @@ class _ProfessionalInfoFormScreenState extends State<ProfessionalInfoFormScreen>
|
||||
],
|
||||
),
|
||||
SizedBox(height: verticalSpacing),
|
||||
_buildField(
|
||||
config: config,
|
||||
label: 'N° Sécurité Sociale (NIR)',
|
||||
NirTextField(
|
||||
controller: _nirController,
|
||||
hint: 'Votre NIR à 13 chiffres',
|
||||
keyboardType: TextInputType.number,
|
||||
validator: (v) {
|
||||
if (v == null || v.isEmpty) return 'NIR requis';
|
||||
if (v.length != 13) return 'Le NIR doit contenir 13 chiffres';
|
||||
if (!RegExp(r'^[1-3]').hasMatch(v[0])) return 'Le NIR doit commencer par 1, 2 ou 3';
|
||||
return null;
|
||||
},
|
||||
fieldWidth: double.infinity,
|
||||
fieldHeight: config.isMobile ? 45.0 : 53.0,
|
||||
labelFontSize: config.isMobile ? 15.0 : 22.0,
|
||||
inputFontSize: config.isMobile ? 14.0 : 20.0,
|
||||
),
|
||||
SizedBox(height: verticalSpacing),
|
||||
Row(
|
||||
@@ -695,18 +699,12 @@ class _ProfessionalInfoFormScreenState extends State<ProfessionalInfoFormScreen>
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
_buildField(
|
||||
config: config,
|
||||
label: 'N° Sécurité Sociale (NIR)',
|
||||
NirTextField(
|
||||
controller: _nirController,
|
||||
hint: 'Votre NIR à 13 chiffres',
|
||||
keyboardType: TextInputType.number,
|
||||
validator: (v) {
|
||||
if (v == null || v.isEmpty) return 'NIR requis';
|
||||
if (v.length != 13) return 'Le NIR doit contenir 13 chiffres';
|
||||
if (!RegExp(r'^[1-3]').hasMatch(v[0])) return 'Le NIR doit commencer par 1, 2 ou 3';
|
||||
return null;
|
||||
},
|
||||
fieldWidth: double.infinity,
|
||||
fieldHeight: 45.0,
|
||||
labelFontSize: 15.0,
|
||||
inputFontSize: 14.0,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
@@ -796,6 +794,7 @@ class _ProfessionalInfoFormScreenState extends State<ProfessionalInfoFormScreen>
|
||||
VoidCallback? onTap,
|
||||
IconData? suffixIcon,
|
||||
String? Function(String?)? validator,
|
||||
List<TextInputFormatter>? inputFormatters,
|
||||
}) {
|
||||
if (config.isReadonly) {
|
||||
return FormFieldWrapper(
|
||||
@@ -817,6 +816,7 @@ class _ProfessionalInfoFormScreenState extends State<ProfessionalInfoFormScreen>
|
||||
onTap: onTap,
|
||||
suffixIcon: suffixIcon,
|
||||
validator: validator,
|
||||
inputFormatters: inputFormatters,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user