Commit graph

33 commits

Author SHA1 Message Date
GitHub Copilot
6ba554c91b [ADD] website_sale_aplicoop: online payment per group order
Members can now pay their eskaera at checkout, through the standard Odoo
payment machinery. Enabled per group order with a new `online_payment`
boolean, off by default: an order without it behaves exactly as before,
members save a draft and the cutoff cron confirms them in bulk.

The flow mirrors website_sale's: the checkout button becomes "Confirm and
pay", saving the cart redirects to a new /eskaera/<slug>/payment step that
renders `payment.form` from `sale`'s `_get_payment_values`, and the standard
/my/orders/<id>/transaction route takes it from there. This addon ships no
provider and configures none; the co-op publishes whichever it wants.

`website_sale`'s `_get_shop_payment_values` is deliberately not reused: it
runs `_get_shop_payment_errors`, which blocks on shippable products without a
delivery method — exactly an eskaera order, collected at the co-op with no
carrier. For the same reason the transaction route stays the portal one,
which does not call `_check_cart_is_ready_to_be_paid()`.

Payment confirms the order, which has three consequences handled here:

* `payment.transaction._check_amount_and_confirm_order` now confirms group
  orders with `from_orderpoint=True`, the way the cutoff cron already does.
  Without it a product with a broken replenishment route raises inside
  `_post_process`, and `/payment/status/poll` rolls back and re-raises: the
  member sees a payment error over a `done` transaction and the retry cron
  fails forever.
* `_confirm_linked_sale_orders` also sweeps the cycle's already confirmed
  orders into the picking batch, scoped by `pickup_date`. Its early return on
  "no drafts" ran before any batching, so a fully prepaid cycle produced no
  batch at all. `_cron_batch_paid_orders_of_closed_cycles` covers the same
  hole for cycles closed by hand.
* A duplicate-order guard answers 409 on save-order, add-to-cart and
  load-draft, and shows a notice on the shop, so a member whose order is
  already placed cannot build and pay for a second one.

The payment policy lives in the model rather than the controller: there are
three sale.order creation paths and two are live, so `_compute_require_payment`
and `_compute_prepayment_percent` are extended instead of patching five vals
dicts. Orders are also created under the group order's company, which is what
filters the payment providers.

Along the way: eskaera drafts were invisible in /my/orders. The portal rule is
`message_partner_ids child_of` and sale.order only subscribes the customer on
send or confirm, never on a draft create, so the `_prepare_orders_domain`
override that includes drafts never had any effect. Fixed with an explicit
`message_subscribe`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 21:59:35 +02:00
GitHub Copilot
158933e96e [FIX] website_sale_aplicoop: two draft-reuse and compute-field bugs
- group.order.home_delivery is a stored compute field with no inverse;
  Odoo still lets write() set it directly, so a stray direct write stuck
  instead of always being re-derived from delivery_product_id. create()
  and write() now strip it from vals, same pattern already used for slug.

- _find_recent_draft_order only bounded drafts by create_date, so a
  freshly created draft for a stale/previous pickup_date (but created
  "now") was wrongly reused. Fix requires both create_date to fall in
  the active window AND pickup_date to match when set — the latter
  alone isn't enough either, per the regression already covered by
  test_find_recent_draft_excludes_previous_cycle (observed in
  production at stage.elikabilbo.eus).
2026-08-13 12:02:33 +02:00
GitHub Copilot
157b344d4b [FIX] website_sale_aplicoop: three defects in the group order slug
- create(): slugs were only checked against the database, so records created
  in the same batch (duplicating several orders from the list view, importing
  rows sharing a name) collided on group_order_slug_uniq. _generate_unique_slug
  now also skips the slugs already handed out in the batch.
- _redirect_to_slug_url(): `post` is passed as a dict instead of splatted, so a
  query parameter named `suffix` no longer binds to the keyword argument of the
  same name (HTTP 500 on /eskaera/<id>/checkout?suffix=x, and a corrupted path
  on the shop route).
