Commit graph

165 commits

Author SHA1 Message Date
GitHub Copilot
40c92613d9 [FIX] website_sale_aplicoop: show the full product name on the cards
The card title was clamped to two lines with an ellipsis, so longer names
were cut off. Drop the -webkit-line-clamp and the overflow:hidden; keep the
two-line min-height so short names still reserve the space and the grid rows
stay aligned, while longer names grow the card downward.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 19:51:07 +02:00
GitHub Copilot
394f9a60a4 [FIX] website_sale_aplicoop: payment-neutral label on the checkout button
The cart button that opens /eskaera/<slug>/checkout read "Proceed to
Checkout", and the curated eu/ca translations went further ("Ordaintzera
Joan", "Finalitza la Comanda"), all implying a payment step. With
instances running both with and without online payment, revert it to the
neutral "Review Order" in the source string, the JS labels and the es/eu/ca
translations.

Changing the msgid also drops the stale DB translation on update, so the
instance that still showed "Ir al pago" picks up the fresh term.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 19:33:43 +02:00
GitHub Copilot
512dd9f00d [FIX] website_sale_aplicoop: restore the compact metrics on product tags
.badge-km seeds --badge-padding-*, --badge-font-* and --badge-border-radius,
but so does Bootstrap's own .badge rule, and this file is @imported — which
the spec hoists above the rest of the bundle, so at equal specificity
Bootstrap always came later and won. The tags rendered at Bootstrap's
default metrics instead of the compact ones written here. Qualify the
selector as .badge.badge-km (0,2,0).

Measured on the card: 7.8/4.2px padding, 12px, 4px radius -> 4/2px, 11px,
--ac-radius-sm, the values the rule asks for.

The colour half needs no qualifier and keeps its own selector reach: Odoo
only reads --background-color/--color/--bg-solid, it never sets them.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 19:21:42 +02:00
GitHub Copilot
e3140817a3 [FIX] website_sale_aplicoop: restore the tag colours and filter states
Tags rendered grey everywhere and the selected/dimmed filter states were
invisible: Odoo 18 paints every .badge from --background-color / --color /
--bg-solid and marks all three declarations !important
(website/static/src/scss/website.scss, "// Badges"), so the plain
background-color / color / border declarations here never landed, however
specific the selector. Set those three custom properties instead.

Same origin as the notification fix: before 5de7573 realtime_search.js
wrote the colours as inline styles with setProperty(..., "important"),
which outranks a stylesheet !important. Moving them into a class dropped
that priority, and the core rule took over.

Note this does not cover the --badge-padding-*/--badge-font-* values in
.badge-km, which Bootstrap's own .badge rule still wins on source order —
the badges keep Bootstrap's default metrics, as they do today.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 19:19:03 +02:00
GitHub Copilot
a029fd9265 [FIX] website_sale_aplicoop: restore the floating notifications
Every error and confirmation popup was rendering off-screen. This addon's
CSS partials reach the page through @import, which the spec hoists above
everything else in the bundle, so an equal-specificity Bootstrap rule
always comes later and wins: .alert (0,1,0) beat .eskaera-notification
(0,1,0) on `position`, pinning the notification to `relative` and leaving
it in normal flow at the foot of <body>, below the footer. Qualifying the
selector as .alert.eskaera-notification (0,2,0) settles it without
!important; padding moves onto Bootstrap's own --alert-padding-* custom
properties, as .badge-km and .product-card > .card already do.

Present since the notification moved from a JS inline style to a class
(5de7573) — an inline style outranks any stylesheet rule, so the class
never inherited the specificity the inline style had.

Also fix the announcement while in the same helper: the urgency test read
`type === "error"`, which no caller passes (the type has to name a
Bootstrap variant, so failures arrive as "danger"), so every error was
announced politely instead of assertively.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 18:53:58 +02:00
GitHub Copilot
96b2e8eedd [FIX] website_sale_aplicoop: drop dead /eskaera/labels fetch, quiet a log
The "Available Orders" page fetched /eskaera/labels with no request body
against a type="json" route, which Odoo can't parse (400 "Invalid JSON
data" on every load). The script also targeted #realtime-category-select,
an element that only exists on the shop page, so it never did anything
useful there anyway: the shop page already re-translates that option via
_initializeTooltips() in website_sale.js. Delete the dead script instead
of fixing a call that was never reachable.

