|
16 | 16 | run_module_uninstallation, |
17 | 17 | run_update_module_list, |
18 | 18 | ) |
| 19 | +from .lib.actions.vies_manager import ( |
| 20 | + disable_vat_validation, |
| 21 | + get_vat_validation_settings, |
| 22 | + restore_vat_validation_settings, |
| 23 | + run_vies_validation, |
| 24 | +) |
19 | 25 | from .lib.validation import display_validation_results, validate_csv_data |
20 | 26 | from .logging_config import log, setup_logging |
21 | 27 | from .migrator import run_migration |
@@ -286,6 +292,315 @@ def invoice_v9_cmd(connection_file: str, **kwargs: Any) -> None: |
286 | 292 | run_invoice_v9_workflow(**kwargs) |
287 | 293 |
|
288 | 294 |
|
| 295 | +# --- VAT Validation Command Group --- |
| 296 | +@cli.group(name="vat") |
| 297 | +def vat_group() -> None: |
| 298 | + """Commands for managing VAT/VIES validation settings.""" |
| 299 | + pass |
| 300 | + |
| 301 | + |
| 302 | +@vat_group.command(name="get-settings") |
| 303 | +@click.option( |
| 304 | + "--connection-file", |
| 305 | + required=True, |
| 306 | + type=click.Path(exists=True, dir_okay=False), |
| 307 | + help="Path to the Odoo connection file.", |
| 308 | +) |
| 309 | +@click.option( |
| 310 | + "--company-ids", |
| 311 | + default=None, |
| 312 | + help="Comma-separated list of company IDs to check. If not specified, checks all.", |
| 313 | +) |
| 314 | +@click.option( |
| 315 | + "--include-stdnum/--no-stdnum", |
| 316 | + default=True, |
| 317 | + help="Include stdnum validation settings. Default: True.", |
| 318 | +) |
| 319 | +def vat_get_settings_cmd( |
| 320 | + connection_file: str, |
| 321 | + company_ids: Optional[str], |
| 322 | + include_stdnum: bool, |
| 323 | +) -> None: |
| 324 | + """Get current VAT validation settings for all companies.""" |
| 325 | + from rich.console import Console |
| 326 | + from rich.table import Table |
| 327 | + |
| 328 | + company_id_list: Optional[list[int]] = None |
| 329 | + if company_ids: |
| 330 | + company_id_list = [int(c.strip()) for c in company_ids.split(",") if c.strip()] |
| 331 | + |
| 332 | + settings = get_vat_validation_settings( |
| 333 | + config=connection_file, |
| 334 | + company_ids=company_id_list, |
| 335 | + include_stdnum=include_stdnum, |
| 336 | + ) |
| 337 | + |
| 338 | + if not settings: |
| 339 | + Console().print("[red]Failed to retrieve VAT settings.[/red]") |
| 340 | + return |
| 341 | + |
| 342 | + console = Console() |
| 343 | + table = Table(title="VAT Validation Settings") |
| 344 | + table.add_column("Company ID", style="cyan") |
| 345 | + table.add_column("VIES Check", style="green") |
| 346 | + |
| 347 | + for company_id, vies_enabled in sorted(settings.vies_settings.items()): |
| 348 | + table.add_row(str(company_id), "✓ Enabled" if vies_enabled else "✗ Disabled") |
| 349 | + |
| 350 | + console.print(table) |
| 351 | + |
| 352 | + if include_stdnum and settings.stdnum_settings: |
| 353 | + console.print("\n[bold]stdnum Settings (ir.config_parameter):[/bold]") |
| 354 | + for key, value in settings.stdnum_settings.items(): |
| 355 | + console.print(f" {key}: {value}") |
| 356 | + |
| 357 | + |
| 358 | +@vat_group.command(name="disable") |
| 359 | +@click.option( |
| 360 | + "--connection-file", |
| 361 | + required=True, |
| 362 | + type=click.Path(exists=True, dir_okay=False), |
| 363 | + help="Path to the Odoo connection file.", |
| 364 | +) |
| 365 | +@click.option( |
| 366 | + "--company-ids", |
| 367 | + default=None, |
| 368 | + help="Comma-separated list of company IDs. If not specified, disables for all.", |
| 369 | +) |
| 370 | +@click.option( |
| 371 | + "--vies/--no-vies", |
| 372 | + default=True, |
| 373 | + help="Disable VIES online check. Default: True.", |
| 374 | +) |
| 375 | +@click.option( |
| 376 | + "--stdnum/--no-stdnum", |
| 377 | + default=True, |
| 378 | + help="Disable stdnum format validation. Default: True.", |
| 379 | +) |
| 380 | +@click.option( |
| 381 | + "--save-settings", |
| 382 | + is_flag=True, |
| 383 | + default=True, |
| 384 | + help="Save current settings for later restoration. Default: True.", |
| 385 | +) |
| 386 | +@click.option( |
| 387 | + "--output", |
| 388 | + default=None, |
| 389 | + type=click.Path(dir_okay=False), |
| 390 | + help="Save settings to a JSON file for later restoration.", |
| 391 | +) |
| 392 | +def vat_disable_cmd( |
| 393 | + connection_file: str, |
| 394 | + company_ids: Optional[str], |
| 395 | + vies: bool, |
| 396 | + stdnum: bool, |
| 397 | + save_settings: bool, |
| 398 | + output: Optional[str], |
| 399 | +) -> None: |
| 400 | + """Disable VAT validation (VIES and/or stdnum) for companies.""" |
| 401 | + import json |
| 402 | + |
| 403 | + from rich.console import Console |
| 404 | + |
| 405 | + console = Console() |
| 406 | + |
| 407 | + company_id_list: Optional[list[int]] = None |
| 408 | + if company_ids: |
| 409 | + company_id_list = [int(c.strip()) for c in company_ids.split(",") if c.strip()] |
| 410 | + |
| 411 | + settings = disable_vat_validation( |
| 412 | + config=connection_file, |
| 413 | + company_ids=company_id_list, |
| 414 | + disable_vies=vies, |
| 415 | + disable_stdnum=stdnum, |
| 416 | + save_settings=save_settings, |
| 417 | + ) |
| 418 | + |
| 419 | + if not settings: |
| 420 | + console.print("[red]Failed to disable VAT validation.[/red]") |
| 421 | + return |
| 422 | + |
| 423 | + console.print("[green]VAT validation disabled successfully.[/green]") |
| 424 | + |
| 425 | + if output: |
| 426 | + settings_dict = { |
| 427 | + "vies_settings": settings.vies_settings, |
| 428 | + "stdnum_settings": settings.stdnum_settings, |
| 429 | + "timestamp": settings.timestamp, |
| 430 | + } |
| 431 | + with open(output, "w") as f: |
| 432 | + json.dump(settings_dict, f, indent=2) |
| 433 | + console.print(f"Settings saved to: {output}") |
| 434 | + elif save_settings: |
| 435 | + console.print( |
| 436 | + "[dim]Settings stored in memory. Use 'vat restore' to restore them.[/dim]" |
| 437 | + ) |
| 438 | + |
| 439 | + |
| 440 | +@vat_group.command(name="restore") |
| 441 | +@click.option( |
| 442 | + "--connection-file", |
| 443 | + required=True, |
| 444 | + type=click.Path(exists=True, dir_okay=False), |
| 445 | + help="Path to the Odoo connection file.", |
| 446 | +) |
| 447 | +@click.option( |
| 448 | + "--input", |
| 449 | + "input_file", |
| 450 | + default=None, |
| 451 | + type=click.Path(exists=True, dir_okay=False), |
| 452 | + help="Restore settings from a JSON file saved by 'vat disable --output'.", |
| 453 | +) |
| 454 | +def vat_restore_cmd( |
| 455 | + connection_file: str, |
| 456 | + input_file: Optional[str], |
| 457 | +) -> None: |
| 458 | + """Restore VAT validation settings to their original state.""" |
| 459 | + import json |
| 460 | + |
| 461 | + from rich.console import Console |
| 462 | + |
| 463 | + from .lib.actions.vies_manager import VatValidationSettings |
| 464 | + |
| 465 | + console = Console() |
| 466 | + |
| 467 | + if input_file: |
| 468 | + with open(input_file) as f: |
| 469 | + data = json.load(f) |
| 470 | + # Convert string keys back to int for company IDs |
| 471 | + vies_settings = {int(k): v for k, v in data.get("vies_settings", {}).items()} |
| 472 | + settings = VatValidationSettings( |
| 473 | + vies_settings=vies_settings, |
| 474 | + stdnum_settings=data.get("stdnum_settings", {}), |
| 475 | + timestamp=data.get("timestamp", 0), |
| 476 | + ) |
| 477 | + else: |
| 478 | + console.print( |
| 479 | + "[red]No settings file provided. " |
| 480 | + "Use --input to specify a settings file.[/red]" |
| 481 | + ) |
| 482 | + console.print( |
| 483 | + "[dim]Tip: Use 'vat disable --output settings.json' to save settings.[/dim]" |
| 484 | + ) |
| 485 | + return |
| 486 | + |
| 487 | + success = restore_vat_validation_settings( |
| 488 | + config=connection_file, |
| 489 | + settings=settings, |
| 490 | + ) |
| 491 | + |
| 492 | + if success: |
| 493 | + console.print("[green]VAT validation settings restored successfully.[/green]") |
| 494 | + else: |
| 495 | + console.print("[red]Failed to restore VAT validation settings.[/red]") |
| 496 | + |
| 497 | + |
| 498 | +@vat_group.command(name="validate") |
| 499 | +@click.option( |
| 500 | + "--connection-file", |
| 501 | + required=True, |
| 502 | + type=click.Path(exists=True, dir_okay=False), |
| 503 | + help="Path to the Odoo connection file.", |
| 504 | +) |
| 505 | +@click.option( |
| 506 | + "--batch-size", |
| 507 | + default=50, |
| 508 | + type=int, |
| 509 | + help="Number of records to validate per batch. Default: 50.", |
| 510 | +) |
| 511 | +@click.option( |
| 512 | + "--delay", |
| 513 | + default=1.0, |
| 514 | + type=float, |
| 515 | + help="Delay between batches in seconds. Default: 1.0.", |
| 516 | +) |
| 517 | +@click.option( |
| 518 | + "--notify-users", |
| 519 | + default=None, |
| 520 | + help="Comma-separated list of user IDs to notify on failures.", |
| 521 | +) |
| 522 | +@click.option( |
| 523 | + "--domain", |
| 524 | + default=None, |
| 525 | + help="Odoo domain filter as a list string. " |
| 526 | + "Example: \"[('is_company', '=', True)]\"", |
| 527 | +) |
| 528 | +@click.option( |
| 529 | + "--max-records", |
| 530 | + default=None, |
| 531 | + type=int, |
| 532 | + help="Maximum number of records to validate.", |
| 533 | +) |
| 534 | +def vat_validate_cmd( |
| 535 | + connection_file: str, |
| 536 | + batch_size: int, |
| 537 | + delay: float, |
| 538 | + notify_users: Optional[str], |
| 539 | + domain: Optional[str], |
| 540 | + max_records: Optional[int], |
| 541 | +) -> None: |
| 542 | + """Validate VAT numbers against VIES in batches with optional notifications.""" |
| 543 | + import ast |
| 544 | + |
| 545 | + from rich.console import Console |
| 546 | + from rich.table import Table |
| 547 | + |
| 548 | + console = Console() |
| 549 | + |
| 550 | + notify_user_ids: Optional[list[int]] = None |
| 551 | + if notify_users: |
| 552 | + notify_user_ids = [int(u.strip()) for u in notify_users.split(",") if u.strip()] |
| 553 | + |
| 554 | + parsed_domain: Optional[list[Any]] = None |
| 555 | + if domain: |
| 556 | + try: |
| 557 | + parsed_domain = ast.literal_eval(domain) |
| 558 | + except (ValueError, SyntaxError) as e: |
| 559 | + console.print(f"[red]Invalid domain format: {e}[/red]") |
| 560 | + return |
| 561 | + |
| 562 | + console.print(f"Starting VIES validation (batch size: {batch_size})...") |
| 563 | + |
| 564 | + result = run_vies_validation( |
| 565 | + config=connection_file, |
| 566 | + batch_size=batch_size, |
| 567 | + delay_between_batches=delay, |
| 568 | + notify_user_ids=notify_user_ids, |
| 569 | + domain=parsed_domain, |
| 570 | + max_records=max_records, |
| 571 | + ) |
| 572 | + |
| 573 | + # Display results |
| 574 | + table = Table(title="VIES Validation Results") |
| 575 | + table.add_column("Metric", style="cyan") |
| 576 | + table.add_column("Value", style="green") |
| 577 | + |
| 578 | + table.add_row("Total Checked", str(result.total_checked)) |
| 579 | + table.add_row("Valid", str(result.valid_count)) |
| 580 | + table.add_row("Invalid", str(result.invalid_count)) |
| 581 | + table.add_row("Errors", str(result.error_count)) |
| 582 | + |
| 583 | + console.print(table) |
| 584 | + |
| 585 | + if result.invalid_partners: |
| 586 | + console.print("\n[bold red]Invalid VAT Numbers:[/bold red]") |
| 587 | + for partner in result.invalid_partners[:20]: |
| 588 | + console.print( |
| 589 | + f" Partner {partner['id']}: {partner['vat']} - {partner['name']}" |
| 590 | + ) |
| 591 | + if len(result.invalid_partners) > 20: |
| 592 | + console.print(f" ... and {len(result.invalid_partners) - 20} more") |
| 593 | + |
| 594 | + if result.error_partners: |
| 595 | + console.print("\n[bold yellow]Errors:[/bold yellow]") |
| 596 | + for partner in result.error_partners[:10]: |
| 597 | + console.print( |
| 598 | + f" Partner {partner['id']}: {partner['vat']} - {partner['error']}" |
| 599 | + ) |
| 600 | + if len(result.error_partners) > 10: |
| 601 | + console.print(f" ... and {len(result.error_partners) - 10} more") |
| 602 | + |
| 603 | + |
289 | 604 | # --- Import Command --- |
290 | 605 | @cli.command(name="import") |
291 | 606 | @click.option( |
|
0 commit comments