- post-migrate: the backfill runs with tracking_disable, `slug` is tracked and
  the upgrade posted a chatter message on every pre-existing order.

Tests for the two reachable cases: batch create and query parameters kept
across the legacy redirect.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 22:53:09 +02:00
GitHub Copilot
23edee6154 [IMP] website_sale_aplicoop: readable slug in the group order URL
Group order pages were published under their database id (`/eskaera/1`), which
says nothing to the member opening the link. `group.order` gains a `slug` field
and the public pages move to `/eskaera/<slug>` (e.g. `/eskaera/escola-fructuos`).

The slug is generated from the order name on creation, is unique, and can be
edited under the name on the form; emptying it regenerates it from the current
name. Values that would make the order unreachable are rejected: a plain number
(the legacy numeric URLs win that match) and the static routes served under
`/eskaera/` (`labels`, `save-order`, `i18n`, ...).

`/eskaera/<id>` and `/eskaera/<id>/checkout` are kept as redirects to their slug
URL, so the links already shared with members keep working. The AJAX endpoints
(`load-page`, `save-order`, `confirm`, ...) stay numeric: they never show up in
the address bar. Consequently the frontend now reads the order id from the
`data-order-id` attribute only, as the URL no longer carries it.

The post-migration script fills the slug of the orders that already existed.

Renaming the `/eskaera` prefix itself (`/escolas` for a schools deployment)
needs no code: a *308 Redirect / Rewrite* rule per public route in Website >
Configuration > Redirects serves the pages on the new prefix, rewrites the
links in the templates and redirects the old URLs, per website. Documented in
`readme/CONFIGURE.rst`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 16:49:54 +02:00
GitHub Copilot
aba22fd230 [IMP] website_sale_aplicoop: automate non-weekly group order cycles
One-time, biweekly and monthly group orders now follow the same cron
confirmation flow as weekly ones (confirm sale orders + batch pickings
when the cycle cutoff passes):

- Biweekly/monthly keep the cutoff_day/pickup_day weekday scheme on a
  recurrence grid anchored at start_date (creation date as fallback):
  cutoffs advance +14 days / +1 month snapped to cutoff_day, with
  catch-up after cron downtime. Previously they behaved as weekly.
- One-time orders (specials/promotions) are driven by end_date
  (cutoff_date = end_date); once passed, the cron confirms, batches
  and closes the group order.
- end_date keeps its "empty = permanent" meaning for recurring orders.
- Website draft-cart lookup window is now period-aware instead of
  assuming a 6-day weekly cycle.
- New cron tests for once/biweekly/monthly cycles; i18n es/eu updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 16:26:14 +02:00
GitHub Copilot
1d6747e703 [IMP] website_sale_aplicoop: filter and cap stock using forecasted net qty
Group orders are not confirmed until the cutoff date, so draft/sent
sale.order lines never generate stock.moves and are invisible to
virtual_available. This change makes the shop aware of that demand.

- group.order._compute_draft_sale_demand: queries sale.order.line in
  draft/sent state (mirroring sale_stock forecasted report logic) and
  returns pending demand per product.id in the product's own UoM.
- _get_products_for_group_order: delegates to new _apply_stock_filter_and_sort
  which excludes storable products whose forecasted net qty
  (virtual_available − draft demand) <= 0, unless allow_out_of_stock_order.
- _compute_stock_ribbons: reads draft_demand_by_product from ORM context
  so is_out_of_stock / is_low_stock / dynamic_ribbon_id reflect net qty.
- Controller: new _prepare_draft_stock_data helper calculates demand once
  per request, injects context, and builds product_max_qty dict. Applied
  in eskaera_shop, load_eskaera_page and load_products_ajax.
