Compare commits

...

10 commits

Author SHA1 Message Date
396894a948
[FIX] sale_order_batch: fix float rounding issue in constraint
Some checks are pending
pre-commit / pre-commit (push) Waiting to run
tests / Detect unreleased dependencies (push) Waiting to run
tests / test with OCB (push) Waiting to run
tests / test with Odoo (push) Waiting to run
2026-06-01 12:23:48 +02:00
4149c353df [FIX] sale_order_batch: unique ids for confirm button 2026-05-28 10:48:24 +02:00
8c3e607077 [ADD] website_variant_values_visibility 2026-05-26 11:25:20 +02:00
4b9796521e [16.0] sale_order_batch: add tests 2026-05-12 15:25:10 +02:00
12736d9972 [16.0] sale_order_batch: fix add followers bug 2026-05-12 15:25:10 +02:00
89bb11becc
[REM] setup-tool files 2026-02-07 23:42:13 +01:00
madmooose
28f2992319
Merge pull request #2 from madmooose/16.0-add-sale_order_batch_stock
[16.0][ADD] sale_order_batch_batch
2026-02-07 23:36:58 +01:00
madmooose
8633a66ea9
Merge pull request #1 from madmooose/16.0-add_sale_order_batch
[16.0][ADD] sale_order_batch
2026-02-07 23:26:20 +01:00
e17935849c [IMP] sale_order_batch: apply pre-commit 2026-02-07 23:23:32 +01:00
752e4f58d0 [IMP] sale_order_batch_stock: apply pre-commit 2026-02-07 23:22:52 +01:00
40 changed files with 1554 additions and 52 deletions

View file

@ -49,6 +49,9 @@ Changelog
- 16.0.2.2.1: Add lst_price to view
- 16.0.2.3.0: Add "Reset to Open"
- 16.0.2.3.1: Remove Sale Order from batch on cancelation
- 16.0.2.4.0: Add Followers on In Progress & add tests
- 16.0.2.4.2: Add unique ids for confirm button
- 16.0.2.4.3: fix float rounding issue
Bug Tracker
===========

View file

@ -4,7 +4,7 @@
"author": "BAKEUP,Niels Göttsch",
"website": "https://ziemlichoptimal.de",
"category": "Sale",
"version": "16.0.2.3.1",
"version": "16.0.2.4.3",
"depends": ["sale", "product"],
"data": [
"security/ir.model.access.csv",

View file

@ -34,7 +34,7 @@ class SaleOrder(models.Model):
for sale_order in self:
company = sale_order.company_id
sale_order = sale_order.with_company(company)
if not sale_order.batch_id and sale_order.state in ["draft", "sent"]:
if not sale_order.batch_id and sale_order.state in ["draft"]:
batch = sale_order._get_current_batch()
if not batch:
batch = sale_order.env["sale.order.batch"].create({})
@ -50,6 +50,18 @@ class SaleOrder(models.Model):
"res_id": self.batch_id.id,
}
def action_quotation_send(self):
invalid_orders = []
if not self.env.context.get("bypass_batch", False):
for order in self:
if order.batch_id:
invalid_orders.append(order.name)
if invalid_orders:
raise UserError(
_(f"Sale Order belongs to a Batch: {', '.join(invalid_orders)}")
)
return super().action_quotation_send()
def action_confirm(self):
invalid_orders = []
if not self.env.context.get("bypass_batch", False):

View file

@ -172,8 +172,8 @@ class SaleOrderBatch(models.Model):
def action_in_progress(self):
for batch in self:
batch.sale_order_line_ids._validate_analytic_distribution()
orders = batch.sale_order_ids
orders.update({"state": "sent"})
orders = batch.with_context(bypass_batch=True).sale_order_ids
orders.action_quotation_sent()
batch.update({"state": "in_progress"})
return True

View file

@ -8,4 +8,7 @@
- 16.0.2.2.0: Add invoice status and menues
- 16.0.2.2.1: Add lst_price to view
- 16.0.2.3.0: Add "Reset to Open"
- 16.0.2.3.1: Remove Sale Order from batch on cancelation
- 16.0.2.3.1: Remove Sale Order from batch on cancelation
- 16.0.2.4.0: Add Followers on In Progress & add tests
- 16.0.2.4.2: Add unique ids for confirm button
- 16.0.2.4.3: fix float rounding issue

View file

@ -405,6 +405,9 @@ anymore as log as they are part of a batch. Only Sale Orders in state Qutotation
<li>16.0.2.2.1: Add lst_price to view</li>
<li>16.0.2.3.0: Add “Reset to Open”</li>
<li>16.0.2.3.1: Remove Sale Order from batch on cancelation</li>
<li>16.0.2.4.0: Add Followers on In Progress &amp; add tests</li>
<li>16.0.2.4.2: Add unique ids for confirm button</li>
<li>16.0.2.4.3: fix float rounding issue</li>
</ul>
</div>
<div class="section" id="bug-tracker">

View file

@ -0,0 +1,7 @@
from . import (
test_sale_order,
test_sale_order_batch,
test_sale_order_batch_cancel_wizard,
test_sale_order_batch_product,
test_sale_order_line,
)

View file

