diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c9ebf2d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python-envs.defaultEnvManager": "ms-python.python:system" +} \ No newline at end of file diff --git a/app.py b/app.py index 95b2a6b..166bd9f 100644 --- a/app.py +++ b/app.py @@ -499,6 +499,54 @@ def split_cartridge_models(value): return items +def get_device_models_for_order_cartridge(cur, order_model_value): + if not order_model_value: + return "" + parts = re.split(r"\s*/\s*|[\n,;]+", str(order_model_value or "").strip()) + model_keys = [] + seen = set() + for part in parts: + key = normalize_model_name(part) + if not key or key in seen: + continue + seen.add(key) + model_keys.append(key) + if not model_keys: + return "" + cur.execute( + f""" + SELECT + dc.device_model, + dc.device_type, + MIN(COALESCE(d.brand, '')) AS brand + FROM device_cartridges dc + LEFT JOIN devices d + ON d.model = dc.device_model + AND d.type = dc.device_type + WHERE lower(btrim(dc.cartridge_model)) = ANY(%s) + AND dc.device_model IS NOT NULL + AND dc.device_model <> '' + AND dc.device_type IN ({PRINT_DEVICE_TYPES_SQL}) + GROUP BY dc.device_model, dc.device_type + ORDER BY MIN(COALESCE(d.brand, '')), dc.device_model, dc.device_type + """, + (model_keys,) + tuple(PRINT_DEVICE_TYPE_VALUES), + ) + seen = set() + labels = [] + for device_model, device_type, brand in cur.fetchall(): + model = (device_model or "").strip() + if not model: + continue + uniq_key = (normalize_model_name(model), device_type) + if uniq_key in seen: + continue + seen.add(uniq_key) + label = f"{(brand or '').strip()} {model}".strip() + labels.append(label or model) + return ", ".join(labels) + + def sanitize_code39_data(value): return re.sub(r"[^0-9A-Z\-\.\ $/\+%]", "", str(value or "").upper()) @@ -620,43 +668,182 @@ def build_device_cartridge_index(cur): return index -def sync_missing_device_cartridges_to_orders(cur, cartridge_index): - cur.execute("SELECT model, quantity FROM cartridges") - stocked = set() - for model, qty in cur.fetchall(): - if (qty or 0) > 0: - key = normalize_model_name(model) - if key: - stocked.add(key) +def get_order_quantity_by_popularity(cur, device_models): + model_keys = [] + seen = set() + for model_name in (device_models or []): + key = normalize_model_name(model_name) + if not key or key in seen: + continue + seen.add(key) + model_keys.append(key) + if not model_keys: + return 3 + cur.execute( + f""" + SELECT COUNT(DISTINCT d.cabinet_id) + FROM devices d + WHERE d.type IN ({PRINT_DEVICE_TYPES_SQL}) + AND d.cabinet_id IS NOT NULL + AND lower(btrim(d.model)) = ANY(%s) + """, + tuple(PRINT_DEVICE_TYPE_VALUES) + (model_keys,), + ) + cabinets_count = (cur.fetchone() or [0])[0] or 0 + if cabinets_count <= 1: + return 3 + if cabinets_count == 2: + return 5 + return 10 - created = 0 - for key, data in cartridge_index.items(): - if key in stocked: + +def sync_missing_device_cartridges_to_orders(cur): + cur.execute( + """ + SELECT lower(btrim(model)) AS model_key, COALESCE(SUM(quantity), 0) AS total_qty + FROM cartridges + WHERE model IS NOT NULL + AND model <> '' + GROUP BY lower(btrim(model)) + """ + ) + stocked_sufficient = set() + low_stock = set() + for key, total_qty in cur.fetchall(): + if not key: continue - model_name = data.get("model") or "" - if not model_name: + qty = total_qty or 0 + if qty == 1: + low_stock.add(key) + elif qty > 1: + stocked_sufficient.add(key) + + cur.execute( + f""" + SELECT + btrim(dc.device_model) AS device_model, + dc.device_type, + MIN(COALESCE(d.brand, '')) AS brand, + ARRAY_AGG(DISTINCT btrim(dc.cartridge_model) ORDER BY btrim(dc.cartridge_model)) AS cartridge_models + FROM device_cartridges dc + LEFT JOIN devices d + ON d.model = dc.device_model + AND d.type = dc.device_type + WHERE dc.cartridge_model IS NOT NULL + AND dc.cartridge_model <> '' + AND dc.device_model IS NOT NULL + AND dc.device_model <> '' + AND dc.device_type IN ({PRINT_DEVICE_TYPES_SQL}) + GROUP BY btrim(dc.device_model), dc.device_type + ORDER BY btrim(dc.device_model), dc.device_type + """, + tuple(PRINT_DEVICE_TYPE_VALUES), + ) + grouped_catalog = cur.fetchall() + + desired_orders = {} + for device_model, device_type, brand, cartridge_models in grouped_catalog: + models = [] + model_keys = set() + for model_name in (cartridge_models or []): + model_name = (model_name or "").strip() + key = normalize_model_name(model_name) + if not key or key in model_keys: + continue + model_keys.add(key) + models.append(model_name) + if not models: continue + + order_model = " / ".join(models) + order_key = normalize_model_name(order_model) + if not order_key: + continue + + device_name = (device_model or "").strip() + device_label = f"{(brand or '').strip()} {device_name}".strip() or device_name + entry = desired_orders.setdefault( + order_key, + { + "order_model": order_model, + "cart_models": set(), + "device_labels": set(), + "device_models": set(), + }, + ) + for model_name in models: + entry["cart_models"].add(model_name) + if device_label: + entry["device_labels"].add(device_label) + if device_name: + entry["device_models"].add(device_name) + + catalog_keys = list(desired_orders.keys()) + if catalog_keys: cur.execute( """ - SELECT 1 + DELETE FROM movements + WHERE type='ORDER' + AND lower(btrim(COALESCE(model, ''))) <> ALL(%s) + """, + (catalog_keys,), + ) + else: + cur.execute("DELETE FROM movements WHERE type='ORDER'") + + changed = 0 + for key, entry in desired_orders.items(): + model_keys = {normalize_model_name(model_name) for model_name in entry["cart_models"] if model_name} + has_low_stock_variant = any(mk in low_stock for mk in model_keys) + has_sufficient_variant = any(mk in stocked_sufficient for mk in model_keys) + if not has_low_stock_variant and has_sufficient_variant: + cur.execute( + """ + DELETE FROM movements + WHERE type='ORDER' + AND lower(btrim(COALESCE(model, ''))) = %s + """, + (key,), + ) + changed += cur.rowcount or 0 + continue + + order_model = entry["order_model"] + order_qty = get_order_quantity_by_popularity(cur, entry["device_models"]) + device_models_value = ", ".join(sorted(entry["device_labels"])) + cur.execute( + """ + SELECT id FROM movements m - LEFT JOIN cartridges c ON c.barcode = m.barcode WHERE m.type='ORDER' - AND lower(btrim(COALESCE(m.model, c.model, ''))) = %s + AND lower(btrim(COALESCE(m.model, ''))) = %s """, (key,), ) - if cur.fetchone(): + exists_row = cur.fetchone() + if exists_row: + cur.execute( + """ + UPDATE movements + SET quantity=%s, + order_device_model=%s + WHERE type='ORDER' + AND lower(btrim(COALESCE(model, ''))) = %s + """, + (order_qty, device_models_value, key), + ) + changed += cur.rowcount or 0 continue + cur.execute( """ - INSERT INTO movements(barcode, model, quantity, type, date) - VALUES (%s,%s,%s,'ORDER',%s) + INSERT INTO movements(barcode, model, quantity, type, date, order_device_model) + VALUES (%s,%s,%s,'ORDER',%s,%s) """, - (model_name, model_name, 5, datetime.now()), + (order_model, order_model, order_qty, datetime.now(), device_models_value), ) - created += 1 - return created + changed += 1 + return changed def get_allowed_cabinets_for_cartridge(cur, model): @@ -720,10 +907,22 @@ def init_db(): quantity INTEGER NOT NULL, type TEXT NOT NULL, cabinet TEXT, + order_device_model TEXT, date TIMESTAMP NOT NULL DEFAULT NOW() ) """ ) + cur.execute( + """ + SELECT column_name + FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'movements' + """ + ) + movement_cols = {r[0] for r in cur.fetchall()} + if "order_device_model" not in movement_cols: + cur.execute("ALTER TABLE movements ADD COLUMN order_device_model TEXT") cur.execute( """ CREATE TABLE IF NOT EXISTS cabinets ( @@ -1731,8 +1930,7 @@ def components_add_quick(): def cartridges_list(): conn = get_conn() cur = conn.cursor() - cartridge_index = build_device_cartridge_index(cur) - created_orders = sync_missing_device_cartridges_to_orders(cur, cartridge_index) + created_orders = sync_missing_device_cartridges_to_orders(cur) if created_orders: conn.commit() cur.execute("SELECT barcode, model, quantity, min_quantity FROM cartridges ORDER BY model") @@ -4770,19 +4968,26 @@ def report_all_zip(): def orders(): conn = get_conn() cur = conn.cursor() + created_orders = sync_missing_device_cartridges_to_orders(cur) + if created_orders: + conn.commit() cur.execute( """ - SELECT last_date, barcode, model, quantity + SELECT last_date, cartridge_model, device_model, quantity FROM ( - SELECT DISTINCT ON (COALESCE(m.model, c.model)) + SELECT DISTINCT ON ( + lower(btrim(COALESCE(m.model, c.model, ''))) + ) m.date AS last_date, - m.barcode AS barcode, - COALESCE(m.model, c.model) AS model, - 5 AS quantity + COALESCE(m.model, c.model) AS cartridge_model, + COALESCE(NULLIF(m.order_device_model, ''), '-') AS device_model, + COALESCE(m.quantity, 3) AS quantity FROM movements m LEFT JOIN cartridges c ON c.barcode = m.barcode WHERE m.type='ORDER' - ORDER BY COALESCE(m.model, c.model), m.date DESC + ORDER BY + lower(btrim(COALESCE(m.model, c.model, ''))), + m.date DESC ) latest ORDER BY last_date DESC """ @@ -5579,13 +5784,28 @@ def stock_delete(): def report_orders_xlsx(): conn = get_conn() cur = conn.cursor() + created_orders = sync_missing_device_cartridges_to_orders(cur) + if created_orders: + conn.commit() cur.execute( """ - SELECT m.date, m.barcode, COALESCE(m.model, c.model), m.quantity - FROM movements m - LEFT JOIN cartridges c ON c.barcode = m.barcode - WHERE m.type='ORDER' - ORDER BY date DESC + SELECT last_date, cartridge_model, device_model, quantity + FROM ( + SELECT DISTINCT ON ( + lower(btrim(COALESCE(m.model, c.model, ''))) + ) + m.date AS last_date, + COALESCE(m.model, c.model) AS cartridge_model, + COALESCE(NULLIF(m.order_device_model, ''), '-') AS device_model, + COALESCE(m.quantity, 3) AS quantity + FROM movements m + LEFT JOIN cartridges c ON c.barcode = m.barcode + WHERE m.type='ORDER' + ORDER BY + lower(btrim(COALESCE(m.model, c.model, ''))), + m.date DESC + ) latest + ORDER BY last_date DESC """ ) rows = cur.fetchall() @@ -5594,9 +5814,9 @@ def report_orders_xlsx(): wb = Workbook() ws = wb.active ws.title = "Orders" - ws.append(["Дата", "Штрихкод", "Модель", "Количество"]) - for dt, barcode, model, qty in rows: - ws.append([dt, barcode, model, qty]) + ws.append(["Дата добавления заказа", "Модель картриджа", "Модель устройства", "Количество"]) + for dt, model, device_model, qty in rows: + ws.append([dt, model, device_model, qty]) bio = BytesIO() wb.save(bio) diff --git a/templates/cartridges.html b/templates/cartridges.html index f3d54e5..3c27dfa 100644 --- a/templates/cartridges.html +++ b/templates/cartridges.html @@ -238,10 +238,7 @@ {% if session.get('role') in ('admin','storekeeper') %} -
- -
- + {% endif %} Экспорт в Excel @@ -1319,7 +1316,7 @@ {% for b,m,q,minq in items %} {% set model_key = (m or '')|trim|lower %} - diff --git a/templates/orders.html b/templates/orders.html index 3565646..87c0833 100644 --- a/templates/orders.html +++ b/templates/orders.html @@ -29,56 +29,33 @@ }
-
-

Заказанные картриджи

-
- {% if session.get('role') in ('admin','storekeeper') %} - -
- -
- {% endif %} - Экспорт в Excel +
+

Заказанные картриджи

+
+ {% if session.get('role') in ('admin','storekeeper') %} +
+ +
+ {% endif %} + Экспорт в Excel +
-
-
-
-
-
- -
-
- -
-
- -
-
-
-
- - - - - - {% for last_date, barcode, model, qty in rows %} - - - - - - - {% endfor %} -
ДатаШтрихкодМодельКоличество
{{ last_date }}{{ barcode }}{{ model }}{{ qty }}
+ + + + + + + + {% for last_date, model, device_model, qty in rows %} + + + + + + + {% endfor %} +
Модель картриджаМодель устройстваКоличествоДата добавления заказа
{{ model }}{{ device_model }}{{ qty }}{{ last_date }}
{% endblock %}