49 Commits

Author SHA1 Message Date
Danamir
e428e7f762 add new ui 2025-11-10 11:28:49 +03:00
Danamir
3f91dc91ec feat(frontend): Vue (CDN) UI matching templates design; reuse /static CSS and Bootstrap; mount /static in FastAPI 2025-11-10 08:45:25 +03:00
Danamir
779c256e7b feat(frontend): mount static UI at /app with simple auditories/equipment browser 2025-11-10 08:40:46 +03:00
Danamir
d686b26465 update requirements 2025-11-10 08:37:23 +03:00
Danamir
08e979ecb2 uppdate 2025-11-10 08:35:07 +03:00
Danamir
72f1d53051 Resolve .gitignore merge conflict; consolidate ignore rules 2025-11-10 08:30:45 +03:00
Danamir
b1e0693131 fix run 2025-11-10 08:25:56 +03:00
Danamir
86713fc75f uncommited changes 2025-11-10 08:11:46 +03:00
Danamir
c24a1fa8c6 Last coommit before refactoring 2025-08-04 10:33:21 +03:00
Danamir
f108e013c2 fix branch 2025-08-04 10:23:40 +03:00
1c33775f92 fixed table 2024-05-22 15:58:16 +03:00
43ab114e1a fixed table 2024-05-22 13:42:52 +03:00
946ad5c31f add sorting func 2024-05-18 20:58:55 +03:00
Your Name
7b956d89bf merge head by alembic 2024-05-06 08:29:58 -04:00
0891abc0e1 add fumc for login 2024-05-06 15:23:24 +03:00
08393f6685 Merge branch 'main' of https://git.danamir.su/danamir/asuinventory 2024-05-06 15:15:23 +03:00
9cde4e2c7d add readexcell func 2024-05-06 15:14:47 +03:00
Your Name
0a60a16344 bug fix 2024-04-09 23:52:19 +03:00
Your Name
496ef3fa9d bug fix 2024-04-09 23:51:48 +03:00
Your Name
3494e5d17c Merge branch 'main' of https://git.danamir.su/danamir/asuinventory 2024-04-08 10:49:58 +03:00
Your Name
29b0070260 add reload page 2024-04-08 10:47:54 +03:00
Your Name
2e9cc918d5 update db schema 2024-04-05 01:26:46 +03:00
Your Name
d943894ae8 all done2 2024-04-04 22:55:38 +03:00
Your Name
1c901ddb00 all done 2024-04-04 22:51:45 +03:00
Your Name
f9a188c927 add data to db 2024-04-04 20:00:26 +03:00
Your Name
1fce4b41c0 add modal fucnc 2024-04-04 19:31:07 +03:00
Your Name
7ccbba06c1 add click on row 2024-04-04 12:48:43 +03:00
Your Name
63115f0328 asdsadsa 2024-04-03 23:28:55 +03:00
cb862c63a6 add new func 2024-04-03 22:50:16 +03:00
927d8d75a9 edit db scheme 2024-04-03 22:37:57 +03:00
509a3ee913 edit gitignore 2024-04-03 22:31:08 +03:00
86f0f9d977 db check 2024-04-03 22:24:02 +03:00
0c4bf4b9fd 12333 2024-04-03 20:05:36 +03:00
d71945ecb2 11111 2024-04-03 20:02:05 +03:00
3cd5fe63b5 12311 2024-04-03 20:01:50 +03:00
0c6001b297 Resolve merge conflict by incorporating both suggestions 2024-04-03 19:58:22 +03:00
682b6c4ffd 1112 2024-04-03 19:57:28 +03:00
9c859d4660 111 2024-04-03 19:55:02 +03:00
Your Name
4c6166c907 add to model 2024-04-02 23:09:27 +03:00
Your Name
2c51bdc695 Merge branch 'dev' 2024-04-02 23:07:44 +03:00
87c4ebe33e db schema update 2024-04-02 16:34:06 +03:00
Your Name
bf93bf0fdb local changes 2024-04-01 22:37:14 +03:00
Your Name
027b1dc855 1111 2024-04-01 22:35:26 +03:00
4a47746e9d merge 2024-04-01 15:21:43 +03:00
12a63278f7 Merge branch 'dev' 2024-04-01 15:17:53 +03:00
Your Name
965695a693 add raspologenie and vde numbers 2024-03-31 23:33:26 +03:00
Your Name
b47e9391ba add bug 2024-03-27 09:45:57 +03:00
Your Name
5259dc5292 sss 2024-03-26 23:03:26 +03:00
Your Name
a6eea28071 add funce 2024-03-26 23:02:37 +03:00
55 changed files with 2469 additions and 216 deletions

37
.gitignore vendored
View File

@@ -1,28 +1,21 @@
*.csv
.vscode
instance
.idea
venv/
instance/
__pychache__/
123
zabalans.csv
project.db
# Ignore Python bytecode caches everywhere
__pycache__/
**/__pycache__/
# Migrations and DB files
migrations/
*.db
# Data and temp files
*.csv
c*.txt
__pycache__/app.cpython-312.pyc
__pycache__/models.cpython-312.pyc
migrations/__pycache__/
migrations/versions/__pycache__/ec6bbcd361bd_.cpython-312.pyc
migrations/versions/__pycache__/ec6bbcd361bd_.cpython-312.pyc
migrations/versions/__pycache__/ec6bbcd361bd_.cpython-312.pyc
migrations/versions/__pycache__/be7c94c549e5_.cpython-312.pyc
migrations/versions/__pycache__/b2a61aef79e9_.cpython-312.pyc
migrations/versions/__pycache__/873defe09f22_.cpython-312.pyc
migrations/versions/__pycache__/256c3a3e91a2_.cpython-312.pyc
migrations/versions/__pycache__/50f85881169e_.cpython-312.pyc
migrations/versions/__pycache__/8e838956713f_.cpython-312.pyc
migrations/versions/__pycache__/4f95d12a8352_.cpython-312.pyc
migrations/__pycache__/env.cpython-312.pyc
# Legacy specific ignores (if present)
backend/venv
backend/__pycache__
backend/routeres/__pycache__

BIN
VedomostMOL.xls Normal file

Binary file not shown.

BIN
VedomostMOL.xlsx Normal file

Binary file not shown.

154
app.py
View File

