From 4b2b53caa7a126e5f4d8bce8e15d4c04371d0723 Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Thu, 29 May 2025 21:35:39 -0400 Subject: [PATCH] Revert "Clean up repository for apps.odoo.com sharing: Remove non-18.0 addons and document them in README.md" Addons were butchered by this commit. This reverts commit b33f25c68858143b9eb4de2fe40a9f083b6c3e6a. --- README.md | 106 -- account_credit_hold/__init__.py | 1 + account_credit_hold/__manifest__.py | 20 + account_credit_hold/migration18.md | 246 +++ account_credit_hold/models/__init__.py | 5 + .../models/account_followup.py | 8 + .../models/account_followup_report.py | 12 + account_credit_hold/models/res_partner.py | 80 + account_credit_hold/models/sale_order.py | 17 + account_credit_hold/models/stock_picking.py | 9 + account_credit_hold/readme.md | 40 + .../views/account_followup_views.xml | 49 + .../views/res_partner_views.xml | 34 + .../views/sale_order_views.xml | 18 + .../views/stock_picking_views.xml | 19 + aged_partner_balance_na/__init__.py | 33 + aged_partner_balance_na/__manifest__.py | 32 + aged_partner_balance_na/models/__init__.py | 1 + .../models/aged_partner_balance.py | 245 +++ ai_integration/__init__.py | 1 + ai_integration/__manifest__.py | 39 + ai_integration/models/__init__.py | 9 + ai_integration/models/ai_generation_params.py | 42 + ai_integration/models/ai_model.py | 80 + ai_integration/models/ai_model_stats.py | 94 + ai_integration/models/ai_provider.py | 73 + ai_integration/models/ai_provider_instance.py | 106 ++ .../models/ai_provider_interface.py | 55 + ai_integration/models/doc_models.md | 90 + ai_integration/models/mixins/__init__.py | 1 + ai_integration/models/mixins/ai_base_mixin.py | 167 ++ ai_integration/models/res_company.py | 21 + ai_integration/models/res_config_settings.py | 22 + ai_integration/security/ai_security.xml | 20 + ai_integration/security/ir.model.access.csv | 11 + ai_integration/security/ir_rule.xml | 28 + ai_integration/views/ai_model_stats_views.xml | 118 ++ ai_integration/views/ai_model_views.xml | 96 + .../views/ai_provider_instance_views.xml | 107 ++ ai_integration/views/ai_provider_views.xml | 88 + ai_integration/views/menu.xml | 51 + ai_integration/views/res_company_views.xml | 14 + .../views/res_config_settings_views.xml | 66 + ai_integration_ollama_api/__init__.py | 1 + ai_integration_ollama_api/__manifest__.py | 36 + .../data/ai_provider_data.xml | 13 + .../data/ai_provider_instance_data.xml | 31 + .../data/ir_model_inherit.xml | 10 + .../data/ollama_provider.xml | 13 + ai_integration_ollama_api/doc/ollama_api.md | 1627 +++++++++++++++++ .../migrations/1.0.0/post-migration.py | 17 + ai_integration_ollama_api/models/__init__.py | 27 + .../models/ai_provider_instance.py | 153 ++ .../models/ai_provider_ollama.py.bak | 35 + .../models/ollama_model_stats.py | 116 ++ .../models/ollama_provider.py | 324 ++++ .../models/ollama_provider_mixin.py | 274 +++ .../security/ir.model.access.csv | 5 + .../views/ai_provider_instance_views.xml | 15 + .../views/ollama_stats_views.xml | 118 ++ .../views/ollama_views.xml | 108 ++ ai_integration_openai_api/__init__.py | 1 + ai_integration_openai_api/__manifest__.py | 35 + .../data/chatgpt_provider.xml | 14 + ai_integration_openai_api/models/__init__.py | 2 + .../models/chatgpt_instance.py | 48 + .../models/chatgpt_provider.py | 163 ++ .../security/ir.model.access.csv | 3 + .../views/chatgpt_instance_views.xml | 106 ++ .../__init__.py | 5 + .../__manifest__.py | 21 + .../migration18.md | 87 + .../models/__init__.py | 2 + .../models/mail_wizard_invite.py | 11 + bemade_attachments_cleanup/__init__.py | 1 + bemade_attachments_cleanup/__manifest__.py | 22 + bemade_attachments_cleanup/i18n/fr_CA.po | 37 + bemade_attachments_cleanup/migration18.md | 31 + .../security/ir.model.access.csv | 2 + bemade_attachments_cleanup/wizard/__init__.py | 1 + .../wizard/attachment_cleanup_wizard.py | 45 + .../wizard/attachment_cleanup_wizard_view.xml | 32 + bemade_documents_portal/__init__.py | 2 + bemade_documents_portal/__manifest__.py | 32 + .../controllers/__init__.py | 1 + bemade_documents_portal/controllers/portal.py | 65 + bemade_documents_portal/models/__init__.py | 1 + bemade_documents_portal/models/documents.py | 17 + .../views/document_portal_templates.xml | 112 ++ bemade_fetchmail_only_production/README.md | 0 bemade_fetchmail_only_production/__init__.py | 4 + .../__manifest__.py | 20 + .../migration18.md | 31 + .../models/__init__.py | 4 + .../models/fetchmail_server.py | 23 + .../readme/HISTORY.rst | 10 + bemade_fix_quality_report/__init__.py | 0 bemade_fix_quality_report/__manifest__.py | 33 + .../worksheet_custom_report_templates.xml | 8 + bemade_fsm/__init__.py | 3 + bemade_fsm/__manifest__.py | 70 + bemade_fsm/data/fsm_data.xml | 28 + bemade_fsm/i18n/fr.po | 1098 +++++++++++ bemade_fsm/migration18.md | 369 ++++ .../migrations/17.0.0.4.0/pre-migrate.py | 81 + bemade_fsm/models/__init__.py | 9 + bemade_fsm/models/fsm_visit.py | 104 ++ bemade_fsm/models/product_template.py | 19 + bemade_fsm/models/project.py | 13 + bemade_fsm/models/res_company.py | 8 + bemade_fsm/models/res_partner.py | 61 + bemade_fsm/models/sale_order.py | 163 ++ bemade_fsm/models/sale_order_line.py | 294 +++ bemade_fsm/models/task.py | 222 +++ bemade_fsm/models/task_template.py | 122 ++ bemade_fsm/reports/__init__.py | 1 + .../worksheet_custom_report_templates.xml | 588 ++++++ .../reports/worksheet_custom_reports.py | 13 + .../reports/worksheet_custom_reports.xml | 11 + bemade_fsm/security/ir.model.access.csv | 5 + bemade_fsm/static/src/js/kanban_view.js | 37 + bemade_fsm/static/src/js/list_view.js | 36 + bemade_fsm/static/src/scss/bemade_fsm.scss | 14 + .../static/src/xml/project_view_buttons.xml | 21 + bemade_fsm/tests/__init__.py | 9 + bemade_fsm/tests/test_bemade_fsm_common.py | 284 +++ bemade_fsm/tests/test_equipment.py | 32 + bemade_fsm/tests/test_fsm_contact_setting.py | 129 ++ bemade_fsm/tests/test_fsm_visit.py | 144 ++ bemade_fsm/tests/test_sale_order.py | 363 ++++ bemade_fsm/tests/test_settings.py | 48 + bemade_fsm/tests/test_task.py | 154 ++ bemade_fsm/tests/test_task_report.py | 43 + bemade_fsm/tests/test_task_template.py | 80 + bemade_fsm/views/menus.xml | 17 + bemade_fsm/views/product_views.xml | 32 + bemade_fsm/views/res_partner.xml | 52 + bemade_fsm/views/sale_order_views.xml | 64 + bemade_fsm/views/task_template_views.xml | 127 ++ bemade_fsm/views/task_views.xml | 229 +++ bemade_fsm/wizard/__init__.py | 2 + bemade_fsm/wizard/new_task_from_template.py | 57 + bemade_fsm/wizard/new_task_from_template.xml | 29 + bemade_fsm/wizard/res_config_settings.py | 22 + bemade_fsm/wizard/res_config_settings.xml | 24 + bemade_full_formview_from_modal/__init__.py | 0 .../__manifest__.py | 40 + .../migration18.md | 64 + .../src/view_dialogs/form_view_dialog.js | 42 + .../src/view_dialogs/form_view_dialog.xml | 13 + .../tests/test_full_formview_from_modal.js | 45 + .../tests/__init__.py | 1 + .../tests/test_full_formview_from_modal.py | 13 + bemade_git_repos_addons/__init__.py | 2 + bemade_git_repos_addons/__manifest__.py | 63 + .../data/default_directories_data.xml | 26 + bemade_git_repos_addons/models/__init__.py | 4 + bemade_git_repos_addons/models/git_addons.py | 28 + bemade_git_repos_addons/models/git_branch.py | 18 + bemade_git_repos_addons/models/git_repos.py | 84 + .../models/res_settings.py | 23 + .../security/ir.model.access.csv | 11 + .../button_create_repos.xml | 12 + .../button_create_repos_controller.js | 21 + .../button_create_repos_view.js | 13 + .../views/action_and_menu.xml | 31 + .../views/git_addons_views.xml | 36 + .../views/git_repos_views.xml | 34 + .../views/res_settings_views.xml | 44 + bemade_git_repos_addons/wizard/__init__.py | 2 + .../wizard/directory_wizard.py | 83 + .../wizard/directory_wizard_views.xml | 31 + .../wizard/git_repos_wizard.py | 36 + .../wizard/git_repos_wizard_views.xml | 21 + bemade_helpdesk_mailcow_blacklist/__init__.py | 1 + .../__manifest__.py | 37 + .../data/helpdesk_stages.xml | 10 + .../demo/demo.xml | 27 + .../migration18.md | 31 + .../models/__init__.py | 1 + .../models/helpdesk_ticket.py | 27 + .../models/res_partner.py | 11 + .../security/ir.model.access.csv | 2 + .../views/helpdesk_ticket_views.xml | 14 + .../__init__.py | 1 + .../__manifest__.py | 31 + .../migration18.md | 72 + .../models/__init__.py | 1 + .../models/mail_thread.py | 43 + bemade_hide_decimal_on_unit/__init__.py | 5 + bemade_hide_decimal_on_unit/__manifest__.py | 23 + bemade_hide_decimal_on_unit/migration18.md | 72 + .../views/purchase.xml | 31 + bemade_hide_decimal_on_unit/views/sale.xml | 16 + bemade_l10n_ca_payroll/__init__.py | 1 + bemade_l10n_ca_payroll/__manifest__.py | 37 + .../data/hr_payslip_input_type_data.xml | 48 + .../data/hr_salary_rule_data.xml | 146 ++ bemade_l10n_ca_payroll/models/__init__.py | 1 + .../models/hr_payroll_structure_type.py | 26 + bemade_l10n_ca_payroll/models/hr_payslip.py | 29 + bemade_mailcow_integration/__init__.py | 3 + bemade_mailcow_integration/__manifest__.py | 45 + .../controllers/controllers.py | 13 + bemade_mailcow_integration/demo/demo.xml | 30 + bemade_mailcow_integration/migration18.md | 95 + .../migrations/15.0.1.0.1/pre-migrate.py | 13 + bemade_mailcow_integration/models/__init__.py | 9 + .../models/mail_alias.py | 29 + bemade_mailcow_integration/models/mailcow.py | 62 + .../models/mailcow_alias.py | 109 ++ .../models/mailcow_blacklist.py | 98 + .../models/mailcow_mailbox.py | 148 ++ .../models/res_config_settings.py | 32 + .../models/res_users.py | 37 + .../security/ir.model.access.csv | 7 + .../static/description/icon.png | Bin 0 -> 24034 bytes .../static/description/logomailcow.svg | 187 ++ .../static/src/js/mailcow.js | 89 + .../static/src/xml/mailcow_templates.xml | 28 + bemade_mailcow_integration/tests/__init__.py | 1 + .../tests/test_mailcow.py | 12 + .../views/mailcow_alias_views.xml | 76 + .../views/mailcow_blacklist_views.xml | 66 + .../views/mailcow_mailbox_views.xml | 82 + .../views/res_config_settings_views.xml | 55 + .../views/res_users_views.xml | 16 + .../views/templates.xml | 24 + bemade_mailcow_integration/views/views.xml | 60 + bemade_margin_vendor_pricelist/__init__.py | 1 + .../__manifest__.py | 31 + .../i18n/bemade_margin_vendor_pricelist.pot | 58 + bemade_margin_vendor_pricelist/i18n/fr.po | 58 + bemade_margin_vendor_pricelist/migration18.md | 93 + .../migrations/17.0.0.0.4/pre-migrate.py | 21 + .../models/__init__.py | 2 + .../models/sale_order.py | 23 + .../models/sale_order_line.py | 117 ++ .../tests/__init__.py | 1 + .../tests/test_margins.py | 372 ++++ .../views/sale_order.xml | 57 + bemade_module_linker/__init__.py | 12 + bemade_module_linker/__manifest__.py | 24 + bemade_module_linker/migration18.md | 31 + bemade_module_linker/models/__init__.py | 1 + .../models/res_config_settings.py | 8 + .../models/res_modules_link.py | 28 + .../views/res_config_settings_views.xml | 33 + bemade_multiple_billing_contacts/__init__.py | 1 + .../__manifest__.py | 44 + .../models/__init__.py | 3 + .../models/account_move.py | 34 + .../models/res_partner.py | 28 + .../models/sale_order.py | 21 + .../tests/__init__.py | 1 + .../tests/test_billing_contacts.py | 117 ++ .../views/account_move_views.xml | 21 + .../views/res_partner_views.xml | 29 + .../views/sale_order_views.xml | 20 + bemade_odoo_partner_scrapper/__init__.py | 3 + bemade_odoo_partner_scrapper/__manifest__.py | 36 + bemade_odoo_partner_scrapper/data/demo.xml | 106 ++ .../data/res_partner_relation_type.xml | 43 + .../models/__init__.py | 3 + .../models/res_partner.py | 264 +++ .../views/res_partner_views.xml | 83 + .../__init__.py | 1 + .../__manifest__.py | 33 + bemade_open_project_details/__init__.py | 0 bemade_open_project_details/__manifest__.py | 39 + .../views/project_project_views.xml | 20 + bemade_packing_wizard/__init__.py | 4 + bemade_packing_wizard/__manifest__.py | 25 + bemade_packing_wizard/migration18.md | 93 + bemade_packing_wizard/models/__init__.py | 5 + .../models/delivery_carrier.py | 13 + .../models/stock_quant_package.py | 84 + .../views/delivery_carrier_views.xml | 13 + .../views/stock_package_views.xml | 17 + bemade_packing_wizard/wizard/__init__.py | 3 + .../wizard/choose_delivery_package.py | 71 + .../wizard/choose_delivery_package_views.xml | 20 + bemade_partner_email_domain/__init__.py | 2 + bemade_partner_email_domain/__manifest__.py | 38 + .../controllers/__init__.py | 1 + .../controllers/main.py | 40 + .../data/mail_template_data.xml | 106 ++ .../data/template_data.xml | 17 + bemade_partner_email_domain/i18n/fr_CA.po | 41 + bemade_partner_email_domain/migration18.md | 93 + .../models/__init__.py | 1 + .../models/res_partner.py | 97 + .../views/res_partner_views.xml | 20 + bemade_partner_root_ancestor/__init__.py | 1 + bemade_partner_root_ancestor/__manifest__.py | 32 + bemade_partner_root_ancestor/migration18.md | 92 + .../models/__init__.py | 1 + .../models/res_partner.py | 18 + .../tests/__init__.py | 1 + .../tests/test_root_ancestor.py | 55 + bemade_payslip_done_as_not_paid/__init__.py | 0 .../__manifest__.py | 31 + .../views/hr_payslip_views.xml | 6 + bemade_picking_upstream/__init__.py | 1 + bemade_picking_upstream/__manifest__.py | 32 + bemade_picking_upstream/migration18.md | 92 + bemade_picking_upstream/models/__init__.py | 1 + .../models/stock_picking.py | 33 + .../views/stock_picking_views.xml | 14 + .../__init__.py | 1 + .../__manifest__.py | 25 + .../migration18.md | 93 + .../models/__init__.py | 3 + .../models/purchase_order.py | 73 + .../models/res_company.py | 59 + .../models/res_config_settings.py | 37 + .../tests/__init__.py | 1 + .../tests/test_purchase_order_overdue.py | 144 ++ .../views/res_config_settings_views.xml | 75 + bemade_pwa_config/__init__.py | 2 + bemade_pwa_config/__manifest__.py | 27 + bemade_pwa_config/controllers/__init__.py | 1 + bemade_pwa_config/controllers/main.py | 42 + bemade_pwa_config/i18n/bemade_pwa_config.pot | 58 + bemade_pwa_config/i18n/fr_CA.po | 58 + bemade_pwa_config/migration18.md | 93 + bemade_pwa_config/models/__init__.py | 2 + bemade_pwa_config/models/res_company.py | 47 + .../models/res_config_settings.py | 31 + .../security/ir.model.access.csv | 0 .../views/res_config_settings_views.xml | 27 + bemade_quotation_alternative/__init__.py | 4 + bemade_quotation_alternative/__manifest__.py | 37 + bemade_quotation_alternative/migration18.md | 93 + .../models/__init__.py | 3 + .../models/sale_order.py | 19 + .../security/ir.model.access.csv | 3 + .../views/sale_order_views.xml | 62 + .../wizard/__init__.py | 4 + .../sale_oder_line_duplication_wizard.py | 9 + .../wizard/sale_order_duplication_wizard.py | 105 ++ .../sale_order_duplication_wizard_view.xml | 39 + bemade_reordering_rules_chatter/__init__.py | 5 + .../__manifest__.py | 22 + .../migration18.md | 93 + .../models/__init__.py | 1 + .../models/stock_warehouse_orderpoint.py | 19 + .../views/stock_warehouse_orderpoint.xml | 29 + bemade_sale_order_line_number/__manifest__.py | 21 + .../views/sale_order_views.xml | 27 + bemade_search_supplier_code/__init__.py | 5 + bemade_search_supplier_code/__manifest__.py | 24 + bemade_search_supplier_code/migration18.md | 31 + .../models/__init__.py | 1 + .../models/product_product.py | 26 + .../views/product_product_views.xml | 12 + bemade_so_and_po_only_company/__init__.py | 0 bemade_so_and_po_only_company/__manifest__.py | 24 + bemade_so_and_po_only_company/migration18.md | 32 + .../views/purchase_order.xml | 15 + .../views/sale_order.xml | 15 + bemade_so_followers_to_picking/__init__.py | 1 + .../__manifest__.py | 22 + bemade_so_followers_to_picking/migration18.md | 93 + .../models/__init__.py | 1 + .../models/stock_picking.py | 20 + bemade_sports_clinic/__init__.py | 2 + bemade_sports_clinic/__manifest__.py | 63 + bemade_sports_clinic/controllers/__init__.py | 1 + .../controllers/team_staff_portal.py | 110 ++ .../data/demo/sports_clinic_demo_data.xml | 189 ++ .../data/sports_clinic_data.xml | 87 + bemade_sports_clinic/i18n/fr_CA.po | 1200 ++++++++++++ .../migrations/16.0.1.5.4/post-migrate.py | 21 + .../migrations/16.0.1.5.7/post-migrate.py | 22 + .../migrations/16.0.1.5.8/post-migrate.py | 7 + .../migrations/16.0.1.6.0/post-migrate.py | 6 + .../migrations/16.0.1.7.0/post-migrate.py | 23 + .../migrations/16.0.1.7.0/pre-migrate.py | 10 + bemade_sports_clinic/models/__init__.py | 7 + bemade_sports_clinic/models/mail_followers.py | 46 + bemade_sports_clinic/models/patient.py | 332 ++++ .../models/patient_contact.py | 35 + bemade_sports_clinic/models/patient_injury.py | 175 ++ bemade_sports_clinic/models/res_partner.py | 57 + bemade_sports_clinic/models/res_users.py | 45 + bemade_sports_clinic/models/sports_team.py | 244 +++ .../security/ir.model.access.csv | 13 + .../security/sports_clinic_groups.xml | 31 + .../security/sports_clinic_rules.xml | 113 ++ .../static/description/icon.png | Bin 0 -> 30588 bytes bemade_sports_clinic/tests/__init__.py | 3 + bemade_sports_clinic/tests/test_patient.py | 222 +++ bemade_sports_clinic/tests/test_rights.py | 114 ++ bemade_sports_clinic/tests/test_users.py | 35 + .../views/res_partner_views.xml | 24 + .../views/res_users_views.xml | 16 + .../views/sports_clinic_menus.xml | 58 + .../views/sports_clinic_portal_views.xml | 220 +++ .../views/sports_patient_injury_views.xml | 69 + .../views/sports_patient_views.xml | 189 ++ .../views/sports_team_views.xml | 160 ++ bemade_stock_quant_valuation/__init__.py | 1 + bemade_stock_quant_valuation/__manifest__.py | 39 + bemade_stock_quant_valuation/migration18.md | 93 + .../models/__init__.py | 1 + .../models/stock_quant.py | 20 + .../views/stock_account_views.xml | 19 + bemade_time_off_follower/__init__.py | 1 + bemade_time_off_follower/__manifest__.py | 15 + bemade_time_off_follower/migration18.md | 93 + bemade_time_off_follower/models/__init__.py | 2 + bemade_time_off_follower/models/hr_leave.py | 11 + .../models/mail_thread.py | 93 + bemade_time_off_follower/tests/__init__.py | 1 + .../tests/test_mail_thread.py | 64 + .../views/hr_leave_views.xml | 13 + bemade_total_show_currency/__manifest__.py | 41 + .../views/total_template.xml | 14 + .../__init__.py | 1 + .../__manifest__.py | 22 + .../migration18.md | 31 + .../models/__init__.py | 1 + .../models/sale_order.py | 28 + bemade_user_custom_apps_order/__init__.py | 27 + bemade_user_custom_apps_order/__manifest__.py | 42 + .../models/__init__.py | 9 + .../models/ir_ui_menu.py | 84 + .../models/res_users.py | 63 + .../models/res_users_menu_order.py | 28 + .../security/ir.model.access.csv | 3 + .../tests/tours/custom_app_order_tour.js | 19 + .../tests/__init__.py | 1 + .../tests/test_user_custom_app_order.py | 36 + .../views/res_users_view.xml | 33 + .../views/user_menu_sequence.xml | 35 + bemade_user_password_bundle/__init__.py | 3 + bemade_user_password_bundle/__manifest__.py | 28 + bemade_user_password_bundle/migration18.md | 93 + .../models/__init__.py | 4 + .../models/hr_employee.py | 21 + .../models/password_bundle.py | 30 + bemade_utils/__init__.py | 2 + bemade_utils/__manifest__.py | 33 + bemade_utils/migration18.md | 31 + bemade_utils/tests/__init__.py | 2 + bemade_utils/tests/test_patching_test.py | 17 + bemade_utils/tools/__init__.py | 2 + bemade_utils/tools/test.py | 28 + .../__init__.py | 1 + .../__manifest__.py | 40 + .../models/__init__.py | 1 + .../models/mrp_production.py | 41 + .../models/mrp_workorder.py | 27 + .../static/src/mrp_display/mrp_display.esm.js | 18 + .../src/mrp_display/mrp_display_action.esm.js | 14 + .../src/mrp_display/mrp_display_record.esm.js | 9 + .../src/mrp_display/mrp_display_record.xml | 19 + .../tests/__init__.py | 1 + .../tests/test_mrp_production.py | 72 + .../views/mrp_production_views.xml | 23 + commercial_invoice/__init__.py | 4 + commercial_invoice/__manifest__.py | 32 + .../data/commercial_invoice_sequence.xml | 12 + commercial_invoice/models/__init__.py | 2 + commercial_invoice/models/account_move.py | 15 + .../models/commercial_invoice.py | 159 ++ commercial_invoice/report/__init__.py | 2 + .../report/commercial_invoice_report.xml | 13 + .../report/report_templates.xml | 257 +++ .../security/ir.model.access.csv | 3 + .../views/account_move_views.xml | 15 + .../views/commercial_invoice_views.xml | 128 ++ confirm_many2one_create/__init__.py | 0 confirm_many2one_create/__manifest__.py | 37 + .../static/src/js/many2one_field.esm.js | 12 + current_user_as_invoice_user/__init__.py | 1 + current_user_as_invoice_user/__manifest__.py | 30 + .../tests/__init__.py | 1 + .../test_current_user_as_invoice_user.py | 52 + .../wizard/__init__.py | 2 + .../wizard/sale_make_invoice_advance.py | 15 + .../wizard/sale_order.py | 13 + customer_applications/__init__.py | 1 + customer_applications/__manifest__.py | 40 + customer_applications/data/menus_actions.xml | 43 + customer_applications/models/__init__.py | 5 + customer_applications/models/application.py | 36 + .../models/application_specification.py | 42 + .../models/application_specification_key.py | 11 + .../models/application_type.py | 54 + customer_applications/models/res_partner.py | 42 + customer_applications/security/groups.xml | 14 + .../security/ir.model.access.csv | 7 + customer_applications/tests/__init__.py | 1 + .../tests/test_application.py | 59 + .../views/application_type_views.xml | 74 + .../views/application_views.xml | 86 + .../views/res_partner_views.xml | 45 + customer_itch_cycle/__init__.py | 2 + customer_itch_cycle/__manifest__.py | 15 + customer_itch_cycle/models/__init__.py | 3 + .../models/itch_cycle_product_partner.py | 33 + customer_itch_cycle/models/sale_order.py | 44 + .../views/itch_cycle_product_partner_view.xml | 44 + .../views/res_partner_view.xml | 23 + fsm_equipment/__init__.py | 1 + fsm_equipment/__manifest__.py | 38 + .../migrations/17.0.0.2.0/post-migrate.py | 30 + fsm_equipment/models/__init__.py | 4 + fsm_equipment/models/equipment.py | 144 ++ fsm_equipment/models/equipment_tag.py | 19 + fsm_equipment/models/res_partner.py | 51 + fsm_equipment/models/task.py | 14 + fsm_equipment/security/ir.model.access.csv | 3 + fsm_equipment/views/equipment_views.xml | 192 ++ fsm_equipment/views/project_task_views.xml | 14 + fsm_equipment/views/res_partner_views.xml | 57 + fsm_visit_confirmation/__init__.py | 2 + fsm_visit_confirmation/__manifest__.py | 33 + .../controllers/__init__.py | 1 + fsm_visit_confirmation/controllers/portal.py | 35 + .../data/mail_templates.xml | 47 + fsm_visit_confirmation/models/__init__.py | 1 + fsm_visit_confirmation/models/project_task.py | 9 + .../project_portal_project_task_templates.xml | 10 + impersonate_user/__init__.py | 2 + impersonate_user/__manifest__.py | 40 + impersonate_user/controllers/__init__.py | 1 + impersonate_user/controllers/main.py | 9 + impersonate_user/models/__init__.py | 2 + impersonate_user/models/ir_http.py | 11 + impersonate_user/models/res_users.py | 45 + .../src/systray_unimpersonate_button.esm.js | 27 + .../src/systray_unimpersonate_button.xml | 15 + .../views/impersonation_views.xml | 28 + improved_mo_origin/__init__.py | 1 + improved_mo_origin/__manifest__.py | 38 + improved_mo_origin/models/__init__.py | 1 + improved_mo_origin/models/mrp_production.py | 71 + .../static/src/mrp_display/mrp_display.esm.js | 25 + .../src/mrp_display/mrp_display_action.esm.js | 14 + .../src/mrp_display/mrp_display_record.esm.js | 9 + .../src/mrp_display/mrp_display_record.xml | 30 + improved_mo_origin/tests/__init__.py | 1 + .../tests/test_mrp_production.py | 118 ++ .../views/mrp_production_views.xml | 25 + interactive_discuss_ai/__init__.py | 1 + interactive_discuss_ai/__manifest__.py | 46 + interactive_discuss_ai/models/__init__.py | 2 + interactive_discuss_ai/models/ai_assistant.py | 101 + interactive_discuss_ai/models/ai_config.py | 61 + interactive_discuss_ai/models/ai_service.py | 62 + .../security/ir.model.access.csv | 3 + .../static/src/js/ai_assistant_chat.js | 52 + .../static/src/xml/ai_assistant_chat.xml | 12 + .../views/ai_assistant_views.xml | 86 + .../views/ai_config_views.xml | 83 + l10n_ca_pst_reports/__init__.py | 0 l10n_ca_pst_reports/__manifest__.py | 33 + l10n_ca_pst_reports/tests/__init__.py | 1 + .../tests/test_report_tax_numbers.py | 65 + l10n_ca_pst_reports/views/external_layout.xml | 23 + mo_back_to_draft/__init__.py | 1 + mo_back_to_draft/__manifest__.py | 33 + mo_back_to_draft/models/__init__.py | 1 + mo_back_to_draft/models/mrp_production.py | 16 + .../views/mrp_production_views.xml | 16 + msg_attachments_to_mail_message/__init__.py | 1 + .../__manifest__.py | 63 + .../migrations/16.0.1.1/__init__.py | 1 + .../migrations/16.0.1.1/pre-migration.py | 36 + .../migrations/__init__.py | 1 + .../models/__init__.py | 3 + .../models/ir_attachment.py | 842 +++++++++ .../models/mail_thread.py | 23 + .../models/res_config_settings.py | 39 + .../requirements.txt | 3 + .../views/res_config_settings_views.xml | 47 + msg_viewer/__init__.py | 2 + msg_viewer/__manifest__.py | 40 + msg_viewer/controllers/__init__.py | 1 + msg_viewer/controllers/main.py | 88 + msg_viewer/models/__init__.py | 1 + msg_viewer/models/ir_attachment.py | 26 + .../attachment_card_msg.xml | 16 + .../src/components/msg_viewer/msg_viewer.js | 72 + .../src/components/msg_viewer/msg_viewer.xml | 16 + msg_viewer/static/src/css/msg_viewer.css | 48 + msg_viewer/static/src/js/msg_viewer.js | 49 + .../src/lib/components/attachment/index.html | 12 + .../src/lib/components/attachment/index.ts | 30 + .../src/lib/components/attachment/styles.css | 3 + .../lib/components/embedded-msg/index.html | 9 + .../src/lib/components/embedded-msg/index.ts | 26 + .../lib/components/embedded-msg/styles.css | 3 + .../src/lib/components/error/index.html | 1 + .../static/src/lib/components/error/index.ts | 6 + .../src/lib/components/error/styles.css | 4 + .../src/lib/components/message/index.html | 33 + .../src/lib/components/message/index.ts | 79 + .../src/lib/components/message/styles.css | 50 + .../src/lib/components/recipient/index.html | 1 + .../src/lib/components/recipient/index.ts | 48 + .../src/lib/components/recipient/styles.css | 0 .../static/src/lib/components/styles.css | 5 + msg_viewer/static/src/lib/favicon.ico | Bin 0 -> 4158 bytes msg_viewer/static/src/lib/index.html | 50 + .../static/src/lib/msg-viewer-main/.gitignore | 184 ++ .../static/src/lib/msg-viewer-main/LICENSE | 21 + .../static/src/lib/msg-viewer-main/README.md | 36 + .../static/src/lib/msg-viewer-main/build.ts | 75 + .../static/src/lib/msg-viewer-main/bun.lockb | Bin 0 -> 24712 bytes .../src/lib/msg-viewer-main/declarations.d.ts | 8 + .../static/src/lib/msg-viewer-main/icon.svg | 12 + .../src/lib/msg-viewer-main/jsconfig.json | 27 + .../lib/components/attachment/index.html | 12 + .../lib/components/attachment/index.ts | 30 + .../lib/components/attachment/styles.css | 3 + .../lib/components/embedded-msg/index.html | 9 + .../lib/components/embedded-msg/index.ts | 26 + .../lib/components/embedded-msg/styles.css | 3 + .../lib/components/error/index.html | 1 + .../lib/components/error/index.ts | 6 + .../lib/components/error/styles.css | 4 + .../lib/components/message/index.html | 33 + .../lib/components/message/index.ts | 79 + .../lib/components/message/styles.css | 50 + .../lib/components/recipient/index.html | 1 + .../lib/components/recipient/index.ts | 48 + .../lib/components/recipient/styles.css | 0 .../msg-viewer-main/lib/components/styles.css | 5 + .../src/lib/msg-viewer-main/lib/favicon.ico | Bin 0 -> 4158 bytes .../src/lib/msg-viewer-main/lib/index.html | 50 + .../lib/resources/google331b65fa04565532.html | 1 + .../msg-viewer-main/lib/resources/robots.txt | 2 + .../lib/msg-viewer-main/lib/scripts/index.ts | 63 + .../msg-viewer-main/lib/scripts/msg/README.md | 37 + .../msg/compound-file/compound-file.ts | 84 + .../compound-file/constants/text-decoder.ts | 1 + .../lib/scripts/msg/compound-file/difat.ts | 34 + .../msg/compound-file/directory/directory.ts | 157 ++ .../directory/enums/color-flag.ts | 4 + .../directory/enums/object-type.ts | 6 + .../directory/types/directory-entry.d.ts | 132 ++ .../lib/scripts/msg/compound-file/fat.ts | 24 + .../lib/scripts/msg/compound-file/header.ts | 125 ++ .../lib/scripts/msg/compound-file/mini-fat.ts | 26 + .../lib/scripts/msg/compound-file/util.ts | 31 + .../lib/scripts/msg/msg-parser.ts | 110 ++ .../lib/scripts/msg/package.json | 8 + .../lib/scripts/msg/rtf/decompressor/crc.ts | 19 + .../msg/rtf/decompressor/dictionary.ts | 14 + .../scripts/msg/rtf/decompressor/header.ts | 49 + .../lib/scripts/msg/rtf/rtf-decompressor.ts | 69 + .../scripts/msg/streams/constants/folders.ts | 1 + .../scripts/msg/streams/entry/entry-stream.ts | 45 + .../streams/entry/types/entry-stream-entry.ts | 26 + .../msg/streams/property/properties.ts | 41 + .../msg/streams/property/property-stream.ts | 88 + .../msg/streams/property/property-types.ts | 83 + .../streams/property/types/property-data.d.ts | 49 + .../property/types/property-header.d.ts | 26 + .../property/types/property-stream-entry.d.ts | 7 + .../lib/scripts/msg/types/message.d.ts | 34 + .../lib/scripts/utils/file-size-util.ts | 12 + .../lib/scripts/utils/html-template-util.ts | 20 + .../lib/msg-viewer-main/lib/styles/root.css | 131 ++ .../lib/msg-viewer-main/lib/styles/styles.css | 2 + .../src/lib/msg-viewer-main/package-lock.json | 761 ++++++++ .../src/lib/msg-viewer-main/package.json | 20 + msg_viewer/static/src/lib/msg-viewer.js | 36 + msg_viewer/static/src/lib/msg-viewer.zip | Bin 0 -> 57332 bytes .../lib/resources/google331b65fa04565532.html | 1 + .../static/src/lib/resources/robots.txt | 2 + msg_viewer/static/src/lib/scripts/index.ts | 63 + .../static/src/lib/scripts/msg/README.md | 37 + .../msg/compound-file/compound-file.ts | 84 + .../compound-file/constants/text-decoder.ts | 1 + .../lib/scripts/msg/compound-file/difat.ts | 34 + .../msg/compound-file/directory/directory.ts | 157 ++ .../directory/enums/color-flag.ts | 4 + .../directory/enums/object-type.ts | 6 + .../directory/types/directory-entry.d.ts | 132 ++ .../src/lib/scripts/msg/compound-file/fat.ts | 24 + .../lib/scripts/msg/compound-file/header.ts | 125 ++ .../lib/scripts/msg/compound-file/mini-fat.ts | 26 + .../src/lib/scripts/msg/compound-file/util.ts | 31 + .../static/src/lib/scripts/msg/msg-parser.ts | 110 ++ .../static/src/lib/scripts/msg/package.json | 8 + .../lib/scripts/msg/rtf/decompressor/crc.ts | 19 + .../msg/rtf/decompressor/dictionary.ts | 14 + .../scripts/msg/rtf/decompressor/header.ts | 49 + .../lib/scripts/msg/rtf/rtf-decompressor.ts | 69 + .../scripts/msg/streams/constants/folders.ts | 1 + .../scripts/msg/streams/entry/entry-stream.ts | 45 + .../streams/entry/types/entry-stream-entry.ts | 26 + .../msg/streams/property/properties.ts | 41 + .../msg/streams/property/property-stream.ts | 88 + .../msg/streams/property/property-types.ts | 83 + .../streams/property/types/property-data.d.ts | 49 + .../property/types/property-header.d.ts | 26 + .../property/types/property-stream-entry.d.ts | 7 + .../src/lib/scripts/msg/types/message.d.ts | 34 + .../src/lib/scripts/utils/file-size-util.ts | 12 + .../lib/scripts/utils/html-template-util.ts | 20 + msg_viewer/static/src/lib/styles/root.css | 131 ++ msg_viewer/static/src/lib/styles/styles.css | 2 + .../static/src/models/attachment_card_msg.js | 50 + msg_viewer/test/FACTURE NO. 7005451.msg | Bin 0 -> 257024 bytes msg_viewer/test/analyze_msg.py | 68 + msg_viewer/views/msg_viewer_views.xml | 31 + multi_account_statement_import/__init__.py | 0 .../__manifest__.py | 32 + odoo_proxmox_manager/README.md | 90 + odoo_proxmox_manager/__init__.py | 3 + odoo_proxmox_manager/__manifest__.py | 35 + odoo_proxmox_manager/controllers/__init__.py | 1 + odoo_proxmox_manager/controllers/main.py | 9 + odoo_proxmox_manager/models/__init__.py | 3 + .../models/proxmox_cluster.py | 37 + odoo_proxmox_manager/models/proxmox_server.py | 164 ++ odoo_proxmox_manager/models/proxmox_vm.py | 87 + odoo_proxmox_manager/requirements.txt | 2 + .../security/ir.model.access.csv | 9 + .../security/proxmox_security.xml | 39 + .../static/description/icon.png | Bin 0 -> 833 bytes .../proxmox_server_list.js | 32 + .../proxmox_server_list.xml | 10 + .../static/src/js/dashboard_view.js | 51 + .../static/src/js/proxmox_server_list.js | 52 + .../static/src/scss/dashboard.scss | 113 ++ .../static/src/xml/dashboard.xml | 57 + .../static/src/xml/proxmox_server_list.xml | 10 + .../views/proxmox_cluster_views.xml | 89 + odoo_proxmox_manager/views/proxmox_menus.xml | 32 + .../views/proxmox_server_views.xml | 108 ++ .../views/proxmox_vm_views.xml | 107 ++ odoo_proxmox_manager/wizard/__init__.py | 1 + .../wizard/proxmox_server_wizard.py | 98 + .../wizard/proxmox_server_wizard_views.xml | 67 + odoo_rsync_backup/__init__.py | 1 + odoo_rsync_backup/__manifest__.py | 20 + odoo_rsync_backup/data/backup_cron.xml | 14 + odoo_rsync_backup/models/__init__.py | 1 + odoo_rsync_backup/models/rsync_config.py | 185 ++ .../security/ir.model.access.csv | 2 + .../views/rsync_config_views.xml | 33 + odoo_to_odoo_bemade/__init__.py | 1 + odoo_to_odoo_bemade/__manifest__.py | 32 + odoo_to_odoo_bemade/data/ir_cron_data.xml | 56 + odoo_to_odoo_bemade/models/__init__.py | 6 + odoo_to_odoo_bemade/models/sync_instance.py | 517 ++++++ odoo_to_odoo_bemade/models/sync_log.py | 62 + odoo_to_odoo_bemade/models/sync_manager.py | 393 ++++ odoo_to_odoo_bemade/models/sync_model.py | 108 ++ .../models/sync_model_field.py | 121 ++ odoo_to_odoo_bemade/models/sync_queue.py | 68 + .../security/ir.model.access.csv | 7 + .../static/description/icon.png | Bin 0 -> 13794 bytes .../static/description/icon.svg | 30 + .../static/description/index.html | 93 + odoo_to_odoo_bemade/views/menus.xml | 33 + .../views/sync_instance_views.xml | 126 ++ odoo_to_odoo_bemade/views/sync_log_views.xml | 98 + .../views/sync_model_views.xml | 129 ++ .../views/sync_queue_views.xml | 122 ++ odoo_to_odoo_bemade_customer/__init__.py | 1 + odoo_to_odoo_bemade_customer/__manifest__.py | 31 + .../data/ir_cron_data.xml | 56 + .../models/__init__.py | 6 + .../models/sync_config.py | 202 ++ .../models/sync_log.py | 131 ++ .../models/sync_manager.py | 281 +++ .../models/sync_model.py | 176 ++ .../models/sync_model_field.py | 117 ++ .../models/sync_queue.py | 116 ++ .../security/ir.model.access.csv | 13 + .../static/description/icon.png | Bin 0 -> 13794 bytes .../static/description/icon.svg | 30 + .../static/description/index.html | 126 ++ odoo_to_odoo_bemade_customer/views/menus.xml | 33 + .../views/sync_instance_views.xml | 129 ++ .../views/sync_log_views.xml | 98 + .../views/sync_model_views.xml | 129 ++ .../views/sync_queue_views.xml | 122 ++ odoo_to_odoo_sync/Spécifications.md | 676 +++++++ odoo_to_odoo_sync/__init__.py | 1 + odoo_to_odoo_sync/__manifest__.py | 28 + odoo_to_odoo_sync/data/ir_cron_data.xml | 15 + odoo_to_odoo_sync/models/__init__.py | 6 + odoo_to_odoo_sync/models/sync_instance.py | 279 +++ odoo_to_odoo_sync/models/sync_log.py | 49 + odoo_to_odoo_sync/models/sync_manager.py | 204 +++ odoo_to_odoo_sync/models/sync_model.py | 94 + odoo_to_odoo_sync/models/sync_model_field.py | 41 + odoo_to_odoo_sync/models/sync_queue.py | 204 +++ .../security/ir.model.access.csv | 13 + odoo_to_odoo_sync/static/description/icon.png | Bin 0 -> 13794 bytes odoo_to_odoo_sync/static/description/icon.svg | 30 + odoo_to_odoo_sync/views/menus.xml | 45 + .../views/sync_instance_views.xml | 68 + odoo_to_odoo_sync/views/sync_log_views.xml | 51 + odoo_to_odoo_sync/views/sync_model_views.xml | 69 + odoo_to_odoo_sync/views/sync_queue_views.xml | 130 ++ partner_equipment_applications/__init__.py | 1 + .../__manifest__.py | 36 + .../models/__init__.py | 2 + .../models/application.py | 29 + .../models/equipment.py | 17 + .../views/application_views.xml | 39 + .../views/equipment_views.xml | 42 + picking_policy_per_customer/__init__.py | 1 + picking_policy_per_customer/__manifest__.py | 23 + .../models/__init__.py | 2 + .../models/res_partner.py | 19 + .../models/sale_order.py | 28 + .../views/res_partner_views.xml | 13 + portal_hide_draft_order_details/__init__.py | 2 + .../__manifest__.py | 34 + portal_hide_draft_order_details/i18n/fr.po | 56 + .../i18n/portal_hide_draft_order_details.pot | 52 + .../models/__init__.py | 1 + .../models/res_company.py | 12 + .../views/sale_order_portal_templates.xml | 50 + .../wizard/__init__.py | 1 + .../wizard/res_config_settings.py | 17 + .../wizard/res_config_settings.xml | 15 + price_update_notifications/__init__.py | 2 + price_update_notifications/__manifest__.py | 39 + .../data/email_templates.xml | 61 + .../data/ir_cron_data.xml | 18 + price_update_notifications/i18n/fr.po | 309 ++++ .../i18n/price_update_notifications.pot | 374 ++++ price_update_notifications/models/__init__.py | 1 + .../models/price_notice_mailing.py | 132 ++ .../security/ir.model.access.csv | 7 + .../views/price_notice_mailing_views.xml | 97 + price_update_notifications/wizard/__init__.py | 1 + .../wizard/send_price_update_notice.py | 83 + .../wizard/send_price_update_notice_views.xml | 36 + purchase_customer_requisition/__init__.py | 1 + purchase_customer_requisition/__manifest__.py | 38 + .../models/__init__.py | 3 + .../models/purchase_order.py | 21 + .../models/purchase_order_line.py | 223 +++ .../models/purchase_requisition.py | 12 + .../tests/__init__.py | 1 + .../tests/test_purchase_order.py | 290 +++ .../views/purchase_requisition_views.xml | 13 + .../views/purchase_views.xml | 16 + qc_partner_default_lang/__init__.py | 1 + qc_partner_default_lang/__manifest__.py | 14 + qc_partner_default_lang/models/__init__.py | 1 + qc_partner_default_lang/models/res_partner.py | 34 + .../static/description/index.html | 27 + recursive_tree_view/README.md | 42 + recursive_tree_view/__init__.py | 2 + recursive_tree_view/__manifest__.py | 41 + recursive_tree_view/hierarchy-svgrepo-com.svg | 15 + recursive_tree_view/models/__init__.py | 1 + recursive_tree_view/models/models.py | 18 + recursive_tree_view/rng/tree_view.rng | 104 ++ .../static/description/icon.png | Bin 0 -> 1984 bytes .../static/description/images/collapsed.png | Bin 0 -> 184474 bytes .../static/description/images/expanded.png | Bin 0 -> 199669 bytes .../description/images/main_screenshot.png | Bin 0 -> 45334 bytes .../static/description/index.html | 97 + .../static/src/list_arch_parser.js | 16 + .../static/src/list_controller.js | 37 + .../static/src/list_renderer.js | 19 + .../static/src/recursive_tree_templates.xml | 40 + .../static/src/relational_model.js | 144 ++ .../static/src/tree_recursive_styles.css | 28 + recursive_tree_view/validation.py | 59 + replace_invoicing_user/__init__.py | 2 + replace_invoicing_user/__manifest__.py | 36 + replace_invoicing_user/models/__init__.py | 2 + replace_invoicing_user/models/account_move.py | 193 ++ .../models/res_config_settings.py | 52 + replace_invoicing_user/tests/__init__.py | 1 + .../test_current_user_as_invoice_user.py | 183 ++ .../views/res_config_settings_views.xml | 34 + replace_invoicing_user/wizard/__init__.py | 2 + .../wizard/sale_make_invoice_advance.py | 62 + replace_invoicing_user/wizard/sale_order.py | 46 + stock_valuation_location/__init__.py | 1 + stock_valuation_location/__manifest__.py | 33 + stock_valuation_location/models/__init__.py | 1 + .../models/stock_valuation_layer.py | 25 + .../views/stock_valuation_layer_views.xml | 29 + temporary_change_image_size/__manifest__.py | 20 + .../views/product_template_views.xml | 13 + theme_durpro/LICENSE | 27 + theme_durpro/__init__.py | 3 + theme_durpro/__manifest__.py | 34 + theme_durpro/data/ir_asset.xml | 43 + theme_durpro/i18n/de.po | 206 +++ theme_durpro/i18n/es.po | 207 +++ theme_durpro/i18n/fr.po | 206 +++ theme_durpro/i18n/it.po | 207 +++ theme_durpro/i18n/nl.po | 206 +++ theme_durpro/i18n/theme_graphene.pot | 202 ++ theme_durpro/i18n/zh_CN.po | 206 +++ theme_durpro/models/__init__.py | 3 + theme_durpro/models/theme_durpro.py | 14 + .../static/description/durpro_poster.png | Bin 0 -> 32744 bytes .../static/description/durpro_screenshot.jpg | Bin 0 -> 84111 bytes theme_durpro/static/description/index.html | 2 + .../static/description/theme_durpro.svg | 236 +++ .../static/src/img/patterns/bg_pattern_01.jpg | Bin 0 -> 49367 bytes .../static/src/img/patterns/bg_pattern_02.jpg | Bin 0 -> 38553 bytes .../static/src/img/patterns/bg_pattern_03.jpg | Bin 0 -> 35626 bytes .../static/src/img/patterns/bg_pattern_04.jpg | Bin 0 -> 79959 bytes .../static/src/img/patterns/bg_pattern_05.jpg | Bin 0 -> 45897 bytes .../static/src/img/patterns/bg_pattern_06.jpg | Bin 0 -> 52671 bytes .../static/src/img/pictures/bg_image_01.jpg | Bin 0 -> 82852 bytes .../static/src/img/pictures/bg_image_02.jpg | Bin 0 -> 81771 bytes .../static/src/img/pictures/bg_image_03.jpg | Bin 0 -> 84532 bytes .../static/src/img/pictures/bg_image_04.jpg | Bin 0 -> 69313 bytes .../static/src/img/pictures/bg_image_05.jpg | Bin 0 -> 132385 bytes .../static/src/img/pictures/bg_image_06.jpg | Bin 0 -> 64071 bytes .../static/src/img/pictures/bg_image_07.jpg | Bin 0 -> 80264 bytes .../static/src/img/pictures/bg_image_08.jpg | Bin 0 -> 203311 bytes .../static/src/img/pictures/bg_image_09.jpg | Bin 0 -> 100158 bytes .../static/src/img/pictures/bg_image_10.jpg | Bin 0 -> 73488 bytes .../static/src/img/pictures/content_01.jpg | Bin 0 -> 3726046 bytes .../static/src/img/pictures/content_02.jpg | Bin 0 -> 94594 bytes .../static/src/img/pictures/content_03.jpg | Bin 0 -> 77542 bytes .../static/src/img/pictures/content_04.jpg | Bin 0 -> 99379 bytes .../static/src/img/pictures/content_05.jpg | Bin 0 -> 69993 bytes .../static/src/img/pictures/content_06.jpg | Bin 0 -> 85457 bytes .../static/src/img/pictures/content_07.jpg | Bin 0 -> 69259 bytes .../static/src/img/pictures/content_08.jpg | Bin 0 -> 93051 bytes .../static/src/img/pictures/content_09.jpg | Bin 0 -> 64562 bytes .../static/src/img/pictures/content_10.jpg | Bin 0 -> 200134 bytes .../static/src/img/pictures/content_11.jpg | Bin 0 -> 92251 bytes .../static/src/img/pictures/content_12.jpg | Bin 0 -> 130164 bytes .../static/src/img/pictures/content_13.jpg | Bin 0 -> 111532 bytes .../static/src/img/pictures/content_14.jpg | Bin 0 -> 119900 bytes .../static/src/img/pictures/content_15.jpg | Bin 0 -> 30465 bytes .../static/src/img/pictures/content_16.jpg | Bin 0 -> 28064 bytes .../static/src/img/pictures/content_17.jpg | Bin 0 -> 30993 bytes .../static/src/img/pictures/content_18.jpg | Bin 0 -> 43790 bytes .../static/src/img/pictures/content_19.jpg | Bin 0 -> 27276 bytes .../static/src/img/pictures/content_20.jpg | Bin 0 -> 23539 bytes .../static/src/img/pictures/content_21.jpg | Bin 0 -> 26760 bytes .../static/src/img/pictures/content_22.jpg | Bin 0 -> 84090 bytes .../static/src/img/pictures/content_23.jpg | Bin 0 -> 298113 bytes .../static/src/img/pictures/s_numbers.jpg | Bin 0 -> 1689792 bytes .../static/src/img/pictures/s_popup.jpg | Bin 0 -> 73698 bytes .../static/src/img/pictures/uiface_01.jpg | Bin 0 -> 4150 bytes .../static/src/img/pictures/uiface_02.jpg | Bin 0 -> 6969 bytes .../static/src/img/pictures/uiface_03.jpg | Bin 0 -> 9117 bytes .../static/src/img/pictures/uiface_04.jpg | Bin 0 -> 11952 bytes .../static/src/img/pictures/uiface_05.jpg | Bin 0 -> 26256 bytes .../static/src/img/pictures/uiface_06.jpg | Bin 0 -> 5422 bytes .../static/src/img/pictures/uiface_07.jpg | Bin 0 -> 5474 bytes theme_durpro/static/src/js/tour.js | 49 + .../s_showcase_slider/000_variables.scss | 21 + .../static/src/scss/bootstrap_overridden.scss | 3 + .../static/src/scss/custom_styles.scss | 5 + .../static/src/scss/primary_variables.scss | 151 ++ .../static/src/scss/secondary_variables.scss | 9 + theme_durpro/views/customizations.xml | 295 +++ theme_durpro/views/images_library.xml | 333 ++++ wazo_integration/__init__.py | 1 + wazo_integration/__manifest__.py | 19 + wazo_integration/models/__init__.py | 2 + wazo_integration/models/res_company.py | 21 + .../models/res_config_settings.py | 57 + .../views/res_config_settings_views.xml | 62 + website_geoip_extended/__init__.py | 3 + website_geoip_extended/__manifest__.py | 41 + website_geoip_extended/models/__init__.py | 3 + .../models/website_visitor.py | 58 + .../views/website_visitor_views.xml | 40 + 977 files changed, 49159 insertions(+), 106 deletions(-) create mode 100644 account_credit_hold/__init__.py create mode 100644 account_credit_hold/__manifest__.py create mode 100644 account_credit_hold/migration18.md create mode 100644 account_credit_hold/models/__init__.py create mode 100644 account_credit_hold/models/account_followup.py create mode 100644 account_credit_hold/models/account_followup_report.py create mode 100644 account_credit_hold/models/res_partner.py create mode 100644 account_credit_hold/models/sale_order.py create mode 100644 account_credit_hold/models/stock_picking.py create mode 100644 account_credit_hold/readme.md create mode 100644 account_credit_hold/views/account_followup_views.xml create mode 100644 account_credit_hold/views/res_partner_views.xml create mode 100644 account_credit_hold/views/sale_order_views.xml create mode 100644 account_credit_hold/views/stock_picking_views.xml create mode 100644 aged_partner_balance_na/__init__.py create mode 100644 aged_partner_balance_na/__manifest__.py create mode 100644 aged_partner_balance_na/models/__init__.py create mode 100644 aged_partner_balance_na/models/aged_partner_balance.py create mode 100644 ai_integration/__init__.py create mode 100644 ai_integration/__manifest__.py create mode 100644 ai_integration/models/__init__.py create mode 100644 ai_integration/models/ai_generation_params.py create mode 100644 ai_integration/models/ai_model.py create mode 100644 ai_integration/models/ai_model_stats.py create mode 100644 ai_integration/models/ai_provider.py create mode 100644 ai_integration/models/ai_provider_instance.py create mode 100644 ai_integration/models/ai_provider_interface.py create mode 100644 ai_integration/models/doc_models.md create mode 100644 ai_integration/models/mixins/__init__.py create mode 100644 ai_integration/models/mixins/ai_base_mixin.py create mode 100644 ai_integration/models/res_company.py create mode 100644 ai_integration/models/res_config_settings.py create mode 100644 ai_integration/security/ai_security.xml create mode 100644 ai_integration/security/ir.model.access.csv create mode 100644 ai_integration/security/ir_rule.xml create mode 100644 ai_integration/views/ai_model_stats_views.xml create mode 100644 ai_integration/views/ai_model_views.xml create mode 100644 ai_integration/views/ai_provider_instance_views.xml create mode 100644 ai_integration/views/ai_provider_views.xml create mode 100644 ai_integration/views/menu.xml create mode 100644 ai_integration/views/res_company_views.xml create mode 100644 ai_integration/views/res_config_settings_views.xml create mode 100644 ai_integration_ollama_api/__init__.py create mode 100644 ai_integration_ollama_api/__manifest__.py create mode 100644 ai_integration_ollama_api/data/ai_provider_data.xml create mode 100644 ai_integration_ollama_api/data/ai_provider_instance_data.xml create mode 100644 ai_integration_ollama_api/data/ir_model_inherit.xml create mode 100644 ai_integration_ollama_api/data/ollama_provider.xml create mode 100644 ai_integration_ollama_api/doc/ollama_api.md create mode 100644 ai_integration_ollama_api/migrations/1.0.0/post-migration.py create mode 100644 ai_integration_ollama_api/models/__init__.py create mode 100644 ai_integration_ollama_api/models/ai_provider_instance.py create mode 100644 ai_integration_ollama_api/models/ai_provider_ollama.py.bak create mode 100644 ai_integration_ollama_api/models/ollama_model_stats.py create mode 100644 ai_integration_ollama_api/models/ollama_provider.py create mode 100644 ai_integration_ollama_api/models/ollama_provider_mixin.py create mode 100644 ai_integration_ollama_api/security/ir.model.access.csv create mode 100644 ai_integration_ollama_api/views/ai_provider_instance_views.xml create mode 100644 ai_integration_ollama_api/views/ollama_stats_views.xml create mode 100644 ai_integration_ollama_api/views/ollama_views.xml create mode 100644 ai_integration_openai_api/__init__.py create mode 100644 ai_integration_openai_api/__manifest__.py create mode 100644 ai_integration_openai_api/data/chatgpt_provider.xml create mode 100644 ai_integration_openai_api/models/__init__.py create mode 100644 ai_integration_openai_api/models/chatgpt_instance.py create mode 100644 ai_integration_openai_api/models/chatgpt_provider.py create mode 100644 ai_integration_openai_api/security/ir.model.access.csv create mode 100644 ai_integration_openai_api/views/chatgpt_instance_views.xml create mode 100644 bemade_add_follower_no_sendmail_default/__init__.py create mode 100644 bemade_add_follower_no_sendmail_default/__manifest__.py create mode 100644 bemade_add_follower_no_sendmail_default/migration18.md create mode 100644 bemade_add_follower_no_sendmail_default/models/__init__.py create mode 100644 bemade_add_follower_no_sendmail_default/models/mail_wizard_invite.py create mode 100644 bemade_attachments_cleanup/__init__.py create mode 100644 bemade_attachments_cleanup/__manifest__.py create mode 100644 bemade_attachments_cleanup/i18n/fr_CA.po create mode 100644 bemade_attachments_cleanup/migration18.md create mode 100644 bemade_attachments_cleanup/security/ir.model.access.csv create mode 100644 bemade_attachments_cleanup/wizard/__init__.py create mode 100644 bemade_attachments_cleanup/wizard/attachment_cleanup_wizard.py create mode 100644 bemade_attachments_cleanup/wizard/attachment_cleanup_wizard_view.xml create mode 100644 bemade_documents_portal/__init__.py create mode 100644 bemade_documents_portal/__manifest__.py create mode 100644 bemade_documents_portal/controllers/__init__.py create mode 100644 bemade_documents_portal/controllers/portal.py create mode 100644 bemade_documents_portal/models/__init__.py create mode 100644 bemade_documents_portal/models/documents.py create mode 100644 bemade_documents_portal/views/document_portal_templates.xml create mode 100644 bemade_fetchmail_only_production/README.md create mode 100644 bemade_fetchmail_only_production/__init__.py create mode 100644 bemade_fetchmail_only_production/__manifest__.py create mode 100644 bemade_fetchmail_only_production/migration18.md create mode 100644 bemade_fetchmail_only_production/models/__init__.py create mode 100644 bemade_fetchmail_only_production/models/fetchmail_server.py create mode 100644 bemade_fetchmail_only_production/readme/HISTORY.rst create mode 100644 bemade_fix_quality_report/__init__.py create mode 100644 bemade_fix_quality_report/__manifest__.py create mode 100644 bemade_fix_quality_report/reports/worksheet_custom_report_templates.xml create mode 100644 bemade_fsm/__init__.py create mode 100644 bemade_fsm/__manifest__.py create mode 100644 bemade_fsm/data/fsm_data.xml create mode 100644 bemade_fsm/i18n/fr.po create mode 100644 bemade_fsm/migration18.md create mode 100644 bemade_fsm/migrations/17.0.0.4.0/pre-migrate.py create mode 100644 bemade_fsm/models/__init__.py create mode 100644 bemade_fsm/models/fsm_visit.py create mode 100644 bemade_fsm/models/product_template.py create mode 100644 bemade_fsm/models/project.py create mode 100644 bemade_fsm/models/res_company.py create mode 100644 bemade_fsm/models/res_partner.py create mode 100644 bemade_fsm/models/sale_order.py create mode 100644 bemade_fsm/models/sale_order_line.py create mode 100644 bemade_fsm/models/task.py create mode 100644 bemade_fsm/models/task_template.py create mode 100644 bemade_fsm/reports/__init__.py create mode 100644 bemade_fsm/reports/worksheet_custom_report_templates.xml create mode 100644 bemade_fsm/reports/worksheet_custom_reports.py create mode 100644 bemade_fsm/reports/worksheet_custom_reports.xml create mode 100644 bemade_fsm/security/ir.model.access.csv create mode 100644 bemade_fsm/static/src/js/kanban_view.js create mode 100644 bemade_fsm/static/src/js/list_view.js create mode 100644 bemade_fsm/static/src/scss/bemade_fsm.scss create mode 100644 bemade_fsm/static/src/xml/project_view_buttons.xml create mode 100644 bemade_fsm/tests/__init__.py create mode 100644 bemade_fsm/tests/test_bemade_fsm_common.py create mode 100644 bemade_fsm/tests/test_equipment.py create mode 100644 bemade_fsm/tests/test_fsm_contact_setting.py create mode 100644 bemade_fsm/tests/test_fsm_visit.py create mode 100644 bemade_fsm/tests/test_sale_order.py create mode 100644 bemade_fsm/tests/test_settings.py create mode 100644 bemade_fsm/tests/test_task.py create mode 100644 bemade_fsm/tests/test_task_report.py create mode 100644 bemade_fsm/tests/test_task_template.py create mode 100644 bemade_fsm/views/menus.xml create mode 100644 bemade_fsm/views/product_views.xml create mode 100644 bemade_fsm/views/res_partner.xml create mode 100644 bemade_fsm/views/sale_order_views.xml create mode 100644 bemade_fsm/views/task_template_views.xml create mode 100644 bemade_fsm/views/task_views.xml create mode 100644 bemade_fsm/wizard/__init__.py create mode 100644 bemade_fsm/wizard/new_task_from_template.py create mode 100644 bemade_fsm/wizard/new_task_from_template.xml create mode 100644 bemade_fsm/wizard/res_config_settings.py create mode 100644 bemade_fsm/wizard/res_config_settings.xml create mode 100644 bemade_full_formview_from_modal/__init__.py create mode 100644 bemade_full_formview_from_modal/__manifest__.py create mode 100644 bemade_full_formview_from_modal/migration18.md create mode 100644 bemade_full_formview_from_modal/static/src/view_dialogs/form_view_dialog.js create mode 100644 bemade_full_formview_from_modal/static/src/view_dialogs/form_view_dialog.xml create mode 100644 bemade_full_formview_from_modal/static/tests/test_full_formview_from_modal.js create mode 100644 bemade_full_formview_from_modal/tests/__init__.py create mode 100644 bemade_full_formview_from_modal/tests/test_full_formview_from_modal.py create mode 100644 bemade_git_repos_addons/__init__.py create mode 100644 bemade_git_repos_addons/__manifest__.py create mode 100644 bemade_git_repos_addons/data/default_directories_data.xml create mode 100644 bemade_git_repos_addons/models/__init__.py create mode 100644 bemade_git_repos_addons/models/git_addons.py create mode 100644 bemade_git_repos_addons/models/git_branch.py create mode 100644 bemade_git_repos_addons/models/git_repos.py create mode 100644 bemade_git_repos_addons/models/res_settings.py create mode 100644 bemade_git_repos_addons/security/ir.model.access.csv create mode 100644 bemade_git_repos_addons/static/src/views/button_create_repos_list/button_create_repos.xml create mode 100644 bemade_git_repos_addons/static/src/views/button_create_repos_list/button_create_repos_controller.js create mode 100644 bemade_git_repos_addons/static/src/views/button_create_repos_list/button_create_repos_view.js create mode 100644 bemade_git_repos_addons/views/action_and_menu.xml create mode 100644 bemade_git_repos_addons/views/git_addons_views.xml create mode 100644 bemade_git_repos_addons/views/git_repos_views.xml create mode 100644 bemade_git_repos_addons/views/res_settings_views.xml create mode 100644 bemade_git_repos_addons/wizard/__init__.py create mode 100644 bemade_git_repos_addons/wizard/directory_wizard.py create mode 100644 bemade_git_repos_addons/wizard/directory_wizard_views.xml create mode 100644 bemade_git_repos_addons/wizard/git_repos_wizard.py create mode 100644 bemade_git_repos_addons/wizard/git_repos_wizard_views.xml create mode 100644 bemade_helpdesk_mailcow_blacklist/__init__.py create mode 100644 bemade_helpdesk_mailcow_blacklist/__manifest__.py create mode 100644 bemade_helpdesk_mailcow_blacklist/data/helpdesk_stages.xml create mode 100644 bemade_helpdesk_mailcow_blacklist/demo/demo.xml create mode 100644 bemade_helpdesk_mailcow_blacklist/migration18.md create mode 100644 bemade_helpdesk_mailcow_blacklist/models/__init__.py create mode 100644 bemade_helpdesk_mailcow_blacklist/models/helpdesk_ticket.py create mode 100644 bemade_helpdesk_mailcow_blacklist/models/res_partner.py create mode 100644 bemade_helpdesk_mailcow_blacklist/security/ir.model.access.csv create mode 100644 bemade_helpdesk_mailcow_blacklist/views/helpdesk_ticket_views.xml create mode 100644 bemade_helpdesk_one_ticket_per_email/__init__.py create mode 100644 bemade_helpdesk_one_ticket_per_email/__manifest__.py create mode 100644 bemade_helpdesk_one_ticket_per_email/migration18.md create mode 100644 bemade_helpdesk_one_ticket_per_email/models/__init__.py create mode 100644 bemade_helpdesk_one_ticket_per_email/models/mail_thread.py create mode 100644 bemade_hide_decimal_on_unit/__init__.py create mode 100644 bemade_hide_decimal_on_unit/__manifest__.py create mode 100644 bemade_hide_decimal_on_unit/migration18.md create mode 100644 bemade_hide_decimal_on_unit/views/purchase.xml create mode 100644 bemade_hide_decimal_on_unit/views/sale.xml create mode 100644 bemade_l10n_ca_payroll/__init__.py create mode 100644 bemade_l10n_ca_payroll/__manifest__.py create mode 100644 bemade_l10n_ca_payroll/data/hr_payslip_input_type_data.xml create mode 100644 bemade_l10n_ca_payroll/data/hr_salary_rule_data.xml create mode 100644 bemade_l10n_ca_payroll/models/__init__.py create mode 100644 bemade_l10n_ca_payroll/models/hr_payroll_structure_type.py create mode 100644 bemade_l10n_ca_payroll/models/hr_payslip.py create mode 100644 bemade_mailcow_integration/__init__.py create mode 100644 bemade_mailcow_integration/__manifest__.py create mode 100644 bemade_mailcow_integration/controllers/controllers.py create mode 100644 bemade_mailcow_integration/demo/demo.xml create mode 100644 bemade_mailcow_integration/migration18.md create mode 100644 bemade_mailcow_integration/migrations/15.0.1.0.1/pre-migrate.py create mode 100644 bemade_mailcow_integration/models/__init__.py create mode 100644 bemade_mailcow_integration/models/mail_alias.py create mode 100644 bemade_mailcow_integration/models/mailcow.py create mode 100644 bemade_mailcow_integration/models/mailcow_alias.py create mode 100644 bemade_mailcow_integration/models/mailcow_blacklist.py create mode 100644 bemade_mailcow_integration/models/mailcow_mailbox.py create mode 100644 bemade_mailcow_integration/models/res_config_settings.py create mode 100644 bemade_mailcow_integration/models/res_users.py create mode 100644 bemade_mailcow_integration/security/ir.model.access.csv create mode 100644 bemade_mailcow_integration/static/description/icon.png create mode 100644 bemade_mailcow_integration/static/description/logomailcow.svg create mode 100644 bemade_mailcow_integration/static/src/js/mailcow.js create mode 100644 bemade_mailcow_integration/static/src/xml/mailcow_templates.xml create mode 100644 bemade_mailcow_integration/tests/__init__.py create mode 100644 bemade_mailcow_integration/tests/test_mailcow.py create mode 100644 bemade_mailcow_integration/views/mailcow_alias_views.xml create mode 100644 bemade_mailcow_integration/views/mailcow_blacklist_views.xml create mode 100644 bemade_mailcow_integration/views/mailcow_mailbox_views.xml create mode 100644 bemade_mailcow_integration/views/res_config_settings_views.xml create mode 100644 bemade_mailcow_integration/views/res_users_views.xml create mode 100644 bemade_mailcow_integration/views/templates.xml create mode 100644 bemade_mailcow_integration/views/views.xml create mode 100644 bemade_margin_vendor_pricelist/__init__.py create mode 100644 bemade_margin_vendor_pricelist/__manifest__.py create mode 100644 bemade_margin_vendor_pricelist/i18n/bemade_margin_vendor_pricelist.pot create mode 100644 bemade_margin_vendor_pricelist/i18n/fr.po create mode 100644 bemade_margin_vendor_pricelist/migration18.md create mode 100644 bemade_margin_vendor_pricelist/migrations/17.0.0.0.4/pre-migrate.py create mode 100644 bemade_margin_vendor_pricelist/models/__init__.py create mode 100644 bemade_margin_vendor_pricelist/models/sale_order.py create mode 100644 bemade_margin_vendor_pricelist/models/sale_order_line.py create mode 100644 bemade_margin_vendor_pricelist/tests/__init__.py create mode 100644 bemade_margin_vendor_pricelist/tests/test_margins.py create mode 100644 bemade_margin_vendor_pricelist/views/sale_order.xml create mode 100644 bemade_module_linker/__init__.py create mode 100644 bemade_module_linker/__manifest__.py create mode 100644 bemade_module_linker/migration18.md create mode 100644 bemade_module_linker/models/__init__.py create mode 100644 bemade_module_linker/models/res_config_settings.py create mode 100644 bemade_module_linker/models/res_modules_link.py create mode 100644 bemade_module_linker/views/res_config_settings_views.xml create mode 100644 bemade_multiple_billing_contacts/__init__.py create mode 100644 bemade_multiple_billing_contacts/__manifest__.py create mode 100644 bemade_multiple_billing_contacts/models/__init__.py create mode 100644 bemade_multiple_billing_contacts/models/account_move.py create mode 100644 bemade_multiple_billing_contacts/models/res_partner.py create mode 100644 bemade_multiple_billing_contacts/models/sale_order.py create mode 100644 bemade_multiple_billing_contacts/tests/__init__.py create mode 100644 bemade_multiple_billing_contacts/tests/test_billing_contacts.py create mode 100644 bemade_multiple_billing_contacts/views/account_move_views.xml create mode 100644 bemade_multiple_billing_contacts/views/res_partner_views.xml create mode 100644 bemade_multiple_billing_contacts/views/sale_order_views.xml create mode 100644 bemade_odoo_partner_scrapper/__init__.py create mode 100644 bemade_odoo_partner_scrapper/__manifest__.py create mode 100644 bemade_odoo_partner_scrapper/data/demo.xml create mode 100644 bemade_odoo_partner_scrapper/data/res_partner_relation_type.xml create mode 100644 bemade_odoo_partner_scrapper/models/__init__.py create mode 100644 bemade_odoo_partner_scrapper/models/res_partner.py create mode 100644 bemade_odoo_partner_scrapper/views/res_partner_views.xml create mode 100644 bemade_odoo_partner_scrapper_js_only/__init__.py create mode 100644 bemade_odoo_partner_scrapper_js_only/__manifest__.py create mode 100644 bemade_open_project_details/__init__.py create mode 100644 bemade_open_project_details/__manifest__.py create mode 100644 bemade_open_project_details/views/project_project_views.xml create mode 100644 bemade_packing_wizard/__init__.py create mode 100644 bemade_packing_wizard/__manifest__.py create mode 100644 bemade_packing_wizard/migration18.md create mode 100644 bemade_packing_wizard/models/__init__.py create mode 100644 bemade_packing_wizard/models/delivery_carrier.py create mode 100644 bemade_packing_wizard/models/stock_quant_package.py create mode 100644 bemade_packing_wizard/views/delivery_carrier_views.xml create mode 100644 bemade_packing_wizard/views/stock_package_views.xml create mode 100644 bemade_packing_wizard/wizard/__init__.py create mode 100644 bemade_packing_wizard/wizard/choose_delivery_package.py create mode 100644 bemade_packing_wizard/wizard/choose_delivery_package_views.xml create mode 100644 bemade_partner_email_domain/__init__.py create mode 100644 bemade_partner_email_domain/__manifest__.py create mode 100644 bemade_partner_email_domain/controllers/__init__.py create mode 100644 bemade_partner_email_domain/controllers/main.py create mode 100644 bemade_partner_email_domain/data/mail_template_data.xml create mode 100644 bemade_partner_email_domain/data/template_data.xml create mode 100644 bemade_partner_email_domain/i18n/fr_CA.po create mode 100644 bemade_partner_email_domain/migration18.md create mode 100644 bemade_partner_email_domain/models/__init__.py create mode 100644 bemade_partner_email_domain/models/res_partner.py create mode 100644 bemade_partner_email_domain/views/res_partner_views.xml create mode 100644 bemade_partner_root_ancestor/__init__.py create mode 100644 bemade_partner_root_ancestor/__manifest__.py create mode 100644 bemade_partner_root_ancestor/migration18.md create mode 100644 bemade_partner_root_ancestor/models/__init__.py create mode 100644 bemade_partner_root_ancestor/models/res_partner.py create mode 100644 bemade_partner_root_ancestor/tests/__init__.py create mode 100644 bemade_partner_root_ancestor/tests/test_root_ancestor.py create mode 100644 bemade_payslip_done_as_not_paid/__init__.py create mode 100644 bemade_payslip_done_as_not_paid/__manifest__.py create mode 100644 bemade_payslip_done_as_not_paid/views/hr_payslip_views.xml create mode 100644 bemade_picking_upstream/__init__.py create mode 100644 bemade_picking_upstream/__manifest__.py create mode 100644 bemade_picking_upstream/migration18.md create mode 100644 bemade_picking_upstream/models/__init__.py create mode 100644 bemade_picking_upstream/models/stock_picking.py create mode 100644 bemade_picking_upstream/views/stock_picking_views.xml create mode 100644 bemade_purchase_warn_supplier_overdue/__init__.py create mode 100644 bemade_purchase_warn_supplier_overdue/__manifest__.py create mode 100644 bemade_purchase_warn_supplier_overdue/migration18.md create mode 100644 bemade_purchase_warn_supplier_overdue/models/__init__.py create mode 100644 bemade_purchase_warn_supplier_overdue/models/purchase_order.py create mode 100644 bemade_purchase_warn_supplier_overdue/models/res_company.py create mode 100644 bemade_purchase_warn_supplier_overdue/models/res_config_settings.py create mode 100644 bemade_purchase_warn_supplier_overdue/tests/__init__.py create mode 100644 bemade_purchase_warn_supplier_overdue/tests/test_purchase_order_overdue.py create mode 100644 bemade_purchase_warn_supplier_overdue/views/res_config_settings_views.xml create mode 100644 bemade_pwa_config/__init__.py create mode 100644 bemade_pwa_config/__manifest__.py create mode 100644 bemade_pwa_config/controllers/__init__.py create mode 100644 bemade_pwa_config/controllers/main.py create mode 100644 bemade_pwa_config/i18n/bemade_pwa_config.pot create mode 100644 bemade_pwa_config/i18n/fr_CA.po create mode 100644 bemade_pwa_config/migration18.md create mode 100644 bemade_pwa_config/models/__init__.py create mode 100644 bemade_pwa_config/models/res_company.py create mode 100644 bemade_pwa_config/models/res_config_settings.py create mode 100644 bemade_pwa_config/security/ir.model.access.csv create mode 100644 bemade_pwa_config/views/res_config_settings_views.xml create mode 100644 bemade_quotation_alternative/__init__.py create mode 100644 bemade_quotation_alternative/__manifest__.py create mode 100644 bemade_quotation_alternative/migration18.md create mode 100644 bemade_quotation_alternative/models/__init__.py create mode 100644 bemade_quotation_alternative/models/sale_order.py create mode 100644 bemade_quotation_alternative/security/ir.model.access.csv create mode 100644 bemade_quotation_alternative/views/sale_order_views.xml create mode 100644 bemade_quotation_alternative/wizard/__init__.py create mode 100644 bemade_quotation_alternative/wizard/sale_oder_line_duplication_wizard.py create mode 100644 bemade_quotation_alternative/wizard/sale_order_duplication_wizard.py create mode 100644 bemade_quotation_alternative/wizard/sale_order_duplication_wizard_view.xml create mode 100644 bemade_reordering_rules_chatter/__init__.py create mode 100644 bemade_reordering_rules_chatter/__manifest__.py create mode 100644 bemade_reordering_rules_chatter/migration18.md create mode 100644 bemade_reordering_rules_chatter/models/__init__.py create mode 100644 bemade_reordering_rules_chatter/models/stock_warehouse_orderpoint.py create mode 100644 bemade_reordering_rules_chatter/views/stock_warehouse_orderpoint.xml create mode 100644 bemade_sale_order_line_number/__manifest__.py create mode 100644 bemade_sale_order_line_number/views/sale_order_views.xml create mode 100644 bemade_search_supplier_code/__init__.py create mode 100644 bemade_search_supplier_code/__manifest__.py create mode 100644 bemade_search_supplier_code/migration18.md create mode 100644 bemade_search_supplier_code/models/__init__.py create mode 100644 bemade_search_supplier_code/models/product_product.py create mode 100644 bemade_search_supplier_code/views/product_product_views.xml create mode 100644 bemade_so_and_po_only_company/__init__.py create mode 100644 bemade_so_and_po_only_company/__manifest__.py create mode 100644 bemade_so_and_po_only_company/migration18.md create mode 100644 bemade_so_and_po_only_company/views/purchase_order.xml create mode 100644 bemade_so_and_po_only_company/views/sale_order.xml create mode 100644 bemade_so_followers_to_picking/__init__.py create mode 100644 bemade_so_followers_to_picking/__manifest__.py create mode 100644 bemade_so_followers_to_picking/migration18.md create mode 100644 bemade_so_followers_to_picking/models/__init__.py create mode 100644 bemade_so_followers_to_picking/models/stock_picking.py create mode 100644 bemade_sports_clinic/__init__.py create mode 100644 bemade_sports_clinic/__manifest__.py create mode 100644 bemade_sports_clinic/controllers/__init__.py create mode 100644 bemade_sports_clinic/controllers/team_staff_portal.py create mode 100644 bemade_sports_clinic/data/demo/sports_clinic_demo_data.xml create mode 100644 bemade_sports_clinic/data/sports_clinic_data.xml create mode 100644 bemade_sports_clinic/i18n/fr_CA.po create mode 100644 bemade_sports_clinic/migrations/16.0.1.5.4/post-migrate.py create mode 100644 bemade_sports_clinic/migrations/16.0.1.5.7/post-migrate.py create mode 100644 bemade_sports_clinic/migrations/16.0.1.5.8/post-migrate.py create mode 100644 bemade_sports_clinic/migrations/16.0.1.6.0/post-migrate.py create mode 100644 bemade_sports_clinic/migrations/16.0.1.7.0/post-migrate.py create mode 100644 bemade_sports_clinic/migrations/16.0.1.7.0/pre-migrate.py create mode 100644 bemade_sports_clinic/models/__init__.py create mode 100644 bemade_sports_clinic/models/mail_followers.py create mode 100644 bemade_sports_clinic/models/patient.py create mode 100644 bemade_sports_clinic/models/patient_contact.py create mode 100644 bemade_sports_clinic/models/patient_injury.py create mode 100644 bemade_sports_clinic/models/res_partner.py create mode 100644 bemade_sports_clinic/models/res_users.py create mode 100644 bemade_sports_clinic/models/sports_team.py create mode 100644 bemade_sports_clinic/security/ir.model.access.csv create mode 100644 bemade_sports_clinic/security/sports_clinic_groups.xml create mode 100644 bemade_sports_clinic/security/sports_clinic_rules.xml create mode 100644 bemade_sports_clinic/static/description/icon.png create mode 100644 bemade_sports_clinic/tests/__init__.py create mode 100644 bemade_sports_clinic/tests/test_patient.py create mode 100644 bemade_sports_clinic/tests/test_rights.py create mode 100644 bemade_sports_clinic/tests/test_users.py create mode 100644 bemade_sports_clinic/views/res_partner_views.xml create mode 100644 bemade_sports_clinic/views/res_users_views.xml create mode 100644 bemade_sports_clinic/views/sports_clinic_menus.xml create mode 100644 bemade_sports_clinic/views/sports_clinic_portal_views.xml create mode 100644 bemade_sports_clinic/views/sports_patient_injury_views.xml create mode 100644 bemade_sports_clinic/views/sports_patient_views.xml create mode 100644 bemade_sports_clinic/views/sports_team_views.xml create mode 100644 bemade_stock_quant_valuation/__init__.py create mode 100644 bemade_stock_quant_valuation/__manifest__.py create mode 100644 bemade_stock_quant_valuation/migration18.md create mode 100644 bemade_stock_quant_valuation/models/__init__.py create mode 100644 bemade_stock_quant_valuation/models/stock_quant.py create mode 100644 bemade_stock_quant_valuation/views/stock_account_views.xml create mode 100644 bemade_time_off_follower/__init__.py create mode 100644 bemade_time_off_follower/__manifest__.py create mode 100644 bemade_time_off_follower/migration18.md create mode 100644 bemade_time_off_follower/models/__init__.py create mode 100644 bemade_time_off_follower/models/hr_leave.py create mode 100644 bemade_time_off_follower/models/mail_thread.py create mode 100644 bemade_time_off_follower/tests/__init__.py create mode 100644 bemade_time_off_follower/tests/test_mail_thread.py create mode 100644 bemade_time_off_follower/views/hr_leave_views.xml create mode 100644 bemade_total_show_currency/__manifest__.py create mode 100644 bemade_total_show_currency/views/total_template.xml create mode 100644 bemade_update_validity_date_when_send_so/__init__.py create mode 100644 bemade_update_validity_date_when_send_so/__manifest__.py create mode 100644 bemade_update_validity_date_when_send_so/migration18.md create mode 100644 bemade_update_validity_date_when_send_so/models/__init__.py create mode 100644 bemade_update_validity_date_when_send_so/models/sale_order.py create mode 100644 bemade_user_custom_apps_order/__init__.py create mode 100644 bemade_user_custom_apps_order/__manifest__.py create mode 100644 bemade_user_custom_apps_order/models/__init__.py create mode 100644 bemade_user_custom_apps_order/models/ir_ui_menu.py create mode 100644 bemade_user_custom_apps_order/models/res_users.py create mode 100644 bemade_user_custom_apps_order/models/res_users_menu_order.py create mode 100644 bemade_user_custom_apps_order/security/ir.model.access.csv create mode 100644 bemade_user_custom_apps_order/static/tests/tours/custom_app_order_tour.js create mode 100644 bemade_user_custom_apps_order/tests/__init__.py create mode 100644 bemade_user_custom_apps_order/tests/test_user_custom_app_order.py create mode 100644 bemade_user_custom_apps_order/views/res_users_view.xml create mode 100644 bemade_user_custom_apps_order/views/user_menu_sequence.xml create mode 100644 bemade_user_password_bundle/__init__.py create mode 100644 bemade_user_password_bundle/__manifest__.py create mode 100644 bemade_user_password_bundle/migration18.md create mode 100644 bemade_user_password_bundle/models/__init__.py create mode 100644 bemade_user_password_bundle/models/hr_employee.py create mode 100644 bemade_user_password_bundle/models/password_bundle.py create mode 100644 bemade_utils/__init__.py create mode 100644 bemade_utils/__manifest__.py create mode 100644 bemade_utils/migration18.md create mode 100644 bemade_utils/tests/__init__.py create mode 100644 bemade_utils/tests/test_patching_test.py create mode 100644 bemade_utils/tools/__init__.py create mode 100644 bemade_utils/tools/test.py create mode 100644 client_name_on_manufacturing_orders/__init__.py create mode 100644 client_name_on_manufacturing_orders/__manifest__.py create mode 100644 client_name_on_manufacturing_orders/models/__init__.py create mode 100644 client_name_on_manufacturing_orders/models/mrp_production.py create mode 100644 client_name_on_manufacturing_orders/models/mrp_workorder.py create mode 100644 client_name_on_manufacturing_orders/static/src/mrp_display/mrp_display.esm.js create mode 100644 client_name_on_manufacturing_orders/static/src/mrp_display/mrp_display_action.esm.js create mode 100644 client_name_on_manufacturing_orders/static/src/mrp_display/mrp_display_record.esm.js create mode 100644 client_name_on_manufacturing_orders/static/src/mrp_display/mrp_display_record.xml create mode 100644 client_name_on_manufacturing_orders/tests/__init__.py create mode 100644 client_name_on_manufacturing_orders/tests/test_mrp_production.py create mode 100644 client_name_on_manufacturing_orders/views/mrp_production_views.xml create mode 100644 commercial_invoice/__init__.py create mode 100644 commercial_invoice/__manifest__.py create mode 100644 commercial_invoice/data/commercial_invoice_sequence.xml create mode 100644 commercial_invoice/models/__init__.py create mode 100644 commercial_invoice/models/account_move.py create mode 100644 commercial_invoice/models/commercial_invoice.py create mode 100644 commercial_invoice/report/__init__.py create mode 100644 commercial_invoice/report/commercial_invoice_report.xml create mode 100644 commercial_invoice/report/report_templates.xml create mode 100644 commercial_invoice/security/ir.model.access.csv create mode 100644 commercial_invoice/views/account_move_views.xml create mode 100644 commercial_invoice/views/commercial_invoice_views.xml create mode 100644 confirm_many2one_create/__init__.py create mode 100644 confirm_many2one_create/__manifest__.py create mode 100644 confirm_many2one_create/static/src/js/many2one_field.esm.js create mode 100644 current_user_as_invoice_user/__init__.py create mode 100644 current_user_as_invoice_user/__manifest__.py create mode 100644 current_user_as_invoice_user/tests/__init__.py create mode 100644 current_user_as_invoice_user/tests/test_current_user_as_invoice_user.py create mode 100644 current_user_as_invoice_user/wizard/__init__.py create mode 100644 current_user_as_invoice_user/wizard/sale_make_invoice_advance.py create mode 100644 current_user_as_invoice_user/wizard/sale_order.py create mode 100644 customer_applications/__init__.py create mode 100644 customer_applications/__manifest__.py create mode 100644 customer_applications/data/menus_actions.xml create mode 100644 customer_applications/models/__init__.py create mode 100644 customer_applications/models/application.py create mode 100644 customer_applications/models/application_specification.py create mode 100644 customer_applications/models/application_specification_key.py create mode 100644 customer_applications/models/application_type.py create mode 100644 customer_applications/models/res_partner.py create mode 100644 customer_applications/security/groups.xml create mode 100644 customer_applications/security/ir.model.access.csv create mode 100644 customer_applications/tests/__init__.py create mode 100644 customer_applications/tests/test_application.py create mode 100644 customer_applications/views/application_type_views.xml create mode 100644 customer_applications/views/application_views.xml create mode 100644 customer_applications/views/res_partner_views.xml create mode 100644 customer_itch_cycle/__init__.py create mode 100644 customer_itch_cycle/__manifest__.py create mode 100644 customer_itch_cycle/models/__init__.py create mode 100644 customer_itch_cycle/models/itch_cycle_product_partner.py create mode 100644 customer_itch_cycle/models/sale_order.py create mode 100644 customer_itch_cycle/views/itch_cycle_product_partner_view.xml create mode 100644 customer_itch_cycle/views/res_partner_view.xml create mode 100644 fsm_equipment/__init__.py create mode 100644 fsm_equipment/__manifest__.py create mode 100644 fsm_equipment/migrations/17.0.0.2.0/post-migrate.py create mode 100644 fsm_equipment/models/__init__.py create mode 100644 fsm_equipment/models/equipment.py create mode 100644 fsm_equipment/models/equipment_tag.py create mode 100644 fsm_equipment/models/res_partner.py create mode 100644 fsm_equipment/models/task.py create mode 100644 fsm_equipment/security/ir.model.access.csv create mode 100644 fsm_equipment/views/equipment_views.xml create mode 100644 fsm_equipment/views/project_task_views.xml create mode 100644 fsm_equipment/views/res_partner_views.xml create mode 100644 fsm_visit_confirmation/__init__.py create mode 100644 fsm_visit_confirmation/__manifest__.py create mode 100644 fsm_visit_confirmation/controllers/__init__.py create mode 100644 fsm_visit_confirmation/controllers/portal.py create mode 100644 fsm_visit_confirmation/data/mail_templates.xml create mode 100644 fsm_visit_confirmation/models/__init__.py create mode 100644 fsm_visit_confirmation/models/project_task.py create mode 100644 fsm_visit_confirmation/views/project_portal_project_task_templates.xml create mode 100644 impersonate_user/__init__.py create mode 100644 impersonate_user/__manifest__.py create mode 100644 impersonate_user/controllers/__init__.py create mode 100644 impersonate_user/controllers/main.py create mode 100644 impersonate_user/models/__init__.py create mode 100644 impersonate_user/models/ir_http.py create mode 100644 impersonate_user/models/res_users.py create mode 100644 impersonate_user/static/src/systray_unimpersonate_button.esm.js create mode 100644 impersonate_user/static/src/systray_unimpersonate_button.xml create mode 100644 impersonate_user/views/impersonation_views.xml create mode 100644 improved_mo_origin/__init__.py create mode 100644 improved_mo_origin/__manifest__.py create mode 100644 improved_mo_origin/models/__init__.py create mode 100644 improved_mo_origin/models/mrp_production.py create mode 100644 improved_mo_origin/static/src/mrp_display/mrp_display.esm.js create mode 100644 improved_mo_origin/static/src/mrp_display/mrp_display_action.esm.js create mode 100644 improved_mo_origin/static/src/mrp_display/mrp_display_record.esm.js create mode 100644 improved_mo_origin/static/src/mrp_display/mrp_display_record.xml create mode 100644 improved_mo_origin/tests/__init__.py create mode 100644 improved_mo_origin/tests/test_mrp_production.py create mode 100644 improved_mo_origin/views/mrp_production_views.xml create mode 100644 interactive_discuss_ai/__init__.py create mode 100644 interactive_discuss_ai/__manifest__.py create mode 100644 interactive_discuss_ai/models/__init__.py create mode 100644 interactive_discuss_ai/models/ai_assistant.py create mode 100644 interactive_discuss_ai/models/ai_config.py create mode 100644 interactive_discuss_ai/models/ai_service.py create mode 100644 interactive_discuss_ai/security/ir.model.access.csv create mode 100644 interactive_discuss_ai/static/src/js/ai_assistant_chat.js create mode 100644 interactive_discuss_ai/static/src/xml/ai_assistant_chat.xml create mode 100644 interactive_discuss_ai/views/ai_assistant_views.xml create mode 100644 interactive_discuss_ai/views/ai_config_views.xml create mode 100644 l10n_ca_pst_reports/__init__.py create mode 100644 l10n_ca_pst_reports/__manifest__.py create mode 100644 l10n_ca_pst_reports/tests/__init__.py create mode 100644 l10n_ca_pst_reports/tests/test_report_tax_numbers.py create mode 100644 l10n_ca_pst_reports/views/external_layout.xml create mode 100644 mo_back_to_draft/__init__.py create mode 100644 mo_back_to_draft/__manifest__.py create mode 100644 mo_back_to_draft/models/__init__.py create mode 100644 mo_back_to_draft/models/mrp_production.py create mode 100644 mo_back_to_draft/views/mrp_production_views.xml create mode 100644 msg_attachments_to_mail_message/__init__.py create mode 100644 msg_attachments_to_mail_message/__manifest__.py create mode 100644 msg_attachments_to_mail_message/migrations/16.0.1.1/__init__.py create mode 100644 msg_attachments_to_mail_message/migrations/16.0.1.1/pre-migration.py create mode 100644 msg_attachments_to_mail_message/migrations/__init__.py create mode 100644 msg_attachments_to_mail_message/models/__init__.py create mode 100644 msg_attachments_to_mail_message/models/ir_attachment.py create mode 100644 msg_attachments_to_mail_message/models/mail_thread.py create mode 100644 msg_attachments_to_mail_message/models/res_config_settings.py create mode 100644 msg_attachments_to_mail_message/requirements.txt create mode 100644 msg_attachments_to_mail_message/views/res_config_settings_views.xml create mode 100644 msg_viewer/__init__.py create mode 100644 msg_viewer/__manifest__.py create mode 100644 msg_viewer/controllers/__init__.py create mode 100644 msg_viewer/controllers/main.py create mode 100644 msg_viewer/models/__init__.py create mode 100644 msg_viewer/models/ir_attachment.py create mode 100644 msg_viewer/static/src/components/attachment_card_msg/attachment_card_msg.xml create mode 100644 msg_viewer/static/src/components/msg_viewer/msg_viewer.js create mode 100644 msg_viewer/static/src/components/msg_viewer/msg_viewer.xml create mode 100644 msg_viewer/static/src/css/msg_viewer.css create mode 100644 msg_viewer/static/src/js/msg_viewer.js create mode 100644 msg_viewer/static/src/lib/components/attachment/index.html create mode 100644 msg_viewer/static/src/lib/components/attachment/index.ts create mode 100644 msg_viewer/static/src/lib/components/attachment/styles.css create mode 100644 msg_viewer/static/src/lib/components/embedded-msg/index.html create mode 100644 msg_viewer/static/src/lib/components/embedded-msg/index.ts create mode 100644 msg_viewer/static/src/lib/components/embedded-msg/styles.css create mode 100644 msg_viewer/static/src/lib/components/error/index.html create mode 100644 msg_viewer/static/src/lib/components/error/index.ts create mode 100644 msg_viewer/static/src/lib/components/error/styles.css create mode 100644 msg_viewer/static/src/lib/components/message/index.html create mode 100644 msg_viewer/static/src/lib/components/message/index.ts create mode 100644 msg_viewer/static/src/lib/components/message/styles.css create mode 100644 msg_viewer/static/src/lib/components/recipient/index.html create mode 100644 msg_viewer/static/src/lib/components/recipient/index.ts create mode 100644 msg_viewer/static/src/lib/components/recipient/styles.css create mode 100644 msg_viewer/static/src/lib/components/styles.css create mode 100644 msg_viewer/static/src/lib/favicon.ico create mode 100644 msg_viewer/static/src/lib/index.html create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/.gitignore create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/LICENSE create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/README.md create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/build.ts create mode 100755 msg_viewer/static/src/lib/msg-viewer-main/bun.lockb create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/declarations.d.ts create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/icon.svg create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/jsconfig.json create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/components/attachment/index.html create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/components/attachment/index.ts create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/components/attachment/styles.css create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/components/embedded-msg/index.html create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/components/embedded-msg/index.ts create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/components/embedded-msg/styles.css create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/components/error/index.html create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/components/error/index.ts create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/components/error/styles.css create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/components/message/index.html create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/components/message/index.ts create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/components/message/styles.css create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/components/recipient/index.html create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/components/recipient/index.ts create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/components/recipient/styles.css create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/components/styles.css create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/favicon.ico create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/index.html create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/resources/google331b65fa04565532.html create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/resources/robots.txt create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/scripts/index.ts create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/scripts/msg/README.md create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/scripts/msg/compound-file/compound-file.ts create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/scripts/msg/compound-file/constants/text-decoder.ts create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/scripts/msg/compound-file/difat.ts create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/scripts/msg/compound-file/directory/directory.ts create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/scripts/msg/compound-file/directory/enums/color-flag.ts create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/scripts/msg/compound-file/directory/enums/object-type.ts create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/scripts/msg/compound-file/directory/types/directory-entry.d.ts create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/scripts/msg/compound-file/fat.ts create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/scripts/msg/compound-file/header.ts create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/scripts/msg/compound-file/mini-fat.ts create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/scripts/msg/compound-file/util.ts create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/scripts/msg/msg-parser.ts create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/scripts/msg/package.json create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/scripts/msg/rtf/decompressor/crc.ts create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/scripts/msg/rtf/decompressor/dictionary.ts create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/scripts/msg/rtf/decompressor/header.ts create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/scripts/msg/rtf/rtf-decompressor.ts create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/scripts/msg/streams/constants/folders.ts create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/scripts/msg/streams/entry/entry-stream.ts create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/scripts/msg/streams/entry/types/entry-stream-entry.ts create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/scripts/msg/streams/property/properties.ts create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/scripts/msg/streams/property/property-stream.ts create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/scripts/msg/streams/property/property-types.ts create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/scripts/msg/streams/property/types/property-data.d.ts create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/scripts/msg/streams/property/types/property-header.d.ts create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/scripts/msg/streams/property/types/property-stream-entry.d.ts create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/scripts/msg/types/message.d.ts create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/scripts/utils/file-size-util.ts create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/scripts/utils/html-template-util.ts create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/styles/root.css create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/lib/styles/styles.css create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/package-lock.json create mode 100644 msg_viewer/static/src/lib/msg-viewer-main/package.json create mode 100644 msg_viewer/static/src/lib/msg-viewer.js create mode 100644 msg_viewer/static/src/lib/msg-viewer.zip create mode 100644 msg_viewer/static/src/lib/resources/google331b65fa04565532.html create mode 100644 msg_viewer/static/src/lib/resources/robots.txt create mode 100644 msg_viewer/static/src/lib/scripts/index.ts create mode 100644 msg_viewer/static/src/lib/scripts/msg/README.md create mode 100644 msg_viewer/static/src/lib/scripts/msg/compound-file/compound-file.ts create mode 100644 msg_viewer/static/src/lib/scripts/msg/compound-file/constants/text-decoder.ts create mode 100644 msg_viewer/static/src/lib/scripts/msg/compound-file/difat.ts create mode 100644 msg_viewer/static/src/lib/scripts/msg/compound-file/directory/directory.ts create mode 100644 msg_viewer/static/src/lib/scripts/msg/compound-file/directory/enums/color-flag.ts create mode 100644 msg_viewer/static/src/lib/scripts/msg/compound-file/directory/enums/object-type.ts create mode 100644 msg_viewer/static/src/lib/scripts/msg/compound-file/directory/types/directory-entry.d.ts create mode 100644 msg_viewer/static/src/lib/scripts/msg/compound-file/fat.ts create mode 100644 msg_viewer/static/src/lib/scripts/msg/compound-file/header.ts create mode 100644 msg_viewer/static/src/lib/scripts/msg/compound-file/mini-fat.ts create mode 100644 msg_viewer/static/src/lib/scripts/msg/compound-file/util.ts create mode 100644 msg_viewer/static/src/lib/scripts/msg/msg-parser.ts create mode 100644 msg_viewer/static/src/lib/scripts/msg/package.json create mode 100644 msg_viewer/static/src/lib/scripts/msg/rtf/decompressor/crc.ts create mode 100644 msg_viewer/static/src/lib/scripts/msg/rtf/decompressor/dictionary.ts create mode 100644 msg_viewer/static/src/lib/scripts/msg/rtf/decompressor/header.ts create mode 100644 msg_viewer/static/src/lib/scripts/msg/rtf/rtf-decompressor.ts create mode 100644 msg_viewer/static/src/lib/scripts/msg/streams/constants/folders.ts create mode 100644 msg_viewer/static/src/lib/scripts/msg/streams/entry/entry-stream.ts create mode 100644 msg_viewer/static/src/lib/scripts/msg/streams/entry/types/entry-stream-entry.ts create mode 100644 msg_viewer/static/src/lib/scripts/msg/streams/property/properties.ts create mode 100644 msg_viewer/static/src/lib/scripts/msg/streams/property/property-stream.ts create mode 100644 msg_viewer/static/src/lib/scripts/msg/streams/property/property-types.ts create mode 100644 msg_viewer/static/src/lib/scripts/msg/streams/property/types/property-data.d.ts create mode 100644 msg_viewer/static/src/lib/scripts/msg/streams/property/types/property-header.d.ts create mode 100644 msg_viewer/static/src/lib/scripts/msg/streams/property/types/property-stream-entry.d.ts create mode 100644 msg_viewer/static/src/lib/scripts/msg/types/message.d.ts create mode 100644 msg_viewer/static/src/lib/scripts/utils/file-size-util.ts create mode 100644 msg_viewer/static/src/lib/scripts/utils/html-template-util.ts create mode 100644 msg_viewer/static/src/lib/styles/root.css create mode 100644 msg_viewer/static/src/lib/styles/styles.css create mode 100644 msg_viewer/static/src/models/attachment_card_msg.js create mode 100644 msg_viewer/test/FACTURE NO. 7005451.msg create mode 100644 msg_viewer/test/analyze_msg.py create mode 100644 msg_viewer/views/msg_viewer_views.xml create mode 100644 multi_account_statement_import/__init__.py create mode 100644 multi_account_statement_import/__manifest__.py create mode 100644 odoo_proxmox_manager/README.md create mode 100644 odoo_proxmox_manager/__init__.py create mode 100644 odoo_proxmox_manager/__manifest__.py create mode 100644 odoo_proxmox_manager/controllers/__init__.py create mode 100644 odoo_proxmox_manager/controllers/main.py create mode 100644 odoo_proxmox_manager/models/__init__.py create mode 100644 odoo_proxmox_manager/models/proxmox_cluster.py create mode 100644 odoo_proxmox_manager/models/proxmox_server.py create mode 100644 odoo_proxmox_manager/models/proxmox_vm.py create mode 100644 odoo_proxmox_manager/requirements.txt create mode 100644 odoo_proxmox_manager/security/ir.model.access.csv create mode 100644 odoo_proxmox_manager/security/proxmox_security.xml create mode 100644 odoo_proxmox_manager/static/description/icon.png create mode 100644 odoo_proxmox_manager/static/src/components/proxmox_server_list/proxmox_server_list.js create mode 100644 odoo_proxmox_manager/static/src/components/proxmox_server_list/proxmox_server_list.xml create mode 100644 odoo_proxmox_manager/static/src/js/dashboard_view.js create mode 100644 odoo_proxmox_manager/static/src/js/proxmox_server_list.js create mode 100644 odoo_proxmox_manager/static/src/scss/dashboard.scss create mode 100644 odoo_proxmox_manager/static/src/xml/dashboard.xml create mode 100644 odoo_proxmox_manager/static/src/xml/proxmox_server_list.xml create mode 100644 odoo_proxmox_manager/views/proxmox_cluster_views.xml create mode 100644 odoo_proxmox_manager/views/proxmox_menus.xml create mode 100644 odoo_proxmox_manager/views/proxmox_server_views.xml create mode 100644 odoo_proxmox_manager/views/proxmox_vm_views.xml create mode 100644 odoo_proxmox_manager/wizard/__init__.py create mode 100644 odoo_proxmox_manager/wizard/proxmox_server_wizard.py create mode 100644 odoo_proxmox_manager/wizard/proxmox_server_wizard_views.xml create mode 100644 odoo_rsync_backup/__init__.py create mode 100644 odoo_rsync_backup/__manifest__.py create mode 100644 odoo_rsync_backup/data/backup_cron.xml create mode 100644 odoo_rsync_backup/models/__init__.py create mode 100644 odoo_rsync_backup/models/rsync_config.py create mode 100644 odoo_rsync_backup/security/ir.model.access.csv create mode 100644 odoo_rsync_backup/views/rsync_config_views.xml create mode 100644 odoo_to_odoo_bemade/__init__.py create mode 100644 odoo_to_odoo_bemade/__manifest__.py create mode 100644 odoo_to_odoo_bemade/data/ir_cron_data.xml create mode 100644 odoo_to_odoo_bemade/models/__init__.py create mode 100644 odoo_to_odoo_bemade/models/sync_instance.py create mode 100644 odoo_to_odoo_bemade/models/sync_log.py create mode 100644 odoo_to_odoo_bemade/models/sync_manager.py create mode 100644 odoo_to_odoo_bemade/models/sync_model.py create mode 100644 odoo_to_odoo_bemade/models/sync_model_field.py create mode 100644 odoo_to_odoo_bemade/models/sync_queue.py create mode 100644 odoo_to_odoo_bemade/security/ir.model.access.csv create mode 100644 odoo_to_odoo_bemade/static/description/icon.png create mode 100644 odoo_to_odoo_bemade/static/description/icon.svg create mode 100644 odoo_to_odoo_bemade/static/description/index.html create mode 100644 odoo_to_odoo_bemade/views/menus.xml create mode 100644 odoo_to_odoo_bemade/views/sync_instance_views.xml create mode 100644 odoo_to_odoo_bemade/views/sync_log_views.xml create mode 100644 odoo_to_odoo_bemade/views/sync_model_views.xml create mode 100644 odoo_to_odoo_bemade/views/sync_queue_views.xml create mode 100644 odoo_to_odoo_bemade_customer/__init__.py create mode 100644 odoo_to_odoo_bemade_customer/__manifest__.py create mode 100644 odoo_to_odoo_bemade_customer/data/ir_cron_data.xml create mode 100644 odoo_to_odoo_bemade_customer/models/__init__.py create mode 100644 odoo_to_odoo_bemade_customer/models/sync_config.py create mode 100644 odoo_to_odoo_bemade_customer/models/sync_log.py create mode 100644 odoo_to_odoo_bemade_customer/models/sync_manager.py create mode 100644 odoo_to_odoo_bemade_customer/models/sync_model.py create mode 100644 odoo_to_odoo_bemade_customer/models/sync_model_field.py create mode 100644 odoo_to_odoo_bemade_customer/models/sync_queue.py create mode 100644 odoo_to_odoo_bemade_customer/security/ir.model.access.csv create mode 100644 odoo_to_odoo_bemade_customer/static/description/icon.png create mode 100644 odoo_to_odoo_bemade_customer/static/description/icon.svg create mode 100644 odoo_to_odoo_bemade_customer/static/description/index.html create mode 100644 odoo_to_odoo_bemade_customer/views/menus.xml create mode 100644 odoo_to_odoo_bemade_customer/views/sync_instance_views.xml create mode 100644 odoo_to_odoo_bemade_customer/views/sync_log_views.xml create mode 100644 odoo_to_odoo_bemade_customer/views/sync_model_views.xml create mode 100644 odoo_to_odoo_bemade_customer/views/sync_queue_views.xml create mode 100644 odoo_to_odoo_sync/Spécifications.md create mode 100644 odoo_to_odoo_sync/__init__.py create mode 100644 odoo_to_odoo_sync/__manifest__.py create mode 100644 odoo_to_odoo_sync/data/ir_cron_data.xml create mode 100644 odoo_to_odoo_sync/models/__init__.py create mode 100644 odoo_to_odoo_sync/models/sync_instance.py create mode 100644 odoo_to_odoo_sync/models/sync_log.py create mode 100644 odoo_to_odoo_sync/models/sync_manager.py create mode 100644 odoo_to_odoo_sync/models/sync_model.py create mode 100644 odoo_to_odoo_sync/models/sync_model_field.py create mode 100644 odoo_to_odoo_sync/models/sync_queue.py create mode 100644 odoo_to_odoo_sync/security/ir.model.access.csv create mode 100644 odoo_to_odoo_sync/static/description/icon.png create mode 100644 odoo_to_odoo_sync/static/description/icon.svg create mode 100644 odoo_to_odoo_sync/views/menus.xml create mode 100644 odoo_to_odoo_sync/views/sync_instance_views.xml create mode 100644 odoo_to_odoo_sync/views/sync_log_views.xml create mode 100644 odoo_to_odoo_sync/views/sync_model_views.xml create mode 100644 odoo_to_odoo_sync/views/sync_queue_views.xml create mode 100644 partner_equipment_applications/__init__.py create mode 100644 partner_equipment_applications/__manifest__.py create mode 100644 partner_equipment_applications/models/__init__.py create mode 100644 partner_equipment_applications/models/application.py create mode 100644 partner_equipment_applications/models/equipment.py create mode 100644 partner_equipment_applications/views/application_views.xml create mode 100644 partner_equipment_applications/views/equipment_views.xml create mode 100644 picking_policy_per_customer/__init__.py create mode 100644 picking_policy_per_customer/__manifest__.py create mode 100644 picking_policy_per_customer/models/__init__.py create mode 100644 picking_policy_per_customer/models/res_partner.py create mode 100644 picking_policy_per_customer/models/sale_order.py create mode 100644 picking_policy_per_customer/views/res_partner_views.xml create mode 100644 portal_hide_draft_order_details/__init__.py create mode 100644 portal_hide_draft_order_details/__manifest__.py create mode 100644 portal_hide_draft_order_details/i18n/fr.po create mode 100644 portal_hide_draft_order_details/i18n/portal_hide_draft_order_details.pot create mode 100644 portal_hide_draft_order_details/models/__init__.py create mode 100644 portal_hide_draft_order_details/models/res_company.py create mode 100644 portal_hide_draft_order_details/views/sale_order_portal_templates.xml create mode 100644 portal_hide_draft_order_details/wizard/__init__.py create mode 100644 portal_hide_draft_order_details/wizard/res_config_settings.py create mode 100644 portal_hide_draft_order_details/wizard/res_config_settings.xml create mode 100644 price_update_notifications/__init__.py create mode 100644 price_update_notifications/__manifest__.py create mode 100644 price_update_notifications/data/email_templates.xml create mode 100644 price_update_notifications/data/ir_cron_data.xml create mode 100644 price_update_notifications/i18n/fr.po create mode 100644 price_update_notifications/i18n/price_update_notifications.pot create mode 100644 price_update_notifications/models/__init__.py create mode 100644 price_update_notifications/models/price_notice_mailing.py create mode 100644 price_update_notifications/security/ir.model.access.csv create mode 100644 price_update_notifications/views/price_notice_mailing_views.xml create mode 100644 price_update_notifications/wizard/__init__.py create mode 100644 price_update_notifications/wizard/send_price_update_notice.py create mode 100644 price_update_notifications/wizard/send_price_update_notice_views.xml create mode 100644 purchase_customer_requisition/__init__.py create mode 100644 purchase_customer_requisition/__manifest__.py create mode 100644 purchase_customer_requisition/models/__init__.py create mode 100644 purchase_customer_requisition/models/purchase_order.py create mode 100644 purchase_customer_requisition/models/purchase_order_line.py create mode 100644 purchase_customer_requisition/models/purchase_requisition.py create mode 100644 purchase_customer_requisition/tests/__init__.py create mode 100644 purchase_customer_requisition/tests/test_purchase_order.py create mode 100644 purchase_customer_requisition/views/purchase_requisition_views.xml create mode 100644 purchase_customer_requisition/views/purchase_views.xml create mode 100644 qc_partner_default_lang/__init__.py create mode 100644 qc_partner_default_lang/__manifest__.py create mode 100644 qc_partner_default_lang/models/__init__.py create mode 100644 qc_partner_default_lang/models/res_partner.py create mode 100644 qc_partner_default_lang/static/description/index.html create mode 100644 recursive_tree_view/README.md create mode 100644 recursive_tree_view/__init__.py create mode 100644 recursive_tree_view/__manifest__.py create mode 100644 recursive_tree_view/hierarchy-svgrepo-com.svg create mode 100644 recursive_tree_view/models/__init__.py create mode 100644 recursive_tree_view/models/models.py create mode 100644 recursive_tree_view/rng/tree_view.rng create mode 100644 recursive_tree_view/static/description/icon.png create mode 100644 recursive_tree_view/static/description/images/collapsed.png create mode 100644 recursive_tree_view/static/description/images/expanded.png create mode 100644 recursive_tree_view/static/description/images/main_screenshot.png create mode 100644 recursive_tree_view/static/description/index.html create mode 100644 recursive_tree_view/static/src/list_arch_parser.js create mode 100644 recursive_tree_view/static/src/list_controller.js create mode 100644 recursive_tree_view/static/src/list_renderer.js create mode 100644 recursive_tree_view/static/src/recursive_tree_templates.xml create mode 100644 recursive_tree_view/static/src/relational_model.js create mode 100644 recursive_tree_view/static/src/tree_recursive_styles.css create mode 100644 recursive_tree_view/validation.py create mode 100644 replace_invoicing_user/__init__.py create mode 100644 replace_invoicing_user/__manifest__.py create mode 100644 replace_invoicing_user/models/__init__.py create mode 100644 replace_invoicing_user/models/account_move.py create mode 100644 replace_invoicing_user/models/res_config_settings.py create mode 100644 replace_invoicing_user/tests/__init__.py create mode 100644 replace_invoicing_user/tests/test_current_user_as_invoice_user.py create mode 100644 replace_invoicing_user/views/res_config_settings_views.xml create mode 100644 replace_invoicing_user/wizard/__init__.py create mode 100644 replace_invoicing_user/wizard/sale_make_invoice_advance.py create mode 100644 replace_invoicing_user/wizard/sale_order.py create mode 100644 stock_valuation_location/__init__.py create mode 100644 stock_valuation_location/__manifest__.py create mode 100644 stock_valuation_location/models/__init__.py create mode 100644 stock_valuation_location/models/stock_valuation_layer.py create mode 100644 stock_valuation_location/views/stock_valuation_layer_views.xml create mode 100644 temporary_change_image_size/__manifest__.py create mode 100644 temporary_change_image_size/views/product_template_views.xml create mode 100644 theme_durpro/LICENSE create mode 100644 theme_durpro/__init__.py create mode 100644 theme_durpro/__manifest__.py create mode 100644 theme_durpro/data/ir_asset.xml create mode 100644 theme_durpro/i18n/de.po create mode 100644 theme_durpro/i18n/es.po create mode 100644 theme_durpro/i18n/fr.po create mode 100644 theme_durpro/i18n/it.po create mode 100644 theme_durpro/i18n/nl.po create mode 100644 theme_durpro/i18n/theme_graphene.pot create mode 100644 theme_durpro/i18n/zh_CN.po create mode 100644 theme_durpro/models/__init__.py create mode 100644 theme_durpro/models/theme_durpro.py create mode 100644 theme_durpro/static/description/durpro_poster.png create mode 100644 theme_durpro/static/description/durpro_screenshot.jpg create mode 100644 theme_durpro/static/description/index.html create mode 100644 theme_durpro/static/description/theme_durpro.svg create mode 100755 theme_durpro/static/src/img/patterns/bg_pattern_01.jpg create mode 100755 theme_durpro/static/src/img/patterns/bg_pattern_02.jpg create mode 100755 theme_durpro/static/src/img/patterns/bg_pattern_03.jpg create mode 100755 theme_durpro/static/src/img/patterns/bg_pattern_04.jpg create mode 100755 theme_durpro/static/src/img/patterns/bg_pattern_05.jpg create mode 100755 theme_durpro/static/src/img/patterns/bg_pattern_06.jpg create mode 100755 theme_durpro/static/src/img/pictures/bg_image_01.jpg create mode 100755 theme_durpro/static/src/img/pictures/bg_image_02.jpg create mode 100755 theme_durpro/static/src/img/pictures/bg_image_03.jpg create mode 100755 theme_durpro/static/src/img/pictures/bg_image_04.jpg create mode 100755 theme_durpro/static/src/img/pictures/bg_image_05.jpg create mode 100755 theme_durpro/static/src/img/pictures/bg_image_06.jpg create mode 100755 theme_durpro/static/src/img/pictures/bg_image_07.jpg create mode 100755 theme_durpro/static/src/img/pictures/bg_image_08.jpg create mode 100755 theme_durpro/static/src/img/pictures/bg_image_09.jpg create mode 100755 theme_durpro/static/src/img/pictures/bg_image_10.jpg create mode 100644 theme_durpro/static/src/img/pictures/content_01.jpg create mode 100644 theme_durpro/static/src/img/pictures/content_02.jpg create mode 100644 theme_durpro/static/src/img/pictures/content_03.jpg create mode 100644 theme_durpro/static/src/img/pictures/content_04.jpg create mode 100644 theme_durpro/static/src/img/pictures/content_05.jpg create mode 100644 theme_durpro/static/src/img/pictures/content_06.jpg create mode 100644 theme_durpro/static/src/img/pictures/content_07.jpg create mode 100644 theme_durpro/static/src/img/pictures/content_08.jpg create mode 100644 theme_durpro/static/src/img/pictures/content_09.jpg create mode 100644 theme_durpro/static/src/img/pictures/content_10.jpg create mode 100644 theme_durpro/static/src/img/pictures/content_11.jpg create mode 100644 theme_durpro/static/src/img/pictures/content_12.jpg create mode 100644 theme_durpro/static/src/img/pictures/content_13.jpg create mode 100644 theme_durpro/static/src/img/pictures/content_14.jpg create mode 100644 theme_durpro/static/src/img/pictures/content_15.jpg create mode 100644 theme_durpro/static/src/img/pictures/content_16.jpg create mode 100644 theme_durpro/static/src/img/pictures/content_17.jpg create mode 100644 theme_durpro/static/src/img/pictures/content_18.jpg create mode 100644 theme_durpro/static/src/img/pictures/content_19.jpg create mode 100644 theme_durpro/static/src/img/pictures/content_20.jpg create mode 100644 theme_durpro/static/src/img/pictures/content_21.jpg create mode 100644 theme_durpro/static/src/img/pictures/content_22.jpg create mode 100644 theme_durpro/static/src/img/pictures/content_23.jpg create mode 100644 theme_durpro/static/src/img/pictures/s_numbers.jpg create mode 100644 theme_durpro/static/src/img/pictures/s_popup.jpg create mode 100644 theme_durpro/static/src/img/pictures/uiface_01.jpg create mode 100644 theme_durpro/static/src/img/pictures/uiface_02.jpg create mode 100644 theme_durpro/static/src/img/pictures/uiface_03.jpg create mode 100644 theme_durpro/static/src/img/pictures/uiface_04.jpg create mode 100644 theme_durpro/static/src/img/pictures/uiface_05.jpg create mode 100644 theme_durpro/static/src/img/pictures/uiface_06.jpg create mode 100644 theme_durpro/static/src/img/pictures/uiface_07.jpg create mode 100644 theme_durpro/static/src/js/tour.js create mode 100644 theme_durpro/static/src/old_snippets/s_showcase_slider/000_variables.scss create mode 100644 theme_durpro/static/src/scss/bootstrap_overridden.scss create mode 100644 theme_durpro/static/src/scss/custom_styles.scss create mode 100644 theme_durpro/static/src/scss/primary_variables.scss create mode 100644 theme_durpro/static/src/scss/secondary_variables.scss create mode 100644 theme_durpro/views/customizations.xml create mode 100644 theme_durpro/views/images_library.xml create mode 100644 wazo_integration/__init__.py create mode 100644 wazo_integration/__manifest__.py create mode 100644 wazo_integration/models/__init__.py create mode 100644 wazo_integration/models/res_company.py create mode 100644 wazo_integration/models/res_config_settings.py create mode 100644 wazo_integration/views/res_config_settings_views.xml create mode 100644 website_geoip_extended/__init__.py create mode 100644 website_geoip_extended/__manifest__.py create mode 100644 website_geoip_extended/models/__init__.py create mode 100644 website_geoip_extended/models/website_visitor.py create mode 100644 website_geoip_extended/views/website_visitor_views.xml diff --git a/README.md b/README.md index c2c2dcb..10389a3 100644 --- a/README.md +++ b/README.md @@ -1,108 +1,2 @@ # bemade-addons Odoo Addons made by Bemade - -## Addons Ported to 18.0 - -The following addons have been ported to Odoo 18.0 and are available in this repository: - -- account_email_to_pdf (18.0.1.0.0) -- batch_picking_create_one_bill (18.0.1.0.1) -- caldav_sync (18.0.0.7.0) -- customer_product_code_search (18.0.1.0.0) -- delivery_carrier_partner_account (18.0.0.1.2) -- incrementing_sequence_mixin (18.0.1.0.0) -- portal_partner_manager (18.0.1.0.0) -- preamble_on_quotation (18.0.0.1.0) -- product_supplierinfo_tracking (18.0.2.0.5) -- purchase_delivery_carrier (18.0.1.0.0) -- reception_purchase_total (18.0.1.0.0) -- sale_mandatory_customer_reference (18.0.1.0.0) -- sale_order_show_delivery_address (18.0.1.0.0) -- shipping_information_on_customer_invoice (18.0.0.1) -- st_laurent_portal_vendor (18.0.1.0.0) -- st_laurent_vendor_orders (18.0.1.0.0) -- unifi_integration (18.0.1.0.1) - -## Addons To Be Ported from 17.0 to 18.0 - -The following addons need to be ported from Odoo 17.0 to 18.0. They have been temporarily removed from this branch but can be reintegrated following the OCA methodology (creating a patch from 17.0 to bring the code in with its history): - -- account_credit_hold (17.0.1.1.1) -- aged_partner_balance_na -- ai_integration (1.0) -- ai_integration_ollama_api -- ai_integration_openai_api (1.0) -- bemade_add_follower_no_sendmail_default (17.0.0.0.1) -- bemade_attachments_cleanup -- bemade_documents_portal -- bemade_fetchmail_only_production (17.0.0.0.1) -- bemade_fix_quality_report -- bemade_fsm (17.0.0.4.2) -- bemade_full_formview_from_modal -- bemade_git_repos_addons -- bemade_helpdesk_mailcow_blacklist -- bemade_helpdesk_one_ticket_per_email -- bemade_hide_decimal_on_unit (17.0.0.1.1) -- bemade_l10n_ca_payroll -- bemade_mailcow_integration -- bemade_margin_vendor_pricelist -- bemade_module_linker -- bemade_multiple_billing_contacts -- bemade_odoo_partner_scrapper -- bemade_odoo_partner_scrapper_js_only -- bemade_open_project_details -- bemade_packing_wizard -- bemade_partner_email_domain -- bemade_partner_root_ancestor -- bemade_payslip_done_as_not_paid -- bemade_picking_upstream -- bemade_purchase_warn_supplier_overdue (1.0) -- bemade_pwa_config -- bemade_quotation_alternative -- bemade_reordering_rules_chatter (17.0.0.0.1) -- bemade_sale_order_line_number -- bemade_search_supplier_code -- bemade_so_and_po_only_company (17.0.0.0.1) -- bemade_so_followers_to_picking (17.0.0.0.1) -- bemade_sports_clinic -- bemade_stock_quant_valuation -- bemade_time_off_follower (17.0.0.0.3) -- bemade_total_show_currency -- bemade_update_validity_date_when_send_so (17.0.0.0.1) -- bemade_user_custom_apps_order -- bemade_user_password_bundle -- bemade_utils -- client_name_on_manufacturing_orders (17.0.2.0.0) -- commercial_invoice -- confirm_many2one_create (17.0.1.0.0) -- current_user_as_invoice_user -- customer_applications (17.0.1.0.0) -- customer_itch_cycle -- fsm_equipment (17.0.0.2.0) -- fsm_visit_confirmation (17.0.0.1.0) -- impersonate_user (17.0.0.0.1) -- improved_mo_origin (17.0.1.0.0) -- interactive_discuss_ai -- l10n_ca_pst_reports -- mo_back_to_draft -- msg_attachments_to_mail_message -- msg_viewer -- multi_account_statement_import -- odoo_proxmox_manager -- odoo_rsync_backup -- odoo_to_odoo_bemade -- odoo_to_odoo_bemade_customer -- odoo_to_odoo_sync -- partner_equipment_applications (17.0.1.0.0) -- picking_policy_per_customer -- portal_hide_draft_order_details (17.0.1.0.0) -- price_update_notifications (17.0.1.0.0) -- purchase_customer_requisition (1.0) -- qc_partner_default_lang -- recursive_tree_view (17.0.0.0.2) -- replace_invoicing_user -- stock_valuation_location (17.0.1.0.0) -- temporary_change_image_size -- theme_durpro -- wazo_integration -- website_geoip_extended diff --git a/account_credit_hold/__init__.py b/account_credit_hold/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/account_credit_hold/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/account_credit_hold/__manifest__.py b/account_credit_hold/__manifest__.py new file mode 100644 index 0000000..e60291f --- /dev/null +++ b/account_credit_hold/__manifest__.py @@ -0,0 +1,20 @@ +{ + "name": "Account Credit Hold", + "version": "17.0.1.1.1", + "summary": "Allows setting clients on credit hold, blocking the ability confirm a new sales order.", + "category": "Accounting/Accounting", + "author": "Bemade Inc.", + "maintainer": "Marc Durepos ", + "website": "http://www.bemade.org", + "license": "LGPL-3", + "depends": ["sale", "account_followup", "stock"], + "data": [ + "views/account_followup_views.xml", + "views/sale_order_views.xml", + "views/res_partner_views.xml", + "views/stock_picking_views.xml", + ], + "demo": [], + "installable": True, + "auto_install": False, +} diff --git a/account_credit_hold/migration18.md b/account_credit_hold/migration18.md new file mode 100644 index 0000000..b6f7efd --- /dev/null +++ b/account_credit_hold/migration18.md @@ -0,0 +1,246 @@ +# Migration vers Odoo 18.0 - Module account_credit_hold + +## Fonctionnalités +- Ajoute un champ "Place on Credit Hold" sur les lignes de suivi de compte (account_followup.followup.line) +- Ajoute des champs et fonctionnalités sur les partenaires: + - postpone_hold_until: Date de report du blocage + - hold_bg: Champ technique pour le statut de blocage + - on_hold: État calculé du blocage de crédit +- Bloque la confirmation des commandes de vente si le client est en blocage de crédit +- Ajoute des indicateurs visuels (ruban rouge) sur: + - Commandes de vente + - Fiches partenaires + - Transferts de stock +- Ajoute des boutons pour mettre/lever le blocage de crédit dans la vue de suivi des comptes + +## Analyse pour la Migration + +### Dépendances +- sale +- account_followup +- stock + +### Changements Techniques Requis +1. Mettre à jour la version dans __manifest__.py vers 18.0 +2. Vérifier la compatibilité des vues XML avec Odoo 18.0 +3. Vérifier si des changements dans l'API account_followup en 18.0 + +### Points d'Attention +1. Le module utilise l'héritage de vues et de modèles standard d'Odoo: + - account_followup.followup.line + - res.partner + - sale.order + - stock.picking + - account.followup.report + +2. Fonctionnalités critiques à tester après migration: + - Calcul automatique du statut on_hold + - Blocage de la confirmation des commandes + - Nettoyage automatique des reports de blocage expirés (@api.autovacuum) + - Affichage correct des rubans d'avertissement + - Propagation du statut hold aux contacts liés (commercial_partner_id) + +3. Implémentation Technique: + - Utilisation de champs computed avec store=True et compute_sudo=True + - Mécanisme de nettoyage automatique via @api.autovacuum + - Héritage de _execute_followup_partner pour automatisation du hold + - Messages de chatter automatiques lors des changements de statut + +4. Points Spécifiques aux Vues: + - Utilisation du widget web_ribbon pour les indicateurs visuels + - Boutons conditionnels dans la vue de suivi des comptes + - Champs invisibles pour la logique d'affichage (hold_bg, on_hold) + - Groupes de sécurité sur le champ postpone_hold_until + +## Questions et Considérations + +1. Vérifier si Odoo 18.0 n'a pas introduit des fonctionnalités natives similaires dans account_followup: + - Système de blocage automatique des clients + - Gestion des périodes de grâce + - Indicateurs visuels de blocage + +2. Points à valider: + - La structure des vues héritées est-elle identique en 18.0? + - Les champs related et computed fonctionnent-ils de la même manière? + - Le système de suivi des comptes (account_followup) a-t-il évolué? + - Le décorateur @api.autovacuum est-il toujours supporté? + - Le widget web_ribbon utilise-t-il toujours la même API? + +3. Considérations d'Architecture: + - Le mécanisme de propagation du statut hold via commercial_partner_id est-il optimal? + - Possibilité de simplifier la logique de calcul du statut hold? + - Pertinence de stocker le champ hold_bg vs calcul à la demande + +4. Alternatives Potentielles: + - Utiliser le système de credit limit natif d'Odoo avec des règles personnalisées? + - Intégrer avec le système de blocage des partenaires d'Odoo? + - Utiliser les étapes de facturation (invoice_status) plutôt qu'un champ séparé? + +## Alternatives Natives Odoo 18.0 + +### Système de Crédit Natif +1. Odoo 18.0 inclut des fonctionnalités natives de gestion de crédit: + - Champ `credit_limit` sur res.partner + - Configuration du blocage au niveau de la société + - Règles de blocage basées sur: + - Montant de crédit maximum + - Factures échues + - Âge des factures + +2. Possibilités d'utilisation des fonctionnalités natives: + - Utiliser `credit_limit` au lieu de `on_hold` + - Configurer les règles de blocage dans la configuration de la comptabilité + - Utiliser les notifications natives de dépassement de crédit + +### Améliorations Possibles +1. Intégration avec le système natif: + - Synchroniser notre `on_hold` avec le système natif de blocage + - Utiliser les API natives de vérification de crédit + - Conserver uniquement les fonctionnalités non disponibles nativement + +2. Simplification du code: + - Remplacer les champs custom par des champs natifs quand possible + - Utiliser le système d'alertes natif pour les rubans + - Intégrer avec le système de workflow natif + +## Recommandations pour la Migration + +### Approche "Vanilla First" +1. Évaluer chaque fonctionnalité custom: + - Est-elle disponible nativement dans Odoo 18.0? + - Peut-elle être remplacée par une configuration native? + - Le besoin business existe-t-il toujours? + +2. Prioriser l'utilisation des fonctionnalités natives: + - Système de crédit natif + - Système de workflow natif + - API de notification standard + - Widgets standards de l'interface + +### Modifications Techniques Recommandées +1. Remplacer les attributs obsolètes: + - Supprimer les `attrs` dans les vues (Odoo 16.0+) + - Utiliser `list` au lieu de `tree` (Odoo 17.0+) + - Adapter les widgets aux nouvelles conventions + +2. Optimisation des performances: + - Utiliser les indexes de base de données appropriés + - Optimiser les recherches et calculs + - Implémenter le lazy loading quand possible + +### Plan de Test Approfondi +1. Tests fonctionnels: + - Validation du comportement avec le système natif + - Tests de régression sur les fonctionnalités custom + - Vérification des performances + +2. Tests d'intégration: + - Interaction avec le workflow de vente + - Synchronisation avec la comptabilité + - Comportement avec les autres modules + +## État de la Migration +⚪ En analyse préliminaire + +## Plan de Migration + +### Étape 1: Analyse des Changements Odoo 18.0 +- [ ] Examiner les changements dans account_followup +- [ ] Vérifier les nouvelles fonctionnalités de gestion de crédit +- [ ] Analyser les modifications des vues héritées + +### Étape 2: Adaptation Technique +- [ ] Mise à jour du manifeste +- [ ] Vérification de la compatibilité des décorateurs +- [ ] Adaptation des vues XML si nécessaire +- [ ] Test des champs computed et related + +### Étape 3: Tests Fonctionnels +- [ ] Validation du mécanisme de hold +- [ ] Test de la propagation aux contacts +- [ ] Vérification des nettoyages automatiques +- [ ] Test des indicateurs visuels + +### Étape 4: Optimisation +- [ ] Évaluation des alternatives natives +- [ ] Simplification potentielle du code +- [ ] Amélioration des performances + +## Notes de Version +- Version originale: 17.0.1.1.1 +- Dernière analyse: 26/01/2025 + +## Fonctionnalités Natives dans Odoo 18.0 + +Odoo 18.0 inclut nativement plusieurs fonctionnalités de gestion du crédit : + +1. **Gestion des Limites de Crédit** + - Champ `credit_limit` sur les partenaires + - Champ `use_partner_credit_limit` pour activer/désactiver par partenaire + - Configuration globale `account_use_credit_limit` au niveau de la société + - Champ `credit` pour le total des créances + - Champ `trust` pour le niveau de confiance du débiteur + +2. **Visibilité et Contrôle** + - Champ `show_credit_limit` basé sur la configuration de la société + - Groupes de sécurité pour la gestion des limites de crédit + +### Différences avec Notre Module + +1. **Fonctionnalités à Migrer** + - [ ] Indicateurs visuels spécifiques pour les clients en dépassement + - [ ] Blocage automatique des commandes en dépassement + - [ ] Workflow d'approbation personnalisé + +2. **Fonctionnalités à Adapter** + - [ ] Utiliser les champs natifs plutôt que nos champs customs + - [ ] Intégrer nos règles de blocage avec le système natif + - [ ] Adapter les rapports et vues pour utiliser les champs natifs + +## Plan de Migration + +### Phase 1 : Préparation +1. **Analyse des Données** + - [ ] Identifier les clients avec des limites de crédit + - [ ] Mapper les champs actuels vers les champs natifs + - [ ] Lister les règles de blocage personnalisées + +2. **Configuration** + - [ ] Activer la gestion du crédit dans la configuration de la société + - [ ] Configurer les groupes de sécurité appropriés + - [ ] Préparer les scripts de migration des données + +### Phase 2 : Migration +1. **Migration des Données** + - [ ] Transférer les limites de crédit vers le champ natif + - [ ] Migrer les configurations de blocage + - [ ] Mettre à jour les vues et rapports + +2. **Développement** + - [ ] Adapter le code de blocage des commandes + - [ ] Implémenter les indicateurs visuels manquants + - [ ] Ajouter les fonctionnalités spécifiques non disponibles nativement + +### Phase 3 : Tests +1. **Validation Fonctionnelle** + - [ ] Tester les limites de crédit + - [ ] Vérifier le blocage des commandes + - [ ] Valider les workflows d'approbation + +2. **Tests d'Intégration** + - [ ] Tester avec les autres modules + - [ ] Vérifier la compatibilité avec les processus existants + +## État de la Migration +🟡 En cours d'analyse - Utilisation partielle des fonctionnalités natives + +## Notes Importantes +- La gestion du crédit est maintenant une fonctionnalité native d'Odoo +- Certaines fonctionnalités spécifiques devront être maintenues +- L'approche recommandée est d'utiliser au maximum les fonctionnalités natives et de ne conserver que les extensions nécessaires + +## Prochaines Étapes +1. Valider l'approche avec l'équipe +2. Créer les scripts de migration des données +3. Développer les fonctionnalités manquantes +4. Planifier la formation des utilisateurs \ No newline at end of file diff --git a/account_credit_hold/models/__init__.py b/account_credit_hold/models/__init__.py new file mode 100644 index 0000000..0c2c11c --- /dev/null +++ b/account_credit_hold/models/__init__.py @@ -0,0 +1,5 @@ +from . import account_followup +from . import res_partner +from . import sale_order +from . import stock_picking +from . import account_followup_report diff --git a/account_credit_hold/models/account_followup.py b/account_credit_hold/models/account_followup.py new file mode 100644 index 0000000..5af3363 --- /dev/null +++ b/account_credit_hold/models/account_followup.py @@ -0,0 +1,8 @@ +from odoo import fields, models, api + + +class FollowupLine(models.Model): + _inherit = 'account_followup.followup.line' + + account_hold = fields.Boolean(string="Place on Credit Hold", + help="Place clients on account hold, restricting confirmation of new orders.") diff --git a/account_credit_hold/models/account_followup_report.py b/account_credit_hold/models/account_followup_report.py new file mode 100644 index 0000000..a28efdc --- /dev/null +++ b/account_credit_hold/models/account_followup_report.py @@ -0,0 +1,12 @@ +from odoo import models, fields, api, _ + + +class FollowUpReport(models.AbstractModel): + _inherit = 'account.followup.report' + + def _get_line_info(self, followup_line): + res = super()._get_line_info(followup_line) + res.update({ + 'credit_hold': followup_line.account_hold + }) + return res diff --git a/account_credit_hold/models/res_partner.py b/account_credit_hold/models/res_partner.py new file mode 100644 index 0000000..7cdf9a1 --- /dev/null +++ b/account_credit_hold/models/res_partner.py @@ -0,0 +1,80 @@ +from odoo import fields, models, api, _ +from datetime import date + + +class Partner(models.Model): + _inherit = "res.partner" + + postpone_hold_until = fields.Date( + string="Postpone Hold", + help="Grace period specific to this partner despite unpaid invoices.", + tracking=True, + ) + + hold_bg = fields.Boolean( + string="Hold (technical)", + compute="_compute_hold_bg", + store=True, + default=False, + compute_sudo=True, + tracking=True, + ) + on_hold = fields.Boolean( + string="Account on Hold", + help="Client account is on hold for unpaid overdue invoices.", + compute="_compute_on_hold", + compute_sudo=True, + ) + + @api.depends("postpone_hold_until", "hold_bg") + def _compute_on_hold(self): + # manually re-compute hold_bg since followup_status doesn't get updated in Python but gets recalculated + # by an SQL query every time + self._compute_hold_bg() + for rec in self: + # If the parent company is on hold, so are all its sub-contacts and subsidiaries + if rec.commercial_partner_id and rec.commercial_partner_id.on_hold: + rec.on_hold = True + return + # If there is no parent company or the parent is not on hold, we compute for ourselves + if rec.hold_bg and not ( + rec.postpone_hold_until and rec.postpone_hold_until > date.today() + ): + rec.on_hold = True + else: + if rec.on_hold: + rec.message_post(_("Credit hold lifted.")) + rec.on_hold = False + + @api.autovacuum + def _cleanup_expired_hold_postponements(self): + expired_holds = self.search([("postpone_hold_until", "<=", date.today())]) + expired_holds.write({"postpone_hold_until": False}) + + def action_credit_hold(self): + for rec in self: + rec.hold_bg = True + rec.message_post(body=_("Placed on credit hold.")) + + def action_lift_credit_hold(self): + for rec in self: + rec.hold_bg = False + rec.message_post(body=_("Credit hold lifted.")) + + def _execute_followup_partner(self, options=None): + res = super()._execute_followup_partner(options) + if self.followup_status == "in_need_of_action": + if self.followup_line_id.account_hold: + self.action_credit_hold() + return res + + @api.depends("followup_status", "followup_line_id") + def _compute_hold_bg(self): + first_followup_level = self._get_first_followup_level() + for rec in self: + prev_hold_bg = rec.hold_bg + level = rec.followup_line_id + if rec.followup_status == "no_action_needed" and not level: + rec.hold_bg = False + else: + rec.hold_bg = prev_hold_bg diff --git a/account_credit_hold/models/sale_order.py b/account_credit_hold/models/sale_order.py new file mode 100644 index 0000000..390e97e --- /dev/null +++ b/account_credit_hold/models/sale_order.py @@ -0,0 +1,17 @@ +from odoo import fields, models, api, _ +from odoo.exceptions import UserError + + +class SaleOrder(models.Model): + _inherit = "sale.order" + + client_on_hold = fields.Boolean(string='Client on Hold', + help="Whether or not a client has been put on hold due to unpaid invoices.", + related="partner_id.on_hold") + + @api.depends('client_on_hold') + def action_confirm(self): + if any(self.mapped('client_on_hold')): + raise UserError(_("This client is on credit hold. No new orders can be confirmed until past-due invoices " + "are paid or the accounting team postpones the hold.")) + super().action_confirm() diff --git a/account_credit_hold/models/stock_picking.py b/account_credit_hold/models/stock_picking.py new file mode 100644 index 0000000..0d46e32 --- /dev/null +++ b/account_credit_hold/models/stock_picking.py @@ -0,0 +1,9 @@ +from odoo import fields, models, api + + +class ModelName(models.Model): + _inherit = "stock.picking" + + client_on_hold = fields.Boolean(string='Client on Hold', + help="Whether or not a client has been put on hold due to unpaid invoices.", + related="partner_id.on_hold") diff --git a/account_credit_hold/readme.md b/account_credit_hold/readme.md new file mode 100644 index 0000000..db624c3 --- /dev/null +++ b/account_credit_hold/readme.md @@ -0,0 +1,40 @@ +# Overview + +This module adds the notion of placing clients on credit hold to the followup levels from the Odoo Enterprise +account_followup module. It adds an option to followup levels to mark clients matching the followup criteria as on +credit hold. This hold restricts the confirmation of new sales orders for these clients. + +Accountant and admin users can set a date until which the account hold will be +postponed on a specific partner's form view. This effectively gives clients an extra +grace period, allowing orders to be confirmed until the period ends. + +# Change Log + +## 17.0.1.0.0 (2024-05-15) + +Various modifications to adapt code to Odoo 17.0 + +## 15.0.2.0.0 (2023-05-04) + +Complete remake of the module, making the "Credit Hold" an action that is either manually or +automatically triggered from the Accounting > Followup Reports section or by setting the automatic application field +on followup levels. + +## 15.0.1.1.0 (2023-05-03) + +Adds a ribbon to stock pickings for clients on hold, and therefore a dependency on stock. + +## 15.0.1.0.2 (2023-05-03) + +Fix to sale order view and sale order confirmation for clients not on hold. + +## 15.0.1.0.1 (2023-05-03) + +Fix clients on hold when status is "outstanding_invoices". + +## 15.0.1.0.0 (2023-05-02) Initial Release + +Initial release of the module, including a setting on follow-up levels to toggle placing on credit hold. Blocks +the confirmation of sales orders for clients on credit hold. Red "Credit Hold" banner appears on sales orders and +partner form view when a client is on credit hold. Credit hold can be postponed by setting the "Postpone Hold" field +on the partner form view. \ No newline at end of file diff --git a/account_credit_hold/views/account_followup_views.xml b/account_credit_hold/views/account_followup_views.xml new file mode 100644 index 0000000..962cd79 --- /dev/null +++ b/account_credit_hold/views/account_followup_views.xml @@ -0,0 +1,49 @@ + + + + account_credit_hold.account_followup_line.form + account_followup.followup.line + + + + + + + + customer.statements.form.view.inherit + res.partner + + + + + + +
+
+ + + + + + + + + + + + + + + + + + +
+
+ + + + ai.model.search + ai.model + + + + + + + + + + + + + + + + + + + AI Models + ai.model + list,form + + {'search_default_group_by_provider': 1} + +