- Template: qty input gets max and data-max-qty from product_max_qty.
- JS: blocks add-to-cart if requested quantity exceeds data-max-qty.
- Fixes type check: type=='consu' → is_storable=True (Odoo 18 semantics).
- 21 new tests in test_forecasted_stock.py covering demand calculation,
  ribbon logic with context, and group order filtering.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 18:18:40 +02:00
GitHub Copilot
87dcb4e350 cron better exception management. show supplier commercial name in product card template. linters config 2026-06-02 10:43:54 +02:00
GitHub Copilot
9d614ed79e [FIX] website_sale_aplicoop: auto-derive home_delivery from delivery_product_id
Make group.order.home_delivery a computed stored field that is True
whenever delivery_product_id is set, eliminating the manual checkbox
that could be forgotten. This fixes sale.order.home_delivery not being
set when the delivery product was configured but the boolean was left
unchecked.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 17:37:33 +02:00
snt
a997331c2d lint: fix linter warnings (log exceptions, disable attribute-string-redundant, suppress C901 where necessary) 2026-05-20 16:05:14 +02:00
snt
3ca90578ae [IMP] website_sale_aplicoop: Validar disponibilidad de productos al cargar órdenes históricas
- Backend: Agregar método _validate_items_for_group_order() para validar que los productos históricos sigan siendo disponibles en la orden de grupo actual
- Backend: Modificar load_order_from_history() para filtrar solo items disponibles antes de pasar al template
- Backend: Generar mensaje de aviso traducido cuando hay productos no disponibles
- Template: Pasar información de productos no disponibles y warnings al JavaScript
- Frontend: Mostrar notificación de advertencia si hubo productos excluidos durante la carga histórica
- Notas: Esto evita cargar productos que ya no existen en la orden actual debido a cambios en categorías, proveedores o listas negras
2026-05-20 16:05:14 +02:00
snt
828278573d Fix stock picking batch date and does not split batchs by consumer group 2026-05-20 16:05:14 +02:00
snt
ce393b6034 [FIX] TestConfirmEskaera_Integration: limpieza de decoradores @patch y corrección de bugs
- Convertir 4 tests de decorador @patch a context manager 'with patch(...)' para evitar RuntimeError en LocalProxy de Werkzeug
- Corregir patrón env(user=..., context=dict(...)) en Odoo 18 (sin .with_context())
- Agregar website real al mock para integración con helpers de pricing (_get_pricing_info)
- Añadir pickup_date en fixture de existing_order para que _find_recent_draft_order localice correctamente
- BUGFIX: Agregar (5,) a order_line para limpiar líneas previas al actualizar pedido existente

Resultado: 0 failed, 0 errors de 4 tests en Docker para TestConfirmEskaera_Integration

BREAKING: _create_or_update_sale_order ahora limpia las líneas anteriores con (5,) antes de asignar las nuevas cuando se actualiza un pedido existente. Comportamiento previo (duplicación de líneas) era un bug.
2026-04-08 17:26:57 +02:00
snt
7d11a95344 [FIX] website_sale_aplicoop: freeze pickup dates on cron confirm 2026-03-31 20:07:50 +02:00
snt
331a2e8944 [FIX] website_sale_aplicoop: harden group order cron 2026-03-31 19:36:20 +02:00
snt
12d434d4c7 [IMP] website_sale_aplicoop: simplify cutoff guard in _confirm_linked_sale_orders
All group orders have cutoff configured; stale orders are already closed/cancelled.
Single guard: skip if cutoff_date >= today (cycle still open).
2026-03-30 19:29:10 +02:00
snt
7fc42625a3 [FIX] website_sale_aplicoop: robust cutoff check in _confirm_linked_sale_orders
Three guards before confirming sale orders:
1. Skip if no cutoff_date (no cutoff_day configured): avoids blindly confirming
   all draft orders for group orders without a weekly cycle defined
2. Skip if cutoff_date >= today: cycle still open
3. Skip if cutoff_date >= 7 days ago: stale date from a previous cycle
   (protects against cron gaps and confirms only within 1-6 days after cutoff)