@@ -8,7 +8,6 @@ import csv
import random
from urllib.parse import unquote
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///project.db"
@@ -22,7 +21,13 @@ db.init_app(app)
migrate = Migrate(app, db)
@app.route("/", methods=['GET', 'POST'])
@app.route("/login", methods=['GET', 'POST'])
def login():
return render_template('login.html')
@app.route("/", methods=['GET', 'POST'])
def index():
results = []
results1 = []
@@ -46,7 +51,7 @@ def index():
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)
return render_template('index.html', aud=all_aud, results=results, res1=results1)
@@ -91,7 +96,7 @@ def searchonaud():
return render_template('searchonaud.html', aud=all_aud, res=res)
@app.route("/addaudtodb", methods=['GET', 'POST'])
@app.route("/addaudtodb", methods=['GET', 'POST'])
def addaudtodb():
if request.method == 'POST':
aud = request.form.get('auditory')
@@ -119,26 +124,33 @@ def alloborud():
@app.route('/getall')
def getall():
oboruds = Oboruds.query.all()
oboruds_json = [{
'id': oborud.id,
'invNumber': oborud.invNumber,
'nazvanie': oborud.nazvanie,
'raspologenie': oborud.raspologenie,
'numberved': oborud.numberved,
'balancenumber': oborud.balancenumber,
'kolichestvo': oborud.kolichestvo,
'balancenumber': oborud.balancenumber,
'aud_id': oborud.aud_id
} for oborud in oboruds]
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(oboruds_json)
@app.route('/updateduplicate', methods=['GET', 'POST'])
def updateduplicate():
if request.method == 'POST':
aud = request.form.get('auditory_dubl')
return jsonify(results)
@app.route('/vneaud', methods=['GET', 'POST'])
@@ -149,12 +161,12 @@ def vneaud():
ak = db.session.query(Oboruds).all()
for dt in data:
res.append([dt.invNumber, dt.nazvanie, dt.typeBalanse])
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'])
@app.route('/zametki', methods=['GET', 'POST'])
def zametki():
zam = db.session.query(Zametki).filter(Zametki.rmdt == None).all()
if request.method == 'POST':
@@ -177,7 +189,7 @@ def js2():
return jsonify({'success': True}), 200, {'ContentType': 'application/json'}
@app.route('/zamsearch', methods=['GET', 'POST'])
@app.route('/zamsearch', methods=['GET', 'POST'])
def zamsearch():
p = request.form.get('srch')
@@ -185,42 +197,61 @@ def zamsearch():
Zametki.txtzam.contains(p)).ll()
zam = []
for item in searchedZam:
zam.append([item.txtzam, item.created_date])
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()
print(query_string)
un_query_string = unquote(unquote(query_string)).split(',')
print(un_query_string)
ob = db.session.query(Oboruds).filter_by(invNumber=un_query_string[0]).first()
ob.raspologenie=un_query_string[2]
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 == None).all()) > 0:
audid = random.choice(db.session.query(Auditory).all())
oborud = random.choice(db.session.query(Oboruds).filter(Oboruds.aud_id == None).all())
oborud.aud_id = audid.id
db.session.commit()
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():
@@ -266,9 +297,48 @@ def createdb():
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()
#ranomraspr()
#createdb()
app.run(debug=True, host='0.0.0.0', port='3800')
app.run(debug=True, host='0.0.0.0', port='3800')

2
backend/__init__.py Normal file
View File

@@ -0,0 +1,2 @@
"""Backend package initializer."""

26
backend/create_new_db.py Normal file
View File

@@ -0,0 +1,26 @@
from sqlalchemy.orm import sessionmaker
from backend.models import Base, User
from backend.database import engine
from backend.security import get_password_hash
def recreate_db_with_admin():
# Drop and recreate all tables
Base.metadata.drop_all(bind=engine)
Base.metadata.create_all(bind=engine)
# Seed default admin user (username: admin, password: admin)
Session = sessionmaker(bind=engine)
db = Session()
try:
if not db.query(User).filter(User.username == 'admin').first():
db.add(User(username='admin', password_hash=get_password_hash('admin'), role='admin'))
db.commit()
finally:
db.close()
if __name__ == "__main__":
recreate_db_with_admin()

15
backend/database.py Normal file
View File

@@ -0,0 +1,15 @@
# backend/database.py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, scoped_session
SQLALCHEMY_DATABASE_URL = "sqlite:///./app.db" # или PostgreSQL URL
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = scoped_session(sessionmaker(autocommit=False, autoflush=False, bind=engine))
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()

56
backend/main.py Normal file
View File

@@ -0,0 +1,56 @@
# backend/main.py
from fastapi import FastAPI
from fastapi.responses import RedirectResponse
from starlette.staticfiles import StaticFiles
from fastapi.middleware.cors import CORSMiddleware
from backend.routers.equipment_types import equipment_types
from backend.routers.auditories import auditories
from backend.routers.oboruds import oboruds
from backend.routers.components import components
from backend.routers.rashodniki import consumables
from backend.routers.zametki import zametki
from backend.routers.auth import auth
from backend.routers.owners import owners
app = FastAPI()
# Для фронтенда Vue.js
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # заменить на ['http://localhost:5173'] для безопасности
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/ping")
def ping():
return {"message": "pong"}
# Serve static assets and frontend
app.mount("/static", StaticFiles(directory="static"), name="static")
app.mount("/app", StaticFiles(directory="frontend", html=True), name="frontend")
@app.get("/")
def root():
return RedirectResponse(url="/app/")
@app.get("/login")
def login_page():
return RedirectResponse(url="/app/login.html")
# Подключение роутов
app.include_router(equipment_types)
app.include_router(auditories)
app.include_router(oboruds)
app.include_router(components)
app.include_router(consumables)
app.include_router(zametki)
app.include_router(auth)
app.include_router(owners)

89
backend/migrate_data.py Normal file
View File