+ No AI models found +

+

+ AI models will be automatically synchronized when you configure and sync a provider instance. +

+
+
+ + +
diff --git a/ai_integration/views/ai_provider_instance_views.xml b/ai_integration/views/ai_provider_instance_views.xml new file mode 100644 index 0000000..a3bc669 --- /dev/null +++ b/ai_integration/views/ai_provider_instance_views.xml @@ -0,0 +1,107 @@ + + + + + ai.provider.instance.list + ai.provider.instance + + + + + + + + + + + + + ai.provider.instance.form + ai.provider.instance + +
+
+
+ +
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + + ai.provider.instance.search + ai.provider.instance + + + + + + + + + + + + + + + + AI Provider Instances + ai.provider.instance + list,form + + {'company_id': False} + +

+ Create your first AI provider instance +

+

+ Configure AI provider instances to connect to different AI services. +

+
+
+ + +
diff --git a/ai_integration/views/ai_provider_views.xml b/ai_integration/views/ai_provider_views.xml new file mode 100644 index 0000000..7579605 --- /dev/null +++ b/ai_integration/views/ai_provider_views.xml @@ -0,0 +1,88 @@ + + + + + ai.provider.tree + ai.provider + list + + + + + + + + + + + + + ai.provider.form + ai.provider + form + +
+ +
+
+
+
+ + + + + + + + + + + + + + +
+
+
+
+ + + + ai.provider.search + ai.provider + search + + + + + + + + + + + + + AI Providers + ai.provider + list,form + {'search_default_active': 1} + +

