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 %}
-
- | {{ last_date }} |
- {{ barcode }} |
- {{ model }} |
- {{ qty }} |
-
- {% endfor %}
-
+
+
+ | Модель картриджа |
+ Модель устройства |
+ Количество |
+ Дата добавления заказа |
+
+ {% for last_date, model, device_model, qty in rows %}
+
+ | {{ model }} |
+ {{ device_model }} |
+ {{ qty }} |
+ {{ last_date }} |
+
+ {% endfor %}
+
{% endblock %}