@ -0,0 +1,158 @@
from odoo.exceptions import UserError
from odoo.tests import TransactionCase
class TestSaleOrder(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.env = cls.env(context={**cls.env.context, "tracking_disable": True})
cls.partner = cls.env["res.partner"].create({"name": "Test Partner"})
cls.product = cls.env["product.product"].create(
{"name": "Test Service", "type": "service", "list_price": 100.0}
)
def _create_order(self, **kwargs):
vals = {
"partner_id": self.partner.id,
"order_line": [
(
0,
0,
{
"product_id": self.product.id,
"product_uom_qty": 1,
"price_unit": 100.0,
},
)
],
}
vals.update(kwargs)
return self.env["sale.order"].create(vals)
def _create_batch(self):
return self.env["sale.order.batch"].create({})
# --- _get_current_batch ---
def test_get_current_batch_finds_open_batch(self):
batch = self._create_batch()
order = self._create_order()
self.assertEqual(order._get_current_batch(), batch)
def test_get_current_batch_ignores_closed_batch(self):
batch = self._create_batch()
batch.action_confirm()
order = self._create_order()
self.assertNotEqual(order._get_current_batch(), batch)
# --- action_add_to_batch ---
def test_action_add_to_batch_creates_new_batch(self):
order = self._create_order()
order.action_add_to_batch()
self.assertTrue(order.batch_id)
def test_action_add_to_batch_uses_existing_open_batch(self):
batch = self._create_batch()
order = self._create_order()
order.action_add_to_batch()
self.assertEqual(order.batch_id, batch)
def test_action_add_to_batch_skips_already_batched_order(self):
batch1 = self._create_batch()
order = self._create_order(batch_id=batch1.id)
order.action_add_to_batch()
self.assertEqual(order.batch_id, batch1)
def test_action_add_to_batch_skips_confirmed_order(self):
order = self._create_order()
order.with_context(bypass_batch=True).action_confirm()
order.action_add_to_batch()
self.assertFalse(order.batch_id)
# --- action_view_sale_order_batch ---
def test_action_view_sale_order_batch_returns_form_action(self):
batch = self._create_batch()
order = self._create_order(batch_id=batch.id)
result = order.action_view_sale_order_batch()
self.assertEqual(result["res_model"], "sale.order.batch")
self.assertEqual(result["res_id"], batch.id)
self.assertEqual(result["view_mode"], "form")
# --- action_confirm ---
def test_action_confirm_raises_user_error_when_in_batch(self):
batch = self._create_batch()
order = self._create_order(batch_id=batch.id)
with self.assertRaises(UserError):
order.action_confirm()
def test_action_confirm_raises_for_multiple_batched_orders(self):
batch = self._create_batch()
order1 = self._create_order(batch_id=batch.id)
order2 = self._create_order(batch_id=batch.id)
with self.assertRaises(UserError):
(order1 | order2).action_confirm()
def test_action_confirm_works_with_bypass_batch_context(self):
batch = self._create_batch()
order = self._create_order(batch_id=batch.id)
order.with_context(bypass_batch=True).action_confirm()
self.assertEqual(order.state, "sale")
def test_action_confirm_works_without_batch(self):
order = self._create_order()
order.action_confirm()
self.assertEqual(order.state, "sale")
# --- action_quotation_send ---
def test_action_quotation_send_raises_user_error_when_in_batch(self):
batch = self._create_batch()
order = self._create_order(batch_id=batch.id)
with self.assertRaises(UserError):
order.action_quotation_send()
def test_action_quotation_send_raises_for_multiple_batched_orders(self):
batch = self._create_batch()
order1 = self._create_order(batch_id=batch.id)
order2 = self._create_order(batch_id=batch.id)
with self.assertRaises(UserError):
(order1 | order2).action_quotation_send()
def test_action_quotation_send_works_with_bypass_batch_context(self):
batch = self._create_batch()
order = self._create_order(batch_id=batch.id)
result = order.with_context(bypass_batch=True).action_quotation_send()
self.assertIsNotNone(result)
def test_action_quotation_send_works_without_batch(self):
order = self._create_order()
result = order.action_quotation_send()
self.assertIsNotNone(result)
# --- action_cancel ---
def test_action_cancel_clears_batch_id(self):
batch = self._create_batch()
order = self._create_order(batch_id=batch.id)
order.action_cancel()
self.assertFalse(order.batch_id)
# --- write ---
def test_write_batch_id_updates_batch_products(self):
batch = self._create_batch()
order = self._create_order()
self.assertFalse(order.order_line.batch_product_id)
order.write({"batch_id": batch.id})
self.assertTrue(order.order_line.batch_product_id)
def test_write_without_batch_id_does_not_change_products(self):
batch = self._create_batch()
order = self._create_order(batch_id=batch.id)
batch_product = order.order_line.batch_product_id
order.write({"note": "test note"})
self.assertEqual(order.order_line.batch_product_id, batch_product)

View file

@ -0,0 +1,249 @@
from odoo.tests import TransactionCase
class TestSaleOrderBatch(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.env = cls.env(context={**cls.env.context, "tracking_disable": True})
cls.partner = cls.env["res.partner"].create({"name": "Test Partner"})
cls.product = cls.env["product.product"].create(
{
"name": "Test Service Product",
"type": "service",
"list_price": 100.0,
"invoice_policy": "order",
}
)
cls.product2 = cls.env["product.product"].create(
{
"name": "Test Service Product 2",
"type": "service",
"list_price": 200.0,
"invoice_policy": "order",
}
)
def _create_batch(self, **kwargs):
return self.env["sale.order.batch"].create(kwargs)
def _create_order(self, batch=None, product=None, qty=1, price=100.0):
if product is None:
product = self.product
vals = {
"partner_id": self.partner.id,
"order_line": [
(
0,
0,
{
"product_id": product.id,
"product_uom_qty": qty,
"price_unit": price,
},
)
],
}
if batch:
vals["batch_id"] = batch.id
return self.env["sale.order"].create(vals)
def test_create_generates_sequence_name(self):
batch = self._create_batch()
self.assertTrue(batch.name.startswith("SOB"))
self.assertNotEqual(batch.name, "New")
def test_create_keeps_explicit_name(self):
batch = self.env["sale.order.batch"].create({"name": "MY-BATCH-001"})
self.assertEqual(batch.name, "MY-BATCH-001")
def test_compute_sale_order_count_with_orders(self):
batch = self._create_batch()
self._create_order(batch=batch)
self._create_order(batch=batch)
self.assertEqual(batch.sale_order_count, 2)
def test_compute_amount_total(self):
batch = self._create_batch()
order1 = self._create_order(batch=batch, price=100.0)
order2 = self._create_order(batch=batch, price=200.0)
self.assertAlmostEqual(
batch.amount_total, order1.amount_total + order2.amount_total
)
def test_compute_product_count_with_products(self):
batch = self._create_batch()
self._create_order(batch=batch, product=self.product)
self._create_order(batch=batch, product=self.product2)
self.assertEqual(batch.product_count, 2)
def test_compute_invoice_ids_with_invoice(self):
batch = self._create_batch()
order = self._create_order(batch=batch)
order.with_context(bypass_batch=True).action_confirm()
invoice = order._create_invoices()
self.assertIn(invoice, batch.invoice_ids)
self.assertEqual(batch.invoice_count, 1)
def test_compute_partner_credit_warning_no_warnings(self):
batch = self._create_batch()
self._create_order(batch=batch)
self.assertEqual(batch.partner_credit_warning, "")
# --- action_view_source_sale_orders ---
def test_action_view_source_sale_orders_zero(self):
batch = self._create_batch()
result = batch.action_view_source_sale_orders()
self.assertEqual(result["type"], "ir.actions.act_window_close")
def test_action_view_source_sale_orders_one(self):
batch = self._create_batch()
order = self._create_order(batch=batch)
result = batch.action_view_source_sale_orders()
self.assertEqual(result["res_id"], order.id)
self.assertIn("views", result)
def test_action_view_source_sale_orders_multiple(self):
batch = self._create_batch()
self._create_order(batch=batch)
self._create_order(batch=batch)
result = batch.action_view_source_sale_orders()
self.assertIn("domain", result)
# --- action_view_products ---
def test_action_view_products_zero(self):
batch = self._create_batch()
result = batch.action_view_products()
self.assertEqual(result["type"], "ir.actions.act_window_close")
def test_action_view_products_one(self):
batch = self._create_batch()
self._create_order(batch=batch)
result = batch.action_view_products()
self.assertIn("res_id", result)
self.assertIn("views", result)
self.assertEqual(result["res_id"], batch.product_ids.id)
def test_action_view_products_multiple(self):
batch = self._create_batch()
self._create_order(batch=batch, product=self.product)
self._create_order(batch=batch, product=self.product2)
result = batch.action_view_products()
self.assertIn("domain", result)
# --- action_view_invoice ---
def test_action_view_invoice_zero(self):
batch = self._create_batch()
result = batch.action_view_invoice()
self.assertEqual(result["type"], "ir.actions.act_window_close")
def test_action_view_invoice_one(self):
batch = self._create_batch()
order = self._create_order(batch=batch)
order.with_context(bypass_batch=True).action_confirm()
order._create_invoices()
result = batch.action_view_invoice()
self.assertIn("res_id", result)
self.assertIn("views", result)
def test_action_view_invoice_multiple(self):
batch = self._create_batch()
order1 = self._create_order(batch=batch)
order2 = self._create_order(batch=batch)
order1.with_context(bypass_batch=True).action_confirm()
order2.with_context(bypass_batch=True).action_confirm()
order1._create_invoices()
order2._create_invoices()
result = batch.action_view_invoice()
self.assertIn("domain", result)
# --- action_in_progress ---
def test_action_in_progress_sets_state(self):
batch = self._create_batch()
self._create_order(batch=batch)
batch.action_in_progress()
self.assertEqual(batch.state, "in_progress")
def test_action_in_progress_sends_quotation(self):
batch = self._create_batch()
order = self._create_order(batch=batch)
batch.action_in_progress()
self.assertEqual(order.state, "sent")
# --- action_confirm ---
def test_action_confirm_sets_batch_closed(self):
batch = self._create_batch()
self._create_order(batch=batch)
batch.action_confirm()
self.assertEqual(batch.state, "closed")
def test_action_confirm_confirms_orders(self):
batch = self._create_batch()
order = self._create_order(batch=batch)
batch.action_confirm()
self.assertEqual(order.state, "sale")
# --- action_open ---
def test_action_open_resets_state(self):
batch = self._create_batch()
self._create_order(batch=batch)
batch.action_in_progress()
batch.action_open()
self.assertEqual(batch.state, "open")
def test_action_open_resets_orders_to_draft(self):
batch = self._create_batch()
order = self._create_order(batch=batch)
batch.action_in_progress()
batch.action_open()
self.assertEqual(order.state, "draft")
# --- _show_cancel_wizard ---
def test_show_cancel_wizard_disabled_by_context(self):
batch = self._create_batch()
self._create_order(batch=batch)
batch.action_in_progress()
result = batch.with_context(disable_cancel_warning=True)._show_cancel_wizard()
self.assertFalse(result)
def test_show_cancel_wizard_no_orders(self):
batch = self._create_batch()
self.assertFalse(batch._show_cancel_wizard())
def test_show_cancel_wizard_with_sent_orders(self):
batch = self._create_batch()
self._create_order(batch=batch)
batch.action_in_progress()
self.assertTrue(batch._show_cancel_wizard())
# --- action_cancel ---
def test_action_cancel_returns_wizard_for_sent_orders(self):
batch = self._create_batch()
self._create_order(batch=batch)
batch.action_in_progress()
result = batch.action_cancel()
self.assertIsNotNone(result)
self.assertEqual(result.get("res_model"), "sale.order.batch.cancel.wizard")
def test_action_cancel_without_wizard_sets_cancel(self):
batch = self._create_batch()
self._create_order(batch=batch)
batch.with_context(disable_cancel_warning=True).action_cancel()
self.assertEqual(batch.state, "cancel")
# --- _action_cancel ---
def test_action_cancel_directly(self):
batch = self._create_batch()
order = self._create_order(batch=batch)
batch._action_cancel()
self.assertEqual(batch.state, "cancel")
self.assertEqual(order.state, "cancel")

View file

@ -0,0 +1,86 @@
from odoo.tests import TransactionCase
class TestSaleOrderBatchCancelWizard(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.env = cls.env(context={**cls.env.context, "tracking_disable": True})
cls.partner = cls.env["res.partner"].create({"name": "Test Partner"})
cls.product = cls.env["product.product"].create(
{"name": "Test Product", "type": "service"}
)
def _create_batch_with_order(self):
batch = self.env["sale.order.batch"].create({})
order = self.env["sale.order"].create(
{
"partner_id": self.partner.id,
"batch_id": batch.id,
"order_line": [
(
0,
0,
{
"product_id": self.product.id,
"product_uom_qty": 1,
"price_unit": 10.0,
},
)
],
}
)
return batch, order
def _create_wizard(self, batch):
return (
self.env["sale.order.batch.cancel.wizard"]
.with_context(active_ids=[batch.id])
.create({"batch_ids": [(4, batch.id)]})
)
# --- _compute_order_ids ---
def test_compute_order_ids(self):
batch, order = self._create_batch_with_order()
wizard = self._create_wizard(batch)
self.assertIn(order, wizard.order_ids)
def test_compute_order_ids_multiple_batches(self):
batch1, order1 = self._create_batch_with_order()
batch2, order2 = self._create_batch_with_order()
wizard = (
self.env["sale.order.batch.cancel.wizard"]
.with_context(active_ids=[batch1.id, batch2.id])
.create({"batch_ids": [(4, batch1.id), (4, batch2.id)]})
)
self.assertIn(order1, wizard.order_ids)
self.assertIn(order2, wizard.order_ids)
# --- action_cancel ---
def test_action_cancel_sets_batch_state_cancel(self):
batch, order = self._create_batch_with_order()
wizard = self._create_wizard(batch)
wizard.action_cancel()
self.assertEqual(batch.state, "cancel")
def test_action_cancel_cancels_orders(self):
batch, order = self._create_batch_with_order()
wizard = self._create_wizard(batch)
wizard.action_cancel()
self.assertEqual(order.state, "cancel")
def test_action_cancel_returns_window_close(self):
batch, order = self._create_batch_with_order()
wizard = self._create_wizard(batch)
result = wizard.action_cancel()
self.assertEqual(result["type"], "ir.actions.act_window_close")
# --- action_discard ---
def test_action_discard_returns_window_close(self):
batch, order = self._create_batch_with_order()
wizard = self._create_wizard(batch)
result = wizard.action_discard()
self.assertEqual(result["type"], "ir.actions.act_window_close")

View file

@ -0,0 +1,119 @@
from odoo.tests import TransactionCase
class TestSaleOrderBatchProduct(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.env = cls.env(context={**cls.env.context, "tracking_disable": True})
cls.partner = cls.env["res.partner"].create({"name": "Test Partner"})
cls.product = cls.env["product.product"].create(
{"name": "Test Product", "type": "service"}
)
def _create_batch_with_order(self, qty=1):
batch = self.env["sale.order.batch"].create({})
order = self.env["sale.order"].create(
{
"partner_id": self.partner.id,
"batch_id": batch.id,
"order_line": [
(
0,
0,
{
"product_id": self.product.id,
"product_uom_qty": qty,
"price_unit": 10.0,
},
)
],
}
)
return batch, order, batch.product_ids[0]
# --- _compute_uom_qty ---
def test_compute_uom_qty_single_line(self):
batch, order, batch_product = self._create_batch_with_order(qty=3)
self.assertAlmostEqual(batch_product.product_uom_qty, 3.0)
def test_compute_uom_qty_multiple_lines(self):
batch, order, batch_product = self._create_batch_with_order(qty=3)
self.env["sale.order.line"].create(
{
"order_id": order.id,
"product_id": self.product.id,
"product_uom_qty": 2,
"price_unit": 10.0,
}
)
self.assertAlmostEqual(batch_product.product_uom_qty, 5.0)
# --- _compute_product_packaging_qty ---
def test_compute_product_packaging_qty_without_packaging(self):
batch, order, batch_product = self._create_batch_with_order()
if not batch_product.product_packaging_id:
self.assertEqual(batch_product.product_packaging_qty, 1)
def test_compute_product_packaging_qty_with_packaging(self):
packaging = self.env["product.packaging"].create(
{
"name": "Box of 10",
"product_id": self.product.id,
"qty": 10.0,
"sales": True,
}
)
batch, order, batch_product = self._create_batch_with_order(qty=5)
self.assertEqual(batch_product.product_packaging_id, packaging)
self.assertEqual(batch_product.product_packaging_qty, 10.0)
# --- _compute_product_packaging_id ---
def test_compute_product_packaging_id_suggests_first_packaging(self):
packaging = self.env["product.packaging"].create(
{
"name": "Box of 6",
"product_id": self.product.id,
"qty": 6.0,
"sales": True,
}
)
batch, order, batch_product = self._create_batch_with_order(qty=4)
self.assertEqual(batch_product.product_packaging_id, packaging)
def test_compute_product_packaging_id_clears_wrong_product_packaging(self):
product2 = self.env["product.product"].create(
{"name": "Other Product", "type": "service"}
)
packaging2 = self.env["product.packaging"].create(
{
"name": "Box of 5",
"product_id": product2.id,
"qty": 5.0,
"sales": True,
}
)
batch, order, batch_product = self._create_batch_with_order()
batch_product.product_packaging_id = packaging2
batch_product._compute_product_packaging_id()
self.assertFalse(batch_product.product_packaging_id)
def test_compute_product_packaging_id_no_packaging_when_no_qty(self):
self.env["product.packaging"].create(
{
"name": "Box of 12",
"product_id": self.product.id,
"qty": 12.0,
"sales": True,
}
)
batch = self.env["sale.order.batch"].create({})
batch_product = self.env["sale.order.batch.product"].create(
{"batch_id": batch.id, "product_id": self.product.id}
)
# With no lines, product_uom_qty is 0 — packaging should not be suggested
self.assertFalse(batch_product.product_uom_qty)
self.assertFalse(batch_product.product_packaging_id)

View file

@ -0,0 +1,145 @@
from odoo.tests import TransactionCase
class TestSaleOrderLine(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.env = cls.env(context={**cls.env.context, "tracking_disable": True})
cls.partner = cls.env["res.partner"].create({"name": "Test Partner"})
cls.product = cls.env["product.product"].create(
{"name": "Product A", "type": "service"}
)
cls.product2 = cls.env["product.product"].create(
{"name": "Product B", "type": "service"}
)
def _create_batch(self):
return self.env["sale.order.batch"].create({})
def _create_order(self, batch=None, product=None, qty=1):
if product is None:
product = self.product
vals = {
"partner_id": self.partner.id,
"order_line": [
(
0,
0,
{
"product_id": product.id,
"product_uom_qty": qty,
"price_unit": 10.0,
},
)
],
}
if batch:
vals["batch_id"] = batch.id
return self.env["sale.order"].create(vals)
# --- create ---
def test_create_line_with_batch_links_batch_product(self):
batch = self._create_batch()
order = self._create_order(batch=batch)
line = order.order_line
self.assertTrue(line.batch_product_id)
self.assertEqual(line.batch_product_id.product_id, self.product)
self.assertEqual(line.batch_product_id.batch_id, batch)
def test_create_line_without_batch_has_no_batch_product(self):
order = self._create_order()
self.assertFalse(order.order_line.batch_product_id)
def test_create_two_lines_same_product_share_batch_product(self):
batch = self._create_batch()
order = self._create_order(batch=batch)
line2 = self.env["sale.order.line"].create(
{
"order_id": order.id,
"product_id": self.product.id,
"product_uom_qty": 2,
"price_unit": 10.0,
}
)
self.assertEqual(order.order_line[0].batch_product_id, line2.batch_product_id)
def test_create_two_lines_different_products_get_different_batch_products(self):
batch = self._create_batch()
order = self._create_order(batch=batch)
line2 = self.env["sale.order.line"].create(
{
"order_id": order.id,
"product_id": self.product2.id,
"product_uom_qty": 1,
"price_unit": 10.0,
}
)
self.assertNotEqual(
order.order_line[0].batch_product_id, line2.batch_product_id
)
self.assertEqual(batch.product_count, 2)
# --- write ---
def test_write_product_id_updates_batch_product(self):
batch = self._create_batch()
order = self._create_order(batch=batch)
line = order.order_line
old_batch_product = line.batch_product_id
line.write({"product_id": self.product2.id})
self.assertNotEqual(line.batch_product_id, old_batch_product)
self.assertEqual(line.batch_product_id.product_id, self.product2)
def test_write_without_product_id_keeps_batch_product(self):
batch = self._create_batch()
order = self._create_order(batch=batch)
line = order.order_line
batch_product = line.batch_product_id
line.write({"product_uom_qty": 5})
self.assertEqual(line.batch_product_id, batch_product)
# --- unlink ---
def test_unlink_last_line_removes_batch_product(self):
batch = self._create_batch()
order = self._create_order(batch=batch)
line = order.order_line
batch_product = line.batch_product_id
line.unlink()
self.assertFalse(batch_product.exists())
def test_unlink_line_keeps_batch_product_when_other_lines_exist(self):
batch = self._create_batch()
order = self._create_order(batch=batch)
line2 = self.env["sale.order.line"].create(
{
"order_id": order.id,
"product_id": self.product.id,
"product_uom_qty": 2,
"price_unit": 10.0,
}
)
batch_product = order.order_line[0].batch_product_id
order.order_line[0].unlink()
self.assertTrue(batch_product.exists())
self.assertIn(line2, batch_product.sale_order_line_ids)
# --- _update_batch_product ---
def test_update_batch_product_links_when_batch_assigned(self):
order = self._create_order()
line = order.order_line
self.assertFalse(line.batch_product_id)
batch = self._create_batch()
order.write({"batch_id": batch.id})
self.assertTrue(line.batch_product_id)
def test_update_batch_product_unlinks_when_batch_removed(self):
batch = self._create_batch()
order = self._create_order(batch=batch)
line = order.order_line
self.assertTrue(line.batch_product_id)
order.with_context(disable_cancel_warning=True).write({"batch_id": False})
self.assertFalse(line.batch_product_id)

View file

@ -85,22 +85,22 @@
/>
<button
name="action_confirm"
id="action_confirm"
data-hotkey="v"
string="Confirm"
class="btn-secondary"
type="object"
attrs="{'invisible': [('state', 'not in',['open'])]}"
/>
<button
name="action_confirm"
id="action_confirm"
id="action_confirm_primary"
data-hotkey="v"
string="Confirm"
class="btn-primary"
type="object"
attrs="{'invisible': [('state', 'not in',['in_progress'])]}"
/>
<button
name="action_confirm"
id="action_confirm_secondary"
data-hotkey="v"
string="Confirm"
class="btn-secondary"
type="object"
attrs="{'invisible': [('state', 'not in',['open'])]}"
/>
<button
id="create_invoices"
name="%(sale.action_view_sale_advance_payment_inv)d"

View file

@ -30,6 +30,22 @@
</button>
</xpath>
<xpath
expr="//button[@name='action_quotation_send'][@states='draft']"
position="attributes"
>
<attribute
name="attrs"
>{'invisible': [('batch_id', '!=', False)]}</attribute>
</xpath>
<xpath
expr="//button[@name='action_quotation_send'][@states='sent,sale']"
position="attributes"
>
<attribute
name="attrs"
>{'invisible': [('batch_id', '!=', False)]}</attribute>
</xpath>
<xpath
expr="//button[@name='action_confirm']"
position="attributes"
>

View file

@ -1,3 +1,7 @@
.. image:: https://odoo-community.org/readme-banner-image
:target: https://odoo-community.org/get-involved?utm_source=readme
:alt: Odoo Community Association
==============================
Sale Order Batch Stock Binding
==============================
@ -13,12 +17,12 @@ Sale Order Batch Stock Binding
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png
.. |badge2| image:: https://img.shields.io/badge/license-LGPL--3-blue.png
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-bakeupboys%2Fnfu-lightgray.png?logo=github
:target: https://github.com/bakeupboys/nfu/tree/16.0/sale_order_batch_stock
:alt: bakeupboys/nfu
.. |badge3| image:: https://img.shields.io/badge/github-madmooose%2Fodooapps-lightgray.png?logo=github
:target: https://github.com/madmooose/odooapps/tree/16.0/sale_order_batch_stock
:alt: madmooose/odooapps
|badge1| |badge2| |badge3|
@ -37,10 +41,10 @@ Changelog
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/bakeupboys/nfu/issues>`_.
Bugs are tracked on `GitHub Issues <https://github.com/madmooose/odooapps/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/bakeupboys/nfu/issues/new?body=module:%20sale_order_batch_stock%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
`feedback <https://github.com/madmooose/odooapps/issues/new?body=module:%20sale_order_batch_stock%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
@ -51,6 +55,7 @@ Authors
~~~~~~~
* BAKEUP
* Niels Göttsch
Contributors
~~~~~~~~~~~~
@ -60,6 +65,6 @@ Contributors
Maintainers
~~~~~~~~~~~
This module is part of the `bakeupboys/nfu <https://github.com/bakeupboys/nfu/tree/16.0/sale_order_batch_stock>`_ project on GitHub.
This module is part of the `madmooose/odooapps <https://github.com/madmooose/odooapps/tree/16.0/sale_order_batch_stock>`_ project on GitHub.
You are welcome to contribute.

View file

@ -1,8 +1,8 @@
{
"name": "Sale Order Batch Stock Binding",
"summary": "Sale Order Batch Stock Bindingh",
"author": "BAKEUP",
"website": "https://www.bakeup.org",
"author": "BAKEUP,Niels Göttsch",
"website": "https://ziemlichoptimal.de",
"category": "hidden",
"version": "16.0.0.0.1",
"depends": ["sale_order_batch", "sale_stock"],

View file

@ -1,6 +1,5 @@
from odoo import api, fields, models
STATES = [("open", "Open"), ("close", "Close")]

View file

@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"

View file

@ -3,7 +3,7 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>Sale Order Batch Stock Binding</title>
<title>README.rst</title>
<style type="text/css">
/*
@ -360,16 +360,21 @@ ul.auto-toc {
</style>
</head>
<body>
<div class="document" id="sale-order-batch-stock-binding">
<h1 class="title">Sale Order Batch Stock Binding</h1>
<div class="document">
<a class="reference external image-reference" href="https://odoo-community.org/get-involved?utm_source=readme">
<img alt="Odoo Community Association" src="https://odoo-community.org/readme-banner-image" />
</a>
<div class="section" id="sale-order-batch-stock-binding">
<h1>Sale Order Batch Stock Binding</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:c32b4440b58e98f3bea5539d78dfa2916d64aebc757c140d78b67b2a2ce288da
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/lgpl-3.0-standalone.html"><img alt="License: LGPL-3" src="https://img.shields.io/badge/licence-LGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/bakeupboys/nfu/tree/16.0/sale_order_batch_stock"><img alt="bakeupboys/nfu" src="https://img.shields.io/badge/github-bakeupboys%2Fnfu-lightgray.png?logo=github" /></a></p>
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/lgpl-3.0-standalone.html"><img alt="License: LGPL-3" src="https://img.shields.io/badge/license-LGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/madmooose/odooapps/tree/16.0/sale_order_batch_stock"><img alt="madmooose/odooapps" src="https://img.shields.io/badge/github-madmooose%2Fodooapps-lightgray.png?logo=github" /></a></p>
<p>This module adds Deliverys to Sale ORder Batch Views.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
@ -385,7 +390,7 @@ ul.auto-toc {
</ul>
</div>
<div class="section" id="changelog">
<h1><a class="toc-backref" href="#toc-entry-1">Changelog</a></h1>
<h2><a class="toc-backref" href="#toc-entry-1">Changelog</a></h2>
<table class="docutils field-list" frame="void" rules="none">
<col class="field-name" />
<col class="field-body" />
@ -396,33 +401,35 @@ ul.auto-toc {
</table>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#toc-entry-2">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/bakeupboys/nfu/issues">GitHub Issues</a>.
<h2><a class="toc-backref" href="#toc-entry-2">Bug Tracker</a></h2>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/madmooose/odooapps/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/bakeupboys/nfu/issues/new?body=module:%20sale_order_batch_stock%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<a class="reference external" href="https://github.com/madmooose/odooapps/issues/new?body=module:%20sale_order_batch_stock%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#toc-entry-3">Credits</a></h1>
<h2><a class="toc-backref" href="#toc-entry-3">Credits</a></h2>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#toc-entry-4">Authors</a></h2>
<h3><a class="toc-backref" href="#toc-entry-4">Authors</a></h3>
<ul class="simple">
<li>BAKEUP</li>
<li>Niels Göttsch</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#toc-entry-5">Contributors</a></h2>
<h3><a class="toc-backref" href="#toc-entry-5">Contributors</a></h3>
<ul class="simple">
<li>Niels Göttsch &lt;<a class="reference external" href="mailto:niels&#64;ziemlichoptimal.de">niels&#64;ziemlichoptimal.de</a>&gt;</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-6">Maintainers</a></h2>
<p>This module is part of the <a class="reference external" href="https://github.com/bakeupboys/nfu/tree/16.0/sale_order_batch_stock">bakeupboys/nfu</a> project on GitHub.</p>
<h3><a class="toc-backref" href="#toc-entry-6">Maintainers</a></h3>
<p>This module is part of the <a class="reference external" href="https://github.com/madmooose/odooapps/tree/16.0/sale_order_batch_stock">madmooose/odooapps</a> project on GitHub.</p>
<p>You are welcome to contribute.</p>
</div>
</div>
</div>
</div>
</body>
</html>

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="view_order_batch_form" model="ir.ui.view">
<field name="name">sale.order.batch.form.stock</field>
<field name="model">sale.order.batch</field>
<field name="inherit_id" ref="sale_order_batch.view_order_batch_form"/>
<field name="inherit_id" ref="sale_order_batch.view_order_batch_form" />
<field name="arch" type="xml">
<xpath expr="//button[@name='action_view_invoice']" position="before">
<button
@ -14,7 +14,11 @@
attrs="{'invisible': [('delivery_count', '=', 0)]}"
groups="stock.group_stock_user"
>
<field name="delivery_count" widget="statinfo" string="Delivery"/>
<field
name="delivery_count"
widget="statinfo"
string="Delivery"
/>
</button>
</xpath>
</field>

View file

@ -1,2 +0,0 @@
# addons listed in this file are ignored by
# setuptools-odoo-make-default (one addon per line)

View file

@ -1,2 +0,0 @@
To learn more about this directory, please visit
https://pypi.python.org/pypi/setuptools-odoo

View file

@ -1 +0,0 @@
../../../../sale_order_batch

View file

@ -1,6 +0,0 @@
import setuptools
setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)

View file

@ -0,0 +1,73 @@
.. image:: https://odoo-community.org/readme-banner-image
:target: https://odoo-community.org/get-involved?utm_source=readme
:alt: Odoo Community Association
=================================
Website Variant Values Visibility
=================================
..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:3b6aaf2b68e397b27d94f5bb02aabb23fd19e14682b50bac025280482e939614
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/license-LGPL--3-blue.png
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-madmooose%2Fodooapps-lightgray.png?logo=github
:target: https://github.com/madmooose/odooapps/tree/16.0/website_variant_values_visibility
:alt: madmooose/odooapps
|badge1| |badge2| |badge3|
This module allows control over the visibility of product variant values
in the e-commerce website.
**Table of contents**
.. contents::
:local:
Changelog
=========
- 16.0.1.0.0: Adds visibility control for variant values in e-commerce.
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/madmooose/odooapps/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/madmooose/odooapps/issues/new?body=module:%20website_variant_values_visibility%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
-------
* IFE Gesellschaft für Forschung und Entwicklung
* Niels Göttsch
Contributors
------------
- René Greßmann
- Hiren Lakhani
- Niels Göttsch
Maintainers
-----------
This module is part of the `madmooose/odooapps <https://github.com/madmooose/odooapps/tree/16.0/website_variant_values_visibility>`_ project on GitHub.
You are welcome to contribute.

View file

@ -0,0 +1 @@
from . import models

View file

@ -0,0 +1,16 @@
{
"name": "Website Variant Values Visibility",
"version": "16.0.1.0.0",
"category": "Product",
"summary": "Adds visibility control for variant values in e-commerce",
"author": "IFE Gesellschaft für Forschung und Entwicklung, Niels Göttsch",
"website": "https://ziemlichoptimal.de",
"depends": ["product", "website_sale"],
"data": [
"views/product_attribute_value_views.xml",
"views/variant_template.xml",
],
"installable": True,
"auto_install": False,
"license": "LGPL-3",
}

View file

@ -0,0 +1,31 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * website_variant_values_visibility
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 17.0+e-20240411\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-06-12 05:27+0000\n"
"PO-Revision-Date: 2024-06-12 05:27+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: website_variant_values_visibility
#: model:ir.model,name:website_variant_values_visibility.model_product_attribute_value
msgid "Attribute Value"
msgstr "Attributwert"
#. module: website_variant_values_visibility
#: model:ir.model.fields,help:website_variant_values_visibility.field_product_attribute_value__visible_in_ecommerce
msgid "Determines if the variant value is visible in the e-commerce."
msgstr "Ermittelt, ob der Variantenwert im E-Commerce sichtbar ist."
#. module: website_variant_values_visibility
#: model:ir.model.fields,field_description:website_variant_values_visibility.field_product_attribute_value__visible_in_ecommerce
msgid "Visible in e-commerce"
msgstr "Sichtbar in E-Commerce"

View file

@ -0,0 +1 @@
from . import product_attribute_value

View file

@ -0,0 +1,16 @@
from odoo import fields, models
class ProductAttributeValue(models.Model):
"""
This class extends the functionality of product attribute values in Odoo.
It adds a boolean field to determine if the variant value is visible in e-commerce.
"""
_inherit = "product.attribute.value"
visible_in_ecommerce = fields.Boolean(
string="Visible in e-commerce",
default=True,
help="Determines if the variant value is visible in the e-commerce.",
)

View file

@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"

View file

@ -0,0 +1,4 @@
- René Greßmann
- Hiren Lakhani
- Niels Göttsch

View file

@ -0,0 +1,2 @@
This module allows control over the visibility of product variant values in the e-commerce website.

View file

@ -0,0 +1 @@
- 16.0.1.0.0: Adds visibility control for variant values in e-commerce.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -0,0 +1,433 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>README.rst</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
Despite the name, some widely supported CSS2 features are used.
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: gray; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic, pre.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document">
<a class="reference external image-reference" href="https://odoo-community.org/get-involved?utm_source=readme">
<img alt="Odoo Community Association" src="https://odoo-community.org/readme-banner-image" />
</a>
<div class="section" id="website-variant-values-visibility">
<h1>Website Variant Values Visibility</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:3b6aaf2b68e397b27d94f5bb02aabb23fd19e14682b50bac025280482e939614
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/lgpl-3.0-standalone.html"><img alt="License: LGPL-3" src="https://img.shields.io/badge/license-LGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/madmooose/odooapps/tree/16.0/website_variant_values_visibility"><img alt="madmooose/odooapps" src="https://img.shields.io/badge/github-madmooose%2Fodooapps-lightgray.png?logo=github" /></a></p>
<p>This module allows control over the visibility of product variant values
in the e-commerce website.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#changelog" id="toc-entry-1">Changelog</a></li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-2">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-3">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-4">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-5">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-6">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="changelog">
<h2><a class="toc-backref" href="#toc-entry-1">Changelog</a></h2>
<ul class="simple">
<li>16.0.1.0.0: Adds visibility control for variant values in e-commerce.</li>
</ul>
</div>
<div class="section" id="bug-tracker">
<h2><a class="toc-backref" href="#toc-entry-2">Bug Tracker</a></h2>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/madmooose/odooapps/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/madmooose/odooapps/issues/new?body=module:%20website_variant_values_visibility%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h2><a class="toc-backref" href="#toc-entry-3">Credits</a></h2>
<div class="section" id="authors">
<h3><a class="toc-backref" href="#toc-entry-4">Authors</a></h3>
<ul class="simple">
<li>IFE Gesellschaft für Forschung und Entwicklung</li>
<li>Niels Göttsch</li>
</ul>
</div>
<div class="section" id="contributors">
<h3><a class="toc-backref" href="#toc-entry-5">Contributors</a></h3>
<ul class="simple">
<li>René Greßmann</li>
<li>Hiren Lakhani</li>
<li>Niels Göttsch</li>
</ul>
</div>
<div class="section" id="maintainers">
<h3><a class="toc-backref" href="#toc-entry-6">Maintainers</a></h3>
<p>This module is part of the <a class="reference external" href="https://github.com/madmooose/odooapps/tree/16.0/website_variant_values_visibility">madmooose/odooapps</a> project on GitHub.</p>
<p>You are welcome to contribute.</p>
</div>
</div>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1 @@
from . import test_product_attribute_value

View file

@ -0,0 +1,43 @@
from odoo.tests import TransactionCase
class TestProductAttributeValue(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.env = cls.env(context={**cls.env.context, "tracking_disable": True})
cls.attribute = cls.env["product.attribute"].create({"name": "Color"})
cls.value_red = cls.env["product.attribute.value"].create(
{"name": "Red", "attribute_id": cls.attribute.id}
)
cls.value_blue = cls.env["product.attribute.value"].create(
{"name": "Blue", "attribute_id": cls.attribute.id}
)
# --- visible_in_ecommerce field ---
def test_default_is_true(self):
value = self.env["product.attribute.value"].create(
{"name": "Green", "attribute_id": self.attribute.id}
)
self.assertTrue(value.visible_in_ecommerce)
def test_can_be_set_false_on_create(self):
value = self.env["product.attribute.value"].create(
{
"name": "Yellow",
"attribute_id": self.attribute.id,
"visible_in_ecommerce": False,
}
)
self.assertFalse(value.visible_in_ecommerce)
def test_write_false(self):
self.value_red.write({"visible_in_ecommerce": False})
self.assertFalse(self.value_red.visible_in_ecommerce)
def test_write_toggle(self):
self.value_red.write({"visible_in_ecommerce": False})
self.assertFalse(self.value_red.visible_in_ecommerce)
self.value_red.write({"visible_in_ecommerce": True})
self.assertTrue(self.value_red.visible_in_ecommerce)

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- This XML record extends the product attribute form view to include the 'visible_in_ecommerce' field -->
<record id="view_product_attribute_value_form" model="ir.ui.view">
<field name="name">product.attribute.form</field>
<field name="model">product.attribute</field>
<field name="inherit_id" ref="product.product_attribute_view_form" />
<field name="arch" type="xml">
<xpath expr="//field[@name='is_custom']" position="after">
<field name="visible_in_ecommerce" />
</xpath>
</field>
</record>
</odoo>

View file

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<template id="products_attributes" inherit_id="website_sale.products_attributes">
<xpath expr="//t[@t-foreach='attributes']/div" position="attributes">
<attribute
name="t-if"
>a.value_ids.filtered(lambda v: v.visible_in_ecommerce) and len(a.value_ids.filtered(lambda v: v.visible_in_ecommerce)) &gt; 1
</attribute>
</xpath>
<xpath
expr="//t[@t-if=&quot;a.display_type == 'select'&quot;]//t"
position="attributes"
>
<attribute
name="t-foreach"
>a.value_ids.filtered(lambda v: v.visible_in_ecommerce)</attribute>
</xpath>
<xpath
expr="//t[@t-if=&quot;a.display_type == 'radio' or a.display_type == 'pills'&quot;]//t"
position="attributes"
>
<attribute
name="t-foreach"
>a.value_ids.filtered(lambda v: v.visible_in_ecommerce)</attribute>
</xpath>
<xpath
expr="//div[@t-if=&quot;a.display_type == 'color'&quot;]//t"
position="attributes"
>
<attribute
name="t-foreach"
>a.value_ids.filtered(lambda v: v.visible_in_ecommerce)</attribute>
</xpath>
</template>
<template id="o_wsale_offcanvas" inherit_id="website_sale.o_wsale_offcanvas">
<xpath expr="//t[@t-foreach='attributes']//div" position="attributes">
<attribute
name="t-if"
>a.value_ids.filtered(lambda v: v.visible_in_ecommerce) and len(a.value_ids.filtered(lambda v: v.visible_in_ecommerce)) &gt; 1</attribute>
</xpath>
<xpath expr="//div[@t-foreach='a.value_ids']" position="attributes">
<attribute
name="t-foreach"
>a.value_ids.filtered(lambda v: v.visible_in_ecommerce)</attribute>
</xpath>
<xpath
expr="//t[@t-foreach='attributes']//div[hasclass('accordion-body')]//t[@t-foreach='a.value_ids']"
position="attributes"
>
<attribute
name="t-foreach"
>a.value_ids.filtered(lambda v: v.visible_in_ecommerce)</attribute>
</xpath>
</template>
</odoo>