240 lines
8.7 KiB
Markdown
240 lines
8.7 KiB
Markdown
|
|
# Интеграция зарплатных данных из 1С - Инструкция по внедрению
|
|||
|
|
|
|||
|
|
## Шаг 1: Применить миграцию БД
|
|||
|
|
|
|||
|
|
Выполните SQL миграцию для создания таблицы истории зарплат:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
psql -d mkd_control_center -f backend/migrate_salary_history.sql
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Или вручную через pgAdmin/psql выполните содержимое файла `backend/migrate_salary_history.sql`
|
|||
|
|
|
|||
|
|
## Шаг 2: Добавить SalaryProcessor в server.js
|
|||
|
|
|
|||
|
|
В файле `backend/server.js` добавьте:
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// В начале файла, после других require
|
|||
|
|
const SalaryProcessor = require('./salaryProcessor');
|
|||
|
|
const salaryProcessor = new SalaryProcessor(pool);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Шаг 3: Добавить API endpoint для загрузки зарплатных отчетов
|
|||
|
|
|
|||
|
|
Добавьте в `backend/server.js` после существующих финансовых endpoints:
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// POST /api/salary/upload-report - загрузка зарплатного отчета из 1С
|
|||
|
|
app.post(`${API_PREFIX}/salary/upload-report`, upload.single('file'), async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
if (!req.file) {
|
|||
|
|
return res.status(400).json({ error: 'Файл не загружен' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const fileType = path.extname(req.file.originalname).toLowerCase() === '.csv' ? 'CSV' : 'XLSX';
|
|||
|
|
const uploadedBy = req.body.uploadedBy || 'System';
|
|||
|
|
|
|||
|
|
// Создаем запись об отчете
|
|||
|
|
const reportResult = await query(
|
|||
|
|
`INSERT INTO financial_reports (filename, file_type, uploaded_by, status)
|
|||
|
|
VALUES ($1, $2, $3, 'processing')
|
|||
|
|
RETURNING id`,
|
|||
|
|
[req.file.originalname, fileType, uploadedBy]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
const reportId = reportResult[0].id;
|
|||
|
|
|
|||
|
|
// Парсим файл
|
|||
|
|
let rows = [];
|
|||
|
|
if (fileType === 'CSV') {
|
|||
|
|
rows = await fileProcessor.parseCSV(req.file.path);
|
|||
|
|
} else {
|
|||
|
|
rows = await fileProcessor.parseXLSX(req.file.path);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Получаем маппинг полей (можно передать в body или использовать по умолчанию)
|
|||
|
|
const mapping = req.body.mapping ? JSON.parse(req.body.mapping) : {
|
|||
|
|
columnMappings: {
|
|||
|
|
// Пример маппинга - настройте под ваш формат 1С
|
|||
|
|
'ФИО': 'employeeIdentifier',
|
|||
|
|
'ИНН': 'inn',
|
|||
|
|
'Период': 'period',
|
|||
|
|
'Оклад': 'baseSalary',
|
|||
|
|
'Начислено': 'actualSalary',
|
|||
|
|
'Премия': 'bonuses',
|
|||
|
|
'Удержано': 'deductions',
|
|||
|
|
'К выплате': 'netSalary',
|
|||
|
|
'Отработано дней': 'workedDays'
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// Обрабатываем зарплатные данные
|
|||
|
|
const result = await salaryProcessor.processSalaryData(rows, mapping, reportId);
|
|||
|
|
|
|||
|
|
// Обновляем статус отчета
|
|||
|
|
await query(
|
|||
|
|
`UPDATE financial_reports
|
|||
|
|
SET status = $1, processed_rows = $2, error_rows = $3, error_log = $4
|
|||
|
|
WHERE id = $5`,
|
|||
|
|
[
|
|||
|
|
result.errorRows > 0 && result.processedRows > 0 ? 'partial' :
|
|||
|
|
result.errorRows > 0 ? 'failed' : 'completed',
|
|||
|
|
result.processedRows,
|
|||
|
|
result.errorRows,
|
|||
|
|
JSON.stringify(result.errors),
|
|||
|
|
reportId
|
|||
|
|
]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// Удаляем файл после обработки
|
|||
|
|
fs.unlinkSync(req.file.path);
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
reportId,
|
|||
|
|
...result
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Error processing salary report:', error);
|
|||
|
|
res.status(500).json({
|
|||
|
|
error: 'Ошибка обработки зарплатного отчета',
|
|||
|
|
details: error.message
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// GET /api/salary/history/:employeeId - получить историю зарплат сотрудника
|
|||
|
|
app.get(`${API_PREFIX}/salary/history/:employeeId`, async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const { employeeId } = req.params;
|
|||
|
|
const { year, month } = req.query;
|
|||
|
|
|
|||
|
|
let queryText = `
|
|||
|
|
SELECT
|
|||
|
|
id,
|
|||
|
|
period_month AS "periodMonth",
|
|||
|
|
period_year AS "periodYear",
|
|||
|
|
base_salary AS "baseSalary",
|
|||
|
|
actual_salary AS "actualSalary",
|
|||
|
|
bonuses,
|
|||
|
|
deductions,
|
|||
|
|
net_salary AS "netSalary",
|
|||
|
|
worked_days AS "workedDays",
|
|||
|
|
worked_hours AS "workedHours",
|
|||
|
|
vacation_days AS "vacationDays",
|
|||
|
|
sick_leave_days AS "sickLeaveDays",
|
|||
|
|
metadata,
|
|||
|
|
imported_at AS "importedAt"
|
|||
|
|
FROM employee_salary_history
|
|||
|
|
WHERE employee_id = $1
|
|||
|
|
`;
|
|||
|
|
const params = [employeeId];
|
|||
|
|
|
|||
|
|
if (year) {
|
|||
|
|
queryText += ` AND period_year = $${params.length + 1}`;
|
|||
|
|
params.push(year);
|
|||
|
|
}
|
|||
|
|
if (month) {
|
|||
|
|
queryText += ` AND period_month = $${params.length + 1}`;
|
|||
|
|
params.push(month);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
queryText += ' ORDER BY period_year DESC, period_month DESC';
|
|||
|
|
|
|||
|
|
const result = await query(queryText, params);
|
|||
|
|
res.json(result);
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Error fetching salary history:', error);
|
|||
|
|
res.status(500).json({ error: 'Ошибка получения истории зарплат' });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Шаг 4: Формат файла из 1С
|
|||
|
|
|
|||
|
|
### Пример структуры Excel/CSV файла из 1С:
|
|||
|
|
|
|||
|
|
| ФИО | ИНН | Период | Оклад | Начислено | Премия | Удержано | К выплате | Отработано дней |
|
|||
|
|
|-----|-----|--------|-------|-----------|--------|----------|-----------|-----------------|
|
|||
|
|
| Иванов Иван Иванович | 123456789012 | 01.2024 | 50000 | 55000 | 5000 | 7150 | 47850 | 22 |
|
|||
|
|
| Петров Петр Петрович | 987654321098 | 01.2024 | 60000 | 65000 | 5000 | 8450 | 56550 | 22 |
|
|||
|
|
|
|||
|
|
### Важные моменты:
|
|||
|
|
|
|||
|
|
1. **Идентификация сотрудника:**
|
|||
|
|
- Приоритет: ИНН > СНИЛС > ФИО
|
|||
|
|
- ФИО должно совпадать с данными в системе (можно частичное совпадение)
|
|||
|
|
|
|||
|
|
2. **Формат периода:**
|
|||
|
|
- Поддерживается: "01.2024", "01/2024", "январь 2024", "2024-01"
|
|||
|
|
- Рекомендуется: "MM.YYYY" (например, "01.2024")
|
|||
|
|
|
|||
|
|
3. **Числовые поля:**
|
|||
|
|
- Могут быть с пробелами: "50 000"
|
|||
|
|
- Могут быть с запятой: "50,000"
|
|||
|
|
- Автоматически конвертируются
|
|||
|
|
|
|||
|
|
## Шаг 5: Настройка маппинга полей
|
|||
|
|
|
|||
|
|
Если названия колонок в вашем файле 1С отличаются, настройте маппинг:
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
const mapping = {
|
|||
|
|
columnMappings: {
|
|||
|
|
'ФИО сотрудника': 'employeeIdentifier',
|
|||
|
|
'ИНН сотрудника': 'inn',
|
|||
|
|
'Месяц расчета': 'period',
|
|||
|
|
'Оклад по штатному расписанию': 'baseSalary',
|
|||
|
|
'Начислено всего': 'actualSalary',
|
|||
|
|
// ... и т.д.
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Шаг 6: Тестирование
|
|||
|
|
|
|||
|
|
1. Экспортируйте отчет по зарплате из 1С в Excel/CSV
|
|||
|
|
2. Загрузите файл через API:
|
|||
|
|
```bash
|
|||
|
|
curl -X POST http://localhost:4000/api/salary/upload-report \
|
|||
|
|
-F "file=@salary_report.xlsx" \
|
|||
|
|
-F "uploadedBy=Admin"
|
|||
|
|
```
|
|||
|
|
3. Проверьте результат:
|
|||
|
|
- Статус обработки
|
|||
|
|
- Количество обработанных строк
|
|||
|
|
- Ошибки (если есть)
|
|||
|
|
|
|||
|
|
## Шаг 7: Создание UI компонента (опционально)
|
|||
|
|
|
|||
|
|
Создайте React компонент для загрузки зарплатных отчетов, аналогичный `ReportUploader.tsx`:
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// components/hr/SalaryReportUploader.tsx
|
|||
|
|
// Аналогично существующему ReportUploader, но для зарплатных данных
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Автоматизация (будущее)
|
|||
|
|
|
|||
|
|
После настройки HTTP-сервиса в 1С можно добавить автоматическую синхронизацию:
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// В cron или по расписанию
|
|||
|
|
app.post(`${API_PREFIX}/salary/sync-from-1c`, async (req, res) => {
|
|||
|
|
// Запрос к HTTP-сервису 1С
|
|||
|
|
// Обработка ответа
|
|||
|
|
// Сохранение данных
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Поддержка
|
|||
|
|
|
|||
|
|
При возникновении проблем:
|
|||
|
|
1. Проверьте формат файла из 1С
|
|||
|
|
2. Проверьте маппинг полей
|
|||
|
|
3. Проверьте логи ошибок в консоли
|
|||
|
|
4. Убедитесь, что сотрудники существуют в системе и имеют ИНН/СНИЛС
|