103 tidak sesuai
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Check stock moves for WH/MO/00012 across databases
|
||||
"""
|
||||
import sys
|
||||
import psycopg2
|
||||
import configparser
|
||||
|
||||
# Parse config to get connection details
|
||||
config_file = 'C:\\addon14\\odoo.conf'
|
||||
config = configparser.ConfigParser()
|
||||
config.read(config_file)
|
||||
|
||||
# Connect to PostgreSQL
|
||||
conn = psycopg2.connect(
|
||||
host=config.get('options', 'db_host', fallback='localhost'),
|
||||
port=config.get('options', 'db_port', fallback='5432'),
|
||||
user=config.get('options', 'db_user', fallback='odoo'),
|
||||
password=config.get('options', 'db_password', fallback='odoo'),
|
||||
dbname='postgres'
|
||||
)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Get all databases
|
||||
cursor.execute("SELECT datname FROM pg_database WHERE datistemplate = false ORDER BY datname;")
|
||||
databases = [db[0] for db in cursor.fetchall()]
|
||||
cursor.close()
|
||||
conn.close()
|
||||
|
||||
print("Searching for MO WH/MO/00012 across databases...")
|
||||
print("="*80)
|
||||
|
||||
# Try all databases
|
||||
for db_name in databases:
|
||||
if db_name in ['postgres', 'template0', 'template1']:
|
||||
continue
|
||||
|
||||
print(f"Checking database: {db_name}...", end=' ')
|
||||
|
||||
try:
|
||||
# Connect to the database
|
||||
conn = psycopg2.connect(
|
||||
host=config.get('options', 'db_host', fallback='localhost'),
|
||||
port=config.get('options', 'db_port', fallback='5432'),
|
||||
user=config.get('options', 'db_user', fallback='odoo'),
|
||||
password=config.get('options', 'db_password', fallback='odoo'),
|
||||
dbname=db_name
|
||||
)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Check if mrp_production table exists
|
||||
cursor.execute("""
|
||||
SELECT EXISTS (
|
||||
SELECT FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = 'mrp_production'
|
||||
);
|
||||
""")
|
||||
|
||||
|
||||
if cursor.fetchone()[0]:
|
||||
print("has mrp_production")
|
||||
# Check for WH/MO/00012
|
||||
cursor.execute("""
|
||||
SELECT id, name, product_id, state
|
||||
FROM mrp_production
|
||||
WHERE name = 'WH/MO/00012'
|
||||
""")
|
||||
|
||||
result = cursor.fetchone()
|
||||
if not result:
|
||||
# List recent MOs
|
||||
cursor.execute("""
|
||||
SELECT id, name, state, create_date
|
||||
FROM mrp_production
|
||||
WHERE name LIKE 'WH/MO/%'
|
||||
ORDER BY create_date DESC
|
||||
LIMIT 5
|
||||
""")
|
||||
recent_mos = cursor.fetchall()
|
||||
if recent_mos:
|
||||
print(f" (not found, but has {len(recent_mos)} recent WH/MO/* orders)")
|
||||
for mo in recent_mos:
|
||||
print(f" - {mo[1]} (state: {mo[2]})")
|
||||
else:
|
||||
print(" (no WH/MO/* orders found)")
|
||||
cursor.close()
|
||||
conn.close()
|
||||
continue
|
||||
|
||||
if result:
|
||||
print(f"\n✅ FOUND in database: {db_name}")
|
||||
print(f" MO ID: {result[0]}")
|
||||
print(f" MO Name: {result[1]}")
|
||||
print(f" Product ID: {result[2]}")
|
||||
print(f" State: {result[3]}")
|
||||
|
||||
mo_id = result[0]
|
||||
|
||||
# Get product name
|
||||
cursor.execute("""
|
||||
SELECT pt.name->>'en_US' as name
|
||||
FROM product_product pp
|
||||
JOIN product_template pt ON pp.product_tmpl_id = pt.id
|
||||
WHERE pp.id = %s
|
||||
""", (result[2],))
|
||||
product_name = cursor.fetchone()
|
||||
if product_name:
|
||||
print(f" Product: {product_name[0]}")
|
||||
|
||||
# Get stock moves for raw materials
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
sm.id,
|
||||
pp.id as product_id,
|
||||
pt.name->>'en_US' as product_name,
|
||||
sm.product_uom_qty,
|
||||
sm.reserved_availability,
|
||||
sm.quantity_done,
|
||||
sm.state,
|
||||
sm.scada_equipment_id,
|
||||
me.name as equipment_name,
|
||||
me.equipment_code
|
||||
FROM stock_move sm
|
||||
JOIN product_product pp ON sm.product_id = pp.id
|
||||
JOIN product_template pt ON pp.product_tmpl_id = pt.id
|
||||
LEFT JOIN maintenance_equipment me ON sm.scada_equipment_id = me.id
|
||||
WHERE sm.raw_material_production_id = %s
|
||||
ORDER BY pt.name, sm.id
|
||||
""", (mo_id,))
|
||||
|
||||
moves = cursor.fetchall()
|
||||
|
||||
print(f"\n Raw Material Moves: {len(moves)}")
|
||||
print(" " + "="*76)
|
||||
|
||||
from collections import defaultdict
|
||||
product_summary = defaultdict(lambda: {'qty': 0, 'reserved': 0, 'consumed': 0, 'moves': []})
|
||||
|
||||
for move in moves:
|
||||
move_id, prod_id, prod_name, qty, reserved, consumed, state, equip_id, equip_name, equip_code = move
|
||||
|
||||
print(f"\n Move ID: {move_id}")
|
||||
print(f" Product: [{prod_id}] {prod_name}")
|
||||
print(f" To Consume: {qty}")
|
||||
print(f" Reserved: {reserved}")
|
||||
print(f" Consumed: {consumed}")
|
||||
print(f" State: {state}")
|
||||
print(f" Equipment: {equip_name} ({equip_code})" if equip_name else " Equipment: None")
|
||||
|
||||
# Group by product ID (as API does)
|
||||
product_summary[prod_id]['name'] = prod_name
|
||||
product_summary[prod_id]['qty'] += qty
|
||||
product_summary[prod_id]['reserved'] += reserved
|
||||
product_summary[prod_id]['consumed'] += consumed
|
||||
product_summary[prod_id]['moves'].append({
|
||||
'id': move_id,
|
||||
'qty': qty,
|
||||
'equipment': f"{equip_name} ({equip_code})" if equip_name else "None"
|
||||
})
|
||||
|
||||
print("\n " + "="*76)
|
||||
print(" API Response Summary (grouped by product):")
|
||||
print(" " + "="*76)
|
||||
|
||||
for prod_id, data in product_summary.items():
|
||||
if len(data['moves']) > 1:
|
||||
print(f"\n ⚠️ {data['name']}")
|
||||
print(f" API will return: {data['qty']} kg (SUM of {len(data['moves'])} moves)")
|
||||
for m in data['moves']:
|
||||
print(f" - Move {m['id']}: {m['qty']} kg from {m['equipment']}")
|
||||
else:
|
||||
m = data['moves'][0]
|
||||
print(f"\n ✓ {data['name']}")
|
||||
print(f" API returns: {data['qty']} kg from {m['equipment']}")
|
||||
|
||||
break
|
||||
|
||||
cursor.close()
|
||||
conn.close()
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error checking {db_name}: {e}")
|
||||
continue
|
||||
|
||||
print("\n" + "="*80)
|
||||
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Check stock moves for WH/MO/00012 to understand why API returns 352 instead of 350
|
||||
"""
|
||||
import xmlrpc.client
|
||||
|
||||
# Konfigurasi
|
||||
url = 'http://localhost:8070'
|
||||
db = 'odoo14c'
|
||||
username = 'admin'
|
||||
password = 'admin'
|
||||
|
||||
# Connect
|
||||
common = xmlrpc.client.ServerProxy(f'{url}/xmlrpc/2/common')
|
||||
uid = common.authenticate(db, username, password, {})
|
||||
models = xmlrpc.client.ServerProxy(f'{url}/xmlrpc/2/object')
|
||||
|
||||
print("="*80)
|
||||
print("Checking MO WH/MO/00012 - POLLAR ANGSA consumption")
|
||||
print("="*80)
|
||||
|
||||
# Find MO
|
||||
mo = models.execute_kw(db, uid, password, 'mrp.production', 'search_read',
|
||||
[[('name', '=', 'WH/MO/00012')]],
|
||||
{'fields': ['id', 'name', 'product_id', 'scada_equipment_id'], 'limit': 1}
|
||||
)
|
||||
|
||||
if not mo:
|
||||
print("MO not found!")
|
||||
exit(1)
|
||||
|
||||
mo = mo[0]
|
||||
print(f"\nMO ID: {mo['id']}")
|
||||
print(f"MO Name: {mo['name']}")
|
||||
print(f"Product: {mo['product_id']}")
|
||||
print(f"MO Equipment: {mo['scada_equipment_id']}")
|
||||
|
||||
# Find all move_raw_ids for POLLAR ANGSA (product_id might be 40025 based on screenshot)
|
||||
print("\n" + "="*80)
|
||||
print("Stock Moves for Raw Materials (move_raw_ids):")
|
||||
print("="*80)
|
||||
|
||||
moves = models.execute_kw(db, uid, password, 'stock.move', 'search_read',
|
||||
[[('raw_material_production_id', '=', mo['id'])]],
|
||||
{'fields': ['id', 'name', 'product_id', 'product_uom_qty', 'reserved_availability',
|
||||
'quantity_done', 'state', 'scada_equipment_id']}
|
||||
)
|
||||
|
||||
print(f"\nTotal moves found: {len(moves)}")
|
||||
|
||||
pollar_angsa_total = 0
|
||||
for move in moves:
|
||||
product_name = move['product_id'][1] if move['product_id'] else 'No Product'
|
||||
equipment_name = move['scada_equipment_id'][1] if move['scada_equipment_id'] else 'No Equipment'
|
||||
|
||||
print(f"\nMove ID: {move['id']}")
|
||||
print(f" Product: {product_name}")
|
||||
print(f" To Consume (product_uom_qty): {move['product_uom_qty']}")
|
||||
print(f" Reserved: {move['reserved_availability']}")
|
||||
print(f" Consumed (quantity_done): {move['quantity_done']}")
|
||||
print(f" State: {move['state']}")
|
||||
print(f" Equipment: {equipment_name}")
|
||||
|
||||
if 'POLLAR' in product_name.upper() or 'ANGSA' in product_name.upper():
|
||||
pollar_angsa_total += move['product_uom_qty']
|
||||
print(f" >>> This is POLLAR ANGSA! Adding {move['product_uom_qty']} to total")
|
||||
|
||||
print("\n" + "="*80)
|
||||
print(f"POLLAR ANGSA Total from API perspective: {pollar_angsa_total}")
|
||||
print("="*80)
|
||||
|
||||
# Check if there are multiple moves for the same product
|
||||
print("\n" + "="*80)
|
||||
print("Grouping by Product (like the API does):")
|
||||
print("="*80)
|
||||
|
||||
from collections import defaultdict
|
||||
product_groups = defaultdict(lambda: {'qty': 0, 'reserved': 0, 'consumed': 0, 'moves': []})
|
||||
|
||||
for move in moves:
|
||||
product_id = move['product_id'][0] if move['product_id'] else None
|
||||
product_name = move['product_id'][1] if move['product_id'] else 'No Product'
|
||||
|
||||
if product_id:
|
||||
product_groups[product_id]['qty'] += move['product_uom_qty']
|
||||
product_groups[product_id]['reserved'] += move['reserved_availability']
|
||||
product_groups[product_id]['consumed'] += move['quantity_done']
|
||||
product_groups[product_id]['moves'].append({
|
||||
'id': move['id'],
|
||||
'name': product_name,
|
||||
'qty': move['product_uom_qty'],
|
||||
'equipment': move['scada_equipment_id'][1] if move['scada_equipment_id'] else 'No Equipment'
|
||||
})
|
||||
|
||||
for prod_id, data in product_groups.items():
|
||||
if len(data['moves']) > 1:
|
||||
print(f"\n⚠️ Product ID {prod_id} has {len(data['moves'])} moves:")
|
||||
print(f" Total to_consume: {data['qty']}")
|
||||
for m in data['moves']:
|
||||
print(f" - Move {m['id']}: {m['name']} - {m['qty']} kg (Equipment: {m['equipment']})")
|
||||
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Check stock moves for WH/MO/00012 using Odoo Shell
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Setup Odoo environment
|
||||
sys.path.insert(0, 'C:\\odoo14c\\server')
|
||||
import odoo
|
||||
from odoo import api, SUPERUSER_ID
|
||||
|
||||
# Initialize Odoo
|
||||
# We need to figure out the database name first
|
||||
print("Available databases:")
|
||||
odoo.service.db.list_dbs(force=True)
|
||||
|
||||
# Try common database names
|
||||
db_names = ['odoo', 'odoo14', 'postgres', 'scada']
|
||||
config_file = 'C:\\addon14\\odoo.conf'
|
||||
|
||||
# Parse config to get connection details
|
||||
import configparser
|
||||
config = configparser.ConfigParser()
|
||||
config.read(config_file)
|
||||
|
||||
print("\nDatabase configuration:")
|
||||
print(f" db_host: {config.get('options', 'db_host', fallback='localhost')}")
|
||||
print(f" db_port: {config.get('options', 'db_port', fallback='5432')}")
|
||||
print(f" db_user: {config.get('options', 'db_user', fallback='odoo')}")
|
||||
|
||||
# Use psycopg2 to list databases
|
||||
import psycopg2
|
||||
|
||||
try:
|
||||
conn = psycopg2.connect(
|
||||
host=config.get('options', 'db_host', fallback='localhost'),
|
||||
port=config.get('options', 'db_port', fallback='5432'),
|
||||
user=config.get('options', 'db_user', fallback='odoo'),
|
||||
password=config.get('options', 'db_password', fallback='odoo'),
|
||||
dbname='postgres' # Connect to postgres db to list all databases
|
||||
)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT datname FROM pg_database WHERE datistemplate = false ORDER BY datname;")
|
||||
databases = cursor.fetchall()
|
||||
|
||||
print("\nAvailable databases:")
|
||||
for db in databases:
|
||||
print(f" - {db[0]}")
|
||||
|
||||
cursor.close()
|
||||
conn.close()
|
||||
|
||||
# Try to find the most likely database
|
||||
likely_db = None
|
||||
for db in databases:
|
||||
db_name = db[0]
|
||||
if db_name in ['odoo14', 'odoo', 'scada', 'grt']:
|
||||
likely_db = db_name
|
||||
break
|
||||
|
||||
if not likely_db and databases:
|
||||
# Use first non-system database
|
||||
for db in databases:
|
||||
if db[0] not in ['postgres', 'template0', 'template1']:
|
||||
likely_db = db[0]
|
||||
break
|
||||
|
||||
if likely_db:
|
||||
print(f"\nUsing database: {likely_db}")
|
||||
|
||||
# Now connect to Odoo with the database
|
||||
odoo.tools.config.parse_config(['--config', config_file, '-d', likely_db])
|
||||
with api.Environment.manage():
|
||||
registry = odoo.registry(likely_db)
|
||||
with registry.cursor() as cr:
|
||||
env = api.Environment(cr, SUPERUSER_ID, {})
|
||||
|
||||
# Find MO
|
||||
mo = env['mrp.production'].search([('name', '=', 'WH/MO/00012')], limit=1)
|
||||
|
||||
if not mo:
|
||||
print("\n⚠️ MO WH/MO/00012 not found!")
|
||||
else:
|
||||
print("\n" + "="*80)
|
||||
print(f"MO: {mo.name}")
|
||||
print(f"Product: {mo.product_id.display_name}")
|
||||
print(f"MO Equipment: {mo.scada_equipment_id.name if mo.scada_equipment_id else 'None'}")
|
||||
print("="*80)
|
||||
|
||||
# Check raw material moves
|
||||
print("\nRaw Material Moves (move_raw_ids):")
|
||||
print("="*80)
|
||||
|
||||
from collections import defaultdict
|
||||
product_summary = defaultdict(lambda: {'qty': 0, 'reserved': 0, 'consumed': 0, 'moves': []})
|
||||
|
||||
for move in mo.move_raw_ids:
|
||||
equipment_name = move.scada_equipment_id.name if move.scada_equipment_id else 'No Equipment'
|
||||
equipment_code = move.scada_equipment_id.equipment_code if move.scada_equipment_id else 'No Code'
|
||||
|
||||
print(f"\nMove ID: {move.id}")
|
||||
print(f" Product: {move.product_id.display_name}")
|
||||
print(f" To Consume: {move.product_uom_qty} {move.product_uom.name}")
|
||||
print(f" Reserved: {move.reserved_availability}")
|
||||
print(f" Consumed: {move.quantity_done}")
|
||||
print(f" State: {move.state}")
|
||||
print(f" Equipment: {equipment_name} ({equipment_code})")
|
||||
|
||||
# Group by product
|
||||
product_key = (move.product_id.product_tmpl_id.id, move.product_id.id)
|
||||
product_summary[product_key]['qty'] += move.product_uom_qty
|
||||
product_summary[product_key]['reserved'] += move.reserved_availability
|
||||
product_summary[product_key]['consumed'] += move.quantity_done
|
||||
product_summary[product_key]['moves'].append({
|
||||
'id': move.id,
|
||||
'name': move.product_id.display_name,
|
||||
'qty': move.product_uom_qty,
|
||||
'equipment': f"{equipment_name} ({equipment_code})"
|
||||
})
|
||||
|
||||
print("\n" + "="*80)
|
||||
print("Summary grouped by Product (as API does):")
|
||||
print("="*80)
|
||||
|
||||
for product_key, data in product_summary.items():
|
||||
if len(data['moves']) > 1:
|
||||
print(f"\n⚠️ Product has {len(data['moves'])} moves:")
|
||||
print(f" API will return total: {data['qty']} kg")
|
||||
for m in data['moves']:
|
||||
print(f" - Move {m['id']}: {m['qty']} kg from {m['equipment']}")
|
||||
else:
|
||||
m = data['moves'][0]
|
||||
print(f"\n✓ {m['name']}: {data['qty']} kg from {m['equipment']}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\nError: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
'name': 'SCADA for Odoo - Manufacturing Integration',
|
||||
'version': '7.0.74',
|
||||
'version': '7.0.75',
|
||||
'category': 'manufacturing',
|
||||
'license': 'LGPL-3',
|
||||
'author': 'PT. Gagak Rimang Teknologi',
|
||||
|
||||
Binary file not shown.
Binary file not shown.
+3057
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Test API fix: Verify that consumption is now grouped by equipment correctly
|
||||
"""
|
||||
import xmlrpc.client
|
||||
|
||||
# Konfigurasi - sesuaikan dengan database yang memiliki MO
|
||||
url = 'http://localhost:8070'
|
||||
db = 'manu14' # atau 'manukanjabung' yang memiliki MO aktif
|
||||
username = 'admin'
|
||||
password = 'admin'
|
||||
|
||||
# Connect
|
||||
common = xmlrpc.client.ServerProxy(f'{url}/xmlrpc/2/common')
|
||||
uid = common.authenticate(db, username, password, {})
|
||||
models = xmlrpc.client.ServerProxy(f'{url}/xmlrpc/2/object')
|
||||
|
||||
print("="*80)
|
||||
print("Testing API Fix: Equipment-based Grouping")
|
||||
print("="*80)
|
||||
|
||||
# Find any confirmed MO
|
||||
mo = models.execute_kw(db, uid, password, 'mrp.production', 'search_read',
|
||||
[[('state', 'in', ['confirmed', 'progress'])]],
|
||||
{'fields': ['id', 'name', 'product_id'], 'limit': 1}
|
||||
)
|
||||
|
||||
if not mo:
|
||||
print("\nNo confirmed/in-progress MO found in database:", db)
|
||||
print("\nTry checking these databases:")
|
||||
print(" - manu14")
|
||||
print(" - manukanjabung")
|
||||
print(" - ilo")
|
||||
exit(1)
|
||||
|
||||
mo = mo[0]
|
||||
mo_id = mo['id']
|
||||
mo_name = mo['name']
|
||||
|
||||
print(f"\nTesting with MO: {mo_name} (ID: {mo_id})")
|
||||
print("="*80)
|
||||
|
||||
# Get stock moves
|
||||
moves = models.execute_kw(db, uid, password, 'stock.move', 'search_read',
|
||||
[[('raw_material_production_id', '=', mo_id)]],
|
||||
{'fields': ['id', 'product_id', 'product_uom_qty', 'reserved_availability',
|
||||
'quantity_done', 'scada_equipment_id']}
|
||||
)
|
||||
|
||||
print(f"\nTotal stock moves: {len(moves)}")
|
||||
print("\n" + "="*80)
|
||||
print("OLD BEHAVIOR (Grouped by Product Only):")
|
||||
print("="*80)
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
# Old way: group by product only
|
||||
old_groups = defaultdict(lambda: {'qty': 0, 'reserved': 0, 'consumed': 0, 'count': 0})
|
||||
for move in moves:
|
||||
prod_id = move['product_id'][0] if move['product_id'] else None
|
||||
prod_name = move['product_id'][1] if move['product_id'] else 'Unknown'
|
||||
|
||||
old_groups[prod_id]['name'] = prod_name
|
||||
old_groups[prod_id]['qty'] += move['product_uom_qty']
|
||||
old_groups[prod_id]['reserved'] += move['reserved_availability']
|
||||
old_groups[prod_id]['consumed'] += move['quantity_done']
|
||||
old_groups[prod_id]['count'] += 1
|
||||
|
||||
for prod_id, data in old_groups.items():
|
||||
print(f"\n{data['name']}")
|
||||
print(f" To Consume: {data['qty']} kg")
|
||||
print(f" Reserved: {data['reserved']} kg")
|
||||
print(f" Consumed: {data['consumed']} kg")
|
||||
if data['count'] > 1:
|
||||
print(f" ⚠️ Summed from {data['count']} moves (POTENTIAL ISSUE)")
|
||||
|
||||
print("\n" + "="*80)
|
||||
print("NEW BEHAVIOR (Grouped by Product + Equipment):")
|
||||
print("="*80)
|
||||
|
||||
# New way: group by product + equipment
|
||||
new_groups = defaultdict(lambda: {'qty': 0, 'reserved': 0, 'consumed': 0})
|
||||
for move in moves:
|
||||
prod_id = move['product_id'][0] if move['product_id'] else None
|
||||
prod_name = move['product_id'][1] if move['product_id'] else 'Unknown'
|
||||
equip_id = move['scada_equipment_id'][0] if move['scada_equipment_id'] else None
|
||||
equip_name = move['scada_equipment_id'][1] if move['scada_equipment_id'] else 'No Equipment'
|
||||
|
||||
key = (prod_id, equip_id)
|
||||
new_groups[key]['name'] = prod_name
|
||||
new_groups[key]['equipment'] = equip_name
|
||||
new_groups[key]['qty'] += move['product_uom_qty']
|
||||
new_groups[key]['reserved'] += move['reserved_availability']
|
||||
new_groups[key]['consumed'] += move['quantity_done']
|
||||
|
||||
for (prod_id, equip_id), data in new_groups.items():
|
||||
print(f"\n{data['name']} from {data['equipment']}")
|
||||
print(f" To Consume: {data['qty']} kg")
|
||||
print(f" Reserved: {data['reserved']} kg")
|
||||
print(f" Consumed: {data['consumed']} kg")
|
||||
print(f" ✓ Matches UI display")
|
||||
|
||||
print("\n" + "="*80)
|
||||
print("SUMMARY:")
|
||||
print("="*80)
|
||||
print(f"Old grouping: {len(old_groups)} items (grouped by product only)")
|
||||
print(f"New grouping: {len(new_groups)} items (grouped by product + equipment)")
|
||||
print("\nThe new grouping matches the UI display exactly, where each")
|
||||
print("component line shows the quantity for a specific equipment (SILO).")
|
||||
print("="*80)
|
||||
Reference in New Issue
Block a user