fix aud search on center

This commit is contained in:
Your Name
2026-05-11 15:12:34 +03:00
parent 54534ee490
commit 9491909f24
61 changed files with 1049 additions and 2774 deletions

View File

@@ -0,0 +1,17 @@
{
"permissions": {
"allow": [
"Bash(backend\\\\venv\\\\Scripts\\\\python.exe -m uvicorn backend.main:app --reload)",
"Bash(python:*)",
"Bash(backend/venv/Scripts/python.exe:*)",
"Bash(backend/venv/Scripts/pip.exe list:*)",
"Bash(backend/venv/Scripts/pip.exe install:*)",
"Bash(backend/venv/Scripts/uvicorn.exe backend.main:app)",
"Bash(curl -s http://localhost:8000/ping)",
"Bash(curl -s http://127.0.0.1:8000/ping)",
"Bash(taskkill /F /IM python.exe)",
"Bash(netstat -ano)",
"Bash(taskkill:*)"
]
}
}

142
INSPECTION_GUIDE.md Normal file
View File

@@ -0,0 +1,142 @@
# Руководство по использованию системы проверки оборудования
## Обзор
Система проверки оборудования позволяет проводить инвентаризацию с использованием штрихкод-сканера или ручного ввода инвентарных номеров.
## Возможности
- ✅ Проверка оборудования по аудиториям или всего подряд
- ✅ Поддержка штрихкод-сканеров (работают как клавиатура)
- ✅ Автоматическое обновление времени при повторном сканировании
- ✅ Сохранение неизвестных номеров
- ✅ Отслеживание прогресса в реальном времени
- ✅ История всех проверок
- ✅ Доступ для всех авторизованных пользователей (включая viewer)
## Как использовать
### 1. Начать новую проверку
1. Войдите в систему
2. Перейдите в раздел "Проверка" в меню
3. (Опционально) Выберите аудиторию для проверки
- Если аудитория не выбрана, будет проверяться всё оборудование
4. Нажмите "Начать проверку"
### 2. Сканирование оборудования
После начала проверки:
1. Фокус автоматически установлен на поле ввода
2. Отсканируйте штрихкод или введите инвентарный номер вручную
3. Нажмите Enter (или сканер сделает это автоматически)
4. Система покажет результат:
-**Зелёное уведомление** - оборудование найдено
-**Красное уведомление** - номер не найден (сохранён в неизвестные)
5. Продолжайте сканирование следующих позиций
### 3. Отслеживание прогресса
Во время проверки отображается:
- **Проверено** - количество отсканированных позиций
- **Всего** - ожидаемое количество оборудования
- **Не найдено** - количество неизвестных штрихкодов
- **Прогресс** - процент завершения
### 4. Завершение проверки
1. После завершения сканирования нажмите "Завершить проверку"
2. Подтвердите действие
3. Проверка будет сохранена в историю
### 5. Просмотр истории
1. На главном экране проверок нажмите "Загрузить историю"
2. Выберите нужную проверку
3. Нажмите "Детали" для просмотра отчёта
## Работа со сканером штрихкодов
### Подключение
1. Подключите USB штрихкод-сканер к компьютеру
2. Сканер работает как клавиатура - не требует драйверов
3. Откройте страницу проверки в браузере
### Сканирование
1. Убедитесь, что фокус на поле ввода
2. Наведите сканер на штрихкод
3. Нажмите кнопку сканирования
4. Сканер введёт номер и нажмёт Enter автоматически
### Советы
- После каждого сканирования фокус автоматически возвращается на поле ввода
- Можно сканировать штрихкоды быстро один за другим
- Уведомления о результатах исчезают автоматически через 3 секунды
## Особенности
### Повторное сканирование
- При повторном сканировании одного и того же оборудования обновляется только время проверки
- В таблице остаётся одна запись на единицу оборудования
### Неизвестные номера
- Если номер не найден в базе данных, он сохраняется в список неизвестных
- Неизвестные номера отображаются отдельным списком
- Это помогает выявить ошибки маркировки или новое оборудование
### Доступ
- Все авторизованные пользователи могут проводить проверки
- Проверки можно просматривать, но нельзя удалять
- Каждая проверка привязана к пользователю, который её создал
## API Endpoints
Система предоставляет следующие API эндпоинты:
- `POST /inspections/sessions` - Начать новую проверку
- `POST /inspections/sessions/{id}/check` - Сканировать штрихкод
- `GET /inspections/sessions/{id}` - Получить статистику проверки
- `POST /inspections/sessions/{id}/complete` - Завершить проверку
- `GET /inspections/sessions` - Список всех проверок
- `GET /inspections/sessions/{id}/records` - Детальный отчёт
Полная документация доступна по адресу: http://localhost:8000/docs
## Устранение неполадок
### Сканер не работает
1. Проверьте USB подключение
2. Убедитесь, что фокус на поле ввода
3. Попробуйте отсканировать в текстовый редактор для проверки
4. Проверьте настройки сканера (должен добавлять Enter в конце)
### Оборудование не находится
1. Проверьте инвентарный номер в базе данных
2. Убедитесь, что штрихкод читается правильно
3. Проверьте список неизвестных штрихкодов
### Прогресс не обновляется
1. Нажмите кнопку "Обновить данные"
2. Проверьте подключение к серверу
3. Обновите страницу браузера
## База данных
Система использует три новые таблицы:
1. **inspection_sessions** - Сессии проверок
2. **inspection_records** - Записи о проверенном оборудовании
3. **unknown_barcodes** - Неизвестные штрихкоды
Таблицы создаются автоматически при запуске скрипта `create_inspection_tables.py`.

View File

344
app.py
View File

@@ -1,344 +0,0 @@
# -*- coding: utf-8 -*-
from flask import Flask, render_template, redirect, url_for, request, jsonify
from models import db, Oboruds, Auditory, Zametki
from flask_migrate import Migrate
from datetime import *
import csv
import random
from urllib.parse import unquote
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///project.db"
app.jinja_env.auto_reload = True
app.config['TEMPLATES_AUTO_RELOAD'] = True
app.secret_key = '6523e58bc0eec42c31b9635d5e0dfc23b6d119b73e633bf3a5284c79bb4a1ede'
db.init_app(app)
migrate = Migrate(app, db)
@app.route("/login", methods=['GET', 'POST'])
def login():
return render_template('login.html')
@app.route("/", methods=['GET', 'POST'])
def index():
results = []
results1 = []
all_aud = db.session.query(Auditory).all()
auds = []
for item in all_aud:
auds.append(item.audnazvanie)
if request.method == 'POST':
p = request.form.get('srch').strip()
all_aud = db.session.query(Auditory).all()
s = db.session.query(Oboruds).filter(
Oboruds.invNumber.contains(p)).first()
if s:
for item in all_aud:
auds.append(item.audnazvanie)
if s.aud_id is None:
results.append([s.invNumber, s.nazvanie])
else:
results1.append(s.invNumber)
results1.append(s.nazvanie)
aud = db.session.get(Auditory, s.aud_id)
results1.append(aud.audnazvanie)
return render_template('index.html', aud=all_aud, results=results, res1=results1)
return render_template('index.html', aud=all_aud, results=results, res1=results1)
@app.route("/perenos", methods=['GET', 'POST'])
def perenos():
audid = request.args.get('audid')
invnomer = request.args.get('invnum')
ob = db.session.query(Oboruds).filter_by(invNumber=invnomer).first()
ob.aud_id = audid
db.session.commit()
return jsonify({'success': True}, 200, {'ContentType': 'application/json'})
@app.route("/addaud", methods=['GET', 'POST'])
def addAud():
return render_template('addaud.html', methods=['GET', 'POST'])
@app.route('/searchonaud', methods=['GET', 'POST'])
def searchonaud():
all_aud = db.session.query(Auditory).all()
res = []
if request.method == 'GET' and request.args.get('auditory'):
audid = request.args.get('auditory')
q = db.session.query(Auditory, Oboruds).filter(
Auditory.id == Oboruds.aud_id
).filter(Auditory.id == audid
).order_by(Oboruds.invNumber).all()
results = []
for auditory, oboruds in q:
results.append({
'num_ved': oboruds.numberved,
'auditory_id': auditory.id,
'auditory_name': auditory.audnazvanie,
'inv_number': oboruds.invNumber,
'oboruds_id': oboruds.nazvanie,
'raspolog': oboruds.raspologenie,
})
return jsonify(results)
else:
return render_template('searchonaud.html', aud=all_aud, res=res)
@app.route("/addaudtodb", methods=['GET', 'POST'])
def addaudtodb():
if request.method == 'POST':
aud = request.form.get('auditory')
db.session.add(Auditory(audnazvanie=aud))
db.session.commit()
return redirect(url_for('addAud'))
@app.route("/addoborudtodb", methods=['GET', 'POST'])
def addoborudtodb():
res = []
if request.method == 'POST':
audid = request.form.get('auditory')
invnomer = request.form.get('invnomer')
ob = db.session.query(Oboruds).filter_by(invNumber=invnomer).first()
ob.aud_id = audid
db.session.commit()
return redirect(url_for('index'))
@app.route('/all')
def alloborud():
return render_template('all.html')
@app.route('/getall')
def getall():
oborud = db.session.query(Oboruds).order_by(Oboruds.invNumber).all()
results = []
for oboruds in oborud:
if oboruds.aud_id is None:
results.append({
'invNumber': oboruds.invNumber,
'nazvanie': oboruds.nazvanie,
'raspologenie': oboruds.raspologenie,
'balancenumber': oboruds.balancenumber,
'kolichestvo': oboruds.kolichestvo,
'numberppasu': oboruds.numberppasu,
'numberved': oboruds.numberved,
'aud': ""})
else:
aud = db.session.query(Auditory).filter_by(id=oboruds.aud_id).first()
results.append({
'invNumber': oboruds.invNumber,
'nazvanie': oboruds.nazvanie,
'raspologenie': oboruds.raspologenie,
'balancenumber': oboruds.balancenumber,
'kolichestvo': oboruds.kolichestvo,
'numberppasu': oboruds.numberppasu,
'numberved': oboruds.numberved,
'aud': aud.audnazvanie})
return jsonify(results)
@app.route('/vneaud', methods=['GET', 'POST'])
def vneaud():
res = []
data = db.session.query(Oboruds).filter(Oboruds.aud_id == None).all()
ak = db.session.query(Oboruds).all()
for dt in data:
res.append([dt.invNumber, dt.nazvanie])
return render_template('vneaud.html', res=res, kolvo=len(data), all_kol=len(ak))
@app.route('/zametki', methods=['GET', 'POST'])
def zametki():
zam = db.session.query(Zametki).filter(Zametki.rmdt == None).all()
if request.method == 'POST':
textzam = request.form.get('textzam')
timeadd = datetime.now(timezone.utc)
if len(textzam) > 0:
db.session.add(Zametki(txtzam=textzam, created_date=timeadd))
db.session.commit()
return redirect(url_for('zametki'))
return render_template('zametki.html', zam=zam)
@app.route('/zamrm', methods=['GET', 'POST'])
def js2():
zmid = request.args.get('zmid')
ob = db.session.query(Zametki).filter_by(id=zmid).first()
ob.rmdt = datetime.now(timezone.utc)
db.session.commit()
return jsonify({'success': True}), 200, {'ContentType': 'application/json'}
@app.route('/zamsearch', methods=['GET', 'POST'])
def zamsearch():
p = request.form.get('srch')
searchedZam = db.session.query(Zametki).filter(
Zametki.txtzam.contains(p)).ll()
zam = []
for item in searchedZam:
zam.append([item.txtzam, item.created_date])
return render_template('zametki.html', zam=zam)
@app.route('/addraspved', methods=['GET', 'POST'])
def addraspved():
if request.method == 'POST':
query_string = request.data.decode()
un_query_string = unquote(unquote(query_string)).split(',')
ob = db.session.query(Oboruds).filter_by(invNumber=un_query_string[0]).first()
ob.numberved = un_query_string[1]
ob.kolichestvo = un_query_string[2]
ob.balancenumber = un_query_string[3]
ob.raspologenie = un_query_string[4]
db.session.commit()
db.session.close()
return jsonify({'success': True}, 200, {'ContentType': 'application/json'})
@app.route('/addoborudasu', methods=['GET', 'POST'])
def addoborud():
if request.method == 'POST':
query_string = request.data.decode()
un_query_string = unquote(unquote(query_string)).split(',')
db.session.add(
Oboruds(invNumber=un_query_string[0],
nazvanie=un_query_string[5],
raspologenie=un_query_string[4],
numberved=un_query_string[1],
kolichestvo=un_query_string[2],
balancenumber=un_query_string[3]
)
)
db.session.commit()
return jsonify({'success': True}, 200, {'ContentType': 'application/json'})
# ==================================================================================
def ranomraspr():
with app.app_context():
while len(db.session.query(Oboruds).filter(Oboruds.aud_id is None).all()) > 0:
audid = random.choice(db.session.query(Auditory).all())
oborud = random.choice(db.session.query(Oboruds).filter(Oboruds.aud_id is None).all())
oborud.aud_id = audid.id
db.session.commit()
def createdb():
with app.app_context():
db.create_all()
auds = ['519', '521', '521a', '522', '523',
'601л',
'602л',
'603л',
'604л',
'605л',
'606л',
'607л',
'608л',
'609л',
'610л',
'611л',
'612л',
'613л',
'616л',
'617л',
'618л',
'619л',
'620л',
'621л',
'622л',
'626л',
'627л',
'703л',
'710л',
'713л']
for aud in auds:
db.session.add(Auditory(audnazvanie=aud))
db.session.commit()
with open('inventMavrin.csv', encoding='cp1251') as csv_file:
csv_reader = csv.reader(csv_file, delimiter=';')
for row in csv_reader:
db.session.add(
Oboruds(invNumber=row[0], nazvanie=row[1], typeBalanse='баланс'))
db.session.commit()
def write2excell():
wb = xlrd.open_workbook("VedIsh.xls")
sheet = wb.sheet_by_index(0)
newFile = copy(wb)
newSheet = newFile.get_sheet(0)
invNomerColum = 6
column_index = 1
for row_idx in range(sheet.nrows):
cell_value = sheet.cell(row_idx, column_index)
if cell_value:
tmp_inv_number = str(cell_value).split(':')[1]
try:
a = tmp_inv_number[1:-1]
inv_number = int(tmp_inv_number[1:-1])
with app.app_context():
auditory_obj = db.session.query(Auditory).join(Oboruds, Oboruds.aud_id == Auditory.id).filter(
Oboruds.invNumber == inv_number).first()
print(auditory_obj.audnazvanie)
# cur.execute("SELECT aud.audnazvanie FROM oboruds AS ob JOIN auditory AS aud ON ob.aud_id = aud.id WHERE ob.invNumber = ?", (inv_number,))
except:
pass
"""
else:
#newSheet.write(row_idx, invNomerColum, "Нет инв номера")
pass
newFile.save("Ved31.xls")
"""
if __name__ == '__main__':
#write2excell()
app.run(debug=True, host='0.0.0.0', port='3800')

