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>
TicketBAI maintains independent chains per invoice series (INV, RINV, etc).
When building the chain for a new invoice, ensure it links to the previous
valid document from the same series, not just the last document regardless
of series.
Fixes error "time data '' does not match format '%d-%m-%Y'" when previous
document's XML is missing or when wrong series document is used for chaining.
- Override _get_sale_values to validate series match
- Add _get_last_chained_document_by_series to filter by series+state+xml
- Skip cancelled documents and documents without valid XML
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
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.
Allow removing stock.move.line records from the Basket Assembly operator
view (select + Delete action) instead of forcing quantity to zero, which
caused issues for operators. Also expose the picking reference field
(hidden by default) before the contact column.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
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>
- Add action_sync_purchase_price_from_supplierinfo on product.template:
iterates variants, applies discount chain from main supplierinfo
(according to last_purchase_price_compute_type), writes net price to
last_purchase_price_received, recomputes theoretical sale price.
Skips manual_update variants and products without a supplier.
Returns a display_notification with updated/skipped counts.
- Register it as ir.actions.server bound to product.template list view.
- Add test_sync_action.py: 10 tests covering all discount types, skip
cases, theoretical price recompute, notification type, idempotency,
and bulk multi-template execution.
- Fix test_no_update_with_zero_quantity: update assertion to reflect that
PO confirmation now syncs the cost via supplierinfo (intended behavior).
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>
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>
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>
The sale price was computed from a gross last_purchase_price_received when
the product relied on its supplierinfo (no stock receipt), ignoring the
vendor discounts even with last_purchase_price_compute_type set.
Sync last_purchase_price_received from the main vendor's supplierinfo on
create/write, applying its discounts according to compute_type (mirrors the
stock.move receipt logic): without_discounts keeps the gross price, with_*
discounts apply 1/2/all three, manual_update is left untouched. The
theoretical price is refreshed (and last_purchase_price_updated flagged)
without overwriting lst_price until the user confirms.
Also drop the redundant _compute_price_rule call in _compute_theoritical_price
by asking the pricelist for the price directly instead of going through
_get_price (which computed the price twice).
Add purchase_triple_discount as an explicit dependency and tests covering
every discount type, manual_update, write re-sync and the main-vendor rule.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Port .github Copilot setup to Claude Code:
- CLAUDE.md: condensed project instructions (auto-loaded each session)
- .claude/skills: per-language guides (odoo-python, odoo-xml-views,
odoo-qweb-html, odoo-javascript) + openspec-* workflow skills
- .claude/commands/opsx: /opsx:explore|propose|apply|archive slash commands
Original .github/ files kept untouched as extended reference.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace transform: scale() (which clips at parent boundaries) with direct
width/height on the form-check-input element so the switch grows without overflow.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The general tbody tr.o_data_row td rule had higher specificity (0,3,2) than
td[name="quantity"] (0,2,1), overriding the 1.5rem font-size. Fixed by using
tbody tr.o_data_row td[name="quantity"] (0,3,3) for the size/weight override.
Also fixed table. prefix on all selectors: arch class lands on the controller
root div, not the <table> element.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
In read mode the value is plain text inside <td name="quantity"> with no
widget wrapper, so font-size and font-weight must target the td itself,
not only .o_field_widget input (edit mode only).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add a "Vista Operarios" button in the batch form header (visible when
in_progress) that opens a full-screen list of stock.move.line filtered
by the current batch. The view shows only the fields operators need
(category, product, partner, home_delivery, consumer_group, collected,
quantity) with larger rows and touch-friendly quantity input via CSS.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
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>
Override _order on stock.move.line using the existing stored Many2one fields.
Odoo JOINs product.category and uses its _order (complete_name, which is stored)
so the full category path is used for sorting without any extra fields.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Override _order on stock.move.line with stored Char fields so the server
returns records in the correct order on initial load (default_order in the
view only applies to interactive client-side sorting, not the initial fetch).
Add product_name and partner_name stored related fields to avoid SQL JOINs
in ORDER BY. Use the same fields in the view's default_order for consistency.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Store picking_partner_id on stock.move.line and set default_order to
product_categ_complete_name, product_id, picking_partner_id in the
detailed operations view so sorting uses the full category path
(e.g. 'SECO ENVASADO / Aperitivos') instead of only the leaf name.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add o_batch_move_line_list class to the detailed ops list and use it to
set min-width: 100px on the quantity column header and cells.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Store picking_partner_id on stock.move.line and set default_order to
product_categ_id, product_id, picking_partner_id in the detailed operations view.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
Native l10n_es_edi_tbai hardcodes ClaveRegimenIvaOpTrascendencia '19' for
REAGYP vendor bills, but '19' only exists in the issued-invoices book. The
Batuz LROE schema for received invoices rejects it (cvc-enumeration-valid),
where the valid REAGYP-compensation key is '02'.
Override _l10n_es_tbai_get_vendor_bill_values_batuz to remap 19 -> 02.
Upstream: https://github.com/odoo/odoo/pull/266214
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>