310 lines
10 KiB
Python
310 lines
10 KiB
Python
import csv
|
|
from proteus import config, Model
|
|
from decimal import Decimal
|
|
|
|
# Configuration
|
|
DATABASE_NAME = 'tradon'
|
|
CSV_FILE_PATH = r'C:\Users\SylvainDUVERNAY\Open Squared\Production - Documents\TRADON Implementation\ITSA\Reference Data\Loaders\Services.csv' # UPDATE THIS PATH!
|
|
|
|
# Product configuration
|
|
PRODUCT_TYPE = 'service' # Service type products
|
|
DEFAULT_CATEGORY = 'Services' # Default category name if not found
|
|
DEFAULT_UOM = 'Mt' # Default UOM if not found
|
|
|
|
def connect_to_tryton():
|
|
"""Establish connection to Tryton database"""
|
|
print(f"Connecting to Tryton database: {DATABASE_NAME}")
|
|
try:
|
|
#config.set_trytond(DATABASE_NAME)
|
|
config.set_xmlrpc('https://admin:dsproject@itsa.open-squared.tech/tradon/')
|
|
|
|
print("✓ Connected successfully!\n")
|
|
return True
|
|
except Exception as e:
|
|
print(f"✗ Connection failed: {e}")
|
|
return False
|
|
|
|
def get_or_create_category(category_name):
|
|
"""Find or create a product category"""
|
|
Category = Model.get('product.category')
|
|
|
|
# Try to find existing category
|
|
categories = Category.find([('name', '=', category_name)])
|
|
|
|
if categories:
|
|
print(f" Found existing category: {category_name}")
|
|
return categories[0]
|
|
else:
|
|
# Create new category
|
|
new_category = Category()
|
|
new_category.name = category_name
|
|
new_category.save()
|
|
print(f" ✓ Created new category: {category_name}")
|
|
return new_category
|
|
|
|
def get_uom(uom_name):
|
|
"""Find Unit of Measure by name"""
|
|
Uom = Model.get('product.uom')
|
|
|
|
# Try exact match first
|
|
uoms = Uom.find([('name', '=', uom_name)])
|
|
|
|
if uoms:
|
|
return uoms[0]
|
|
|
|
# Try case-insensitive search
|
|
all_uoms = Uom.find([])
|
|
for uom in all_uoms:
|
|
if uom.name.lower() == uom_name.lower():
|
|
return uom
|
|
|
|
# If not found, return Unit (default)
|
|
print(f" ⚠ Warning: UOM '{uom_name}' not found, using 'Unit'")
|
|
default_uoms = Uom.find([('name', '=', 'Unit')])
|
|
if default_uoms:
|
|
return default_uoms[0]
|
|
|
|
# If even Unit is not found, get the first available
|
|
all_uoms = Uom.find([])
|
|
if all_uoms:
|
|
print(f" ⚠ Using first available UOM: {all_uoms[0].name}")
|
|
return all_uoms[0]
|
|
|
|
raise ValueError("No UOM found in database!")
|
|
|
|
def check_product_exists(code):
|
|
"""Check if product with given code already exists"""
|
|
Product = Model.get('product.product')
|
|
products = Product.find([('code', '=', code)])
|
|
return products[0] if products else None
|
|
|
|
def create_service_product(row, category, uom):
|
|
"""Create a new service product"""
|
|
Product = Model.get('product.product')
|
|
Template = Model.get('product.template')
|
|
|
|
# Create template first
|
|
template = Template()
|
|
template.name = row['name']
|
|
template.type = PRODUCT_TYPE
|
|
template.list_price = Decimal(row['sale_price']) if row['sale_price'] else Decimal('0.00')
|
|
template.cost_price = Decimal(row['cost_price']) if row['cost_price'] else Decimal('0.00')
|
|
template.default_uom = uom
|
|
template.category = category
|
|
template.salable = True
|
|
template.purchasable = False # Services typically not purchased
|
|
|
|
if row.get('description'):
|
|
template.description = row['description']
|
|
|
|
template.save()
|
|
|
|
# Create product variant
|
|
product = Product()
|
|
product.template = template
|
|
product.code = row['code']
|
|
product.save()
|
|
|
|
return product
|
|
|
|
def import_services(csv_file):
|
|
"""Import services from CSV file"""
|
|
Product = Model.get('product.product')
|
|
|
|
imported_count = 0
|
|
skipped_count = 0
|
|
error_count = 0
|
|
errors = []
|
|
|
|
print(f"{'='*70}")
|
|
print(f"Importing service products from: {csv_file}")
|
|
print(f"{'='*70}\n")
|
|
|
|
try:
|
|
# Open with utf-8-sig to handle BOM
|
|
with open(csv_file, 'r', encoding='utf-8-sig') as file:
|
|
reader = csv.DictReader(file)
|
|
|
|
# Debug: Show detected columns
|
|
print(f"Detected columns: {reader.fieldnames}\n")
|
|
|
|
for row_num, row in enumerate(reader, start=2):
|
|
try:
|
|
# Clean up values
|
|
code = row.get('code', '').strip()
|
|
name = row.get('name', '').strip()
|
|
category_name = row.get('category', DEFAULT_CATEGORY).strip() or DEFAULT_CATEGORY
|
|
uom_name = row.get('uom', DEFAULT_UOM).strip() or DEFAULT_UOM
|
|
sale_price = row.get('sale_price', '0.00').strip()
|
|
cost_price = row.get('cost_price', '0.00').strip()
|
|
description = row.get('description', '').strip()
|
|
|
|
# Skip empty rows
|
|
if not code and not name:
|
|
continue
|
|
|
|
# Validate required fields
|
|
if not code or not name:
|
|
errors.append(f"Row {row_num}: Missing code or name")
|
|
error_count += 1
|
|
print(f"✗ Row {row_num}: Missing required fields")
|
|
continue
|
|
|
|
print(f"Processing Row {row_num}: {code} - {name}")
|
|
|
|
# Check if product already exists
|
|
existing_product = check_product_exists(code)
|
|
|
|
if existing_product:
|
|
print(f" ⚠ Product code '{code}' already exists: {existing_product.template.name}")
|
|
print(f" Skipping...")
|
|
skipped_count += 1
|
|
continue
|
|
|
|
# Get or create category
|
|
category = get_or_create_category(category_name)
|
|
|
|
# Get UOM
|
|
uom = get_uom(uom_name)
|
|
print(f" Using UOM: {uom.name}")
|
|
|
|
# Create the product
|
|
row_data = {
|
|
'code': code,
|
|
'name': name,
|
|
'sale_price': sale_price,
|
|
'cost_price': cost_price,
|
|
'description': description
|
|
}
|
|
|
|
product = create_service_product(row_data, category, uom)
|
|
|
|
print(f" ✓ Created service product: {name}")
|
|
print(f" Code: {code}")
|
|
print(f" Category: {category.name}")
|
|
print(f" Sale Price: {sale_price}")
|
|
print(f" Cost Price: {cost_price}")
|
|
if description:
|
|
print(f" Description: {description[:50]}...")
|
|
print()
|
|
|
|
imported_count += 1
|
|
|
|
except Exception as e:
|
|
error_msg = f"Row {row_num} - {code} ({name}): {str(e)}"
|
|
errors.append(error_msg)
|
|
error_count += 1
|
|
print(f"✗ Error on row {row_num}: {e}\n")
|
|
|
|
# Summary
|
|
print(f"{'='*70}")
|
|
print("IMPORT SUMMARY")
|
|
print(f"{'='*70}")
|
|
print(f"Successfully imported: {imported_count} service products")
|
|
print(f"Skipped (already exist): {skipped_count} products")
|
|
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 service products"""
|
|
Product = Model.get('product.product')
|
|
|
|
print(f"\n{'='*70}")
|
|
print("VERIFICATION - Service Products")
|
|
print(f"{'='*70}\n")
|
|
|
|
# Find all service type products
|
|
products = Product.find([('template.type', '=', 'service')])
|
|
|
|
if products:
|
|
print(f"Found {len(products)} service products:\n")
|
|
print(f"{'Code':<12} {'Name':<35} {'Category':<20} {'Sale Price':<12}")
|
|
print("-" * 80)
|
|
|
|
for product in products:
|
|
code = product.code or 'N/A'
|
|
name = product.template.name[:34] if product.template.name else 'N/A'
|
|
category = product.template.category.name if product.template.category else 'N/A'
|
|
sale_price = f"{product.template.list_price:.2f}" if product.template.list_price else '0.00'
|
|
|
|
print(f"{code:<12} {name:<35} {category:<20} {sale_price:<12}")
|
|
else:
|
|
print("No service products found")
|
|
|
|
print()
|
|
|
|
def list_available_uoms():
|
|
"""List all available UOMs in the database"""
|
|
print(f"\n{'='*70}")
|
|
print("AVAILABLE UNITS OF MEASURE")
|
|
print(f"{'='*70}\n")
|
|
|
|
Uom = Model.get('product.uom')
|
|
uoms = Uom.find([])
|
|
|
|
if uoms:
|
|
print(f"Found {len(uoms)} UOMs:\n")
|
|
for uom in uoms:
|
|
print(f" - {uom.name} (Symbol: {uom.symbol if hasattr(uom, 'symbol') else 'N/A'})")
|
|
else:
|
|
print("No UOMs found")
|
|
|
|
print()
|
|
|
|
def list_available_categories():
|
|
"""List all available product categories"""
|
|
print(f"\n{'='*70}")
|
|
print("AVAILABLE PRODUCT CATEGORIES")
|
|
print(f"{'='*70}\n")
|
|
|
|
Category = Model.get('product.category')
|
|
categories = Category.find([])
|
|
|
|
if categories:
|
|
print(f"Found {len(categories)} categories:\n")
|
|
for cat in categories:
|
|
print(f" - {cat.name}")
|
|
else:
|
|
print("No categories found")
|
|
|
|
print()
|
|
|
|
def main():
|
|
print("="*70)
|
|
print("TRYTON SERVICE PRODUCT IMPORT SCRIPT (using Proteus)")
|
|
print("="*70)
|
|
print()
|
|
|
|
# Connect to Tryton
|
|
if not connect_to_tryton():
|
|
return 1
|
|
|
|
# Optional: List available UOMs and categories
|
|
# Uncomment these if you want to see what's available in your database
|
|
# list_available_uoms()
|
|
# list_available_categories()
|
|
|
|
# Import service products
|
|
import_services(CSV_FILE_PATH)
|
|
|
|
# Verify import
|
|
verify_import()
|
|
|
|
return 0
|
|
|
|
if __name__ == '__main__':
|
|
exit(main()) |