+ No AI providers found +

+

+ AI providers are automatically created when you install AI integration modules. +

+
+
+ +
diff --git a/ai_integration/views/menu.xml b/ai_integration/views/menu.xml new file mode 100644 index 0000000..56a0198 --- /dev/null +++ b/ai_integration/views/menu.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ai_integration/views/res_company_views.xml b/ai_integration/views/res_company_views.xml new file mode 100644 index 0000000..6349ecb --- /dev/null +++ b/ai_integration/views/res_company_views.xml @@ -0,0 +1,14 @@ + + + + + AI Integration + ir.actions.act_window + res.config.settings + form + inline + {'module': 'ai_integration'} + + + + diff --git a/ai_integration/views/res_config_settings_views.xml b/ai_integration/views/res_config_settings_views.xml new file mode 100644 index 0000000..953574f --- /dev/null +++ b/ai_integration/views/res_config_settings_views.xml @@ -0,0 +1,66 @@ + + + + + AI Integration Settings + ir.actions.act_window + res.config.settings + form + inline + {"module" : "ai_integration"} + + + + res.config.settings.view.form.inherit.ai.integration + res.config.settings + + + +
+

AI Integration

+
+
+
+
+ Default Provider Configuration +
+ Configure the default AI provider instance and model for this company +
+
+
+
+
+
+
+
+
+ +
+
+
+ Batch Processing +
+ Configure batch processing parameters for AI operations +
+
+
+
+
+
+
+
+ + +
+ + + + diff --git a/ai_integration_ollama_api/__init__.py b/ai_integration_ollama_api/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/ai_integration_ollama_api/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/ai_integration_ollama_api/__manifest__.py b/ai_integration_ollama_api/__manifest__.py new file mode 100644 index 0000000..e119cbc --- /dev/null +++ b/ai_integration_ollama_api/__manifest__.py @@ -0,0 +1,36 @@ +{ + 'name': 'Ollama Integration', + 'version': '1.0.0', + 'category': 'Technical', + 'summary': 'Integration with Ollama AI models', + 'description': """ +Ollama Integration +================== +This module provides integration with Ollama, allowing you to use local AI models +in your Odoo instance. Features include: + +* Connection to local Ollama server +* Support for all Ollama models +* Automatic model discovery and synchronization +* Configurable model parameters +""", + 'author': 'Bemade', + 'website': 'https://www.bemade.org', + 'depends': [ + 'ai_integration' + ], + 'data': [ + 'data/ai_provider_data.xml', + 'data/ai_provider_instance_data.xml', + 'views/ollama_stats_views.xml', + 'views/ai_provider_instance_views.xml', + 'security/ir.model.access.csv', + ], + 'external_dependencies': { + 'python': ['requests'], + }, + 'installable': True, + 'application': False, + 'auto_install': False, + 'license': 'LGPL-3', +} diff --git a/ai_integration_ollama_api/data/ai_provider_data.xml b/ai_integration_ollama_api/data/ai_provider_data.xml new file mode 100644 index 0000000..fc7228b --- /dev/null +++ b/ai_integration_ollama_api/data/ai_provider_data.xml @@ -0,0 +1,13 @@ + + + + + + Ollama + ollama + Ollama is a local AI model provider that allows you to run various open-source models locally. + http://localhost:11434 + + + + diff --git a/ai_integration_ollama_api/data/ai_provider_instance_data.xml b/ai_integration_ollama_api/data/ai_provider_instance_data.xml new file mode 100644 index 0000000..7097bb4 --- /dev/null +++ b/ai_integration_ollama_api/data/ai_provider_instance_data.xml @@ -0,0 +1,31 @@ + + + + + + Ollama Local + ollama + http://localhost:11434 + llama3.2 + 0.7 + 40 + 0.9 + 1.1 + 4096 + 1024 + 0.05 + 64 + 0 + 1 + 8 + 0 + 5.0 + 0.1 + 8 + 0 + 1.0 + + + + + diff --git a/ai_integration_ollama_api/data/ir_model_inherit.xml b/ai_integration_ollama_api/data/ir_model_inherit.xml new file mode 100644 index 0000000..a1c3091 --- /dev/null +++ b/ai_integration_ollama_api/data/ir_model_inherit.xml @@ -0,0 +1,10 @@ + + + + + + ai.provider.ollama + + + + diff --git a/ai_integration_ollama_api/data/ollama_provider.xml b/ai_integration_ollama_api/data/ollama_provider.xml new file mode 100644 index 0000000..03a101a --- /dev/null +++ b/ai_integration_ollama_api/data/ollama_provider.xml @@ -0,0 +1,13 @@ + + + + + + Ollama + ollama + Local AI models powered by Ollama + http://localhost:11434 + + + + diff --git a/ai_integration_ollama_api/doc/ollama_api.md b/ai_integration_ollama_api/doc/ollama_api.md new file mode 100644 index 0000000..603b15b --- /dev/null +++ b/ai_integration_ollama_api/doc/ollama_api.md @@ -0,0 +1,1627 @@ +# API + +## Endpoints + +- [Generate a completion](#generate-a-completion) +- [Generate a chat completion](#generate-a-chat-completion) +- [Create a Model](#create-a-model) +- [List Local Models](#list-local-models) +- [Show Model Information](#show-model-information) +- [Copy a Model](#copy-a-model) +- [Delete a Model](#delete-a-model) +- [Pull a Model](#pull-a-model) +- [Push a Model](#push-a-model) +- [Generate Embeddings](#generate-embeddings) +- [List Running Models](#list-running-models) +- [Version](#version) + +## Conventions + +### Model names + +Model names follow a `model:tag` format, where `model` can have an optional namespace such as `example/model`. Some examples are `orca-mini:3b-q4_1` and `llama3:70b`. The tag is optional and, if not provided, will default to `latest`. The tag is used to identify a specific version. + +### Durations + +All durations are returned in nanoseconds. + +### Streaming responses + +Certain endpoints stream responses as JSON objects. Streaming can be disabled by providing `{"stream": false}` for these endpoints. + +## Generate a completion + +``` +POST /api/generate +``` + +Generate a response for a given prompt with a provided model. This is a streaming endpoint, so there will be a series of responses. The final response object will include statistics and additional data from the request. + +### Parameters + +- `model`: (required) the [model name](#model-names) +- `prompt`: the prompt to generate a response for +- `suffix`: the text after the model response +- `images`: (optional) a list of base64-encoded images (for multimodal models such as `llava`) + +Advanced parameters (optional): + +- `format`: the format to return a response in. Format can be `json` or a JSON schema +- `options`: additional model parameters listed in the documentation for the [Modelfile](./modelfile.md#valid-parameters-and-values) such as `temperature` +- `system`: system message to (overrides what is defined in the `Modelfile`) +- `template`: the prompt template to use (overrides what is defined in the `Modelfile`) +- `stream`: if `false` the response will be returned as a single response object, rather than a stream of objects +- `raw`: if `true` no formatting will be applied to the prompt. You may choose to use the `raw` parameter if you are specifying a full templated prompt in your request to the API +- `keep_alive`: controls how long the model will stay loaded into memory following the request (default: `5m`) +- `context` (deprecated): the context parameter returned from a previous request to `/generate`, this can be used to keep a short conversational memory + +#### Structured outputs + +Structured outputs are supported by providing a JSON schema in the `format` parameter. The model will generate a response that matches the schema. See the [structured outputs](#request-structured-outputs) example below. + +#### JSON mode + +Enable JSON mode by setting the `format` parameter to `json`. This will structure the response as a valid JSON object. See the JSON mode [example](#request-json-mode) below. + +> [!IMPORTANT] +> It's important to instruct the model to use JSON in the `prompt`. Otherwise, the model may generate large amounts whitespace. + +### Examples + +#### Generate request (Streaming) + +##### Request + +```shell +curl http://localhost:11434/api/generate -d '{ + "model": "llama3.2", + "prompt": "Why is the sky blue?" +}' +``` + +##### Response + +A stream of JSON objects is returned: + +```json +{ + "model": "llama3.2", + "created_at": "2023-08-04T08:52:19.385406455-07:00", + "response": "The", + "done": false +} +``` + +The final response in the stream also includes additional data about the generation: + +- `total_duration`: time spent generating the response +- `load_duration`: time spent in nanoseconds loading the model +- `prompt_eval_count`: number of tokens in the prompt +- `prompt_eval_duration`: time spent in nanoseconds evaluating the prompt +- `eval_count`: number of tokens in the response +- `eval_duration`: time in nanoseconds spent generating the response +- `context`: an encoding of the conversation used in this response, this can be sent in the next request to keep a conversational memory +- `response`: empty if the response was streamed, if not streamed, this will contain the full response + +To calculate how fast the response is generated in tokens per second (token/s), divide `eval_count` / `eval_duration` * `10^9`. + +```json +{ + "model": "llama3.2", + "created_at": "2023-08-04T19:22:45.499127Z", + "response": "", + "done": true, + "context": [1, 2, 3], + "total_duration": 10706818083, + "load_duration": 6338219291, + "prompt_eval_count": 26, + "prompt_eval_duration": 130079000, + "eval_count": 259, + "eval_duration": 4232710000 +} +``` + +#### Request (No streaming) + +##### Request + +A response can be received in one reply when streaming is off. + +```shell +curl http://localhost:11434/api/generate -d '{ + "model": "llama3.2", + "prompt": "Why is the sky blue?", + "stream": false +}' +``` + +##### Response + +If `stream` is set to `false`, the response will be a single JSON object: + +```json +{ + "model": "llama3.2", + "created_at": "2023-08-04T19:22:45.499127Z", + "response": "The sky is blue because it is the color of the sky.", + "done": true, + "context": [1, 2, 3], + "total_duration": 5043500667, + "load_duration": 5025959, + "prompt_eval_count": 26, + "prompt_eval_duration": 325953000, + "eval_count": 290, + "eval_duration": 4709213000 +} +``` + +#### Request (with suffix) + +##### Request + +```shell +curl http://localhost:11434/api/generate -d '{ + "model": "codellama:code", + "prompt": "def compute_gcd(a, b):", + "suffix": " return result", + "options": { + "temperature": 0 + }, + "stream": false +}' +``` + +##### Response + +```json +{ + "model": "codellama:code", + "created_at": "2024-07-22T20:47:51.147561Z", + "response": "\n if a == 0:\n return b\n else:\n return compute_gcd(b % a, a)\n\ndef compute_lcm(a, b):\n result = (a * b) / compute_gcd(a, b)\n", + "done": true, + "done_reason": "stop", + "context": [...], + "total_duration": 1162761250, + "load_duration": 6683708, + "prompt_eval_count": 17, + "prompt_eval_duration": 201222000, + "eval_count": 63, + "eval_duration": 953997000 +} +``` + +#### Request (Structured outputs) + +##### Request + +```shell +curl -X POST http://localhost:11434/api/generate -H "Content-Type: application/json" -d '{ + "model": "llama3.1:8b", + "prompt": "Ollama is 22 years old and is busy saving the world. Respond using JSON", + "stream": false, + "format": { + "type": "object", + "properties": { + "age": { + "type": "integer" + }, + "available": { + "type": "boolean" + } + }, + "required": [ + "age", + "available" + ] + } +}' +``` + +##### Response + +```json +{ + "model": "llama3.1:8b", + "created_at": "2024-12-06T00:48:09.983619Z", + "response": "{\n \"age\": 22,\n \"available\": true\n}", + "done": true, + "done_reason": "stop", + "context": [1, 2, 3], + "total_duration": 1075509083, + "load_duration": 567678166, + "prompt_eval_count": 28, + "prompt_eval_duration": 236000000, + "eval_count": 16, + "eval_duration": 269000000 +} +``` + +#### Request (JSON mode) + +> [!IMPORTANT] +> When `format` is set to `json`, the output will always be a well-formed JSON object. It's important to also instruct the model to respond in JSON. + +##### Request + +```shell +curl http://localhost:11434/api/generate -d '{ + "model": "llama3.2", + "prompt": "What color is the sky at different times of the day? Respond using JSON", + "format": "json", + "stream": false +}' +``` + +##### Response + +```json +{ + "model": "llama3.2", + "created_at": "2023-11-09T21:07:55.186497Z", + "response": "{\n\"morning\": {\n\"color\": \"blue\"\n},\n\"noon\": {\n\"color\": \"blue-gray\"\n},\n\"afternoon\": {\n\"color\": \"warm gray\"\n},\n\"evening\": {\n\"color\": \"orange\"\n}\n}\n", + "done": true, + "context": [1, 2, 3], + "total_duration": 4648158584, + "load_duration": 4071084, + "prompt_eval_count": 36, + "prompt_eval_duration": 439038000, + "eval_count": 180, + "eval_duration": 4196918000 +} +``` + +The value of `response` will be a string containing JSON similar to: + +```json +{ + "morning": { + "color": "blue" + }, + "noon": { + "color": "blue-gray" + }, + "afternoon": { + "color": "warm gray" + }, + "evening": { + "color": "orange" + } +} +``` + +#### Request (with images) + +To submit images to multimodal models such as `llava` or `bakllava`, provide a list of base64-encoded `images`: + +#### Request + +```shell +curl http://localhost:11434/api/generate -d '{ + "model": "llava", + "prompt":"What is in this picture?", + "stream": false, + "images": ["iVBORw0KGgoAAAANSUhEUgAAAG0AAABmCAYAAADBPx+VAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAA3VSURBVHgB7Z27r0zdG8fX743i1bi1ikMoFMQloXRpKFFIqI7LH4BEQ+NWIkjQuSWCRIEoULk0gsK1kCBI0IhrQVT7tz/7zZo888yz1r7MnDl7z5xvsjkzs2fP3uu71nNfa7lkAsm7d++Sffv2JbNmzUqcc8m0adOSzZs3Z+/XES4ZckAWJEGWPiCxjsQNLWmQsWjRIpMseaxcuTKpG/7HP27I8P79e7dq1ars/yL4/v27S0ejqwv+cUOGEGGpKHR37tzJCEpHV9tnT58+dXXCJDdECBE2Ojrqjh071hpNECjx4cMHVycM1Uhbv359B2F79+51586daxN/+pyRkRFXKyRDAqxEp4yMlDDzXG1NPnnyJKkThoK0VFd1ELZu3TrzXKxKfW7dMBQ6bcuWLW2v0VlHjx41z717927ba22U9APcw7Nnz1oGEPeL3m3p2mTAYYnFmMOMXybPPXv2bNIPpFZr1NHn4HMw0KRBjg9NuRw95s8PEcz/6DZELQd/09C9QGq5RsmSRybqkwHGjh07OsJSsYYm3ijPpyHzoiacg35MLdDSIS/O1yM778jOTwYUkKNHWUzUWaOsylE00MyI0fcnOwIdjvtNdW/HZwNLGg+sR1kMepSNJXmIwxBZiG8tDTpEZzKg0GItNsosY8USkxDhD0Rinuiko2gfL/RbiD2LZAjU9zKQJj8RDR0vJBR1/Phx9+PHj9Z7REF4nTZkxzX4LCXHrV271qXkBAPGfP/atWvu/PnzHe4C97F48eIsRLZ9+3a3f/9+87dwP1JxaF7/3r17ba+5l4EcaVo0lj3SBq5kGTJSQmLWMjgYNei2GPT1MuMqGTDEFHzeQSP2wi/jGnkmPJ/nhccs44jvDAxpVcxnq0F6eT8h4ni/iIWpR5lPyA6ETkNXoSukvpJAD3AsXLiwpZs49+fPn5ke4j10TqYvegSfn0OnafC+Tv9ooA/JPkgQysqQNBzagXY55nO/oa1F7qvIPWkRL12WRpMWUvpVDYmxAPehxWSe8ZEXL20sadYIozfmNch4QJPAfeJgW3rNsnzphBKNJM2KKODo1rVOMRYik5ETy3ix4qWNI81qAAirizgMIc+yhTytx0JWZuNI03qsrgWlGtwjoS9XwgUhWGyhUaRZZQNNIEwCiXD16tXcAHUs79co0vSD8rrJCIW98pzvxpAWyyo3HYwqS0+H0BjStClcZJT5coMm6D2LOF8TolGJtK9fvyZpyiC5ePFi9nc/oJU4eiEP0jVoAnHa9wyJycITMP78+eMeP37sXrx44d6+fdt6f82aNdkx1pg9e3Zb5W+RSRE+n+VjksQWifvVaTKFhn5O8my63K8Qabdv33b379/PiAP//vuvW7BggZszZ072/+TJk91YgkafPn166zXB1rQHFvouAWHq9z3SEevSUerqCn2/dDCeta2jxYbr69evk4MHDyY7d+7MjhMnTiTPnz9Pfv/+nfQT2ggpO2dMF8cghuoM7Ygj5iWCqRlGFml0QC/ftGmTmzt3rmsaKDsgBSPh0/8yPeLLBihLkOKJc0jp8H8vUzcxIA1k6QJ/c78tWEyj5P3o4u9+jywNPdJi5rAH9x0KHcl4Hg570eQp3+vHXGyrmEeigzQsQsjavXt38ujRo44LQuDDhw+TW7duRS1HGgMxhNXHgflaNTOsHyKvHK5Ijo2jbFjJBQK9YwFd6RVMzfgRBmEfP37suBBm/p49e1qjEP2mwTViNRo0VJWH1deMXcNK08uUjVUu7s/zRaL+oLNxz1bpANco4npUgX4G2eFbpDFyQoQxojBCpEGSytmOH8qrH5Q9vuzD6ofQylkCUmh8DBAr+q8JCyVNtWQIidKQE9wNtLSQnS4jDSsxNHogzFuQBw4cyM61UKVsjfr3ooBkPSqqQHesUPWVtzi9/vQi1T+rJj7WiTz4Pt/l3LxUkr5P2VYZaZ4URpsE+st/dujQoaBBYokbrz/8TJNQYLSonrPS9kUaSkPeZyj1AWSj+d+VBoy1pIWVNed8P0Ll/ee5HdGRhrHhR5GGN0r4LGZBaj8oFDJitBTJzIZgFcmU0Y8ytWMZMzJOaXUSrUs5RxKnrxmbb5YXO9VGUhtpXldhEUogFr3IzIsvlpmdosVcGVGXFWp2oU9kLFL3dEkSz6NHEY1sjSRdIuDFWEhd8KxFqsRi1uM/nz9/zpxnwlESONdg6dKlbsaMGS4EHFHtjFIDHwKOo46l4TxSuxgDzi+rE2jg+BaFruOX4HXa0Nnf1lwAPufZeF8/r6zD97WK2qFnGjBxTw5qNGPxT+5T/r7/7RawFC3j4vTp09koCxkeHjqbHJqArmH5UrFKKksnxrK7FuRIs8STfBZv+luugXZ2pR/pP9Ois4z+TiMzUUkUjD0iEi1fzX8GmXyuxUBRcaUfykV0YZnlJGKQpOiGB76x5GeWkWWJc3mOrK6S7xdND+W5N6XyaRgtWJFe13GkaZnKOsYqGdOVVVbGupsyA/l7emTLHi7vwTdirNEt0qxnzAvBFcnQF16xh/TMpUuXHDowhlA9vQVraQhkudRdzOnK+04ZSP3DUhVSP61YsaLtd/ks7ZgtPcXqPqEafHkdqa84X6aCeL7YWlv6edGFHb+ZFICPlljHhg0bKuk0CSvVznWsotRu433alNdFrqG45ejoaPCaUkWERpLXjzFL2Rpllp7PJU2a/v7Ab8N05/9t27Z16KUqoFGsxnI9EosS2niSYg9SpU6B4JgTrvVW1flt1sT+0ADIJU2maXzcUTraGCRaL1Wp9rUMk16PMom8QhruxzvZIegJjFU7LLCePfS8uaQdPny4jTTL0dbee5mYokQsXTIWNY46kuMbnt8Kmec+LGWtOVIl9cT1rCB0V8WqkjAsRwta93TbwNYoGKsUSChN44lgBNCoHLHzquYKrU6qZ8lolCIN0Rh6cP0Q3U6I6IXILYOQI513hJaSKAorFpuHXJNfVlpRtmYBk1Su1obZr5dnKAO+L10Hrj3WZW+E3qh6IszE37F6EB+68mGpvKm4eb9bFrlzrok7fvr0Kfv727dvWRmdVTJHw0qiiCUSZ6wCK+7XL/AcsgNyL74DQQ730sv78Su7+t/A36MdY0sW5o40ahslXr58aZ5HtZB8GH64m9EmMZ7FpYw4T6QnrZfgenrhFxaSiSGXtPnz57e9TkNZLvTjeqhr734CNtrK41L40sUQckmj1lGKQ0rC37x544r8eNXRpnVE3ZZY7zXo8NomiO0ZUCj2uHz58rbXoZ6gc0uA+F6ZeKS/jhRDUq8MKrTho9fEkihMmhxtBI1DxKFY9XLpVcSkfoi8JGnToZO5sU5aiDQIW716ddt7ZLYtMQlhECdBGXZZMWldY5BHm5xgAroWj4C0hbYkSc/jBmggIrXJWlZM6pSETsEPGqZOndr2uuuR5rF169a2HoHPdurUKZM4CO1WTPqaDaAd+GFGKdIQkxAn9RuEWcTRyN2KSUgiSgF5aWzPTeA/lN5rZubMmR2bE4SIC4nJoltgAV/dVefZm72AtctUCJU2CMJ327hxY9t7EHbkyJFseq+EJSY16RPo3Dkq1kkr7+q0bNmyDuLQcZBEPYmHVdOBiJyIlrRDq41YPWfXOxUysi5fvtyaj+2BpcnsUV/oSoEMOk2CQGlr4ckhBwaetBhjCwH0ZHtJROPJkyc7UjcYLDjmrH7ADTEBXFfOYmB0k9oYBOjJ8b4aOYSe7QkKcYhFlq3QYLQhSidNmtS2RATwy8YOM3EQJsUjKiaWZ+vZToUQgzhkHXudb/PW5YMHD9yZM2faPsMwoc7RciYJXbGuBqJ1UIGKKLv915jsvgtJxCZDubdXr165mzdvtr1Hz5LONA8jrUwKPqsmVesKa49S3Q4WxmRPUEYdTjgiUcfUwLx589ySJUva3oMkP6IYddq6HMS4o55xBJBUeRjzfa4Zdeg56QZ43LhxoyPo7Lf1kNt7oO8wWAbNwaYjIv5lhyS7kRf96dvm5Jah8vfvX3flyhX35cuX6HfzFHOToS1H4BenCaHvO8pr8iDuwoUL7tevX+b5ZdbBair0xkFIlFDlW4ZknEClsp/TzXyAKVOmmHWFVSbDNw1l1+4f90U6IY/q4V27dpnE9bJ+v87QEydjqx/UamVVPRG+mwkNTYN+9tjkwzEx+atCm/X9WvWtDtAb68Wy9LXa1UmvCDDIpPkyOQ5ZwSzJ4jMrvFcr0rSjOUh+GcT4LSg5ugkW1Io0/SCDQBojh0hPlaJdah+tkVYrnTZowP8iq1F1TgMBBauufyB33x1v+NWFYmT5KmppgHC+NkAgbmRkpD3yn9QIseXymoTQFGQmIOKTxiZIWpvAatenVqRVXf2nTrAWMsPnKrMZHz6bJq5jvce6QK8J1cQNgKxlJapMPdZSR64/UivS9NztpkVEdKcrs5alhhWP9NeqlfWopzhZScI6QxseegZRGeg5a8C3Re1Mfl1ScP36ddcUaMuv24iOJtz7sbUjTS4qBvKmstYJoUauiuD3k5qhyr7QdUHMeCgLa1Ear9NquemdXgmum4fvJ6w1lqsuDhNrg1qSpleJK7K3TF0Q2jSd94uSZ60kK1e3qyVpQK6PVWXp2/FC3mp6jBhKKOiY2h3gtUV64TWM6wDETRPLDfSakXmH3w8g9Jlug8ZtTt4kVF0kLUYYmCCtD/DrQ5YhMGbA9L3ucdjh0y8kOHW5gU/VEEmJTcL4Pz/f7mgoAbYkAAAAAElFTkSuQmCC"] +}' +``` + +#### Response + +```json +{ + "model": "llava", + "created_at": "2023-11-03T15:36:02.583064Z", + "response": "A happy cartoon character, which is cute and cheerful.", + "done": true, + "context": [1, 2, 3], + "total_duration": 2938432250, + "load_duration": 2559292, + "prompt_eval_count": 1, + "prompt_eval_duration": 2195557000, + "eval_count": 44, + "eval_duration": 736432000 +} +``` + +#### Request (Raw Mode) + +In some cases, you may wish to bypass the templating system and provide a full prompt. In this case, you can use the `raw` parameter to disable templating. Also note that raw mode will not return a context. + +##### Request + +```shell +curl http://localhost:11434/api/generate -d '{ + "model": "mistral", + "prompt": "[INST] why is the sky blue? [/INST]", + "raw": true, + "stream": false +}' +``` + +#### Request (Reproducible outputs) + +For reproducible outputs, set `seed` to a number: + +##### Request + +```shell +curl http://localhost:11434/api/generate -d '{ + "model": "mistral", + "prompt": "Why is the sky blue?", + "options": { + "seed": 123 + } +}' +``` + +##### Response + +```json +{ + "model": "mistral", + "created_at": "2023-11-03T15:36:02.583064Z", + "response": " The sky appears blue because of a phenomenon called Rayleigh scattering.", + "done": true, + "total_duration": 8493852375, + "load_duration": 6589624375, + "prompt_eval_count": 14, + "prompt_eval_duration": 119039000, + "eval_count": 110, + "eval_duration": 1779061000 +} +``` + +#### Generate request (With options) + +If you want to set custom options for the model at runtime rather than in the Modelfile, you can do so with the `options` parameter. This example sets every available option, but you can set any of them individually and omit the ones you do not want to override. + +##### Request + +```shell +curl http://localhost:11434/api/generate -d '{ + "model": "llama3.2", + "prompt": "Why is the sky blue?", + "stream": false, + "options": { + "num_keep": 5, + "seed": 42, + "num_predict": 100, + "top_k": 20, + "top_p": 0.9, + "min_p": 0.0, + "typical_p": 0.7, + "repeat_last_n": 33, + "temperature": 0.8, + "repeat_penalty": 1.2, + "presence_penalty": 1.5, + "frequency_penalty": 1.0, + "mirostat": 1, + "mirostat_tau": 0.8, + "mirostat_eta": 0.6, + "penalize_newline": true, + "stop": ["\n", "user:"], + "numa": false, + "num_ctx": 1024, + "num_batch": 2, + "num_gpu": 1, + "main_gpu": 0, + "low_vram": false, + "vocab_only": false, + "use_mmap": true, + "use_mlock": false, + "num_thread": 8 + } +}' +``` + +##### Response + +```json +{ + "model": "llama3.2", + "created_at": "2023-08-04T19:22:45.499127Z", + "response": "The sky is blue because it is the color of the sky.", + "done": true, + "context": [1, 2, 3], + "total_duration": 4935886791, + "load_duration": 534986708, + "prompt_eval_count": 26, + "prompt_eval_duration": 107345000, + "eval_count": 237, + "eval_duration": 4289432000 +} +``` + +#### Load a model + +If an empty prompt is provided, the model will be loaded into memory. + +##### Request + +```shell +curl http://localhost:11434/api/generate -d '{ + "model": "llama3.2" +}' +``` + +##### Response + +A single JSON object is returned: + +```json +{ + "model": "llama3.2", + "created_at": "2023-12-18T19:52:07.071755Z", + "response": "", + "done": true +} +``` + +#### Unload a model + +If an empty prompt is provided and the `keep_alive` parameter is set to `0`, a model will be unloaded from memory. + +##### Request + +```shell +curl http://localhost:11434/api/generate -d '{ + "model": "llama3.2", + "keep_alive": 0 +}' +``` + +##### Response + +A single JSON object is returned: + +```json +{ + "model": "llama3.2", + "created_at": "2024-09-12T03:54:03.516566Z", + "response": "", + "done": true, + "done_reason": "unload" +} +``` + +## Generate a chat completion + +``` +POST /api/chat +``` + +Generate the next message in a chat with a provided model. This is a streaming endpoint, so there will be a series of responses. Streaming can be disabled using `"stream": false`. The final response object will include statistics and additional data from the request. + +### Parameters + +- `model`: (required) the [model name](#model-names) +- `messages`: the messages of the chat, this can be used to keep a chat memory +- `tools`: list of tools in JSON for the model to use if supported + +The `message` object has the following fields: + +- `role`: the role of the message, either `system`, `user`, `assistant`, or `tool` +- `content`: the content of the message +- `images` (optional): a list of images to include in the message (for multimodal models such as `llava`) +- `tool_calls` (optional): a list of tools in JSON that the model wants to use + +Advanced parameters (optional): + +- `format`: the format to return a response in. Format can be `json` or a JSON schema. +- `options`: additional model parameters listed in the documentation for the [Modelfile](./modelfile.md#valid-parameters-and-values) such as `temperature` +- `stream`: if `false` the response will be returned as a single response object, rather than a stream of objects +- `keep_alive`: controls how long the model will stay loaded into memory following the request (default: `5m`) + +### Structured outputs + +Structured outputs are supported by providing a JSON schema in the `format` parameter. The model will generate a response that matches the schema. See the [Chat request (Structured outputs)](#chat-request-structured-outputs) example below. + +### Examples + +#### Chat Request (Streaming) + +##### Request + +Send a chat message with a streaming response. + +```shell +curl http://localhost:11434/api/chat -d '{ + "model": "llama3.2", + "messages": [ + { + "role": "user", + "content": "why is the sky blue?" + } + ] +}' +``` + +##### Response + +A stream of JSON objects is returned: + +```json +{ + "model": "llama3.2", + "created_at": "2023-08-04T08:52:19.385406455-07:00", + "message": { + "role": "assistant", + "content": "The", + "images": null + }, + "done": false +} +``` + +Final response: + +```json +{ + "model": "llama3.2", + "created_at": "2023-08-04T19:22:45.499127Z", + "done": true, + "total_duration": 4883583458, + "load_duration": 1334875, + "prompt_eval_count": 26, + "prompt_eval_duration": 342546000, + "eval_count": 282, + "eval_duration": 4535599000 +} +``` + +#### Chat request (No streaming) + +##### Request + +```shell +curl http://localhost:11434/api/chat -d '{ + "model": "llama3.2", + "messages": [ + { + "role": "user", + "content": "why is the sky blue?" + } + ], + "stream": false +}' +``` + +##### Response + +```json +{ + "model": "llama3.2", + "created_at": "2023-12-12T14:13:43.416799Z", + "message": { + "role": "assistant", + "content": "Hello! How are you today?" + }, + "done": true, + "total_duration": 5191566416, + "load_duration": 2154458, + "prompt_eval_count": 26, + "prompt_eval_duration": 383809000, + "eval_count": 298, + "eval_duration": 4799921000 +} +``` + +#### Chat request (Structured outputs) + +##### Request + +```shell +curl -X POST http://localhost:11434/api/chat -H "Content-Type: application/json" -d '{ + "model": "llama3.1", + "messages": [{"role": "user", "content": "Ollama is 22 years old and busy saving the world. Return a JSON object with the age and availability."}], + "stream": false, + "format": { + "type": "object", + "properties": { + "age": { + "type": "integer" + }, + "available": { + "type": "boolean" + } + }, + "required": [ + "age", + "available" + ] + }, + "options": { + "temperature": 0 + } +}' +``` + +##### Response + +```json +{ + "model": "llama3.1", + "created_at": "2024-12-06T00:46:58.265747Z", + "message": { "role": "assistant", "content": "{\"age\": 22, \"available\": false}" }, + "done_reason": "stop", + "done": true, + "total_duration": 2254970291, + "load_duration": 574751416, + "prompt_eval_count": 34, + "prompt_eval_duration": 1502000000, + "eval_count": 12, + "eval_duration": 175000000 +} +``` + +#### Chat request (With History) + +Send a chat message with a conversation history. You can use this same approach to start the conversation using multi-shot or chain-of-thought prompting. + +##### Request + +```shell +curl http://localhost:11434/api/chat -d '{ + "model": "llama3.2", + "messages": [ + { + "role": "user", + "content": "why is the sky blue?" + }, + { + "role": "assistant", + "content": "due to rayleigh scattering." + }, + { + "role": "user", + "content": "how is that different than mie scattering?" + } + ] +}' +``` + +##### Response + +A stream of JSON objects is returned: + +```json +{ + "model": "llama3.2", + "created_at": "2023-08-04T08:52:19.385406455-07:00", + "message": { + "role": "assistant", + "content": "The" + }, + "done": false +} +``` + +Final response: + +```json +{ + "model": "llama3.2", + "created_at": "2023-08-04T19:22:45.499127Z", + "done": true, + "total_duration": 8113331500, + "load_duration": 6396458, + "prompt_eval_count": 61, + "prompt_eval_duration": 398801000, + "eval_count": 468, + "eval_duration": 7701267000 +} +``` + +#### Chat request (with images) + +##### Request + +Send a chat message with images. The images should be provided as an array, with the individual images encoded in Base64. + +```shell +curl http://localhost:11434/api/chat -d '{ + "model": "llava", + "messages": [ + { + "role": "user", + "content": "what is in this image?", + "images": ["iVBORw0KGgoAAAANSUhEUgAAAG0AAABmCAYAAADBPx+VAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAA3VSURBVHgB7Z27r0zdG8fX743i1bi1ikMoFMQloXRpKFFIqI7LH4BEQ+NWIkjQuSWCRIEoULk0gsK1kCBI0IhrQVT7tz/7zZo888yz1r7MnDl7z5xvsjkzs2fP3uu71nNfa7lkAsm7d++Sffv2JbNmzUqcc8m0adOSzZs3Z+/XES4ZckAWJEGWPiCxjsQNLWmQsWjRIpMseaxcuTKpG/7HP27I8P79e7dq1ars/yL4/v27S0ejqwv+cUOGEGGpKHR37tzJCEpHV9tnT58+dXXCJDdECBE2Ojrqjh071hpNECjx4cMHVycM1Uhbv359B2F79+51586daxN/+pyRkRFXKyRDAqxEp4yMlDDzXG1NPnnyJKkThoK0VFd1ELZu3TrzXKxKfW7dMBQ6bcuWLW2v0VlHjx41z717927ba22U9APcw7Nnz1oGEPeL3m3p2mTAYYnFmMOMXybPPXv2bNIPpFZr1NHn4HMw0KRBjg9NuRw95s8PEcz/6DZELQd/09C9QGq5RsmSRybqkwHGjh07OsJSsYYm3ijPpyHzoiacg35MLdDSIS/O1yM778jOTwYUkKNHWUzUWaOsylE00MyI0fcnOwIdjvtNdW/HZwNLGg+sR1kMepSNJXmIwxBZiG8tDTpEZzKg0GItNsosY8USkxDhD0Rinuiko2gfL/RbiD2LZAjU9zKQJj8RDR0vJBR1/Phx9+PHj9Z7REF4nTZkxzX4LCXHrV271qXkBAPGfP/atWvu/PnzHe4C97F48eIsRLZ9+3a3f/9+87dwP1JxaF7/3r17ba+5l4EcaVo0lj3SBq5kGTJSQmLWMjgYNei2GPT1MuMqGTDEFHzeQSP2wi/jGnkmPJ/nhccs44jvDAxpVcxnq0F6eT8h4ni/iIWpR5lPyA6ETkNXoSukvpJAD3AsXLiwpZs49+fPn5ke4j10TqYvegSfn0OnafC+Tv9ooA/JPkgQysqQNBzagXY55nO/oa1F7qvIPWkRL12WRpMWUvpVDYmxAPehxWSe8ZEXL20sadYIozfmNch4QJPAfeJgW3rNsnzphBKNJM2KKODo1rVOMRYik5ETy3ix4qWNI81qAAirizgMIc+yhTytx0JWZuNI03qsrgWlGtwjoS9XwgUhWGyhUaRZZQNNIEwCiXD16tXcAHUs79co0vSD8rrJCIW98pzvxpAWyyo3HYwqS0+H0BjStClcZJT5coMm6D2LOF8TolGJtK9fvyZpyiC5ePFi9nc/oJU4eiEP0jVoAnHa9wyJycITMP78+eMeP37sXrx44d6+fdt6f82aNdkx1pg9e3Zb5W+RSRE+n+VjksQWifvVaTKFhn5O8my63K8Qabdv33b379/PiAP//vuvW7BggZszZ072/+TJk91YgkafPn166zXB1rQHFvouAWHq9z3SEevSUerqCn2/dDCeta2jxYbr69evk4MHDyY7d+7MjhMnTiTPnz9Pfv/+nfQT2ggpO2dMF8cghuoM7Ygj5iWCqRlGFml0QC/ftGmTmzt3rmsaKDsgBSPh0/8yPeLLBihLkOKJc0jp8H8vUzcxIA1k6QJ/c78tWEyj5P3o4u9+jywNPdJi5rAH9x0KHcl4Hg570eQp3+vHXGyrmEeigzQsQsjavXt38ujRo44LQuDDhw+TW7duRS1HGgMxhNXHgflaNTOsHyKvHK5Ijo2jbFjJBQK9YwFd6RVMzfgRBmEfP37suBBm/p49e1qjEP2mwTViNRo0VJWH1deMXcNK08uUjVUu7s/zRaL+oLNxz1bpANco4npUgX4G2eFbpDFyQoQxojBCpEGSytmOH8qrH5Q9vuzD6ofQylkCUmh8DBAr+q8JCyVNtWQIidKQE9wNtLSQnS4jDSsxNHogzFuQBw4cyM61UKVsjfr3ooBkPSqqQHesUPWVtzi9/vQi1T+rJj7WiTz4Pt/l3LxUkr5P2VYZaZ4URpsE+st/dujQoaBBYokbrz/8TJNQYLSonrPS9kUaSkPeZyj1AWSj+d+VBoy1pIWVNed8P0Ll/ee5HdGRhrHhR5GGN0r4LGZBaj8oFDJitBTJzIZgFcmU0Y8ytWMZMzJOaXUSrUs5RxKnrxmbb5YXO9VGUhtpXldhEUogFr3IzIsvlpmdosVcGVGXFWp2oU9kLFL3dEkSz6NHEY1sjSRdIuDFWEhd8KxFqsRi1uM/nz9/zpxnwlESONdg6dKlbsaMGS4EHFHtjFIDHwKOo46l4TxSuxgDzi+rE2jg+BaFruOX4HXa0Nnf1lwAPufZeF8/r6zD97WK2qFnGjBxTw5qNGPxT+5T/r7/7RawFC3j4vTp09koCxkeHjqbHJqArmH5UrFKKksnxrK7FuRIs8STfBZv+luugXZ2pR/pP9Ois4z+TiMzUUkUjD0iEi1fzX8GmXyuxUBRcaUfykV0YZnlJGKQpOiGB76x5GeWkWWJc3mOrK6S7xdND+W5N6XyaRgtWJFe13GkaZnKOsYqGdOVVVbGupsyA/l7emTLHi7vwTdirNEt0qxnzAvBFcnQF16xh/TMpUuXHDowhlA9vQVraQhkudRdzOnK+04ZSP3DUhVSP61YsaLtd/ks7ZgtPcXqPqEafHkdqa84X6aCeL7YWlv6edGFHb+ZFICPlljHhg0bKuk0CSvVznWsotRu433alNdFrqG45ejoaPCaUkWERpLXjzFL2Rpllp7PJU2a/v7Ab8N05/9t27Z16KUqoFGsxnI9EosS2niSYg9SpU6B4JgTrvVW1flt1sT+0ADIJU2maXzcUTraGCRaL1Wp9rUMk16PMom8QhruxzvZIegJjFU7LLCePfS8uaQdPny4jTTL0dbee5mYokQsXTIWNY46kuMbnt8Kmec+LGWtOVIl9cT1rCB0V8WqkjAsRwta93TbwNYoGKsUSChN44lgBNCoHLHzquYKrU6qZ8lolCIN0Rh6cP0Q3U6I6IXILYOQI513hJaSKAorFpuHXJNfVlpRtmYBk1Su1obZr5dnKAO+L10Hrj3WZW+E3qh6IszE37F6EB+68mGpvKm4eb9bFrlzrok7fvr0Kfv727dvWRmdVTJHw0qiiCUSZ6wCK+7XL/AcsgNyL74DQQ730sv78Su7+t/A36MdY0sW5o40ahslXr58aZ5HtZB8GH64m9EmMZ7FpYw4T6QnrZfgenrhFxaSiSGXtPnz57e9TkNZLvTjeqhr734CNtrK41L40sUQckmj1lGKQ0rC37x544r8eNXRpnVE3ZZY7zXo8NomiO0ZUCj2uHz58rbXoZ6gc0uA+F6ZeKS/jhRDUq8MKrTho9fEkihMmhxtBI1DxKFY9XLpVcSkfoi8JGnToZO5sU5aiDQIW716ddt7ZLYtMQlhECdBGXZZMWldY5BHm5xgAroWj4C0hbYkSc/jBmggIrXJWlZM6pSETsEPGqZOndr2uuuR5rF169a2HoHPdurUKZM4CO1WTPqaDaAd+GFGKdIQkxAn9RuEWcTRyN2KSUgiSgF5aWzPTeA/lN5rZubMmR2bE4SIC4nJoltgAV/dVefZm72AtctUCJU2CMJ327hxY9t7EHbkyJFseq+EJSY16RPo3Dkq1kkr7+q0bNmyDuLQcZBEPYmHVdOBiJyIlrRDq41YPWfXOxUysi5fvtyaj+2BpcnsUV/oSoEMOk2CQGlr4ckhBwaetBhjCwH0ZHtJROPJkyc7UjcYLDjmrH7ADTEBXFfOYmB0k9oYBOjJ8b4aOYSe7QkKcYhFlq3QYLQhSidNmtS2RATwy8YOM3EQJsUjKiaWZ+vZToUQgzhkHXudb/PW5YMHD9yZM2faPsMwoc7RciYJXbGuBqJ1UIGKKLv915jsvgtJxCZDubdXr165mzdvtr1Hz5LONA8jrUwKPqsmVesKa49S3Q4WxmRPUEYdTjgiUcfUwLx589ySJUva3oMkP6IYddq6HMS4o55xBJBUeRjzfa4Zdeg56QZ43LhxoyPo7Lf1kNt7oO8wWAbNwaYjIv5lhyS7kRf96dvm5Jah8vfvX3flyhX35cuX6HfzFHOToS1H4BenCaHvO8pr8iDuwoUL7tevX+b5ZdbBair0xkFIlFDlW4ZknEClsp/TzXyAKVOmmHWFVSbDNw1l1+4f90U6IY/q4V27dpnE9bJ+v87QEydjqx/UamVVPRG+mwkNTYN+9tjkwzEx+atCm/X9WvWtDtAb68Wy9LXa1UmvCDDIpPkyOQ5ZwSzJ4jMrvFcr0rSjOUh+GcT4LSg5ugkW1Io0/SCDQBojh0hPlaJdah+tkVYrnTZowP8iq1F1TgMBBauufyB33x1v+NWFYmT5KmppgHC+NkAgbmRkpD3yn9QIseXymoTQFGQmIOKTxiZIWpvAatenVqRVXf2nTrAWMsPnKrMZHz6bJq5jvce6QK8J1cQNgKxlJapMPdZSR64/UivS9NztpkVEdKcrs5alhhWP9NeqlfWopzhZScI6QxseegZRGeg5a8C3Re1Mfl1ScP36ddcUaMuv24iOJtz7sbUjTS4qBvKmstYJoUauiuD3k5qhyr7QdUHMeCgLa1Ear9NquemdXgmum4fvJ6w1lqsuDhNrg1qSpleJK7K3TF0Q2jSd94uSZ60kK1e3qyVpQK6PVWXp2/FC3mp6jBhKKOiY2h3gtUV64TWM6wDETRPLDfSakXmH3w8g9Jlug8ZtTt4kVF0kLUYYmCCtD/DrQ5YhMGbA9L3ucdjh0y8kOHW5gU/VEEmJTcL4Pz/f7mgoAbYkAAAAAElFTkSuQmCC"] + } + ] +}' +``` + +##### Response + +```json +{ + "model": "llava", + "created_at": "2023-12-13T22:42:50.203334Z", + "message": { + "role": "assistant", + "content": " The image features a cute, little pig with an angry facial expression. It's wearing a heart on its shirt and is waving in the air. This scene appears to be part of a drawing or sketching project.", + "images": null + }, + "done": true, + "total_duration": 1668506709, + "load_duration": 1986209, + "prompt_eval_count": 26, + "prompt_eval_duration": 359682000, + "eval_count": 83, + "eval_duration": 1303285000 +} +``` + +#### Chat request (Reproducible outputs) + +##### Request + +```shell +curl http://localhost:11434/api/chat -d '{ + "model": "llama3.2", + "messages": [ + { + "role": "user", + "content": "Hello!" + } + ], + "options": { + "seed": 101, + "temperature": 0 + } +}' +``` + +##### Response + +```json +{ + "model": "llama3.2", + "created_at": "2023-12-12T14:13:43.416799Z", + "message": { + "role": "assistant", + "content": "Hello! How are you today?" + }, + "done": true, + "total_duration": 5191566416, + "load_duration": 2154458, + "prompt_eval_count": 26, + "prompt_eval_duration": 383809000, + "eval_count": 298, + "eval_duration": 4799921000 +} +``` + +#### Chat request (with tools) + +##### Request + +```shell +curl http://localhost:11434/api/chat -d '{ + "model": "llama3.2", + "messages": [ + { + "role": "user", + "content": "What is the weather today in Paris?" + } + ], + "stream": false, + "tools": [ + { + "type": "function", + "function": { + "name": "get_current_weather", + "description": "Get the current weather for a location", + "parameters": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "The location to get the weather for, e.g. San Francisco, CA" + }, + "format": { + "type": "string", + "description": "The format to return the weather in, e.g. 'celsius' or 'fahrenheit'", + "enum": ["celsius", "fahrenheit"] + } + }, + "required": ["location", "format"] + } + } + } + ] +}' +``` + +##### Response + +```json +{ + "model": "llama3.2", + "created_at": "2024-07-22T20:33:28.123648Z", + "message": { + "role": "assistant", + "content": "", + "tool_calls": [ + { + "function": { + "name": "get_current_weather", + "arguments": { + "format": "celsius", + "location": "Paris, FR" + } + } + } + ] + }, + "done_reason": "stop", + "done": true, + "total_duration": 885095291, + "load_duration": 3753500, + "prompt_eval_count": 122, + "prompt_eval_duration": 328493000, + "eval_count": 33, + "eval_duration": 552222000 +} +``` + +#### Load a model + +If the messages array is empty, the model will be loaded into memory. + +##### Request + +```shell +curl http://localhost:11434/api/chat -d '{ + "model": "llama3.2", + "messages": [] +}' +``` + +##### Response + +```json +{ + "model": "llama3.2", + "created_at":"2024-09-12T21:17:29.110811Z", + "message": { + "role": "assistant", + "content": "" + }, + "done_reason": "load", + "done": true +} +``` + +#### Unload a model + +If the messages array is empty and the `keep_alive` parameter is set to `0`, a model will be unloaded from memory. + +##### Request + +```shell +curl http://localhost:11434/api/chat -d '{ + "model": "llama3.2", + "messages": [], + "keep_alive": 0 +}' +``` + +##### Response + +A single JSON object is returned: + +```json +{ + "model": "llama3.2", + "created_at":"2024-09-12T21:33:17.547535Z", + "message": { + "role": "assistant", + "content": "" + }, + "done_reason": "unload", + "done": true +} +``` + +## Create a Model + +``` +POST /api/create +``` + +Create a model from: + * another model; + * a safetensors directory; or + * a GGUF file. + +If you are creating a model from a safetensors directory or from a GGUF file, you must [create a blob](#create-a-blob) for each of the files and then use the file name and SHA256 digest associated with each blob in the `files` field. + +### Parameters + +- `model`: name of the model to create +- `from`: (optional) name of an existing model to create the new model from +- `files`: (optional) a dictionary of file names to SHA256 digests of blobs to create the model from +- `adapters`: (optional) a dictionary of file names to SHA256 digests of blobs for LORA adapters +- `template`: (optional) the prompt template for the model +- `license`: (optional) a string or list of strings containing the license or licenses for the model +- `system`: (optional) a string containing the system prompt for the model +- `parameters`: (optional) a dictionary of parameters for the model (see [Modelfile](./modelfile.md#valid-parameters-and-values) for a list of parameters) +- `messages`: (optional) a list of message objects used to create a conversation +- `stream`: (optional) if `false` the response will be returned as a single response object, rather than a stream of objects +- `quantize` (optional): quantize a non-quantized (e.g. float16) model + +#### Quantization types + +| Type | Recommended | +| --- | :-: | +| q2_K | | +| q3_K_L | | +| q3_K_M | | +| q3_K_S | | +| q4_0 | | +| q4_1 | | +| q4_K_M | * | +| q4_K_S | | +| q5_0 | | +| q5_1 | | +| q5_K_M | | +| q5_K_S | | +| q6_K | | +| q8_0 | * | + +### Examples + +#### Create a new model + +Create a new model from an existing model. + +##### Request + +```shell +curl http://localhost:11434/api/create -d '{ + "model": "mario", + "from": "llama3.2", + "system": "You are Mario from Super Mario Bros." +}' +``` + +##### Response + +A stream of JSON objects is returned: + +```json +{"status":"reading model metadata"} +{"status":"creating system layer"} +{"status":"using already created layer sha256:22f7f8ef5f4c791c1b03d7eb414399294764d7cc82c7e94aa81a1feb80a983a2"} +{"status":"using already created layer sha256:8c17c2ebb0ea011be9981cc3922db8ca8fa61e828c5d3f44cb6ae342bf80460b"} +{"status":"using already created layer sha256:7c23fb36d80141c4ab8cdbb61ee4790102ebd2bf7aeff414453177d4f2110e5d"} +{"status":"using already created layer sha256:2e0493f67d0c8c9c68a8aeacdf6a38a2151cb3c4c1d42accf296e19810527988"} +{"status":"using already created layer sha256:2759286baa875dc22de5394b4a925701b1896a7e3f8e53275c36f75a877a82c9"} +{"status":"writing layer sha256:df30045fe90f0d750db82a058109cecd6d4de9c90a3d75b19c09e5f64580bb42"} +{"status":"writing layer sha256:f18a68eb09bf925bb1b669490407c1b1251c5db98dc4d3d81f3088498ea55690"} +{"status":"writing manifest"} +{"status":"success"} +``` + +#### Quantize a model + +Quantize a non-quantized model. + +##### Request + +```shell +curl http://localhost:11434/api/create -d '{ + "model": "llama3.1:quantized", + "from": "llama3.1:8b-instruct-fp16", + "quantize": "q4_K_M" +}' +``` + +##### Response + +A stream of JSON objects is returned: + +```json +{"status":"quantizing F16 model to Q4_K_M"} +{"status":"creating new layer sha256:667b0c1932bc6ffc593ed1d03f895bf2dc8dc6df21db3042284a6f4416b06a29"} +{"status":"using existing layer sha256:11ce4ee3e170f6adebac9a991c22e22ab3f8530e154ee669954c4bc73061c258"} +{"status":"using existing layer sha256:0ba8f0e314b4264dfd19df045cde9d4c394a52474bf92ed6a3de22a4ca31a177"} +{"status":"using existing layer sha256:56bb8bd477a519ffa694fc449c2413c6f0e1d3b1c88fa7e3c9d88d3ae49d4dcb"} +{"status":"creating new layer sha256:455f34728c9b5dd3376378bfb809ee166c145b0b4c1f1a6feca069055066ef9a"} +{"status":"writing manifest"} +{"status":"success"} +``` + +#### Create a model from GGUF + +Create a model from a GGUF file. The `files` parameter should be filled out with the file name and SHA256 digest of the GGUF file you wish to use. Use [/api/blobs/:digest](#push-a-blob) to push the GGUF file to the server before calling this API. + + +##### Request + +```shell +curl http://localhost:11434/api/create -d '{ + "model": "my-gguf-model", + "files": { + "test.gguf": "sha256:432f310a77f4650a88d0fd59ecdd7cebed8d684bafea53cbff0473542964f0c3" + } +}' +``` + +##### Response + +A stream of JSON objects is returned: + +```json +{"status":"parsing GGUF"} +{"status":"using existing layer sha256:432f310a77f4650a88d0fd59ecdd7cebed8d684bafea53cbff0473542964f0c3"} +{"status":"writing manifest"} +{"status":"success"} +``` + + +#### Create a model from a Safetensors directory + +The `files` parameter should include a dictionary of files for the safetensors model which includes the file names and SHA256 digest of each file. Use [/api/blobs/:digest](#push-a-blob) to first push each of the files to the server before calling this API. Files will remain in the cache until the Ollama server is restarted. + +##### Request + +```shell +curl http://localhost:11434/api/create -d '{ + "model": "fred", + "files": { + "config.json": "sha256:dd3443e529fb2290423a0c65c2d633e67b419d273f170259e27297219828e389", + "generation_config.json": "sha256:88effbb63300dbbc7390143fbbdd9d9fa50587b37e8bfd16c8c90d4970a74a36", + "special_tokens_map.json": "sha256:b7455f0e8f00539108837bfa586c4fbf424e31f8717819a6798be74bef813d05", + "tokenizer.json": "sha256:bbc1904d35169c542dffbe1f7589a5994ec7426d9e5b609d07bab876f32e97ab", + "tokenizer_config.json": "sha256:24e8a6dc2547164b7002e3125f10b415105644fcf02bf9ad8b674c87b1eaaed6", + "model.safetensors": "sha256:1ff795ff6a07e6a68085d206fb84417da2f083f68391c2843cd2b8ac6df8538f" + } +}' +``` + +##### Response + +A stream of JSON objects is returned: + +```shell +{"status":"converting model"} +{"status":"creating new layer sha256:05ca5b813af4a53d2c2922933936e398958855c44ee534858fcfd830940618b6"} +{"status":"using autodetected template llama3-instruct"} +{"status":"using existing layer sha256:56bb8bd477a519ffa694fc449c2413c6f0e1d3b1c88fa7e3c9d88d3ae49d4dcb"} +{"status":"writing manifest"} +{"status":"success"} +``` + +## Check if a Blob Exists + +```shell +HEAD /api/blobs/:digest +``` + +Ensures that the file blob (Binary Large Object) used with create a model exists on the server. This checks your Ollama server and not ollama.com. + +### Query Parameters + +- `digest`: the SHA256 digest of the blob + +### Examples + +#### Request + +```shell +curl -I http://localhost:11434/api/blobs/sha256:29fdb92e57cf0827ded04ae6461b5931d01fa595843f55d36f5b275a52087dd2 +``` + +#### Response + +Return 200 OK if the blob exists, 404 Not Found if it does not. + +## Push a Blob + +``` +POST /api/blobs/:digest +``` + +Push a file to the Ollama server to create a "blob" (Binary Large Object). + +### Query Parameters + +- `digest`: the expected SHA256 digest of the file + +### Examples + +#### Request + +```shell +curl -T model.gguf -X POST http://localhost:11434/api/blobs/sha256:29fdb92e57cf0827ded04ae6461b5931d01fa595843f55d36f5b275a52087dd2 +``` + +#### Response + +Return 201 Created if the blob was successfully created, 400 Bad Request if the digest used is not expected. + +## List Local Models + +``` +GET /api/tags +``` + +List models that are available locally. + +### Examples + +#### Request + +```shell +curl http://localhost:11434/api/tags +``` + +#### Response + +A single JSON object will be returned. + +```json +{ + "models": [ + { + "name": "codellama:13b", + "modified_at": "2023-11-04T14:56:49.277302595-07:00", + "size": 7365960935, + "digest": "9f438cb9cd581fc025612d27f7c1a6669ff83a8bb0ed86c94fcf4c5440555697", + "details": { + "format": "gguf", + "family": "llama", + "families": null, + "parameter_size": "13B", + "quantization_level": "Q4_0" + } + }, + { + "name": "llama3:latest", + "modified_at": "2023-12-07T09:32:18.757212583-08:00", + "size": 3825819519, + "digest": "fe938a131f40e6f6d40083c9f0f430a515233eb2edaa6d72eb85c50d64f2300e", + "details": { + "format": "gguf", + "family": "llama", + "families": null, + "parameter_size": "7B", + "quantization_level": "Q4_0" + } + } + ] +} +``` + +## Show Model Information + +``` +POST /api/show +``` + +Show information about a model including details, modelfile, template, parameters, license, system prompt. + +### Parameters + +- `model`: name of the model to show +- `verbose`: (optional) if set to `true`, returns full data for verbose response fields + +### Examples + +#### Request + +```shell +curl http://localhost:11434/api/show -d '{ + "model": "llama3.2" +}' +``` + +#### Response + +```json +{ + "modelfile": "# Modelfile generated by \"ollama show\"\n# To build a new Modelfile based on this one, replace the FROM line with:\n# FROM llava:latest\n\nFROM /Users/matt/.ollama/models/blobs/sha256:200765e1283640ffbd013184bf496e261032fa75b99498a9613be4e94d63ad52\nTEMPLATE \"\"\"{{ .System }}\nUSER: {{ .Prompt }}\nASSISTANT: \"\"\"\nPARAMETER num_ctx 4096\nPARAMETER stop \"\u003c/s\u003e\"\nPARAMETER stop \"USER:\"\nPARAMETER stop \"ASSISTANT:\"", + "parameters": "num_keep 24\nstop \"<|start_header_id|>\"\nstop \"<|end_header_id|>\"\nstop \"<|eot_id|>\"", + "template": "{{ if .System }}<|start_header_id|>system<|end_header_id|>\n\n{{ .System }}<|eot_id|>{{ end }}{{ if .Prompt }}<|start_header_id|>user<|end_header_id|>\n\n{{ .Prompt }}<|eot_id|>{{ end }}<|start_header_id|>assistant<|end_header_id|>\n\n{{ .Response }}<|eot_id|>", + "details": { + "parent_model": "", + "format": "gguf", + "family": "llama", + "families": [ + "llama" + ], + "parameter_size": "8.0B", + "quantization_level": "Q4_0" + }, + "model_info": { + "general.architecture": "llama", + "general.file_type": 2, + "general.parameter_count": 8030261248, + "general.quantization_version": 2, + "llama.attention.head_count": 32, + "llama.attention.head_count_kv": 8, + "llama.attention.layer_norm_rms_epsilon": 0.00001, + "llama.block_count": 32, + "llama.context_length": 8192, + "llama.embedding_length": 4096, + "llama.feed_forward_length": 14336, + "llama.rope.dimension_count": 128, + "llama.rope.freq_base": 500000, + "llama.vocab_size": 128256, + "tokenizer.ggml.bos_token_id": 128000, + "tokenizer.ggml.eos_token_id": 128009, + "tokenizer.ggml.merges": [], // populates if `verbose=true` + "tokenizer.ggml.model": "gpt2", + "tokenizer.ggml.pre": "llama-bpe", + "tokenizer.ggml.token_type": [], // populates if `verbose=true` + "tokenizer.ggml.tokens": [] // populates if `verbose=true` + } +} +``` + +## Copy a Model + +``` +POST /api/copy +``` + +Copy a model. Creates a model with another name from an existing model. + +### Examples + +#### Request + +```shell +curl http://localhost:11434/api/copy -d '{ + "source": "llama3.2", + "destination": "llama3-backup" +}' +``` + +#### Response + +Returns a 200 OK if successful, or a 404 Not Found if the source model doesn't exist. + +## Delete a Model + +``` +DELETE /api/delete +``` + +Delete a model and its data. + +### Parameters + +- `model`: model name to delete + +### Examples + +#### Request + +```shell +curl -X DELETE http://localhost:11434/api/delete -d '{ + "model": "llama3:13b" +}' +``` + +#### Response + +Returns a 200 OK if successful, 404 Not Found if the model to be deleted doesn't exist. + +## Pull a Model + +``` +POST /api/pull +``` + +Download a model from the ollama library. Cancelled pulls are resumed from where they left off, and multiple calls will share the same download progress. + +### Parameters + +- `model`: name of the model to pull +- `insecure`: (optional) allow insecure connections to the library. Only use this if you are pulling from your own library during development. +- `stream`: (optional) if `false` the response will be returned as a single response object, rather than a stream of objects + +### Examples + +#### Request + +```shell +curl http://localhost:11434/api/pull -d '{ + "model": "llama3.2" +}' +``` + +#### Response + +If `stream` is not specified, or set to `true`, a stream of JSON objects is returned: + +The first object is the manifest: + +```json +{ + "status": "pulling manifest" +} +``` + +Then there is a series of downloading responses. Until any of the download is completed, the `completed` key may not be included. The number of files to be downloaded depends on the number of layers specified in the manifest. + +```json +{ + "status": "downloading digestname", + "digest": "digestname", + "total": 2142590208, + "completed": 241970 +} +``` + +After all the files are downloaded, the final responses are: + +```json +{ + "status": "verifying sha256 digest" +} +{ + "status": "writing manifest" +} +{ + "status": "removing any unused layers" +} +{ + "status": "success" +} +``` + +if `stream` is set to false, then the response is a single JSON object: + +```json +{ + "status": "success" +} +``` + +## Push a Model + +``` +POST /api/push +``` + +Upload a model to a model library. Requires registering for ollama.ai and adding a public key first. + +### Parameters + +- `model`: name of the model to push in the form of `/:` +- `insecure`: (optional) allow insecure connections to the library. Only use this if you are pushing to your library during development. +- `stream`: (optional) if `false` the response will be returned as a single response object, rather than a stream of objects + +### Examples + +#### Request + +```shell +curl http://localhost:11434/api/push -d '{ + "model": "mattw/pygmalion:latest" +}' +``` + +#### Response + +If `stream` is not specified, or set to `true`, a stream of JSON objects is returned: + +```json +{ "status": "retrieving manifest" } +``` + +and then: + +```json +{ + "status": "starting upload", + "digest": "sha256:bc07c81de745696fdf5afca05e065818a8149fb0c77266fb584d9b2cba3711ab", + "total": 1928429856 +} +``` + +Then there is a series of uploading responses: + +```json +{ + "status": "starting upload", + "digest": "sha256:bc07c81de745696fdf5afca05e065818a8149fb0c77266fb584d9b2cba3711ab", + "total": 1928429856 +} +``` + +Finally, when the upload is complete: + +```json +{"status":"pushing manifest"} +{"status":"success"} +``` + +If `stream` is set to `false`, then the response is a single JSON object: + +```json +{ "status": "success" } +``` + +## Generate Embeddings + +``` +POST /api/embed +``` + +Generate embeddings from a model + +### Parameters + +- `model`: name of model to generate embeddings from +- `input`: text or list of text to generate embeddings for + +Advanced parameters: + +- `truncate`: truncates the end of each input to fit within context length. Returns error if `false` and context length is exceeded. Defaults to `true` +- `options`: additional model parameters listed in the documentation for the [Modelfile](./modelfile.md#valid-parameters-and-values) such as `temperature` +- `keep_alive`: controls how long the model will stay loaded into memory following the request (default: `5m`) + +### Examples + +#### Request + +```shell +curl http://localhost:11434/api/embed -d '{ + "model": "all-minilm", + "input": "Why is the sky blue?" +}' +``` + +#### Response + +```json +{ + "model": "all-minilm", + "embeddings": [[ + 0.010071029, -0.0017594862, 0.05007221, 0.04692972, 0.054916814, + 0.008599704, 0.105441414, -0.025878139, 0.12958129, 0.031952348 + ]], + "total_duration": 14143917, + "load_duration": 1019500, + "prompt_eval_count": 8 +} +``` + +#### Request (Multiple input) + +```shell +curl http://localhost:11434/api/embed -d '{ + "model": "all-minilm", + "input": ["Why is the sky blue?", "Why is the grass green?"] +}' +``` + +#### Response + +```json +{ + "model": "all-minilm", + "embeddings": [[ + 0.010071029, -0.0017594862, 0.05007221, 0.04692972, 0.054916814, + 0.008599704, 0.105441414, -0.025878139, 0.12958129, 0.031952348 + ],[ + -0.0098027075, 0.06042469, 0.025257962, -0.006364387, 0.07272725, + 0.017194884, 0.09032035, -0.051705178, 0.09951512, 0.09072481 + ]] +} +``` + +## List Running Models +``` +GET /api/ps +``` + +List models that are currently loaded into memory. + +#### Examples + +### Request + +```shell +curl http://localhost:11434/api/ps +``` + +#### Response + +A single JSON object will be returned. + +```json +{ + "models": [ + { + "name": "mistral:latest", + "model": "mistral:latest", + "size": 5137025024, + "digest": "2ae6f6dd7a3dd734790bbbf58b8909a606e0e7e97e94b7604e0aa7ae4490e6d8", + "details": { + "parent_model": "", + "format": "gguf", + "family": "llama", + "families": [ + "llama" + ], + "parameter_size": "7.2B", + "quantization_level": "Q4_0" + }, + "expires_at": "2024-06-04T14:38:31.83753-07:00", + "size_vram": 5137025024 + } + ] +} +``` + +## Generate Embedding + +> Note: this endpoint has been superseded by `/api/embed` + +``` +POST /api/embeddings +``` + +Generate embeddings from a model + +### Parameters + +- `model`: name of model to generate embeddings from +- `prompt`: text to generate embeddings for + +Advanced parameters: + +- `options`: additional model parameters listed in the documentation for the [Modelfile](./modelfile.md#valid-parameters-and-values) such as `temperature` +- `keep_alive`: controls how long the model will stay loaded into memory following the request (default: `5m`) + +### Examples + +#### Request + +```shell +curl http://localhost:11434/api/embeddings -d '{ + "model": "all-minilm", + "prompt": "Here is an article about llamas..." +}' +``` + +#### Response + +```json +{ + "embedding": [ + 0.5670403838157654, 0.009260174818336964, 0.23178744316101074, -0.2916173040866852, -0.8924556970596313, + 0.8785552978515625, -0.34576427936553955, 0.5742510557174683, -0.04222835972905159, -0.137906014919281 + ] +} +``` + +## Version + +``` +GET /api/version +``` + +Retrieve the Ollama version + +### Examples + +#### Request + +```shell +curl http://localhost:11434/api/version +``` + +#### Response + +```json +{ + "version": "0.5.1" +} +``` + diff --git a/ai_integration_ollama_api/migrations/1.0.0/post-migration.py b/ai_integration_ollama_api/migrations/1.0.0/post-migration.py new file mode 100644 index 0000000..0998710 --- /dev/null +++ b/ai_integration_ollama_api/migrations/1.0.0/post-migration.py @@ -0,0 +1,17 @@ +def migrate(cr, version): + # Add num_predict column if it doesn't exist + cr.execute(""" + DO $$ + BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM information_schema.columns + WHERE table_name='ai_provider_instance' + AND column_name='num_predict' + ) THEN + ALTER TABLE ai_provider_instance + ADD COLUMN num_predict integer DEFAULT 1024; + END IF; + END + $$; + """) diff --git a/ai_integration_ollama_api/models/__init__.py b/ai_integration_ollama_api/models/__init__.py new file mode 100644 index 0000000..abd67f0 --- /dev/null +++ b/ai_integration_ollama_api/models/__init__.py @@ -0,0 +1,27 @@ +"""Ollama AI Integration Models Package. + +This package contains all the model definitions required for integrating +Ollama AI with Odoo's AI framework. The models are loaded in a specific +order to handle dependencies correctly. + +Module Structure: +1. ollama_provider_mixin - Base configuration and parameter definitions +2. ollama_provider - Core Ollama API integration implementation +3. ollama_model_stats - Usage statistics and performance tracking +4. ai_provider_instance - Instance-specific configuration and management + +Note: The import order is important to avoid circular dependencies. +""" + +# Base Configuration +from . import ollama_provider_mixin + +# Core Implementation +from . import ollama_provider +from . import ai_provider_instance + +# Statistics and Monitoring +from . import ollama_model_stats + +# Instance Management +from . import ai_provider_instance diff --git a/ai_integration_ollama_api/models/ai_provider_instance.py b/ai_integration_ollama_api/models/ai_provider_instance.py new file mode 100644 index 0000000..466ceb3 --- /dev/null +++ b/ai_integration_ollama_api/models/ai_provider_instance.py @@ -0,0 +1,153 @@ +from odoo import models, fields, api, _ +from odoo.exceptions import UserError +import requests + +class OllamaAIProviderInstance(models.Model): + """Extends the AI Provider Instance model to support Ollama-specific configuration. + + This model inherits from both ai.provider.instance and ollama.provider.mixin to: + 1. Add Ollama-specific fields (num_ctx, temperature, etc.) + 2. Handle field visibility based on provider_type + 3. Manage field cleanup when switching providers + """ + _inherit = ['ai.provider.instance', 'ollama.provider.mixin'] + _name = 'ai.provider.instance' + _description = 'Ollama AI Provider Instance' + + # Override provider_type to add Ollama option + provider_type = fields.Selection( + selection_add=[('ollama', 'Ollama')], + ondelete={'ollama': lambda r: r.write({'provider_type': 'none'})} + ) + + @api.onchange('provider_type') + def _onchange_provider_type(self): + """Handle provider type changes. + + When switching to 'ollama': + - Set default host if empty + + When switching away from 'ollama': + - Clear Ollama-specific fields + """ + if self.provider_type == 'ollama': + if not self.host: + self.host = 'http://localhost:11434' + else: + # Clear Ollama-specific fields + self.update({ + 'num_ctx': False, # Context length + 'temperature': False, # Sampling temperature + 'top_p': False, # Nucleus sampling threshold + 'top_k': False, # Top-k sampling threshold + 'repeat_penalty': False, # Penalty for repeated tokens + 'repeat_last_n': False, # Number of tokens to consider for repeat penalty + 'num_thread': False, # Number of CPU threads to use + 'num_gpu': False, # Number of GPUs to use + 'num_batch': False, # Batch size for inference + 'model_name': False, # Model name/path + }) + + # Override default host for Ollama + host = fields.Char( + default='http://localhost:11434', + help='Ollama server host URL') + + def test_connection(self): + """Test the connection to the Ollama server. + + This method attempts to connect to the Ollama server and verify + that it is responding correctly. It will raise a user-friendly + error if the connection fails. + + Returns: + dict: Action to display success message + """ + self.ensure_one() + if self.provider_type != 'ollama': + return + + try: + # Try to list models as a basic connectivity test + response = requests.get(f'{self.host}/api/tags') + response.raise_for_status() + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': _('Success'), + 'message': _('Successfully connected to Ollama server'), + 'sticky': False, + 'type': 'success', + } + } + except Exception as e: + raise UserError(_('Connection test failed: %s', str(e))) + + def sync_models(self): + """Synchronize available models from the Ollama server. + + This method fetches the list of available models from the Ollama + server and creates or updates the corresponding AI model records + in Odoo. + + Returns: + dict: Action to display success message + """ + self.ensure_one() + if self.provider_type != 'ollama': + return + + try: + # Get models from Ollama API + response = requests.get(f'{self.host}/api/tags') + response.raise_for_status() + + # Parse response + models = [{ + 'name': model['name'], + 'id': model['name'], + } for model in response.json()['models']] + + for model_data in models: + # Create or update AI model record + vals = { + 'name': model_data['name'], + 'identifier': model_data['id'], + 'provider_instance_id': self.id, + 'active': True, + } + # Search for existing model + existing = self.env['ai.model'].search([ + ('identifier', '=', model_data['id']), + ('provider_instance_id', '=', self.id) + ], limit=1) + + if existing: + existing.write(vals) + else: + self.env['ai.model'].create(vals) + + # Invalidate the cache to force reload of related records + self.invalidate_recordset(['model_ids']) + + # Return action to reload the view completely + return { + 'type': 'ir.actions.act_window', + 'res_model': 'ai.provider.instance', + 'res_id': self.id, + 'view_mode': 'form', + 'target': 'current', + 'flags': { + 'mode': 'readonly', + 'reload': True, # Force reload + }, + 'context': {'notification': { + 'type': 'success', + 'title': _('Success'), + 'message': _('Successfully synchronized %d models', len(models)), + 'sticky': False, + }} + } + except Exception as e: + raise UserError(_('Model synchronization failed: %s', str(e))) diff --git a/ai_integration_ollama_api/models/ai_provider_ollama.py.bak b/ai_integration_ollama_api/models/ai_provider_ollama.py.bak new file mode 100644 index 0000000..6d6a39f --- /dev/null +++ b/ai_integration_ollama_api/models/ai_provider_ollama.py.bak @@ -0,0 +1,35 @@ +from odoo import models, fields, api, _ +from .ollama_provider_mixin import OllamaProviderMixin + +class OllamaAIProvider(models.Model, OllamaProviderMixin): + _name = 'ai.provider.ollama' + _description = 'Ollama AI Provider' + _inherit = ['ai.provider.interface'] + + provider_type = fields.Selection( + selection=[('ollama', 'Ollama')], + default='ollama', + required=True, + help='Type of AI provider') + + # Ollama-specific Parameters + host = fields.Char( + string='Host', + default='http://localhost:11434', + required=True, + help='Ollama server host URL') + + def _get_available_models(self): + """Get list of available models from Ollama server.""" + # TODO: Implement model discovery + return [] + + def _generate_text(self, prompt, **kwargs): + """Generate text using Ollama model.""" + # TODO: Implement text generation + return "" + + def _embed_text(self, text, **kwargs): + """Generate embeddings for text using Ollama model.""" + # TODO: Implement text embedding + return [] diff --git a/ai_integration_ollama_api/models/ollama_model_stats.py b/ai_integration_ollama_api/models/ollama_model_stats.py new file mode 100644 index 0000000..996da76 --- /dev/null +++ b/ai_integration_ollama_api/models/ollama_model_stats.py @@ -0,0 +1,116 @@ +from odoo import models, fields, api +from datetime import datetime, timedelta + +class OllamaModelStats(models.Model): + """Tracks and stores daily usage statistics for Ollama AI models. + + This model maintains detailed daily statistics for each Ollama model, + including request counts, token usage, response times, and error rates. + It inherits from ai.model.stats for base statistics functionality. + + Key Features: + - Daily usage tracking per model + - Performance metrics collection + - Error rate monitoring + - Version tracking for model updates + + Technical Details: + - One stat entry per model per day (enforced by SQL constraint) + - Automatic version tracking from Ollama API + - Aggregated statistics calculation + - Ordered by date for easy historical analysis + """ + _name = 'ollama.model.stats' + _description = 'Ollama Model Usage Statistics' + _inherit = ['ai.model.stats'] + _order = 'date desc' # Most recent stats first + + model_id = fields.Many2one('ai.model', string='Model', required=True, ondelete='cascade') + date = fields.Date(string='Date', required=True, default=fields.Date.context_today) + request_count = fields.Integer(string='Number of Requests', default=0) + token_count = fields.Integer(string='Total Tokens', default=0) + avg_response_time = fields.Float(string='Average Response Time (ms)', digits=(10, 2), default=0) + error_count = fields.Integer(string='Number of Errors', default=0) + version = fields.Char(string='Model Version', help='Version of the model when stats were recorded') + + _sql_constraints = [ + ('unique_model_date', 'unique(model_id, date)', 'Only one stat entry per model per day is allowed.') + ] + + def _update_stats(self, model, tokens, response_time, error=False): + """Update daily statistics for a specific model. + + This method handles the creation or update of daily statistics entries. + It maintains running averages and cumulative counts for various metrics. + + Args: + model (ai.model): The model record being tracked + tokens (int): Number of tokens in the current request + response_time (float): Response time in milliseconds + error (bool): Whether this request resulted in an error + + Technical Notes: + - Creates new stat entry if none exists for today + - Updates running averages for response time + - Fetches and stores model version from Ollama API + - Maintains cumulative counts for requests and errors + """ + today = fields.Date.context_today(self) + stats = self.search([ + ('model_id', '=', model.id), + ('date', '=', today) + ]) + + if not stats: + # Get model version + version = self.env['ai.provider.ollama']._get_model_info( + model.provider_instance_id, + model.identifier + ).get('details', {}).get('sha256', '')[:8] # First 8 chars of SHA + + stats = self.create({ + 'model_id': model.id, + 'date': today, + 'version': version + }) + + # Update statistics + new_count = stats.request_count + 1 + new_tokens = stats.token_count + tokens + new_time = ((stats.avg_response_time * stats.request_count) + response_time) / new_count + new_errors = stats.error_count + (1 if error else 0) + + stats.write({ + 'request_count': new_count, + 'token_count': new_tokens, + 'avg_response_time': new_time, + 'error_count': new_errors + }) + + @api.model + def get_model_stats(self, model_id, days=30): + """Get statistics for a model over the specified number of days.""" + start_date = fields.Date.today() - timedelta(days=days) + stats = self.search([ + ('model_id', '=', model_id), + ('date', '>=', start_date) + ]) + + return { + 'daily_stats': [{ + 'date': stat.date, + 'requests': stat.request_count, + 'tokens': stat.token_count, + 'response_time': stat.avg_response_time, + 'errors': stat.error_count, + 'version': stat.version + } for stat in stats], + 'summary': { + 'total_requests': sum(stat.request_count for stat in stats), + 'total_tokens': sum(stat.token_count for stat in stats), + 'avg_response_time': sum(stat.avg_response_time * stat.request_count for stat in stats) / + (sum(stat.request_count for stat in stats) if stats else 1), + 'total_errors': sum(stat.error_count for stat in stats), + 'versions_used': list(set(stat.version for stat in stats if stat.version)) + } + } diff --git a/ai_integration_ollama_api/models/ollama_provider.py b/ai_integration_ollama_api/models/ollama_provider.py new file mode 100644 index 0000000..6abf869 --- /dev/null +++ b/ai_integration_ollama_api/models/ollama_provider.py @@ -0,0 +1,324 @@ +import json +import logging +import requests +from odoo import models, fields, api, _ +from odoo.exceptions import UserError + +_logger = logging.getLogger(__name__) + +class OllamaProvider(models.Model): + """Main Ollama AI Provider implementation. + + This model implements the core functionality for interacting with Ollama's API, + including model management, text generation, and error handling. + + Key Responsibilities: + - Model discovery and validation + - API communication and error handling + - Request formatting and response parsing + - Resource management and cleanup + + Technical Details: + - Implements the ai.provider.interface for standardized AI provider integration + - Uses Ollama's HTTP API for all operations + - Handles both synchronous and asynchronous requests + - Provides detailed error messages for troubleshooting + """ + _name = 'ai.provider.ollama' + _description = 'Ollama AI Provider' + _inherit = ['ai.provider.interface'] + + @api.model + def _get_models(self, instance): + """Get list of available models from Ollama server. + + Args: + instance (ai.provider.instance): Provider instance to get models for + + Returns: + list: List of model dictionaries with keys: + - name: Model name + - id: Model identifier + - details: Additional model metadata + + Raises: + UserError: If unable to connect or retrieve models + """ + try: + response = requests.get(f"{instance.host}/api/tags") + response.raise_for_status() + + models_data = response.json().get('models', []) + return [{ + 'name': model['name'], + 'id': model['name'], + 'details': model + } for model in models_data] + + except requests.exceptions.RequestException as e: + raise UserError(_('Failed to connect to Ollama server: %s', str(e))) + except (KeyError, ValueError) as e: + raise UserError(_('Invalid response from Ollama server: %s', str(e))) + + # API Configuration + timeout = fields.Integer( + string='Timeout', + default=30, + help='API request timeout in seconds') + + def _get_provider_type(self): + return 'ollama' + + def _get_model_info(self, instance, model_name): + """Get detailed information about a specific model.""" + try: + response = requests.post( + f"{instance.host}/api/show", + json={'name': model_name} + ) + if response.status_code == 200: + return response.json() + else: + _logger.error( + "Failed to get model info for %s. Status: %s, Error: %s", + model_name, response.status_code, response.text + ) + return None + except requests.exceptions.RequestException as e: + _logger.error("Error getting model info: %s", str(e)) + return None + + def pull_model(self, instance, model_name): + """Pull a model from Ollama.""" + try: + # Start the pull + response = requests.post( + f"{instance.host}/api/pull", + json={'name': model_name}, + stream=True # Enable streaming for progress updates + ) + + if response.status_code != 200: + raise UserError(_("Failed to pull model %s: %s") % + (model_name, response.text)) + + # Process the streaming response + for line in response.iter_lines(): + if line: + try: + progress = json.loads(line) + status = progress.get('status') + if status: + _logger.info( + "Pulling model %s: %s", + model_name, status + ) + except json.JSONDecodeError: + continue + + return True + + except requests.exceptions.RequestException as e: + raise UserError(_("Error pulling model %s: %s") % + (model_name, str(e))) + + def delete_model(self, instance, model_name): + """Delete a model from Ollama.""" + try: + response = requests.delete( + f"{instance.host}/api/delete", + json={'name': model_name} + ) + + if response.status_code != 200: + raise UserError(_("Failed to delete model %s: %s") % + (model_name, response.text)) + + return True + + except requests.exceptions.RequestException as e: + raise UserError(_("Error deleting model %s: %s") % + (model_name, str(e))) + + def test_connection(self, instance): + """Test the connection to the Ollama server.""" + try: + response = requests.get(f"{instance.host}/api/tags") + if response.status_code != 200: + raise UserError(_( + "Failed to connect to Ollama server. Status code: %s. Error: %s", + response.status_code, response.text + )) + return True + except requests.exceptions.RequestException as e: + raise UserError(_( + "Failed to connect to Ollama server: %s", str(e) + )) + + def _format_chat_messages(self, messages): + """Format chat messages for Ollama API.""" + formatted_prompt = "" + for message in messages: + role = message.get('role', 'user') + content = message.get('content', '') + + if role == 'system': + formatted_prompt += f"{content}\n" + elif role == 'assistant': + formatted_prompt += f"Assistant: {content}\n" + else: # user + formatted_prompt += f"Human: {content}\n" + + return formatted_prompt.strip() + + def generate_response(self, instance, model, messages, **kwargs): + """Generate a response using the chat completion API.""" + try: + # Format messages into Ollama's expected format + prompt = self._format_chat_messages(messages) + + # Get model options from instance + options = instance._get_provider_options() + + # Prepare the request payload + payload = { + 'model': model.identifier, + 'prompt': prompt, + 'stream': False, + **options + } + + # Make the API call + response = requests.post( + f"{instance.host}/api/generate", + json=payload + ) + + if response.status_code != 200: + raise UserError(_("Failed to generate response: %s") % response.text) + + response_data = response.json() + generated_text = response_data.get('response', '') + + # Update statistics + total_tokens = response_data.get('eval_count', 0) + response_time = response_data.get('total_duration', 0) + version = self._get_model_info(instance, model.identifier)\ + .get('details', {}).get('sha256', '')[:8] + self._track_model_usage( + model, total_tokens, response_time, version=version + ) + + # Return the response in a standardized format + return { + 'content': generated_text, + 'role': 'assistant', + 'metadata': { + 'eval_count': response_data.get('eval_count'), + 'eval_duration': response_data.get('eval_duration'), + 'total_duration': response_data.get('total_duration'), + 'load_duration': response_data.get('load_duration'), + } + } + + except requests.exceptions.RequestException as e: + # Log error in statistics + if model: + self._track_model_usage(model, error=True) + raise UserError(_("Error generating response: %s") % str(e)) + + def sync_models(self, instance): + """Synchronize available models from the Ollama server.""" + self.test_connection(instance) + + try: + response = requests.get(f"{instance.host}/api/tags") + models_data = response.json().get('models', []) + + # Get existing models for this instance + existing_models = self.env['ai.model'].search([ + ('provider_instance_id', '=', instance.id) + ]) + existing_identifiers = {m.identifier: m for m in existing_models} + + for model_data in models_data: + identifier = model_data.get('name') + if not identifier: + continue + + # Get model details + model_info = self._get_model_info(instance, identifier) + model_details = model_info.get('details', {}) + + model_values = { + 'name': identifier.title(), + 'identifier': identifier, + 'provider_instance_id': instance.id, + 'company_id': instance.company_id.id, + 'active': True, + 'description': f"Ollama model: {identifier}\\nSize: {model_data.get('size', 'Unknown')}\\nModified: {model_data.get('modified', 'Unknown')}", + } + + if identifier in existing_identifiers: + # Update existing model + existing_identifiers[identifier].write(model_values) + del existing_identifiers[identifier] + else: + # Create new model + self.env['ai.model'].create(model_values) + + # Deactivate models that no longer exist + for model in existing_identifiers.values(): + model.active = False + + return True + + except requests.exceptions.RequestException as e: + raise UserError(_( + "Failed to sync models from Ollama server: %s", str(e) + )) + + def send_message(self, message, model, **kwargs): + """Send a message to the Ollama server.""" + instance = model.provider_instance_id + + try: + # Prepare the request payload + payload = { + 'model': model.identifier, + 'prompt': message.get('content', ''), + 'stream': False, + 'options': kwargs.get('options', {}), + } + + # Add system message if provided + if message.get('role') == 'system': + payload['system'] = message['content'] + + # Send the request + response = requests.post( + f"{instance.host}/api/generate", + json=payload, + timeout=(instance.timeout or 30) + ) + + if response.status_code != 200: + raise UserError(_( + "Ollama server error. Status code: %s. Error: %s", + response.status_code, response.text + )) + + result = response.json() + return result.get('response', '') + + except requests.exceptions.Timeout: + raise UserError(_("Request to Ollama server timed out")) + except requests.exceptions.RequestException as e: + raise UserError(_( + "Error communicating with Ollama server: %s", str(e) + )) + except Exception as e: + _logger.error("Unexpected error in Ollama provider: %s", str(e)) + raise UserError(_( + "Unexpected error in Ollama provider: %s", str(e) + )) diff --git a/ai_integration_ollama_api/models/ollama_provider_mixin.py b/ai_integration_ollama_api/models/ollama_provider_mixin.py new file mode 100644 index 0000000..c50a4fe --- /dev/null +++ b/ai_integration_ollama_api/models/ollama_provider_mixin.py @@ -0,0 +1,274 @@ +from odoo import models, fields, api, _ +from odoo.exceptions import UserError +import requests +import logging +import json + +_logger = logging.getLogger(__name__) + +class OllamaProviderMixin(models.AbstractModel): + """Mixin model that provides Ollama-specific configuration parameters. + + This mixin is designed to be inherited by models that need to interact with + the Ollama AI provider. It provides all the necessary fields and methods + for configuring and interacting with Ollama's API. + + Key Features: + - Provider type selection and validation + - Context window configuration + - Advanced sampling parameters (temperature, top-k, top-p) + - Token generation controls + + Technical Details: + - Inherits from ai.generation.params for base AI generation parameters + - Implements Ollama-specific API parameters + - Provides default values optimized for general use cases + """ + _name = 'ollama.provider.mixin' + _description = 'Ollama Provider Configuration Mixin' + _inherit = ['ai.generation.params'] + + # Model Parameters + model_name = fields.Char( + string='Model Name', + help='Name of the Ollama model to use (e.g. llama2, mistral, codellama)', + required=True, + default='deepseek-r1:32b') + + # Context Window Configuration + num_ctx = fields.Integer( + string='Context Length', + help='Maximum number of tokens to consider for context. A larger context window allows ' + 'the model to access more historical information but requires more memory. ' + 'Range: [0 - 32768].', + default=8192) + + # Generation Parameters + temperature = fields.Float( + string='Temperature', + help='Controls randomness in the output. Higher values make the output more random, ' + 'while lower values make it more focused and deterministic. ' + 'Range: [0.0 - 2.0]', + default=0.7) + + top_p = fields.Float( + string='Top P (Nucleus Sampling)', + help='Limits the cumulative probability of tokens to sample from. Only the most likely ' + 'tokens with total probability mass of top_p are considered. ' + 'Range: [0.0 - 1.0].', + default=0.9) + + top_k = fields.Integer( + string='Top K', + help='Limits the cumulative probability of tokens to sample from. Only the top K ' + 'most likely tokens are considered for sampling at each step. ' + 'Range: [1 - 100].', + default=40) + + min_p = fields.Float( + string='Min P', + help='Sets a minimum probability threshold for token selection. Range: [0.0 - 1.0].', + default=0.05, + digits=(3, 2)) + + repeat_penalty = fields.Float( + string='Repeat Penalty', + help='Penalty for repeating tokens. Range: [1.0 - 2.0]. Higher values make repetition less likely.', + default=1.1, + digits=(3, 2)) + + # Advanced Configuration + stop_sequences = fields.Char( + string='Stop Sequences', + help='Comma-separated list of sequences where the model should stop generating further tokens.') + + num_predict = fields.Integer( + string='Maximum Tokens', + help='Maximum number of tokens to predict. Set to -1 for unlimited.', + default=2048) + + repeat_last_n = fields.Integer( + string='Repeat Last N', + help='Sets the context window for repeat penalty. Range: [0 - 4096]. Default is 64, 0 disables.', + default=64 + ) + + def generate_text(self, prompt, **kwargs): + """Generate text using the Ollama API. + + Args: + prompt (str): The prompt to generate text from + **kwargs: Additional parameters to pass to the API + + Returns: + str: The generated text + """ + self.ensure_one() + + # Prepare the request + url = f"{self.host}/api/generate" + + # Build the request data + data = { + 'model': self.model_name, + 'prompt': prompt, + 'stream': False, + 'num_ctx': self.num_ctx, + 'temperature': self.temperature, + 'top_k': self.top_k, + 'top_p': self.top_p, + 'repeat_penalty': self.repeat_penalty, + 'repeat_last_n': self.repeat_last_n, + 'num_predict': self.num_predict, + 'min_p': self.min_p, + 'seed': self.seed, + 'num_gpu': self.num_gpu, + 'num_thread': self.num_thread, + 'mirostat': int(self.mirostat), + 'mirostat_tau': self.mirostat_tau, + 'mirostat_eta': self.mirostat_eta, + 'num_batch': self.num_batch, + 'num_keep': self.num_keep, + 'tfs_z': self.tfs_z, + 'skip_special_tokens': self.skip_special_tokens + } + + # Add any additional parameters + if kwargs: + data.update(kwargs) + + # Make the request + try: + _logger = logging.getLogger(__name__) + _logger.info("Sending request to Ollama API with data: %s", data) + + response = requests.post(url, json=data, timeout=self.timeout) + response.raise_for_status() + + # Log the raw response + _logger.info("Raw API response: %s", response.text) + + # Parse the response + result = response.json() + response_text = result.get('response', '') + + # Si la réponse est une chaîne JSON, la parser + try: + if isinstance(response_text, str): + parsed_response = json.loads(response_text) + _logger.info("Parsed nested JSON response: %s", parsed_response) + return parsed_response + else: + _logger.info("Direct response: %s", response_text) + return response_text + except json.JSONDecodeError: + # Si ce n'est pas du JSON valide, retourner le texte tel quel + _logger.info("Non-JSON response: %s", response_text) + return response_text + + except requests.exceptions.RequestException as e: + raise UserError(_('Failed to generate text: %s') % str(e)) + + # Advanced Generation Parameters + seed = fields.Integer( + string='Random Seed', + help='Sets the random seed for generation. Range: [0 - 2147483647]. Use 0 for random.', + default=0) + + num_gpu = fields.Integer( + string='Number of GPUs', + help='Number of GPUs to use for generation. Range: [0 - 8]. 0 means CPU only.', + default=1) + + num_thread = fields.Integer( + string='Number of Threads', + help='Number of CPU threads to use for generation. Range: [1 - 32].', + default=8) + + mirostat = fields.Selection([ + ('0', 'Disabled'), + ('1', 'Mirostat'), + ('2', 'Mirostat 2.0')], + string='Mirostat Mode', + help='Enable Mirostat sampling for controlling perplexity', + default='0') + + mirostat_tau = fields.Float( + string='Mirostat Tau', + help='Mirostat target entropy. Range: [0.0 - 10.0].', + default=5.0, + digits=(3, 2)) + + mirostat_eta = fields.Float( + string='Mirostat Eta', + help='Mirostat learning rate. Range: [0.0 - 1.0].', + default=0.1, + digits=(3, 2)) + + # Ollama-specific Response Control + + tfs_z = fields.Float( + string='Tail Free Sampling Z', + help='Tail free sampling parameter. Range: [0.0 - 2.0]. Higher value = more focused.', + default=1.0, + digits=(3, 2)) + + # System Settings + num_batch = fields.Integer( + string='Batch Size', + help='Number of prompts to batch together', + default=8) + + num_keep = fields.Integer( + string='Keep Last N Tokens', + help='Number of tokens to keep from initial prompt', + default=0) + + skip_special_tokens = fields.Boolean( + string='Skip Special Tokens', + help='Skip special tokens in generation', + default=True) + + @api.model + def default_get(self, fields_list): + defaults = super().default_get(fields_list) + if 'provider_type' in fields_list: + defaults['provider_type'] = 'ollama' + if 'host' in fields_list and not defaults.get('host'): + defaults['host'] = 'http://localhost:11434' + return defaults + + def _get_provider_options(self): + """Get Ollama-specific options for API calls.""" + self.ensure_one() + options = { + 'model': self.model_name, + 'temperature': self.temperature, + 'num_ctx': self.num_ctx, + 'num_predict': self.num_predict, + 'top_k': self.top_k, + 'top_p': self.top_p, + 'min_p': self.min_p, + 'repeat_penalty': self.repeat_penalty, + 'repeat_last_n': self.repeat_last_n, + 'seed': self.seed, + 'num_gpu': self.num_gpu, + 'num_thread': self.num_thread, + 'mirostat': int(self.mirostat), + 'mirostat_tau': self.mirostat_tau, + 'mirostat_eta': self.mirostat_eta, + 'num_batch': self.num_batch, + 'num_keep': self.num_keep, + 'tfs_z': self.tfs_z, + 'skip_special_tokens': self.skip_special_tokens, + 'stream': False + } + + if self.stop_sequences: + options['stop'] = [ + seq.strip() + for seq in self.stop_sequences.split(',') + if seq.strip() + ] + + return options diff --git a/ai_integration_ollama_api/security/ir.model.access.csv b/ai_integration_ollama_api/security/ir.model.access.csv new file mode 100644 index 0000000..1cebd2d --- /dev/null +++ b/ai_integration_ollama_api/security/ir.model.access.csv @@ -0,0 +1,5 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_ollama_model_stats_user,ollama.model.stats.user,model_ollama_model_stats,base.group_user,1,0,0,0 +access_ollama_model_stats_manager,ollama.model.stats.manager,model_ollama_model_stats,base.group_system,1,1,1,1 +access_ai_provider_ollama_user,ai.provider.ollama.user,model_ai_provider_ollama,base.group_user,1,0,0,0 +access_ai_provider_ollama_manager,ai.provider.ollama.manager,model_ai_provider_ollama,base.group_system,1,1,1,1 diff --git a/ai_integration_ollama_api/views/ai_provider_instance_views.xml b/ai_integration_ollama_api/views/ai_provider_instance_views.xml new file mode 100644 index 0000000..9375f4e --- /dev/null +++ b/ai_integration_ollama_api/views/ai_provider_instance_views.xml @@ -0,0 +1,15 @@ + + + + + ai.provider.instance.list.ollama + ai.provider.instance + + primary + + + true + + + + diff --git a/ai_integration_ollama_api/views/ollama_stats_views.xml b/ai_integration_ollama_api/views/ollama_stats_views.xml new file mode 100644 index 0000000..cecf64c --- /dev/null +++ b/ai_integration_ollama_api/views/ollama_stats_views.xml @@ -0,0 +1,118 @@ + + + + + ollama.model.stats.list + ollama.model.stats + + + + + + + + + + + + + + + + ollama.model.stats.form + ollama.model.stats + +
+ + + + + + + + + + + + + + + +
+
+
+ + + + ollama.model.stats.search + ollama.model.stats + + + + + + + + + + + + + + + + + + + + ollama.model.stats.graph + ollama.model.stats + + + + + + + + + + + + + ollama.model.stats.pivot + ollama.model.stats + + + + + + + + + + + + + + + Model Statistics + ollama.model.stats + list,form,graph,pivot + {'search_default_last_month': 1} + +

