diff --git a/appointment_filter/__init__.py b/appointment_filter/__init__.py new file mode 100644 index 00000000000..e046e49fbe2 --- /dev/null +++ b/appointment_filter/__init__.py @@ -0,0 +1 @@ +from . import controllers diff --git a/appointment_filter/__manifest__.py b/appointment_filter/__manifest__.py new file mode 100644 index 00000000000..a5f9ed2d99d --- /dev/null +++ b/appointment_filter/__manifest__.py @@ -0,0 +1,9 @@ +{ + "name": "Appointment Filter", + "depends": ["website_appointment", "appointment_account_payment"], + "data": [ + "views/website_appointment_filter_template.xml", + ], + "installable": True, + "license": "LGPL-3", +} diff --git a/appointment_filter/controllers/__init__.py b/appointment_filter/controllers/__init__.py new file mode 100644 index 00000000000..c9bc0f941bd --- /dev/null +++ b/appointment_filter/controllers/__init__.py @@ -0,0 +1 @@ +from . import appointment_filter diff --git a/appointment_filter/controllers/appointment_filter.py b/appointment_filter/controllers/appointment_filter.py new file mode 100644 index 00000000000..585239f99db --- /dev/null +++ b/appointment_filter/controllers/appointment_filter.py @@ -0,0 +1,48 @@ +from odoo.addons.website_appointment.controllers.appointment import WebsiteAppointment +from odoo.http import request + + +class AppointmentFilterController(WebsiteAppointment): + def _appointments_base_domain( + cls, + filter_appointment_type_ids, + search=False, + invite_token=False, + additional_domain=None, + ): + domain = super()._appointments_base_domain( + filter_appointment_type_ids, search, invite_token, additional_domain + ) + + filter_location = request.params.get("filter_location") + if filter_location == "Online": + domain.append(("location_id", "=", False)) + elif filter_location == "Offline": + domain.append(("location_id", "!=", False)) + + filter_based_on = request.params.get("filter_based_on") + if filter_based_on == "Users": + domain.append(("schedule_based_on", "=", "users")) + elif filter_based_on == "Resources": + domain.append(("schedule_based_on", "=", "resources")) + + filter_payment = request.params.get("filter_payment") + if filter_payment == "Required": + domain.append(("has_payment_step", "=", True)) + elif filter_payment == "No Required": + domain.append(("has_payment_step", "=", False)) + + return domain + + def _prepare_appointments_cards_data(self, page, appointment_types, **kwargs): + context = super()._prepare_appointments_cards_data( + page, appointment_types, **kwargs + ) + + context["filters"] = { + "filter_location": kwargs.get("filter_location"), + "filter_based_on": kwargs.get("filter_based_on"), + "filter_payment": kwargs.get("filter_payment"), + } + + return context diff --git a/appointment_filter/views/website_appointment_filter_template.xml b/appointment_filter/views/website_appointment_filter_template.xml new file mode 100644 index 00000000000..3bf6aa9fa20 --- /dev/null +++ b/appointment_filter/views/website_appointment_filter_template.xml @@ -0,0 +1,67 @@ + + + + diff --git a/billing_address_in_website_sale/__init__.py b/billing_address_in_website_sale/__init__.py new file mode 100644 index 00000000000..e046e49fbe2 --- /dev/null +++ b/billing_address_in_website_sale/__init__.py @@ -0,0 +1 @@ +from . import controllers diff --git a/billing_address_in_website_sale/__manifest__.py b/billing_address_in_website_sale/__manifest__.py new file mode 100644 index 00000000000..e1e8b23d551 --- /dev/null +++ b/billing_address_in_website_sale/__manifest__.py @@ -0,0 +1,19 @@ +{ + "name": "Billing Address In Website", + "depends": [ + "website_sale", + ], + "data": [ + "views/templates.xml", + ], + "assets": { + "web.assets_frontend": [ + "billing_address_in_website_sale/static/src/**/*", + ], + "web.assets_tests": [ + "billing_address_in_website_sale/static/tests/**/*", + ], + }, + "installable": True, + "license": "LGPL-3", +} diff --git a/billing_address_in_website_sale/controllers/__init__.py b/billing_address_in_website_sale/controllers/__init__.py new file mode 100644 index 00000000000..12a7e529b67 --- /dev/null +++ b/billing_address_in_website_sale/controllers/__init__.py @@ -0,0 +1 @@ +from . import main diff --git a/billing_address_in_website_sale/controllers/main.py b/billing_address_in_website_sale/controllers/main.py new file mode 100644 index 00000000000..5c933725108 --- /dev/null +++ b/billing_address_in_website_sale/controllers/main.py @@ -0,0 +1,43 @@ +from odoo.addons.website_sale.controllers.main import WebsiteSale +from odoo.http import request, route + + +class WebsiteSaleInherit(WebsiteSale): + @route("/shop/vat/address", type="json", auth="public", website=True) + def get_address(self, vat=None): + if not vat: + return {"error": "Please enter VAT number"} + address_values = request.env["res.partner"].sudo().enrich_by_gst(vat) + if not address_values: + return {"error": "Please enter valid VAT number"} + return address_values + + @route( + "/shop/billing_address/submit", + type="json", + auth="public", + website=True, + ) + def shop_billing_address_submit(self, address=None, name=None, partner_id=None): + order_sudo = request.website.sale_get_order() + if partner_id: + partner_sudo = request.env["res.partner"].browse(int(partner_id)) + partner_sudo.write({"name": name}) + return True + partner_sudo = request.env["res.partner"].sudo().create({ + 'name': name, + 'company_type': 'company', + 'parent_id': False, + 'street': address.get('street'), + 'street2': address.get('street2'), + 'city': address.get('city'), + 'state_id': address.get('state_id', {}).get('id', False), + 'country_id': address.get('country_id', {}).get('id', False), + 'zip': address.get('zip'), + 'vat': address.get('vat'), + 'email': address.get("email") or order_sudo.partner_id.email, + 'phone': address.get("phone") or order_sudo.partner_id.phone, + }) + order_sudo._update_address(partner_sudo.id, {"partner_invoice_id"}) + order_sudo.partner_id.write({"type": "delivery", "parent_id": partner_sudo}) + return True diff --git a/billing_address_in_website_sale/static/src/checkout.js b/billing_address_in_website_sale/static/src/checkout.js new file mode 100644 index 00000000000..f763e6a766b --- /dev/null +++ b/billing_address_in_website_sale/static/src/checkout.js @@ -0,0 +1,38 @@ +import WebsiteSaleCheckout from '@website_sale/js/checkout'; +import { rpc } from '@web/core/network/rpc'; + +WebsiteSaleCheckout.include({ + + events: Object.assign({ + 'change #want_tax_credit_checkbox': '_onTaxCreditToggle', + 'change #vat_number': '_onChangeVat', + }, WebsiteSaleCheckout.prototype.events), + + async start() { + this.vatLable = this.el.querySelector('#vat_label'); + this.companyName = this.el.querySelector('#company_name'); + this.address = this.el.querySelector('#address'); + this.partnerId = this.el.querySelector('#partner_id'); + return this._super(...arguments); + }, + + async _onTaxCreditToggle(ev) { + const checkbox = ev.currentTarget; + const taxContainer = this.el.querySelector('#tax_credit_container'); + taxContainer.classList.toggle('d-none', !checkbox.checked); + if (!checkbox.checked) { + const selectedDeliveryAddress = this._getSelectedAddress('delivery'); + await this._selectMatchingBillingAddress(selectedDeliveryAddress.dataset.partnerId); + } + }, + + async _onChangeVat(ev) { + const vat = ev.currentTarget.value.trim(); + const addressValues = await rpc('/shop/vat/address', { vat }); + this.vatLable.textContent = addressValues.country_id ? addressValues.country_id.display_name : "Vat Number"; + this.companyName.value = addressValues.name || ""; + this.address.value = JSON.stringify(addressValues); + this.partnerId.value = ""; + }, + +}); diff --git a/billing_address_in_website_sale/static/src/website_sale_tracking.js b/billing_address_in_website_sale/static/src/website_sale_tracking.js new file mode 100644 index 00000000000..89028bf2234 --- /dev/null +++ b/billing_address_in_website_sale/static/src/website_sale_tracking.js @@ -0,0 +1,37 @@ +import websiteSaleTracking from '@website_sale/js/website_sale_tracking' +import { rpc } from "@web/core/network/rpc"; + +websiteSaleTracking.include({ + + events: Object.assign({ + 'click #confirm_btn': '_onConfirmClick', + }, websiteSaleTracking.prototype.events), + + async _onConfirmClick(ev) { + ev.preventDefault(); + const checkbox = this.el.querySelector("#want_tax_credit_checkbox"); + if (checkbox && checkbox.checked) { + const name = this.el.querySelector("#company_name").value.trim(); + const partner_id = this.el.querySelector('input[name="partner_id"]').value; + const address = JSON.parse(this.el.querySelector("#address").value || '{"error": "Please enter VAT number"}'); + if (address.error) { + this._displayError(address.error); + return; + } + if (!name) { + this._displayError("Company name is required."); + return; + } + await rpc('/shop/billing_address/submit', { name, partner_id, address, }); + } + window.location.href = "/shop/confirm_order"; + }, + + _displayError(msg) { + const errorsDiv = this.el.querySelector("#errors"); + const errorHeader = document.createElement('h5'); + errorHeader.classList.add('text-danger', 'alert', 'alert-danger'); + errorHeader.textContent = msg; + errorsDiv.replaceChildren(errorHeader); + }, +}) diff --git a/billing_address_in_website_sale/static/tests/tours/test_tax_credit_checkbox.js b/billing_address_in_website_sale/static/tests/tours/test_tax_credit_checkbox.js new file mode 100644 index 00000000000..225963a344d --- /dev/null +++ b/billing_address_in_website_sale/static/tests/tours/test_tax_credit_checkbox.js @@ -0,0 +1,64 @@ +import { registry } from "@web/core/registry"; +import * as tourUtils from "@website_sale/js/tours/tour_utils"; + +registry.category("web_tour.tours").add("tax_credit_checkbox_test", { + test: true, + url: "/shop", + steps: () => [ + ...tourUtils.addToCart({ productName: "Office Chair Black TEST" }), + tourUtils.goToCart({ quantity: 1 }), + tourUtils.goToCheckout(), + { + content: "Click 'Want Tax Credit' checkbox", + trigger: "input#want_tax_credit_checkbox", + run: "click", + }, + { + content: "Check VAT and Company fields are visible", + trigger: '#vat_number:visible, #company_name:visible', + }, + { + content: "Click Confirm button", + trigger: "#confirm_btn", + run: "click", + }, + { + content: "Error should show: please enter correct Vat Number", + trigger: "#errors:contains('Please enter VAT number')", + }, + { + content: "Uncheck 'Want Tax Credit' checkbox", + trigger: "input#want_tax_credit_checkbox:checked", + run: "click", + }, + { + content: "Click Confirm button", + trigger: "#confirm_btn", + run: "click", + }, + { + content: "Check delivery and billing are same", + trigger: "#delivery_and_billing", + run: () => { + const address_card = document.querySelector("#delivery_and_billing"); + const address_text = address_card.innerText; + if (!address_text.includes("Delivery & Billing")) { + throw new Error("addresses are not the same"); + } + } + }, + { + content: "Click Edit button on payment page", + trigger: "#delivery_and_billing a[href='/shop/checkout']", + run: "click" + }, + { + content: "Verify Want Tax Credit checkbox is unchecked", + trigger: "#want_tax_credit_checkbox:not(:checked)", + }, + { + content: "Verify tax credit container is hidden", + trigger: "#tax_credit_container:not(:visible)", + }, + ], +}); diff --git a/billing_address_in_website_sale/tests/__init__.py b/billing_address_in_website_sale/tests/__init__.py new file mode 100644 index 00000000000..7837c60e353 --- /dev/null +++ b/billing_address_in_website_sale/tests/__init__.py @@ -0,0 +1 @@ +from . import test_tax_credit_checkbox diff --git a/billing_address_in_website_sale/tests/test_tax_credit_checkbox.py b/billing_address_in_website_sale/tests/test_tax_credit_checkbox.py new file mode 100644 index 00000000000..ca55b420771 --- /dev/null +++ b/billing_address_in_website_sale/tests/test_tax_credit_checkbox.py @@ -0,0 +1,13 @@ +import odoo.tests + + +class TestTaxCreditCheckbox(odoo.tests.HttpCase): + def test_tax_credit_checkbox_flow(self): + self.env["product.product"].create( + { + "name": "Office Chair Black TEST", + "list_price": 12.50, + } + ) + + self.start_tour("/", "tax_credit_checkbox_test", login="admin") diff --git a/billing_address_in_website_sale/views/templates.xml b/billing_address_in_website_sale/views/templates.xml new file mode 100644 index 00000000000..d0a7ae55a85 --- /dev/null +++ b/billing_address_in_website_sale/views/templates.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + diff --git a/distribute_cost_task/__init__.py b/distribute_cost_task/__init__.py new file mode 100644 index 00000000000..b60c2d2e62d --- /dev/null +++ b/distribute_cost_task/__init__.py @@ -0,0 +1,2 @@ +from . import model +from . import wizard diff --git a/distribute_cost_task/__manifest__.py b/distribute_cost_task/__manifest__.py new file mode 100644 index 00000000000..fe37d695322 --- /dev/null +++ b/distribute_cost_task/__manifest__.py @@ -0,0 +1,11 @@ +{ + "name": "Distribute Cost Price", + "depends": ["sale_management"], + "data": [ + "security/ir.model.access.csv", + "wizard/order_line_wizard.xml", + "views/sale_oder_inherit_view.xml", + ], + "installable": True, + "license": "LGPL-3", +} diff --git a/distribute_cost_task/model/__init__.py b/distribute_cost_task/model/__init__.py new file mode 100644 index 00000000000..85d9886c49b --- /dev/null +++ b/distribute_cost_task/model/__init__.py @@ -0,0 +1,3 @@ +from . import order_line_cost_divide +from . import sale_order +from . import sale_order_line diff --git a/distribute_cost_task/model/order_line_cost_divide.py b/distribute_cost_task/model/order_line_cost_divide.py new file mode 100644 index 00000000000..ce3e1cfad59 --- /dev/null +++ b/distribute_cost_task/model/order_line_cost_divide.py @@ -0,0 +1,15 @@ +from odoo import fields, models + + +class OrderLineCostDivide(models.Model): + _name = "order.line.cost.divide" + _description = "order line cost divide" + + cost = fields.Float("Divided Cost") + divide_from_order_line = fields.Many2one( + "sale.order.line", string="Divide from order line" + ) + divide_to_order_line = fields.Many2one( + "sale.order.line", string="Divide to Order Line" + ) + order_id = fields.Many2one("sale.order", string="Order Id", required=True) diff --git a/distribute_cost_task/model/sale_order.py b/distribute_cost_task/model/sale_order.py new file mode 100644 index 00000000000..85c108d7c88 --- /dev/null +++ b/distribute_cost_task/model/sale_order.py @@ -0,0 +1,32 @@ +from odoo import api, fields, models + + +class SaleOrder(models.Model): + _inherit = "sale.order" + + divide_column = fields.Boolean( + string="Divide Column", + compute="_compute_divide_column", + store=True, + default=False, + ) + + @api.depends("order_line.divide_cost") + def _compute_divide_column(self): + data = dict( + self.env["order.line.cost.divide"]._read_group( + [("order_id", "in", self.ids)], ["order_id"], ["__count"] + ) + ) + for order in self: + if data.get(order): + has_zero_divide_cost = any( + line.divide_cost == 0 for line in order.order_line + ) + order.divide_column = has_zero_divide_cost + + def _get_order_lines_to_report(self): + order_lines = super()._get_order_lines_to_report() + return order_lines.filtered( + lambda line: not line.divide_from_order_lines or line.divide_cost > 0 + ) diff --git a/distribute_cost_task/model/sale_order_line.py b/distribute_cost_task/model/sale_order_line.py new file mode 100644 index 00000000000..1fade13d36f --- /dev/null +++ b/distribute_cost_task/model/sale_order_line.py @@ -0,0 +1,45 @@ +from odoo import api, fields, models + + +class SaleOrderLine(models.Model): + _inherit = "sale.order.line" + + divide_cost = fields.Float("Devision") + divide_to_order_lines = fields.One2many( + "order.line.cost.divide", "divide_to_order_line", string="Divided to order line" + ) + divide_from_order_lines = fields.One2many( + "order.line.cost.divide", + "divide_from_order_line", + string="Divided from order line", + ) + + def action_open_order_line_wizard(self): + return { + "name": "Order Line Wizard", + "type": "ir.actions.act_window", + "res_model": "order.wizard", + "view_mode": "form", + "target": "new", + "context": { + "order_id": self.order_id.id, + }, + } + + @api.ondelete(at_uninstall=False) + def _unlink_order_line(self): + for record in self: + for line in record.divide_to_order_lines: + line.divide_from_order_line.divide_cost += line.cost + line.cost = 0.0 + for line in record.divide_from_order_lines: + line.divide_to_order_line.divide_cost -= line.cost + line.cost = 0.0 + + @api.depends("product_uom_qty", "discount", "price_unit", "tax_id", "divide_cost") + def _compute_amount(self): + super()._compute_amount() + for record in self: + if record.divide_from_order_lines: + record.price_subtotal -= record.price_unit + record.price_subtotal += record.divide_cost diff --git a/distribute_cost_task/security/ir.model.access.csv b/distribute_cost_task/security/ir.model.access.csv new file mode 100644 index 00000000000..88258c8c2e7 --- /dev/null +++ b/distribute_cost_task/security/ir.model.access.csv @@ -0,0 +1,4 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_order_wizard,access_order_wizard,model_order_wizard,base.group_user,1,1,1,0 +access_order_line_wizard,access_order_line_wizard,model_order_line_wizard,base.group_user,1,1,1,0 +access_order_line_cost_divide,access_order_line_cost_divide,model_order_line_cost_divide,base.group_user,1,1,1,1 diff --git a/distribute_cost_task/views/sale_oder_inherit_view.xml b/distribute_cost_task/views/sale_oder_inherit_view.xml new file mode 100644 index 00000000000..0bcf468a039 --- /dev/null +++ b/distribute_cost_task/views/sale_oder_inherit_view.xml @@ -0,0 +1,15 @@ + + + sale.order.line.inherit.view + sale.order + + + + + + diff --git a/sales_person_in_pos/views/pos_views.xml b/sales_person_in_pos/views/pos_views.xml new file mode 100644 index 00000000000..205fbe5edc9 --- /dev/null +++ b/sales_person_in_pos/views/pos_views.xml @@ -0,0 +1,24 @@ + + + + pos.order.form + pos.order + + + + + + + + + + pos.order.list + pos.order + + + + + + + +