Files
Implementation_ITSA/Reference Data/python_project/scripts/import_purchase_fees.py
AzureAD\SylvainDUVERNAY 078843f991 Initial commit
2026-02-13 14:23:19 +01:00

364 lines
14 KiB
Python

import sys
from pathlib import Path
# Add parent directory to Python path so we can import helpers
parent_dir = Path(__file__).parent.parent
sys.path.insert(0, str(parent_dir))
import csv
from decimal import Decimal
from proteus import config, Model
from helpers.config import (
PURCHASE_FEES_CSV,
connect_to_tryton)
from helpers.tryton_helpers import (
find_party_by_name,
find_product_by_code,
find_purchase_contract_by_ref,
find_contract_line_by_sequence,
find_currency_by_code,
parse_decimal,
find_supplier_category,
ensure_party_is_supplier,
find_fee_mode_by_name,
find_payable_receivable_by_name,
get_existing_fees_for_line,
fee_already_exists)
# CSV Configuration
CSV_FILE_PATH = PURCHASE_FEES_CSV
# Import options
AUTO_ENABLE_SUPPLIER = True # Set to False to skip auto-enabling supplier flag
SKIP_NON_SUPPLIERS = False # Set to True to skip parties that aren't suppliers
def import_purchase_contract_fees(csv_file):
"""Import purchase contract line fees from CSV"""
print(f"{'='*70}")
print("IMPORTING PURCHASE CONTRACT LINE FEES")
print(f"{'='*70}\n")
# Get models
try:
PurchaseLineFee = Model.get('fee.fee')
except Exception as e:
print(f"✗ Error: Could not load fee.fee model - {e}")
print("Please ensure the model name is correct for your Tryton customization")
return
imported_count = 0
skipped_count = 0
error_count = 0
errors = []
try:
with open(csv_file, 'r', encoding='utf-8-sig') as file:
reader = csv.DictReader(file)
current_contract_ref = None
current_contract = None
current_line_sequence = None
current_line = None
for row_num, row in enumerate(reader, start=2): # Start at 2 (header is row 1)
try:
# Extract data from CSV
contract_ref = row.get('contract_ref', '').strip()
line_sequence = row.get('line_sequence', '').strip()
product_code = row.get('product', '').strip()
supplier_name = row.get('supplier', '').strip()
currency_code = row.get('currency', '').strip()
p_r_value = row.get('p_r', '').strip()
mode_name = row.get('mode', '').strip()
price_value = row.get('price', '').strip()
unit_value = row.get('unit', '').strip()
print(f"Processing row {row_num}: {contract_ref} - Line {line_sequence} - {product_code}")
# Validate required fields
if not contract_ref:
print(f" ✗ Skipping: Missing contract_ref\n")
skipped_count += 1
continue
if not line_sequence:
print(f" ✗ Skipping: Missing line_sequence\n")
skipped_count += 1
continue
if not product_code:
print(f" ✗ Skipping: Missing product\n")
skipped_count += 1
continue
# Cache contract and line if same as previous row
if contract_ref != current_contract_ref:
current_contract = find_purchase_contract_by_ref(contract_ref)
current_contract_ref = contract_ref
current_line_sequence = None
current_line = None
if not current_contract:
print(f" ✗ Skipping: Contract not found\n")
skipped_count += 1
continue
# Cache line if same as previous row
if line_sequence != current_line_sequence:
current_line = find_contract_line_by_sequence(current_contract, line_sequence)
current_line_sequence = line_sequence
if not current_line:
print(f" ✗ Skipping: Contract line not found\n")
skipped_count += 1
continue
# Find related records
product = find_product_by_code(product_code)
if not product:
print(f" ✗ Skipping: Product not found\n")
skipped_count += 1
continue
supplier = find_party_by_name(supplier_name)
if not supplier:
print(f" ✗ Skipping: Supplier not found\n")
skipped_count += 1
continue
# Ensure party has SUPPLIER category
supplier, is_supplier = ensure_party_is_supplier(supplier, auto_enable=AUTO_ENABLE_SUPPLIER)
if not is_supplier:
if SKIP_NON_SUPPLIERS:
print(f" ⚠ Skipping purchase - party does not have SUPPLIER category\n")
skipped_count += 1
current_purchase = None
continue
else:
error_msg = f"Row {row_num}: Party '{supplier.rec_name}' does not have SUPPLIER category"
errors.append(error_msg)
error_count += 1
current_purchase = None
continue
currency = find_currency_by_code(currency_code)
if not currency:
print(f" ✗ Skipping: Currency not found\n")
skipped_count += 1
continue
# Parse price
price = parse_decimal(price_value, 'price')
if price is None:
print(f" ✗ Skipping: Invalid price\n")
skipped_count += 1
continue
# Determine payable/receivable
payable_receivable = find_payable_receivable_by_name(p_r_value)
# Find fee mode
mode = find_fee_mode_by_name(mode_name)
# Check if fee already exists
existing_fees = get_existing_fees_for_line(current_line)
if fee_already_exists(existing_fees, product, supplier, price):
print(f" ○ Fee already exists for this line\n")
skipped_count += 1
continue
# Create the fee
fee = PurchaseLineFee()
fee.line = current_line
fee.product = product
fee.supplier = supplier
fee.currency = currency
fee.price = price
# Set type if found and field exists
if mode and hasattr(fee, 'type'):
fee.type = 'ordered' # Assuming all imported fees are 'ordered'
# Set weight_type if found and field exists
if mode and hasattr(fee, 'weight_type'):
fee.weight_type = 'brut'
# Set p_r (payable or receivable) if found and field exists
if mode and hasattr(fee, 'p_r'):
fee.p_r = payable_receivable
# Set mode if found and field exists
if mode and hasattr(fee, 'mode'):
fee.mode = mode
# Set unit if field exists
if unit_value and hasattr(fee, 'unit'):
# Try to find the unit
Unit = Model.get('product.uom')
units = Unit.find([('symbol', '=', unit_value)])
if not units:
units = Unit.find([('name', '=', unit_value)])
if units:
fee.unit = units[0]
# Save the fee
fee.save()
print(f" ✓ Fee created successfully")
print(f" Product: {product.rec_name}")
print(f" Supplier: {supplier.rec_name}")
print(f" Price: {price} {currency.code}")
print(f" Type: {payable_receivable}")
print()
imported_count += 1
except Exception as e:
error_msg = f"Row {row_num} - {contract_ref}: {str(e)}"
errors.append(error_msg)
error_count += 1
print(f"✗ Error on row {row_num}: {e}\n")
import traceback
traceback.print_exc()
# Summary
print(f"{'='*70}")
print("IMPORT SUMMARY")
print(f"{'='*70}")
print(f"Successfully imported: {imported_count} fees")
print(f"Skipped (missing data or already exist): {skipped_count} fees")
print(f"Errors: {error_count}")
if errors:
print(f"\nError details:")
for error in errors:
print(f" - {error}")
print(f"\n{'='*70}")
except FileNotFoundError:
print(f"✗ Error: CSV file not found at {csv_file}")
print(f"Please update CSV_FILE_PATH in the script with the correct path.")
except Exception as e:
print(f"✗ Fatal error: {e}")
import traceback
traceback.print_exc()
def verify_import():
"""Verify imported purchase contract fees"""
print(f"\n{'='*70}")
print("VERIFICATION - Purchase Contract Line Fees")
print(f"{'='*70}\n")
try:
PurchaseLineFee = Model.get('fee.fee')
# Find all fees (or limit to recently created ones)
fees = PurchaseLineFee.find([], order=[('id', 'DESC')])
if fees:
print(f"Found {len(fees)} fees (showing last 50):\n")
print(f"{'ID':<8} {'Contract':<15} {'Product':<25} {'Supplier':<25} {'Price':<12} {'Type':<12}")
print("-" * 105)
for fee in fees[:50]: # Show last 50 created
fee_id = fee.id
# Get contract reference
contract_ref = 'N/A'
if hasattr(fee, 'line') and fee.line:
line = fee.line
if hasattr(line, 'purchase') and line.purchase:
contract = line.purchase
if hasattr(contract, 'reference') and contract.reference:
contract_ref = str(contract.reference)[:14]
product = fee.product.rec_name[:24] if hasattr(fee, 'product') and fee.product else 'N/A'
supplier = fee.supplier.rec_name[:24] if hasattr(fee, 'supplier') and fee.supplier else 'N/A'
price = f"{fee.price:.2f}" if hasattr(fee, 'price') and fee.price else 'N/A'
# Get type (payable/receivable)
fee_type = 'N/A'
if hasattr(fee, 'type'):
fee_type = fee.type
elif hasattr(fee, 'payable_receivable'):
fee_type = fee.payable_receivable
print(f"{fee_id:<8} {contract_ref:<15} {product:<25} {supplier:<25} {price:<12} {fee_type:<12}")
else:
print("No fees found")
print()
except Exception as e:
print(f"✗ Error during verification: {e}")
import traceback
traceback.print_exc()
def list_purchase_contracts():
"""List purchase contracts for debugging"""
Purchase = Model.get('purchase.purchase')
print(f"\n{'='*70}")
print("AVAILABLE PURCHASE CONTRACTS (first 20)")
print(f"{'='*70}")
contracts = Purchase.find([], order=[('id', 'DESC')], limit=20)
if contracts:
print(f"{'ID':<8} {'Reference':<20} {'Party':<30} {'State':<12}")
print("-" * 70)
for contract in contracts:
contract_id = contract.id
reference = contract.reference[:19] if contract.reference else 'N/A'
party = contract.party.rec_name[:29] if contract.party else 'N/A'
state = contract.state if contract.state else 'N/A'
print(f"{contract_id:<8} {reference:<20} {party:<30} {state:<12}")
# Show number of lines
if hasattr(contract, 'lines') and contract.lines:
print(f" Lines: {len(contract.lines)}")
else:
print("No purchase contracts found")
print(f"{'='*70}\n")
def main():
print("="*70)
print("TRYTON PURCHASE CONTRACT FEE IMPORT SCRIPT")
print("Using Proteus with XML-RPC Connection")
print("="*70)
print()
# Connect to Tryton using XML-RPC
if not connect_to_tryton():
return 1
# Optional: List purchase contracts for debugging
# Uncomment the following line to see available contracts
# list_purchase_contracts()
# Import purchase contract fees
import_purchase_contract_fees(CSV_FILE_PATH)
# Verify import
verify_import()
return 0
if __name__ == '__main__':
exit(main())