# 07 — معمارية التكامل

**الجمهور:** Backend Engineers, DevOps  
**المبدأ:** Microservices communicate via HTTP API — never shared database

---

## 1. HR في النظام العام

```
                    ┌─────────────┐
                    │   Gateway   │ :3400
                    │  /api/hr/*  │
                    └──────┬──────┘
                           │
              ┌────────────┼────────────┐
              │            │            │
        ┌─────▼─────┐ ┌───▼───┐ ┌─────▼─────┐
        │    HR     │ │ Auth  │ │ Accounting│
        │   :3407   │ │ :3401 │ │   :3403   │
        └─────┬─────┘ └───┬───┘ └─────┬─────┘
              │           │           │
         DB_HR_*     DB_AUTH_*    DB_ACC_*
```

---

## 2. الخدمات المرتبطة

| Service | Port | Integration | Priority |
|---------|------|-------------|----------|
| **auth** | 3401 | User ↔ Employee, permissions | P1 |
| **core** | 3402 | Company settings, module registry | P1 |
| **branches** | 3408 | branch_id validation, branch filter | P0 ✅ |
| **accounting** | 3403 | Payroll journal entries | P2 |
| **notifications** | 3422 | Alerts, approvals | P1 |
| **marketing** | — | employee_id validation | P1 |
| **dashboard** | panel | Real KPIs from /hr/stats | P0 |

---

## 3. Auth Service Integration

### 3.1 User ↔ Employee Link

**Auth DB change:**

```sql
ALTER TABLE users ADD COLUMN employee_id INT NULL;
```

**Flow:**

```
HR: POST /employees/:id/link-user { userId }
  → HR validates employee exists
  → HR calls Auth internal OR shared pool to auth DB
  → SET users.employee_id = :employeeId WHERE id = :userId
  → Ensure no duplicate link
```

**Pattern:** HR service already has `authPool = createPool('auth')` — use for read/write user.employee_id

### 3.2 Permissions Extension

File: `services/auth/src/lib/permissions.js`

```javascript
// Phase 1 additions
{ code: 'hr.leave.read', module: 'hr', action: 'read', ... },
{ code: 'hr.leave.write', module: 'hr', action: 'write', ... },
{ code: 'hr.leave.approve', module: 'hr', action: 'approve', ... },
{ code: 'hr.attendance.read', ... },
{ code: 'hr.attendance.write', ... },
// Phase 2
{ code: 'hr.payroll.read', ... },
{ code: 'hr.payroll.write', ... },
{ code: 'hr.payroll.approve', ... },
```

**Role mapping:**

