chore: import enterprise addons 19.0 bulk snapshot

This commit is contained in:
1b-agent committed 2026-09-16 10:13:08 +00:00
commit ca4af030d3
39951 files changed
+15570005

No files matched your search

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

@@ -0,0 +1 @@
<svg width="50" height="50" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg"><path d="m31.134 28.846 7.142-27.613c.251-.934-.71-1.612-1.47-.998L8.352 23.231c-.632.51-.367 1.273.432 1.487l19.863 5.514c1.09.291 2.203-.33 2.487-1.386Z" fill="#2EBCFA"/><path d="m18.867 21.153-7.143 27.613c-.25.934.71 1.612 1.47.998l28.455-22.997c.631-.51.367-1.272-.433-1.486l-19.862-5.514c-1.09-.291-2.203.329-2.487 1.386Z" fill="#985184"/><path d="m32.67 22.908-1.535 5.937c-.284 1.057-1.398 1.677-2.487 1.386L17.332 27.09l1.536-5.937c.284-1.057 1.397-1.677 2.486-1.386l11.316 3.141Z" fill="#144496"/></svg>

After

Width:  |  Height:  |  Size: 597 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

@@ -0,0 +1,24 @@
import { FSMProductCatalogKanbanRecord } from "@industry_fsm_sale/components/product_catalog/kanban_record";
import { patch } from "@web/core/utils/patch";
patch(FSMProductCatalogKanbanRecord.prototype, {
updateQuantity(quantity) {
if (this.productCatalogData.tracking) {
super.updateQuantity(this.productCatalogData.quantity);
} else {
if (
this.productCatalogData.quantity === this.productCatalogData.minimumQuantityOnProduct &&
quantity < this.productCatalogData.quantity
) {
// This condition is only triggered when the product was already at the minimum quantity
// possible, as stated in the fsm_stock module, then the user inputs a quantity lower
// than this limit, in this case we need the record to forcefully update the record.
this.props.record.load();
this.props.record.model.notify();
} else {
super.updateQuantity(Math.max(quantity, this.productCatalogData.minimumQuantityOnProduct));
}
}
},
});
@@ -0,0 +1,19 @@
import { ProductCatalogSaleOrderLine } from "@sale_stock/product_catalog/sale_order_line/sale_order_line";
import { patch } from "@web/core/utils/patch";
patch(ProductCatalogSaleOrderLine, {
props: {
...ProductCatalogSaleOrderLine.props,
tracking: Boolean,
minimumQuantityOnProduct: Number,
},
});
patch(ProductCatalogSaleOrderLine.prototype, {
get disableRemove() {
if (this.env.fsm_task_id) {
return this.props.quantity === this.props.minimumQuantityOnProduct;
}
return this.props.quantity === this.props.deliveredQty;
},
});
@@ -0,0 +1,3 @@
.o_product_catalog_cancel_global_click .fa-list {
color: black;
}
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-inherit="product.ProductCatalogOrderLine" t-inherit-mode="extension">
<xpath expr="//div[hasclass('o_product_catalog_quantity')]/div/div/input" position="replace">
<input t-if="!this.props.tracking or !this.env.fsm_task_id"
class="o_input form-control text-center text-bg-light border border-end-0 rounded-0"
type="number"
t-att-class="this.env.displayUoM ? 'w-25' : 'w-100'"
t-att-value="quantity"
t-on-change="this.env.setQuantity"
/>
<button t-else=""
class="o_input form-control border text-center text-bg-light"
t-on-click.stop="(ev) => this.env.increaseQuantity()"
t-esc="quantity"
>
</button>
</xpath>
<xpath expr="//div[hasclass('o_product_catalog_buttons')]" position="before">
<button t-if="this.props.tracking and this.props.quantity != 0 and this.env.fsm_task_id"
class="btn align-self-end o_product_catalog_cancel_global_click"
type="object" t-on-click="this.env.resetQuantity">
<i class="fa fa-list text-success" title="Lot/Serial number"></i>
</button>
</xpath>
</t>
</templates>
@@ -0,0 +1,139 @@
import { expect, test, describe } from "@odoo/hoot";
import { click } from "@odoo/hoot-dom";
import { runAllTimers } from "@odoo/hoot-mock";
import { deepCopy } from "@web/core/utils/objects";
import { defineModels, mountView, onRpc, contains } from "@web/../tests/web_test_helpers";
import { defineMailModels } from "@mail/../tests/mail_test_helpers";
import { ProductProduct } from "@industry_fsm_sale/../tests/industry_fsm_sale_mock_model";
const stockSaleOrderLineInfo = {
1: {
quantity: 0,
readOnly: false,
price: 14,
deliveredQty: 0,
minimumQuantityOnProduct: 0,
tracking: false,
productType: "consu",
uomDisplayName: "Unit",
},
2: {
quantity: 1,
readOnly: false,
price: 400,
deliveredQty: 0,
minimumQuantityOnProduct: 0,
tracking: true,
productType: "consu",
uomDisplayName: "Unit",
},
3: {
quantity: 3,
price: 70,
readOnly: false,
deliveredQty: 2,
minimumQuantityOnProduct: 2,
tracking: false,
productType: "consu",
uomDisplayName: "Unit",
},
};
describe.current.tags("desktop");
defineModels([ProductProduct]);
defineMailModels();
onRpc("/product/catalog/order_lines_info", () => deepCopy(stockSaleOrderLineInfo));
test("check disabling of decrease/remove buttons when quantity of product is equal to minimumQuantityOnProduct", async () => {
onRpc("/product/catalog/update_order_line_info", async (request) => {
const { params } = await request.json();
const { product_id, quantity } = params;
expect.step("update_sale_order_line_info");
if (product_id === 3) {
expect(quantity).toBe(2, {
message: "Using the Remove button should set the quantity to 0 in the route params",
});
}
return {};
});
await mountView({
resModel: "product.product",
type: "kanban",
context: {
product_catalog_order_model: "sale.order",
fsm_task_id: 1,
},
});
await click(".o_kanban_record:nth-of-type(3) .o_product_catalog_quantity button:has(i.fa-minus)");
await runAllTimers(); // for skipping the debounce delay
expect(
".o_kanban_record:nth-of-type(3) .o_product_catalog_quantity button:has(i.fa-minus)[disabled]"
).toHaveCount(1, {
message: "The minus button should be disabled",
});
expect(
".o_kanban_record:nth-of-type(3) .o_product_catalog_buttons button:has(i.fa-trash)[disabled]"
).toHaveCount(1, {
message: "The remove button should be disabled",
});
expect.verifySteps(["update_sale_order_line_info"]);
});
test("check quantity not decreasable below minimumQuantityOnProduct", async () => {
onRpc("/product/catalog/update_order_line_info", async (request) => {
const { params } = await request.json();
const { product_id, quantity } = params;
expect.step("update_sale_order_line_info");
if (product_id === 3) {
expect(quantity).toBe(2, {
message:
"Trying to set a quantity below the minimumQuantityOnProduct should result in giving minimumQuantityOnProduct value in the route params",
});
}
return {};
});
await mountView({
resModel: "product.product",
type: "kanban",
context: {
product_catalog_order_model: "sale.order",
fsm_task_id: 1,
},
});
expect(".o_kanban_record:nth-child(3) .o_product_catalog_quantity .o_input").toHaveValue(3, {
message: "The product quantity should be equal to 3.",
});
await contains(".o_kanban_record:nth-child(3) .o_product_catalog_quantity .o_input").edit(1);
await runAllTimers(); // for skipping the debounce delay
expect(".o_kanban_record:nth-child(3) .o_product_catalog_quantity .o_input").toHaveValue(2, {
message:
"Trying to remove a product that has a minimumQuantityOnProduct should set the value to minimuQuantityOnProduct",
});
expect.verifySteps(["update_sale_order_line_info"]);
});
test("check fa-list display", async () => {
await mountView({
resModel: "product.product",
type: "kanban",
context: {
product_catalog_order_model: "sale.order",
fsm_task_id: 1,
},
});
expect(".o_kanban_record:nth-of-type(2) button:has(i.fa-list)").toHaveCount(1, {
message: "The fa-list icon should be available",
});
});
@@ -0,0 +1,30 @@
import { registry } from "@web/core/registry";
import { patch } from "@web/core/utils/patch";
patch(registry.category("web_tour.tours").get("industry_fsm_sale_products_tour"), {
steps() {
const originalSteps = super.steps();
const fsmStockIndex = originalSteps.findIndex((step) => step.id === "fsm_stock_start");
originalSteps.splice(fsmStockIndex + 1, 0, {
trigger: '.o_fsm_product_kanban_view .o_kanban_group:has(.o_kanban_header:has(span:contains("Service"))) .o_kanban_record:has(span:contains("Acoustic Bloc Screens"))',
content: 'Add 1 quantity to the Service product',
run: "click",
}, {
trigger: '.o_fsm_product_kanban_view .o_kanban_group:has(.o_kanban_header:has(span:contains("Service"))) .o_kanban_record:has(span:contains("Acoustic Bloc Screens"))',
content: 'Add 1 quantity to the Service product',
run: "click",
}, {
trigger: '.o_fsm_product_kanban_view .o_kanban_group:has(.o_kanban_header:has(span:contains("Service"))) .o_kanban_record:has(span:contains("Acoustic Bloc Screens")) button:has(i.fa-minus:not([disabled]))',
content: 'Check that the quantity of the Service product is still decreasable even though it is considered delivered by default',
run: "click",
}, {
trigger: '.o_fsm_product_kanban_view .o_kanban_group:has(.o_kanban_header:has(span:contains("Service"))) .o_kanban_record:has(span:contains("Acoustic Bloc Screens")) .o_product_catalog_buttons .fa-trash',
content: 'Check that the quantity of the Service product is still removable even though it is considered delivered by default',
run: "click",
}, {
trigger: '.o_fsm_product_kanban_view .o_kanban_group:has(.o_kanban_header:has(span:contains("Service"))) .o_kanban_record:has(span:contains("Acoustic Bloc Screens")) .o_product_catalog_buttons .fa-shopping-cart',
content: 'Check that the quantity of the Service product were correctly removed',
});
return originalSteps;
}
});
@@ -0,0 +1,298 @@
import { registry } from "@web/core/registry";
import { stepUtils } from "@web_tour/tour_utils";
const StepToFSMProductsKanbanWithFavoritesFilterSteps = [
{
content: 'Open FSM app.',
trigger: '.o_app[data-menu-xmlid="industry_fsm.fsm_menu_root"]',
run: "click",
},
{
content: 'Open All Tasks.',
trigger: 'button[data-menu-xmlid="industry_fsm.fsm_menu_all_tasks_root"]',
run: "click",
},
{
content: 'Open All Tasks.',
trigger: 'a[data-menu-xmlid="industry_fsm.fsm_menu_all_tasks_todo"]',
run: "click",
},
{
content: 'Open Task Form',
trigger: ".o_data_row:has(span:contains(Fsm task)) .o_data_cell",
run: "click",
},
{
content: 'Open the Product Kanban View',
trigger: 'button[name="action_fsm_view_material"]',
run: "click",
}
];
const AddTrackingLineAndValidateSteps = [
{
content: 'Add a line in the wizard',
trigger: ".modal div[name=tracking_line_ids] a[role=button]:contains(Add a line)",
run: "click",
},
{
content: 'Enter the lot number',
trigger:
".modal div[name=tracking_line_ids] tr.o_data_row.o_selected_row div[name=lot_id] input[type=text]",
run: 'edit Lot_1',
},
{
isActive: ["auto"],
content: 'Select Lot_1',
trigger: ".o-autocomplete--dropdown-menu li:contains(Lot_1)",
run: "click",
},
{
content: 'Validate',
trigger: ".modal button[name=generate_lot]",
run: "click",
},
{
trigger: "body:not(:has(.modal))",
},
];
/**
* This tour depends on data created by python test in charge of launching it.
* It is not intended to work when launched from interface.
* The main purpose of this tour is:
* - To check that quantity_decreasable is correctly used in the interface and prevents
* the user to decrease quantities.
* - To check that warehouses (in the move and the default one of the user) are taken into account
* in quantity_decreasable
* - To check that the fsm.stock.tracking wizard is taking the warehouses into account. When one line
* is implied in a move from a different warehouse than the current user's default one, an added
* column is displayed with the warehouse of the move implied by the line. The records that implies
* moves from another warehouse than the current user's default one are muted and readonly.
*/
registry.category("web_tour.tours").add('industry_fsm_stock_test_tour', {
url: "/odoo",
steps: () => [
stepUtils.showAppsMenuItem(),
...StepToFSMProductsKanbanWithFavoritesFilterSteps,
{
trigger: ".o_kanban_record:contains(Product A)",
},
{
content: 'Add quantity to the first product (no lot)',
trigger: '.o_kanban_record:first-child button:has(i.fa-shopping-cart)',
run: "click",
},
{
content: 'Add quantity to the first product (no lot)',
trigger: '.o_kanban_record:first-child button:has(i.fa-plus)',
run: "click",
},
{
content: 'Add quantity to the second product (lot)',
trigger: '.o_kanban_record:nth-of-type(2) button:has(i.fa-shopping-cart)',
run: "click",
},
{
content: 'Check that the warehouse column is not visible (thus that the second one is the Quantity)',
trigger: 'div[name="tracking_line_ids"] table thead th:nth-of-type(2)[data-name="quantity"]',
},
...AddTrackingLineAndValidateSteps,
{
trigger: '.o_back_button',
content: 'go back to fsm',
run: "click",
},
{
trigger: 'button[name="action_view_so"]',
content: 'Go to the sale order',
run: "click",
},
{
trigger: 'button[name="action_view_delivery"]',
content: 'Go to the the delivery',
run: "click",
},
{
trigger: 'button[name="button_validate"]',
content: 'Validate delivery',
run: "click",
},
{
trigger: '.o_back_button',
content: 'Go back to SO',
run: "click",
},
{
trigger: 'a.fw-bold.text-truncate[data-tooltip*="Fsm task"]',
content: 'Go back to fsm task',
run: "click",
},
{
trigger: 'button[name="action_fsm_view_material"]',
content: 'Click on the Products stat button',
run: "click",
},
{
content: 'Check that is it not possible to reduce the quantity of the first product (no lot) since it has been delivered',
trigger: '.o_kanban_record:first-child:has(button:has(i.fa-trash)[disabled])',
},
{
content: 'Check that is it not possible to reduce the quantity of the first product (lot) since it has been delivered',
trigger: '.o_kanban_record:nth-of-type(2) .o_product_catalog_quantity:has(button[disabled]):has(i.fa-minus)',
},
{
content: 'Add quantity to the first product (no lot)',
trigger: '.o_kanban_record:first-child button:has(i.fa-plus)',
run: "click",
},
{
content: 'Check that is it now possible to reduce the quantity of the first product (no lot) since we added quantities',
trigger: '.o_kanban_record:first-child:not(button:has(i.fa-trash)[disabled])',
},
{
content: 'Open the second product (lot) SN assignation wizard by using the plus button',
trigger: '.o_kanban_record:nth-of-type(2) button:has(i.fa-plus)',
run: "click",
},
{
trigger:
'.modal-content .modal-body .o_form_view_container div[name="tracking_line_ids"]',
},
{
content: 'Check that clicking on the plus button opened the Serial number assignation wizard',
trigger: '.modal-content .modal-header .btn-close',
run: "click",
},
{
content: 'Open the second product (lot) SN assignation wizard by using the fa-list button',
trigger: '.o_kanban_record:nth-of-type(2) button:has(i.fa-list)',
run: "click",
},
{
trigger:
'.modal-content .modal-body .o_form_view_container div[name="tracking_line_ids"]',
},
{
content: 'Check that clicking on the fa-list button opened the Serial number assignation wizard',
trigger: '.modal-content .modal-header .btn-close',
run: "click",
},
{
content: 'Open the second product (lot) SN assignation wizard by inputing a quantity',
trigger: '.o_kanban_record:nth-of-type(2) .o_product_catalog_quantity .text-bg-light',
run: "click",
},
{
content: 'Check that inputing a quantity opened the Serial number assignation wizard',
trigger: '.modal-content .modal-body .o_form_view_container div[name="tracking_line_ids"]',
run: "click",
},
{
content: 'Check that the Already Delivered list view is displayed',
trigger: 'div[name="tracking_validated_line_ids"] table',
run: "click",
},
{
content: 'Check that the warehouse column is not visible (thus that the second one is the Quantity)',
trigger: 'div[name="tracking_line_ids"] table thead th:nth-of-type(2)[data-name="quantity"]',
},
...AddTrackingLineAndValidateSteps,
{
content: 'Check that its now possible to reduce the quantity of the first product (lot) since we added new quantities to it',
trigger: '.o_kanban_record:nth-of-type(2) .o_product_catalog_quantity:not(button:has(i.fa-minus)[disabled])',
},
{
content: 'Open the second product (lot) SN assignation wizard by using the minus button',
trigger: '.o_kanban_record:nth-of-type(2) button:has(i.fa-minus)',
run: "click",
},
{
trigger:
'.modal-content .modal-body .o_form_view_container div[name="tracking_line_ids"]',
},
{
content: 'Check that clicking on the minus button opened the Serial number assignation wizard',
trigger: '.modal-content .modal-header .btn-close',
run: "click",
},
{
content: 'Go back to app switcher',
trigger: 'nav.o_main_navbar a.o_menu_toggle',
run: "click",
},
{
content: 'Open user menu',
trigger: 'div.o_user_menu button',
run: "click",
},
{
content: 'Open My Preferences',
trigger: '.dropdown-menu .dropdown-item[data-menu="preferences"]',
run: "click",
},
{
content: 'Change the default warehouse to WH B',
trigger: 'div[name="property_warehouse_id"] input[type="text"]',
run: 'edit WH B',
},
{
isActive: ["auto"],
content: 'Select WH B',
trigger: ".ui-menu-item > a:contains(WH B)",
run: "click",
},
{
content: "Save the form",
trigger: 'button[name="preference_save"]',
run: "click",
expectUnloadPage: true,
},
...StepToFSMProductsKanbanWithFavoritesFilterSteps,
{
content: 'Check that is it not possible to reduce the quantity of the first product (no lot) because of the warehouse change',
trigger: '.o_kanban_record:first-child:has(button:has(i.fa-trash)[disabled])',
run: "click",
},
{
content: 'Check that is it not possible to reduce the quantity of the first product (lot) because of the warehouse change',
trigger: '.o_kanban_record:nth-of-type(2) .o_product_catalog_quantity:has(button[disabled]):has(i.fa-minus)',
run: "click",
},
{
content: 'Add quantity to the first product (lot)',
trigger: '.o_kanban_record:nth-of-type(2) button:has(i.fa-plus)',
run: "click",
},
{
content: 'Check that the warehouse column is visible',
trigger: 'div[name="tracking_line_ids"] table thead th:nth-of-type(2)[data-name="warehouse_id"]',
run: "click",
},
{
content: "Check that the previous entry which is in a different warehouse than the user's default one is muted and readonly",
trigger: 'div[name="tracking_line_ids"] table tbody tr:first-child.text-muted td[name="quantity"].o_readonly_modifier',
run: "click",
},
...AddTrackingLineAndValidateSteps,
{
content: 'Add quantity to the first product (lot)',
trigger: '.o_kanban_record:nth-of-type(2) button:has(i.fa-plus)',
run: "click",
},
{
content: 'Check that the warehouse column is visible',
trigger: 'div[name="tracking_line_ids"] table thead th:nth-of-type(2)[data-name="warehouse_id"]',
run: "click",
},
{
content: "Check that the previous entry which is in a different warehouse than the user's default one is muted and readonly",
trigger: 'div[name="tracking_line_ids"] table tbody tr:first-child.text-muted td[name="quantity"].o_readonly_modifier',
run: "click",
},
{
content: "Check that the previous entry which is in the same warehouse than the user's default one is not muted and editable",
trigger: 'div[name="tracking_line_ids"] table tbody tr:nth-of-type(2):not(.text-muted) td[name="quantity"]:not(.o_readonly_modifier)',
run: "click",
},
]});