infinite_scroll.js also logged console.error for the expected case of
running on a page with no product grid (this script loads site-wide);
downgrade to console.log since it's not an error.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 18:21:52 +02:00
GitHub Copilot
6a59d9f6ad [IMP] website_sale_aplicoop: compact mobile layout for the Eskaera shop
Reorder cart/tags/category/search/products into one flex row with
Bootstrap order utilities, so mobile shows cart, tags, category, search,
then products, while desktop keeps its current layout. Product cards
become a two-column grid of compact tiles from the smallest phones, with
a 4:3 photo instead of a tall rectangle. Origin, supplier and tags move
behind a per-product "i" toggle instead of always reserving their own
row, which also drops the now-unused any_product_has_tags plumbing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 15:24:34 +02:00
GitHub Copilot
ef1283be7c [REF] stock_picking_batch_collect: rename and drop the aplicoop dependency
"custom" said nothing about what the module does. It is really about collecting
goods into baskets: the extra detailed-operation columns, the is_collected flag,
the Product Summary tab, the per-company validation restrictions and the
Basket Assembly operator view all serve that one job. Rename it accordingly.

Invert the dependency while at it. A generic warehouse addon was dragging in an
entire eCommerce application, and the whole coupling was a single field:
stock.move.line.home_delivery, related to picking_id.home_delivery. Everything
else was already duck-typed. website_sale_aplicoop now depends on this module
and injects its own consumer group columns into these views.

This removes duplicated logic rather than relocating it: stock.picking
.batch_consumer_group_id re-derived from sale_id a value aplicoop already stored
as stock.picking.consumer_group_id, and the duplicate carried no @api.depends,
so it never recomputed reliably. The batch transfers list now shows the stored
field, which is sortable and groupable.

The two aplicoop tests that probed information_schema for the res_company
batch_* columns can drop that guard: a real dependency guarantees them.

Renaming an addon is not something a migrations/ script can do, since a renamed
addon is a brand new module to Odoo and its migration scripts never run. A
pre_init_hook does it instead: it fires on install after the Python is imported
but before registry.load(), which is the window where remapping ir_model_data
makes Odoo reuse the existing tables and columns. is_collected, the summary line
table and the company settings all survive untouched.

Two details the hook has to get right:

- ir_model_constraint.module and ir_model_relation.module are integer FKs with
  ON DELETE CASCADE, so they must be repointed before the old module row is
  deleted or the bookkeeping goes with it.
- Deleting an ir_model_data row does not cascade to the record it points at.
  Artifacts handed over to aplicoop only need the xmlid dropped, but artifacts
  that disappear need the record deleted too, or the field survives as an orphan
  manual field and the view as a custom view referencing it.

Verified against a restored copy of the dev database: 50 xmlids moved, 19 summary
lines and 5 collected move lines preserved, no orphans, both test suites green,
and the module installs cleanly on a database without website_sale_aplicoop.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 14:26:47 +02:00
GitHub Copilot
e625b0c2f3 [IMP] website_sale_aplicoop: pay on the checkout page, not a step later
The separate /eskaera/<slug>/payment step is gone. Members review the
summary, choose home delivery and pick a payment method on the checkout,
in one screen; the old URL redirects there so bookmarks and sessions that
were mid-flow do not hit a 404.

The checkout now renders the member's draft sale.order instead of the
localStorage cart. That is what fixes the products appearing "out of
nowhere" between the two pages: the summary was a snapshot of localStorage
taken at page load, and `_autoLoadDraftOnInit` then pulled the draft back
into localStorage without re-rendering. Deleting a product in the shop
removed it from the cart but left the line on the draft, so the autoload
resurrected it, the confirm button sent it back, and it only became
visible one page later. The checkout no longer auto-loads the draft — it
renders it, and what it shows is what the payment form charges.

"Proceed to Checkout" pushes the cart to that draft before navigating.
Saving is idempotent: `_merge_or_replace_draft` reuses the cycle's draft
and, through the new `_draft_matches_lines`, rewrites `order_line` only
when the lines actually differ — replacing them unlinks and recreates
every one of them, which is pure churn when nothing changed.

The home delivery checkbox goes through the new /eskaera/set-home-delivery
so the delivery line moves on the order itself. Writing only to
localStorage would have changed the summary and left the amount alone,
which with online payment on is the amount being charged.

Also fixes the confirmation notice nobody ever saw: saving answered with
the payment step URL and the frontend followed it immediately, destroying
the toast in the same tick. Saving no longer navigates; the caller decides
whether it is staying or moving on.