| Role | Permissions |
|------|-------------|
| admin | all hr.* |
| hr_manager | all hr.* |
| branch_manager | hr.read, hr.leave.approve, hr.attendance.read |
| finance | hr.payroll.read, hr.payroll.approve |
| employee (self) | via /me/* endpoints with own employee_id |

### 3.3 Self-Service Auth

```javascript
// In leave application route
const employeeId = req.user.employeeId;
if (!employeeId && !hasPermission('hr.leave.write')) {
  return res.status(403).json({ message: 'No employee linked', messageAr: '...' });
}
// Self-service: can only create for own employeeId
if (payload.employeeId !== employeeId && !hasPermission('hr.leave.write')) {
  return res.status(403).json(...);
}
```

---

## 4. Core Service Integration

### 4.1 Company HR Settings

Store in core company record (JSON column or settings table):

```javascript
// services/hr/src/lib/settings.js
export async function getHrSettings(corePool, companyId) {
  const [rows] = await corePool.query(
    'SELECT settings FROM companies WHERE id = ?',
    [companyId]
  );
  return rows[0]?.settings?.hr ?? DEFAULT_HR_SETTINGS;
}
```

**Usage:** GOSI rates, WPS config, MOL ID, alert thresholds

### 4.2 Module Registry

Already registered in `services/core/src/db/migrate.js`:

```javascript
{ code: 'hr', name: 'الموارد البشرية', ... }
```

Extend with sub-modules when adding features:

```javascript
{ code: 'hr.leave', parent: 'hr', ... },
{ code: 'hr.payroll', parent: 'hr', ... },
```

---

## 5. Branches Service Integration

### 5.1 Current ✅

- `employees.branch_id` references branches service
- Branch filter via `getBranchFilterSql` from `@erp/shared`

### 5.2 Attendance by Branch

```javascript
// HR server.js — optional branches pool for validation
const branchesPool = createPool('branches');

// In attendance route
const branch = await validateBranch(branchesPool, companyId, branchId);
```

### 5.3 Branch Manager Scope

Branch managers see only their branch employees/attendance/leave via existing RBAC branch filter.

---

## 6. Accounting Service Integration (Phase 2)

### 6.1 Pattern (from POS)

POS creates accounting pools in `server.js`:

```javascript
const accountingPool = createPool('accounting');
```

HR follows same pattern for payroll posting.

### 6.2 Payroll → Journal Entry Flow

```
Payroll Run approved
  → For each salary slip:
      Compute component amounts
  → Aggregate by account (salary expense, GOSI payable, net payable)
  → POST to accounting service:
      POST /api/accounting/journal-entries
      {
        companyId, date, reference: "PAY-2026-06",
        lines: [
          { accountId: 5010, debit: 150000, credit: 0, description: "Salaries" },
          { accountId: 2150, debit: 0, credit: 13500, description: "GOSI Payable" },
          { accountId: 2100, debit: 0, credit: 136500, description: "Salaries Payable" }
        ]
      }
  → Store journal_entry_id on payroll_run
```

### 6.3 Account Mapping

Salary components link to accounting accounts:

```sql
salary_components.account_id → accounting.accounts.id
```

Setup UI: dropdown of accounts from accounting `/accounts?type=expense|liability`

### 6.4 Payment Flow

After WPS bank transfer:

```
POST /payroll-runs/:id/mark-paid
  → accounting: POST /payments { type: 'salary', amount, bankAccount }
  → Update salary_slips.status = 'paid'
```

---

## 7. Notifications Service Integration

### 7.1 Pattern

From `@erp/shared`:

```javascript
import { publishNotification } from '@erp/shared';

await publishNotification(notificationsPool, {
  companyId,
  userId: approverId,
  type: 'hr.leave.pending',
  title: 'Leave Request',
  titleAr: 'طلب إجازة',
  body: `${employeeName} requested ${days} days leave`,
  link: `/admin/hr/leave-applications/${id}`,
});
```

### 7.2 Event Catalog

| Event | Recipients | Trigger |
|-------|------------|---------|
| hr.leave.pending | Approver | Leave application created |
| hr.leave.approved | Employee | Leave approved |
| hr.leave.rejected | Employee | Leave rejected |
| hr.document.expiring | HR manager | Cron daily check |
| hr.iqama.expiring | HR manager | Cron daily check |
| hr.contract.expiring | HR manager | Cron daily check |
| hr.payroll.ready | Finance | Payroll processed |
| hr.payroll.approved | HR manager | Payroll approved |
| hr.attendance.absent | Branch manager | Daily absent report |

### 7.3 Cron Jobs

Add to PM2 or separate scheduler:

```javascript
// services/hr/src/jobs/expiryAlerts.js
// Run daily at 08:00 AST
// Query employees with expiring documents
// Publish notifications
```

---

## 8. Marketing Service Integration

### 8.1 Current Issue

Marketing uses `linked_employee_id` / `employee_id` as bare integers without validation.

### 8.2 Fix (Phase 1)

Option A — Marketing calls HR API:

```javascript
// services/marketing — before saving coupon
const res = await fetch(`${GATEWAY}/api/hr/employees/${employeeId}`, {
  headers: { Authorization, 'X-Company-Id': companyId }
});
if (!res.ok) throw new Error('Invalid employee');
```

Option B — HR exposes bulk validate:

```
POST /employees/validate
Body: { ids: [1, 2, 3] }
Response: { valid: [1, 2], invalid: [3] }
```

---

## 9. Dashboard Integration

### 9.1 Current

`panel/src/config/dashboard.js` — mock employee count

### 9.2 Fix

```javascript
// In dashboard data loader
const hrStats = await api.hrStats();
kpis.employees = hrStats.activeEmployees;
kpis.onLeave = hrStats.onLeave;
kpis.expiringIqama = hrStats.expiringIqama;
```

---

## 10. Biometric Device Integration

### 10.1 Architecture

```
Biometric Device (ZKTeco/etc)
    ↓ CSV / SDK / Middleware
Integration Script (cron or daemon)
    ↓ HTTP POST
Gateway /api/hr/checkins/import
    ↓
HR Service → employee_checkins
    ↓ (cron)
POST /attendance/process-auto
    ↓
attendances table
```

### 10.2 Device API Key (optional)

For direct device → API without user JWT:

```javascript
// New auth method: X-Device-Key header
// Stored in company settings
// Maps attendance_device_id → employee
```

### 10.3 ERPNext Reference

ERPNext endpoint: `add_log_based_on_employee_field` — we mirror:

```
POST /checkins
{
  "attendanceDeviceId": "DEV001",
  "logType": "IN",
  "checkinTime": "2026-06-01T08:00:00+03:00"
}
```

---

## 11. File Storage

### 11.1 Current Structure

```
storage/hr/{companyId}/{employeeId}/{filename}
```

### 11.2 Extensions

```
storage/hr/{companyId}/wps/{runNumber}.sif
storage/hr/{companyId}/payslips/{slipNumber}.pdf
storage/hr/{companyId}/leave/{applicationId}/attachment.pdf
storage/hr/{companyId}/resumes/{applicantId}.pdf
```

Reuse `services/hr/src/lib/storage.js` pattern.

---

## 12. Multi-Pool Setup (server.js template)

```javascript
// services/hr/src/server.js — Phase 2
const pool = createPool('hr');
const authPool = createPool('auth');
const corePool = createPool('core');
const accountingPool = createPool('accounting');
const notificationsPool = createPool('notifications');

app.use('/leave-applications', createLeaveRouter(pool, authPool, notificationsPool));
app.use('/payroll-runs', createPayrollRouter(pool, authPool, accountingPool, corePool));
```

---

## 13. Error Handling Cross-Service

```javascript
async function postPayrollJournal(accountingPool, entry) {
  try {
    const result = await accountingClient.createJournalEntry(entry);
    return result.id;
  } catch (err) {
    // Rollback payroll status
    await pool.query('UPDATE payroll_runs SET status = ? WHERE id = ?', ['review', runId]);
    throw new ServiceError('Accounting posting failed', 'فشل ترحيل المحاسبة', 502);
  }
}
```

**Rule:** HR owns payroll data; accounting owns journals. Never write to accounting DB directly.

---

## 14. Deployment Notes

### PM2

Already in `ecosystem.config.cjs`:

```javascript
{ name: 'hr-service', script: 'services/hr/src/server.js', ... }
```

### Migrations

```bash
cd services/hr && node src/db/migrate.js
```

Run on deploy after schema changes.

### Environment

```env
DB_HR_HOST=localhost
DB_HR_NAME=erp_hr
DB_HR_USER=...
DB_HR_PASSWORD=...
```

---

## 15. Testing Strategy

| Layer | Approach |
|-------|----------|
| Unit | lib/validators, lib/payroll.js formulas |
| Integration | supertest on routes with test DB |
| Cross-service | mock pools for accounting/notifications |
| E2E | leave apply → approve → attendance on_leave |

---

## 16. Sequence Diagram — Leave Approval

```
Employee          HR Service        Auth          Notifications
   │                  │               │                │
   │ POST /leave-applications         │                │
   │─────────────────>│               │                │
   │                  │ get approver  │                │
   │                  │──────────────>│                │
   │                  │<──────────────│                │
   │                  │ validate balance               │
   │                  │ insert application             │
   │                  │ publish hr.leave.pending       │
   │                  │──────────────────────────────>│
   │<─────────────────│               │                │
   │ 201 Created      │               │                │
   │                  │               │                │
Manager              │               │                │
   │ POST /leave-applications/:id/approve             │
   │─────────────────>│               │                │
   │                  │ update status │                │
   │                  │ insert ledger │                │
   │                  │ update attendance (future dates)│
   │                  │ publish hr.leave.approved      │
   │                  │──────────────────────────────>│
   │<─────────────────│               │                │
```

---

## 17. Next Steps

1. Implement User ↔ Employee link (Auth + HR)
2. Add notifications for document expiry (existing stats data)
3. Wire dashboard to `/hr/stats`
4. Begin Leave module per [03-HR_DEVELOPMENT_ROADMAP.md](./03-HR_DEVELOPMENT_ROADMAP.md)