2026-03-30 19:23:19 +02:00
snt
cd68e8bb5b [FIX] website_sale_aplicoop: revert >= to > condition in _confirm_linked_sale_orders
The cutoff day itself is still open for orders; confirmation should only
happen when the cutoff date is strictly in the past (>= today = skip).
The real bug was only the order of operations in _cron_update_dates.
2026-03-30 19:18:41 +02:00
snt
ac00294623 [FIX] website_sale_aplicoop: fix auto-confirm sale.order on cutoff day
- _cron_update_dates: call _confirm_linked_sale_orders() BEFORE recomputing
  dates so cutoff_date still points to the current cycle when the check runs
- _confirm_linked_sale_orders: change >= to > so orders are confirmed both
  on the cutoff day itself and after it has passed
2026-03-30 19:14:02 +02:00
snt
89c008441e [FIX] Resolver 3 fallos de tests en website_sale_aplicoop
- Fix: delivery product price now includes VAT (homepage/checkout)
  * Added _get_delivery_product_display_price() helper to use same pricing pipeline as regular products
  * Uses pricelist + tax calculations instead of bare list_price
  * Fallback chain: pricelist → bare list_price → default 5.74
  * Updated context in eskaera_shop() and eskaera_checkout()

- Test: test_constraint_cutoff_before_pickup_invalid
  * Constraint removed: now allows any combination of cutoff_day and pickup_day
  * Updated test to reflect this change (no ValidationError expected)

- Test: test_day_names_not_using_inline_underscore
  * Fixed to check sub-template eskaera_order_card_meta where day_names is actually used
  * eskaera_page calls this sub-template so day_names context is inherited

Results: 128 tests - 0 failed, 0 errors (100% pass rate)
2026-03-30 16:21:57 +02:00
snt
e2ced75ecd [ADD] website_sale_aplicoop: create picking batches after cutoff
- Add stock_picking_batch dependency to manifest
- Add cutoff date validation in _confirm_linked_sale_orders()
- Create _create_picking_batches_for_sale_orders() method
- Group pickings by consumer_group_id into separate batches
- Set batch scheduled_date from group order pickup_date
- Add test_cron_picking_batch.py with 7 tests covering:
  - Skip orders before cutoff
  - Confirm orders after cutoff
  - Separate batches per consumer group
  - Same group orders in same batch
  - Batch has scheduled_date
  - No duplicate batches on re-run
  - Closed group orders not processed
2026-03-06 15:45:12 +01:00
snt
2f57a5d14e [IMP] group_order: confirmar sale orders en cron diario 2026-02-27 15:19:41 +01:00
snt
2a005a9d5a feat(website_sale_aplicoop): sistema de ribbons basado en stock
- Añadidos ribbons 'Sin Stock' (rojo) y 'Pocas Existencias' (amarillo)
- Nuevo campo configurable: umbral de stock bajo (default: 5.0)
- Campos computed en product.product:
  * is_out_of_stock: True cuando qty_available <= 0
  * is_low_stock: True cuando 0 < qty_available <= threshold
  * dynamic_ribbon_id: ribbon automático según nivel de stock
- Ordenamiento mejorado: productos con stock primero, sin stock al final
- Template actualizado:
  * Muestra ribbon de stock en tarjeta de producto
  * Deshabilita botón add-to-cart cuando producto sin stock
  * Cambia icono a 'fa-ban' en productos sin stock
- Vista de configuración: campo 'Low Stock Threshold' en Settings > Shop Performance
- Solo aplica a productos type='consu' (almacenables)
- Tests: 112 pasados, 0 fallos
2026-02-25 19:48:39 +01:00
snt
539cd5cccd feat(website_sale_aplicoop): ordenar productos por website_sequence y nombre
- Ordena productos primero por website_sequence y luego alfabéticamente por nombre (case-insensitive)
- Fix test: cambiar tipo de producto de 'product' a 'consu' (Odoo 18 compatibility)
2026-02-25 19:34:49 +01:00
snt
c1226e720b [ADD] website_sale_aplicoop: Category blacklist with recursive exclusion
- Add comprehensive test suite for excluded_category_ids
- 9 tests covering: single category, recursive subcategories,
  parent exclusion, direct product override, unrelated categories,
  empty blacklist, multiple exclusions, combined blacklists,
  available_products_count validation