+ No statistics recorded yet +

+

+ Statistics will be automatically recorded as you use your AI models. +

+
+
+ + + +
diff --git a/ai_integration_ollama_api/views/ollama_views.xml b/ai_integration_ollama_api/views/ollama_views.xml new file mode 100644 index 0000000..c459b12 --- /dev/null +++ b/ai_integration_ollama_api/views/ollama_views.xml @@ -0,0 +1,108 @@ + + + + + ollama.instance.form + ai.provider.instance + form + primary + + + +
+ +
+
+ + + + + + + + + + + + + + + + + + + + +
+
+ + + + ollama.instance.list + ai.provider.instance + list + + + + + + + + + + + + + + + ollama.instance.search + ai.provider.instance + primary + + + + + + + + + + + Ollama Instances + ai.provider.instance + list,form + [('provider_type', '=', 'ollama')] + {'default_provider_type': 'ollama'} + +

+ Create your first Ollama instance +

+

+ Configure Ollama instances to use local AI models in your Odoo instance. + Make sure you have Ollama installed and running on your server. +

+
+
+ + + +
diff --git a/ai_integration_openai_api/__init__.py b/ai_integration_openai_api/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/ai_integration_openai_api/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/ai_integration_openai_api/__manifest__.py b/ai_integration_openai_api/__manifest__.py new file mode 100644 index 0000000..f397818 --- /dev/null +++ b/ai_integration_openai_api/__manifest__.py @@ -0,0 +1,35 @@ +{ + 'name': 'ChatGPT-Compatible AI Integration', + 'version': '1.0', + 'category': 'Technical', + 'summary': 'Integration for ChatGPT-compatible AI providers', + 'description': """ +ChatGPT-Compatible AI Integration +================================ +This module provides integration with ChatGPT-compatible AI providers (OpenWebUI, ChatGPT, etc), offering: + +* ChatGPT-compatible provider implementation +* Model synchronization +* Advanced parameter configuration +* Usage statistics tracking +* Support for multiple providers using the ChatGPT API format +""", + 'author': 'Bemade', + 'website': 'https://www.bemade.org', + 'depends': [ + 'ai_integration', + 'openwebui_integration' + ], + 'data': [ + 'security/ir.model.access.csv', + 'views/chatgpt_instance_views.xml', + 'data/chatgpt_provider.xml', + ], + 'external_dependencies': { + 'python': ['requests'], + }, + 'installable': True, + 'application': False, + 'auto_install': False, + 'license': 'LGPL-3', +} diff --git a/ai_integration_openai_api/data/chatgpt_provider.xml b/ai_integration_openai_api/data/chatgpt_provider.xml new file mode 100644 index 0000000..061810b --- /dev/null +++ b/ai_integration_openai_api/data/chatgpt_provider.xml @@ -0,0 +1,14 @@ + + + + + + ChatGPT-Compatible + chatgpt + chatgpt.ai.instance + ai.provider.chatgpt + Integration with ChatGPT-compatible AI providers (OpenWebUI, ChatGPT, etc). + https://platform.openai.com/docs/api-reference/chat + + + diff --git a/ai_integration_openai_api/models/__init__.py b/ai_integration_openai_api/models/__init__.py new file mode 100644 index 0000000..9ae9743 --- /dev/null +++ b/ai_integration_openai_api/models/__init__.py @@ -0,0 +1,2 @@ +from . import chatgpt_provider +from . import chatgpt_instance diff --git a/ai_integration_openai_api/models/chatgpt_instance.py b/ai_integration_openai_api/models/chatgpt_instance.py new file mode 100644 index 0000000..9c1b15a --- /dev/null +++ b/ai_integration_openai_api/models/chatgpt_instance.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +from odoo import models, fields, api, _ + +class ChatGPTInstance(models.Model): + _name = 'chatgpt.ai.instance' + _description = 'ChatGPT-Compatible Instance Configuration' + _inherit = ['ai.provider.instance', 'ai.generation.params'] + + provider_type = fields.Selection( + selection_add=[('chatgpt', 'ChatGPT-Compatible')], + ondelete={'chatgpt': 'cascade'}) + + # ChatGPT API Parameters + + presence_penalty = fields.Float( + string='Presence Penalty', + help='Penalty for new tokens based on their presence in text. Range: [-2.0 - 2.0].', + default=0.0, + digits=(3, 2)) + + frequency_penalty = fields.Float( + string='Frequency Penalty', + help='Penalty for new tokens based on their frequency in text. Range: [-2.0 - 2.0].', + default=0.0, + digits=(3, 2)) + + # Provider Settings + + def _get_provider_options(self): + """Get ChatGPT-compatible options for API calls.""" + self.ensure_one() + + options = { + 'temperature': self.temperature, + 'top_p': self.top_p, + 'max_tokens': self.max_tokens, + 'presence_penalty': self.presence_penalty, + 'frequency_penalty': self.frequency_penalty, + 'stream': self.stream_response, + } + + if self.stop_sequences: + options['stop'] = [ + seq.strip() + for seq in self.stop_sequences.split(',') + ] + + return options diff --git a/ai_integration_openai_api/models/chatgpt_provider.py b/ai_integration_openai_api/models/chatgpt_provider.py new file mode 100644 index 0000000..8dd2201 --- /dev/null +++ b/ai_integration_openai_api/models/chatgpt_provider.py @@ -0,0 +1,163 @@ +# -*- coding: utf-8 -*- +import json +import logging +import requests +from odoo import models, fields, api, _ +from odoo.exceptions import UserError + +_logger = logging.getLogger(__name__) + +class ChatGPTProvider(models.AbstractModel): + _name = 'ai.provider.chatgpt' + _description = 'ChatGPT-Compatible AI Provider' + _inherit = ['ai.provider'] + + def _get_provider_type(self): + return 'chatgpt' + + def test_connection(self, instance): + """Test the connection to the OpenWebUI server.""" + try: + response = requests.get(f"{instance.host}/api/v1/models") + if response.status_code != 200: + raise UserError(_( + "Failed to connect to AI server. Status code: %s. Error: %s", + response.status_code, response.text + )) + return True + except requests.exceptions.RequestException as e: + raise UserError(_( + "Failed to connect to AI server: %s", str(e) + )) + + def sync_models(self, instance): + """Synchronize available models from the OpenWebUI server.""" + self.test_connection(instance) + + try: + # Get models from provider + response = requests.get(f"{instance.host}/api/v1/models") + models_data = response.json() + + # Get existing models for this instance + existing_models = self.env['ai.model'].search([ + ('provider_instance_id', '=', instance.id) + ]) + existing_identifiers = {m.identifier: m for m in existing_models} + + for model_data in models_data: + identifier = model_data.get('id') + if not identifier: + continue + + # Get model details + model_info = self._get_model_info(instance, identifier) + model_details = model_info.get('details', {}) + + model_values = { + 'name': model_data.get('name', identifier), + 'identifier': identifier, + 'description': model_details.get('description', ''), + 'version': model_details.get('version', ''), + 'provider_instance_id': instance.id, + } + + if identifier in existing_identifiers: + # Update existing model + existing_identifiers[identifier].write(model_values) + else: + # Create new model + self.env['ai.model'].create(model_values) + + return True + + except requests.exceptions.RequestException as e: + raise UserError(_("Error synchronizing models: %s") % str(e)) + + def _get_model_info(self, instance, model_name): + """Get detailed information about a specific model.""" + try: + response = requests.get( + f"{instance.host}/api/v1/models/{model_name}/info" + ) + if response.status_code == 200: + return response.json() + else: + _logger.error( + "Failed to get model info for %s. Status: %s, Error: %s", + model_name, response.status_code, response.text + ) + return {} + except requests.exceptions.RequestException as e: + _logger.error("Error getting model info: %s", str(e)) + return {} + + def _format_chat_messages(self, messages): + """Format chat messages for OpenWebUI API.""" + formatted_messages = [] + for message in messages: + role = message.get('role', 'user') + content = message.get('content', '') + + formatted_messages.append({ + 'role': role, + 'content': content + }) + + return formatted_messages + + def generate_response(self, instance, model, messages, **kwargs): + """Generate a response using the chat completion API.""" + try: + # Format messages for OpenWebUI + formatted_messages = self._format_chat_messages(messages) + + # Get model options from instance + options = instance._get_provider_options() + + # Prepare the request payload + payload = { + 'model': model.identifier, + 'messages': formatted_messages, + **options + } + + # Make the API call + response = requests.post( + f"{instance.host}/api/v1/chat/completions", + json=payload + ) + + if response.status_code != 200: + raise UserError(_("Failed to generate response: %s") % response.text) + + response_data = response.json() + generated_text = response_data.get('choices', [{}])[0].get('message', {}).get('content', '') + + # Update statistics + total_tokens = response_data.get('usage', {}).get('total_tokens', 0) + response_time = response_data.get('response_ms', 0) + version = self._get_model_info(instance, model.identifier)\ + .get('details', {}).get('version', '') + + self._track_model_usage( + model, total_tokens, response_time, version=version + ) + + # Return the response in a standardized format + return { + 'content': generated_text, + 'role': 'assistant', + 'metadata': { + 'total_tokens': total_tokens, + 'response_time': response_time, + 'model_version': version, + 'usage': response_data.get('usage', {}) + } + } + + except requests.exceptions.RequestException as e: + # Log error in statistics + if model: + self._track_model_usage(model, error=True) + raise UserError(_("Error generating response: %s") % str(e)) diff --git a/ai_integration_openai_api/security/ir.model.access.csv b/ai_integration_openai_api/security/ir.model.access.csv new file mode 100644 index 0000000..ca4dd20 --- /dev/null +++ b/ai_integration_openai_api/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_openwebui_ai_instance_user,openwebui.ai.instance user,model_openwebui_ai_instance,base.group_user,1,0,0,0 +access_openwebui_ai_instance_system,openwebui.ai.instance system,model_openwebui_ai_instance,base.group_system,1,1,1,1 diff --git a/ai_integration_openai_api/views/chatgpt_instance_views.xml b/ai_integration_openai_api/views/chatgpt_instance_views.xml new file mode 100644 index 0000000..cd547bb --- /dev/null +++ b/ai_integration_openai_api/views/chatgpt_instance_views.xml @@ -0,0 +1,106 @@ + + + + + chatgpt.ai.instance.form + chatgpt.ai.instance + +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + + chatgpt.ai.instance.tree + chatgpt.ai.instance + + + + + + + + + + + + + chatgpt.ai.instance.search + chatgpt.ai.instance + + + + + + + + + + + + + + + + ChatGPT-Compatible Instances + chatgpt.ai.instance + tree,form + +

