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>
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>
Show picking_partner_id after product_id in the detailed operations
list so the destination contact is visible next to the product.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
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>
Add product_default_code related field to stock.move.line and
stock.picking.batch.summary.line. Show it as a hidden-by-default column
to the right of product_id in both detailed operations and product summary
views. Hide the code prefix from the product name via display_default_code
context so sorting is alphabetical.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Hide location_id, location_dest_id and picking_id (optional=hide) in the
detailed operations list view to reduce visual clutter.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add product_categ_id before product_id (visible by default) and hide
company and unit of measure fields by default in the detailed operations tab.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace old Odoo 16/17 class names (.o_list_view_table) with Odoo 18
equivalents (table.o_list_table, tr.o_data_row) so zebra striping
renders correctly in the detailed operations list view.
Remove background:transparent rule on .o_field_widget * that was
breaking the boolean_toggle widget appearance on striped rows.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace old Odoo 16/17 class names (.o_list_view_table) with Odoo 18
equivalents (table.o_list_table, tr.o_data_row) so zebra striping
renders correctly in the detailed operations list view.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
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>