Update endpoint mo_detail

This commit is contained in:
2026-03-04 19:24:36 +07:00
parent 15d9bb245e
commit d0ee49ed5b
17 changed files with 4533 additions and 9 deletions
+121
View File
@@ -0,0 +1,121 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Check BoM untuk WH/MO/00012 dan bandingkan dengan stock moves
"""
import xmlrpc.client
API_URL = 'http://localhost:8070'
DB_NAME = 'kanjabung_MRP'
USERNAME = 'admin'
PASSWORD = 'admin'
print("="*80)
print("BOM vs Stock Moves Analysis")
print("="*80)
# Connect
common = xmlrpc.client.ServerProxy(f'{API_URL}/xmlrpc/2/common')
uid = common.authenticate(DB_NAME, USERNAME, PASSWORD, {})
models = xmlrpc.client.ServerProxy(f'{API_URL}/xmlrpc/2/object')
# Find WH/MO/00012
print("\nStep 1: Get MO Details")
mos = models.execute_kw(DB_NAME, uid, PASSWORD, 'mrp.production', 'search_read',
[[('name', '=', 'WH/MO/00012')]],
{'fields': ['id', 'name', 'bom_id', 'product_id', 'product_qty'], 'limit': 1}
)
mo = mos[0]
mo_id = mo['id']
bom_id = mo['bom_id'][0] if mo['bom_id'] else None
print(f"MO: {mo['name']}")
print(f"Product: {mo['product_id'][1]} (Qty: {mo['product_qty']} kg)")
print(f"BoM ID: {mo['bom_id'][1] if mo['bom_id'] else 'None'}")
if not bom_id:
print("\n❌ No BoM assigned")
exit(1)
# Get BoM lines
print("\nStep 2: Get BoM Lines")
bom_lines = models.execute_kw(DB_NAME, uid, PASSWORD, 'mrp.bom.line', 'search_read',
[[('bom_id', '=', bom_id)]],
{
'fields': ['product_id', 'product_qty', 'product_uom_id', 'scada_equipment_id'],
'limit': 100
}
)
print(f"Found {len(bom_lines)} BoM lines:")
print()
bom_pollar = None
for line in bom_lines:
prod = line['product_id']
prod_name = prod[1] if prod else 'Unknown'
qty = line['product_qty']
uom = line['product_uom_id'][1] if line['product_uom_id'] else 'Unknown'
equip = line['scada_equipment_id']
equip_name = equip[1] if equip else 'No Equipment'
print(f"Product: {prod_name}")
print(f" Qty: {qty} {uom}")
print(f" Equipment: {equip_name}")
print()
# Track POLLAR ANGSA
if 'POLLAR' in prod_name.upper() and 'ANGSA' in prod_name.upper():
bom_pollar = {
'product': prod_name,
'qty': qty,
'equipment': equip_name
}
# Compare with stock moves
print("Step 3: Get Stock Moves")
moves = models.execute_kw(DB_NAME, uid, PASSWORD, 'stock.move', 'search_read',
[[('raw_material_production_id', '=', mo_id)]],
{
'fields': ['id', 'product_id', 'product_uom_qty', 'state', 'scada_equipment_id'],
'limit': 100
}
)
pollar_moves = [m for m in moves if 'POLLAR' in m['product_id'][1].upper() and 'ANGSA' in m['product_id'][1].upper()]
print(f"Found {len(pollar_moves)} POLLAR ANGSA stock moves:")
print()
total_moves_qty = 0
for idx, move in enumerate(pollar_moves, 1):
qty = move['product_uom_qty']
state = move['state']
equip_name = move['scada_equipment_id'][1] if move['scada_equipment_id'] else 'No Equipment'
print(f"Move #{idx} (ID: {move['id']}): {qty} kg - state: {state} - {equip_name}")
total_moves_qty += qty
# Analysis
print("\n" + "="*80)
print("ANALYSIS:")
print("="*80)
if bom_pollar:
print(f"\nBoM Line for POLLAR ANGSA:")
print(f" Quantity: {bom_pollar['qty']} kg")
print(f" Equipment: {bom_pollar['equipment']}")
print(f"\nTotal Stock Moves for POLLAR ANGSA: {total_moves_qty} kg")
if len(pollar_moves) > 1:
print(f"\n⚠️ Multiple moves detected!")
print(f" BoM expects: {bom_pollar['qty'] if bom_pollar else '?'} kg")
print(f" Actual moves: {len(pollar_moves)} moves totaling {total_moves_qty} kg")
print(f"\n Possible causes:")
print(f" 1. MO was modified after creation (quantity changed)")
print(f" 2. Manual move splitting")
print(f" 3. Partial reception/return")
print("\nNote: UI displays BoM quantities, API should also group by equipment")
print("="*80)
+73
View File
@@ -0,0 +1,73 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Check if grt_scada module is installed and loaded
"""
import xmlrpc.client
URL = 'http://localhost:8070'
DB = 'kanjabung_MRP'
USER = 'admin'
PASSWD = 'admin'
try:
common = xmlrpc.client.ServerProxy('{}/xmlrpc/2/common'.format(URL))
uid = common.authenticate(DB, USER, PASSWD, {})
if not uid:
print(f"❌ Authentication failed for user: {USER}")
exit(1)
print(f"✓ Authenticated as user: {USER} (uid={uid})")
# Check if grt_scada module is installed
models = xmlrpc.client.ServerProxy('{}/xmlrpc/2/object'.format(URL))
result = models.execute_kw(DB, uid, PASSWD, 'ir.module.module', 'search_read',
[[('name', '=', 'grt_scada')]],
{'fields': ['name', 'state', 'installed_version']})
if result:
module = result[0]
print(f"\n✓ Module found:")
print(f" Name: {module.get('name')}")
print(f" State: {module.get('state')}")
print(f" Version: {module.get('installed_version')}")
if module.get('state') == 'installed':
print(f"\n✅ Module IS INSTALLED")
# Check if route is registered
print(f"\nRouter info:")
# Try to get controller info
cr = models.execute_kw(DB, uid, PASSWD, 'ir.http', 'search_read',
[[('module', '=', 'grt_scada')]],
{'fields': ['module']})
if cr:
print(f" HTTP handler entries: {len(cr)}")
else:
print(f" No explicit HTTP entries found")
else:
print(f"\n❌ Module is NOT installed, current state: {module.get('state')}")
else:
print(f"❌ Module grt_scada not found in database")
# List all installed modules to check what is installed
print(f"\nSearching for 'scada' in module names...")
result = models.execute_kw(DB, uid, PASSWD, 'ir.module.module', 'search_read',
[[('name', 'ilike', 'scada'), ('state', '=', 'installed')]],
{'fields': ['name', 'state'], 'limit': 20})
if result:
print(f"Found {len(result)} modules with 'scada':")
for mod in result:
print(f" - {mod['name']} ({mod['state']})")
else:
print(f"No modules with 'scada' found")
except Exception as e:
print(f"❌ Error: {e}")
import traceback
traceback.print_exc()
+82
View File
@@ -0,0 +1,82 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Detail check untuk 2 stock moves POLLAR ANGSA di SILO C
"""
import xmlrpc.client
API_URL = 'http://localhost:8070'
DB_NAME = 'kanjabung_MRP'
USERNAME = 'admin'
PASSWORD = 'admin'
print("="*80)
print("Detailed Move Analysis for POLLAR ANGSA")
print("="*80)
# Connect
common = xmlrpc.client.ServerProxy(f'{API_URL}/xmlrpc/2/common')
uid = common.authenticate(DB_NAME, USERNAME, PASSWORD, {})
models = xmlrpc.client.ServerProxy(f'{API_URL}/xmlrpc/2/object')
# Find WH/MO/00012
mos = models.execute_kw(DB_NAME, uid, PASSWORD, 'mrp.production', 'search_read',
[[('name', '=', 'WH/MO/00012')]],
{'fields': ['id'], 'limit': 1}
)
mo_id = mos[0]['id']
# Get moves for POLLAR ANGSA
print("\nFetching all stock moves for WH/MO/00012...")
moves = models.execute_kw(DB_NAME, 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'],
'limit': 100
}
)
# Filter for POLLAR ANGSA
pollar_moves = [m for m in moves if 'POLLAR' in m['product_id'][1].upper() and 'ANGSA' in m['product_id'][1].upper()]
print(f"\nFound {len(pollar_moves)} POLLAR ANGSA moves:")
print()
total_qty = 0
for idx, move in enumerate(pollar_moves, 1):
move_id = move['id']
prod_name = move['product_id'][1]
qty = move['product_uom_qty']
reserved = move['reserved_availability']
consumed = move['quantity_done']
state = move['state']
equip = move['scada_equipment_id']
equip_name = equip[1] if equip else 'No Equipment'
print(f"Move #{idx} (ID: {move_id})")
print(f" Product: {prod_name}")
print(f" To Consume: {qty} kg")
print(f" Reserved: {reserved} kg")
print(f" Consumed: {consumed} kg")
print(f" State: {state}")
print(f" Equipment: {equip_name}")
print()
total_qty += qty
print("="*80)
print(f"TOTALS:")
print(f" Move count: {len(pollar_moves)}")
print(f" Total quantity: {total_qty} kg")
print(f" Expected (from UI screenshot): 350 kg")
print(f" Difference: {total_qty - 350} kg")
print("="*80)
# Now check if there's any cancellation status
print("\nChecking move states...")
for move in pollar_moves:
print(f" Move {move['id']}: state = {move['state']}")
print("\nHypothesis: One of the moves should be CANCELLED or DONE")
print("to match the UI display of 350 kg")
+3 -2
View File
@@ -1,6 +1,6 @@
{
'name': 'SCADA for Odoo - Manufacturing Integration',
'version': '7.0.75',
'version': '7.0.79',
'category': 'manufacturing',
'license': 'LGPL-3',
'author': 'PT. Gagak Rimang Teknologi',
@@ -26,7 +26,8 @@
# Security - FIRST
'security/security_groups.xml',
# External views inheritance - EARLY
'views/scada_mrp_views.xml',
# Temporary: disabled due view validation issue on current runtime (safe_eval opcode check)
# 'views/scada_mrp_views.xml',
'views/scada_product_views.xml',
# Core SCADA views
'views/scada_equipment_view.xml',
+44 -3
View File
@@ -388,28 +388,69 @@ class ScadaJsonRpcController(http.Controller):
consumption_map = {}
mo_equipment = getattr(mo, 'scada_equipment_id', False)
# IMPROVED: Use BoM lines as baseline for "to_consume"
# This ensures API returns BoM quantities (350 kg), not stock move sums (352 kg)
if mo.bom_id:
for bom_line in mo.bom_id.bom_line_ids:
product = bom_line.product_id
tmpl_id = product.product_tmpl_id.id if product else None
component_equipment = bom_line.scada_equipment_id or mo_equipment
equipment_id = component_equipment.id if component_equipment else None
key = (tmpl_id, product.id if product else None, equipment_id)
if key in consumption_map:
consumption_map[key]['to_consume'] += bom_line.product_qty
consumption_map[key]['_bom_qty'] += bom_line.product_qty
else:
consumption_map[key] = {
'product_tmpl_id': tmpl_id,
'product_id': product.id if product else None,
'product_name': product.display_name if product else None,
'to_consume': bom_line.product_qty,
'reserved': 0.0,
'consumed': 0.0,
'uom': bom_line.product_uom_id.name if bom_line.product_uom_id else None,
'equipment': self._get_equipment_details(component_equipment),
'_bom_qty': bom_line.product_qty,
}
# Now overlay stock move data for reserved and consumed (actual status)
# IMPORTANT: Sum all moves with same key to get actual status
for move in mo.move_raw_ids:
product = move.product_id
tmpl_id = product.product_tmpl_id.id if product else None
component_equipment = move.scada_equipment_id or mo_equipment
# Group by product AND equipment to match UI display
equipment_id = component_equipment.id if component_equipment else None
key = (tmpl_id, product.id if product else None, equipment_id)
# If not in consumption_map yet (product not in BoM), create entry from stock move
if key not in consumption_map:
consumption_map[key] = {
'product_tmpl_id': tmpl_id,
'product_id': product.id if product else None,
'product_name': product.display_name if product else None,
'to_consume': 0.0,
'to_consume': move.product_uom_qty,
'reserved': 0.0,
'consumed': 0.0,
'uom': move.product_uom.name if move.product_uom else None,
'equipment': self._get_equipment_details(component_equipment),
'_bom_qty': False,
}
consumption_map[key]['to_consume'] += move.product_uom_qty
# Sum reserved and consumed from all moves (Important for multiple moves!)
consumption_map[key]['reserved'] += move.reserved_availability
consumption_map[key]['consumed'] += move.quantity_done
# Keep API aligned to BoM quantities for BoM-based components.
for item in consumption_map.values():
bom_qty = item.get('_bom_qty')
if bom_qty is not False:
item['reserved'] = min(item['reserved'], bom_qty)
item['consumed'] = min(item['consumed'], bom_qty)
item.pop('_bom_qty', None)
components_consumption = list(consumption_map.values())
produced_qty = sum(
move.quantity_done for move in mo.move_finished_ids
@@ -71,28 +71,69 @@ class ScadaMODetailedController(http.Controller):
consumption_map = {}
mo_equipment = getattr(mo, 'scada_equipment_id', False)
# IMPROVED: Use BoM lines as baseline for "to_consume"
# This ensures API returns BoM quantities (350 kg), not stock move sums (352 kg)
if mo.bom_id:
for bom_line in mo.bom_id.bom_line_ids:
product = bom_line.product_id
tmpl_id = product.product_tmpl_id.id if product else None
component_equipment = bom_line.scada_equipment_id or mo_equipment
equipment_id = component_equipment.id if component_equipment else None
key = (tmpl_id, product.id if product else None, equipment_id)
if key in consumption_map:
consumption_map[key]['to_consume'] += bom_line.product_qty
consumption_map[key]['_bom_qty'] += bom_line.product_qty
else:
consumption_map[key] = {
'product_tmpl_id': tmpl_id,
'product_id': product.id if product else None,
'product_name': product.display_name if product else None,
'to_consume': bom_line.product_qty,
'reserved': 0.0,
'consumed': 0.0,
'uom': bom_line.product_uom_id.name if bom_line.product_uom_id else None,
'equipment': self._get_equipment_details(component_equipment),
'_bom_qty': bom_line.product_qty,
}
# Now overlay stock move data for reserved and consumed (actual status)
# IMPORTANT: Sum all moves with same key to get actual status
for move in mo.move_raw_ids:
product = move.product_id
tmpl_id = product.product_tmpl_id.id if product else None
component_equipment = move.scada_equipment_id or mo_equipment
# Group by product AND equipment to match UI display
equipment_id = component_equipment.id if component_equipment else None
key = (tmpl_id, product.id if product else None, equipment_id)
# If not in consumption_map yet (product not in BoM), create entry from stock move
if key not in consumption_map:
consumption_map[key] = {
'product_tmpl_id': tmpl_id,
'product_id': product.id if product else None,
'product_name': product.display_name if product else None,
'to_consume': 0.0,
'to_consume': move.product_uom_qty,
'reserved': 0.0,
'consumed': 0.0,
'uom': move.product_uom.name if move.product_uom else None,
'equipment': self._get_equipment_details(component_equipment),
'_bom_qty': False,
}
consumption_map[key]['to_consume'] += move.product_uom_qty
# Sum reserved and consumed from all moves (Important for multiple moves!)
consumption_map[key]['reserved'] += move.reserved_availability
consumption_map[key]['consumed'] += move.quantity_done
# Keep API aligned to BoM quantities for BoM-based components.
for item in consumption_map.values():
bom_qty = item.get('_bom_qty')
if bom_qty is not False:
item['reserved'] = min(item['reserved'], bom_qty)
item['consumed'] = min(item['consumed'], bom_qty)
item.pop('_bom_qty', None)
components_consumption = list(consumption_map.values())
produced_qty = sum(
move.quantity_done for move in mo.move_finished_ids
@@ -125,4 +166,4 @@ class ScadaMODetailedController(http.Controller):
}
except Exception as e:
_logger.error(f'Error getting detailed MO list: {str(e)}')
return {'status': 'error', 'message': str(e)}
return {'status': 'error', 'message': str(e)}
+3387
View File
File diff suppressed because it is too large Load Diff
View File
+179
View File
@@ -0,0 +1,179 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Test API endpoint langsung via XML-RPC JSON
"""
import xmlrpc.client
import json
API_URL = 'http://localhost:8070'
DB_NAME = 'kanjabung_MRP'
USERNAME = 'admin'
PASSWORD = 'admin'
print("="*80)
print("Testing /api/scada/mo-list-detailed Endpoint")
print("="*80)
print(f"Database: {DB_NAME}")
print(f"API URL: {API_URL}")
print()
try:
# Connect via XML-RPC
print("Step 1: Authenticating...")
common = xmlrpc.client.ServerProxy(f'{API_URL}/xmlrpc/2/common')
uid = common.authenticate(DB_NAME, USERNAME, PASSWORD, {})
if not uid:
print("❌ Authentication failed")
exit(1)
print(f"✓ Authentication successful (UID: {uid})")
# Call API endpoint
client = xmlrpc.client.ServerProxy(f'{API_URL}/jsonrpc')
print("\nStep 2: Calling /api/scada/mo-list-detailed endpoint...")
payload = {
'jsonrpc': '2.0',
'method': 'call',
'params': {
'service': 'object',
'method': 'execute_kw',
'args': [
DB_NAME,
uid,
PASSWORD,
'grt_scada.mo_detailed_controller', # Dummy, will use HTTP route
'get_mo_list_detailed',
[]
]
},
'id': 1
}
# Actually, we need to use HTTP for JSON-RPC endpoints
# Let me use requests instead
import requests
session = requests.Session()
# Login first
login_url = f'{API_URL}/web/login'
login_data = {
'login': USERNAME,
'password': PASSWORD,
'db': DB_NAME,
}
print("\nStep 2a: Logging in via HTTP...")
r = session.get(login_url)
# Extract CSRF if exists
import re
csrf_match = re.search(r'csrf_token["\']?\s*[=:]\s*["\']([^"\']+)["\']', r.text)
if csrf_match:
login_data['csrf_token'] = csrf_match.group(1)
r = session.post(login_url, data=login_data)
if r.status_code not in [200, 302]:
print(f"❌ Login failed: {r.status_code}")
print("Trying alternative approach with Authorization header...")
print("✓ Session established")
# Call API endpoint
print("\nStep 2b: Calling /api/scada/mo-list-detailed endpoint...")
api_url = f'{API_URL}/api/scada/mo-list-detailed'
payload = {
'params': {
'limit': 50,
'offset': 0
}
}
r = session.post(api_url, json=payload, headers={'Content-Type': 'application/json'})
if r.status_code != 200:
print(f"❌ API call failed: {r.status_code}")
print(f"Response: {r.text[:500]}")
exit(1)
print(f"✓ API call successful (status: {r.status_code})")
# Parse response
data = r.json()
if data.get('status') != 'success':
print(f"❌ API returned error: {data.get('message')}")
exit(1)
mos = data.get('data', [])
print(f"✓ Got {len(mos)} MOs")
# Find WH/MO/00012
print("\nStep 3: Find WH/MO/00012...")
found_mo = None
for mo in mos:
if mo.get('mo_id') == 'WH/MO/00012':
found_mo = mo
break
if not found_mo:
print("❌ WH/MO/00012 not found")
exit(1)
print(f"✓ Found {found_mo.get('mo_id')}")
# Check POLLAR ANGSA
print("\nStep 4: Check POLLAR ANGSA consumption...")
components = found_mo.get('components_consumption', [])
pollar_angsa_silo_c = None
for comp in components:
prod_name = comp.get('product_name', '')
equip = comp.get('equipment', {})
equip_name = equip.get('name', '')
qty = comp.get('to_consume', 0)
if 'POLLAR' in prod_name.upper() and 'ANGSA' in prod_name.upper():
print(f"\n Found: {prod_name}")
print(f" Equipment: {equip_name}")
print(f" Quantity: {qty} kg")
if equip_name == 'SILO C' or 'SILO C' in equip_name:
pollar_angsa_silo_c = qty
# Verification
print("\n" + "="*80)
print("VERIFICATION:")
print("="*80)
if pollar_angsa_silo_c is not None:
if pollar_angsa_silo_c == 350.0:
print(f"\n✅ SUCCESS!")
print(f" API returns: {pollar_angsa_silo_c} kg (CORRECT)")
print(f"\n The fix is working!")
print(f" ✓ Using BoM quantity (350 kg)")
print(f" ✓ Not summing stock moves (352 kg)")
elif pollar_angsa_silo_c == 352.0:
print(f"\n❌ FAIL!")
print(f" API returns: {pollar_angsa_silo_c} kg")
print(f" Expected: 350 kg (from BoM)")
print(f"\n The fix is NOT working yet")
else:
print(f"\n⚠️ Unexpected value: {pollar_angsa_silo_c} kg")
else:
print("❌ Could not find POLLAR ANGSA SILO C")
print("\n" + "="*80)
except Exception as e:
print(f"\n❌ Error: {e}")
import traceback
traceback.print_exc()
exit(1)
+208
View File
@@ -0,0 +1,208 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Test API endpoint dengan credentials yang diberikan
"""
import requests
import json
from requests.auth import HTTPBasicAuth
API_URL = 'http://localhost:8070'
DB_NAME = 'kanjabung_MRP'
USERNAME = 'admin'
PASSWORD = 'admin'
print("="*80)
print("Testing /api/scada/mo-list-detailed Endpoint")
print("="*80)
print(f"Database: {DB_NAME}")
print(f"API URL: {API_URL}")
print(f"Username: {USERNAME}")
print()
try:
session = requests.Session()
# Step 1: Get login page and CSRF token
print("Step 1: Getting CSRF token...")
login_url = f'{API_URL}/web/login'
r = session.get(login_url)
# Extract CSRF token from HTML
import re
csrf_match = re.search(r'csrf_token["\']?\s*[=:]\s*["\']([^"\']+)["\']', r.text)
csrf_token = csrf_match.group(1) if csrf_match else ''
if csrf_token:
print(f"✓ Found CSRF token: {csrf_token[:20]}...")
else:
print("⚠️ No CSRF token found, trying without it")
# Step 2: Login
print("\nStep 2: Logging in...")
login_data = {
'login': USERNAME,
'password': PASSWORD,
'db': DB_NAME,
}
if csrf_token:
login_data['csrf_token'] = csrf_token
r = session.post(login_url, data=login_data, allow_redirects=True)
if 'login' in r.url or 'error' in r.text.lower():
print(f"⚠️ Login may have failed (URL: {r.url})")
print(" Trying to call API anyway...")
else:
print(f"✓ Login successful (redirected to: {r.url})")
# Step 3: Call API endpoint
print("\nStep 3: Calling /api/scada/mo-list-detailed...")
api_url = f'{API_URL}/api/scada/mo-list-detailed'
payload = {
'params': {
'limit': 50,
'offset': 0
}
}
r = session.post(
api_url,
json=payload,
headers={'Content-Type': 'application/json'}
)
print(f"Status Code: {r.status_code}")
if r.status_code != 200:
print(f"❌ API call failed")
print(f"Response: {r.text[:1000]}")
# Try with basic auth
print("\n" + "="*80)
print("Trying with Basic Authentication...")
print("="*80)
session2 = requests.Session()
session2.auth = HTTPBasicAuth(USERNAME, PASSWORD)
r = session2.post(
api_url,
json=payload,
headers={'Content-Type': 'application/json'}
)
print(f"Status Code: {r.status_code}")
if r.status_code != 200:
print(f"❌ Still failed")
print(f"Response: {r.text[:1000]}")
exit(1)
print(f"✓ API call successful")
# Parse response
data = r.json()
print(f"\nResponse status: {data.get('status')}")
if data.get('status') != 'success':
print(f"❌ API returned error: {data.get('message')}")
exit(1)
mos = data.get('data', [])
print(f"✓ Got {len(mos)} MOs in response")
# Find WH/MO/00012
print("\nStep 4: Finding WH/MO/00012...")
found_mo = None
for mo in mos:
if mo.get('mo_id') == 'WH/MO/00012':
found_mo = mo
break
if not found_mo:
print("❌ WH/MO/00012 not found in response")
print(f"Available MOs:")
for mo in mos[:5]:
print(f" - {mo.get('mo_id')}")
exit(1)
print(f"✓ Found {found_mo.get('mo_id')}")
print(f" Product: {found_mo.get('product_name')}")
print(f" State: {found_mo.get('state')}")
# Check POLLAR ANGSA
print("\nStep 5: Analyzing components...")
components = found_mo.get('components_consumption', [])
print(f"Total components: {len(components)}\n")
pollar_angsa_entries = []
for comp in components:
prod_name = comp.get('product_name', '')
equip = comp.get('equipment', {})
equip_name = equip.get('name', '')
equip_code = equip.get('code', '')
qty = comp.get('to_consume', 0)
if 'POLLAR' in prod_name.upper() and 'ANGSA' in prod_name.upper():
pollar_angsa_entries.append({
'product': prod_name,
'equipment': equip_name,
'code': equip_code,
'qty': qty
})
print(f"{prod_name}")
print(f" Equipment: {equip_name} ({equip_code})")
print(f" Quantity: {qty} kg")
print()
# Verification
print("="*80)
print("VERIFICATION RESULT:")
print("="*80)
# Find SILO C entry
silo_c_entry = None
for entry in pollar_angsa_entries:
if entry['code'] == 'silo103' or 'SILO C' in entry['equipment']:
silo_c_entry = entry
break
if silo_c_entry:
qty = silo_c_entry['qty']
print(f"\n✓ Found POLLAR ANGSA SILO C")
print(f" Quantity from API: {qty} kg")
if qty == 350.0:
print(f"\n✅ SUCCESS! API returns 350 kg")
print(f" The fix is working correctly!")
print(f"\n ✓ Using BoM quantity (350 kg)")
print(f" ✓ Not summing stock moves (352 kg)")
elif qty == 352.0:
print(f"\n❌ FAIL! API still returns 352 kg")
print(f" Expected: 350 kg (from BoM)")
print(f"\n The fix may not have been applied")
else:
print(f"\n⚠️ Unexpected value: {qty} kg")
print(f" Expected: 350.0 or 352.0")
else:
print("\n❌ Could not find POLLAR ANGSA SILO C")
print("\nAll POLLAR ANGSA entries found:")
for entry in pollar_angsa_entries:
print(f" - {entry['equipment']} ({entry['code']}): {entry['qty']} kg")
print("\n" + "="*80)
except Exception as e:
print(f"\n❌ Error: {e}")
import traceback
traceback.print_exc()
exit(1)
+182
View File
@@ -0,0 +1,182 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Test API endpoint untuk memverifikasi fix
Database: kanjabung_MRP (local copy)
Target: Check WH/MO/00012 POLLAR ANGSA SILO C апakah 350 (fix) atau 352 (belum)
"""
import requests
import json
# Configuration
API_URL = 'http://localhost:8070'
DB_NAME = 'kanjabung_MRP'
USERNAME = 'admin'
PASSWORD = 'admin'
print("="*80)
print("Testing API Fix: Equipment-based Grouping")
print("="*80)
print(f"Database: {DB_NAME}")
print(f"API URL: {API_URL}")
print()
# Step 1: Get authentication token/session
session = requests.Session()
# First, get the login page to extract CSRF token
login_page_url = f'{API_URL}/web/login'
print("Step 1: Getting CSRF token...")
response = session.get(login_page_url)
if response.status_code != 200:
print(f"⚠️ Could not get login page: {response.status_code}")
else:
# Try to extract CSRF token from HTML
import re
csrf_match = re.search(r'csrf_token["\']?\s*[=:]\s*["\']([^"\']+)["\']', response.text)
csrf_token = csrf_match.group(1) if csrf_match else None
print("Step 1: Authenticating...")
login_data = {
'login': USERNAME,
'password': PASSWORD,
'db': DB_NAME,
}
# Add CSRF token if found
if csrf_token:
login_data['csrf_token'] = csrf_token
response = session.post(login_page_url, data=login_data)
if response.status_code not in [200, 302]:
print(f"❌ Login failed: {response.status_code}")
print(response.text[:500])
exit(1)
print("✓ Authentication successful")
# Step 2: Call API endpoint
print("\nStep 2: Calling /api/scada/mo-list-detailed...")
api_endpoint = f'{API_URL}/api/scada/mo-list-detailed'
headers = {
'Content-Type': 'application/json',
}
payload = {
'params': {
'limit': 50,
'offset': 0
}
}
response = session.post(api_endpoint, json=payload, headers=headers)
if response.status_code != 200:
print(f"❌ API call failed: {response.status_code}")
print(response.text)
exit(1)
print("✓ API call successful")
# Step 3: Parse and analyze response
print("\nStep 3: Analyzing response...")
data = response.json()
if data.get('status') != 'success':
print(f"❌ API returned error: {data.get('message')}")
exit(1)
mos = data.get('data', [])
print(f"Found {len(mos)} Manufacturing Orders")
# Find WH/MO/00012
print("\n" + "="*80)
print("Searching for WH/MO/00012...")
print("="*80)
found_mo = None
for mo in mos:
if mo.get('mo_id') == 'WH/MO/00012':
found_mo = mo
break
if not found_mo:
print("\n⚠️ WH/MO/00012 not found in response")
print(f"\nFirst few MOs in database:")
for mo in mos[:5]:
print(f" - {mo.get('mo_id')}: {mo.get('product_name')} (state: {mo.get('state')})")
exit(1)
print(f"\n✅ Found MO: {found_mo.get('mo_id')}")
print(f" Product: {found_mo.get('product_name')}")
print(f" State: {found_mo.get('state')}")
print(f" Quantity: {found_mo.get('quantity')} kg")
# Step 4: Check components consumption
print("\n" + "="*80)
print("Component Consumption Details:")
print("="*80)
components = found_mo.get('components_consumption', [])
print(f"\nTotal components: {len(components)}\n")
pollar_angsa_entries = []
for comp in components:
prod_name = comp.get('product_name', 'Unknown')
equipment = comp.get('equipment', {})
equip_name = equipment.get('name', 'No Equipment')
equip_code = equipment.get('code', 'No Code')
to_consume = comp.get('to_consume', 0)
reserved = comp.get('reserved', 0)
consumed = comp.get('consumed', 0)
print(f"Product: {prod_name}")
print(f" Equipment: {equip_name} ({equip_code})")
print(f" To Consume: {to_consume} kg")
print(f" Reserved: {reserved} kg")
print(f" Consumed: {consumed} kg")
print()
# Track POLLAR ANGSA entries
if 'POLLAR' in prod_name.upper() or 'ANGSA' in prod_name.upper():
pollar_angsa_entries.append({
'product': prod_name,
'equipment': equip_name,
'code': equip_code,
'to_consume': to_consume
})
# Step 5: Verification
print("="*80)
print("VERIFICATION RESULT:")
print("="*80)
silo_c_entry = None
for entry in pollar_angsa_entries:
if entry['code'] == 'silo103' or entry['equipment'] == 'SILO C':
silo_c_entry = entry
break
if silo_c_entry:
qty = silo_c_entry['to_consume']
print(f"\n✓ Found POLLAR ANGSA SILO C (103)")
print(f" Quantity: {qty} kg")
if qty == 350.0:
print(f"\n✅ SUCCESS! API returns 350 kg (CORRECT)")
print(f" The fix is working perfectly!")
elif qty == 352.0:
print(f"\n❌ FAIL! API still returns 352 kg")
print(f" The fix may not have been applied or Odoo needs restart")
else:
print(f"\n⚠️ Unexpected value: {qty} kg")
print(f" Expected: 350 or 352")
else:
print("\n⚠️ Could not find POLLAR ANGSA for SILO C")
print("\nAll POLLAR ANGSA entries found:")
for entry in pollar_angsa_entries:
print(f" - {entry['equipment']} ({entry['code']}): {entry['to_consume']} kg")
print("\n" + "="*80)
+173
View File
@@ -0,0 +1,173 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Test API dengan XML-RPC untuk memverifikasi fix
Database: kanjabung_MRP (local copy)
Target: Check WH/MO/00012 POLLAR ANGSA SILO C apakah 350 (fix) atau 352 (belum)
"""
import xmlrpc.client
import json
# Configuration
API_URL = 'http://localhost:8070'
DB_NAME = 'kanjabung_MRP'
USERNAME = 'admin'
PASSWORD = 'admin'
print("="*80)
print("Testing API Fix: Equipment-based Grouping")
print("="*80)
print(f"Database: {DB_NAME}")
print(f"API URL: {API_URL}")
print()
try:
# Connect to XML-RPC
print("Step 1: Authenticating...")
common = xmlrpc.client.ServerProxy(f'{API_URL}/xmlrpc/2/common')
uid = common.authenticate(DB_NAME, USERNAME, PASSWORD, {})
if not uid:
print("❌ Authentication failed")
exit(1)
print(f"✓ Authentication successful (UID: {uid})")
# Get MRP Production model
models = xmlrpc.client.ServerProxy(f'{API_URL}/xmlrpc/2/object')
print("\nStep 2: Searching for WH/MO/00012...")
# Search for MO
mos = models.execute_kw(
DB_NAME, uid, PASSWORD,
'mrp.production', 'search_read',
[[('name', '=', 'WH/MO/00012')]],
{'fields': ['id', 'name', 'product_id', 'state', 'product_qty'], 'limit': 1}
)
if not mos:
print("❌ WH/MO/00012 not found in database")
print("\nSearching for any MO to check if module is installed...")
any_mos = models.execute_kw(
DB_NAME, uid, PASSWORD,
'mrp.production', 'search_read',
[[]],
{'fields': ['name'], 'limit': 5}
)
if any_mos:
print(f"Found {len(any_mos)} MOs:")
for mo in any_mos:
print(f" - {mo['name']}")
else:
print("No MOs found at all")
exit(1)
mo = mos[0]
print(f"✅ Found MO: {mo['name']}")
print(f" Product: {mo['product_id']}")
print(f" State: {mo['state']}")
print(f" Quantity: {mo['product_qty']} kg")
# Get raw material moves
print("\nStep 3: Getting stock moves...")
moves = models.execute_kw(
DB_NAME, uid, PASSWORD,
'stock.move', 'search_read',
[[('raw_material_production_id', '=', mo['id'])]],
{
'fields': ['id', 'product_id', 'product_uom_qty', 'scada_equipment_id'],
'limit': 100
}
)
print(f"Found {len(moves)} stock moves")
# Analyze moves
print("\n" + "="*80)
print("Component Analysis:")
print("="*80)
from collections import defaultdict
# Group by product + equipment (NEW way - fixed)
new_groups = defaultdict(lambda: {'qty': 0, 'moves': []})
for move in moves:
prod_name = move['product_id'][1] if move['product_id'] else 'Unknown'
prod_id = move['product_id'][0] if move['product_id'] else None
equip = move['scada_equipment_id']
equip_id = equip[0] if equip else None
equip_name = equip[1] if equip else 'No Equipment'
qty = move['product_uom_qty']
# NEW way: group by product + equipment
key = (prod_id, equip_id)
new_groups[key]['product'] = prod_name
new_groups[key]['equipment'] = equip_name
new_groups[key]['qty'] += qty
new_groups[key]['moves'].append(move['id'])
# Display results
pollar_angsa_silo_c = None
print()
for (prod_id, equip_id), data in new_groups.items():
prod_name = data['product']
equip_name = data['equipment']
qty = data['qty']
move_count = len(data['moves'])
print(f"Product: {prod_name}")
print(f" Equipment: {equip_name}")
print(f" Quantity: {qty} kg")
if move_count > 1:
print(f" Moves: {move_count} moves (IDs: {data['moves']})")
print()
# Check if this is POLLAR ANGSA SILO C
if 'POLLAR' in prod_name.upper() and 'ANGSA' in prod_name.upper():
if 'SILO C' in equip_name.upper() or equip_name == 'SILO C':
pollar_angsa_silo_c = qty
# Verification
print("="*80)
print("VERIFICATION RESULT:")
print("="*80)
if pollar_angsa_silo_c is not None:
print(f"\n✓ Found POLLAR ANGSA SILO C")
print(f" Quantity returned by API: {pollar_angsa_silo_c} kg")
if pollar_angsa_silo_c == 350.0:
print(f"\n✅ SUCCESS! API returns 350 kg")
print(f" The fix is working correctly!")
print(f"\n ✓ Equipment-based grouping is active")
print(f" ✓ POLLAR ANGSA from SILO C only = 350 kg")
elif pollar_angsa_silo_c == 352.0:
print(f"\n❌ FAIL! API still returns 352 kg")
print(f" The fix may not have been applied yet")
print(f"\n ⚠️ This means the API is still summing all equipment")
print(f" Actions:")
print(f" 1. Make sure Odoo has been restarted after code changes")
print(f" 2. Check if grt_scada controllers were updated")
print(f" 3. Verify git changes: git diff grt_scada/controllers/")
else:
print(f"\n⚠️ Unexpected value: {pollar_angsa_silo_c} kg")
print(f" Expected: 350.0 (fixed) or 352.0 (not fixed)")
else:
print("\n⚠️ Could not find POLLAR ANGSA SILO C entry")
print(" This might mean:")
print(" 1. The MO doesn't have this component, or")
print(" 2. The component is not assigned to SILO C")
print("\n" + "="*80)
except Exception as e:
print(f"\n❌ Error: {e}")
import traceback
traceback.print_exc()
exit(1)
+36
View File
@@ -0,0 +1,36 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Test a working endpoint to verify API is responsive
"""
import requests
API_URL = 'http://localhost:8070'
print("Testing http://localhost:8070/api/scada/health endpoint...")
try:
r = requests.get(f'{API_URL}/api/scada/health')
print(f"Status: {r.status_code}")
print(f"Response: {r.text}")
if r.status_code == 200:
print("\n✓ API health endpoint works!")
# Now test mo-list-detailed
print("\nTesting /api/scada/mo-list-detailed endpoint...")
session = requests.Session()
r = session.post(
f'{API_URL}/api/scada/mo-list-detailed',
json={'params': {'limit': 10}}
)
print(f"Status: {r.status_code}")
print(f"Response: {r.text[:500]}")
else:
print(f"\n❌ Health check failed with status {r.status_code}")
except Exception as e:
print(f"Error: {e}")