Persist verificationId for staff certificates
Add support for verificationId throughout the certificate flow: schema, GraphQL mutations/queries, domain, repositories, service implementation, and UI. - Backend: add verificationId to Certificate schema and include it in upsert/create mutations; add auth insecureReason notes to related connector operations. - Data layer: add verificationId parameter to StaffConnectorRepository API and propagation in implementation (SDK call remains commented with FIXME until dataconnect SDK is regenerated). - Domain: add verificationId field to StaffCertificate (constructor, copyWith, props). - Certificates flow: create verification via verificationService, pass returned verificationId to upsertStaffCertificate so the verification record is persisted with the certificate. - UI: update certificate upload page to show existing file path, disable editing of name/issuer/number, rearrange fields, move remove button, change file icon and text style. - Misc: minor lambda formatting cleanup in benefits mapping. Note: the generated dataconnect SDK must be refreshed to enable the new .verificationId(...) call (there is a commented FIXME in the connector implementation).
This commit is contained in:
@@ -49,16 +49,24 @@ class CertificatesRepositoryImpl implements CertificatesRepository {
|
||||
await _signedUrlService.createSignedUrl(fileUri: uploadRes.fileUri);
|
||||
|
||||
// 3. Initiate verification
|
||||
// 3. Initiate verification
|
||||
final String staffId = await _service.getStaffId();
|
||||
await _verificationService.createVerification(
|
||||
fileUri: uploadRes.fileUri,
|
||||
type: certificationType.value,
|
||||
category: 'CERTIFICATE',
|
||||
subjectType: 'STAFF',
|
||||
subjectId: staffId,
|
||||
final List<domain.StaffCertificate> allCerts = await getCertificates();
|
||||
final domain.StaffCertificate currentCert = allCerts.firstWhere(
|
||||
(domain.StaffCertificate c) => c.certificationType == certificationType,
|
||||
);
|
||||
|
||||
final String staffId = await _service.getStaffId();
|
||||
final VerificationResponse verificationRes = await _verificationService
|
||||
.createVerification(
|
||||
fileUri: uploadRes.fileUri,
|
||||
type: certificationType.value,
|
||||
category: 'certification',
|
||||
subjectType: 'worker',
|
||||
subjectId: staffId,
|
||||
rules: <String, dynamic>{
|
||||
'certificateDescription': currentCert.description,
|
||||
},
|
||||
);
|
||||
|
||||
// 4. Update/Create Certificate in Data Connect
|
||||
await _service.getStaffRepository().upsertStaffCertificate(
|
||||
certificationType: certificationType,
|
||||
@@ -70,6 +78,7 @@ class CertificatesRepositoryImpl implements CertificatesRepository {
|
||||
certificateNumber: certificateNumber,
|
||||
validationStatus:
|
||||
domain.StaffCertificateValidationStatus.pendingExpertReview,
|
||||
verificationId: verificationRes.verificationId,
|
||||
);
|
||||
|
||||
// 5. Return updated list or the specific certificate
|
||||
|
||||
@@ -44,6 +44,7 @@ class _CertificateUploadPageState extends State<CertificateUploadPage> {
|
||||
_numberController.text = widget.certificate!.certificateNumber ?? '';
|
||||
_nameController.text = widget.certificate!.name;
|
||||
_selectedType = widget.certificate!.certificationType;
|
||||
_selectedFilePath = widget.certificate?.certificateUrl;
|
||||
} else {
|
||||
_selectedType = ComplianceType.other;
|
||||
}
|
||||
@@ -116,6 +117,8 @@ class _CertificateUploadPageState extends State<CertificateUploadPage> {
|
||||
}
|
||||
|
||||
Future<void> _showRemoveConfirmation(BuildContext context) async {
|
||||
final CertificateUploadCubit cubit =
|
||||
BlocProvider.of<CertificateUploadCubit>(context);
|
||||
final bool? confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (BuildContext context) => AlertDialog(
|
||||
@@ -136,9 +139,7 @@ class _CertificateUploadPageState extends State<CertificateUploadPage> {
|
||||
);
|
||||
|
||||
if (confirmed == true && mounted) {
|
||||
BlocProvider.of<CertificateUploadCubit>(
|
||||
context,
|
||||
).deleteCertificate(widget.certificate!.certificationType);
|
||||
await cubit.deleteCertificate(widget.certificate!.certificationType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,6 +190,7 @@ class _CertificateUploadPageState extends State<CertificateUploadPage> {
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
TextField(
|
||||
controller: _nameController,
|
||||
enabled: false,
|
||||
decoration: InputDecoration(
|
||||
hintText: t.staff_certificates.upload_modal.name_hint,
|
||||
border: OutlineInputBorder(
|
||||
@@ -198,6 +200,46 @@ class _CertificateUploadPageState extends State<CertificateUploadPage> {
|
||||
),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
|
||||
// Issuer Field
|
||||
Text(
|
||||
t.staff_certificates.upload_modal.issuer_label,
|
||||
style: UiTypography.body2m.textPrimary,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
TextField(
|
||||
controller: _issuerController,
|
||||
enabled: false,
|
||||
decoration: InputDecoration(
|
||||
hintText: t.staff_certificates.upload_modal.issuer_hint,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
|
||||
// Certificate Number Field
|
||||
Text(
|
||||
'Certificate Number',
|
||||
style: UiTypography.body2m.textPrimary,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
TextField(
|
||||
controller: _numberController,
|
||||
enabled: false,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Enter number if applicable',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
|
||||
const Divider(),
|
||||
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
|
||||
// Expiry Date Field
|
||||
Text(
|
||||
t.staff_certificates.upload_modal.expiry_label,
|
||||
@@ -239,40 +281,6 @@ class _CertificateUploadPageState extends State<CertificateUploadPage> {
|
||||
),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
|
||||
// Issuer Field
|
||||
Text(
|
||||
t.staff_certificates.upload_modal.issuer_label,
|
||||
style: UiTypography.body2m.textPrimary,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
TextField(
|
||||
controller: _issuerController,
|
||||
decoration: InputDecoration(
|
||||
hintText: t.staff_certificates.upload_modal.issuer_hint,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
|
||||
// Certificate Number Field
|
||||
Text(
|
||||
'Certificate Number',
|
||||
style: UiTypography.body2m.textPrimary,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
TextField(
|
||||
controller: _numberController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Enter number if applicable',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
|
||||
// File Selector
|
||||
Text(
|
||||
t.staff_certificates.upload_modal.upload_file,
|
||||
@@ -283,29 +291,6 @@ class _CertificateUploadPageState extends State<CertificateUploadPage> {
|
||||
selectedFilePath: _selectedFilePath,
|
||||
onTap: _pickFile,
|
||||
),
|
||||
|
||||
// Remove Button (only if existing)
|
||||
if (widget.certificate != null) ...<Widget>[
|
||||
const SizedBox(height: UiConstants.space8),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: TextButton.icon(
|
||||
onPressed: () => _showRemoveConfirmation(context),
|
||||
icon: const Icon(UiIcons.delete, size: 20),
|
||||
label: Text(t.staff_certificates.card.remove),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: UiColors.destructive,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: UiConstants.space4,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
side: const BorderSide(color: UiColors.destructive),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -314,6 +299,7 @@ class _CertificateUploadPageState extends State<CertificateUploadPage> {
|
||||
padding: const EdgeInsets.all(UiConstants.space5),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: UiConstants.space4,
|
||||
children: <Widget>[
|
||||
// Attestation
|
||||
Row(
|
||||
@@ -334,7 +320,6 @@ class _CertificateUploadPageState extends State<CertificateUploadPage> {
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
@@ -391,6 +376,30 @@ class _CertificateUploadPageState extends State<CertificateUploadPage> {
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Remove Button (only if existing)
|
||||
if (widget.certificate != null) ...<Widget>[
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: TextButton.icon(
|
||||
onPressed: () => _showRemoveConfirmation(context),
|
||||
icon: const Icon(UiIcons.delete, size: 20),
|
||||
label: Text(t.staff_certificates.card.remove),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: UiColors.destructive,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: UiConstants.space4,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
side: const BorderSide(
|
||||
color: UiColors.destructive,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -449,18 +458,17 @@ class _FileSelector extends StatelessWidget {
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(UiConstants.space4),
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.tagActive,
|
||||
border: Border.all(color: UiColors.primary),
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
const Icon(UiIcons.file, color: UiColors.primary),
|
||||
const Icon(UiIcons.certificate, color: UiColors.primary),
|
||||
const SizedBox(width: UiConstants.space3),
|
||||
Expanded(
|
||||
child: Text(
|
||||
selectedFilePath!.split('/').last,
|
||||
style: UiTypography.body1m.textPrimary,
|
||||
style: UiTypography.body1m.primary,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user