@@ -0,0 +1,89 @@
import sys
from sqlalchemy import create_engine, text, inspect
from sqlalchemy.orm import sessionmaker
from backend.database import SessionLocal as NewSession, engine as new_engine
from backend import models
from pathlib import Path
OLD_DB_URL = "sqlite:///./instance/project.-10-11-25.db"
old_engine = create_engine(OLD_DB_URL, connect_args={"check_same_thread": False})
OldSession = sessionmaker(bind=old_engine)
old_db = OldSession()
new_db = NewSession()
def log(msg: str):
print(f"[INFO] {msg}", file=sys.stderr)
def ensure_schema():
"""Ensure new DB has required tables/columns (owners table and oboruds.owner_id)."""
# Create any missing tables defined in models (e.g., owners)
models.Base.metadata.create_all(bind=new_engine)
# If oboruds.owner_id is missing (older DB), add the column (SQLite allows simple ALTER)
try:
inspector = inspect(new_engine)
cols = [c["name"] for c in inspector.get_columns("oboruds")]
if "owner_id" not in cols:
with new_engine.begin() as conn:
conn.execute(text("ALTER TABLE oboruds ADD COLUMN owner_id INTEGER"))
log("Добавлен столбец oboruds.owner_id")
except Exception as e:
log(f"Предупреждение: проверка/добавление owner_id не выполнена: {e}")
def migrate():
ensure_schema()
log("Запуск переноса данных из old_app.db → app.db")
# Тип оборудования по умолчанию
log("Добавление типа оборудования по умолчанию: 'Неизвестно'")
default_type = models.EquipmentType(name="Неизвестно")
new_db.add(default_type)
new_db.commit()
# Перенос аудиторий
log("Перенос аудиторий...")
auditory_map = {}
aud_rows = old_db.execute(text("SELECT id, audnazvanie FROM auditory")).fetchall()
for aud in aud_rows:
new_aud = models.Auditory(audnazvanie=aud.audnazvanie)
new_db.add(new_aud)
new_db.flush()
auditory_map[aud.id] = new_aud.id
log(f" → перенесено: {len(aud_rows)}")
# Перенос оборудования
log("Перенос оборудования...")
ob_rows = old_db.execute(text("SELECT * FROM oboruds")).fetchall()
for ob in ob_rows:
new_ob = models.Oboruds(
invNumber=ob.invNumber,
nazvanie=ob.nazvanie,
raspologenie=ob.raspologenie,
numberppasu=ob.numberppasu,
kolichestvo=ob.kolichestvo,
aud_id=auditory_map.get(ob.aud_id),
type_id=default_type.id
)
new_db.add(new_ob)
log(f" → перенесено: {len(ob_rows)}")
# Перенос заметок
log("Перенос заметок...")
z_rows = old_db.execute(text("SELECT * FROM zametki")).fetchall()
for z in z_rows:
new_z = models.Zametki(
txtzam=z.txtzam,
created_date=z.created_date,
rmdt=z.rmdt
)
new_db.add(new_z)
log(f" → перенесено: {len(z_rows)}")
new_db.commit()
log("✅ Перенос завершён успешно.")
if __name__ == "__main__":
migrate()

100
backend/models.py Normal file
View File

@@ -0,0 +1,100 @@
# backend/models.py
from sqlalchemy import Column, Integer, String, ForeignKey, DateTime
from sqlalchemy.orm import relationship, declarative_base
import datetime
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
username = Column(String(150), unique=True, nullable=False)
password_hash = Column(String(255), nullable=False)
role = Column(String(50), nullable=False, default='viewer') # 'admin' | 'editor' | 'viewer'
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 Owner(Base):
__tablename__ = 'owners'
id = Column(Integer, primary_key=True)
name = Column(String, unique=True, nullable=False)
oboruds = relationship("Oboruds", back_populates="owner")
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")
owner_id = Column(Integer, ForeignKey("owners.id"))
owner = relationship("Owner", 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)

BIN
backend/requirements.txt Normal file

Binary file not shown.

View File

@@ -0,0 +1,2 @@
"""Routers package initializer."""

View File

@@ -0,0 +1,19 @@
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from .. import models, schemas, database
from ..security import require_roles
auditories = APIRouter(prefix="/auditories", tags=["auditories"])
@auditories.post("/", response_model=schemas.AuditoryRead, dependencies=[Depends(require_roles(["admin", "editor"]))])
def create_auditory(item: schemas.AuditoryCreate, db: Session = Depends(database.get_db)):
obj = models.Auditory(**item.dict())
db.add(obj)
db.commit()
db.refresh(obj)
return obj
@auditories.get("/", response_model=list[schemas.AuditoryRead])
def list_auditories(db: Session = Depends(database.get_db)):
return db.query(models.Auditory).all()

63
backend/routers/auth.py Normal file
View File

@@ -0,0 +1,63 @@
from datetime import timedelta
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
from sqlalchemy.orm import Session
from .. import schemas, database
from ..security import authenticate_user, create_access_token, get_password_hash, require_roles
from ..models import User
auth = APIRouter(prefix="/auth", tags=["auth"])
@auth.post("/token", response_model=schemas.Token)
def login_for_access_token(
form_data: OAuth2PasswordRequestForm = Depends(),
db: Session = Depends(database.get_db),
):
user = authenticate_user(db, form_data.username, form_data.password)
if not user:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect username or password")
access_token_expires = timedelta(minutes=60)
access_token = create_access_token(data={"sub": user.username, "role": user.role}, expires_delta=access_token_expires)
return {"access_token": access_token, "token_type": "bearer"}
@auth.post("/users", response_model=schemas.UserRead, dependencies=[Depends(require_roles(["admin"]))])
def create_user(item: schemas.UserCreate, db: Session = Depends(database.get_db)):
if db.query(User).filter(User.username == item.username).first():
raise HTTPException(status_code=400, detail="Username already exists")
obj = User(username=item.username, password_hash=get_password_hash(item.password), role=item.role)
db.add(obj)
db.commit()
db.refresh(obj)
return obj
@auth.post("/users/admin", response_model=schemas.UserRead, dependencies=[Depends(require_roles(["admin"]))])
def create_admin_user(item: schemas.UserCreate, db: Session = Depends(database.get_db)):
if db.query(User).filter(User.username == item.username).first():
raise HTTPException(status_code=400, detail="Username already exists")
obj = User(username=item.username, password_hash=get_password_hash(item.password), role="admin")
db.add(obj)
db.commit()
db.refresh(obj)
return obj
@auth.get("/users", response_model=list[schemas.UserRead], dependencies=[Depends(require_roles(["admin"]))])
def list_users(db: Session = Depends(database.get_db)):
return db.query(User).all()
@auth.patch("/users/{user_id}/role", response_model=schemas.UserRead, dependencies=[Depends(require_roles(["admin"]))])
def update_user_role(user_id: int, payload: schemas.UserRoleUpdate, db: Session = Depends(database.get_db)):
user = db.query(User).filter(User.id == user_id).first()
if not user:
raise HTTPException(status_code=404, detail="User not found")
user.role = payload.role
db.commit()
db.refresh(user)
return user

View File

