diff --git a/.flake8 b/.flake8 index d5708c3..84e6b2d 100644 --- a/.flake8 +++ b/.flake8 @@ -4,7 +4,7 @@ [flake8] max-line-length = 88 -max-complexity = 30 +max-complexity = 16 # B = bugbear # B9 = bugbear opinionated (incl line length) select = C,E,F,W,B,B9 @@ -13,6 +13,12 @@ select = C,E,F,W,B,B9 # W503: line break before binary operator (black-compatible) # B950: line too long (soft limit, complements B) ignore = E203,E501,W503,B950 +# OCA standard per-file ignores: +# - __init__.py imports submodules that are intentionally "unused" (F401) +# - __manifest__.py is a bare dict literal, not a useless expression (B018) +per-file-ignores = + __init__.py: F401 + __manifest__.py: B018 exclude = .git, __pycache__, diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1e09870..946f57c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -110,22 +110,11 @@ repos: - id: flake8 name: flake8 additional_dependencies: ["flake8-bugbear==23.12.2"] - - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.19.1 - hooks: - - id: mypy - # do not run on test files or __init__ files (mypy does not support - # namespace packages) - exclude: (/tests/|/__init__\.py$|^scripts/) - # Exclude migrations explicitly to avoid duplicate-module errors - args: ["--exclude", "(?i).*/migrations/.*"] - additional_dependencies: - - "lxml" - - "odoo-stubs" - - "types-python-dateutil" - - "types-pytz" - - "types-requests" - - "types-setuptools" + # NOTE: mypy is intentionally not configured here. It is not part of the + # OCA pre-commit standard and breaks on Odoo's migration folder layout + # (e.g. ".../migrations/18.0.2.1.0/post-migrate.py" -> not a valid package + # name), because mypy's exclude does not apply to files passed explicitly + # by pre-commit. - repo: https://github.com/PyCQA/pylint rev: v4.0.4 hooks: diff --git a/.pylintrc b/.pylintrc index 6dfee34..774c9c3 100644 --- a/.pylintrc +++ b/.pylintrc @@ -42,7 +42,6 @@ enable=anomalous-backslash-in-string, manifest-deprecated-key, manifest-external-assets, manifest-maintainers-list, - manifest-required-author, manifest-required-key, manifest-required-key-app, manifest-superfluous-key, @@ -53,7 +52,6 @@ enable=anomalous-backslash-in-string, method-search, missing-odoo-file, missing-odoo-file-app, - missing-readme, missing-return, no-raise-unlink, no-search-all, diff --git a/.pylintrc-mandatory b/.pylintrc-mandatory index 277f3b8..598a103 100644 --- a/.pylintrc-mandatory +++ b/.pylintrc-mandatory @@ -26,7 +26,6 @@ enable=anomalous-backslash-in-string, manifest-data-duplicated, manifest-deprecated-key, manifest-maintainers-list, - manifest-required-author, manifest-required-key, manifest-superfluous-key, manifest-version-format, @@ -34,7 +33,6 @@ enable=anomalous-backslash-in-string, method-inverse, method-required-super, method-search, - missing-readme, missing-return, no-raise-unlink, no-write-in-compute, diff --git a/account_invoice_triple_discount/__manifest__.py b/account_invoice_triple_discount/__manifest__.py index 955fe16..5715710 100644 --- a/account_invoice_triple_discount/__manifest__.py +++ b/account_invoice_triple_discount/__manifest__.py @@ -13,5 +13,4 @@ "excludes": ["account_invoice_fixed_discount"], "post_init_hook": "post_init_hook", "data": ["report/invoice.xml", "views/account_move.xml"], - "installable": True, } diff --git a/membership_expiry_reminder/__manifest__.py b/membership_expiry_reminder/__manifest__.py index 6b9a673..4cd0f4b 100644 --- a/membership_expiry_reminder/__manifest__.py +++ b/membership_expiry_reminder/__manifest__.py @@ -13,5 +13,4 @@ "data/ir_cron.xml", "views/res_config_settings_views.xml", ], - "installable": True, } diff --git a/mypy.ini b/mypy.ini deleted file mode 100644 index c69ad73..0000000 --- a/mypy.ini +++ /dev/null @@ -1,18 +0,0 @@ -[mypy] -# Exclude migration scripts (post-migrate.py etc.) from mypy checks to avoid -# duplicate module name errors when multiple addons include scripts with the -# same filename. -# Exclude scripts/ directory from mypy checks (utility scripts, not Odoo code) -exclude = .*/migrations/.*|^scripts/.* - -# Ignore missing imports from Odoo modules -ignore_missing_imports = True - -[mypy-odoo] -ignore_missing_imports = True - -[mypy-odoo.*] -ignore_missing_imports = True - -[mypy-odoo.addons.*] -ignore_missing_imports = True diff --git a/portal_event_registration/__manifest__.py b/portal_event_registration/__manifest__.py index da15c91..e8c861a 100644 --- a/portal_event_registration/__manifest__.py +++ b/portal_event_registration/__manifest__.py @@ -11,8 +11,6 @@ "data": [ "security/security.xml", "security/ir.model.access.csv", - "security/ir.model.access.csv", "views/portal_templates.xml", ], - "installable": True, } diff --git a/product_get_price_helper/__manifest__.py b/product_get_price_helper/__manifest__.py index aafe61c..1ad048c 100644 --- a/product_get_price_helper/__manifest__.py +++ b/product_get_price_helper/__manifest__.py @@ -10,6 +10,5 @@ "author": "ACSONE SA/NV,Odoo Community Association (OCA)", "website": "https://github.com/OCA/product-attribute", "depends": ["account", "product"], - "data": [], "demo": ["demo/account.xml", "demo/pricelist.xml"], } diff --git a/product_main_seller/__manifest__.py b/product_main_seller/__manifest__.py index 76c768f..faa14da 100644 --- a/product_main_seller/__manifest__.py +++ b/product_main_seller/__manifest__.py @@ -16,6 +16,5 @@ "views/view_product_product.xml", "views/view_product_template.xml", ], - "installable": True, "pre_init_hook": "pre_init_hook", } diff --git a/product_price_category/__manifest__.py b/product_price_category/__manifest__.py index 4085f59..3fd306e 100644 --- a/product_price_category/__manifest__.py +++ b/product_price_category/__manifest__.py @@ -18,5 +18,4 @@ "views/product_template.xml", ], "maintainers": ["sbejaoui"], - "installable": True, } diff --git a/purchase_triple_discount/__manifest__.py b/purchase_triple_discount/__manifest__.py index 0eb72d2..c9e6bd1 100644 --- a/purchase_triple_discount/__manifest__.py +++ b/purchase_triple_discount/__manifest__.py @@ -18,5 +18,4 @@ "views/res_partner_view.xml", ], "post_init_hook": "post_init_hook", - "installable": True, } diff --git a/pyproject.toml b/pyproject.toml index f84dd65..8e27141 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,24 +17,4 @@ extend-exclude = ''' )/ ''' -[tool.isort] -profile = "black" -force_single_line = true -line_length = 88 -multi_line_output = 3 -include_trailing_comma = true -force_grid_wrap = 0 -use_parentheses = true -ensure_newline_before_comments = true -skip_glob = ["*/__init__.py"] -known_odoo = ["odoo"] -known_odoo_addons = ["odoo.addons"] -sections = ["FUTURE", "STDLIB", "THIRDPARTY", "ODOO", "ODOO_ADDONS", "FIRSTPARTY", "LOCALFOLDER"] -default_section = "THIRDPARTY" - -[tool.mypy] -# Excluir carpetas de migraciones y archivos de post-migrate.py que usan guiones -# (evita errores de "Duplicate module" en mypy cuando múltiples addons contienen -# archivos con el mismo nombre como `post-migrate.py`). Usamos una expresión -# regular que coincide con cualquier ruta que contenga `/migrations/`. -exclude = "(?i).*/migrations/.*" +# isort config lives in .isort.cfg (single source of truth). diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 2e65587..0000000 --- a/setup.cfg +++ /dev/null @@ -1,21 +0,0 @@ -[flake8] -max-line-length = 88 -max-complexity = 16 -select = C,E,F,W,B,B9 -ignore = E203,E501,W503,B950 -exclude = scripts/, .venv/ - -[isort] -profile = black -force_single_line = True -line_length = 88 -multi_line_output = 3 -include_trailing_comma = True -force_grid_wrap = 0 -use_parentheses = True -ensure_newline_before_comments = True -skip_glob = */__init__.py -known_odoo=odoo -known_odoo_addons=odoo.addons -sections=FUTURE,STDLIB,THIRDPARTY,ODOO,ODOO_ADDONS,FIRSTPARTY,LOCALFOLDER -default_section=THIRDPARTY diff --git a/website_sale_aplicoop/controllers/website_sale_pricing.py b/website_sale_aplicoop/controllers/website_sale_pricing.py index caeae9c..6266d5e 100644 --- a/website_sale_aplicoop/controllers/website_sale_pricing.py +++ b/website_sale_aplicoop/controllers/website_sale_pricing.py @@ -246,7 +246,7 @@ def _get_product_supplier_info(self, products): supplier_name = "" if product.seller_ids: partner = product.seller_ids[0].partner_id.sudo() - supplier_name = partner.name or "" + supplier_name = partner.company_name or partner.name or "" if partner.city: supplier_name += f" ({partner.city})" product_supplier_info[product.id] = supplier_name diff --git a/website_sale_aplicoop/models/group_order.py b/website_sale_aplicoop/models/group_order.py index c873c13..7b3ed6a 100644 --- a/website_sale_aplicoop/models/group_order.py +++ b/website_sale_aplicoop/models/group_order.py @@ -10,9 +10,6 @@ from odoo import models from odoo.exceptions import ValidationError _logger = logging.getLogger(__name__) -# Pylint: explicit 'string' attributes are intentional for readable labels in views. -# Some linters flag these as redundant; disable that specific check here. -# pylint: disable=attribute-string-redundant class GroupOrder(models.Model): @@ -945,6 +942,7 @@ class GroupOrder(models.Model): # route configuration doesn't block all remaining orders. confirmed_sale_orders = SaleOrder.browse() failed_sale_orders = SaleOrder.browse() + failure_reasons = {} for sale_order in sale_orders: try: @@ -953,9 +951,18 @@ class GroupOrder(models.Model): # route issues. This cron confirms business orders first # and handles stock exceptions operationally. sale_order.with_context(from_orderpoint=True).action_confirm() - confirmed_sale_orders |= sale_order - except Exception: + # Flush INSIDE the savepoint. Odoo raises many + # ValidationErrors lazily at flush/recompute time, which + # would otherwise escape this savepoint (it has already + # been released on block exit) and poison the + # transaction, aborting every remaining sale order. By + # forcing the flush here, a failing order is rolled back + # to its savepoint and the loop continues cleanly. + self.env.flush_all() + confirmed_sale_orders |= sale_order + except Exception as exc: failed_sale_orders |= sale_order + failure_reasons[sale_order.id] = str(exc) or exc.__class__.__name__ _logger.exception( "Cron: Error confirming sale order %s (%s) for group order %s (%s). " "Order skipped; remaining orders will continue.", @@ -965,9 +972,12 @@ class GroupOrder(models.Model): self.name, ) + batches = self.env["stock.picking.batch"] if confirmed_sale_orders: # Create picking batches only for confirmed sale orders - self._create_picking_batches_for_sale_orders(confirmed_sale_orders) + batches = self._create_picking_batches_for_sale_orders( + confirmed_sale_orders + ) self._log_missing_procurement_warnings(confirmed_sale_orders) if failed_sale_orders: @@ -980,6 +990,9 @@ class GroupOrder(models.Model): self.name, failed_sale_orders.ids, ) + self._post_failed_sale_orders_message( + batches, failed_sale_orders, failure_reasons + ) except Exception: _logger.exception( "Cron: Error confirming sale orders for group order %s (%s)", @@ -987,6 +1000,66 @@ class GroupOrder(models.Model): self.name, ) + def _post_failed_sale_orders_message( + self, batches, failed_sale_orders, failure_reasons=None + ): + """Post a note on the picking batch wall about sale orders that failed. + + Operators read the batch chatter to follow up manually on orders that + could not be confirmed (and are therefore NOT part of the batch). + + Args: + batches: stock.picking.batch recordset to notify (may be empty). + failed_sale_orders: sale.order recordset that failed to confirm. + failure_reasons: optional {sale_order_id: error message} mapping. + """ + self.ensure_one() + if not failed_sale_orders: + return + + failure_reasons = failure_reasons or {} + from markupsafe import Markup + from markupsafe import escape + + items = Markup() + for sale_order in failed_sale_orders: + reason = failure_reasons.get(sale_order.id) + label = sale_order.name or self.env._("(unnamed)") + partner = sale_order.partner_id.display_name or "" + item = escape(f"{label} ({partner})") if partner else escape(label) + if reason: + item += Markup(": ") + escape(reason) + items += Markup("