Added ability to generate and copy identity card number. #3

Merged
mastah merged 1 commits from feature/identitycard into develop 2026-01-27 23:24:38 +01:00
6 changed files with 142 additions and 2 deletions

View File

@@ -11,6 +11,7 @@ import { PeselComponent } from './components/pesel/pesel.component';
import { NipComponent } from './components/nip/nip.component';
import { RegonComponent } from './components/regon/regon.component';
import { IbanComponent } from './components/iban/iban.component';
import { IdentityCardComponent } from './components/identitycard/identitycard.component';
import {NgOptimizedImage} from "@angular/common";
@NgModule({
@@ -20,7 +21,8 @@ import {NgOptimizedImage} from "@angular/common";
PeselComponent,
NipComponent,
RegonComponent,
IbanComponent
IbanComponent,
IdentityCardComponent
],
imports: [
BrowserModule,

View File

@@ -3,5 +3,6 @@
<app-regon></app-regon>
<app-nip></app-nip>
<app-pesel></app-pesel>
<app-identity-card></app-identity-card>
<app-iban></app-iban>
</div>
</div>

View File

@@ -0,0 +1,17 @@
<div class="generator">
<div class="title">
<span>DOWÓD OSOBISTY</span>
</div>
<div class="container">
<input type="text" [formControl]="valueField" />
<div class="buttons">
<img id="identityCardGenerateButton" ngSrc="assets/autorenew_black_24dp.svg" title="Generuj numer dowodu" alt="Generuj numer dowodu" style="margin-left: 1em;" (click)="generate()"
height="24" width="24"/>
<img id="identityCardCopyButton" ngSrc="assets/content_copy_black_24dp.svg" title="Skopiuj numer dowodu do schowka" alt="Skopiuj numer dowodu do schowka" style="margin: auto 1em;" (click)="copyToClipboard()"
height="24" width="24"/>
<img id="identityCardHelpButton" ngSrc="assets/info_black_24dp.svg" title="Szczegóły dotyczące numeru dowodu" alt="Definicja numeru dowodu na Wikipedii" (click)="navigateToDocs()"
height="24" width="24"/>
</div>
</div>
</div>

View File

@@ -0,0 +1,35 @@
import { Component, OnInit } from '@angular/core';
import { AbstractControl, FormControl } from '@angular/forms';
import { IdentityCardService } from 'src/app/service/identity-card.service';
import { ClipboardService } from 'src/app/service/gui/clipboard.service';
@Component({
selector: 'app-identity-card',
templateUrl: './identitycard.component.html',
styleUrls: ['./identitycard.component.scss'],
standalone: false
})
export class IdentityCardComponent implements OnInit {
public valueField: FormControl;
constructor(private identityCardService: IdentityCardService, private clipboardService: ClipboardService) {
this.valueField = new FormControl('');
this.valueField.setValidators([(control: AbstractControl) => this.identityCardService.validateIdentityCard(control.value)]);
}
ngOnInit(): void {
this.generate();
}
generate(): void {
this.valueField.setValue(this.identityCardService.generateIdentityCard());
}
copyToClipboard(): void {
this.clipboardService.copyToClipboard(this.valueField.value);
}
navigateToDocs(): void {
window.open('https://pl.wikipedia.org/wiki/Dow%C3%B3d_osobisty_w_Polsce#Numer_dowodu_osobistego', "_blank");
}
}

View File

@@ -0,0 +1,85 @@
import { Injectable } from '@angular/core';
import { ValidationErrors } from '@angular/forms';
import { CommonService } from './common.service';
@Injectable({
providedIn: 'root'
})
export class IdentityCardService {
private static readonly weights: number[] = [7, 3, 1, 0, 7, 3, 1, 7, 3];
private static readonly letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
constructor(private common: CommonService) { }
public generateIdentityCard(): string {
let result = "";
// Generate 3 letters
for (let i = 0; i < 3; i++) {
result += IdentityCardService.letters.charAt(this.common.getRandomInt(0, 25));
}
// Generate 5 digits (for positions 4-8)
let digits = "";
for (let i = 0; i < 5; i++) {
digits += this.common.getRandomInt(0, 9).toString();
}
// Calculate sum
let sum = 0;
// Letters
for (let i = 0; i < 3; i++) {
sum += this.letterToValue(result.charAt(i)) * IdentityCardService.weights[i];
}
// Digits
for (let i = 0; i < 5; i++) {
sum += parseInt(digits.charAt(i), 10) * IdentityCardService.weights[i + 4];
}
const controlDigit = sum % 10;
return result + controlDigit + digits;
}
public validateIdentityCard(id: string): ValidationErrors | null {
const errors: ValidationErrors = {};
if (!id) {
errors.required = true;
return errors;
}
const upperId = id.toUpperCase();
if (upperId.length !== 9) {
if (upperId.length < 9) errors.tooShort = true;
else errors.tooLong = true;
}
const pattern = /^[A-Z]{3}[0-9]{6}$/;
if (!pattern.test(upperId)) {
errors.invalidPattern = true;
}
if (Object.keys(errors).length > 0) return errors;
// Control digit validation
let sum = 0;
for (let i = 0; i < 9; i++) {
if (i === 3) continue; // skip control digit
const char = upperId.charAt(i);
const val = i < 3 ? this.letterToValue(char) : parseInt(char, 10);
sum += val * IdentityCardService.weights[i];
}
const calculatedControl = sum % 10;
const actualControl = parseInt(upperId.charAt(3), 10);
if (calculatedControl !== actualControl) {
errors.invalidControlDigit = true;
}
return Object.keys(errors).length > 0 ? errors : null;
}
private letterToValue(letter: string): number {
return IdentityCardService.letters.indexOf(letter.toUpperCase()) + 10;
}
}