from proteus import config, Model import psycopg2 # XML-RPC Configuration (default connection method) HTTPS = 'https://' SERVER_URL = 'itsa.open-squared.tech' DATABASE_NAME = 'tradon' USERNAME = 'admin' PASSWORD = 'dsproject' # PostgreSQL Configuration (for direct database inspection) DB_HOST = '72.61.163.139' DB_PORT = 5433 DB_USER = 'postgres' DB_PASSWORD = 'dsproject' print("="*80) print("CUSTOM FIELDS IDENTIFICATION FOR purchase.purchase") print("="*80) # Connect to Tryton via XML-RPC print(f"\nConnecting via XML-RPC to {SERVER_URL}...") config.set_xmlrpc(f'{HTTPS}{USERNAME}:{PASSWORD}@{SERVER_URL}/{DATABASE_NAME}/') print("✓ Connected successfully\n") Purchase = Model.get('purchase.purchase') # Get all fields that Proteus sees proteus_fields = sorted([key for key in dir(Purchase) if not key.startswith('_') and key not in ['create', 'delete', 'save', 'find', 'copy', 'read', 'write', 'search']]) print(f"1. FIELDS VISIBLE TO PROTEUS: {len(proteus_fields)} fields") print("-"*80) # Standard Tryton purchase.purchase fields (from base module) standard_purchase_fields = { 'id', 'create_date', 'create_uid', 'write_date', 'write_uid', 'company', 'party', 'invoice_party', 'invoice_address', 'payment_term', 'warehouse', 'currency', 'description', 'comment', 'state', 'purchase_date', 'invoice_method', 'lines', 'invoices', 'invoices_ignored', 'invoices_recreated', 'invoice_lines', 'invoice_lines_ignored', 'moves', 'shipment_state', 'invoice_state', 'number', 'reference', 'shipments', 'shipment_returns', 'rec_name', 'origin', 'untaxed_amount', 'tax_amount', 'total_amount', 'untaxed_amount_cache', 'tax_amount_cache', 'total_amount_cache', 'delivery_date', 'party_lang', 'contact', 'xml_id' } # Identify potential custom fields potential_custom_fields = [f for f in proteus_fields if f not in standard_purchase_fields] print(f"\n2. POTENTIAL CUSTOM FIELDS: {len(potential_custom_fields)} fields") print("-"*80) for field in potential_custom_fields: print(f" - {field}") # Connect to PostgreSQL to get actual table columns print(f"\n3. COLUMNS IN POSTGRESQL TABLE 'purchase_purchase'") print("-"*80) try: conn = psycopg2.connect( dbname=DATABASE_NAME, host=DB_HOST, port=DB_PORT, user=DB_USER, password=DB_PASSWORD ) cursor = conn.cursor() # Get all columns from purchase_purchase table cursor.execute(""" SELECT column_name, data_type, character_maximum_length, is_nullable, column_default FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'purchase_purchase' ORDER BY ordinal_position; """) db_columns = cursor.fetchall() print(f"Total columns in database: {len(db_columns)}\n") # Standard columns that typically exist in purchase_purchase standard_db_columns = { 'id', 'create_date', 'create_uid', 'write_date', 'write_uid', 'company', 'party', 'invoice_party', 'invoice_address', 'payment_term', 'warehouse', 'currency', 'description', 'comment', 'state', 'purchase_date', 'invoice_method', 'number', 'reference', 'delivery_date', 'contact', 'shipment_state', 'invoice_state', 'origin', 'untaxed_amount_cache', 'tax_amount_cache', 'total_amount_cache' } db_column_names = [col[0] for col in db_columns] custom_db_columns = [col for col in db_columns if col[0] not in standard_db_columns] print("Custom columns in database:") for col in custom_db_columns: col_name, data_type, max_length, nullable, default = col length_info = f"({max_length})" if max_length else "" print(f" - {col_name:<30} {data_type}{length_info:<15} NULL: {nullable}") # Compare: Fields in Proteus vs Columns in DB print(f"\n4. COMPARISON: PROTEUS vs DATABASE") print("-"*80) # Fields in Proteus but NOT as direct columns in DB (might be related fields, functions, etc.) proteus_only = set(potential_custom_fields) - set(db_column_names) if proteus_only: print(f"\nFields in Proteus but NOT as columns in DB ({len(proteus_only)}):") print("(These might be Many2One, One2Many, Function fields, etc.)") for field in sorted(proteus_only): print(f" - {field}") # Columns in DB but NOT visible in Proteus (these are the problem!) db_only = set([col[0] for col in custom_db_columns]) - set(proteus_fields) if db_only: print(f"\n⚠️ COLUMNS IN DATABASE BUT NOT VISIBLE IN PROTEUS ({len(db_only)}):") print("(These fields MUST be added to the Python model!)") for field in sorted(db_only): print(f" - {field}") # Fields that exist in BOTH Proteus and DB both = set(potential_custom_fields) & set([col[0] for col in custom_db_columns]) if both: print(f"\n✓ Custom fields properly defined in BOTH Proteus and DB ({len(both)}):") for field in sorted(both): print(f" - {field}") cursor.close() conn.close() except Exception as e: print(f"Error connecting to PostgreSQL: {e}") # Test persistence of custom fields print(f"\n5. TESTING FIELD PERSISTENCE") print("-"*80) try: # Find a draft purchase to test drafts = Purchase.find([('state', '=', 'draft')], limit=1) if drafts: test_purchase = drafts[0] test_id = test_purchase.id print(f"Testing with purchase ID: {test_id}") print("\nTesting custom fields (attempting to set and save):\n") # Test a sample of custom fields test_fields = {} # Add fields to test if they exist if 'reference' in potential_custom_fields: test_fields['reference'] = 'TEST_REF' if 'crop' in potential_custom_fields: test_fields['crop'] = 'TEST_CROP' if 'forex' in potential_custom_fields: test_fields['forex'] = 'TEST_FOREX' if 'broker' in potential_custom_fields: test_fields['broker'] = 'TEST_BROKER' if 'certif' in potential_custom_fields: test_fields['certif'] = 'TEST_CERT' if 'wb' in potential_custom_fields: test_fields['wb'] = 'TEST_WB' for field_name, test_value in test_fields.items(): try: original_value = getattr(test_purchase, field_name, None) setattr(test_purchase, field_name, test_value) test_purchase.save() # Reload reloaded = Purchase(test_id) new_value = getattr(reloaded, field_name, None) if new_value == test_value: print(f" ✓ {field_name}: PERSISTS correctly") # Restore original value setattr(reloaded, field_name, original_value) reloaded.save() else: print(f" ✗ {field_name}: Does NOT persist (expected: '{test_value}', got: '{new_value}')") except Exception as e: print(f" ✗ {field_name}: Error - {str(e)[:60]}") else: print("No draft purchases found for testing") except Exception as e: print(f"Error during persistence testing: {e}") print("\n" + "="*80) print("SUMMARY & RECOMMENDATIONS") print("="*80) print(""" Next steps for your colleague: 1. Review the "⚠️ COLUMNS IN DATABASE BUT NOT VISIBLE IN PROTEUS" section → These fields exist in PostgreSQL but are missing from the Python model 2. Review fields that "Does NOT persist" in the testing section → These fields are visible but not working correctly 3. Add missing fields to your custom Tryton module: File: modules/your_custom_module/purchase.py from trytond.pool import PoolMeta from trytond.model import fields class Purchase(metaclass=PoolMeta): __name__ = 'purchase.purchase' # Add each missing field with appropriate type: custom_field = fields.Char('Custom Field') custom_number = fields.Integer('Custom Number') custom_date = fields.Date('Custom Date') custom_many2one = fields.Many2One('other.model', 'Reference') # etc... 4. Increment module version in tryton.cfg 5. Update module: trytond-admin -d tradon -u your_custom_module 6. Restart Tryton server 7. Re-run this script to verify all fields work correctly """)