sistem-absensi1: The Attendance App That Tries to Understand Any Fingerprint Export

A Laravel 11 and React system built for messy biometric data, local shift rules, and the real-world chaos of small-business payroll.

8 min read • View on GitHub • More from Z-dev-collab

A fingerprint machine pushes out a messy stack of mismatched attendance sheets while a clean ledger and laptop receive the data on the other side. The scene explains the article’s core idea: the system stands between chaotic hardware exports and usable payroll records.
The app acts less like a form and more like a translator for hostile input.
Key Takeaways

The app does not trust your spreadsheet

Most attendance tools assume the export is polite. This one assumes the opposite. It looks at the first rows of a workbook, searches for layout markers, and decides what kind of file it is before it commits anything to the database.

That choice changes the whole system. Instead of forcing every fingerprint machine into one template, sistem-absensi1 tries to recognize whatever the hardware produced and then normalize it into payroll-friendly records.

Three different attendance export fragments are sorted into separate trays by a magnifying lens and a set of hands. The visual explains how the import engine recognizes different workbook layouts before normalizing them into one internal format.
The important step is not reading rows. It is identifying the layout first.

Why attendance data is harder than it looks

Attendance is rarely just check in and check out. It is a mix of hardware exports, inconsistent vendor formats, shift rules that cross midnight, and local business habits that do not fit neat SaaS assumptions.

ApproachAssumptionFailure modeWhat sistem-absensi1 does instead
Strict template importEvery export looks the sameOne altered column breaks the whole flowScans the file for clues before choosing a parser
Vendor-specific importerEach machine deserves its own code pathMaintenance grows with every new modelUses heuristics to classify export shape first
Manual cleanup in ExcelHumans can normalize everythingErrors slip in and auditability gets worseConverts messy input into structured records automatically
Heuristic detection and normalizationThe file is hostile until proven otherwiseRequires a smarter import layerInspects early rows, routes by pattern, and writes transactionally

That is why this repository is interesting. It does not just store attendance. It absorbs dirty operational data from the edges of a small business and tries to make it reliable enough for reporting.

The import engine is the real product

The core service is AttendanceExcelImportService.php. It inspects the first 20 rows of a workbook, looks for markers such as machine headers and attendance record patterns, and decides whether it is dealing with a fingerprint log, a horizontal attendance sheet, or a vendor-specific export.

// Simplified shape of the import flow
$workbook = $reader->load($filePath);
foreach ($workbook->getAllSheets() as $sheet) {
    $sampleRows = $sheet->rangeToArray('A1:Z20');

    if ($this->looksLikeFingerprintRecord($sampleRows)) {
        $records = $this->parseFingerprintRecord($sheet);
    } elseif ($this->looksLikeHorizontalAttendance($sampleRows)) {
        $records = $this->parseHorizontalAttendance($sheet);
    } else {
        $records = $this->parseVendorSpecificExport($sheet);
    }

    DB::transaction(function () use ($records) {
        $this->writeEmployees($records);
        $this->writeAttendanceHistory($records);
    });
}

The hidden intelligence is the classification step. The app samples first, then decides what kind of file it is.

The important detail is not just parsing. It is the write path. The service updates employees and attendance history inside a single transaction, so a half-imported workbook does not leave the database in a broken state.

That is defensive software in the best sense. It assumes the input is inconsistent and protects the system from becoming inconsistent too.

Current state on one side, history on the other

The model splits the problem into two layers. Employee holds the live state, such as current status and today’s snapshot. EmployeeAttendance holds durable history, with a unique constraint on employee_id and date.

LayerJobWhy it matters
EmployeeCurrent state and today’s snapshotKeeps the active view fast and simple
EmployeeAttendanceHistorical attendance logPreserves auditability and avoids overwriting past records
Unique constraintOne record per employee per datePrevents duplicate history during repeated imports

That split is clean because it matches how people actually use the system. Managers want the current picture, but payroll and audits need a stable record of what happened.

The frontend is not just a viewer

The React and TypeScript UI is doing real work. It parses dates in multiple formats, including Indonesian month names, and it understands shift windows that are not neatly nine to five.

type ParsedDate = {
  year: number;
  month: number;
  day: number;
};

function parseDateParts(input: string): ParsedDate | null {
  // Handles ISO, slash-separated, and localized month names.
  // The goal is to accept messy operational data without making the user clean it first.
  return null;
}

const workShifts = [
  '10:00 - 22:00',
  '22:00 - 10:00',
  '08:00 - 17:00'
];

That makes the frontend a validation layer, not a decorative shell. It helps users catch problems before they turn into database writes, which is exactly what a file import screen should do.

The result is a product that reflects its market. Local date formats, practical shift patterns, and simple operational workflows matter more here than abstract elegance.

The repo looks AI-assisted on purpose

The .agents and .claude directories are not cosmetic. They suggest a codebase shaped for machine-readable rules, maintenance prompts, and assistant-friendly workflows.

That is a meaningful signal. Even in an early-stage repository, the developer is trying to encode conventions so both humans and coding assistants can stay aligned.

Why this stack fits its market

Laravel 11, React, Vite, TypeScript, SQLite or MySQL, Docker, and Render are all sensible choices for a compact operational app. The stack is modern, but not overbuilt.

ChoiceWhy it fits
Laravel 11Fast to build, easy to structure services and transactions
React + TypeScriptGood fit for a data-heavy import and review UI
PhpSpreadsheet and XLSXUseful on both server and client for Excel handling
Docker and RenderKeeps deployment straightforward for a small team

This is the kind of stack you choose when the product’s value is in the workflow, not in platform novelty. It needs to be understandable, maintainable, and easy to deploy for a narrow but real market.