View File

@@ -0,0 +1,11 @@
{
"permissions": {
"allow": [
"Bash(.venvScriptsactivate)",
"Bash(python:*)",
"Bash(venv/Scripts/python.exe:*)",
"Bash(timeout 5 tail:*)",
"Bash(curl:*)"
]
}
}

View File

@@ -12,6 +12,7 @@ from backend.routers.rashodniki import consumables
from backend.routers.zametki import zametki from backend.routers.zametki import zametki
from backend.routers.auth import auth from backend.routers.auth import auth
from backend.routers.owners import owners from backend.routers.owners import owners
from backend.routers.inspections import inspections
@@ -32,8 +33,7 @@ def ping():
return {"message": "pong"} return {"message": "pong"}
# Serve static assets and frontend # Serve frontend
app.mount("/static", StaticFiles(directory="static"), name="static")
app.mount("/app", StaticFiles(directory="frontend", html=True), name="frontend") app.mount("/app", StaticFiles(directory="frontend", html=True), name="frontend")
@app.get("/") @app.get("/")
@@ -46,11 +46,12 @@ def login_page():
# Подключение роутов # Подключение роутов
app.include_router(equipment_types) app.include_router(equipment_types)
app.include_router(auditories) app.include_router(auditories)
app.include_router(oboruds) app.include_router(oboruds)
app.include_router(components) app.include_router(components)
app.include_router(consumables) app.include_router(consumables)
app.include_router(zametki) app.include_router(zametki)
app.include_router(auth) app.include_router(auth)
app.include_router(owners) app.include_router(owners)
app.include_router(inspections)

View File

@@ -1,6 +1,6 @@
# backend/models.py # backend/models.py
from sqlalchemy import Column, Integer, String, ForeignKey, DateTime from sqlalchemy import Column, Integer, String, ForeignKey, DateTime, UniqueConstraint
from sqlalchemy.orm import relationship, declarative_base from sqlalchemy.orm import relationship, declarative_base
import datetime import datetime
@@ -98,3 +98,47 @@ class Zametki(Base):
txtzam = Column(String(10000)) txtzam = Column(String(10000))
created_date = Column(DateTime, default=datetime.datetime.utcnow) created_date = Column(DateTime, default=datetime.datetime.utcnow)
rmdt = Column(DateTime) rmdt = Column(DateTime)
class InspectionSession(Base):
__tablename__ = 'inspection_sessions'
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
started_at = Column(DateTime, nullable=False)
completed_at = Column(DateTime, nullable=True)
aud_id = Column(Integer, ForeignKey("auditories.id"), nullable=True) # null = всё подряд
# Relationships
user = relationship("User")
auditory = relationship("Auditory")
records = relationship("InspectionRecord", back_populates="session")
unknown_barcodes = relationship("UnknownBarcode", back_populates="session")
class InspectionRecord(Base):
__tablename__ = 'inspection_records'
id = Column(Integer, primary_key=True)
session_id = Column(Integer, ForeignKey("inspection_sessions.id"), nullable=False)
oborud_id = Column(Integer, ForeignKey("oboruds.id"), nullable=False)
checked_at = Column(DateTime, nullable=False) # обновляется при повторном сканировании
# Relationships
session = relationship("InspectionSession", back_populates="records")
oborud = relationship("Oboruds")
# Уникальное ограничение: одна запись на оборудование в рамках сессии
__table_args__ = (UniqueConstraint('session_id', 'oborud_id', name='_session_oborud_uc'),)
class UnknownBarcode(Base):
__tablename__ = 'unknown_barcodes'
id = Column(Integer, primary_key=True)
session_id = Column(Integer, ForeignKey("inspection_sessions.id"), nullable=False)
barcode = Column(String(100), nullable=False)
scanned_at = Column(DateTime, nullable=False)
# Relationships
session = relationship("InspectionSession", back_populates="unknown_barcodes")

View File

@@ -16,7 +16,7 @@ class EquipmentTypeRead(EquipmentTypeBase):
id: int id: int
class Config: class Config:
orm_mode = True from_attributes = True
# === Component === # === Component ===
@@ -31,7 +31,7 @@ class ComponentRead(ComponentBase):
id: int id: int
class Config: class Config:
orm_mode = True from_attributes = True
# === Consumable === # === Consumable ===
@@ -46,7 +46,7 @@ class ConsumableRead(ConsumableBase):
id: int id: int
class Config: class Config:
orm_mode = True from_attributes = True
# === Owner === # === Owner ===
@@ -60,7 +60,7 @@ class OwnerRead(OwnerBase):
id: int id: int
class Config: class Config:
orm_mode = True from_attributes = True
# === Oborud === # === Oborud ===
@@ -95,7 +95,7 @@ class OborudRead(OborudBase):
consumables: List[ConsumableRead] = [] consumables: List[ConsumableRead] = []
class Config: class Config:
orm_mode = True from_attributes = True
# === Auditory === # === Auditory ===
@@ -109,7 +109,7 @@ class AuditoryRead(AuditoryBase):
id: int id: int
class Config: class Config:
orm_mode = True from_attributes = True
# === Zametka === # === Zametka ===
@@ -125,7 +125,7 @@ class ZametkaRead(ZametkaBase):
created_date: Optional[datetime] = None created_date: Optional[datetime] = None
class Config: class Config:
orm_mode = True from_attributes = True
# === Auth/User === # === Auth/User ===
@@ -151,8 +151,67 @@ class UserRead(UserBase):
id: int id: int
class Config: class Config:
orm_mode = True from_attributes = True
class UserRoleUpdate(BaseModel): class UserRoleUpdate(BaseModel):
role: Role role: Role
# === Inspection System ===
# InspectionSession
class InspectionSessionBase(BaseModel):
aud_id: Optional[int] = None
class InspectionSessionCreate(InspectionSessionBase):
pass
class InspectionSessionRead(InspectionSessionBase):
id: int
user_id: int
started_at: datetime
completed_at: Optional[datetime] = None
class Config:
from_attributes = True
# InspectionRecord
class InspectionRecordRead(BaseModel):
id: int
session_id: int
oborud_id: int
checked_at: datetime
oborud: Optional[OborudRead] = None
class Config:
from_attributes = True
# UnknownBarcode
class UnknownBarcodeRead(BaseModel):
id: int
session_id: int
barcode: str
scanned_at: datetime
class Config:
from_attributes = True
# Ответ на сканирование
class CheckBarcodeResponse(BaseModel):
status: Literal["found", "not_found"]
equipment: Optional[OborudRead] = None
message: str
# Статистика сессии
class InspectionSessionStats(BaseModel):
session: InspectionSessionRead
total_expected: int # всего оборудования (в аудитории или всего)
total_checked: int # проверено уникальных позиций
total_unknown: int # неизвестных штрихкодов
progress_percent: float
# Детальный отчёт
class InspectionDetailReport(BaseModel):
records: List[InspectionRecordRead]
unknown_barcodes: List[UnknownBarcodeRead]

View File