- Update UI to show excluded_category_ids in 'Productos Excluidos'
- Bump version to 18.0.1.6.0
- Update CHANGELOG with category blacklist documentation

Technical notes:
- Category blacklist was already implemented in model/logic
- This commit adds missing tests and documentation
- Recursive exclusion via get_all_excluded_descendants()
- Blacklist has absolute priority over all inclusion sources
2026-02-22 23:04:33 +01:00
snt
d90f2cdc61 [ADD] website_sale_aplicoop: Supplier blacklist feature for group orders
- Add excluded_supplier_ids field for supplier exclusion
- Filter products by main_seller_id (from product_main_seller addon)
- Blacklist has absolute priority over all inclusion sources
- Products with blacklisted main supplier never appear in orders
- Update _get_products_for_group_order() with supplier blacklist logic
- Add excluded_supplier_ids to 'Productos Excluidos' section in form view
- Add comprehensive test suite (TestSupplierBlacklist class with 9 tests):
  * Test exclusion by main_seller_id
  * Test multiple supplier exclusion
  * Test products without main seller not affected
  * Test blacklist with direct product inclusion
  * Test blacklist priority over supplier inclusion
  * Test combined product and supplier blacklist
  * Test available_products_count with supplier blacklist
- Add Spanish and Euskera translations
- Update available_products_count computation to include excluded_supplier_ids
- Version bump to 18.0.1.5.0

Use case: Exclude all products from specific supplier (e.g., temporary unavailability)
Example: Category with 100 products, exclude supplier X → all products from X excluded
Workflow: Bulk inclusion via categories + supplier-level exclusion + product-level exclusion
2026-02-22 21:35:40 +01:00
snt
75ebb7b907 [ADD] website_sale_aplicoop: Product blacklist feature for group orders
- Add excluded_product_ids field for explicit product exclusion
- Blacklist has absolute priority over all inclusion sources (product_ids, category_ids, supplier_ids)
- Update _get_products_for_group_order() with blacklist filter logic
- Rename 'Associations' section to 'Catálogo de Productos' with subsections:
  * Productos Incluidos (whitelist: suppliers, categories, direct products)
  * Productos Excluidos (blacklist: explicit exclusions)
- Add comprehensive test suite (TestProductBlacklist class with 7 tests)
- Add Spanish and Euskera translations
- Update available_products_count computation to include excluded_product_ids
- Version bump to 18.0.1.4.0

Use case: Bulk inclusion via categories/suppliers + fine-grained exclusion via blacklist
Example: Select a category with 100 products, exclude 5 unwanted → 95 available
2026-02-22 20:41:11 +01:00
snt
cafa19ffea [ADD] website_sale_aplicoop: Add sequence field to group.order for custom ordering
- Add sequence field to group.order model with default value 10
- Update _order to sort by sequence first, then start_date desc
- Add sequence field to tree view with handle widget for drag-and-drop reordering
- Add sequence field to form view for manual editing
- Orders in website list will now be ordered by sequence field
2026-02-21 16:37:55 +01:00
snt
cf9ea887c1 [REF] Code quality improvements and structure fixes
- Add mypy.ini configuration to exclude migration scripts
- Rename migration files to proper snake_case (post-migration.py → post_migration.py)
- Add __init__.py to migration directories for proper Python package structure
- Add new portal access tests for website_sale_aplicoop
- Code formatting improvements (black, isort)
- Update copilot instructions and project configuration