@@ -0,0 +1,19 @@
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from .. import models, schemas, database
from ..security import require_roles
components = APIRouter(prefix="/components", tags=["components"])
@components.post("/", response_model=schemas.ComponentRead, dependencies=[Depends(require_roles(["admin", "editor"]))])
def create_component(item: schemas.ComponentCreate, db: Session = Depends(database.get_db)):
obj = models.Component(**item.dict())
db.add(obj)
db.commit()
db.refresh(obj)
return obj
@components.get("/", response_model=list[schemas.ComponentRead])
def list_components(db: Session = Depends(database.get_db)):
return db.query(models.Component).all()

View File

@@ -0,0 +1,18 @@
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from .. import models, schemas, database
from ..security import require_roles
equipment_types = APIRouter(prefix="/equipment-types", tags=["equipment_types"])
@equipment_types.post("/", response_model=schemas.EquipmentTypeRead, dependencies=[Depends(require_roles(["admin", "editor"]))])
def create_equipment_type(item: schemas.EquipmentTypeCreate, db: Session = Depends(database.get_db)):
obj = models.EquipmentType(**item.dict())
db.add(obj)
db.commit()
db.refresh(obj)
return obj
@equipment_types.get("/", response_model=list[schemas.EquipmentTypeRead])
def list_equipment_types(db: Session = Depends(database.get_db)):
return db.query(models.EquipmentType).all()

View File

@@ -0,0 +1,42 @@
from fastapi import APIRouter, Depends, HTTPException
from typing import Optional
from sqlalchemy.orm import Session
from .. import models, schemas, database
from ..security import require_roles
oboruds = APIRouter(prefix="/oboruds", tags=["oboruds"])
@oboruds.post("/", response_model=schemas.OborudRead, dependencies=[Depends(require_roles(["admin", "editor"]))])
def create_oborud(item: schemas.OborudCreate, db: Session = Depends(database.get_db)):
obj = models.Oboruds(**item.dict())
db.add(obj)
db.commit()
db.refresh(obj)
return obj
@oboruds.get("/", response_model=list[schemas.OborudRead])
def list_oboruds(aud_id: Optional[int] = None, db: Session = Depends(database.get_db)):
query = db.query(models.Oboruds)
if aud_id is not None:
query = query.filter(models.Oboruds.aud_id == aud_id)
return query.all()
@oboruds.get("/{oborud_id}", response_model=schemas.OborudRead)
def get_oborud(oborud_id: int, db: Session = Depends(database.get_db)):
obj = db.query(models.Oboruds).filter(models.Oboruds.id == oborud_id).first()
if not obj:
raise HTTPException(status_code=404, detail="Oborud not found")
return obj
@oboruds.patch("/{oborud_id}", response_model=schemas.OborudRead, dependencies=[Depends(require_roles(["admin", "editor"]))])
def update_oborud(oborud_id: int, payload: schemas.OborudUpdate, db: Session = Depends(database.get_db)):
obj = db.query(models.Oboruds).filter(models.Oboruds.id == oborud_id).first()
if not obj:
raise HTTPException(status_code=404, detail="Oborud not found")
data = payload.dict(exclude_unset=True)
for k, v in data.items():
setattr(obj, k, v)
db.commit()
db.refresh(obj)
return obj

22
backend/routers/owners.py Normal file
View File

@@ -0,0 +1,22 @@
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from .. import models, schemas, database
from ..security import require_roles
owners = APIRouter(prefix="/owners", tags=["owners"])
@owners.post("/", response_model=schemas.OwnerRead, dependencies=[Depends(require_roles(["admin", "editor"]))])
def create_owner(item: schemas.OwnerCreate, db: Session = Depends(database.get_db)):
obj = models.Owner(**item.dict())
db.add(obj)
db.commit()
db.refresh(obj)
return obj
@owners.get("/", response_model=list[schemas.OwnerRead])
def list_owners(db: Session = Depends(database.get_db)):
return db.query(models.Owner).all()

View File

@@ -0,0 +1,18 @@
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from .. import models, schemas, database
from ..security import require_roles
consumables = APIRouter(prefix="/consumables", tags=["consumables"])
@consumables.post("/", response_model=schemas.ConsumableRead, dependencies=[Depends(require_roles(["admin", "editor"]))])
def create_consumable(item: schemas.ConsumableCreate, db: Session = Depends(database.get_db)):
obj = models.Consumable(**item.dict())
db.add(obj)
db.commit()
db.refresh(obj)
return obj
@consumables.get("/", response_model=list[schemas.ConsumableRead])
def list_consumables(db: Session = Depends(database.get_db)):
return db.query(models.Consumable).all()

View File

@@ -0,0 +1,18 @@
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from .. import models, schemas, database
from ..security import require_roles
zametki = APIRouter(prefix="/zametki", tags=["zametki"])
@zametki.post("/", response_model=schemas.ZametkaRead, dependencies=[Depends(require_roles(["admin", "editor"]))])
def create_zametka(item: schemas.ZametkaCreate, db: Session = Depends(database.get_db)):
obj = models.Zametki(**item.dict())
db.add(obj)
db.commit()
db.refresh(obj)
return obj
@zametki.get("/", response_model=list[schemas.ZametkaRead])
def list_zametki(db: Session = Depends(database.get_db)):
return db.query(models.Zametki).all()

158
backend/schemas.py Normal file
View File