@@ -5,6 +5,7 @@ const api = {
oboruds: (audId) => `/oboruds/?aud_id=${encodeURIComponent(audId)}`, oboruds: (audId) => `/oboruds/?aud_id=${encodeURIComponent(audId)}`,
allOboruds: "/oboruds/?sort_by_inv=true", allOboruds: "/oboruds/?sort_by_inv=true",
owners: "/owners/", owners: "/owners/",
zametki: "/zametki/",
}; };
async function fetchJSON(url) { async function fetchJSON(url) {
@@ -21,6 +22,8 @@ createApp({
selectedAudId: '', selectedAudId: '',
oboruds: [], oboruds: [],
allOboruds: [], allOboruds: [],
unassignedOboruds: [],
totalOboruds: 0,
status: '', status: '',
error: '', error: '',
printTitle: '', printTitle: '',
@@ -33,6 +36,33 @@ createApp({
newAudName: '', newAudName: '',
owners: [], owners: [],
newOwnerName: '', 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: { computed: {
@@ -117,6 +147,27 @@ createApp({
this.view = 'allEquipment'; this.view = 'allEquipment';
await this.loadAllOboruds(); 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) { getAuditoryName(audId) {
if (!audId) return ''; if (!audId) return '';
const aud = this.auditories.find(a => a.id === audId); const aud = this.auditories.find(a => a.id === audId);
@@ -129,6 +180,120 @@ createApp({
window.print(); 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) { async saveOwner(item) {
try { try {
this.status = 'Сохранение владельца…'; this.status = 'Сохранение владельца…';
@@ -235,6 +400,187 @@ createApp({
this.error = 'Не удалось добавить аудиторию'; this.error = 'Не удалось добавить аудиторию';
this.status = ''; 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() { mounted() {
@@ -245,6 +591,7 @@ createApp({
} catch {} } catch {}
this.loadAuditories(); this.loadAuditories();
this.loadOwners(); this.loadOwners();
this.loadEquipmentTypes();
if (this.isAdmin) { if (this.isAdmin) {
this.loadUsers(); this.loadUsers();
} }

View File

@@ -4,7 +4,7 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>АСУ Инвентаризация</title> <title>АСУ Инвентаризация</title>
<link rel="stylesheet" href="/static/css/bootstrap.min.css" /> <link rel="stylesheet" href="/app/bootstrap.min.css" />
<link rel="stylesheet" href="/app/styles.css" /> <link rel="stylesheet" href="/app/styles.css" />
</head> </head>
<body> <body>
@@ -27,10 +27,23 @@
<ul class="navbar-nav me-auto mb-2 mb-lg-0"> <ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item"><a class="nav-link" href="#" @click.prevent="view='byAud'">По аудитории</a></li> <li class="nav-item"><a class="nav-link" href="#" @click.prevent="view='byAud'">По аудитории</a></li>
<li class="nav-item"><a class="nav-link" href="#" @click.prevent="showAllEquipment">Всё оборудование</a></li> <li class="nav-item"><a class="nav-link" href="#" @click.prevent="showAllEquipment">Всё оборудование</a></li>
<li class="nav-item"><a class="nav-link" href="#" @click.prevent="showUnassigned">Не распределено</a></li>
<li class="nav-item" v-if="isAuth"><a class="nav-link" href="#" @click.prevent="showInspection">Проверка</a></li>
<li class="nav-item" v-if="isAdmin"><a class="nav-link" href="#" @click.prevent="view='users'">Пользователи</a></li> <li class="nav-item" v-if="isAdmin"><a class="nav-link" href="#" @click.prevent="view='users'">Пользователи</a></li>
<li class="nav-item" v-if="isAdmin"><a class="nav-link" href="#" @click.prevent="view='audManage'">Аудитории</a></li> <li class="nav-item" v-if="isAdmin"><a class="nav-link" href="#" @click.prevent="view='audManage'">Аудитории</a></li>
<li class="nav-item"><a class="nav-link" href="/docs" target="_blank">API Docs</a></li> <li class="nav-item"><a class="nav-link" href="#" @click.prevent="showZametki">Заметки</a></li>
<li class="nav-item" v-if="canEdit"><a class="nav-link" href="#" @click.prevent="view='owners'">Владельцы</a></li> <li class="nav-item dropdown" v-if="canEdit">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">Добавить</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" @click.prevent="view='addEquipment'">Оборудование</a></li>
<li><a class="dropdown-item" href="#" @click.prevent="view='addAuditory'">Аудиторию</a></li>
<li><a class="dropdown-item" href="#" @click.prevent="view='addType'">Тип оборудования</a></li>
<li><a class="dropdown-item" href="#" @click.prevent="view='addOwner'">Владельца</a></li>
<li v-if="isAdmin"><hr class="dropdown-divider"></li>
<li v-if="isAdmin"><a class="dropdown-item" href="#" @click.prevent="view='addUser'">Пользователя</a></li>
</ul>
</li>
<li class="nav-item" v-if="isAuth"><a class="nav-link" href="/docs" target="_blank">API Docs</a></li>
</ul> </ul>
<span class="navbar-text ms-auto" v-if="isAuth">Роль: {{ role }}</span> <span class="navbar-text ms-auto" v-if="isAuth">Роль: {{ role }}</span>
<a class="btn btn-outline-primary ms-2" v-if="!isAuth" href="/login">Войти</a> <a class="btn btn-outline-primary ms-2" v-if="!isAuth" href="/login">Войти</a>
@@ -46,8 +59,8 @@
<div class="card-body"> <div class="card-body">
<h3 class="card-title no-print">Оборудование по аудитории</h3> <h3 class="card-title no-print">Оборудование по аудитории</h3>
<h2 class="print-only print-title">{{ printTitle }}</h2> <h2 class="print-only print-title">{{ printTitle }}</h2>
<div class="mb-2 d-flex align-items-center gap-2 no-print"> <div class="mb-2 d-flex align-items-center justify-content-center gap-2 no-print">
<label for="aud-select" class="me-2">Аудитория:</label> <label for="aud-select" class="mb-0">Аудитория:</label>
<select id="aud-select" class="form-select w-auto" v-model="selectedAudId"> <select id="aud-select" class="form-select w-auto" v-model="selectedAudId">
<option value="">— выберите аудиторию —</option> <option value="">— выберите аудиторию —</option>
<option v-for="a in auditories" :key="a.id" :value="a.id">{{ a.audnazvanie }}</option> <option v-for="a in auditories" :key="a.id" :value="a.id">{{ a.audnazvanie }}</option>
@@ -102,30 +115,34 @@
<div v-if="view==='allEquipment'" class="row"> <div v-if="view==='allEquipment'" class="row">
<div class="card col-12"> <div class="card col-12">
<div class="card-body"> <div class="card-body">
<h3 class="card-title">Всё оборудование (по инв. номеру)</h3> <h3 class="card-title no-print">Всё оборудование (по инв. номеру)</h3>
<div class="status" :class="{error: !!error}">{{ status }}</div> <h2 class="print-only print-title">Всё оборудование</h2>
<div class="mb-2 no-print">
<button class="btn btn-secondary" @click="printAllEquipment" :disabled="!allOboruds.length">Печать</button>
</div>
<div class="status no-print" :class="{error: !!error}">{{ status }}</div>
<div class="table-responsive"> <div class="table-responsive">
<table class="table datatable"> <table class="table datatable">
<thead> <thead>
<tr> <tr>
<th scope="col"></th> <th scope="col" class="num-col"></th>
<th scope="col">Инв. номер</th> <th scope="col" class="inv-col">Инв. номер</th>
<th scope="col">Название</th> <th scope="col">Название</th>
<th scope="col">Аудитория</th> <th scope="col" class="aud-col">Аудитория</th>
<th scope="col">Расположение</th> <th scope="col">Расположение</th>
<th scope="col">Кол-во</th> <th scope="col" class="no-print">Кол-во</th>
<th scope="col">Владелец</th> <th scope="col" class="no-print">Владелец</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="(it, index) in allOboruds" :key="it.id"> <tr v-for="(it, index) in allOboruds" :key="it.id">
<td>{{ index + 1 }}</td> <td class="num-col">{{ index + 1 }}</td>
<td class="inv">{{ it.invNumber ?? '' }}</td> <td class="inv-col">{{ it.invNumber ?? '' }}</td>
<td>{{ it.nazvanie ?? '' }}</td> <td>{{ it.nazvanie ?? '' }}</td>
<td>{{ getAuditoryName(it.aud_id) }}</td> <td class="aud-col">{{ getAuditoryName(it.aud_id) }}</td>
<td class="rasp">{{ it.raspologenie ?? '' }}</td> <td class="rasp">{{ it.raspologenie ?? '' }}</td>
<td>{{ it.kolichestvo ?? '' }}</td> <td class="no-print">{{ it.kolichestvo ?? '' }}</td>
<td>{{ it.owner?.name ?? '' }}</td> <td class="no-print">{{ it.owner?.name ?? '' }}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@@ -261,9 +278,357 @@
</div> </div>
</div> </div>
</div> </div>
<div v-if="view==='unassigned'" class="row">
<div class="card col-12">
<div class="card-body">
<h3 class="card-title">Не распределено: {{ unassignedOboruds.length }} из {{ totalOboruds }}</h3>
<div class="status" :class="{error: !!error}">{{ status }}</div>
<div class="table-responsive">
<table class="table datatable">
<thead>
<tr>
<th scope="col"></th>
<th scope="col">Инв. номер</th>
<th scope="col">Название</th>
<th scope="col">Владелец</th>
</tr>
</thead>
<tbody>
<tr v-for="(it, index) in unassignedOboruds" :key="it.id">
<td>{{ index + 1 }}</td>
<td class="inv">{{ it.invNumber ?? '' }}</td>
<td>{{ it.nazvanie ?? '' }}</td>
<td>{{ it.owner?.name ?? '' }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div v-if="view==='zametki'" class="row">
<div class="card col-12">
<div class="card-body">
<h3 class="card-title">Заметки</h3>
<div v-if="canEdit" class="mb-4">
<h5>Добавить заметку</h5>
<div class="row g-2 align-items-end">
<div class="col-md-8">
<textarea class="form-control" v-model="newZametkaText" rows="3" placeholder="Текст заметки..."></textarea>
</div>
<div class="col-auto">
<button class="btn btn-success" @click="createZametka">Добавить</button>
</div>
</div>
</div>
<div class="status" :class="{error: !!error}">{{ status }}</div>
<h5>Активные заметки</h5>
<div v-if="zametki.length === 0" class="text-muted">Нет активных заметок</div>
<div v-for="z in zametki" :key="z.id" class="card mb-2">
<div class="card-body">
<p class="card-text" style="white-space: pre-wrap;">{{ z.txtzam }}</p>
<small class="text-muted">{{ formatDate(z.created_date) }}</small>
<button v-if="canEdit" class="btn btn-sm btn-outline-success float-end" @click="resolveZametka(z.id)">Решено</button>
</div>
</div>
</div>
</div>
</div>
<!-- Добавить оборудование -->
<div v-if="view==='addEquipment'" class="row">
<div class="card col-12 col-md-8">
<div class="card-body">
<h3 class="card-title">Добавить оборудование</h3>
<div class="row g-3">
<div class="col-md-4">
<label class="form-label">Инв. номер</label>
<input type="number" class="form-control" v-model="newEquipment.invNumber" />
</div>
<div class="col-md-8">
<label class="form-label">Название</label>
<input type="text" class="form-control" v-model="newEquipment.nazvanie" required />
</div>
<div class="col-md-4">
<label class="form-label">Аудитория</label>
<select class="form-select" v-model="newEquipment.aud_id">
<option value="">— не выбрано —</option>
<option v-for="a in auditories" :key="a.id" :value="a.id">{{ a.audnazvanie }}</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label">Тип</label>
<select class="form-select" v-model="newEquipment.type_id">
<option value="">— не выбрано —</option>
<option v-for="t in equipmentTypes" :key="t.id" :value="t.id">{{ t.name }}</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label">Владелец</label>
<select class="form-select" v-model="newEquipment.owner_id">
<option value="">— не выбрано —</option>
<option v-for="o in owners" :key="o.id" :value="o.id">{{ o.name }}</option>
</select>
</div>
<div class="col-md-8">
<label class="form-label">Расположение</label>
<input type="text" class="form-control" v-model="newEquipment.raspologenie" />
</div>
<div class="col-md-4">
<label class="form-label">Количество</label>
<input type="number" class="form-control" v-model="newEquipment.kolichestvo" />
</div>
</div>
<div class="status mt-3" :class="{error: !!error}">{{ status }}</div>
<button class="btn btn-success mt-3" @click="createEquipment">Добавить</button>
</div>
</div>
</div>
<!-- Добавить аудиторию -->
<div v-if="view==='addAuditory'" class="row">
<div class="card col-12 col-md-6">
<div class="card-body">
<h3 class="card-title">Добавить аудиторию</h3>
<div class="mb-3">
<label class="form-label">Название аудитории</label>
<input type="text" class="form-control" v-model="newAudName" placeholder="например, 519" />
</div>
<div class="status" :class="{error: !!error}">{{ status }}</div>
<button class="btn btn-success" @click="createAuditory">Добавить</button>
</div>
</div>
</div>
<!-- Добавить тип оборудования -->
<div v-if="view==='addType'" class="row">
<div class="card col-12 col-md-6">
<div class="card-body">
<h3 class="card-title">Добавить тип оборудования</h3>
<div class="mb-3">
<label class="form-label">Название типа</label>
<input type="text" class="form-control" v-model="newTypeName" placeholder="например, Компьютер" />
</div>
<div class="status" :class="{error: !!error}">{{ status }}</div>
<button class="btn btn-success" @click="createEquipmentType">Добавить</button>
<h5 class="mt-4">Существующие типы</h5>
<ul class="list-group">
<li class="list-group-item" v-for="t in equipmentTypes" :key="t.id">{{ t.name }}</li>
</ul>
</div>
</div>
</div>
<!-- Добавить владельца -->
<div v-if="view==='addOwner'" class="row">
<div class="card col-12 col-md-6">
<div class="card-body">
<h3 class="card-title">Добавить владельца</h3>
<div class="mb-3">
<label class="form-label">Имя владельца</label>
<input type="text" class="form-control" v-model="newOwnerName" placeholder="например, Иванов И.И." />
</div>
<div class="status" :class="{error: !!error}">{{ status }}</div>
<button class="btn btn-success" @click="createOwner">Добавить</button>
<h5 class="mt-4">Существующие владельцы</h5>
<ul class="list-group">
<li class="list-group-item" v-for="o in owners" :key="o.id">{{ o.name }}</li>
</ul>
</div>
</div>
</div>
<!-- Добавить пользователя -->
<div v-if="view==='addUser'" class="row">
<div class="card col-12 col-md-6">
<div class="card-body">
<h3 class="card-title">Добавить пользователя</h3>
<div class="mb-3">
<label class="form-label">Логин</label>
<input type="text" class="form-control" v-model="newAdminUsername" />
</div>
<div class="mb-3">
<label class="form-label">Пароль</label>
<input type="password" class="form-control" v-model="newAdminPassword" />
</div>
<div class="status" :class="{error: !!error}">{{ status }}</div>
<button class="btn btn-success" @click="createAdmin">Создать администратора</button>
</div>
</div>
</div>
<!-- Проверка оборудования -->
<div v-if="view==='inspection'" class="row">
<div class="card col-12">
<div class="card-body">
<h3 class="card-title">Проверка оборудования</h3>
<!-- Блок 1: Начать проверку (если нет активной сессии) -->
<div v-if="!activeInspection">
<h5>Начать новую проверку</h5>
<div class="row g-2 align-items-end mb-3">
<div class="col-auto">
<label class="form-label">Аудитория (необязательно)</label>
<select class="form-select" v-model="inspectionAudId">
<option value="">— Всё оборудование —</option>
<option v-for="a in auditories" :key="a.id" :value="a.id">{{ a.audnazvanie }}</option>
</select>
</div>
<div class="col-auto">
<button class="btn btn-success" @click="startInspection">Начать проверку</button>
</div>
</div>
<h5 class="mt-4">История проверок</h5>
<button class="btn btn-secondary mb-3" @click="loadInspectionHistory">Загрузить историю</button>
<div v-if="inspectionHistory.length > 0" class="table-responsive">
<table class="table datatable">
<thead>
<tr>
<th>ID</th>
<th>Начата</th>
<th>Завершена</th>
<th>Аудитория</th>
<th>Действия</th>
</tr>
</thead>
<tbody>
<tr v-for="sess in inspectionHistory" :key="sess.id">
<td>{{ sess.id }}</td>
<td>{{ formatDate(sess.started_at) }}</td>
<td>{{ sess.completed_at ? formatDate(sess.completed_at) : '—' }}</td>
<td>{{ sess.aud_id ? getAuditoryName(sess.aud_id) : 'Всё' }}</td>
<td>
<button class="btn btn-sm btn-primary" @click="viewHistoryDetails(sess.id)">Детали</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Блок 2: Активная проверка -->
<div v-else>
<div class="alert alert-info">
<strong>Активная проверка</strong><br>
<span v-if="activeInspection.aud_id">Аудитория: {{ getAuditoryName(activeInspection.aud_id) }}</span>
<span v-else>Всё оборудование</span><br>
Начата: {{ formatDate(activeInspection.started_at) }}
</div>
<!-- Статистика прогресса -->
<div class="row mb-3">
<div class="col-md-3">
<div class="card text-center">
<div class="card-body">
<h5 class="card-title">{{ inspectionStats.total_checked }}</h5>
<p class="card-text">Проверено</p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-center">
<div class="card-body">
<h5 class="card-title">{{ inspectionStats.total_expected }}</h5>
<p class="card-text">Всего</p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-center">
<div class="card-body">
<h5 class="card-title">{{ inspectionStats.total_unknown }}</h5>
<p class="card-text">Не найдено</p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-center bg-success text-white">
<div class="card-body">
<h5 class="card-title">{{ inspectionStats.progress_percent }}%</h5>
<p class="card-text">Прогресс</p>
</div>
</div>
</div>
</div>
<!-- Поле для сканирования -->
<div class="mb-3">
<label class="form-label">Сканируйте штрихкод или введите инв. номер</label>
<input
ref="barcodeInput"
type="text"
class="form-control form-control-lg"
v-model="scannedBarcode"
@keyup.enter="checkBarcode"
placeholder="Отсканируйте или введите номер..."
autofocus
/>
</div>
<!-- Последний результат сканирования -->
<div v-if="lastScanResult" class="alert" :class="lastScanResult.status === 'found' ? 'alert-success' : 'alert-danger'">
{{ lastScanResult.message }}
<div v-if="lastScanResult.equipment">
<strong>{{ lastScanResult.equipment.nazvanie }}</strong>
</div>
</div>
<!-- Кнопки действий -->
<div class="mt-3 mb-4">
<button class="btn btn-primary me-2" @click="refreshInspectionData">Обновить данные</button>
<button class="btn btn-success me-2" @click="completeInspection">Завершить проверку</button>
<button class="btn btn-secondary" @click="cancelInspection">Отменить</button>
</div>
<!-- Таблица проверенного оборудования (в реальном времени) -->
<h5 class="mt-4">Проверенное оборудование</h5>
<div class="table-responsive">
<table class="table datatable">
<thead>
<tr>
<th>Инв. номер</th>
<th>Название</th>
<th>Аудитория</th>
<th>Проверено</th>
</tr>
</thead>
<tbody>
<tr v-for="rec in checkedEquipment" :key="rec.id">
<td>{{ rec.oborud?.invNumber }}</td>
<td>{{ rec.oborud?.nazvanie }}</td>
<td>{{ getAuditoryName(rec.oborud?.aud_id) }}</td>
<td>{{ formatDate(rec.checked_at) }}</td>
</tr>
</tbody>
</table>
</div>
<!-- Неизвестные штрихкоды -->
<div v-if="unknownBarcodes.length > 0">
<h5 class="mt-4 text-danger">Неизвестные штрихкоды</h5>
<ul class="list-group">
<li class="list-group-item" v-for="ub in unknownBarcodes" :key="ub.id">
{{ ub.barcode }} — {{ formatDate(ub.scanned_at) }}
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div> </div>
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script> <script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="/app/app.js" defer></script> <script src="/app/app.js" defer></script>
</body> </body>
</html> </html>

View File

@@ -4,8 +4,8 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Вход — АСУ Инвентаризация</title> <title>Вход — АСУ Инвентаризация</title>
<link rel="stylesheet" href="/static/css/bootstrap.min.css" /> <link rel="stylesheet" href="/app/bootstrap.min.css" />
<link rel="stylesheet" href="/static/css/index.css" /> <link rel="stylesheet" href="/app/styles.css" />
<style> <style>
.login-card { max-width: 420px; margin: 40px auto; } .login-card { max-width: 420px; margin: 40px auto; }
.muted { color: #6c757d; font-size: 0.9rem; } .muted { color: #6c757d; font-size: 0.9rem; }

View File

@@ -75,6 +75,23 @@ table {
width: 110px; width: 110px;
} }
td.aud-col, th.aud-col {
width: 70px;
min-width: 70px;
max-width: 70px;
}
td.num-col, th.num-col {
width: 40px;
min-width: 40px;
max-width: 40px;
text-align: center;
}
td.inv-col, th.inv-col {
width: auto;
}
.inv { .inv {
width: 400px; width: 400px;
} }
@@ -169,6 +186,16 @@ table {
border: 1px solid #000000; border: 1px solid #000000;
padding: 5px; padding: 5px;
font-size: 12pt; font-size: 12pt;
word-break: normal;
word-wrap: break-word;
overflow-wrap: break-word;
white-space: normal;
}
td.inv-col, th.inv-col {
width: 100px;
min-width: 100px;
max-width: 100px;
} }
table.rs-table-bordered { table.rs-table-bordered {

View File

@@ -1 +0,0 @@
Single-database configuration for Flask.

View File

@@ -1,52 +0,0 @@
# A generic, single database configuration.
[alembic]
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
script_location = .
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic,flask_migrate
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[logger_flask_migrate]
level = INFO
handlers =
qualname = flask_migrate
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

View File

@@ -1,113 +0,0 @@
import logging
from logging.config import fileConfig
from flask import current_app
from alembic import context
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
logger = logging.getLogger('alembic.env')
def get_engine():
try:
# this works with Flask-SQLAlchemy<3 and Alchemical
return current_app.extensions['migrate'].db.get_engine()
except (TypeError, AttributeError):
# this works with Flask-SQLAlchemy>=3
return current_app.extensions['migrate'].db.engine
def get_engine_url():
try:
return get_engine().url.render_as_string(hide_password=False).replace(
'%', '%%')
except AttributeError:
return str(get_engine().url).replace('%', '%%')
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
config.set_main_option('sqlalchemy.url', get_engine_url())
target_db = current_app.extensions['migrate'].db
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def get_metadata():
if hasattr(target_db, 'metadatas'):
return target_db.metadatas[None]
return target_db.metadata
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url, target_metadata=get_metadata(), literal_binds=True
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
# this callback is used to prevent an auto-migration from being generated
# when there are no changes to the schema
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
def process_revision_directives(context, revision, directives):
if getattr(config.cmd_opts, 'autogenerate', False):
script = directives[0]
if script.upgrade_ops.is_empty():
directives[:] = []
logger.info('No changes in schema detected.')
conf_args = current_app.extensions['migrate'].configure_args
if conf_args.get("process_revision_directives") is None:
conf_args["process_revision_directives"] = process_revision_directives
connectable = get_engine()
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=get_metadata(),
**conf_args
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

View File

@@ -1,24 +0,0 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}
def upgrade():
${upgrades if upgrades else "pass"}
def downgrade():
${downgrades if downgrades else "pass"}

View File

@@ -1,32 +0,0 @@
"""empty message
Revision ID: 10da3140ab2e
Revises: 4eacd6dcd461
Create Date: 2024-04-05 01:25:48.931573
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '10da3140ab2e'
down_revision = '4eacd6dcd461'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('oboruds', schema=None) as batch_op:
batch_op.add_column(sa.Column('kolichestvo', sa.Integer(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('oboruds', schema=None) as batch_op:
batch_op.drop_column('kolichestvo')
# ### end Alembic commands ###

View File

@@ -1,32 +0,0 @@
"""empty message
Revision ID: 256c3a3e91a2
Revises: 4f95d12a8352
Create Date: 2024-03-13 16:01:01.559719
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '256c3a3e91a2'
down_revision = '4f95d12a8352'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('oboruds', schema=None) as batch_op:
batch_op.drop_column('somepl')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('oboruds', schema=None) as batch_op:
batch_op.add_column(sa.Column('somepl', sa.VARCHAR(length=10), nullable=True))
# ### end Alembic commands ###

View File

@@ -1,54 +0,0 @@
"""empty message
Revision ID: 4eacd6dcd461
Revises: c208cbc25232
Create Date: 2024-04-03 23:25:42.271936
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '4eacd6dcd461'
down_revision = 'c208cbc25232'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('oboruds', schema=None) as batch_op:
batch_op.add_column(sa.Column('numberved', sa.String(length=100), nullable=True))
batch_op.add_column(sa.Column('numberppasu', sa.String(length=100), nullable=True))
batch_op.add_column(sa.Column('balancenumber', sa.Integer(), nullable=True))
batch_op.alter_column('nazvanie',
existing_type=sa.TEXT(length=500),
type_=sa.String(length=500),
existing_nullable=True)
batch_op.alter_column('raspologenie',
existing_type=sa.TEXT(length=200),
type_=sa.String(length=200),
existing_nullable=True)
batch_op.drop_column('buhnumberpp')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('oboruds', schema=None) as batch_op:
batch_op.add_column(sa.Column('buhnumberpp', sa.VARCHAR(length=100), nullable=True))
batch_op.alter_column('raspologenie',
existing_type=sa.String(length=200),
type_=sa.TEXT(length=200),
existing_nullable=True)
batch_op.alter_column('nazvanie',
existing_type=sa.String(length=500),
type_=sa.TEXT(length=500),
existing_nullable=True)
batch_op.drop_column('balancenumber')
batch_op.drop_column('numberppasu')
batch_op.drop_column('numberved')
# ### end Alembic commands ###

View File

@@ -1,32 +0,0 @@
"""empty message
Revision ID: 4f95d12a8352
Revises: 50f85881169e
Create Date: 2024-03-13 15:57:33.119683
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '4f95d12a8352'
down_revision = '50f85881169e'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('oboruds', schema=None) as batch_op:
batch_op.add_column(sa.Column('somepl', sa.String(length=10), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('oboruds', schema=None) as batch_op:
batch_op.drop_column('somepl')
# ### end Alembic commands ###

View File

@@ -1,24 +0,0 @@
"""empty message
Revision ID: 50f85881169e
Revises: b24baa0d98e6
Create Date: 2024-03-13 15:55:03.770084
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '50f85881169e'
down_revision = 'b24baa0d98e6'
branch_labels = None
depends_on = None
def upgrade():
pass
def downgrade():
pass

View File

@@ -1,34 +0,0 @@
"""empty message
Revision ID: 6fc3d1adb061
Revises: be7c94c549e5
Create Date: 2024-04-02 16:32:23.180273
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '6fc3d1adb061'
down_revision = 'be7c94c549e5'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('oboruds', schema=None) as batch_op:
batch_op.add_column(sa.Column('buhnumberpp', sa.String(length=100), nullable=True))
batch_op.add_column(sa.Column('kolichestvo', sa.Integer(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('oboruds', schema=None) as batch_op:
batch_op.drop_column('kolichestvo')
batch_op.drop_column('buhnumberpp')
# ### end Alembic commands ###

View File

@@ -1,32 +0,0 @@
"""empty message
Revision ID: 873defe09f22
Revises: ec6bbcd361bd
Create Date: 2024-03-13 22:34:36.718676
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '873defe09f22'
down_revision = 'ec6bbcd361bd'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('oboruds', schema=None) as batch_op:
batch_op.add_column(sa.Column('raspologenie', sa.String(length=200), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('oboruds', schema=None) as batch_op:
batch_op.drop_column('raspologenie')
# ### end Alembic commands ###

View File

@@ -1,32 +0,0 @@
"""empty message
Revision ID: 885bdd7b5161
Revises: be7c94c549e5
Create Date: 2024-04-02 23:03:59.401369
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '885bdd7b5161'
down_revision = 'be7c94c549e5'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('oboruds', schema=None) as batch_op:
batch_op.add_column(sa.Column('kolichestvo', sa.Integer(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('oboruds', schema=None) as batch_op:
batch_op.drop_column('kolichestvo')
# ### end Alembic commands ###

View File

@@ -1,54 +0,0 @@
"""empty message
Revision ID: 8e5efc4de919
Revises: c208cbc25232
Create Date: 2024-04-03 22:36:46.208266
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '8e5efc4de919'
down_revision = 'c208cbc25232'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('oboruds', schema=None) as batch_op:
batch_op.add_column(sa.Column('numberved', sa.String(length=100), nullable=True))
batch_op.add_column(sa.Column('numberppasu', sa.String(length=100), nullable=True))
batch_op.add_column(sa.Column('balancenumber', sa.Integer(), nullable=True))
batch_op.alter_column('nazvanie',
existing_type=sa.TEXT(length=500),
type_=sa.String(length=500),
existing_nullable=True)
batch_op.alter_column('raspologenie',
existing_type=sa.TEXT(length=200),
type_=sa.String(length=200),
existing_nullable=True)
batch_op.drop_column('buhnumberpp')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('oboruds', schema=None) as batch_op:
batch_op.add_column(sa.Column('buhnumberpp', sa.TEXT(length=100), nullable=True))
batch_op.alter_column('raspologenie',
existing_type=sa.String(length=200),
type_=sa.TEXT(length=200),
existing_nullable=True)
batch_op.alter_column('nazvanie',
existing_type=sa.String(length=500),
type_=sa.TEXT(length=500),
existing_nullable=True)
batch_op.drop_column('balancenumber')
batch_op.drop_column('numberppasu')
batch_op.drop_column('numberved')
# ### end Alembic commands ###

View File

@@ -1,32 +0,0 @@
"""empty message
Revision ID: 8e838956713f
Revises: 256c3a3e91a2
Create Date: 2024-03-20 19:03:51.112016
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '8e838956713f'
down_revision = '256c3a3e91a2'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('oboruds', schema=None) as batch_op:
batch_op.add_column(sa.Column('numberved', sa.String(length=100), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('oboruds', schema=None) as batch_op:
batch_op.drop_column('numberved')
# ### end Alembic commands ###

View File

@@ -1,24 +0,0 @@
"""empty message
Revision ID: b24baa0d98e6
Revises: 873defe09f22, b2a61aef79e9
Create Date: 2024-03-13 15:42:47.733687
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'b24baa0d98e6'
down_revision = ('873defe09f22', 'b2a61aef79e9')
branch_labels = None
depends_on = None
def upgrade():
pass
def downgrade():
pass

View File

@@ -1,24 +0,0 @@
"""empty message
Revision ID: b2a61aef79e9
Revises: ec6bbcd361bd
Create Date: 2024-03-13 01:48:30.093937
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'b2a61aef79e9'
down_revision = 'ec6bbcd361bd'
branch_labels = None
depends_on = None
def upgrade():
pass
def downgrade():
pass

View File

@@ -1,34 +0,0 @@
"""empty message
Revision ID: be7c94c549e5
Revises: 8e838956713f
Create Date: 2024-04-01 15:09:52.082987
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'be7c94c549e5'
down_revision = '8e838956713f'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('oboruds', schema=None) as batch_op:
batch_op.add_column(sa.Column('balancenumber', sa.String(length=30), nullable=True))
batch_op.drop_column('typeBalanse')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('oboruds', schema=None) as batch_op:
batch_op.add_column(sa.Column('typeBalanse', sa.VARCHAR(length=30), nullable=True))
batch_op.drop_column('balancenumber')
# ### end Alembic commands ###

View File

@@ -1,24 +0,0 @@
"""empty message
Revision ID: c208cbc25232
Revises: 6fc3d1adb061, 885bdd7b5161
Create Date: 2024-04-03 22:11:15.008480
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'c208cbc25232'
down_revision = ('6fc3d1adb061', '885bdd7b5161')
branch_labels = None
depends_on = None
def upgrade():
pass
def downgrade():
pass

View File

@@ -1,24 +0,0 @@
"""empty message
Revision ID: d4a8e4c9e65a
Revises: 10da3140ab2e, 8e5efc4de919
Create Date: 2024-05-06 08:27:39.088982
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'd4a8e4c9e65a'
down_revision = ('10da3140ab2e', '8e5efc4de919')
branch_labels = None
depends_on = None
def upgrade():
pass
def downgrade():
pass

View File

@@ -1,44 +0,0 @@
"""empty message
Revision ID: ec6bbcd361bd
Revises:
Create Date: 2024-03-13 08:22:23.761783
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'ec6bbcd361bd'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('oboruds', schema=None) as batch_op:
batch_op.drop_column('duplicate')
with op.batch_alter_table('zametki', schema=None) as batch_op:
batch_op.alter_column('txtzam',
existing_type=sa.TEXT(length=10000),
type_=sa.String(length=10000),
existing_nullable=True)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('zametki', schema=None) as batch_op:
batch_op.alter_column('txtzam',
existing_type=sa.String(length=10000),
type_=sa.TEXT(length=10000),
existing_nullable=True)
with op.batch_alter_table('oboruds', schema=None) as batch_op:
batch_op.add_column(sa.Column('duplicate', sa.BOOLEAN(), nullable=True))
# ### end Alembic commands ###

View File

@@ -1,76 +0,0 @@
# backend/models.py
from sqlalchemy import Column, Integer, String, ForeignKey, DateTime
from sqlalchemy.orm import relationship, declarative_base
import datetime
Base = declarative_base()
class Auditory(Base):
__tablename__ = 'auditories'
id = Column(Integer, primary_key=True)
audnazvanie = Column(String)
oboruds = relationship("Oboruds", back_populates="auditory")
class EquipmentType(Base):
__tablename__ = 'equipment_types'
id = Column(Integer, primary_key=True)
name = Column(String, unique=True, nullable=False)
oboruds = relationship("Oboruds", back_populates="type")
class Oboruds(Base):
__tablename__ = 'oboruds'
id = Column(Integer, primary_key=True)
invNumber = Column(Integer)
nazvanie = Column(String(500))
raspologenie = Column(String(200))
numberppasu = Column(String(100))
kolichestvo = Column(Integer)
aud_id = Column(Integer, ForeignKey("auditories.id"))
auditory = relationship("Auditory", back_populates="oboruds")
type_id = Column(Integer, ForeignKey("equipment_types.id"))
type = relationship("EquipmentType", back_populates="oboruds")
components = relationship("Component", back_populates="oborud")
consumables = relationship("Consumable", back_populates="oborud")
class Component(Base):
__tablename__ = 'components'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
description = Column(String)
oborud_id = Column(Integer, ForeignKey("oboruds.id"))
oborud = relationship("Oboruds", back_populates="components")
class Consumable(Base):
__tablename__ = 'consumables'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
description = Column(String)
oborud_id = Column(Integer, ForeignKey("oboruds.id"))
oborud = relationship("Oboruds", back_populates="consumables")
class Zametki(Base):
__tablename__ = 'zametki'
id = Column(Integer, primary_key=True)
txtzam = Column(String(10000))
created_date = Column(DateTime, default=datetime.datetime.utcnow)
rmdt = Column(DateTime)

View File

@@ -23,6 +23,7 @@ Flask-SQLAlchemy==3.1.1
waitress==3.0.2 waitress==3.0.2
# Auth # Auth
python-jose==3.3.0 python-jose==3.5.0
passlib==1.7.4 passlib==1.7.4
bcrypt==4.2.0 bcrypt==5.0.0
python-multipart==0.0.28

View File

@@ -1,6 +0,0 @@
from waitress import serve
from app import app
if __name__ == '__main__':
serve(app, port='3800')

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,179 +0,0 @@
* {
margin: 0;
padding: 0;
}
body {
background-color: #E2F3FD;
min-width: 580px;
}
.row {
text-align: center;
display: flex;
justify-content: center;
border: 1px;
}
header {
text-align: center;
background-color: #6A90B6;
padding: 2px;
}
a:hover {
text-decoration: none;
color: #041322;
}
a {
color: #041322;
}
.container {
margin: 10px;
}
button {
background-color: #E07D54;
margin-top: 5px;
margin-bottom: 5px;
}
.card {
/*width: 200px; */
margin: 10px;
border-radius: 15px;
border-color: #E07D54;
}
h5 {
text-align: center;
}
.hidden-column {
display: none;
}
nav {
width: 100%;
}
table {
word-break: break-all;
border-collapse: separate !important;
}
.table td {
font-size: 14px;
padding: 0;
max-width: 10rem;
word-break: break-all;
border-collapse: separate !important;
}
.aud {
width: 110px;
}
.inv {
width: 400px;
}
.rasp {
width: 200px;
word-break: break-word;
white-space: nowrap;
}
#modal_matcenn {
margin-left: 20px;
}
.datatable {
background-color: whitesmoke;
}
.datatable th:nth-child(7) {
width: 200px;
}
@media print {
* {
font-family: "Times New Roman", Times, serif;
}
body {
margin: 0;
padding: 0;
background-color: transparent;
}
a {
text-decoration: none;
border: none;
}
@page {
size: A4; /* или letter, legal, tabloid, etc. */
margin: 1cm; /* Устанавливаем поля */
align-items: center;
}
.card {
border: none;
width: 100%;
}
.no-print {
display: none;
}
table.rs-table-bordered {
border: 1px solid #000000;
margin-top: 20px;
font-size: 14pt;
}
table.rs-table-bordered > thead > tr > th {
border: 1px solid #000000;
padding: 2px;
font-size: 14pt;
}
table.rs-table-bordered > tbody > tr > td {
border: 1px solid #000000;
padding: 10px;
font-size: 14pt;
}
}

View File

@@ -1,282 +0,0 @@
function clearTable() {
var table = document.getElementById("alldatatable");
var rowCount = table.rows.length;
// Iterate through each row and remove it
for (var i = rowCount - 1; i > 0; i--) {
table.deleteRow(i);
}
}
function getAllData() {
clearTable();
let tableBody = document.getElementById('alldatatable').getElementsByTagName("tbody")[0];
i = 0;
$.getJSON("/getall", function (data) {
$.each(data, function (index, item) {
let newRow = tableBody.insertRow(tableBody.rows.length);
let cell1 = newRow.insertCell(0);
let cell2 = newRow.insertCell(1);
let cell3 = newRow.insertCell(2);
let cell4 = newRow.insertCell(3);
let cell5 = newRow.insertCell(4);
let cell6 = newRow.insertCell(5);
let cell7 = newRow.insertCell(6);
let cell8 = newRow.insertCell(7);
i++;
cell1.innerText = i;
cell2.innerText = item.numberved;
cell3.innerText = item.invNumber;
cell4.innerText = item.nazvanie;
cell5.innerText = item.kolichestvo;
cell6.innerText = item.balancenumber;
cell7.innerText = item.aud;
cell8.innerText = item.raspologenie;
$(newRow).data('itemData', i);
$(newRow).on("click", function () {
let vednumbertxt = newRow.cells[1].innerText;
let invnomertxt = newRow.cells[2].innerText;
let nazvanietxt = newRow.cells[3].innerText;
let kolvotxt = newRow.cells[4].innerText;
let schettxt = newRow.cells[5].innerText;
let raspologtxt = newRow.cells[7].innerText;
$('#getmodal').modal('show');
let vedomost = document.getElementById('modal_vednumber')
let invnom = document.getElementById('modal_invnom')
let matcen = document.getElementById('modal_matcenn')
let kolvo = document.getElementById('modal_kolvo')
let balancenumber = document.getElementById('modal_balance')
let rasp = document.getElementById('modal_rapolog')
invnom.innerText = invnomertxt
matcen.innerText = nazvanietxt.substring(0, 20)
if (vednumbertxt.length > 0) {
vedomost.value = vednumbertxt;
}
if (kolvotxt.length > 0) {
kolvo.value = kolvotxt;
}
if (schettxt.length > 0) {
balancenumber.value = schettxt;
}
if (raspologtxt.length > 0) {
rasp.value = raspologtxt;
}
});
});
});
}
$(document).ready(function () {
getAllData();
});
$('#modalclose').click(function () {
let vednumber = document.getElementById('modal_vednumber')
let kolvo = document.getElementById('modal_kolvo')
let balancenumber = document.getElementById('modal_balance')
let matcen = document.getElementById('modal_matcenn')
let rasp = document.getElementById('modal_rapolog')
vednumber.value = '';
kolvo.value = '';
balancenumber.value = '';
matcen.value = '';
rasp.value = '';
$('#getmodal').modal('hide')
})
$('#modalsavetodb').click(function () {
let invnom = document.getElementById('modal_invnom')
let vednumber = document.getElementById('modal_vednumber')
let kolvo = document.getElementById('modal_kolvo')
let balancenumber = document.getElementById('modal_balance')
let matcen = document.getElementById('modal_matcenn')
let rasp = document.getElementById('modal_rapolog')
let nazv = document.getElementById('modal_nazvanie')
let changeddata = new Array()
changeddata[0] = invnom.text;
changeddata[1] = vednumber.value;
changeddata[2] = kolvo.value;
changeddata[3] = balancenumber.value;
changeddata[4] = rasp.value;
let sendData = changeddata.join(',')
console.log(sendData)
$.ajax({
url: "/addraspved",
type: "POST",
contentType: "application/json;charset=utf-8",
dataType: "json",
data: sendData,
success: function () {
$('#getmodal').modal('hide')
vednumber.value = '';
kolvo.value = '';
balancenumber.value = '';
matcen.value = '';
rasp.value = '';
changeddata = [];
window.location.reload();
},
})
})
$('#addoborud').click(function () {
$('#addmodal').modal('show');
})
$('#modal2savetodb').click(function () {
let invnomer = document.getElementById('modal2_invnom')
let vednumber = document.getElementById('modal2_vednumber')
let kolvo = document.getElementById('modal2_kolvo')
let balancenumber = document.getElementById('modal2_balance')
let matcen = document.getElementById('modal2_matcenn')
let rasp = document.getElementById('modal2_rapolog')
let nazv = document.getElementById('modal2_nazvanie')
let changeddata = new Array()
changeddata[0] = invnomer.value;
changeddata[1] = vednumber.value;
changeddata[2] = kolvo.value;
changeddata[3] = balancenumber.value;
changeddata[4] = rasp.value;
changeddata[5] = nazv.value;
let sendData = changeddata.join(',')
console.log(sendData)
$.ajax({
url: "/addoborudasu",
type: "POST",
contentType: "application/json;charset=utf-8",
dataType: "json",
data: sendData,
success: function () {
vednumber.value = '';
invnomer.value = '';
nazvanie.value = '';
kolvo.value = '';
balancenumber.value = '';
matcen.value = '';
rasp.value = '';
$('#addmodal').modal('hide')
window.location.reload()
},
})
})
$('#modal2close').click(function () {
let vednumber = document.getElementById('modal2_vednumber')
let invnomer = document.getElementById('modal2_invnom')
let nazvanie = document.getElementById('modal2_nazvanie')
let kolvo = document.getElementById('modal2_kolvo')
let balancenumber = document.getElementById('modal2_balance')
let matcen = document.getElementById('modal2_matcenn')
let rasp = document.getElementById('modal2_rapolog')
vednumber.value = '';
invnomer.value = '';
nazvanie.value = '';
kolvo.value = '';
balancenumber.value = '';
matcen.value = '';
rasp.value = '';
$('#addmodal').modal('hide')
})
$('#modal2').on('hidden.bs.modal', function () {
location.reload();
})
$(document).ready(function () {
// Слушаем событие клика по заголовкам таблицы
$('#alldatatable thead th').on('click', function () {
var columnIndex = $(this).index(); // Индекс колонки
var sortColumn = $(this).text().toLowerCase(); // Текст заголовка колонки
// Сортируем таблицу по выбранной колонке
$('#alldatatable tbody tr').sort(function (a, b) {
var valA;
var valB;
if (isNaN(parseFloat($(a).find('td:eq(' + columnIndex + ')').text()))) {
// если это текстовая колонка, то сортируем по алфавиту
valA = $(a).find('td:eq(' + columnIndex + ')').text().toLowerCase();
valB = $(b).find('td:eq(' + columnIndex + ')').text().toLowerCase();
} else {
// если это числовая колонка, то сортируем по числовому значению
valA = parseFloat($(a).find('td:eq(' + columnIndex + ')').text());
valB = parseFloat($(b).find('td:eq(' + columnIndex + ')').text());
}
if (valA < valB) return -1;
if (valA > valB) return 1;
return 0;
}).appendTo('#alldatatable tbody');
// Обновляем классы для активной колонки
$('#alldatatable thead th').removeClass('active');
$(this).addClass('active');
});
});

View File

@@ -1,35 +0,0 @@
function getData(){
const audid = document.getElementById('auditory')
$.ajax({
url: "/getall",
type: "get",
contentType: 'application/json',
dataType: 'json',
success: function(response){
var data = response;
const table = document.getElementById('datatable')
table.innerHTML = ''
var headTable = '<tr> <td >Номер в Инв. вед</td> <td id="invnomer">Инв. номер</td><td>Название</td><td class="no-print aud">Аудитория</td> <td >Расположение</td> <td id="proverka"class="hidden-column"> Проверено</td> </tr>'
table.innerHTML += headTable
var tr =""
data.forEach(element => {
tr += '<tr onclick="tableclick(this)">'
tr += '<td>' + element.num_ved + '</td>'
tr += '<td clas="inv">' + element.inv_number + '</td>'
tr += '<td>' + element.oboruds_id + '</td>'
tr += '<td class="no-print">' + element.auditory_name + '</td>'
tr += '<td class="rasp">' +element.raspolog + '</td>'
tr += '<td>' + '\n' + '</td>'
tr += '</tr>'
});
table.innerHTML += tr
}
})
}

View File

@@ -1,32 +0,0 @@
$( document ).ready(function() {
$(".updatebtn").click(function(){
var audid = document.getElementById('auditory_dubl').value;
var invnum = document.getElementById('invnomer').textContent;
console.log('start perenos')
$.ajax({
url: "/perenos",
type: 'get',
contentType: 'application/json',
dataType: 'json',
data: {'audid':audid,
'invnum':invnum
},
error: function(error){
console.log(error);
}
})
invnomer.textContent=''
nazvanie.textContent=''
aud.textContent=''
auditory_dubl.selectedIndex = 0;
})
})

View File

View File

@@ -1,25 +0,0 @@
$("#printbutton").click(function(){
let aud = document.getElementById('auditory')
let audtext = aud.options[aud.selectedIndex].text;
const h2 = document.querySelector('h2');
h2.textContent = "Аудитория № " + audtext
document.getElementById("datatable").className="rs-table-bordered px-3"
let column = document.getElementById("proverka")
column.classList.remove("hidden-column")
window.print()
document.getElementById("datatable").className="table"
h2.textContent='распределение мат. ценностей'
column.classList.add("hidden-column")
})

View File

@@ -1,5 +0,0 @@
$("#printallbutton").click(function(){
console.log("aaaaaaa")
})

View File

@@ -1,125 +0,0 @@
function getData(){
const audid = document.getElementById('auditory')
$.ajax({
url: "/searchonaud",
type: "get",
contentType: 'application/json',
dataType: 'json',
data: {
auditory: audid.value
},
success: function(response){
var data = response;
const table = document.getElementById('datatable')
var headTable = '<tr> <td >Номер в Инв. вед</td> <td id="invnomer">Инв. номер</td><td>Название</td><td class="no-print aud">Аудитория</td> <td >Расположение</td> <td id="proverka"class="hidden-column"> Проверено</td> </tr>'
var tr =""
data.forEach(element => {
tr += '<tr onclick="tableclick(this)">'
tr += '<td>' + element.num_ved + '</td>'
tr += '<td clas="inv">' + element.inv_number + '</td>'
tr += '<td>' + element.oboruds_id + '</td>'
tr += '<td class="no-print">' + element.auditory_name + '</td>'
tr += '<td class="rasp">' +element.raspolog + '</td>'
tr += '<td>' + '\n' + '</td>'
tr += '</tr>'
});
table.innerHTML = headTable + tr
}
})
}
$("#searchbutton").click(function(){
getData();
})
$('#modalsavetodb').click(function(){
let rasp = document.getElementById('modal_rapolog')
let vedomost = document.getElementById('modal_vednumber')
let invnom = document.getElementById('modal_invnom')
let matcen = document.getElementById('modal_matcenn')
let changeddata = new Array()
changeddata[0] = invnom.text
changeddata[1] = vedomost.value
changeddata[2] = rasp.value
let sendData = changeddata.join(',')
changeddata = []
$.ajax({
url: "/addraspved",
type: "POST",
contentType: "application/json;charset=utf-8",
dataType: "json",
data: sendData,
success: function(){
/*
rasp='',
vedomost = '',
invnom = '',
matcen = '',
changeddata = []
*/
$('#getmodal').modal('hide').data( 'bs.modal', null );
getData();
}
})
})
function tableclick(tableRow){
let nomved = tableRow.childNodes[0].innerHTML;
let invnomer = tableRow.childNodes[1].innerHTML;
let nazvanie = tableRow.childNodes[2].innerHTML;
let raspolog = tableRow.childNodes[4].innerHTML;
$('#getmodal').modal('show')
let rasp = document.getElementById('modal_rapolog')
let vedomost = document.getElementById('modal_vednumber')
let invnom = document.getElementById('modal_invnom')
let matcen = document.getElementById('modal_matcenn')
invnom.innerText = invnomer
matcen.innerText = nazvanie.substring(0,15)
if (nomved.length >0){
vedomost.value = nomved
}
if(raspolog.length>0){
rasp.value = raspolog
}
$("#mimodal").on('hidden.bs.modal', function () {
$(this).data('bs.modal', null);
});
$('#modalclose').click(function(){
$('#getmodal').modal('hide').data( 'bs.modal', null );
} )
}

View File

@@ -1,47 +0,0 @@
function tableclick(x){
// let data = document.getElementById(x.rowIndex)
let datas = x.innerText.split('\t')
invnom.innerText=datas[1]+"\t"
matcen.innerText=datas[2]
if (datas[4].length>0){
rasp.value=datas[4];
}
if (datas[0].length>0){
vedomost.value=datas[0]
}
$('#modalclose').click(function(){
$('#getmodal').modal('hide');
} )
$('#modalsavetodb').click(function(){
$.ajax({
url: "/addraspved",
type: "POST",
contentType: "application/json;charset=utf-8",
dataType: "json",
data: {
rasp: rasp.value,
ved: vedomost.value,
inv: invnomer
},
success:function() {
rasp.value = '';
vedomost.value= '';
data=[];
rasp
$('#getmodal').modal('hide');
getData();
}
})
})}

View File

@@ -1,15 +0,0 @@
$( document ).ready(function() {
$(".reshbtn").click(function(){
var zmid = this.id
$.ajax({
url: "/zamrm",
type: 'get',
contentType: 'application/json',
dataType: 'json',
data: {'zmid':zmid},
})
var parent = document.getElementById('zambody')
var child = document.getElementById(zmid)
parent.removeChild(child)
})
})

View File

@@ -1,24 +0,0 @@
{% extends 'base.html' %}
{% block content %}
<div class="row">
<div class="card col-md-10 col-10">
<div class="card-body">
<h5 class="card-title">Добавить аудиторию</h5>
<form method='POST' action="/addaudtodb" class="card" >
<input type="text" class="form-control" name="auditory">
<button> Добавить</button>
</form>
</div>
</div>
</div>
{%endblock%}

View File

@@ -1,147 +0,0 @@
{% extends 'base.html' %}
{% block content %}
<!-- Modal -->
<div class="modal fade" id="getmodal" tabindex="-1" role="dialog" aria-labelledby="exampleModalCenterTitle" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-body" id="textarea">
<div class="row">
<a id="modal_invnom"> </a><a id="modal_matcenn"></a>
</div>
<div class="row">
№ из ведомости
<input type="text" class="form-control" id ='modal_vednumber' placeholder="Номер из ведомости">
</div>
<div class="row">
Количество
<input type="text" class="form-control" id ='modal_kolvo' placeholder="Количество">
</div>
<div class="row">
Балансовый счёт
<input type="text" class="form-control" id ='modal_balance' placeholder="Балансовый счёт">
</div>
<div class="row">
Расположение
<input type="text" class="form-control" id ='modal_rapolog' placeholder="Введите расположение">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal" id="modalclose">Закрыть</button>
<button type="button" class="btn btn-primary" id="modalsavetodb" >Сохранить изменения</button>
</div>
</div>
</div>
</div>
</div>
<!-- Modal2 -->
<div class="modal fade" id="addmodal" tabindex="-1" role="dialog" aria-labelledby="exampleModalCenterTitle" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-body" id="textarea">
<div class="row">
<a id="modal_invnom"> </a><a id="modal2_matcenn"></a>
</div>
<div class="row">
№ из ведомости
<input type="text" class="form-control" id ='modal2_vednumber' placeholder="Номер из ведомости">
<div class="row">
Инвентарный номер
<input type="text" class="form-control" id ='modal2_invnom' placeholder="Инвентарный номер">
</div>
</div>
<div class="row">
Название
<input type="text" class="form-control" id ='modal2_nazvanie' placeholder="Название">
</div>
<div class="row">
Количество
<input type="text" class="form-control" id ='modal2_kolvo' placeholder="Количество">
</div>
<div class="row">
Балансовый счёт
<input type="text" class="form-control" id ='modal2_balance' placeholder="Балансовый счёт">
</div>
<div class="row">
Расположение
<input type="text" class="form-control" id ='modal2_rapolog' placeholder="Введите расположение">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal" id="modal2close">Закрыть</button>
<button type="button" class="btn btn-primary" id="modal2savetodb" >Сохранить изменения</button>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<h3 id ='123' class=" no-print"> Все мат. ценности </h3>
</div>
<div class="row col-12">
<button class="button" id="printallbutton"> Печать </button>
</div>
<div class="row col-12">
<button class="button" id="addoborud"> Добавить </button>
</div>
<div class="row">
<div class="card col-md-11 table-responsive">
<table id="alldatatable" class="alldatable table pagebreak" >
<thead>
<tr>
<th scope="col"><br>п/п <br>АСУ</th>
<th scope="col">№ п/п <br>вед</th>
<th scope="col">Инв. номер</th>
<th scope="col">Название</th>
<th scope="col">Кол-во</th>
<th scope="col">Счёт</th>
<th scope="col">Ауд - я</th>
<th scope="col">Расположение</th>
</tr>
</thead>
<tr>
</tr>
</table>
</div>
</div>
<script src="{{url_for('static', filename='js/allmatc.js') }}"></script>
{% endblock %}

View File

@@ -1,147 +0,0 @@
{% extends 'base.html' %}
{% block content %}
<!-- Modal -->
<div class="modal fade" id="getmodal" tabindex="-1" role="dialog" aria-labelledby="exampleModalCenterTitle" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-body" id="textarea">
<div class="row">
<a id="modal_invnom"> </a><a id="modal_matcenn"></a>
</div>
<div class="row">
№ из ведомости
<input type="text" class="form-control" id ='modal_vednumber' placeholder="Номер из ведомости">
</div>
<div class="row">
Количество
<input type="text" class="form-control" id ='modal_kolvo' placeholder="Количество">
</div>
<div class="row">
Балансовый счёт
<input type="text" class="form-control" id ='modal_balance' placeholder="Балансовый счёт">
</div>
<div class="row">
Расположение
<input type="text" class="form-control" id ='modal_rapolog' placeholder="Введите расположение">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal" id="modalclose">Закрыть</button>
<button type="button" class="btn btn-primary" id="modalsavetodb" >Сохранить изменения</button>
</div>
</div>
</div>
</div>
</div>
<!-- Modal2 -->
<div class="modal fade" id="addmodal" tabindex="-1" role="dialog" aria-labelledby="exampleModalCenterTitle" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-body" id="textarea">
<div class="row">
<a id="modal_invnom"> </a><a id="modal2_matcenn"></a>
</div>
<div class="row">
№ из ведомости
<input type="text" class="form-control" id ='modal2_vednumber' placeholder="Номер из ведомости">
<div class="row">
Инвентарный номер
<input type="text" class="form-control" id ='modal2_invnom' placeholder="Инвентарный номер">
</div>
</div>
<div class="row">
Название
<input type="text" class="form-control" id ='modal2_nazvanie' placeholder="Название">
</div>
<div class="row">
Количество
<input type="text" class="form-control" id ='modal2_kolvo' placeholder="Количество">
</div>
<div class="row">
Балансовый счёт
<input type="text" class="form-control" id ='modal2_balance' placeholder="Балансовый счёт">
</div>
<div class="row">
Расположение
<input type="text" class="form-control" id ='modal2_rapolog' placeholder="Введите расположение">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal" id="modal2close">Закрыть</button>
<button type="button" class="btn btn-primary" id="modal2savetodb" >Сохранить изменения</button>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<h3 id ='123' class=" no-print"> Все мат. ценности </h3>
</div>
<div class="row col-12">
<button class="button" id="printallbutton"> Печать </button>
</div>
<div class="row col-12">
<button class="button" id="addoborud"> Добавить </button>
</div>
<div class="row">
<div class="card col-md-11 table-responsive">
<table id="alldatatable" class="alldatable table pagebreak" >
<thead>
<tr>
<th scope="col"><br>п/п <br>АСУ</th>
<th scope="col">№ п/п <br>вед</th>
<th scope="col">Инв. номер</th>
<th scope="col">Название</th>
<th scope="col">Кол-во</th>
<th scope="col">Счёт</th>
<th scope="col">Ауд - я</th>
<th scope="col">Расположение</th>
</tr>
</thead>
<tr>
</tr>
</table>
</div>
</div>
<script src="{{url_for('static', filename='js/allmatc.js') }}"></script>
{% endblock %}

View File

@@ -1,28 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" href="{{ url_for('static',filename='css/bootstrap.min.css')}}">
<link rel="stylesheet" href="{{ url_for('static',filename='css/index.css')}}">
<title class="no-print">Инвентаризация</title>
</head>
{% include 'head.html' %}
<div class="row no-print">
{% include 'navbar.html' %}
</div>
<body>
{% block content %} {% endblock %}
{% include 'js.html' %}
</body>
</html>

View File

@@ -1,13 +0,0 @@
<header >
<h1>
<a href="/">Инвентаризация кафедры <br>
"Автоматизированные системы управления"</br> </a>
</h1>
<h2>
распределение мат. ценностей
</h2>
</header>

View File

@@ -1,84 +0,0 @@
{% extends 'base.html' %}
{% block content %}
<div class="row">
<div class="card col-md-6 col-10" >
<div class="card-body">
<form method="POST" action="/">
<input type="text" name="srch" placeholder="инвентарный номер">
<button> Найти </button>
</form>
</div>
</div>
</div>
<div class="row">
<div class="card col-md-10 col-10">
<div class="card-body">
<h3 class="card-title"> Нераспределённые </h3>
<form method="POST" action="/addoborudtodb">
<table class="table" name="table" col-md-10>
<thead>
<tr>
<th scope="col">Инв. номер</th>
<th scope="col">Название</th>
<th scope="col">Аудитория</th>
</tr>
{% for item in results: %}
<tr>
<td> <input type="hidden" name="invnomer" value="{{ item[0] }}"> {{ item[0] }} </td>
<td> {{ item[1] }} </td>
<td> <select name="auditory" id="auditory">
{% for item in aud: %}
<option name="optauditory" value="{{item.id}}">{{ item.audnazvanie }}</option>
{% endfor %}
</select>
</td>
</td>
</tr>
{% endfor %}
</table>
<button> Обновить</button>
</form>
</div>
</div>
</div>
<div class="row">
<div class="card col-md-10 col-10">
<div class="card-body">
<h3 class="card-title"> Распределённые </h3>
<table class="table" col-md-10>
<thead>
<tr>
<th scope="col">Инв. номер</th>
<th scope="col">Название</th>
<th scope="col">Аудитория исходная</th>
<th scope="col">Аудитория переноса</th>
</tr>
<td id="invnomer"> {{res1[0]}} </td>
<td id="nazvanie"> {{res1[1]}} </td>
<td id="aud"> {{res1[2]}} </td>
<td>
<select name="auditory" id="auditory_dubl">
{% for item in aud: %}
<option name="optauditory" value="{{item.id}}">{{ item.audnazvanie }}</option>
{% endfor %}
</select>
</td>
</table>
<button class="updatebtn" > Перенести </button>
</div>
</div>
</div>
{%endblock%}

View File

@@ -1,13 +0,0 @@
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://code.jquery.com/jquery-3.7.1.js"></script>
<script src="{{url_for('static', filename='js/index.js') }}"></script>
<script src="{{url_for('static', filename='js/printall.js') }}"></script>
<script src="{{url_for('static', filename='js/zametki.js') }}"></script>
<script src="{{url_for('static', filename='js/searchonaud.js') }}"></script>
<script src="{{url_for('static', filename='js/print.js') }}"></script>
<script src="{{url_for('static', filename='js/modal.js') }}"></script>
<script src="{{url_for('static', filename='js/allmatc.js') }}"></script>

View File

@@ -1,62 +0,0 @@
{% extends 'base.html' %}
{% block content %}
<div class="row">
<div class="card col-4" >
<div class="card-body">
<form class="form-horizontal">
<fieldset>
<!-- Form Name -->
<legend>Авторизация</legend>
<!-- Text input-->
<div class="form-group">
<label control-label" for="logininput">Логин</label>
<div>
<input id="logininput" name="logininput" type="text" placeholder="Введите логин" class="form-control input-md">
</div>
</div>
<!-- Password input-->
<div class="form-group">
<label class="control-label" for="passwordinput">Пароль</label>
<input id="passwordinput" name="passwordinput" type="password" placeholder="Введите пароль" class="form-control input-md">
</div>
</div>
<div class="row">
<button id="btn_login" name="btn_login" class="btn btn-primary">Войти</button>
</div>
</fieldset>
</form>
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,39 +0,0 @@
<div class="row no-print">
<nav class="no-print navbar navbar-expand-lg navbar-light">
<div class="no-print container-fluid">
<button class="no-print navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
aria-controls="no-print navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="no-printnavbar-toggler-icon"></span>
</button>
<div class="no-print ollapse navbar-collapse" id="navbarSupportedContent">
<ul class="no-print navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item no-print">
<a class="nav-link no-print" aria-current="page" href="{{ url_for('index') }}">Главная</a>
</li>
<li class="nav-item no-print ">
<a class="nav-link no-print " aria-current="page" href="{{ url_for('alloborud') }}">Вся таблица</a>
</li>
<li class="nav-item no-print">
<a class="nav-link no-print" href="{{url_for('searchonaud')}}">Поаудиторно</a>
</li>
<li class="nav-item no-print">
<a class="nav-link no-print" aria-current="page" href="{{url_for('addAud')}}">Добавить аудиторию</a>
</li>
<li class="nav-item no-print">
<a class="nav-link no-print" aria-current="page" href="{{url_for('vneaud')}}">Не распределено</a>
</li>
<li class="nav-item no-print">
<a class="nav-link no-print" aria-current="page" href="{{url_for('zametki')}}">Заметки</a>
</li>
</ul>
</div>
</div>
</nav>
</div>

View File

@@ -1,49 +0,0 @@
{% extends 'base.html' %}
{% block content %}
<div class="row">
<div class="card col-md-10 col-10">
<div class="card-body">
<form method="POST" action="/search">
<input type="text" name="srch" placeholder="инвентарный номер">
<button> Найти </button>
</form>
</div>
</div>
</div>
<div class="row">
<div class="card col-md-10 col-10">
<div class="card-body">
<h3 class="card-title"> Распределённые </h3>
<form method="POST" action="/updateduplicate">
<table class="table" col-md-10>
<thead>
<tr>
<th scope="col">Инв. номер</th>
<th scope="col">Название</th>
</tr>
</tr>
<td> {{res1[0]}} </td>
</table>
</form>
</div>
</div>
</div>
{%endblock%}

View File

@@ -1,59 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{{ url_for('static',filename='css/bootstrap.min.css')}}">
<link rel="stylesheet" href="{{ url_for('static',filename='css/index.css')}}">
<title>Инвентаризация</title>
</head>
<body>
<header>
<h1>
Инвентаризация каф АСУ
</h1>
<h2>
Аудитория
</h2>
</header>
<div class="row">
<div class="card col-md-10 col-10">
<div class="card-body">
<h3 class="card-title"> Поаудиторно </h3>
<form method="POST" action="/addoborudtodb">
<table class="table" id="datatable" col-md-10>
<thead>
<tr>
<th scope="col">Инв. номер</th>
<th scope="col">Название</th>
<th scope="col">Аудитория</th>
</tr>
{% for item in res: %}
<tr>
<td> <input type="hidden" name="invnomer" value="{{ item[0] }}"> {{ item[0] }} </td>
<td> {{ item[1] }} </td>
<td>
{{item[2]}}
</td>
</td>
</tr>
{% endfor %}
</table>
</form>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@@ -1,100 +0,0 @@
{% extends 'base.html' %}
{% block content %}
<!-- Modal -->
<div class="modal fade" id="getmodal" tabindex="-1" role="dialog" aria-labelledby="exampleModalCenterTitle"
aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-body" id="textarea">
<input type="text" class="form-control" id='vednumber' placeholder="Номер из веломости">
<input type="text" class="form-control" id='rapolog' placeholder="Введите расположение">
<div class="row">
<a id="modal_invnom"> </a><a id="modal_matcenn"></a>
</div>
<form method="POST" action="/addraspved">
<div class="row">
№ из ведомости
<input type="text" class="form-control" name="modal_vednumber" id='modal_vednumber'
placeholder="Номер из ведомости">
</div>
<div class="row">
Расположение
<input type="text" class="form-control" name="modal_rapolog" id='modal_rapolog'
placeholder="Введите расположение">
</div>
</form>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal" id="modalclose">Закрыть
</button>
<button type="button" class="btn btn-primary" id="modalsavetodb">Сохранить изменения</button>
</div>
</div>
</div>
</div>
</div>
<div class="row no-print">
<div class="card col-md-10 col-10">
<div class="card-body">
<select name="auditory" id="auditory">
{% for item in aud: %}
<option name="optauditory" value="{{ item.id }}">{{ item.audnazvanie }}</option>
{% endfor %}
</select>
<button id="searchbutton"> Найти</button>
<button id="printbutton"> Печать</button>
</div>
</div>
</div>
<div class="row">
<div class="card col-md-10 col-10">
<div class="card-body">
<h3 class="card-title no-print"> Поаудиторно </h3>
<table class="table " id="datatable">
<th>Номер в Инв. вед</th>
<th>Инв. номер</th>
<th>Название</th>
<th class="no-print">Аудитория</th>
<th>Расположение</th>
{% for item in res %}
<td><input type="hidden" name="invnomer" value="{{ item[0] }}"> {{ item[0] }} </td>
<td> {{ item[1] }} </td>
<td class="no-print"> {{ item[2] }} </td>
<td id="proverka"> Проверено</td>
{% endfor %}
</table>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,34 +0,0 @@
{% extends 'base.html' %}
{% block content %}
<div class="row">
<div class="card col-md-10 col-10">
<div class="card-body">
<form>
<h3 class="card-title"> Не распределено {{ kolvo }} штук из {{ all_kol }} </h3>
<table class="table" col-md-10>
<thead>
<tr>
<th scope="col">Инв. номер</th>
<th scope="col">Название</th>
</tr>
{% for item in res: %}
<tr>
<td> <input type="hidden" name="invnomer" value="{{ item[0] }}"> {{ item[0] }} </td>
<td> {{ item[1] }} </td>
<td> {{ item[2] }} </td>
<td> {{ item[3] }} </td>
</tr>
{% endfor %}
</table>
</form>
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,39 +0,0 @@
{% extends 'base.html' %}
{% block content %}
<div class="row">
<div class="card col-md-8 col-10">
<div class="card-body">
<h3 class="card-title"> Добавление заметки </h3>
<form method="POST" action="/zametki">
<textarea id="textzam" name="textzam" class="col-6"></textarea>
<div class="row">
<button> Добавить </button>
</div>
</form>
</div>
</div>
</div>
<div id="zambody" class="container col-12">
{% for item in zam: %}
<div class="row" id="{{ item.id }}">
<div class="card col-md-6 col-10">
<div class="card-body">
{{ item.txtzam }}
</div>
{{ item.created_date }}
<div class="row">
<button id="{{ item.id }}" class="reshbtn col-4"> Решено </button>
</div>
</div>
</div>
{% endfor %}
</div>
{%endblock%}