Related to previous code quality refactoring work.
2026-02-21 13:51:25 +01:00
snt
6fbc7b9456 [FIX] website_sale_aplicoop: Remove redundant string= attributes and fix OCA linting warnings
- Remove redundant string= from 17 field definitions where name matches string value (W8113)
- Convert @staticmethod to instance methods in selection methods for proper self.env._() access
- Fix W8161 (prefer-env-translation) by using self.env._() instead of standalone _()
- Fix W8301/W8115 (translation-not-lazy) by proper placement of % interpolation outside self.env._()
- Remove unused imports of odoo._ from group_order.py and sale_order_extension.py
- All OCA linting warnings in website_sale_aplicoop main models are now resolved

Changes:
- website_sale_aplicoop/models/group_order.py: 21 field definitions cleaned
- website_sale_aplicoop/models/sale_order_extension.py: 5 field definitions cleaned + @staticmethod conversion
- Consistent with OCA standards for addon submission
2026-02-18 17:54:43 +01:00
snt
5c89795e30 [IMP] website_sale_aplicoop: Fix mandatory translation linting errors
- Added self.env._() translation to ValidationError in _check_company_groups
- Added self.env._() translation to ValidationError in _check_dates
- Replaced f-strings with .format() for proper lazy translation
2026-02-18 17:46:38 +01:00
snt
8b0a402ccf [FIX] website_sale_aplicoop: Critical date calculation fixes (v18.0.1.3.1)
- Fixed _compute_cutoff_date logic: Changed days_ahead <= 0 to days_ahead < 0 to allow cutoff_date same day as today
- Enabled store=True for delivery_date field to persist calculated values and enable database filtering
- Added constraint _check_cutoff_before_pickup to validate pickup_day >= cutoff_day in weekly orders
- Added @api.onchange methods for immediate UI feedback when changing cutoff_day or pickup_day
- Created daily cron job _cron_update_dates to automatically recalculate dates for active orders
- Added 'Calculated Dates' section in form view showing readonly cutoff_date, pickup_date, delivery_date
- Added 6 regression tests with @tagged('post_install', 'date_calculations')
- Updated documentation with comprehensive changelog

This is a more robust fix than v18.0.1.2.0, addressing edge cases in date calculations.
2026-02-18 17:45:45 +01:00
snt
9000e92324 [DOC] website_sale_aplicoop: Add lazy loading documentation and implement v18.0.1.3.0 feature
- Add LAZY_LOADING.md with complete technical documentation (600+ lines)
- Add LAZY_LOADING_QUICK_START.md for quick reference (5 min)
- Add LAZY_LOADING_DOCS_INDEX.md as navigation guide
- Add UPGRADE_INSTRUCTIONS_v18.0.1.3.0.md with step-by-step installation
- Create DOCUMENTATION.md as main documentation index
- Update README.md with lazy loading reference
- Update docs/README.md with new docs section
- Update website_sale_aplicoop/README.md with features and changelog
- Create website_sale_aplicoop/CHANGELOG.md with version history

Lazy Loading Implementation (v18.0.1.3.0):
- Reduces initial store load from 10-20s to 500-800ms (20x faster)
- Add pagination configuration to res_config_settings
- Add _get_products_paginated() method to group_order model
- Implement AJAX endpoint for product loading
- Create 'Load More' button in website templates
- Add JavaScript listener for lazy loading behavior
- Backward compatible: can be disabled in settings

Performance Improvements:
- Initial load: 500-800ms (vs 10-20s before)
- Subsequent pages: 200-400ms via AJAX
- DOM optimization: 20 products initial vs 1000+ before
- Configurable: enable/disable and items per page

Documentation Coverage:
- Technical architecture and design
- Installation and upgrade instructions
- Configuration options and best practices
- Troubleshooting and common issues
- Performance metrics and validation
- Rollback procedures
- Future improvements roadmap
2026-02-16 18:39:39 +01:00
snt
7cff89e418 Aplicoop desde el repo de kidekoop 2026-02-11 15:32:11 +01:00