600 lines
19 KiB
JavaScript
600 lines
19 KiB
JavaScript
const { createApp } = Vue;
|
||
|
||
const api = {
|
||
auds: "/auditories/",
|
||
oboruds: (audId) => `/oboruds/?aud_id=${encodeURIComponent(audId)}`,
|
||
allOboruds: "/oboruds/?sort_by_inv=true",
|
||
owners: "/owners/",
|
||
zametki: "/zametki/",
|
||
};
|
||
|
||
async function fetchJSON(url) {
|
||
const res = await fetch(url);
|
||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||
return res.json();
|
||
}
|
||
|
||
createApp({
|
||
data() {
|
||
return {
|
||
view: 'byAud',
|
||
auditories: [],
|
||
selectedAudId: '',
|
||
oboruds: [],
|
||
allOboruds: [],
|
||
unassignedOboruds: [],
|
||
totalOboruds: 0,
|
||
status: '',
|
||
error: '',
|
||
printTitle: '',
|
||
// auth/user management
|
||
token: '',
|
||
role: '',
|
||
users: [],
|
||
newAdminUsername: '',
|
||
newAdminPassword: '',
|
||
newAudName: '',
|
||
owners: [],
|
||
newOwnerName: '',
|
||
zametki: [],
|
||
newZametkaText: '',
|
||
equipmentTypes: [],
|
||
newTypeName: '',
|
||
newEquipment: {
|
||
invNumber: null,
|
||
nazvanie: '',
|
||
raspologenie: '',
|
||
kolichestvo: 1,
|
||
aud_id: '',
|
||
type_id: '',
|
||
owner_id: ''
|
||
},
|
||
// Inspection
|
||
activeInspection: null,
|
||
inspectionAudId: '',
|
||
scannedBarcode: '',
|
||
lastScanResult: null,
|
||
inspectionStats: {
|
||
total_checked: 0,
|
||
total_expected: 0,
|
||
total_unknown: 0,
|
||
progress_percent: 0
|
||
},
|
||
checkedEquipment: [],
|
||
unknownBarcodes: [],
|
||
inspectionHistory: []
|
||
};
|
||
},
|
||
computed: {
|
||
isAuth() { return !!this.token; },
|
||
isAdmin() { return this.role === 'admin'; },
|
||
isEditor() { return this.role === 'editor'; },
|
||
canEdit() { return this.isAdmin || this.isEditor; },
|
||
},
|
||
methods: {
|
||
logout() {
|
||
try {
|
||
localStorage.removeItem('access_token');
|
||
localStorage.removeItem('role');
|
||
} catch {}
|
||
this.token = '';
|
||
this.role = '';
|
||
this.users = [];
|
||
this.status = '';
|
||
this.error = '';
|
||
this.view = 'byAud';
|
||
// опционально: редирект на страницу логина
|
||
// window.location.href = '/login';
|
||
},
|
||
authHeaders() {
|
||
const h = {};
|
||
if (this.token) h['Authorization'] = `Bearer ${this.token}`;
|
||
return h;
|
||
},
|
||
async fetchAuth(url, options = {}) {
|
||
const opt = { ...options, headers: { ...(options.headers||{}), ...this.authHeaders() } };
|
||
const res = await fetch(url, opt);
|
||
if (!res.ok) {
|
||
const text = await res.text();
|
||
throw new Error(`HTTP ${res.status}: ${text}`);
|
||
}
|
||
return res.json();
|
||
},
|
||
async loadAuditories() {
|
||
this.status = 'Загрузка аудиторий…';
|
||
this.error = '';
|
||
try {
|
||
this.auditories = await fetchJSON(api.auds);
|
||
this.status = '';
|
||
} catch (e) {
|
||
console.error(e);
|
||
this.error = 'Не удалось загрузить аудитории';
|
||
this.status = '';
|
||
}
|
||
},
|
||
async loadOboruds() {
|
||
if (!this.selectedAudId) {
|
||
this.error = '';
|
||
this.status = 'Выберите аудиторию';
|
||
return;
|
||
}
|
||
this.status = 'Загрузка оборудования…';
|
||
this.error = '';
|
||
try {
|
||
this.oboruds = await fetchJSON(api.oboruds(this.selectedAudId));
|
||
// init selected owner helper field
|
||
this.oboruds.forEach(o => { o.selectedOwnerId = o.owner?.id || ''; });
|
||
this.status = '';
|
||
} catch (e) {
|
||
console.error(e);
|
||
this.error = 'Не удалось загрузить оборудование';
|
||
this.status = '';
|
||
}
|
||
},
|
||
async loadAllOboruds() {
|
||
this.status = 'Загрузка всего оборудования…';
|
||
this.error = '';
|
||
try {
|
||
this.allOboruds = await fetchJSON(api.allOboruds);
|
||
this.status = `Загружено ${this.allOboruds.length} записей`;
|
||
} catch (e) {
|
||
console.error(e);
|
||
this.error = 'Не удалось загрузить оборудование';
|
||
this.status = '';
|
||
}
|
||
},
|
||
async showAllEquipment() {
|
||
this.view = 'allEquipment';
|
||
await this.loadAllOboruds();
|
||
},
|
||
async loadUnassigned() {
|
||
this.status = 'Загрузка нераспределённого оборудования…';
|
||
this.error = '';
|
||
try {
|
||
const [oboruds, stats] = await Promise.all([
|
||
fetchJSON('/oboruds/?unassigned=true&sort_by_inv=true'),
|
||
fetchJSON('/oboruds/stats')
|
||
]);
|
||
this.unassignedOboruds = oboruds;
|
||
this.totalOboruds = stats.total;
|
||
this.status = '';
|
||
} catch (e) {
|
||
console.error(e);
|
||
this.error = 'Не удалось загрузить данные';
|
||
this.status = '';
|
||
}
|
||
},
|
||
async showUnassigned() {
|
||
this.view = 'unassigned';
|
||
await this.loadUnassigned();
|
||
},
|
||
getAuditoryName(audId) {
|
||
if (!audId) return '';
|
||
const aud = this.auditories.find(a => a.id === audId);
|
||
return aud ? aud.audnazvanie : '';
|
||
},
|
||
printPage() {
|
||
const aud = this.auditories.find(a => a.id === this.selectedAudId);
|
||
this.printTitle = 'Аудитория № ' + (aud ? aud.audnazvanie : '');
|
||
this.$nextTick(() => {
|
||
window.print();
|
||
});
|
||
},
|
||
printAllEquipment() {
|
||
window.print();
|
||
},
|
||
async loadZametki() {
|
||
this.status = 'Загрузка заметок…';
|
||
this.error = '';
|
||
try {
|
||
this.zametki = await fetchJSON(api.zametki);
|
||
this.status = '';
|
||
} catch (e) {
|
||
console.error(e);
|
||
this.error = 'Не удалось загрузить заметки';
|
||
this.status = '';
|
||
}
|
||
},
|
||
async showZametki() {
|
||
this.view = 'zametki';
|
||
await this.loadZametki();
|
||
},
|
||
async createZametka() {
|
||
if (!this.newZametkaText.trim()) {
|
||
this.status = 'Введите текст заметки';
|
||
return;
|
||
}
|
||
try {
|
||
this.status = 'Добавление заметки…';
|
||
await this.fetchAuth(api.zametki, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ txtzam: this.newZametkaText }),
|
||
});
|
||
this.newZametkaText = '';
|
||
await this.loadZametki();
|
||
this.status = 'Заметка добавлена';
|
||
} catch (e) {
|
||
console.error(e);
|
||
this.error = 'Не удалось добавить заметку';
|
||
this.status = '';
|
||
}
|
||
},
|
||
async resolveZametka(id) {
|
||
try {
|
||
this.status = 'Отметка заметки как решённой…';
|
||
await this.fetchAuth(`/zametki/${id}/resolve`, { method: 'PATCH' });
|
||
await this.loadZametki();
|
||
this.status = 'Заметка отмечена как решённая';
|
||
} catch (e) {
|
||
console.error(e);
|
||
this.error = 'Не удалось отметить заметку';
|
||
this.status = '';
|
||
}
|
||
},
|
||
formatDate(dateStr) {
|
||
if (!dateStr) return '';
|
||
const d = new Date(dateStr);
|
||
return d.toLocaleString('ru-RU');
|
||
},
|
||
async loadEquipmentTypes() {
|
||
try {
|
||
this.equipmentTypes = await fetchJSON('/equipment-types/');
|
||
} catch (e) {
|
||
console.error(e);
|
||
}
|
||
},
|
||
async createEquipmentType() {
|
||
if (!this.newTypeName.trim()) {
|
||
this.status = 'Введите название типа';
|
||
return;
|
||
}
|
||
try {
|
||
this.status = 'Добавление типа…';
|
||
await this.fetchAuth('/equipment-types/', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ name: this.newTypeName }),
|
||
});
|
||
this.newTypeName = '';
|
||
await this.loadEquipmentTypes();
|
||
this.status = 'Тип добавлен';
|
||
} catch (e) {
|
||
console.error(e);
|
||
this.error = 'Не удалось добавить тип';
|
||
this.status = '';
|
||
}
|
||
},
|
||
async createEquipment() {
|
||
if (!this.newEquipment.nazvanie.trim()) {
|
||
this.status = 'Введите название оборудования';
|
||
return;
|
||
}
|
||
try {
|
||
this.status = 'Добавление оборудования…';
|
||
const data = {
|
||
nazvanie: this.newEquipment.nazvanie,
|
||
invNumber: this.newEquipment.invNumber || null,
|
||
raspologenie: this.newEquipment.raspologenie || null,
|
||
kolichestvo: this.newEquipment.kolichestvo || null,
|
||
aud_id: this.newEquipment.aud_id || null,
|
||
type_id: this.newEquipment.type_id || null,
|
||
owner_id: this.newEquipment.owner_id || null,
|
||
};
|
||
await this.fetchAuth('/oboruds/', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(data),
|
||
});
|
||
this.newEquipment = { invNumber: null, nazvanie: '', raspologenie: '', kolichestvo: 1, aud_id: '', type_id: '', owner_id: '' };
|
||
this.status = 'Оборудование добавлено';
|
||
} catch (e) {
|
||
console.error(e);
|
||
this.error = 'Не удалось добавить оборудование';
|
||
this.status = '';
|
||
}
|
||
},
|
||
async saveOwner(item) {
|
||
try {
|
||
this.status = 'Сохранение владельца…';
|
||
await this.fetchAuth(`/oboruds/${item.id}`, {
|
||
method: 'PATCH',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ owner_id: item.selectedOwnerId || null }),
|
||
});
|
||
this.status = 'Сохранено';
|
||
} catch (e) {
|
||
console.error(e);
|
||
this.error = 'Не удалось сохранить владельца';
|
||
this.status = '';
|
||
}
|
||
},
|
||
async loadOwners() {
|
||
try {
|
||
this.status = this.status || 'Загрузка владельцев…';
|
||
const data = await fetchJSON(api.owners);
|
||
this.owners = data;
|
||
this.status = '';
|
||
} catch (e) {
|
||
console.error(e);
|
||
this.error = 'Не удалось загрузить владельцев';
|
||
this.status = '';
|
||
}
|
||
},
|
||
async createOwner() {
|
||
if (!this.newOwnerName) {
|
||
this.status = 'Укажите имя владельца';
|
||
return;
|
||
}
|
||
try {
|
||
this.status = 'Добавление владельца…';
|
||
await this.fetchAuth(api.owners, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ name: this.newOwnerName }),
|
||
});
|
||
this.newOwnerName = '';
|
||
await this.loadOwners();
|
||
// refresh table owner names if visible
|
||
if (this.selectedAudId) await this.loadOboruds();
|
||
this.status = 'Владелец добавлен';
|
||
} catch (e) {
|
||
console.error(e);
|
||
this.error = 'Не удалось добавить владельца';
|
||
this.status = '';
|
||
}
|
||
},
|
||
async loadUsers() {
|
||
try {
|
||
this.status = 'Загрузка пользователей…';
|
||
this.error = '';
|
||
this.users = await this.fetchAuth('/auth/users');
|
||
this.status = '';
|
||
} catch (e) {
|
||
console.error(e);
|
||
this.error = 'Не удалось загрузить пользователей';
|
||
this.status = '';
|
||
}
|
||
},
|
||
async createAdmin() {
|
||
if (!this.newAdminUsername || !this.newAdminPassword) {
|
||
this.status = 'Укажите логин и пароль';
|
||
return;
|
||
}
|
||
try {
|
||
this.status = 'Создание администратора…';
|
||
this.error = '';
|
||
await this.fetchAuth('/auth/users/admin', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ username: this.newAdminUsername, password: this.newAdminPassword }),
|
||
});
|
||
this.newAdminUsername = '';
|
||
this.newAdminPassword = '';
|
||
await this.loadUsers();
|
||
this.status = 'Администратор создан';
|
||
} catch (e) {
|
||
console.error(e);
|
||
this.error = 'Не удалось создать администратора';
|
||
this.status = '';
|
||
}
|
||
},
|
||
async createAuditory() {
|
||
if (!this.newAudName) {
|
||
this.status = 'Укажите название аудитории';
|
||
return;
|
||
}
|
||
try {
|
||
this.status = 'Добавление аудитории…';
|
||
this.error = '';
|
||
await this.fetchAuth('/auditories/', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ audnazvanie: this.newAudName }),
|
||
});
|
||
this.newAudName = '';
|
||
await this.loadAuditories();
|
||
this.status = 'Аудитория добавлена';
|
||
} catch (e) {
|
||
console.error(e);
|
||
this.error = 'Не удалось добавить аудиторию';
|
||
this.status = '';
|
||
}
|
||
},
|
||
|
||
// Inspection methods
|
||
async showInspection() {
|
||
this.view = 'inspection';
|
||
this.status = '';
|
||
this.error = '';
|
||
},
|
||
|
||
async startInspection() {
|
||
try {
|
||
this.status = 'Начало проверки…';
|
||
this.error = '';
|
||
const response = await this.fetchAuth('/inspections/sessions', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
aud_id: this.inspectionAudId || null
|
||
})
|
||
});
|
||
|
||
this.activeInspection = response;
|
||
await this.loadInspectionStats();
|
||
this.status = 'Проверка начата';
|
||
|
||
// Фокус на поле ввода
|
||
this.$nextTick(() => {
|
||
if (this.$refs.barcodeInput) {
|
||
this.$refs.barcodeInput.focus();
|
||
}
|
||
});
|
||
} catch (e) {
|
||
console.error(e);
|
||
this.error = 'Не удалось начать проверку: ' + e.message;
|
||
this.status = '';
|
||
}
|
||
},
|
||
|
||
async checkBarcode() {
|
||
if (!this.scannedBarcode.trim()) return;
|
||
|
||
try {
|
||
this.status = 'Проверка штрихкода…';
|
||
const response = await this.fetchAuth(`/inspections/sessions/${this.activeInspection.id}/check`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
inv_number: this.scannedBarcode
|
||
})
|
||
});
|
||
|
||
this.lastScanResult = response;
|
||
|
||
// Очистить поле и обновить данные
|
||
this.scannedBarcode = '';
|
||
await this.loadInspectionStats();
|
||
await this.loadInspectionDetails();
|
||
this.status = '';
|
||
|
||
// Автоматически скрыть уведомление через 3 секунды
|
||
setTimeout(() => {
|
||
this.lastScanResult = null;
|
||
}, 3000);
|
||
|
||
// Вернуть фокус на поле ввода
|
||
this.$nextTick(() => {
|
||
if (this.$refs.barcodeInput) {
|
||
this.$refs.barcodeInput.focus();
|
||
}
|
||
});
|
||
|
||
} catch (e) {
|
||
console.error(e);
|
||
this.error = 'Не удалось проверить штрихкод: ' + e.message;
|
||
this.status = '';
|
||
}
|
||
},
|
||
|
||
async loadInspectionStats() {
|
||
try {
|
||
const response = await this.fetchAuth(`/inspections/sessions/${this.activeInspection.id}`);
|
||
this.inspectionStats = response;
|
||
} catch (e) {
|
||
console.error(e);
|
||
this.error = 'Не удалось загрузить статистику';
|
||
}
|
||
},
|
||
|
||
async loadInspectionDetails() {
|
||
try {
|
||
const response = await this.fetchAuth(`/inspections/sessions/${this.activeInspection.id}/records`);
|
||
this.checkedEquipment = response.records;
|
||
this.unknownBarcodes = response.unknown_barcodes;
|
||
} catch (e) {
|
||
console.error(e);
|
||
this.error = 'Не удалось загрузить детали проверки';
|
||
}
|
||
},
|
||
|
||
async refreshInspectionData() {
|
||
this.status = 'Обновление данных…';
|
||
await this.loadInspectionStats();
|
||
await this.loadInspectionDetails();
|
||
this.status = 'Данные обновлены';
|
||
},
|
||
|
||
async completeInspection() {
|
||
if (!confirm('Завершить проверку?')) return;
|
||
|
||
try {
|
||
this.status = 'Завершение проверки…';
|
||
await this.fetchAuth(`/inspections/sessions/${this.activeInspection.id}/complete`, {
|
||
method: 'POST'
|
||
});
|
||
|
||
this.activeInspection = null;
|
||
this.inspectionStats = {
|
||
total_checked: 0,
|
||
total_expected: 0,
|
||
total_unknown: 0,
|
||
progress_percent: 0
|
||
};
|
||
this.checkedEquipment = [];
|
||
this.unknownBarcodes = [];
|
||
this.lastScanResult = null;
|
||
this.status = 'Проверка завершена';
|
||
} catch (e) {
|
||
console.error(e);
|
||
this.error = 'Не удалось завершить проверку: ' + e.message;
|
||
this.status = '';
|
||
}
|
||
},
|
||
|
||
cancelInspection() {
|
||
if (confirm('Прервать проверку без сохранения?')) {
|
||
this.activeInspection = null;
|
||
this.inspectionStats = {
|
||
total_checked: 0,
|
||
total_expected: 0,
|
||
total_unknown: 0,
|
||
progress_percent: 0
|
||
};
|
||
this.checkedEquipment = [];
|
||
this.unknownBarcodes = [];
|
||
this.lastScanResult = null;
|
||
this.status = 'Проверка отменена';
|
||
}
|
||
},
|
||
|
||
async loadInspectionHistory() {
|
||
try {
|
||
this.status = 'Загрузка истории проверок…';
|
||
this.error = '';
|
||
this.inspectionHistory = await this.fetchAuth('/inspections/sessions');
|
||
this.status = `Загружено ${this.inspectionHistory.length} проверок`;
|
||
} catch (e) {
|
||
console.error(e);
|
||
this.error = 'Не удалось загрузить историю проверок: ' + e.message;
|
||
this.status = '';
|
||
}
|
||
},
|
||
|
||
async viewHistoryDetails(sessionId) {
|
||
try {
|
||
this.status = 'Загрузка деталей проверки…';
|
||
const [stats, details] = await Promise.all([
|
||
this.fetchAuth(`/inspections/sessions/${sessionId}`),
|
||
this.fetchAuth(`/inspections/sessions/${sessionId}/records`)
|
||
]);
|
||
|
||
// Показать активную проверку с данными истории
|
||
this.activeInspection = stats.session;
|
||
this.inspectionStats = stats;
|
||
this.checkedEquipment = details.records;
|
||
this.unknownBarcodes = details.unknown_barcodes;
|
||
this.status = '';
|
||
} catch (e) {
|
||
console.error(e);
|
||
this.error = 'Не удалось загрузить детали: ' + e.message;
|
||
this.status = '';
|
||
}
|
||
}
|
||
},
|
||
mounted() {
|
||
// read auth from localStorage
|
||
try {
|
||
this.token = localStorage.getItem('access_token') || '';
|
||
this.role = localStorage.getItem('role') || '';
|
||
} catch {}
|
||
this.loadAuditories();
|
||
this.loadOwners();
|
||
this.loadEquipmentTypes();
|
||
if (this.isAdmin) {
|
||
this.loadUsers();
|
||
}
|
||
}
|
||
}).mount('#app');
|