pillow-py 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pillow_py-1.0.0/PKG-INFO +9 -0
- pillow_py-1.0.0/pillow_py/__init__.py +1 -0
- pillow_py-1.0.0/pillow_py/cli.py +40 -0
- pillow_py-1.0.0/pillow_py/template/app.py +209 -0
- pillow_py-1.0.0/pillow_py/template/database/schema.sql +86 -0
- pillow_py-1.0.0/pillow_py/template/database/seed.sql +121 -0
- pillow_py-1.0.0/pillow_py/template/requirements.txt +3 -0
- pillow_py-1.0.0/pillow_py/template/static/css/style.css +339 -0
- pillow_py-1.0.0/pillow_py/template/static/img/favicon.ico +0 -0
- pillow_py-1.0.0/pillow_py/template/static/img/logo.png +0 -0
- pillow_py-1.0.0/pillow_py/template/static/js/live_filters.js +16 -0
- pillow_py-1.0.0/pillow_py/template/static/uploads/products/1.jpg +0 -0
- pillow_py-1.0.0/pillow_py/template/static/uploads/products/10.jpg +0 -0
- pillow_py-1.0.0/pillow_py/template/static/uploads/products/2.jpg +0 -0
- pillow_py-1.0.0/pillow_py/template/static/uploads/products/3.jpg +0 -0
- pillow_py-1.0.0/pillow_py/template/static/uploads/products/4.jpg +0 -0
- pillow_py-1.0.0/pillow_py/template/static/uploads/products/5.jpg +0 -0
- pillow_py-1.0.0/pillow_py/template/static/uploads/products/6.jpg +0 -0
- pillow_py-1.0.0/pillow_py/template/static/uploads/products/7.jpg +0 -0
- pillow_py-1.0.0/pillow_py/template/static/uploads/products/8.jpg +0 -0
- pillow_py-1.0.0/pillow_py/template/static/uploads/products/9.jpg +0 -0
- pillow_py-1.0.0/pillow_py/template/static/uploads/products/picture.png +0 -0
- pillow_py-1.0.0/pillow_py/template/templates/base.html +48 -0
- pillow_py-1.0.0/pillow_py/template/templates/login.html +22 -0
- pillow_py-1.0.0/pillow_py/template/templates/message.html +8 -0
- pillow_py-1.0.0/pillow_py/template/templates/order_form.html +60 -0
- pillow_py-1.0.0/pillow_py/template/templates/orders.html +50 -0
- pillow_py-1.0.0/pillow_py/template/templates/product_form.html +70 -0
- pillow_py-1.0.0/pillow_py/template/templates/products.html +76 -0
- pillow_py-1.0.0/pillow_py.egg-info/PKG-INFO +9 -0
- pillow_py-1.0.0/pillow_py.egg-info/SOURCES.txt +35 -0
- pillow_py-1.0.0/pillow_py.egg-info/dependency_links.txt +1 -0
- pillow_py-1.0.0/pillow_py.egg-info/entry_points.txt +2 -0
- pillow_py-1.0.0/pillow_py.egg-info/requires.txt +3 -0
- pillow_py-1.0.0/pillow_py.egg-info/top_level.txt +1 -0
- pillow_py-1.0.0/pyproject.toml +24 -0
- pillow_py-1.0.0/setup.cfg +4 -0
pillow_py-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '1.0.0'
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import shutil
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from importlib.resources import files
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def copy_template(target: Path, force: bool = False) -> None:
|
|
8
|
+
src = files('pillow_py').joinpath('template')
|
|
9
|
+
target = target.resolve()
|
|
10
|
+
if target.exists() and any(target.iterdir()) and not force:
|
|
11
|
+
raise SystemExit('Папка уже существует и не пустая. Используй --force или выбери другую папку.')
|
|
12
|
+
target.mkdir(parents=True, exist_ok=True)
|
|
13
|
+
for item in src.iterdir():
|
|
14
|
+
dst = target / item.name
|
|
15
|
+
if item.is_dir():
|
|
16
|
+
if dst.exists() and force:
|
|
17
|
+
shutil.rmtree(dst)
|
|
18
|
+
shutil.copytree(item, dst, dirs_exist_ok=force)
|
|
19
|
+
else:
|
|
20
|
+
shutil.copy2(item, dst)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def main() -> None:
|
|
24
|
+
parser = argparse.ArgumentParser(description='Создание готового Flask + PostgreSQL + Pillow проекта МирИгрушек')
|
|
25
|
+
parser.add_argument('folder', nargs='?', default='mirtoys_flask_pg_pillow', help='папка для проекта')
|
|
26
|
+
parser.add_argument('--force', action='store_true', help='перезаписать существующую папку')
|
|
27
|
+
args = parser.parse_args()
|
|
28
|
+
copy_template(Path(args.folder), args.force)
|
|
29
|
+
print(f'Готово: проект создан в папке {args.folder}')
|
|
30
|
+
print('Дальше команды:')
|
|
31
|
+
print(f' cd {args.folder}')
|
|
32
|
+
print(' py -m venv .venv')
|
|
33
|
+
print(' .venv\\Scripts\\activate')
|
|
34
|
+
print(' pip install -r requirements.txt')
|
|
35
|
+
print(' python app.py initdb')
|
|
36
|
+
print(' python app.py')
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
if __name__ == '__main__':
|
|
40
|
+
main()
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import os, sys, time
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import psycopg2
|
|
4
|
+
import psycopg2.extras
|
|
5
|
+
from PIL import Image
|
|
6
|
+
from werkzeug.utils import secure_filename
|
|
7
|
+
from flask import Flask, flash, redirect, render_template, request, session, url_for
|
|
8
|
+
app = Flask(__name__)
|
|
9
|
+
app.config['SECRET_KEY'] = 'student-secret-key'
|
|
10
|
+
DB = dict(dbname=os.getenv('DB_NAME','SEMEN'), user=os.getenv('DB_USER','postgres'),
|
|
11
|
+
password=os.getenv('DB_PASSWORD','123456789'), host=os.getenv('DB_HOST','localhost'),
|
|
12
|
+
port=os.getenv('DB_PORT','5432'), cursor_factory=psycopg2.extras.RealDictCursor)
|
|
13
|
+
UPLOAD_DIR, PLACEHOLDER = Path('static/uploads/products'), 'picture.png'
|
|
14
|
+
def conn():
|
|
15
|
+
return psycopg2.connect(**DB)
|
|
16
|
+
def rows(sql, p=()):
|
|
17
|
+
with conn() as c, c.cursor() as cur:
|
|
18
|
+
cur.execute(sql, p); return cur.fetchall()
|
|
19
|
+
def one(sql, p=()):
|
|
20
|
+
r = rows(sql, p); return r[0] if r else None
|
|
21
|
+
def exec_sql(sql, p=()):
|
|
22
|
+
with conn() as c, c.cursor() as cur:
|
|
23
|
+
cur.execute(sql, p); c.commit()
|
|
24
|
+
def init_db():
|
|
25
|
+
with conn() as c, c.cursor() as cur:
|
|
26
|
+
for name in ('database/schema.sql', 'database/seed.sql'):
|
|
27
|
+
cur.execute(Path(name).read_text(encoding='utf-8'))
|
|
28
|
+
c.commit()
|
|
29
|
+
print('База mir_toys_db заполнена')
|
|
30
|
+
def role(): return session.get('role_code', 'guest')
|
|
31
|
+
def admin(): return role() == 'admin'
|
|
32
|
+
def staff(): return role() in ('admin', 'manager')
|
|
33
|
+
def need_admin():
|
|
34
|
+
if not admin():
|
|
35
|
+
flash('Ошибка доступа: изменять данные может только администратор.', 'error'); return False
|
|
36
|
+
return True
|
|
37
|
+
def need_staff():
|
|
38
|
+
if not staff():
|
|
39
|
+
flash('Ошибка доступа: заказы доступны только менеджеру и администратору.', 'error'); return False
|
|
40
|
+
return True
|
|
41
|
+
@app.context_processor
|
|
42
|
+
def ctx():
|
|
43
|
+
return dict(role=role(), is_admin=admin(), is_staff=staff(), full_name=session.get('full_name', 'Гость'))
|
|
44
|
+
def del_img(name):
|
|
45
|
+
p = UPLOAD_DIR / str(name or '')
|
|
46
|
+
if name and name != PLACEHOLDER and p.exists(): p.unlink()
|
|
47
|
+
def save_img(f, old=PLACEHOLDER):
|
|
48
|
+
if not f or not f.filename: return old or PLACEHOLDER
|
|
49
|
+
ext = f.filename.rsplit('.', 1)[-1].lower()
|
|
50
|
+
if ext not in ('jpg', 'jpeg', 'png'): return old or PLACEHOLDER
|
|
51
|
+
UPLOAD_DIR.mkdir(parents=True, exist_ok=True)
|
|
52
|
+
name = f'{int(time.time())}_{secure_filename(f.filename) or "photo.jpg"}'
|
|
53
|
+
img = Image.open(f).convert('RGB'); img.thumbnail((300, 200))
|
|
54
|
+
out = Image.new('RGB', (300, 200), (255, 255, 255))
|
|
55
|
+
out.paste(img, ((300-img.width)//2, (200-img.height)//2))
|
|
56
|
+
out.save(UPLOAD_DIR / name, quality=90)
|
|
57
|
+
return name
|
|
58
|
+
@app.route('/')
|
|
59
|
+
def index(): return redirect(url_for('login'))
|
|
60
|
+
@app.route('/guest')
|
|
61
|
+
def guest_login():
|
|
62
|
+
session.clear(); return redirect(url_for('products'))
|
|
63
|
+
@app.route('/login', methods=['GET', 'POST'])
|
|
64
|
+
def login():
|
|
65
|
+
if request.method == 'POST':
|
|
66
|
+
u = one('''SELECT u.id_user,u.full_name,r.role_code,r.role_name FROM users u
|
|
67
|
+
JOIN roles r ON r.id_role=u.id_role
|
|
68
|
+
WHERE u.login=%s AND u.password_value=%s''',
|
|
69
|
+
(request.form.get('login','').strip(), request.form.get('password','').strip()))
|
|
70
|
+
if u:
|
|
71
|
+
session.update(id_user=u['id_user'], full_name=u['full_name'], role_code=u['role_code'], role_name=u['role_name'])
|
|
72
|
+
flash('Вход выполнен успешно.', 'success'); return redirect(url_for('products'))
|
|
73
|
+
flash('Ошибка входа: неверный логин или пароль.', 'error')
|
|
74
|
+
return render_template('login.html', title='Вход в систему')
|
|
75
|
+
@app.route('/logout')
|
|
76
|
+
def logout():
|
|
77
|
+
session.clear(); flash('Выход выполнен.', 'success'); return redirect(url_for('login'))
|
|
78
|
+
@app.route('/products')
|
|
79
|
+
def products():
|
|
80
|
+
wh, p = [], []
|
|
81
|
+
search = request.args.get('search', '').strip() if staff() else ''
|
|
82
|
+
supplier_id = request.args.get('supplier_id', '').strip() if staff() else ''
|
|
83
|
+
sort = request.args.get('sort', 'name') if staff() else 'name'
|
|
84
|
+
if search:
|
|
85
|
+
wh.append("CONCAT_WS(' ',p.article,p.name,p.unit_name,p.description,c.category_name,m.manufacturer_name,s.supplier_name) ILIKE %s")
|
|
86
|
+
p.append(f'%{search}%')
|
|
87
|
+
if supplier_id.isdigit(): wh.append('p.id_supplier=%s'); p.append(int(supplier_id))
|
|
88
|
+
order = {'price_asc':'p.price ASC','price_desc':'p.price DESC','stock_asc':'p.stock_quantity ASC',
|
|
89
|
+
'stock_desc':'p.stock_quantity DESC'}.get(sort, 'p.name ASC')
|
|
90
|
+
data = rows(f'''SELECT p.*,c.category_name,m.manufacturer_name,s.supplier_name,
|
|
91
|
+
ROUND((p.price-p.price*p.discount/100)::numeric,2) final_price
|
|
92
|
+
FROM products p JOIN categories c ON c.id_category=p.id_category
|
|
93
|
+
JOIN manufacturers m ON m.id_manufacturer=p.id_manufacturer
|
|
94
|
+
JOIN suppliers s ON s.id_supplier=p.id_supplier
|
|
95
|
+
{'WHERE '+ ' AND '.join(wh) if wh else ''} ORDER BY {order}, p.name''', p)
|
|
96
|
+
suppliers = rows('SELECT id_supplier,supplier_name FROM suppliers ORDER BY supplier_name')
|
|
97
|
+
return render_template('products.html', title='Список товаров', products=data, suppliers=suppliers,
|
|
98
|
+
search=search, supplier_id=supplier_id, sort_mode=sort)
|
|
99
|
+
def product_lists():
|
|
100
|
+
return dict(categories=rows('SELECT * FROM categories ORDER BY category_name'),
|
|
101
|
+
manufacturers=rows('SELECT * FROM manufacturers ORDER BY manufacturer_name'),
|
|
102
|
+
suppliers=rows('SELECT * FROM suppliers ORDER BY supplier_name'))
|
|
103
|
+
def get_product_form(product=None):
|
|
104
|
+
if request.method == 'POST':
|
|
105
|
+
try:
|
|
106
|
+
a = request.form; old = product['image_filename'] if product else PLACEHOLDER
|
|
107
|
+
vals = (a['article'].strip().upper(), a['name'].strip(), a.get('unit_name','шт.').strip(),
|
|
108
|
+
float(a['price'].replace(',','.')), int(a['id_supplier']), int(a['id_manufacturer']),
|
|
109
|
+
int(a['id_category']), float(a['discount'].replace(',','.')), int(a['stock_quantity']),
|
|
110
|
+
a.get('description','').strip(), save_img(request.files.get('image_file'), old))
|
|
111
|
+
if not vals[0] or not vals[1]: raise ValueError('Заполните артикул и название.')
|
|
112
|
+
if vals[3] < 0 or vals[8] < 0 or not 0 <= vals[7] <= 100: raise ValueError('Проверьте цену, остаток и скидку.')
|
|
113
|
+
if product:
|
|
114
|
+
exec_sql('''UPDATE products SET article=%s,name=%s,unit_name=%s,price=%s,id_supplier=%s,
|
|
115
|
+
id_manufacturer=%s,id_category=%s,discount=%s,stock_quantity=%s,
|
|
116
|
+
description=%s,image_filename=%s WHERE id_product=%s''', vals + (product['id_product'],))
|
|
117
|
+
if vals[-1] != old: del_img(old)
|
|
118
|
+
flash('Товар изменён.', 'success')
|
|
119
|
+
else:
|
|
120
|
+
exec_sql('''INSERT INTO products(article,name,unit_name,price,id_supplier,id_manufacturer,
|
|
121
|
+
id_category,discount,stock_quantity,description,image_filename)
|
|
122
|
+
VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)''', vals)
|
|
123
|
+
flash('Товар добавлен.', 'success')
|
|
124
|
+
return redirect(url_for('products'))
|
|
125
|
+
except Exception as e: flash(f'Ошибка сохранения: {e}', 'error')
|
|
126
|
+
return render_template('product_form.html', title='Товар', product=product, **product_lists())
|
|
127
|
+
@app.route('/products/add', methods=['GET', 'POST'])
|
|
128
|
+
def product_add():
|
|
129
|
+
return get_product_form() if need_admin() else redirect(url_for('products'))
|
|
130
|
+
@app.route('/products/<int:product_id>/edit', methods=['GET', 'POST'])
|
|
131
|
+
def product_edit(product_id):
|
|
132
|
+
if not need_admin(): return redirect(url_for('products'))
|
|
133
|
+
p = one('SELECT * FROM products WHERE id_product=%s', (product_id,))
|
|
134
|
+
return get_product_form(p) if p else redirect(url_for('products'))
|
|
135
|
+
@app.route('/products/<int:product_id>/delete', methods=['POST'])
|
|
136
|
+
def product_delete(product_id):
|
|
137
|
+
if not need_admin(): return redirect(url_for('products'))
|
|
138
|
+
p = one('SELECT image_filename FROM products WHERE id_product=%s', (product_id,))
|
|
139
|
+
if one('SELECT 1 FROM order_items WHERE id_product=%s LIMIT 1', (product_id,)):
|
|
140
|
+
flash('Товар есть в заказе, удалить нельзя.', 'error'); return redirect(url_for('products'))
|
|
141
|
+
if p: exec_sql('DELETE FROM products WHERE id_product=%s', (product_id,)); del_img(p['image_filename'])
|
|
142
|
+
flash('Товар удалён.', 'success'); return redirect(url_for('products'))
|
|
143
|
+
def item_text(order_id):
|
|
144
|
+
return ', '.join(x for r in rows('''SELECT p.article,oi.quantity FROM order_items oi
|
|
145
|
+
JOIN products p ON p.id_product=oi.id_product WHERE oi.id_order=%s''', (order_id,))
|
|
146
|
+
for x in (r['article'], str(r['quantity'])))
|
|
147
|
+
def parse_items(text):
|
|
148
|
+
a = [x.strip().upper() for x in text.replace(';', ',').split(',') if x.strip()]
|
|
149
|
+
if not a or len(a) % 2: raise ValueError('Формат: артикул, количество.')
|
|
150
|
+
return [(a[i], int(a[i+1])) for i in range(0, len(a), 2) if int(a[i+1]) > 0]
|
|
151
|
+
@app.route('/orders')
|
|
152
|
+
def orders():
|
|
153
|
+
if not need_staff(): return redirect(url_for('products'))
|
|
154
|
+
data = rows('''SELECT o.*,pp.address,u.full_name client_name,st.status_name,
|
|
155
|
+
STRING_AGG(p.article||' x'||oi.quantity, ', ' ORDER BY oi.id_order_item) article_list,
|
|
156
|
+
COALESCE(SUM(oi.quantity*(p.price-p.price*p.discount/100)),0)::numeric(12,2) total_sum
|
|
157
|
+
FROM orders o JOIN pickup_points pp ON pp.id_pickup_point=o.id_pickup_point
|
|
158
|
+
JOIN users u ON u.id_user=o.id_client JOIN order_statuses st ON st.id_status=o.id_status
|
|
159
|
+
LEFT JOIN order_items oi ON oi.id_order=o.id_order LEFT JOIN products p ON p.id_product=oi.id_product
|
|
160
|
+
GROUP BY o.id_order,pp.address,u.full_name,st.status_name ORDER BY o.order_number''')
|
|
161
|
+
return render_template('orders.html', title='Заказы', orders=data)
|
|
162
|
+
def order_refs():
|
|
163
|
+
return dict(products_list=rows('SELECT article,name FROM products ORDER BY article'),
|
|
164
|
+
points=rows('SELECT * FROM pickup_points ORDER BY id_pickup_point'),
|
|
165
|
+
statuses=rows('SELECT * FROM order_statuses ORDER BY id_status'),
|
|
166
|
+
clients=rows("""SELECT u.id_user,u.full_name FROM users u JOIN roles r ON r.id_role=u.id_role
|
|
167
|
+
WHERE r.role_code='client' ORDER BY u.full_name"""))
|
|
168
|
+
def get_order_form(order=None):
|
|
169
|
+
items_text = request.form.get('order_items','').strip() if request.method == 'POST' else (item_text(order['id_order']) if order else '')
|
|
170
|
+
if request.method == 'POST':
|
|
171
|
+
try:
|
|
172
|
+
a = request.form; items = parse_items(items_text)
|
|
173
|
+
vals = (a.get('order_date') or None, a.get('delivery_date') or None, int(a['id_pickup_point']),
|
|
174
|
+
int(a['id_client']), int(a['receive_code']), int(a['id_status']))
|
|
175
|
+
with conn() as c, c.cursor() as cur:
|
|
176
|
+
if order:
|
|
177
|
+
oid = order['id_order']; cur.execute('''UPDATE orders SET order_date=%s,delivery_date=%s,
|
|
178
|
+
id_pickup_point=%s,id_client=%s,receive_code=%s,id_status=%s WHERE id_order=%s''', vals+(oid,))
|
|
179
|
+
cur.execute('DELETE FROM order_items WHERE id_order=%s', (oid,))
|
|
180
|
+
else:
|
|
181
|
+
cur.execute('SELECT COALESCE(MAX(order_number),0)+1 n FROM orders'); num = cur.fetchone()['n']
|
|
182
|
+
cur.execute('''INSERT INTO orders(order_number,order_date,delivery_date,id_pickup_point,id_client,receive_code,id_status)
|
|
183
|
+
VALUES(%s,%s,%s,%s,%s,%s,%s) RETURNING id_order''', (num,)+vals); oid = cur.fetchone()['id_order']
|
|
184
|
+
for art, qty in items:
|
|
185
|
+
cur.execute('SELECT id_product FROM products WHERE article=%s', (art,)); pr = cur.fetchone()
|
|
186
|
+
if not pr: raise ValueError(f'Товар {art} не найден.')
|
|
187
|
+
cur.execute('INSERT INTO order_items(id_order,id_product,quantity) VALUES(%s,%s,%s)', (oid, pr['id_product'], qty))
|
|
188
|
+
c.commit()
|
|
189
|
+
flash('Заказ сохранён.', 'success'); return redirect(url_for('orders'))
|
|
190
|
+
except Exception as e: flash(f'Ошибка сохранения заказа: {e}', 'error')
|
|
191
|
+
return render_template('order_form.html', title='Заказ', order=order, items_text=items_text, **order_refs())
|
|
192
|
+
@app.route('/orders/add', methods=['GET', 'POST'])
|
|
193
|
+
def order_add(): return get_order_form() if need_admin() else redirect(url_for('orders'))
|
|
194
|
+
@app.route('/orders/<int:order_id>/edit', methods=['GET', 'POST'])
|
|
195
|
+
def order_edit(order_id):
|
|
196
|
+
if not need_admin(): return redirect(url_for('orders'))
|
|
197
|
+
o = one('SELECT * FROM orders WHERE id_order=%s', (order_id,))
|
|
198
|
+
return get_order_form(o) if o else redirect(url_for('orders'))
|
|
199
|
+
@app.route('/orders/<int:order_id>/delete', methods=['POST'])
|
|
200
|
+
def order_delete(order_id):
|
|
201
|
+
if need_admin(): exec_sql('DELETE FROM orders WHERE id_order=%s', (order_id,)); flash('Заказ удалён.', 'success')
|
|
202
|
+
return redirect(url_for('orders'))
|
|
203
|
+
@app.errorhandler(404)
|
|
204
|
+
def e404(e): return render_template('message.html', title='Страница не найдена', message='Такой страницы нет.'), 404
|
|
205
|
+
@app.errorhandler(500)
|
|
206
|
+
def e500(e): return render_template('message.html', title='Ошибка сервера', message='Проверьте PostgreSQL.'), 500
|
|
207
|
+
if __name__ == '__main__':
|
|
208
|
+
if len(sys.argv) > 1 and sys.argv[1] == 'initdb': init_db()
|
|
209
|
+
else: app.run(debug=True)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
DROP TABLE IF EXISTS order_items CASCADE;
|
|
2
|
+
DROP TABLE IF EXISTS orders CASCADE;
|
|
3
|
+
DROP TABLE IF EXISTS products CASCADE;
|
|
4
|
+
DROP TABLE IF EXISTS users CASCADE;
|
|
5
|
+
DROP TABLE IF EXISTS roles CASCADE;
|
|
6
|
+
DROP TABLE IF EXISTS categories CASCADE;
|
|
7
|
+
DROP TABLE IF EXISTS manufacturers CASCADE;
|
|
8
|
+
DROP TABLE IF EXISTS suppliers CASCADE;
|
|
9
|
+
DROP TABLE IF EXISTS pickup_points CASCADE;
|
|
10
|
+
DROP TABLE IF EXISTS order_statuses CASCADE;
|
|
11
|
+
|
|
12
|
+
CREATE TABLE roles (
|
|
13
|
+
id_role SERIAL PRIMARY KEY,
|
|
14
|
+
role_code VARCHAR(30) NOT NULL UNIQUE,
|
|
15
|
+
role_name VARCHAR(80) NOT NULL UNIQUE
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
CREATE TABLE users (
|
|
19
|
+
id_user SERIAL PRIMARY KEY,
|
|
20
|
+
id_role INTEGER NOT NULL REFERENCES roles(id_role) ON UPDATE CASCADE,
|
|
21
|
+
full_name VARCHAR(180) NOT NULL,
|
|
22
|
+
login VARCHAR(160) NOT NULL UNIQUE,
|
|
23
|
+
password_value VARCHAR(120) NOT NULL
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
CREATE TABLE categories (
|
|
27
|
+
id_category SERIAL PRIMARY KEY,
|
|
28
|
+
category_name VARCHAR(120) NOT NULL UNIQUE
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
CREATE TABLE manufacturers (
|
|
32
|
+
id_manufacturer SERIAL PRIMARY KEY,
|
|
33
|
+
manufacturer_name VARCHAR(120) NOT NULL UNIQUE
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
CREATE TABLE suppliers (
|
|
37
|
+
id_supplier SERIAL PRIMARY KEY,
|
|
38
|
+
supplier_name VARCHAR(120) NOT NULL UNIQUE
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
CREATE TABLE products (
|
|
42
|
+
id_product SERIAL PRIMARY KEY,
|
|
43
|
+
article VARCHAR(30) NOT NULL UNIQUE,
|
|
44
|
+
name TEXT NOT NULL,
|
|
45
|
+
unit_name VARCHAR(30) NOT NULL DEFAULT 'шт.',
|
|
46
|
+
price NUMERIC(12,2) NOT NULL CHECK (price >= 0),
|
|
47
|
+
id_supplier INTEGER NOT NULL REFERENCES suppliers(id_supplier) ON UPDATE CASCADE,
|
|
48
|
+
id_manufacturer INTEGER NOT NULL REFERENCES manufacturers(id_manufacturer) ON UPDATE CASCADE,
|
|
49
|
+
id_category INTEGER NOT NULL REFERENCES categories(id_category) ON UPDATE CASCADE,
|
|
50
|
+
discount NUMERIC(5,2) NOT NULL DEFAULT 0 CHECK (discount >= 0 AND discount <= 100),
|
|
51
|
+
stock_quantity INTEGER NOT NULL DEFAULT 0 CHECK (stock_quantity >= 0),
|
|
52
|
+
description TEXT,
|
|
53
|
+
image_filename VARCHAR(255) DEFAULT 'picture.png'
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
CREATE TABLE pickup_points (
|
|
57
|
+
id_pickup_point SERIAL PRIMARY KEY,
|
|
58
|
+
address TEXT NOT NULL UNIQUE
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
CREATE TABLE order_statuses (
|
|
62
|
+
id_status SERIAL PRIMARY KEY,
|
|
63
|
+
status_name VARCHAR(60) NOT NULL UNIQUE
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
CREATE TABLE orders (
|
|
67
|
+
id_order SERIAL PRIMARY KEY,
|
|
68
|
+
order_number INTEGER NOT NULL UNIQUE,
|
|
69
|
+
order_date DATE,
|
|
70
|
+
delivery_date DATE,
|
|
71
|
+
id_pickup_point INTEGER NOT NULL REFERENCES pickup_points(id_pickup_point) ON UPDATE CASCADE,
|
|
72
|
+
id_client INTEGER NOT NULL REFERENCES users(id_user) ON UPDATE CASCADE,
|
|
73
|
+
receive_code INTEGER NOT NULL CHECK (receive_code >= 0),
|
|
74
|
+
id_status INTEGER NOT NULL REFERENCES order_statuses(id_status) ON UPDATE CASCADE
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
CREATE TABLE order_items (
|
|
78
|
+
id_order_item SERIAL PRIMARY KEY,
|
|
79
|
+
id_order INTEGER NOT NULL REFERENCES orders(id_order) ON DELETE CASCADE ON UPDATE CASCADE,
|
|
80
|
+
id_product INTEGER NOT NULL REFERENCES products(id_product) ON UPDATE CASCADE,
|
|
81
|
+
quantity INTEGER NOT NULL CHECK (quantity > 0)
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
CREATE INDEX idx_products_article ON products(article);
|
|
85
|
+
CREATE INDEX idx_products_supplier ON products(id_supplier);
|
|
86
|
+
CREATE INDEX idx_order_items_order ON order_items(id_order);
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
INSERT INTO roles (role_code, role_name) VALUES ('admin','Администратор'), ('manager','Менеджер'), ('client','Авторизированный клиент');
|
|
2
|
+
|
|
3
|
+
INSERT INTO categories (category_name) VALUES
|
|
4
|
+
('Детский музыкальный инструмент'),
|
|
5
|
+
('Игровой набор'),
|
|
6
|
+
('Конструктор'),
|
|
7
|
+
('Машинка');
|
|
8
|
+
|
|
9
|
+
INSERT INTO manufacturers (manufacturer_name) VALUES
|
|
10
|
+
('ABSпластик'),
|
|
11
|
+
('BambiniFelici'),
|
|
12
|
+
('Junion');
|
|
13
|
+
|
|
14
|
+
INSERT INTO suppliers (supplier_name) VALUES
|
|
15
|
+
('CHILITOY'),
|
|
16
|
+
('Knauf'),
|
|
17
|
+
('Pikeshop'),
|
|
18
|
+
('Playbig'),
|
|
19
|
+
('Vinylon');
|
|
20
|
+
|
|
21
|
+
INSERT INTO pickup_points (address) VALUES
|
|
22
|
+
('420151, г. Лесной, ул. Вишневая, 32'),
|
|
23
|
+
('125061, г. Лесной, ул. Подгорная, 8'),
|
|
24
|
+
('630370, г. Лесной, ул. Шоссейная, 24'),
|
|
25
|
+
('400562, г. Лесной, ул. Зеленая, 32'),
|
|
26
|
+
('614510, г. Лесной, ул. Маяковского, 47'),
|
|
27
|
+
('410542, г. Лесной, ул. Светлая, 46'),
|
|
28
|
+
('620839, г. Лесной, ул. Цветочная, 8'),
|
|
29
|
+
('443890, г. Лесной, ул. Коммунистическая, 1'),
|
|
30
|
+
('603379, г. Лесной, ул. Спортивная, 46'),
|
|
31
|
+
('603721, г. Лесной, ул. Гоголя, 41'),
|
|
32
|
+
('410172, г. Лесной, ул. Северная, 13'),
|
|
33
|
+
('614611, г. Лесной, ул. Молодежная, 50'),
|
|
34
|
+
('454311, г.Лесной, ул. Новая, 19'),
|
|
35
|
+
('660007, г.Лесной, ул. Октябрьская, 19'),
|
|
36
|
+
('603036, г. Лесной, ул. Садовая, 4'),
|
|
37
|
+
('394060, г.Лесной, ул. Фрунзе, 43'),
|
|
38
|
+
('410661, г. Лесной, ул. Школьная, 50'),
|
|
39
|
+
('625590, г. Лесной, ул. Коммунистическая, 20'),
|
|
40
|
+
('625683, г. Лесной, ул. 8 Марта'),
|
|
41
|
+
('450983, г.Лесной, ул. Комсомольская, 26'),
|
|
42
|
+
('394782, г. Лесной, ул. Чехова, 3'),
|
|
43
|
+
('603002, г. Лесной, ул. Дзержинского, 28'),
|
|
44
|
+
('450558, г. Лесной, ул. Набережная, 30'),
|
|
45
|
+
('344288, г. Лесной, ул. Чехова, 1'),
|
|
46
|
+
('614164, г.Лесной, ул. Степная, 30'),
|
|
47
|
+
('394242, г. Лесной, ул. Коммунистическая, 43'),
|
|
48
|
+
('660540, г. Лесной, ул. Солнечная, 25'),
|
|
49
|
+
('125837, г. Лесной, ул. Шоссейная, 40'),
|
|
50
|
+
('125703, г. Лесной, ул. Партизанская, 49'),
|
|
51
|
+
('625283, г. Лесной, ул. Победы, 46'),
|
|
52
|
+
('614753, г. Лесной, ул. Полевая, 35'),
|
|
53
|
+
('426030, г. Лесной, ул. Маяковского, 44'),
|
|
54
|
+
('450375, г. Лесной ул. Клубная, 44'),
|
|
55
|
+
('625560, г. Лесной, ул. Некрасова, 12'),
|
|
56
|
+
('630201, г. Лесной, ул. Комсомольская, 17'),
|
|
57
|
+
('190949, г. Лесной, ул. Мичурина, 26');
|
|
58
|
+
|
|
59
|
+
INSERT INTO order_statuses (status_name) VALUES ('Новый'), ('Завершен'), ('Отменен');
|
|
60
|
+
|
|
61
|
+
INSERT INTO users (id_role, full_name, login, password_value) VALUES
|
|
62
|
+
((SELECT id_role FROM roles WHERE role_code = 'admin'), 'Ворсин Петр Евгеньевич', '94d5ous@gmail.com', 'uzWC67'),
|
|
63
|
+
((SELECT id_role FROM roles WHERE role_code = 'admin'), 'Старикова Елена Павловна', 'uth4iz@mail.com', '2L6KZG'),
|
|
64
|
+
((SELECT id_role FROM roles WHERE role_code = 'admin'), 'Одинцов Серафим Артёмович', 'yzls62@outlook.com', 'JlFRCZ'),
|
|
65
|
+
((SELECT id_role FROM roles WHERE role_code = 'manager'), 'Михайлюк Анна Вячеславовна', '1diph5e@tutanota.com', '8ntwUp'),
|
|
66
|
+
((SELECT id_role FROM roles WHERE role_code = 'manager'), 'Ситдикова Елена Анатольевна', 'tjde7c@yahoo.com', 'YOyhfR'),
|
|
67
|
+
((SELECT id_role FROM roles WHERE role_code = 'manager'), 'Никифорова Весения Николаевна', 'wpmrc3do@tutanota.com', 'RSbvHv'),
|
|
68
|
+
((SELECT id_role FROM roles WHERE role_code = 'client'), 'Степанов Михаил Артёмович', '5d4zbu@tutanota.com', 'rwVDh9'),
|
|
69
|
+
((SELECT id_role FROM roles WHERE role_code = 'client'), 'Ворсин Петр Евгеньевич', 'ptec8ym@yahoo.com', 'LdNyos'),
|
|
70
|
+
((SELECT id_role FROM roles WHERE role_code = 'client'), 'Старикова Елена Павловна', '1qz4kw@mail.com', 'gynQMT'),
|
|
71
|
+
((SELECT id_role FROM roles WHERE role_code = 'client'), 'Сазонов Руслан Германович', '4np6se@mail.com', 'AtnDjr');
|
|
72
|
+
|
|
73
|
+
INSERT INTO products (article, name, unit_name, price, id_supplier, id_manufacturer, id_category, discount, stock_quantity, description, image_filename) VALUES
|
|
74
|
+
('PMEZMH', 'Детский игровой набор машинок Щенячий патруль / Dogs mini . 9 героев + 9 инерфионных машинок', 'шт.', 1414, (SELECT id_supplier FROM suppliers WHERE supplier_name='Pikeshop'), (SELECT id_manufacturer FROM manufacturers WHERE manufacturer_name='ABSпластик'), (SELECT id_category FROM categories WHERE category_name='Игровой набор'), 22, 50, 'Детский набор машинок с героями мультсериала «Щенячий патруль» подойдет как для мальчиков, так и для девочек. В детский набор входит 9 фигурок щенков спасателей.', '1.jpg'),
|
|
75
|
+
('BPV4MM', 'Конструктор Гарри Поттер Сова Букля 630 деталей совместим с lego harry potter, лего совместимый)', 'шт.', 771, (SELECT id_supplier FROM suppliers WHERE supplier_name='Playbig'), (SELECT id_manufacturer FROM manufacturers WHERE manufacturer_name='ABSпластик'), (SELECT id_category FROM categories WHERE category_name='Конструктор'), 15, 26, 'Коллекционная модель Букля состоит из множества потрясающих элементов, а также специального механизма внутри. С его помощью можно плавно поднимать-опускать крылья птицы.', '2.jpg'),
|
|
76
|
+
('JVL42J', 'Музыкальные инструменты для детей, ксилофон, барабаны, развивающие игрушки, игрушки для детей', 'шт.', 2750, (SELECT id_supplier FROM suppliers WHERE supplier_name='Playbig'), (SELECT id_manufacturer FROM manufacturers WHERE manufacturer_name='BambiniFelici'), (SELECT id_category FROM categories WHERE category_name='Детский музыкальный инструмент'), 15, 0, 'Откройте мир музыки для вашего ребенка с этой уникальной игрушкой! Это многофункциональное музыкальное чудо объединяет в себе всё, что нужно для творческого развития.', '3.jpg'),
|
|
77
|
+
('F895RB', 'Машинка игрушка диско шар светящаяся музыкальная', 'шт.', 368, (SELECT id_supplier FROM suppliers WHERE supplier_name='Knauf'), (SELECT id_manufacturer FROM manufacturers WHERE manufacturer_name='ABSпластик'), (SELECT id_category FROM categories WHERE category_name='Машинка'), 6, 7, 'Светящаяся музыкальная машина с диско шаром переливается разными цветами, играет ритмичные мелодии, объезжает препятствия и крутится, поэтому с ней точно не будет скучно.', '4.jpg'),
|
|
78
|
+
('3XBOTN', 'Игровой набор Hot Wheels Action Loop Cyclone Challenge Track, с машинкой и удобным хранением, HTK16', 'шт.', 3426, (SELECT id_supplier FROM suppliers WHERE supplier_name='Knauf'), (SELECT id_manufacturer FROM manufacturers WHERE manufacturer_name='BambiniFelici'), (SELECT id_category FROM categories WHERE category_name='Игровой набор'), 10, 21, 'Игровой набор Hot Wheels Action Loop Cyclone Challenge Track - это уникальная игра, которая позволит вам испытать себя и своих друзей в скорости и ловкости. Этот набор состоит из металлической дорожки с циклоном, которая создает потрясающий эффект и добавляет дополнительную сложность в игру.', '5.jpg'),
|
|
79
|
+
('3L7RCZ', 'Игровой набор с деревянными машинками Стройплощадка Кран-Паркс, Junion', 'шт.', 7400, (SELECT id_supplier FROM suppliers WHERE supplier_name='Knauf'), (SELECT id_manufacturer FROM manufacturers WHERE manufacturer_name='Junion'), (SELECT id_category FROM categories WHERE category_name='Игровой набор'), 15, 0, 'Игровой набор «Стройплощадка Кран-Паркс Junion» — это большая игрушечная парковка с деревянными машинками и настоящим подъёмным краном, придуманная в Яндексе настоящими родителями.', '6.jpg'),
|
|
80
|
+
('S72AM3', 'Синтезатор детский с микрофоном 61 клавиша', 'шт.', 1749, (SELECT id_supplier FROM suppliers WHERE supplier_name='CHILITOY'), (SELECT id_manufacturer FROM manufacturers WHERE manufacturer_name='Junion'), (SELECT id_category FROM categories WHERE category_name='Детский музыкальный инструмент'), 10, 35, 'Откройте для ребенка дверь в мир музыки с детским синтезатором! Этот компактный инструмент с микрофоном станет верным другом для юных музыкантов, помогая им развивать творческий потенциал и получать удовольствие от игры.', '7.jpg'),
|
|
81
|
+
('2G3280', 'Деревянный игровой набор JUNION Стройплощадка "Кран-Паркс" с подъёмным, строительным краном и машинками, 18 предметов, подвижные элементы', 'шт.', 1624, (SELECT id_supplier FROM suppliers WHERE supplier_name='Vinylon'), (SELECT id_manufacturer FROM manufacturers WHERE manufacturer_name='Junion'), (SELECT id_category FROM categories WHERE category_name='Игровой набор'), 9, 20, 'Игровой набор «Стройплощадка Кран-Паркс Junion» — это большая игрушечная парковка с деревянными машинками и настоящим подъёмным краном, придуманная в Яндексе настоящими родителями.', '8.jpg'),
|
|
82
|
+
('MIO8YV', 'Музыкальная игрушка интерактивная Пульт, детский прорезыватель для малышей', 'шт.', 305, (SELECT id_supplier FROM suppliers WHERE supplier_name='Vinylon'), (SELECT id_manufacturer FROM manufacturers WHERE manufacturer_name='BambiniFelici'), (SELECT id_category FROM categories WHERE category_name='Детский музыкальный инструмент'), 9, 31, 'Музыкальная игрушка интерактивная Пульт, детский прорезыватель для малышей', '9.jpg'),
|
|
83
|
+
('UER2QD', 'Большой набор опытов и экспериментов для детей 14 в 1', 'шт.', 2506, (SELECT id_supplier FROM suppliers WHERE supplier_name='Vinylon'), (SELECT id_manufacturer FROM manufacturers WHERE manufacturer_name='BambiniFelici'), (SELECT id_category FROM categories WHERE category_name='Игровой набор'), 8, 27, 'Большой набор опытов и экспериментов для детей 14 в 1', '10.jpg');
|
|
84
|
+
|
|
85
|
+
INSERT INTO orders (order_number, order_date, delivery_date, id_pickup_point, id_client, receive_code, id_status) VALUES
|
|
86
|
+
(1, '2025-02-27', '2025-04-20', 1, (SELECT id_user FROM users WHERE full_name='Степанов Михаил Артёмович' AND id_role=(SELECT id_role FROM roles WHERE role_code='client') LIMIT 1), 901, (SELECT id_status FROM order_statuses WHERE status_name='Завершен')),
|
|
87
|
+
(2, '2024-09-28', '2025-04-21', 11, (SELECT id_user FROM users WHERE full_name='Ворсин Петр Евгеньевич' AND id_role=(SELECT id_role FROM roles WHERE role_code='client') LIMIT 1), 902, (SELECT id_status FROM order_statuses WHERE status_name='Завершен')),
|
|
88
|
+
(3, '2025-03-21', '2025-04-22', 2, (SELECT id_user FROM users WHERE full_name='Старикова Елена Павловна' AND id_role=(SELECT id_role FROM roles WHERE role_code='client') LIMIT 1), 903, (SELECT id_status FROM order_statuses WHERE status_name='Завершен')),
|
|
89
|
+
(4, '2025-02-20', '2025-04-23', 11, (SELECT id_user FROM users WHERE full_name='Сазонов Руслан Германович' AND id_role=(SELECT id_role FROM roles WHERE role_code='client') LIMIT 1), 904, (SELECT id_status FROM order_statuses WHERE status_name='Завершен')),
|
|
90
|
+
(5, '2025-03-17', '2025-04-24', 2, (SELECT id_user FROM users WHERE full_name='Степанов Михаил Артёмович' AND id_role=(SELECT id_role FROM roles WHERE role_code='client') LIMIT 1), 905, (SELECT id_status FROM order_statuses WHERE status_name='Завершен')),
|
|
91
|
+
(6, '2025-03-01', '2025-04-25', 15, (SELECT id_user FROM users WHERE full_name='Ворсин Петр Евгеньевич' AND id_role=(SELECT id_role FROM roles WHERE role_code='client') LIMIT 1), 906, (SELECT id_status FROM order_statuses WHERE status_name='Завершен')),
|
|
92
|
+
(7, '2025-02-28', '2025-04-26', 3, (SELECT id_user FROM users WHERE full_name='Старикова Елена Павловна' AND id_role=(SELECT id_role FROM roles WHERE role_code='client') LIMIT 1), 907, (SELECT id_status FROM order_statuses WHERE status_name='Завершен')),
|
|
93
|
+
(8, '2025-03-31', '2025-04-27', 19, (SELECT id_user FROM users WHERE full_name='Сазонов Руслан Германович' AND id_role=(SELECT id_role FROM roles WHERE role_code='client') LIMIT 1), 908, (SELECT id_status FROM order_statuses WHERE status_name='Новый')),
|
|
94
|
+
(9, '2025-04-02', '2025-04-28', 5, (SELECT id_user FROM users WHERE full_name='Старикова Елена Павловна' AND id_role=(SELECT id_role FROM roles WHERE role_code='client') LIMIT 1), 909, (SELECT id_status FROM order_statuses WHERE status_name='Новый')),
|
|
95
|
+
(10, '2025-04-03', '2025-04-29', 19, (SELECT id_user FROM users WHERE full_name='Сазонов Руслан Германович' AND id_role=(SELECT id_role FROM roles WHERE role_code='client') LIMIT 1), 910, (SELECT id_status FROM order_statuses WHERE status_name='Новый'));
|
|
96
|
+
|
|
97
|
+
INSERT INTO order_items (id_order, id_product, quantity) VALUES
|
|
98
|
+
((SELECT id_order FROM orders WHERE order_number=1), (SELECT id_product FROM products WHERE article='PMEZMH'), 2),
|
|
99
|
+
((SELECT id_order FROM orders WHERE order_number=1), (SELECT id_product FROM products WHERE article='BPV4MM'), 2),
|
|
100
|
+
((SELECT id_order FROM orders WHERE order_number=2), (SELECT id_product FROM products WHERE article='JVL42J'), 1),
|
|
101
|
+
((SELECT id_order FROM orders WHERE order_number=2), (SELECT id_product FROM products WHERE article='F895RB'), 1),
|
|
102
|
+
((SELECT id_order FROM orders WHERE order_number=3), (SELECT id_product FROM products WHERE article='3XBOTN'), 10),
|
|
103
|
+
((SELECT id_order FROM orders WHERE order_number=3), (SELECT id_product FROM products WHERE article='3L7RCZ'), 10),
|
|
104
|
+
((SELECT id_order FROM orders WHERE order_number=4), (SELECT id_product FROM products WHERE article='S72AM3'), 5),
|
|
105
|
+
((SELECT id_order FROM orders WHERE order_number=4), (SELECT id_product FROM products WHERE article='2G3280'), 4),
|
|
106
|
+
((SELECT id_order FROM orders WHERE order_number=5), (SELECT id_product FROM products WHERE article='MIO8YV'), 2),
|
|
107
|
+
((SELECT id_order FROM orders WHERE order_number=5), (SELECT id_product FROM products WHERE article='UER2QD'), 2),
|
|
108
|
+
((SELECT id_order FROM orders WHERE order_number=6), (SELECT id_product FROM products WHERE article='PMEZMH'), 2),
|
|
109
|
+
((SELECT id_order FROM orders WHERE order_number=6), (SELECT id_product FROM products WHERE article='BPV4MM'), 2),
|
|
110
|
+
((SELECT id_order FROM orders WHERE order_number=7), (SELECT id_product FROM products WHERE article='JVL42J'), 1),
|
|
111
|
+
((SELECT id_order FROM orders WHERE order_number=7), (SELECT id_product FROM products WHERE article='F895RB'), 1),
|
|
112
|
+
((SELECT id_order FROM orders WHERE order_number=8), (SELECT id_product FROM products WHERE article='3XBOTN'), 10),
|
|
113
|
+
((SELECT id_order FROM orders WHERE order_number=8), (SELECT id_product FROM products WHERE article='3L7RCZ'), 10),
|
|
114
|
+
((SELECT id_order FROM orders WHERE order_number=9), (SELECT id_product FROM products WHERE article='S72AM3'), 5),
|
|
115
|
+
((SELECT id_order FROM orders WHERE order_number=9), (SELECT id_product FROM products WHERE article='2G3280'), 4),
|
|
116
|
+
((SELECT id_order FROM orders WHERE order_number=10), (SELECT id_product FROM products WHERE article='MIO8YV'), 2),
|
|
117
|
+
((SELECT id_order FROM orders WHERE order_number=10), (SELECT id_product FROM products WHERE article='UER2QD'), 2);
|
|
118
|
+
|
|
119
|
+
SELECT setval('orders_id_order_seq', (SELECT MAX(id_order) FROM orders));
|
|
120
|
+
|
|
121
|
+
SELECT setval('products_id_product_seq', (SELECT MAX(id_product) FROM products));
|