@@ -0,0 +1,158 @@
# backend/schemas.py
from pydantic import BaseModel
from typing import Optional, List, Literal
from datetime import datetime
# === Equipment Type ===
class EquipmentTypeBase(BaseModel):
name: str
class EquipmentTypeCreate(EquipmentTypeBase):
pass
class EquipmentTypeRead(EquipmentTypeBase):
id: int
class Config:
orm_mode = True
# === Component ===
class ComponentBase(BaseModel):
name: str
description: Optional[str] = None
class ComponentCreate(ComponentBase):
oborud_id: int
class ComponentRead(ComponentBase):
id: int
class Config:
orm_mode = True
# === Consumable ===
class ConsumableBase(BaseModel):
name: str
description: Optional[str] = None
class ConsumableCreate(ConsumableBase):
oborud_id: int
class ConsumableRead(ConsumableBase):
id: int
class Config:
orm_mode = True
# === Owner ===
class OwnerBase(BaseModel):
name: str
class OwnerCreate(OwnerBase):
pass
class OwnerRead(OwnerBase):
id: int
class Config:
orm_mode = True
# === Oborud ===
class OborudBase(BaseModel):
invNumber: Optional[int]
nazvanie: str
raspologenie: Optional[str] = None
numberppasu: Optional[str] = None
kolichestvo: Optional[int] = None
aud_id: int
type_id: int
owner_id: Optional[int] = None
class OborudCreate(OborudBase):
pass
class OborudUpdate(BaseModel):
invNumber: Optional[int] = None
nazvanie: Optional[str] = None
raspologenie: Optional[str] = None
numberppasu: Optional[str] = None
kolichestvo: Optional[int] = None
aud_id: Optional[int] = None
type_id: Optional[int] = None
owner_id: Optional[int] = None
class OborudRead(OborudBase):
id: int
type: EquipmentTypeRead
owner: Optional[OwnerRead] = None
components: List[ComponentRead] = []
consumables: List[ConsumableRead] = []
class Config:
orm_mode = True
# === Auditory ===
class AuditoryBase(BaseModel):
audnazvanie: str
class AuditoryCreate(AuditoryBase):
pass
class AuditoryRead(AuditoryBase):
id: int
class Config:
orm_mode = True
# === Zametka ===
class ZametkaBase(BaseModel):
txtzam: str
rmdt: Optional[datetime] = None
class ZametkaCreate(ZametkaBase):
pass
class ZametkaRead(ZametkaBase):
id: int
created_date: datetime
class Config:
orm_mode = True
# === Auth/User ===
class Token(BaseModel):
access_token: str
token_type: str = "bearer"
class TokenData(BaseModel):
username: Optional[str] = None
role: Optional[str] = None
Role = Literal["admin", "editor", "viewer"]
class UserBase(BaseModel):
username: str
role: Role = "viewer"
class UserCreate(UserBase):
password: str
class UserRead(UserBase):
id: int
class Config:
orm_mode = True
class UserRoleUpdate(BaseModel):
role: Role

79
backend/security.py Normal file
View File

@@ -0,0 +1,79 @@
import os
from datetime import datetime, timedelta, timezone
from typing import Optional, Callable, Iterable
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
from passlib.context import CryptContext
from sqlalchemy.orm import Session
from . import models
from .database import get_db
SECRET_KEY = os.getenv("SECRET_KEY", "dev-secret-change-me")
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", "60"))
# Use pbkdf2_sha256 to avoid external bcrypt backend issues
pwd_context = CryptContext(schemes=["pbkdf2_sha256"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/token")
def verify_password(plain_password: str, password_hash: str) -> bool:
return pwd_context.verify(plain_password, password_hash)
def get_password_hash(password: str) -> str:
return pwd_context.hash(password)
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
to_encode = data.copy()
expire = datetime.now(timezone.utc) + (expires_delta or timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES))
to_encode.update({"exp": expire})
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
def get_user_by_username(db: Session, username: str) -> Optional[models.User]:
return db.query(models.User).filter(models.User.username == username).first()
def authenticate_user(db: Session, username: str, password: str) -> Optional[models.User]:
user = get_user_by_username(db, username)
if not user:
return None
if not verify_password(password, user.password_hash):
return None
return user
async def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)) -> models.User:
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
user = get_user_by_username(db, username=username)
if user is None:
raise credentials_exception
return user
def require_roles(allowed_roles: Iterable[str]) -> Callable[[models.User], models.User]:
allowed = set(allowed_roles)
def _dependency(user: models.User = Depends(get_current_user)) -> models.User:
if user.role not in allowed:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Insufficient permissions")
return user
return _dependency

221
frontend/app.js Normal file
View File