Along the way: checkout_labels.js and the eskaera_checkout_summary /
eskaera_payment templates are removed, superseded by the server-rendered
summary and checkout, and the stale sessionStorage delivery preference no
longer overrides the checkbox the order just rendered.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 19:14:49 +02:00
GitHub Copilot
f81c1ca8e7 [IMP] website_sale_aplicoop: surface the tax display setting on the Eskaera page
Pricing now follows website_sale, which includes the website's `Display
Product Prices`. That setting defaults to tax-excluded in 18.0, so a website
nobody configured shows Eskaera prices without VAT -- correct behaviour, but
easy to read as a bug when the members are consumers and expect the price they
will pay.

The setting is a related field website_sale already exposes, so it now appears
in the Eskaera block too, right where the co-op switches the feature on, rather
than only under eCommerce.

Tests cover both values on the shop and the listing paths, so the tax-excluded
case is a decision on record instead of a surprise.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 19:13:57 +02:00
GitHub Copilot
5a2f5d120f [ADD] website_sale_aplicoop: serve Eskaera per website
A co-op can run the plain shop on one website and the group orders on another,
but routes are registered process-wide: every /eskaera page answered on every
website of the database, and Odoo had already copied the Eskaera menu to all of
them.

`website.eskaera_enabled` decides which websites serve it, on by default so
installing changes nothing. It reaches the settings screen through `website_id`,
so it follows the website selector there. Where it is off the routes raise
NotFound and the menu is hidden.

The menu is hidden rather than deleted, by extending `_compute_visible`. That
keeps the record and any manual rename or reordering, so switching the feature
back on restores it as it was.

Note that a JSON route reports the 404 inside the JSON-RPC payload and still
answers HTTP 200; that is the transport, not a hole in the guard, and a test
pins it so the next reader does not take it for one.

The three remaining settings (lazy loading, products per page, low stock
threshold) are still `config_parameter`, so they stay global to the database.
Two websites share their values.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 17:31:03 +02:00
GitHub Copilot
817ff31d39 [REF] website_sale_aplicoop: price through website_sale instead of privately
Eskaera carried its own pricing path: a `website_sale_aplicoop.pricelist_id`
setting resolved inside the Eskaera controllers, plus a local reimplementation
of the tax and discount maths. The pricelist part meant /shop and Eskaera could
quote different prices on the same website, and the maths part had drifted from
the original it was copied from.

Pricing is now the standard one. The setting is gone (a migration drops the
parameter, which would otherwise linger looking like live configuration) and
`_resolve_pricelist` is `request.website.pricelist_id`. Scoping a pricelist to
a website is already core's job through `product.pricelist.website_id`, so
running the plain shop on one website and the co-op on another needs no code
here.

Taxes are applied by `product.template._apply_taxes_to_price`, which fixes a
real defect: it calls `_get_tax_included_unit_price_from_price` first, and this
module did not. With a fiscal position remapping a tax-included tax, a product
at 121 (100 + 21%) was displayed at 121 instead of 110, because the price was
handed to the mapped tax as if it were already that tax's gross amount. The
helper is a no-op without a remapping, so ordinary pricing is untouched. It
also means the website's `show_line_subtotals_tax_selection` is respected
rather than overridden with a hardcoded tax-included display.

Listing a page now costs one `_compute_price_rule` call instead of one per
product, which matters on the lazy-loading path.

Two smaller things found on the way. `_get_product_price_rule` was being passed
`target_currency=`, which is not one of its arguments: it fell into **kwargs
and was ignored, so the conversion never happened. And `_compute_price_info`
resolved `product.product_variant_ids[0]`, but it receives variants, so a
multi-variant template was priced from its first variant rather than the one
asked for.

The delivery display price loses its hardcoded 5.74 fallback and reuses the
same helpers as everything else.

Kept local, because none of it is pricing: the /Kg and /L suffixes, the 0.1
quantity step for bulk goods, the base unit price and the supplier name.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 17:28:51 +02:00
GitHub Copilot
3e4bd5e5db [ADD] website_sale_aplicoop: restore the test coverage lost with the dead code
The dead-code cleanup dropped 11 test files that were never wired into
tests/__init__.py. Reviewing what they covered turned up real holes, so the
worthwhile ones come back, rewritten against the current schema.

Blacklists were the serious gap: product, supplier and category exclusions
have absolute priority over product discovery and nothing exercised them.
The old file had two separate defects. Its supplier fixtures wrote
`main_seller_id` directly, but product_main_seller computes that field from
`variant_seller_ids`, so the compute reset it to False and the blacklist had
nothing to exclude; they now create real supplierinfo records. Worse, four
whole classes asserted against `group_order.product_ids` -- the m2m *input* --
instead of the discovery result, so they set `category_ids` and then checked a
field they never touched. Those go through `_get_products_for_group_order`
now, and three tests that had no assertions at all got some.

The remaining three failures were test bugs too, all Odoo 17->18 leftovers:

* Date cases assumed `pickup_date` derives from `start_date`. The chain is
  cutoff -> pickup -> delivery, and a recurring order whose start date has
  passed rolls forward to the current cycle, so a 2024 order has no 2024
  pickup. The new file anchors on future dates and finds the next 29 February
  dynamically, with a class documenting the roll-forward itself.
* `/eskaera/labels` is `type="json"`; the old test hit it with a plain GET and
  read the resulting 400 as a bug. It is called over JSON-RPC now, and a test
  pins the 400 so nobody repeats it. Also `uom.uom.categ` -> `uom.category`.
* `price_include` is computed in 18.0, so fixtures must set
  `price_include_override`. On top of that `_get_price` filters taxes by
  company and defaults to `env.company`, not the fixture's, which left the
  tax list empty -- `tax_included` was False for the wrong reason.

Two of the portal tests were passing for the wrong reason as well: the access
guard bounced them to /eskaera, which also answers 200. Membership has to be
set from the member side with `is_group`, and a new test checks the final URL
rather than the status alone. Each fixture that can silently build the wrong
thing now carries a guard test.

Left out on purpose: three files were unimplemented placeholders, and
test_draft_persistence still deserves recovering (see the notes file).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 17:24:31 +02:00
GitHub Copilot
b3999e2283 [REM] website_sale_aplicoop: remove dead code and orphaned tests
Duplicate _translate_labels fallback, unreachable /eskaera/add-to-cart and
/eskaera/save-cart routes (the frontend cart is localStorage-only and uses
save-order), redundant pickup wrappers, unused pagination/count helpers and
fields, deprecated JS shims, and the already-empty checkout_summary.js and
i18n key/init leftovers.

Also drops 11 tests/*.py never wired into tests/__init__.py: three were
unimplemented placeholders (setUp with no assertions), and the other eight
had real assertions but were bit-rotted against the current schema (e.g.
res.partner.is_supplier no longer exists) — wiring them in surfaced 43
failures unrelated to this cleanup. Verified 0 failed/0 errors of 270 tests
both before and after.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 22:31:13 +02:00
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
a67181ab42 [IMP] website_sale_aplicoop: skip the product-card tags row when unused
The tags row was reserved (empty, aria-hidden) on every product card so
prices stay aligned across a grid row regardless of which cards have tags.
But when no product in the current batch has a published tag, that row was
still a fixed, permanent gap on every card for a feature nobody was using.

any_product_has_tags is now computed once per batch (controller-side, per
CLAUDE.md's no-logic-in-QWeb rule) from the already-filtered published_tags,
and the template skips the row entirely when it's False across all three
render paths (initial page, load-more, infinite-scroll AJAX).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 12:50:32 +02:00
GitHub Copilot
f3e39550c2 [I18N] website_sale_aplicoop: translate Eskaera menu to Catalan and Spanish
- Catalan: "Eskaera" → "Comandes"
- Spanish: "Eskaera" → "Pedidos"

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-08-16 12:18:22 +02:00
GitHub Copilot
4c1813a811 mover campos de compra colectiva a su página propia dentro del notebook 2026-08-16 12:00:29 +02:00
GitHub Copilot
5de7573fba [IMP] website_sale_aplicoop: accessibility and responsive polish — phase 8
Quantity control layout: changed from wrapping flex to deliberate two-row
grid so the add-to-cart button sits full-width underneath the stepper,
avoiding accidental line breaks and giving the primary action more pulsing
area. Also removed the /Kg suffix's redundant font rules.

Order card delivery row: reordered to badge-then-date (visually centred),
removed the "Delivery" label, and swapped bg-primary to text-bg-primary for
proper contrast on the home-delivery badge (3.13:1 minimum).

Tooltip translations: moved hardcoded static tooltips from data-bs-title
(untranslatable) to title (QWeb-translatable). Handled the edge case of
"Save Cart" and "Back to Cart" which had translations but no model_terms
reference in the POT, causing the merge to discard them — added the view
reference so translations now apply.

Load-from-history page: added accessibility: a visible status message
("Loading your order…"), a <noscript> fallback with link to the group
order, lang and viewport meta tags, and localised strings for all three
languages. Gave the page a minimal inline style block since it does not
inherit the token system (no website.layout).

Translations: 11 new entries (es/eu/ca) for new/reworded UI strings, plus
two code catalogue entries (products found, Close) that needed the
#. odoo-python comment for _() resolution. Documented the silent-failure
pattern in docs/TRANSLATIONS.md: the POT merge, untranslatable attributes,
and missing code comments.

Tests: 246 passing. Pre-commit: clean.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-08-16 11:34:38 +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
e59c706ef6 [I18N] website_sale_aplicoop, product_origin_char: add Catalan translation
Add ca.po for both addons, matching the existing es/eu coverage
(378 and 4 entries respectively). Add the missing "ca" fallback block
in website_sale_i18n.py alongside the existing es/eu ones, and correct
the i18n README, which falsely claimed complete pt/gl/fr/it coverage
that was never actually present.
2026-08-13 11:52:54 +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
f2194c5367 [ADD] website_sale_disable_cart: extract cart restriction from website_sale_aplicoop
The "shop as a read-only catalog" behaviour lived inside website_sale_aplicoop,
so every site that wanted the eskaera flow also lost the standard cart. It now
ships as its own installable addon, with the redirect target configurable
instead of hardcoded to /eskaera.

- website_sale_disable_cart: hides the cart UI (12 header styles + the product
  card quick-add) and overrides the standard cart endpoints. Redirect URL is
  configurable in Website settings (default /shop); only site-internal paths are
  accepted, so a misconfigured value cannot turn the shop into an open redirect
  nor loop back into a disabled route.
- Fixes carried over from the original code: /shop/cart/quantity is the Odoo 18
  path (it was /shop/cart_quantity, which never matched), the boxed, sidebar and
  sales two/three/four headers were not covered (the cart link stayed visible on
  them), and the routes now override the standard methods instead of registering
  duplicate ones.
- website_sale_aplicoop 18.0.1.12.0: drops the view file and the four redirect
  routes; installing it no longer touches the standard shop.

Upgrade order matters: update website_sale_aplicoop first, then install
website_sale_disable_cart in a second Odoo run — both use the same XPaths and
obsolete records are only cleaned up at the end of a run.

Tests: 8/8 in website_sale_disable_cart, aplicoop unaffected (its 2 failures
predate this change). Verified live on a DB clone: /shop/cart returns 303 to the
configured URL and no cart markup remains on /shop.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 11:34:59 +02:00
GitHub Copilot
5a6d9c9894 [FIX] website_sale_aplicoop: precio por kg/litro con IVA y granel por litro
El precio por unidad de las tarjetas usaba el campo nativo base_unit_price
(list_price sin impuestos), mostrando un importe distinto al precio
principal de la tarjeta, que sí incluye IVA. Ahora se calcula a partir del
precio ya impositado. Además se extiende el sufijo de precio por unidad
("/L") a productos vendidos por litro, que ya tenían el step de 0.1 pero
no el indicador visual.
2026-07-31 18:48:58 +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
9484f8c349 [FIX] website_sale_aplicoop: split cart and cycle stamp in localStorage
965c5c2 stored the cart as {cutoff_date, items: {...}} in
eskaera_<id>_cart. That broke every other reader of the same key:

- checkout_labels.js iterates Object.keys() expecting product IDs and
  rendered "cutoff_date" / "items" as ghost rows → users on the
  checkout page saw their cart as empty.
- home_delivery.js read/wrote the cart in place; the in-place mutation
  destroyed the wrapper.
- _saveOrderDraft serialised the same object straight to the server,
  POSTing "cutoff_date" and "items" as productIds.

Split the schema: eskaera_<id>_cart keeps the plain {productId: {...}}
shape every other JS file already relies on; the cycle marker moves to
eskaera_<id>_cart_cycle. _loadCart migrates browsers still holding the
v18.0.1.10.0 wrapped value on the next read.

Also make eviction strictly opt-in: drop the cart only when we know
the current cycle AND the stored cycle disagrees. Missing data on
either side (check-status didn't run, XHR failed) → preserve. This
restores the checkout page where check-status isn't called.

Version bump triggers the in-tab auto-reload added in 6e6d1e5 for
clients already on that build.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 15:37:55 +02:00
GitHub Copilot
6e6d1e5256 [IMP] website_sale_aplicoop: signal frontend to reload on deploy
When we ship a JS-only fix, users with a long-lived tab keep running
the old code until they hard-refresh — there is no clean way to push
new code to an already-loaded page. Now /eskaera/check-status returns
the module's installed_version as client_version, and the eskaera
page embeds the same version in data-build-version on the cart
container. The JS captures the page's build version at init; on every
check-status response it compares them and triggers window.location.
reload() on mismatch. A sessionStorage timestamp guards against
reload loops if the versions stay disagreeing (cached HTML upstream).

The version bump in __manifest__.py also invalidates the asset bundle
URL hash, so even users without this signalling path get fresh JS on
their next navigation.

This is forward-looking: clients on the old JS (no check) still need
one manual refresh today, but every future deploy will auto-recover.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 15:31:37 +02:00
GitHub Copilot
bd8d87060f Bump version 2026-06-04 15:22:19 +02:00
GitHub Copilot
48597ae8c7 add l10n_es_partner dependency for commercial name field. 2026-06-04 15:14:53 +02:00
GitHub Copilot
965c5c2495 [FIX] website_sale_aplicoop: stamp localStorage cart with cycle and evict on rollover
The cart was reappearing on stage with ghost items that had never been
saved as drafts on the server. Root cause: the localStorage cart had no
cycle awareness. Users who added items but never clicked Save left the
items in localStorage forever, and the only existing eviction path
(_clearCurrentOrderCartSilently via _checkGroupOrderStatus) only fires
when cutoff_passed flips true — which it rarely does, because the
stored cutoff_date is normally in the future for an active cycle. On
the next visit, _loadCart happily rehydrated the stale items and
_autoLoadDraftOnInit short-circuited because the cart was no longer
empty.

Stamp every cart written to localStorage with the cutoff_date it
belongs to and reject on cycle mismatch in _loadCart. The cutoff_date
is captured from /eskaera/check-status (which already returned it).
Legacy unstamped values and corrupt JSON are also dropped, so existing
browsers self-heal on first reload after the deploy.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-04 15:14:04 +02:00
a0ba3426c8 fix comercial name in supplier info 2026-06-02 10:50:36 +00: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
c8e0461120 [IMP] website_sale_aplicoop: render user-defined website_sale ribbon alongside stock ribbon
Eskaera shop cards now display the standard website_sale ribbon
(variant_ribbon_id or website_ribbon_id, e.g. "Novedad") in addition
to the dynamic stock ribbon. Stock ribbons move to the right so they
coexist with user-defined ribbons (which default to the left).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 18:11:47 +02:00
GitHub Copilot
3b8cb7582b [FIX] website_sale_aplicoop: scope draft lookup to active cycle window
The /eskaera/load-draft endpoint was returning previous-cycle drafts when
group_order.cutoff_date was still in the future but group_order.pickup_date
had not yet been recomputed (its compute only depends on pickup_day and
start_date). Both the stale group_order.pickup_date and the old draft's
pickup_date held the same past value, so the exact-pickup-date filter in
_find_recent_draft_order matched the stale draft and the cart was
repopulated with old products immediately after being cleared.

Replace the pickup_date exact-match + current-week fallback with a single
cutoff-anchored window: create_date in [cutoff_date - 6 days, cutoff_date].
Drafts created outside that window belong to a previous cycle and must
not be reused. The change applies to all four callers (load-draft,
clear-cart, save-order, confirm) so the merge/confirm paths also stop
attaching to stale drafts.

Covered by a new regression test that mirrors the production setup
(matching pickup_date, draft create_date backdated 10 days).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 17:40:48 +02:00
GitHub Copilot
e2a22c5d71 Save Draft transation 2026-05-28 13:53:00 +02:00
GitHub Copilot
b01794c319 [FIX] website_sale_aplicoop: clear cart when group order cutoff has passed
Previously, when a user reopened a group order whose cutoff day had
already passed, the /eskaera/check-status response correctly triggered
the localStorage cart clear, but _autoLoadDraftOnInit immediately
re-fetched the previous cycle's draft sale.order from /eskaera/load-draft
(which only guarded on group_order.state, not cutoff_date) and the stale
items reappeared in the cart, confusing users.

Add a cutoff_date < today guard to load_draft_cart so the endpoint
returns the existing clear_cart unavailable response, and short-circuit
_autoLoadDraftOnInit on the frontend via a _skipDraftAutoLoad flag set
in _checkGroupOrderStatus to avoid the now-pointless XHR round trip.

Covered by a new regression test in tests/test_group_order_status_endpoint.py.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 13:51:37 +02:00
GitHub Copilot
eb515c28e1 [FIX] website_sale_aplicoop: show pickup slots on checkout page
Replace old pickup_day/pickup_date display with the slot-based logic
(pickup_slot_ids) on the checkout page, consistent with the shop page.
Falls back to legacy pickup_day if no slots are configured.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 13:44:34 +02:00
GitHub Copilot
5eea3e2a23 [IMP] website_sale_aplicoop: improve i18n label fallbacks and response parsing
Add hardcoded fallback translations for es/eu when PO-based translation
returns the source string unchanged. Expand labels dict with all keys
needed by the frontend. Fix JSON response parsing in template
(data.result || data). Add js_translations keys. Add pot file for
stock_picking_batch_custom.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 12:28:33 +02:00
GitHub Copilot
647438e012 [FIX] website_sale_aplicoop: home delivery unchecked by default
The checkout home-delivery checkbox had a fixed checked="checked"
attribute, so it always rendered checked even when the delivery
product was not in the cart. Remove it and let checkDeliveryInCart()
mark it only when the delivery product is actually present.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 12:26:04 +02:00
GitHub Copilot
98fe8676ea group order form notebook 2026-05-21 17:37:22 +02:00
GitHub Copilot
b0dc189708 traducciones 2026-05-21 16:14:04 +02:00
GitHub Copilot
876e7b1fbf traducciones 2026-05-21 15:15:30 +02:00
GitHub Copilot
d3d1231d29 disable load in cart portal buttons 2026-05-21 13:50:50 +02:00
GitHub Copilot
f983d71ea3 [FIX] website_sale_aplicoop: fix account.tax access error and duplicate home-delivery handler
Add sudo() to pricelist_item and fiscal position fallback in _get_pricing_info
so portal users can price the delivery product without triggering an AccessError
on account.tax. Remove the redundant #home-delivery-btn click handler from
website_sale.js — home_delivery.js already owns that button via
bindShopHomeDeliveryButton(), which manages the active class and localStorage cart.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 18:20:02 +02:00
GitHub Copilot
27097004fb [FIX] website_sale_aplicoop: wire home delivery preference through all save flows
Three bugs prevented home_delivery from reaching sale.order:

1. #home-delivery-btn (shop sidebar) had no JS handler — clicking it did
   nothing. Now it toggles active state and persists choice to sessionStorage.

2. _executeSaveCartAsDraft (Save Cart button) never included is_delivery in
   the request body. Now reads the toggle button state (or the page-level
   data-home-delivery-enabled fallback) and sends is_delivery correctly.

3. #home-delivery-checkbox on checkout page was unchecked by default and
   always shown. Now it is pre-checked when group_order.home_delivery is
   True, wrapped in t-if to hide it when delivery is not configured, and
   synced bidirectionally with sessionStorage so the shop-page toggle state
   carries over to checkout.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 17:57:07 +02:00
GitHub Copilot
a1899483c5 [TEST] website_sale_aplicoop: add tests for home_delivery computed field
- New test_home_delivery.py: verifies group.order.home_delivery derives
  from delivery_product_id (compute), and that sale.order.home_delivery
  is set correctly through _get_effective_delivery_context
- Fix test_save_order_endpoints: replace home_delivery=True (now ignored
  on computed field) with delivery_product_id to enable delivery
- Fix test_phase3_confirm_eskaera: same fix for integration test setUp

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 17:49:50 +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
GitHub Copilot
fb1efb0474 [IMP] website_sale_aplicoop: mostrar día y horario completo en pickup slots
Antes: si el slot tenía label, solo se mostraba el label (sin día ni hora).
Ahora: siempre se muestra día + rango horario (09:00–13:00), con el label
entre paréntesis como información adicional opcional.

Aplicado en eskaera_order_card_meta (tarjeta compacta) y en la vista
detalle de la tienda (group_order.pickup_slot_ids).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 17:28:39 +02:00