+ Create your first ChatGPT-compatible instance +

+

+ Configure instances to connect to ChatGPT-compatible servers (OpenWebUI, ChatGPT, etc). +

+
+
+ + + +
diff --git a/bemade_add_follower_no_sendmail_default/__init__.py b/bemade_add_follower_no_sendmail_default/__init__.py new file mode 100644 index 0000000..02565c9 --- /dev/null +++ b/bemade_add_follower_no_sendmail_default/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +from . import models + + + diff --git a/bemade_add_follower_no_sendmail_default/__manifest__.py b/bemade_add_follower_no_sendmail_default/__manifest__.py new file mode 100644 index 0000000..68ec78f --- /dev/null +++ b/bemade_add_follower_no_sendmail_default/__manifest__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +{ + "name": "Change default when adding follower", + "version": "17.0.0.0.1", + "category": "Extra Tools", + 'summary': 'Change default when adding follower', + "description": """ + Change default when adding follower + Send mail false by default + """, + "author": "Bemade", + 'website': 'https://www.bemade.org', + "depends": [ + 'mail', + ], + "data": [ + ], + "auto_install": False, + "installable": True, + 'license': 'OPL-1' +} diff --git a/bemade_add_follower_no_sendmail_default/migration18.md b/bemade_add_follower_no_sendmail_default/migration18.md new file mode 100644 index 0000000..9e3e516 --- /dev/null +++ b/bemade_add_follower_no_sendmail_default/migration18.md @@ -0,0 +1,87 @@ +# Migration vers Odoo 18.0 - bemade_add_follower_no_sendmail_default + +## Description du module +Ce module modifie le comportement par défaut du wizard d'ajout de followers pour que l'option "Envoyer un email" soit désactivée par défaut. + +## Analyse technique +- Dépendances : mail +- Modèles modifiés : + - mail.wizard.invite : Modification de la valeur par défaut du champ send_mail à False +- Implémentation actuelle : + - Hérite de mail.wizard.invite + - Redéfinit uniquement le champ send_mail avec default=False + +## Alternatives Natives + +### Configuration Système +1. Vérifier dans Odoo 18.0 : + - Paramètres de configuration du module mail + - Paramètres système (ir.config_parameter) + - Préférences utilisateur + +### Approches Alternatives +1. Configuration par utilisateur : + - Ajouter une préférence utilisateur dans res.users + - Utiliser cette préférence comme valeur par défaut + +2. Configuration par type de document : + - Ajouter un paramètre dans les paramètres de notification par modèle + - Permettre une configuration plus granulaire + +## Recommandations pour la Migration + +### Approche "Vanilla First" +1. Évaluer les alternatives natives : + - [X] Vérifier si Odoo 18.0 a ajouté une configuration similaire + - [ ] Explorer les nouvelles fonctionnalités de notification + - [ ] Vérifier les paramètres de notification par défaut + +2. Si aucune alternative native n'existe : + - [ ] Considérer l'ajout d'une configuration système + - [ ] Implémenter une solution plus flexible (par utilisateur ou par type de document) + +### Modifications Techniques +1. Si le module est conservé : + - [ ] Mettre à jour la version dans __manifest__.py + - [ ] Vérifier la compatibilité de l'héritage du wizard + - [ ] Vérifier si le champ send_mail existe toujours et a le même comportement + - [ ] Adapter le code aux nouvelles conventions Odoo 18.0 + +2. Si migration vers une solution native : + - [ ] Créer un module de migration pour la transition + - [ ] Migrer les configurations existantes + - [ ] Prévoir un plan de désactivation du module + +## Fonctionnalité Native dans Odoo 18.0 +✅ La fonctionnalité existe nativement dans Odoo 18.0 ! + +Dans le modèle `mail.wizard.invite` (`mail/wizard/mail_wizard_invite.py`), le champ `notify` est déjà défini avec `default=False` : +```python +notify = fields.Boolean('Notify Recipients', default=False) +``` + +## Plan de Migration + +### Actions Requises +1. **Désactivation du Module** : + - [ ] Désactiver le module avant la migration vers Odoo 18.0 + - [ ] Vérifier qu'aucun autre module ne dépend de celui-ci + - [ ] Informer les utilisateurs que le comportement est maintenant natif + +2. **Vérification** : + - [ ] Tester le comportement natif dans Odoo 18.0 + - [ ] Confirmer que le comportement par défaut est identique + - [ ] Documenter tout changement d'interface utilisateur + +## État de la Migration +🟢 Pas de migration nécessaire - Utiliser la fonctionnalité native + +## Notes Importantes +- Le comportement souhaité (notification désactivée par défaut) est maintenant le comportement standard d'Odoo 18.0 +- L'interface utilisateur est similaire, utilisant un widget boolean_toggle +- Aucune personnalisation supplémentaire n'est nécessaire + +## Prochaines Étapes +1. Planifier la désactivation du module +2. Informer les utilisateurs du changement +3. Retirer le module de la liste des dépendances des autres modules si nécessaire \ No newline at end of file diff --git a/bemade_add_follower_no_sendmail_default/models/__init__.py b/bemade_add_follower_no_sendmail_default/models/__init__.py new file mode 100644 index 0000000..cd332dd --- /dev/null +++ b/bemade_add_follower_no_sendmail_default/models/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from . import mail_wizard_invite diff --git a/bemade_add_follower_no_sendmail_default/models/mail_wizard_invite.py b/bemade_add_follower_no_sendmail_default/models/mail_wizard_invite.py new file mode 100644 index 0000000..fb352d8 --- /dev/null +++ b/bemade_add_follower_no_sendmail_default/models/mail_wizard_invite.py @@ -0,0 +1,11 @@ +# Copyright Bemade.org +from odoo import models, fields, api + + +class MailWizardInviteDefault(models.TransientModel): + _inherit = 'mail.wizard.invite' + + send_mail = fields.Boolean( + default=False, + help="If true, an invitation email will be sent to the recipient" + ) diff --git a/bemade_attachments_cleanup/__init__.py b/bemade_attachments_cleanup/__init__.py new file mode 100644 index 0000000..4027237 --- /dev/null +++ b/bemade_attachments_cleanup/__init__.py @@ -0,0 +1 @@ +from . import wizard diff --git a/bemade_attachments_cleanup/__manifest__.py b/bemade_attachments_cleanup/__manifest__.py new file mode 100644 index 0000000..2d18b49 --- /dev/null +++ b/bemade_attachments_cleanup/__manifest__.py @@ -0,0 +1,22 @@ +{ + 'name': "Attachment Cleanup", + 'version': '1.0.1', + 'author': "Bemade", + 'website': "http://www.bemade.org", + 'category': 'Tools', + 'license': 'OPL-1', + 'summary': "Find and delete missing attachments", + 'description': """ + This module adds a wizard in the settings menu to find and delete missing attachments. + The wizard lists all attachments that are not found in the filestore, and allows the user to select and delete them. + Please use this tool with caution, as deleting attachments cannot be undone. + """, + 'depends': ['base'], + 'data': [ + 'wizard/attachment_cleanup_wizard_view.xml', + 'security/ir.model.access.csv', + + ], + 'installable': True, + 'application': False, +} diff --git a/bemade_attachments_cleanup/i18n/fr_CA.po b/bemade_attachments_cleanup/i18n/fr_CA.po new file mode 100644 index 0000000..5b3b39b --- /dev/null +++ b/bemade_attachments_cleanup/i18n/fr_CA.po @@ -0,0 +1,37 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * attachment_cleanup +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-06-08 12:00+0000\n" +"PO-Revision-Date: 2023-06-08 12:00+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: fr_CA\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: \n" + +#. module: attachment_cleanup +#: model:ir.actions.act_window,name:attachment_cleanup.action_open_attachment_cleanup_wizard +msgid "Cleanup Attachments" +msgstr "Nettoyer les pièces jointes" + +#. module: attachment_cleanup +#: model:ir.ui.view,arch_db:attachment_cleanup.view_attachment_cleanup_wizard +msgid "Delete" +msgstr "Supprimer" + +#. module: attachment_cleanup +#: model:ir.ui.view,arch_db:attachment_cleanup.view_attachment_cleanup_wizard +msgid "Cancel" +msgstr "Annuler" + +#. module: attachment_cleanup +#: model:ir.model.fields,field_description:attachment_cleanup.field_attachment_cleanup_wizard_attachment_ids +msgid "Missing Attachments" +msgstr "Pièces jointes manquantes" diff --git a/bemade_attachments_cleanup/migration18.md b/bemade_attachments_cleanup/migration18.md new file mode 100644 index 0000000..9eeb92b --- /dev/null +++ b/bemade_attachments_cleanup/migration18.md @@ -0,0 +1,31 @@ +# Migration vers Odoo 18.0 - bemade_attachments_cleanup + +## Description +Module de nettoyage des pièces jointes obsolètes + +## Fonctionnalités Ajoutées +- Suppression automatique des pièces jointes non utilisées +- Configuration des règles de nettoyage +- Historique des suppressions + +## Modèles et Champs Modifiés +- ir.attachment + - Ajout du champ cleanup_date (date) + - Ajout du champ cleanup_reason (text) + +## Statut Migration +- [ ] A migrer +- [ ] En cours +- [ ] Migré + +## Détails Migration +- Vérifier si la fonctionnalité existe déjà dans Odoo 18.0 +- Analyser les impacts sur les workflows existants + +## Actions Requises +- [ ] Vérifier la compatibilité avec Odoo 18.0 +- [ ] Tester les fonctionnalités +- [ ] Mettre à jour la documentation + +## Notes +- Ce module pourrait être remplacé par une configuration native dans Odoo 18.0 \ No newline at end of file diff --git a/bemade_attachments_cleanup/security/ir.model.access.csv b/bemade_attachments_cleanup/security/ir.model.access.csv new file mode 100644 index 0000000..735f299 --- /dev/null +++ b/bemade_attachments_cleanup/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_attachment_cleanup_wizard,access.attachment.cleanup.wizard,model_attachment_cleanup_wizard,base.group_system,1,1,1,1 diff --git a/bemade_attachments_cleanup/wizard/__init__.py b/bemade_attachments_cleanup/wizard/__init__.py new file mode 100644 index 0000000..c5856e0 --- /dev/null +++ b/bemade_attachments_cleanup/wizard/__init__.py @@ -0,0 +1 @@ +from . import attachment_cleanup_wizard diff --git a/bemade_attachments_cleanup/wizard/attachment_cleanup_wizard.py b/bemade_attachments_cleanup/wizard/attachment_cleanup_wizard.py new file mode 100644 index 0000000..f2446a3 --- /dev/null +++ b/bemade_attachments_cleanup/wizard/attachment_cleanup_wizard.py @@ -0,0 +1,45 @@ +from odoo import api, fields, models +import os + + +class AttachmentCleanupWizard(models.TransientModel): + _name = 'attachment.cleanup.wizard' + _description = 'Attachment Cleanup Wizard' + + attachment_ids = fields.Many2many( + 'ir.attachment', + string='Missing Attachments', + readonly=True, + help='Attachments that are not found in the filestore' + ) + + @api.model + def default_get(self, fields_list): + res = super(AttachmentCleanupWizard, self).default_get(fields_list) + + attachments_1 = self.sudo().env['ir.attachment'].search([]) + + self._cr.execute("SELECT id FROM ir_attachment WHERE company_id = %s", (self.env.company.id,)) + + attachments = self._cr.fetchall() + + filestore_location = self.env['ir.attachment']._filestore() + + missing_attachments = [] + for attachment_id in attachments: + # if attachment.id == 47984: + # print(attachment.store_fname) + attachment = self.env['ir.attachment'].browse(attachment_id) + + if attachment.store_fname: + file_path = os.path.join(filestore_location, attachment.store_fname) + if not os.path.exists(file_path): + missing_attachments.append(attachment.id) + + if missing_attachments: + res.update({'attachment_ids': [(6, 0, missing_attachments)]}) + return res + + def action_cleanup_attachments(self): + self.ensure_one() + self.attachment_ids.unlink() diff --git a/bemade_attachments_cleanup/wizard/attachment_cleanup_wizard_view.xml b/bemade_attachments_cleanup/wizard/attachment_cleanup_wizard_view.xml new file mode 100644 index 0000000..b652785 --- /dev/null +++ b/bemade_attachments_cleanup/wizard/attachment_cleanup_wizard_view.xml @@ -0,0 +1,32 @@ + + + attachment.cleanup.wizard.view + attachment.cleanup.wizard + +
+ + + + + + + + +