@@ -0,0 +1,221 @@
const { createApp } = Vue;
const api = {
auds: "/auditories/",
oboruds: (audId) => `/oboruds/?aud_id=${encodeURIComponent(audId)}`,
owners: "/owners/",
};
async function fetchJSON(url) {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}
createApp({
data() {
return {
view: 'byAud',
auditories: [],
selectedAudId: '',
oboruds: [],
status: '',
error: '',
// auth/user management
token: '',
role: '',
users: [],
newAdminUsername: '',
newAdminPassword: '',
newAudName: '',
owners: [],
newOwnerName: '',
};
},
computed: {
isAuth() { return !!this.token; },
isAdmin() { return this.role === 'admin'; },
isEditor() { return this.role === 'editor'; },
canEdit() { return this.isAdmin || this.isEditor; },
},
methods: {
logout() {
try {
localStorage.removeItem('access_token');
localStorage.removeItem('role');
} catch {}
this.token = '';
this.role = '';
this.users = [];
this.status = '';
this.error = '';
this.view = 'byAud';
// опционально: редирект на страницу логина
// window.location.href = '/login';
},
authHeaders() {
const h = {};
if (this.token) h['Authorization'] = `Bearer ${this.token}`;
return h;
},
async fetchAuth(url, options = {}) {
const opt = { ...options, headers: { ...(options.headers||{}), ...this.authHeaders() } };
const res = await fetch(url, opt);
if (!res.ok) {
const text = await res.text();
throw new Error(`HTTP ${res.status}: ${text}`);
}
return res.json();
},
async loadAuditories() {
this.status = 'Загрузка аудиторий…';
this.error = '';
try {
this.auditories = await fetchJSON(api.auds);
this.status = '';
} catch (e) {
console.error(e);
this.error = 'Не удалось загрузить аудитории';
this.status = '';
}
},
async loadOboruds() {
if (!this.selectedAudId) {
this.error = '';
this.status = 'Выберите аудиторию';
return;
}
this.status = 'Загрузка оборудования…';
this.error = '';
try {
this.oboruds = await fetchJSON(api.oboruds(this.selectedAudId));
// init selected owner helper field
this.oboruds.forEach(o => { o.selectedOwnerId = o.owner?.id || ''; });
this.status = '';
} catch (e) {
console.error(e);
this.error = 'Не удалось загрузить оборудование';
this.status = '';
}
},
async saveOwner(item) {
try {
this.status = 'Сохранение владельца…';
await this.fetchAuth(`/oboruds/${item.id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ owner_id: item.selectedOwnerId || null }),
});
this.status = 'Сохранено';
} catch (e) {
console.error(e);
this.error = 'Не удалось сохранить владельца';
this.status = '';
}
},
async loadOwners() {
try {
this.status = this.status || 'Загрузка владельцев…';
const data = await fetchJSON(api.owners);
this.owners = data;
this.status = '';
} catch (e) {
console.error(e);
this.error = 'Не удалось загрузить владельцев';
this.status = '';
}
},
async createOwner() {
if (!this.newOwnerName) {
this.status = 'Укажите имя владельца';
return;
}
try {
this.status = 'Добавление владельца…';
await this.fetchAuth(api.owners, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: this.newOwnerName }),
});
this.newOwnerName = '';
await this.loadOwners();
// refresh table owner names if visible
if (this.selectedAudId) await this.loadOboruds();
this.status = 'Владелец добавлен';
} catch (e) {
console.error(e);
this.error = 'Не удалось добавить владельца';
this.status = '';
}
},
async loadUsers() {
try {
this.status = 'Загрузка пользователей…';
this.error = '';
this.users = await this.fetchAuth('/auth/users');
this.status = '';
} catch (e) {
console.error(e);
this.error = 'Не удалось загрузить пользователей';
this.status = '';
}
},
async createAdmin() {
if (!this.newAdminUsername || !this.newAdminPassword) {
this.status = 'Укажите логин и пароль';
return;
}
try {
this.status = 'Создание администратора…';
this.error = '';
await this.fetchAuth('/auth/users/admin', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: this.newAdminUsername, password: this.newAdminPassword }),
});
this.newAdminUsername = '';
this.newAdminPassword = '';
await this.loadUsers();
this.status = 'Администратор создан';
} catch (e) {
console.error(e);
this.error = 'Не удалось создать администратора';
this.status = '';
}
},
async createAuditory() {
if (!this.newAudName) {
this.status = 'Укажите название аудитории';
return;
}
try {
this.status = 'Добавление аудитории…';
this.error = '';
await this.fetchAuth('/auditories/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ audnazvanie: this.newAudName }),
});
this.newAudName = '';
await this.loadAuditories();
this.status = 'Аудитория добавлена';
} catch (e) {
console.error(e);
this.error = 'Не удалось добавить аудиторию';
this.status = '';
}
}
},
mounted() {
// read auth from localStorage
try {
this.token = localStorage.getItem('access_token') || '';
this.role = localStorage.getItem('role') || '';
} catch {}
this.loadAuditories();
this.loadOwners();
if (this.isAdmin) {
this.loadUsers();
}
}
}).mount('#app');

229
frontend/index.html Normal file
View File

@@ -0,0 +1,229 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>АСУ Инвентаризация</title>
<link rel="stylesheet" href="/static/css/bootstrap.min.css" />
<link rel="stylesheet" href="/static/css/index.css" />
</head>
<body>
<header>
<h1>
<a href="/app/">АСУ Инвентаризация</a>
</h1>
<h2>Учет оборудования. Демоверсия</h2>
</header>
<div id="app">
<div class="row no-print">
<nav class="no-print navbar navbar-expand-lg navbar-light">
<div class="container-fluid">
<a class="navbar-brand" href="/app/">Главная</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<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" 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"><a class="nav-link" href="/docs" target="_blank">API Docs</a></li>
<li class="nav-item" v-if="canEdit"><a class="nav-link" href="#" @click.prevent="view='owners'">Владельцы</a></li>
</ul>
<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>
<button class="btn btn-outline-secondary ms-2" v-else @click.prevent="logout">Выйти</button>
</div>
</div>
</nav>
</div>
<div class="container">
<div v-if="view==='byAud'" class="row">
<div class="card col-md-10 col-10">
<div class="card-body">
<h3 class="card-title">Оборудование по аудитории</h3>
<div class="mb-2 d-flex align-items-center gap-2">
<label for="aud-select" class="me-2">Аудитория:</label>
<select id="aud-select" class="form-select w-auto" v-model="selectedAudId">
<option value="">— выберите аудиторию —</option>
<option v-for="a in auditories" :key="a.id" :value="a.id">{{ a.audnazvanie }}</option>
</select>
<button class="btn btn-primary" @click="loadOboruds">Показать</button>
</div>
<div class="status" :class="{error: !!error}">{{ status }}</div>
<div class="table-responsive">
<table class="table datatable">
<thead>
<tr>
<th scope="col">ID</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>
<tbody>
<tr v-for="it in oboruds" :key="it.id">
<td>{{ it.id }}</td>
<td class="inv">{{ it.invNumber ?? '' }}</td>
<td>{{ it.nazvanie ?? '' }}</td>
<td class="rasp">{{ it.raspologenie ?? '' }}</td>
<td>{{ it.kolichestvo ?? '' }}</td>
<td>{{ it.type?.name ?? '' }}</td>
<td>
<template v-if="canEdit">
<select class="form-select form-select-sm d-inline w-auto" v-model="it.selectedOwnerId">
<option value="">— нет —</option>
<option v-for="ow in owners" :key="ow.id" :value="ow.id">{{ ow.name }}</option>
</select>
<button class="btn btn-sm btn-outline-primary ms-2" @click="saveOwner(it)">Сохранить</button>
</template>
<template v-else>
{{ it.owner?.name ?? '' }}
</template>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div v-if="view==='users'" class="row">
<div class="card col-md-10 col-10">
<div class="card-body">
<h3 class="card-title">Администрирование пользователей</h3>
<div v-if="role!=='admin'" class="alert alert-warning">Недостаточно прав. Войдите как администратор.</div>
<div v-else>
<h5 class="mt-2">Создать пользователя-админа</h5>
<div class="row g-2 align-items-end">
<div class="col-auto">
<label class="form-label">Логин</label>
<input class="form-control" v-model="newAdminUsername" placeholder="username" />
</div>
<div class="col-auto">
<label class="form-label">Пароль</label>
<input type="password" class="form-control" v-model="newAdminPassword" placeholder="password" />
</div>
<div class="col-auto">
<button class="btn btn-success" @click="createAdmin">Создать админа</button>
</div>
</div>
<div class="status mt-2" :class="{error: !!error}">{{ status }}</div>
<h5 class="mt-4">Пользователи</h5>
<div class="table-responsive">
<table class="table datatable">
<thead>
<tr>
<th>ID</th>
<th>Логин</th>
<th>Роль</th>
</tr>
</thead>
<tbody>
<tr v-for="u in users" :key="u.id">
<td>{{ u.id }}</td>
<td>{{ u.username }}</td>
<td>{{ u.role }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div v-if="view==='audManage'" class="row">
<div class="card col-md-10 col-10">
<div class="card-body">
<h3 class="card-title">Управление аудиториями</h3>
<div v-if="role!=='admin'" class="alert alert-warning">Недостаточно прав. Войдите как администратор.</div>
<div v-else>
<h5 class="mt-2">Добавить аудиторию</h5>
<div class="row g-2 align-items-end">
<div class="col-auto">
<label class="form-label">Название аудитории</label>
<input class="form-control" v-model="newAudName" placeholder="например, 519" />
</div>
<div class="col-auto">
<button class="btn btn-success" @click="createAuditory">Добавить</button>
</div>
</div>
<div class="status mt-2" :class="{error: !!error}">{{ status }}</div>
<h5 class="mt-4">Список аудиторий</h5>
<div class="table-responsive">
<table class="table datatable">
<thead>
<tr>
<th>ID</th>
<th>Название</th>
</tr>
</thead>
<tbody>
<tr v-for="a in auditories" :key="a.id">
<td>{{ a.id }}</td>
<td>{{ a.audnazvanie }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div v-if="view==='owners'" class="row">
<div class="card col-md-10 col-10">
<div class="card-body">
<h3 class="card-title">Управление владельцами</h3>
<div v-if="!canEdit" class="alert alert-warning">Недостаточно прав. Войдите как администратор или редактор.</div>
<div v-else>
<h5 class="mt-2">Добавить владельца</h5>
<div class="row g-2 align-items-end">
<div class="col-auto">
<label class="form-label">Имя владельца</label>
<input class="form-control" v-model="newOwnerName" placeholder="например, Иванов И.И." />
</div>
<div class="col-auto">
<button class="btn btn-success" @click="createOwner">Добавить</button>
</div>
</div>
<div class="status mt-2" :class="{error: !!error}">{{ status }}</div>
<h5 class="mt-4">Список владельцев</h5>
<div class="table-responsive">
<table class="table datatable">
<thead>
<tr>
<th>ID</th>
<th>Имя</th>
</tr>
</thead>
<tbody>
<tr v-for="o in owners" :key="o.id">
<td>{{ o.id }}</td>
<td>{{ o.name }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<script src="/app/app.js" defer></script>
</body>
</html>

44
frontend/login.html Normal file
View File

@@ -0,0 +1,44 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Вход — АСУ Инвентаризация</title>
<link rel="stylesheet" href="/static/css/bootstrap.min.css" />
<link rel="stylesheet" href="/static/css/index.css" />
<style>
.login-card { max-width: 420px; margin: 40px auto; }
.muted { color: #6c757d; font-size: 0.9rem; }
</style>
</head>
<body>
<header>
<h1><a href="/app/">АСУ Инвентаризация</a></h1>
<h2>Авторизация</h2>
</header>
<main class="container">
<div class="card login-card">
<div class="card-body">
<h5 class="card-title mb-3">Вход</h5>
<form id="login-form">
<div class="mb-3 text-start">
<label for="username" class="form-label">Логин</label>
<input type="text" id="username" class="form-control" autocomplete="username" required />
</div>
<div class="mb-3 text-start">
<label for="password" class="form-label">Пароль</label>
<input type="password" id="password" class="form-control" autocomplete="current-password" required />
</div>
<div id="status" class="muted mb-2"></div>
<button type="submit" class="btn btn-primary w-100">Войти</button>
</form>
<div class="muted mt-3">Демо: admin / admin (после инициализации БД)</div>
</div>
</div>
</main>
<script src="/app/login.js" defer></script>
</body>
</html>

58
frontend/login.js Normal file
View File

@@ -0,0 +1,58 @@
function setStatus(msg, type = "info") {
const el = document.getElementById("status");
el.textContent = msg || "";
el.style.color = type === 'error' ? '#b91c1c' : type === 'ok' ? '#16a34a' : '';
}
function decodeRoleFromJWT(token) {
try {
const payload = JSON.parse(atob(token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/')));
return payload.role || null;
} catch { return null; }
}
async function login(username, password) {
const params = new URLSearchParams();
params.set('username', username);
params.set('password', password);
try {
const res = await fetch('/auth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: params.toString(),
});
if (!res.ok) {
const txt = await res.text();
throw new Error(`Ошибка входа (${res.status}): ${txt}`);
}
const data = await res.json();
const token = data.access_token;
if (!token) throw new Error('Токен не получен');
localStorage.setItem('access_token', token);
const role = decodeRoleFromJWT(token);
if (role) localStorage.setItem('role', role);
return { token, role };
} catch (e) {
throw e;
}
}
document.addEventListener('DOMContentLoaded', () => {
const form = document.getElementById('login-form');
form.addEventListener('submit', async (ev) => {
ev.preventDefault();
setStatus('Входим…');
const username = document.getElementById('username').value.trim();
const password = document.getElementById('password').value;
try {
const { role } = await login(username, password);
setStatus(`Успешный вход${role ? ' (' + role + ')' : ''}`, 'ok');
// Небольшая задержка для визуального отклика
setTimeout(() => { window.location.href = '/app/'; }, 400);
} catch (e) {
console.error(e);
setStatus(e.message || 'Ошибка авторизации', 'error');
}
});
});

19
frontend/styles.css Normal file
View File

@@ -0,0 +1,19 @@
:root { --bg: #0f172a; --fg: #e2e8f0; --muted: #94a3b8; --accent: #38bdf8; --err: #ef4444; --warn: #f59e0b; }
* { box-sizing: border-box; }
body { margin: 0; font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; background: var(--bg); color: var(--fg); }
header { display: flex; justify-content: space-between; align-items: center; padding: 16px 20px; border-bottom: 1px solid #1f2937; }
header h1 { margin: 0; font-size: 20px; }
header nav a { color: var(--accent); text-decoration: none; }
main { padding: 20px; max-width: 1000px; margin: 0 auto; }
.panel { background: #0b1220; border: 1px solid #1f2937; border-radius: 8px; padding: 16px; }
.controls { display: flex; gap: 8px; align-items: center; margin-bottom: 12px; }
select, button { padding: 8px 10px; border-radius: 6px; border: 1px solid #1f2937; background: #0a0f1a; color: var(--fg); }
button { cursor: pointer; }
button:hover { border-color: var(--accent); }
.status { min-height: 20px; color: var(--muted); margin-bottom: 8px; }
.status.error { color: var(--err); }
.status.warn { color: var(--warn); }
table { width: 100%; border-collapse: collapse; }
th, td { border-bottom: 1px solid #1f2937; padding: 8px; text-align: left; }
th { color: var(--muted); font-weight: 600; }

View File

@@ -0,0 +1,32 @@
"""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

@@ -0,0 +1,54 @@
"""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

@@ -0,0 +1,34 @@
"""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

@@ -0,0 +1,54 @@
"""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

@@ -0,0 +1,24 @@
"""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

@@ -0,0 +1,24 @@
"""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,37 +1,76 @@
# backend/models.py
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import Column, Integer, String, ForeignKey, DateTime
from sqlalchemy.orm import relationship, declarative_base
import datetime
db = SQLAlchemy()
Base = declarative_base()
class Auditory(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
audnazvanie=db.Column(db.String)
oboruds = db.relationship('Oboruds')
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'
class Oboruds(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
invNumber=db.Column(db.Integer)
nazvanie=db.Column(db.String(500))
balancenumber = db.Column(db.String(30))
raspologenie = db.Column(db.String(200))
numberved = db.Column(db.String(100))
numberppasu = db.Column(db.String(100))
kolichestvo = db.Column(db.Integer)
id = Column(Integer, primary_key=True)
name = Column(String, unique=True, nullable=False)
aud_id = db.Column(db.Integer, db.ForeignKey(Auditory.id))
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'
class Zametki(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
txtzam=db.Column(db.String(10000))
created_date = db.Column(db.DateTime, default=datetime.datetime.utcnow)
rmdt = db.Column(db.DateTime)
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)

Binary file not shown.

6
serve.py Normal file
View File

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

View File

@@ -8,7 +8,7 @@ body {
min-width: 580px;
}
.row{
.row {
text-align: center;
@@ -35,7 +35,7 @@ a:hover {
color: #041322;
}
a{
a {
color: #041322;
@@ -54,7 +54,7 @@ button {
.card {
width: 200px;
/*width: 200px; */
margin: 10px;
border-radius: 15px;
border-color: #E07D54;
@@ -67,51 +67,62 @@ h5 {
}
.hidden-column{
display: none;
.hidden-column {
display: none;
}
nav{
nav {
width:100%;
width: 100%;
}
.table{
table {
word-break: break-all;
border-collapse: separate !important;
}
.aud{
.table td {
font-size: 14px;
padding: 0;
max-width: 10rem;
word-break: break-all;
border-collapse: separate !important;
}
.aud {
width: 110px;
}
.inv{
.inv {
width: 400px;
}
.rasp{
max-width: 200px;
.rasp {
width: 200px;
word-break: break-word;
white-space: nowrap;
}
#modal_matcenn{
#modal_matcenn {
margin-left: 20px;
}
.datatable{
.datatable {
background-color: whitesmoke;
}
.datatable th:nth-child(7) {
width: 200px;
}
@media print {
*{
* {
font-family: "Times New Roman", Times, serif;
}
@@ -139,29 +150,30 @@ nav{
width: 100%;
}
.no-print {
display: none;
}
table.rs-table-bordered{
border:1px solid #000000;
margin-top:20px;
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;
}
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;
table.rs-table-bordered > tbody > tr > td {
border: 1px solid #000000;
padding: 10px;
font-size: 14pt;
}
}

View File

@@ -1,35 +1,282 @@
function getAllData(){
$.ajax({
function clearTable() {
var table = document.getElementById("alldatatable");
var rowCount = table.rows.length;
url: "/getall",
type: "get",
contentType: 'application/json',
dataType: 'json',
success: function(response){
console.log(response)
let data = response;
$('#datatable tbody').empty();
$.each(response, function(index, item) {
$('#datatable tbody').append(
'<tr>' +
'<td>' + item.invNumber + '</td>' +
'<td>' + item.nazvanie + '</td>' +
'<td>' + item.raspologenie + '</td>' +
'<td>' + item.numberved + '</td>' +
'<td>' + item.buhnumberpp + '</td>' +
'<td>' + item.kolichestvo + '</td>' +
'<td>' + item.balancenumber + '</td>' +
'</tr>'
);
})
},
})
// Iterate through each row and remove it
for (var i = rowCount - 1; i > 0; i--) {
table.deleteRow(i);
}
}
$(document).ready(function(){
getAllData()
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

@@ -16,9 +16,9 @@ function getData(){
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 =""
@@ -33,7 +33,7 @@ function getData(){
tr += '</tr>'
});
table.innerHTML += tr
table.innerHTML = headTable + tr
}
})
}

View File

@@ -3,30 +3,141 @@
{% block content %}
<div class="row col-12">
<div class=" card col-11">
<h3 class=" no-print"> Все мат. ценности </h3>
<!-- 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 class="row col-12">
<button class="button" id="printallbutton"> Печать </button>
</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 id="datable" class="datable table pagebreak" >
<div class="card col-md-11 table-responsive">
<table id="alldatatable" class="alldatable table pagebreak" >
<thead>
<tr>
<th scope="col">п/п</th>
<th scope="col">№ п/п вед</th>
<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>
<th scope="col">Расположение</th>
</tr>
</thead>
<tr>
</tr>
</table>
</div>

147
templates/all_OLD.html Normal file
View File

@@ -0,0 +1,147 @@
{% 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

@@ -10,3 +10,4 @@
<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>

62
templates/login.html Normal file
View File

@@ -0,0 +1,62 @@
{% 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

@@ -3,23 +3,49 @@
{% 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">
<!-- 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="Введите расположение">
<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 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 class="row no-print">
@@ -27,48 +53,48 @@
<div class="card-body">
<select name="auditory" id="auditory">
<select name="auditory" id="auditory">
{% for item in aud: %}
<option name="optauditory" value="{{item.id}}">{{ item.audnazvanie }}</option>
{% endfor %}
</select>
{% for item in aud: %}
<option name="optauditory" value="{{ item.id }}">{{ item.audnazvanie }}</option>
{% endfor %}
</select>
<button id="searchbutton"> Найти </button>
<button id="printbutton"> Печать </button>
<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>
<div class="card-body">
<h3 class="card-title no-print"> Поаудиторно </h3>
<table class="table" id="datatable" col-md-10>
<table class="table " id="datatable">
<td >Номер в Инв. вед</td>
<td >Инв. номер</td>
<td >Название</td>
<td class="no-print">Аудитория</td>
<td >Расположение</td>
<th>Номер в Инв. вед</th>
<th>Инв. номер</th>
<th>Название</th>
<th class="no-print">Аудитория</th>
<th>Расположение</th>
{% for item in res: %}
{% 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>
<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>
{% endfor %}
</table>
</div>
</div>
</div>
</div>
{%endblock%}
{% endblock %}