103 tidak sesuai

This commit is contained in:
2026-03-04 18:09:17 +07:00
parent 9308831bb8
commit 15d9bb245e
8 changed files with 3597 additions and 1 deletions
+187
View File
@@ -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)
+101
View File
@@ -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']})")
+140
View File
@@ -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 -1
View File
@@ -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',
+3057
View File
File diff suppressed because it is too large Load Diff
+111
View File
@@ -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)