diff --git a/all_in_one_pos_kit/README.rst b/all_in_one_pos_kit/README.rst new file mode 100755 index 000000000..b8b57fb8d --- /dev/null +++ b/all_in_one_pos_kit/README.rst @@ -0,0 +1,46 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: https://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +All in One POS Kit +================== +* This module combines a variety of POS features. + +Configuration +============= +* No additional configurations needed + +License +------- +GNU AFFERO GENERAL PUBLIC LICENSE Version 3 (AGPL v3) +(https://www.gnu.org/licenses/agpl-3.0-standalone.html) + +Company +------- +* `Cybrosys Techno Solutions `__ + +Credits +------- +* Developer: (V16) Afra MP, + (V17) Ayana KP, Amaya Aravind, +Contact: odoo@cybrosys.com + +Contacts +-------- +* Mail Contact : odoo@cybrosys.com + +Bug Tracker +----------- +Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. + +Maintainer +========== +.. image:: https://cybrosys.com/images/logo.png + :target: https://cybrosys.com +This module is maintained by Cybrosys Technologies. + +For support and more information, please visit https://www.cybrosys.com + +Further information +=================== +HTML Description: ``__ diff --git a/all_in_one_pos_kit/__init__.py b/all_in_one_pos_kit/__init__.py new file mode 100644 index 000000000..f0d89cb45 --- /dev/null +++ b/all_in_one_pos_kit/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Ayana KP(odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################### +from . import controllers +from . import models +from . import report diff --git a/all_in_one_pos_kit/__manifest__.py b/all_in_one_pos_kit/__manifest__.py new file mode 100644 index 000000000..d995d3ec5 --- /dev/null +++ b/all_in_one_pos_kit/__manifest__.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Ayana KP(odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################### +{ + 'name': 'All in One POS Kit', + 'version': '17.0.1.0.0', + 'category': 'Point of Sale', + 'summary': """This module combines Different POS features""", + 'description': """ All in One POS Kit module contain many features, such as POS dashboard, Dynamic POS Report Maker, Age Restricted Products in POS, Advanced POS Receipt with Customer Details and Invoice Details, + Helps to directly log in to POS, Allows to create multiple barcode for a single Product, etc.""", + 'author': 'Cybrosys Techno Solutions', + 'company': 'Cybrosys Techno Solutions', + 'maintainer': 'Cybrosys Techno Solutions', + 'website': "https://www.cybrosys.com", + 'depends': ['hr', 'point_of_sale', 'mrp', 'web'], + 'external_dependencies': {'python': ['twilio', 'pandas']}, + 'data': [ + 'security/all_in_one_pos_kit_security.xml', + 'security/ir.model.access.csv', + 'views/res_config_settings_views.xml', + 'views/pos_config_views.xml', + 'views/pos_order_views.xml', + 'views/dashboard_views.xml', + 'views/product_template_views.xml', + 'views/res_users_views.xml', + 'views/product_product_views.xml', + 'views/pos_report_views.xml', + 'views/pos_greetings_views.xml', + 'views/meals_planning_views.xml', + 'report/all_in_one_pos_kit_templates.xml', + ], + 'assets': { + 'point_of_sale._assets_pos': [ + 'all_in_one_pos_kit/static/src/exchange_product/scss/pos.scss', + 'all_in_one_pos_kit/static/src/order_line_image/css/order_line_image.css', + 'all_in_one_pos_kit/static/src/product_creation/css/*', + 'all_in_one_pos_kit/static/src/mass_edit/js/*', + 'all_in_one_pos_kit/static/src/service_charge/js/*', + 'all_in_one_pos_kit/static/src/exchange_product/js/*', + 'all_in_one_pos_kit/static/src/age_restricted/js/*', + 'all_in_one_pos_kit/static/src/multi_barcode/js/pos_scan.js', + 'all_in_one_pos_kit/static/src/delete_order_line/js/*', + 'all_in_one_pos_kit/static/src/custom_tip/js/PaymentScreen.js', + 'all_in_one_pos_kit/static/src/pos_mrp_order/js/*', + 'all_in_one_pos_kit/static/src/pos_num_show_hide/js/pos_numpad.js', + 'all_in_one_pos_kit/static/src/product_creation/js/*', + 'all_in_one_pos_kit/static/src/advanced_receipt/js/*', + 'all_in_one_pos_kit/static/src/order_line_image/js/pos_order_line.js', + 'all_in_one_pos_kit/static/src/time_based_product/js/*', + 'all_in_one_pos_kit/static/src/exchange_product/xml/*', + 'all_in_one_pos_kit/static/src/mass_edit/xml/*', + 'all_in_one_pos_kit/static/src/service_charge/xml/ServiceChargeButton.xml', + 'all_in_one_pos_kit/static/src/age_restricted/xml/restrict_popup.xml', + 'all_in_one_pos_kit/static/src/order_line_image/xml/pos_order_line.xml', + 'all_in_one_pos_kit/static/src/delete_order_line/xml/*', + 'all_in_one_pos_kit/static/src/custom_tip/xml/PaymentScreen.xml', + 'all_in_one_pos_kit/static/src/pos_num_show_hide/xml/pos.xml', + 'all_in_one_pos_kit/static/src/product_creation/xml/*', + 'all_in_one_pos_kit/static/src/advanced_receipt/xml/OrderReceipt.xml', + 'all_in_one_pos_kit/static/src/pos_logo/xml/*', + ], + 'web.assets_backend': [ + 'all_in_one_pos_kit/static/src/dashboard/css/pos_dashboard.css', + 'all_in_one_pos_kit/static/src/pos_report/css/*', + 'all_in_one_pos_kit/static/src/dashboard/xml/pos_dashboard.xml', + 'all_in_one_pos_kit/static/src/dashboard/js/pos_dashboard.js', + 'all_in_one_pos_kit/static/src/pos_report/js/pos_report.js', + 'all_in_one_pos_kit/static/src/pos_report/xml/*', + 'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.bundle.js', + ], + }, + 'images': ['static/description/banner.jpg'], + 'license': 'AGPL-3', + 'installable': True, + 'application': False, + 'auto_install': False, +} diff --git a/all_in_one_pos_kit/controllers/__init__.py b/all_in_one_pos_kit/controllers/__init__.py new file mode 100644 index 000000000..f3e1c1db5 --- /dev/null +++ b/all_in_one_pos_kit/controllers/__init__.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Ayana KP(odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################### +from . import all_in_one_pos_kit +from . import xlsx_report diff --git a/all_in_one_pos_kit/controllers/all_in_one_pos_kit.py b/all_in_one_pos_kit/controllers/all_in_one_pos_kit.py new file mode 100644 index 000000000..197c990d0 --- /dev/null +++ b/all_in_one_pos_kit/controllers/all_in_one_pos_kit.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Ayana KP(odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################### +from odoo import http +from odoo.http import request +import werkzeug +from odoo.addons.web.controllers.main import home + + +class PosScreen(home.Home): + """The class PosScreen is used to log in pos session directly""" + + @http.route('/web/login', type='http', auth="none") + def web_login(self, redirect=None, **kw): + """Override to add direct login to POS""" + res = super().web_login(redirect=redirect, **kw) + if request.env.user.pos_conf_id: + if not request.env.user.pos_conf_id.current_session_id: + request.env['pos.session'].sudo().create({ + 'user_id': request.env.uid, + 'config_id': request.env.user.pos_conf_id.id + }) + return werkzeug.utils.redirect('/pos/ui') + return res diff --git a/all_in_one_pos_kit/controllers/xlsx_report.py b/all_in_one_pos_kit/controllers/xlsx_report.py new file mode 100644 index 000000000..ca855223f --- /dev/null +++ b/all_in_one_pos_kit/controllers/xlsx_report.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Ayana KP(odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################### +import json +from odoo import http +from odoo.http import content_disposition, request +from odoo.tools import html_escape + + +class TBXLSXReportController(http.Controller): + """Controller class for generating and downloading XLSX reports.""" + + @http.route('/pos_dynamic_xlsx_reports', type='http', auth='user', + methods=['POST'], csrf=False) + def get_report_xlsx(self, model, options, output_format, report_data, + report_name, dfr_data): + """ Generate and download an XLSX report. + :param model: The model name for which the report is generated. + :param options: Options/configuration for the report. + :param output_format: The output format of the report (e.g.,'xlsx') + :param report_data: Data required for generating the report. + :param report_name: The name of the report. + :param dfr_data: Additional data required for the report. + :returns: The HTTP response containing the generated XLSX report""" + report_obj = request.env[model].with_user(request.session.uid) + try: + if output_format == 'xlsx': + response = request.make_response(None, headers=[ + ('Content-Type', 'application/vnd.ms-excel'), ( + 'Content-Disposition', + content_disposition(report_name + '.xlsx'))]) + report_obj.get_pos_xlsx_report(options, response, report_data, + dfr_data) + response.set_cookie('fileToken', 'dummy-because-api-expects-one') + return response + except Exception as e: + error = {'code': 200, 'message': 'Odoo Server Error', + 'data': http.serialize_exception(e)} + return request.make_response(html_escape(json.dumps(error))) diff --git a/all_in_one_pos_kit/doc/RELEASE_NOTES.md b/all_in_one_pos_kit/doc/RELEASE_NOTES.md new file mode 100644 index 000000000..292f1c561 --- /dev/null +++ b/all_in_one_pos_kit/doc/RELEASE_NOTES.md @@ -0,0 +1,5 @@ +## Module +#### 07.05.2025 +#### Version 17.0.1.0.0 +#### ADD +- Initial commit for All in One POS Kit diff --git a/all_in_one_pos_kit/models/__init__.py b/all_in_one_pos_kit/models/__init__.py new file mode 100644 index 000000000..f29acfb7d --- /dev/null +++ b/all_in_one_pos_kit/models/__init__.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Ayana KP(odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################### +from . import account_move +from . import meals_planning +from . import mrp_production +from . import multi_barcode_product +from . import pos_config +from . import pos_greetings +from . import pos_order +from . import pos_report +from . import pos_session +from . import product_product +from . import product_template +from . import res_config_settings +from . import res_users +from . import stock_lot diff --git a/all_in_one_pos_kit/models/account_move.py b/all_in_one_pos_kit/models/account_move.py new file mode 100644 index 000000000..deb5ef384 --- /dev/null +++ b/all_in_one_pos_kit/models/account_move.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Ayana KP(odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################### +import math +import re +from odoo import api, fields, models + + +class AccountMove(models.Model): + """Inherit the account_move module to add new fields and functions""" + _inherit = "account.move" + + account_barcode = fields.Char(string='Barcode', + help='Barcode associated with the account ' + 'move.') + + @api.model + def create(self, vals): + """Super the create function to It generates an EAN barcode based on + the ID of the created record and assigns it to the `account_barcode` + field.""" + res = super(AccountMove, self).create(vals) + res.account_barcode = self.generate_ean(str(res.id)) + return res + + def ean_checksum(self, eancode): + """Returns the checksum of an ean string of length 13, returns -1 if + the string has the wrong length""" + if len(eancode) != 13: + return -1 + oddsum = 0 + evensum = 0 + finalean = eancode[::-1][1:] + for i in range(len(finalean)): + if i % 2 == 0: + oddsum += int(finalean[i]) + else: + evensum += int(finalean[i]) + return int(10 - math.ceil((oddsum * 3) + evensum % 10.0)) % 10 + + def check_ean(eancode): + """Returns True if eancode is a valid ean13 string, or null""" + if not eancode: + return True + if len(eancode) != 13: + return False + try: + int(eancode) + except: + return False + return eancode.ean_checksum(eancode) == int(eancode[-1]) + + def generate_ean(self, ean): + """Creates and returns a valid ean13 from an invalid one""" + if not ean: + return "0000000000000" + ean = re.sub("[A-Za-z]", "0", ean) + ean = re.sub("[^0-9]", "", ean) + ean = ean[:13] + if len(ean) < 13: + ean = ean + '0' * (13 - len(ean)) + return ean[:-1] + str(self.ean_checksum(ean)) diff --git a/all_in_one_pos_kit/models/meals_planning.py b/all_in_one_pos_kit/models/meals_planning.py new file mode 100644 index 000000000..debb198ce --- /dev/null +++ b/all_in_one_pos_kit/models/meals_planning.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +################################################################################ +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies(). +# Author: Ayana KP(odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +################################################################################ +from odoo import api, fields, models +from odoo.exceptions import ValidationError + + +class MealsPlanning(models.Model): + """By using this model user can specify the time range and pos session""" + _name = 'meals.planning' + _description = "Product Planning" + _inherit = ['mail.thread', 'mail.activity.mixin'] + + name = fields.Char(string='Name', required=True, + help='Name for Product Planning', copy=False) + pos_ids = fields.Many2many('pos.session', string='Shops', + copy=False, help='Choose PoS Sessions', + required=True) + time_from = fields.Float(string='From', required=True, + help='Add from time(24hr)') + time_to = fields.Float(string='To', required=True, help='Add to time(24hr)') + menu_product_ids = fields.Many2many('product.product', + string='Product', + domain=[('available_in_pos', '=', True)]) + + state = fields.Selection([('activated', 'Activated'), + ('deactivated', 'Deactivated')], + default='deactivated') + company_id = fields.Many2one('res.company', string='Company', + default=lambda self: self.env.company) + + @api.constrains('time_from', 'time_to') + def _check_time_range(self): + """Validation for from time and to time""" + if self.time_from >= self.time_to: + raise ValidationError('From time must be less than to time!') + elif self.time_from > 24.0 or self.time_to > 24.0: + raise ValidationError( + 'Time value greater than 24 is not valid!') + + def action_activate_meals_plan(self): + """Change state to activate""" + self.write({ + 'state': 'activated'}) + + def action_deactivate_meals_plan(self): + """Change state to deactivate""" + self.write({ + 'state': 'deactivated'}) \ No newline at end of file diff --git a/all_in_one_pos_kit/models/mrp_production.py b/all_in_one_pos_kit/models/mrp_production.py new file mode 100644 index 000000000..254c43cb6 --- /dev/null +++ b/all_in_one_pos_kit/models/mrp_production.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Ayana KP(odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################### +from odoo import models + + +class MrpProduction(models.Model): + """ Extends MRP Production model for creating manufacturing orders from POS + orders.""" + _inherit = 'mrp.production' + + def create_mrp_from_pos(self, products): + """ Function for creating manufacturing orders.""" + product_ids = [] + if products: + for product in products: + if self.env['product.product'].browse( + int(product['id'])).to_make_mrp: + flag = 1 + if product_ids: + for product_id in product_ids: + if product_id['id'] == product['id']: + product_id['qty'] += product['qty'] + flag = 0 + if flag: + product_ids.append(product) + for prod in product_ids: + if prod['qty'] > 0: + bom_count = self.env['mrp.bom'].search([ + ('product_tmpl_id', '=', prod['product_tmpl_id'])]) + if bom_count: + bom_temp = self.env['mrp.bom'].search([ + ('product_tmpl_id', '=', prod['product_tmpl_id']), + ('product_id', '=', False)]) + bom_prod = self.env['mrp.bom'].search([ + ('product_id', '=', prod['id'])]) + if bom_prod: + bom = bom_prod[0] + elif bom_temp: + bom = bom_temp[0] + else: + bom = [] + if bom: + vals = { + 'origin': 'POS-' + prod['pos_reference'], + 'state': 'confirmed', + 'product_id': prod['id'], + 'product_tmpl_id': prod['product_tmpl_id'], + 'product_uom_id': prod['uom_id'], + 'product_qty': prod['qty'], + 'bom_id': bom.id, + } + mrp_order = self.sudo().create(vals) + list_value = [] + for bom_line in mrp_order.bom_id.bom_line_ids: + list_value.append((0, 0, { + 'raw_material_production_id': mrp_order.id, + 'name': mrp_order.name, + 'product_id': bom_line.product_id.id, + 'product_uom': bom_line.product_uom_id.id, + 'product_uom_qty': (bom_line.product_qty * mrp_order.product_qty)/self.env['mrp.bom'].search([("product_tmpl_id", "=", prod['product_tmpl_id'])]).product_qty, + 'picking_type_id': mrp_order.picking_type_id.id, + 'location_id': mrp_order.location_src_id.id, + 'location_dest_id': bom_line.product_id.with_company(self.company_id.id).property_stock_production.id, + 'company_id': mrp_order.company_id.id, + })) + finished_vals = { + 'product_id': prod['id'], + 'product_uom_qty': prod['qty'], + 'product_uom': prod['uom_id'], + 'name': mrp_order.name, + 'date_deadline': mrp_order.date_deadline, + 'picking_type_id': mrp_order.picking_type_id.id, + 'location_id': mrp_order.location_src_id.id, + 'location_dest_id': mrp_order.location_dest_id.id, + 'company_id': mrp_order.company_id.id, + 'production_id': mrp_order.id, + 'warehouse_id': mrp_order.location_dest_id.warehouse_id.id, + 'origin': mrp_order.name, + 'group_id': mrp_order.procurement_group_id.id, + 'propagate_cancel': mrp_order.propagate_cancel, + } + mrp_order.update({ + 'move_raw_ids': list_value, + 'move_finished_ids': [ + (0, 0, finished_vals)] + }) + return True diff --git a/all_in_one_pos_kit/models/multi_barcode_product.py b/all_in_one_pos_kit/models/multi_barcode_product.py new file mode 100644 index 000000000..fba80e2b0 --- /dev/null +++ b/all_in_one_pos_kit/models/multi_barcode_product.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Ayana KP(odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################### +from odoo import fields, models + + +class ProductMultiBarcode(models.Model): + """Created new model to add store multi barcode for product""" + _name = 'multi.barcode.products' + _description = 'For creating multiple Barcodes for products' + + multi_barcode = fields.Char(string="Barcode", + help="Provide alternate barcodes for this " + "product") + product_id = fields.Many2one('product.product', + string='Product', + help='Related product name in product.product' + ' model') + product_template_id = fields.Many2one('product.template', + string='Product template', + help='Related product name in ' + 'product.template model') + _sql_constraints = [('field_unique', 'unique(multi_barcode)', + 'Existing barcode is not allowed !'), ] + + def get_barcode_val(self, product): + """Returns barcode of record in self and product id""" + return self.multi_barcode, product diff --git a/all_in_one_pos_kit/models/pos_config.py b/all_in_one_pos_kit/models/pos_config.py new file mode 100644 index 000000000..9755a7106 --- /dev/null +++ b/all_in_one_pos_kit/models/pos_config.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Ayana KP(odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################### +from odoo import api, fields, models + + +class PosConfig(models.Model): + """Inherited POS Configuration to add field's and functions""" + _inherit = 'pos.config' + + is_session = fields.Boolean(string="Session", + compute='_compute_is_session', + help="Check it is for sessions") + is_service_charges = fields.Boolean(string="Service Charges", + help="Enable to add service charge") + charge_type = fields.Selection([('amount', 'Amount'), + ('percentage', 'Percentage')], + string='Type', default='amount', + help="Can choose charge percentage or " + "amount") + service_charge = fields.Float(string='Service Charge', + help="Charge need to apply") + service_product_id = fields.Many2one( + 'product.product', string='Service Product', + domain="[('available_in_pos', '=', True),('sale_ok', '=', True)," + "('type', '=', 'service')]", help="Service Product") + image = fields.Binary(string='Image', help='Add logo for pos session') + user_ids = fields.Many2many('res.users', + compute='_compute_user_ids', string='User', + help="The users who are allowed to access this" + "feature.") + + def _compute_is_session(self): + """To check the service charge is set up for session wise or + globally""" + if self.env['ir.config_parameter'].sudo().get_param( + 'all_in_one_pos_kit.visibility') == 'session': + self.is_session = True + else: + self.is_session = False + + @api.onchange('is_service_charges') + def _onchange_is_service_charges(self): + """When the service charge is enabled set service product + and amount by default per session""" + if self.is_service_charges: + if not self.service_product_id: + self.service_product_id = self.env['product.product'].search( + [('available_in_pos', '=', True), ('sale_ok', '=', True), + ('type', '=', 'service')], limit=1) + self.service_charge = 10.0 + else: + self.service_product_id = False + self.service_charge = 0.0 + + def _compute_user_ids(self): + """Computes the allowed users in pos""" + for record in self: + if record.env.user.show_users: + record.user_ids = self.env['res.users'].search( + [('pos_config_ids', '=', record.id)]) + else: + record.user_ids = None diff --git a/all_in_one_pos_kit/models/pos_greetings.py b/all_in_one_pos_kit/models/pos_greetings.py new file mode 100644 index 000000000..b463def68 --- /dev/null +++ b/all_in_one_pos_kit/models/pos_greetings.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Ayana KP(odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNor similar depending on the terminology used in your Odoo instance.U AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################### +from odoo import fields, models + + +class PosGreetings(models.Model): + """Model for creating pos greetings details""" + _name = 'pos.greetings' + _description = 'POS Greetings' + _rec_name = 'order_id' + + customer_id = fields.Many2one('res.partner', + help="Select customer for sending greetings", + string='Customer') + order_id = fields.Many2one('pos.order', + help="Pos order details of related to the " + "greeting messages", + string='Order') + twilio_auth_token = fields.Char(string="Token", + help="Authentication token for sending " + "greetings messages") + twilio_number = fields.Char('Twilio Number', + help="Twilio number for sending greetings " + "messages") + to_number = fields.Char('Customer Number', + help="Add the receiver number for sending " + "greetings") + sms_body = fields.Char('Body', required=True, + help="Body of the greetings message") + session_id = fields.Many2one('pos.session', string='Session', + help="Pos session id which the greetings " + "messages related to") + send_sms = fields.Boolean(string='Send SMS', + help="Used for identifying is the sms is send " + "or not ", + default=False) diff --git a/all_in_one_pos_kit/models/pos_order.py b/all_in_one_pos_kit/models/pos_order.py new file mode 100644 index 000000000..d763be5db --- /dev/null +++ b/all_in_one_pos_kit/models/pos_order.py @@ -0,0 +1,265 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Ayana KP(odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################### +import pytz +from twilio.rest import Client +from odoo import api, fields, models + + +class PosOrder(models.Model): + """Inherited the pos_order class to add filed and function to calculate pos + order details in the dashboard menu""" + _inherit = 'pos.order' + + exchange = fields.Boolean(string='Exchange', + help='Enable if the order contain is exchange ' + 'product') + sale_barcode = fields.Char(string='Barcode', + help='Barcode associated with the pos order.') + + def set_pos_exchange_order(self): + """Mark order a exchanged""" + self.exchange = True + return + + @api.model + def get_department(self, option): + """Function to filter the POs sales report chart""" + company_id = self.env.company.id + if option == 'pos_hourly_sales': + query = '''select EXTRACT(hour FROM date_order at time zone 'utc' at time zone '{}') + as date_month,sum(amount_total) from pos_order where + EXTRACT(month FROM date_order::date) = EXTRACT(month FROM CURRENT_DATE) + AND pos_order.company_id = ''' + str( + company_id) + ''' group by date_month ''' + query = query.format( + self.env.user.tz if self.env.user.tz else pytz.UTC) + label = 'HOURS' + elif option == 'pos_monthly_sales': + query = '''select date_order::date as date_month,sum(amount_total) from pos_order where + EXTRACT(month FROM date_order::date) = EXTRACT(month FROM CURRENT_DATE) AND pos_order.company_id = ''' + str( + company_id) + ''' group by date_month ''' + label = 'DAYS' + else: + query = '''select TO_CHAR(date_order,'MON')date_month,sum(amount_total) from pos_order where + EXTRACT(year FROM date_order::date) = EXTRACT(year FROM CURRENT_DATE) AND pos_order.company_id = ''' + str( + company_id) + ''' group by date_month''' + label = 'MONTHS' + self._cr.execute(query) + docs = self._cr.dictfetchall() + order = [] + today = [] + for record in docs: + order.append(record.get('sum')) + today.append(record.get('date_month')) + return [order, today, label] + + @api.model + def get_details(self): + """Function to get payment details,session details and sales person + details""" + company_id = self.env.company + self._cr.execute('''select pos_payment_method.name ->>'en_US',sum(amount) + from pos_payment inner join pos_payment_method on + pos_payment_method.id=pos_payment.payment_method_id + where pos_payment.company_id = ''' + str(company_id.id) + " " + ''' + group by pos_payment_method.name ORDER + BY sum(amount) DESC; ''') + payment_details = self._cr.fetchall() + self._cr.execute('''select hr_employee.name,sum(pos_order.amount_paid) + as total,count(pos_order.amount_paid) as orders from + pos_order inner join hr_employee on pos_order.user_id = + hr_employee.user_id where pos_order.company_id =''' + str( + company_id.id) + " " + '''GROUP BY hr_employee.name order by total DESC;''') + salesperson = self._cr.fetchall() + payments = [] + for rec in payment_details: + rec = list(rec) + if company_id.currency_id.position == 'after': + rec[1] = "%s %s" % (rec[1], company_id.currency_id.symbol) + else: + rec[1] = "%s %s" % (company_id.currency_id.symbol, rec[1]) + payments.append(tuple(rec)) + total_sales = [] + for rec in salesperson: + rec = list(rec) + if company_id.currency_id.position == 'after': + rec[1] = "%s %s" % (rec[1], company_id.currency_id.symbol) + else: + rec[1] = "%s %s" % (company_id.currency_id.symbol, rec[1]) + total_sales.append(tuple(rec)) + sessions_list = [] + session = {'opened': 'Opened', 'opening_control': "Opening Control"} + for session_id in self.env['pos.config'].search([]): + if session.get(session_id.pos_session_state) is None: + sessions_list.append({'session': session_id.name, + 'status': 'Closed'}) + else: + sessions_list.append({'session': session_id.name, + 'status': session.get( + session_id.pos_session_state)}) + return {'payment_details': payments, 'salesperson': total_sales, + 'selling_product': sessions_list} + + @api.model + def get_refund_details(self): + """Function to get total count of orders,session and refund orders""" + total = sum(self.env['pos.order'].search([]).mapped('amount_total')) + today_refund_total = 0 + today_sale = 0 + for pos_order_id in self.env['pos.order'].search([]): + if pos_order_id.date_order.date() == fields.date.today(): + today_sale = today_sale + 1 + if pos_order_id.amount_total < 0.0: + today_refund_total = today_refund_total + 1 + magnitude = 0 + while abs(total) >= 1000: + magnitude += 1 + total /= 1000.0 + # add more suffixes if you need them + val = '%.2f%s' % (total, ['', 'K', 'M', 'G', 'T', 'P'][magnitude]) + data= { + 'total_sale': val, + 'total_order_count': self.env['pos.order'].search_count([]), + 'total_refund_count': self.env['pos.order'].search_count( + [('amount_total', '<', 0.0)]), + 'total_session': self.env['pos.session'].search_count([]), + 'today_refund_total': today_refund_total, + 'today_sale': today_sale, + } + return data + + @api.model + def get_the_top_customer(self): + """Function to get top 10 customer in pos""" + self._cr.execute('''select res_partner.name as customer,pos_order.partner_id,sum(pos_order.amount_paid) as amount_total from pos_order + inner join res_partner on res_partner.id = pos_order.partner_id where pos_order.company_id = ''' + str( + self.env.company.id) + ''' GROUP BY pos_order.partner_id, + res_partner.name ORDER BY amount_total DESC LIMIT 10;''') + top_customer = self._cr.dictfetchall() + order = [] + day = [] + for record in top_customer: + order.append(record.get('amount_total')) + day.append(record.get('customer')) + return [order, day] + + @api.model + def get_the_top_products(self): + """Function to get top 10 product in """ + + self._cr.execute('''select DISTINCT(product_template.name)->>'en_US' as product_name,sum(qty) as total_quantity from + pos_order_line inner join product_product on product_product.id=pos_order_line.product_id inner join + product_template on product_product.product_tmpl_id = product_template.id where pos_order_line.company_id = ''' + str( + self.env.company.id) + ''' group by product_template.id ORDER + BY total_quantity DESC Limit 10 ''') + top_product = self._cr.dictfetchall() + total_quantity = [] + product_name = [] + for record in top_product: + total_quantity.append(record.get('total_quantity')) + product_name.append(record.get('product_name')) + return [total_quantity, product_name] + + @api.model + def get_the_top_categories(self): + """Function to get top categories in pos""" + query = '''select DISTINCT(product_category.complete_name) as product_category,sum(qty) as total_quantity + from pos_order_line inner join product_product on product_product.id=pos_order_line.product_id inner join + product_template on product_product.product_tmpl_id = product_template.id inner join product_category on + product_category.id =product_template.categ_id where pos_order_line.company_id = ''' + str( + self.env.company.id) + ''' group by product_category ORDER BY total_quantity DESC ''' + self._cr.execute(query) + top_categories = self._cr.dictfetchall() + total_quantity = [] + product_categ = [] + for record in top_categories: + total_quantity.append(record.get('total_quantity')) + product_categ.append(record.get('product_category')) + return [total_quantity, product_categ] + + @api.model + def get_invoice(self, id): + """Retrieve invoice information based on a POS reference ID. + This method searches for a POS record with the specified reference ID. It + then retrieves the associated invoice based on the name matching the + reference. The invoice details, including ID, name, base URL, and account + barcode, are returned as a dictionary. + :param id: The POS reference ID to search for. + :return: A dictionary containing the invoice details. + :rtype: dict""" + invoice_id = self.env['account.move'].search( + [('ref', '=', self.search([('pos_reference', '=', id)]).name)]) + return {'invoice_id': invoice_id.id, 'invoice_name': invoice_id.name, + 'base_url': self.env['ir.config_parameter'].get_param( + 'web.base.url'), 'barcode': invoice_id.account_barcode} + + @api.model + def create_from_ui(self, orders, draft=False): + """Create POS orders from the user interface and send SMS messages to + customers.This method creates POS orders from the provided data and + sends SMS messages to customers if the 'customer_msg' parameter is + set and the customer has a valid phone number.""" + res = super(PosOrder, self).create_from_ui(orders) + id = [line['id'] for line in res if line['id']] + if backend_order := self.search([('id', 'in', id)]): + for pos_order in backend_order: + params = self.env['ir.config_parameter'].sudo() + customer_msg = params.get_param('all_in_one_pos_kit.customer_msg') + twilio_auth_token = params.get_param('all_in_one_pos_kit.twilio_auth_token') + account_sid = params.get_param('all_in_one_pos_kit.account_sid') + twilio_number = params.get_param('all_in_one_pos_kit.twilio_number') + sms_body = params.get_param('all_in_one_pos_kit.sms_body') + if customer_msg and pos_order.partner_id.phone: + try: + customer_phone = str(pos_order.partner_id.phone) + # Download the helper library from https://www.twilio.com/docs/python/install + client = Client(account_sid, twilio_auth_token) + message = client.messages.create( + body=sms_body, + from_=twilio_number, + to=customer_phone + ) + self.env['pos.greetings'].create({ + 'customer_id': pos_order.partner_id.id, + 'order_id': pos_order.id, + 'twilio_auth_token': twilio_auth_token, + 'twilio_number': twilio_number, + 'to_number': customer_phone, + 'session_id': pos_order.session_id.id, + 'sms_body': sms_body, + 'send_sms': True, + }) + except Exception as e: + pass + return res + + +class PosOrderLine(models.Model): + """Inherit the class pos_order_line""" + _inherit = "pos.order.line" + + @api.model + def get_product_details(self, ids): + """Function to get the product details""" + return [{'product_id': rec.product_id.id, 'name': rec.product_id.name, + 'qty': rec.qty} + for rec in self.env['pos.order.line'].browse(ids)] diff --git a/all_in_one_pos_kit/models/pos_report.py b/all_in_one_pos_kit/models/pos_report.py new file mode 100644 index 000000000..f0c867253 --- /dev/null +++ b/all_in_one_pos_kit/models/pos_report.py @@ -0,0 +1,438 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Ayana KP(odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################### +import io +import json +from odoo import api, fields, models +try: + from odoo.tools.misc import xlsxwriter +except ImportError: + import xlsxwriter + + +class PosReport(models.Model): + """Class to generate PDF and XLS report in POS""" + _name = "pos.report" + _description = 'POS Report' + + pos_report = fields.Char(string="PoS Report", help="Enter the name of the " + "PoS report.") + date_from = fields.Datetime(string="Date From", help="Specify the starting" + "date for the report") + date_to = fields.Datetime(string="Date to", help="Specify the ending date" + "for the report.") + report_type = fields.Selection( + [('report_by_order', 'Report By Order'), + ('report_by_order_detail','Report By Order Detail'), + ('report_by_product', 'Report By Product'), + ('report_by_categories','Report By Categories'), + ('report_by_salesman', 'Report By Salesman'), + ('report_by_payment', 'Report By Payment')], default='report_by_order', + string='Report Type',help="Select the type of report to generate.") + + @api.model + def pos_report(self, option): + """The pos_report method is used to generate the PoS report based on the + selected option. It retrieves the necessary data from the database and + returns the report in a specific format.""" + report_values = self.browse(option[0]) + data = {'report_type': report_values.report_type,'model': self} + if report_values.date_from: + data.update({'date_from': report_values.date_from}) + if report_values.date_to: + data.update({'date_to': report_values.date_to}) + self._get_report_values(data) + return { + 'name': "PoS Orders", + 'type': 'ir.actions.client', + 'tag': 'pos_r', + 'orders': data, + 'filters': self.get_filter(option), + 'report_lines': self._get_report_values(data).get('POS'), + 'report_main_line': self._get_report_values(data).get('pos_main'), + } + + def get_filter(self, option): + """Get the filter settings for the report""" + data = self.get_filter_data(option) + filters = {} + if data.get('report_type') == 'report_by_order': + filters['report_type'] = 'Report By Order' + elif data.get('report_type') == 'report_by_order_detail': + filters['report_type'] = 'Report By Order Detail' + elif data.get('report_type') == 'report_by_product': + filters['report_type'] = 'Report By Product' + elif data.get('report_type') == 'report_by_categories': + filters['report_type'] = 'Report By Categories' + elif data.get('report_type') == 'report_by_salesman': + filters['report_type'] = 'Report By Salesman' + elif data.get('report_type') == 'report_by_payment': + filters['report_type'] = 'Report By Payment' + else: + filters['report_type'] = 'report_by_order' + return filters + + def get_filter_data(self, option): + """Get the filter data for the report""" + return {'report_type': self.env['pos.report'].search([ + ('id', '=', option[0])]).report_type} + + def _get_report_sub_lines(self, data): + """Get the sub_lines of the report""" + report_sub_lines = [] + if data.get('report_type') == 'report_by_order': + query = ''' + select l.name,l.date_order,l.partner_id,l.amount_total,l.note,l.user_id,res_partner.name,l.name as shop,pos_session.name as session, + res_users.partner_id as user_partner,sum(pos_order_line.qty),l.id as id, + (SELECT res_partner.name as salesman FROM res_partner WHERE res_partner.id = res_users.partner_id) + from pos_order as l + left join pos_session on l.session_id = pos_session.id + left join res_partner on l.partner_id = res_partner.id + left join res_users on l.user_id = res_users.id + left join pos_order_line on l.id = pos_order_line.order_id + ''' + term = 'Where ' + if data.get('date_from'): + query += "Where l.date_order >= '%s' " % data.get('date_from') + term = 'AND ' + if data.get('date_to'): + query += term + "l.date_order <= '%s' " % data.get('date_to') + query += "group by l.user_id,res_users.partner_id,res_partner.name,l.partner_id,l.date_order,pos_session.name,l.session_id,l.name,l.amount_total,l.note,l.id" + self._cr.execute(query) + report_sub_lines.append(self._cr.dictfetchall()) + elif data.get('report_type') == 'report_by_order_detail': + query = ''' + select l.name,l.date_order,l.partner_id,l.amount_total,l.note,l.user_id,res_partner.name,l.name as shop,pos_session.name as session, + res_users.partner_id as user_partner,sum(pos_order_line.qty), pos_order_line.full_product_name, pos_order_line.price_unit,pos_order_line.price_subtotal,pos_order_line.price_subtotal_incl,pos_order_line.product_id,product_product.default_code, + (SELECT res_partner.name as salesman FROM res_partner WHERE res_partner.id = res_users.partner_id) + from pos_order as l + left join pos_session on l.session_id = pos_session.id + left join res_partner on l.partner_id = res_partner.id + left join res_users on l.user_id = res_users.id + left join pos_order_line on l.id = pos_order_line.order_id + left join product_product on pos_order_line.product_id = product_product.id + ''' + term = 'Where ' + if data.get('date_from'): + query += "Where l.date_order >= '%s' " % data.get('date_from') + term = 'AND ' + if data.get('date_to'): + query += term + "l.date_order <= '%s' " % data.get('date_to') + query += "group by l.user_id,res_users.partner_id,res_partner.name,l.partner_id,l.date_order,pos_session.name,l.session_id,l.name,l.amount_total,l.note,pos_order_line.full_product_name,pos_order_line.price_unit,pos_order_line.price_subtotal,pos_order_line.price_subtotal_incl,pos_order_line.product_id,product_product.default_code" + self._cr.execute(query) + report_sub_lines.append(self._cr.dictfetchall()) + elif data.get('report_type') == 'report_by_product': + query = ''' + select l.amount_total,l.amount_paid,sum(pos_order_line.qty) as qty, pos_order_line.full_product_name, pos_order_line.product_id as id, pos_order_line.price_unit,product_product.default_code,product_category.name + from pos_order as l + left join pos_order_line on l.id = pos_order_line.order_id + left join product_product on pos_order_line.product_id = product_product.id + left join product_template on pos_order_line.product_id = product_template.id + left join product_category on product_category.id = product_template.categ_id + ''' + term = 'Where ' + if data.get('date_from'): + query += "Where l.date_order >= '%s' " % data.get('date_from') + term = 'AND ' + if data.get('date_to'): + query += term + "l.date_order <= '%s' " % data.get('date_to') + query += "group by l.amount_total,l.amount_paid,pos_order_line.full_product_name,pos_order_line.price_unit,pos_order_line.product_id,product_product.default_code,product_template.categ_id,product_category.name" + self._cr.execute(query) + report_sub_lines.append(self._cr.dictfetchall()) + elif data.get('report_type') == 'report_by_categories': + query = ''' + select product_category.name,sum(l.qty) as qty,sum(l.price_subtotal) as amount_total,sum(price_subtotal_incl) as total_incl + from pos_order_line as l + left join product_template on l.product_id = product_template.id + left join product_category on product_category.id = product_template.categ_id + left join pos_order on l.order_id = pos_order.id + ''' + term = 'Where ' + if data.get('date_from'): + query += "Where pos_order.date_order >= '%s' " % data.get( + 'date_from') + term = 'AND ' + if data.get('date_to'): + query += term + "pos_order.date_order <= '%s' " % data.get( + 'date_to') + query += "group by product_category.name" + self._cr.execute(query) + report_sub_lines.append(self._cr.dictfetchall()) + elif data.get('report_type') == 'report_by_salesman': + query = ''' + select res_partner.name,sum(pos_order_line.qty) as qty,sum(pos_order_line.price_subtotal) as amount,count(l.id) as order + from pos_order as l + left join res_users on l.user_id = res_users.id + left join res_partner on res_users.partner_id = res_partner.id + left join pos_order_line on l.id = pos_order_line.order_id + ''' + term = 'Where ' + if data.get('date_from'): + query += "Where l.date_order >= '%s' " % data.get('date_from') + term = 'AND ' + if data.get('date_to'): + query += term + "l.date_order <= '%s' " % data.get('date_to') + query += "group by res_partner.name" + self._cr.execute(query) + report_sub_lines.append(self._cr.dictfetchall()) + elif data.get('report_type') == 'report_by_payment': + query = ''' + select pos_payment_method.name,sum(l.amount_total),pos_session.name as session,pos_config.name as config + from pos_order as l + left join pos_payment on l.id = pos_payment.pos_order_id + left join pos_payment_method on pos_payment.payment_method_id = pos_payment_method.id + left join pos_session on l.session_id = pos_session.id + left join pos_config on pos_session.config_id = pos_config.id + ''' + term = 'Where ' + if data.get('date_from'): + query += "Where l.date_order >= '%s' " % data.get('date_from') + term = 'AND ' + if data.get('date_to'): + query += term + "l.date_order <= '%s' " % data.get('date_to') + query += "group by pos_payment_method.name,pos_session.name,pos_config.name" + self._cr.execute(query) + report_sub_lines.append(self._cr.dictfetchall()) + return report_sub_lines + + def _get_report_total_value(self, data): + """The _get_report_total_value method retrieves the total values of the + report based on the selected report type and filters.""" + report_main_lines = [] + if data.get('report_type') == 'report_by_order': + self._cr.execute(''' + select count(l.id) as order,sum(l.amount_total) as amount + from pos_order as l + ''') + report_main_lines.append(self._cr.dictfetchall()) + elif data.get('report_type') == 'report_by_order_detail': + self._cr.execute(''' + select count(line.id) as order,sum(line.price_subtotal) as total,sum(line.price_subtotal_incl) + from pos_order_line as line + ''') + report_main_lines.append(self._cr.dictfetchall()) + elif data.get('report_type') == 'report_by_product': + self._cr.execute(''' + select count(l.product_id) as order,sum(l.price_subtotal) as amount + from pos_order_line as l + ''') + report_main_lines.append(self._cr.dictfetchall()) + else: + report_main_lines = False + return report_main_lines + + def _get_report_values(self, data): + """The _get_report_values method generates the report values by calling + _get_report_sub_lines and _get_report_total_value.""" + report_res_total = self._get_report_total_value(data) + if data.get('report_type'): + report_res = self._get_report_sub_lines(data)[0] + else: + report_res = self._get_report_sub_lines(data) + if data.get('report_type') == 'report_by_order': + report_res_total = self._get_report_total_value(data)[0] + return { + 'doc_ids': self.ids, + 'docs': data['model'], + 'POS': report_res, + 'pos_main': report_res_total, + + } + + def get_pos_xlsx_report(self, data, response, report_data, dfr_data): + """Generate an XLSX report for the Point of Sale. + :param data: JSON-encoded data representing the report filters + :param response: HTTP response object + :param report_data: JSON-encoded report datas + :param dfr_data: data for DFR (Data Field Relations)""" + report_data_main = json.loads(report_data) + output = io.BytesIO() + filters = json.loads(data) + workbook = xlsxwriter.Workbook(output, {'in_memory': True}) + sheet = workbook.add_worksheet() + heading = workbook.add_format({'align': 'center', 'bold': True, + 'font_size': '10px', 'border': 2, + 'border_color': 'black'}) + txt_l = workbook.add_format({'font_size': '10px', 'border': 1, + 'bold': True}) + if filters.get('report_type') == 'report_by_order': + sheet.merge_range('D5:F5', 'Report Type: ' + + filters.get('report_type'), txt_l) + sheet.write('A7', 'PoS', heading) + sheet.write('B7', 'Order', heading) + sheet.write('C7', 'Date Order', heading) + sheet.write('D7', 'Customer', heading) + sheet.write('E7', 'Salesman', heading) + sheet.write('F7', 'Total Qty', heading) + sheet.write('G7', 'Amount Total', heading) + sheet.write('H7', 'Note', heading) + lst = [rec for rec in report_data_main[0]] + row = 6 + col = 0 + sheet.set_column(3, 0, 15) + sheet.set_column(4, 1, 15) + sheet.set_column(5, 2, 15) + sheet.set_column(6, 3, 15) + sheet.set_column(7, 4, 15) + sheet.set_column(8, 5, 15) + sheet.set_column(9, 6, 15) + for rec_data in report_data_main: + row += 1 + sheet.write(row, col, rec_data['shop'], txt_l) + sheet.write(row, col + 1, rec_data['session'], txt_l) + sheet.write(row, col + 2, rec_data['date_order'], txt_l) + sheet.write(row, col + 3, rec_data['name'], txt_l) + sheet.write(row, col + 4, rec_data['salesman'], txt_l) + sheet.write(row, col + 5, rec_data['sum'], txt_l) + sheet.write(row, col + 6, rec_data['amount_total'], txt_l) + sheet.write(row, col + 7, rec_data['note'], txt_l) + if filters.get('report_type') == 'report_by_order_detail': + sheet.merge_range('E5:G5', 'Report Type: ' + + filters.get('report_type'), txt_l) + sheet.write('A7', 'PoS', heading) + sheet.write('B7', 'Order', heading) + sheet.write('C7', 'Date Order', heading) + sheet.write('D7', 'Customer', heading) + sheet.write('E7', 'Salesman', heading) + sheet.write('F7', 'Product Code', heading) + sheet.write('G7', 'Product Name', heading) + sheet.write('H7', 'Price unit', heading) + sheet.write('I7', 'Qty', heading) + sheet.write('J7', 'Price Subtotal', heading) + sheet.write('K7', 'Price Subtotal Incl', heading) + lst = [rec for rec in report_data_main[0]] + row = 6 + col = 0 + sheet.set_column(3, 0, 15) + sheet.set_column(4, 1, 15) + sheet.set_column(5, 2, 15) + sheet.set_column(6, 3, 15) + sheet.set_column(7, 4, 15) + sheet.set_column(8, 5, 15) + sheet.set_column(9, 6, 15) + sheet.set_column(10, 7, 15) + sheet.set_column(11, 8, 15) + sheet.set_column(12, 9, 15) + for rec_data in report_data_main: + row += 1 + sheet.write(row, col, rec_data['shop'], txt_l) + sheet.write(row, col + 1, rec_data['session'], txt_l) + sheet.write(row, col + 2, rec_data['date_order'], txt_l) + sheet.write(row, col + 3, rec_data['name'], txt_l) + sheet.write(row, col + 4, rec_data['salesman'], txt_l) + sheet.write(row, col + 5, rec_data['default_code'], txt_l) + sheet.write(row, col + 6, rec_data['full_product_name'], txt_l) + sheet.write(row, col + 7, rec_data['price_unit'], txt_l) + sheet.write(row, col + 8, rec_data['sum'], txt_l) + sheet.write(row, col + 9, rec_data['price_subtotal'], txt_l) + sheet.write(row, col + 10, rec_data['price_subtotal_incl'], + txt_l) + if filters.get('report_type') == 'report_by_product': + sheet.merge_range('B5:D5', 'Report Type: ' + + filters.get('report_type'), txt_l) + sheet.write('A7', 'Category', heading) + sheet.write('B7', 'Product Code', heading) + sheet.write('C7', 'Product Name', heading) + sheet.write('D7', 'Qty', heading) + sheet.write('E7', 'Amount Total', heading) + sheet.write('F7', 'Amount Total Incl', heading) + lst = [rec for rec in report_data_main[0]] + row = 6 + col = 0 + sheet.set_column(3, 0, 15) + sheet.set_column(4, 1, 15) + sheet.set_column(5, 2, 15) + sheet.set_column(6, 3, 15) + sheet.set_column(7, 4, 15) + sheet.set_column(8, 5, 15) + for rec_data in report_data_main: + row += 1 + sheet.write(row, col, rec_data['name'], txt_l) + sheet.write(row, col + 1, rec_data['default_code'], txt_l) + sheet.write(row, col + 2, rec_data['full_product_name'], txt_l) + sheet.write(row, col + 3, rec_data['qty'], txt_l) + sheet.write(row, col + 4, rec_data['amount_total'], txt_l) + sheet.write(row, col + 5, rec_data['amount_paid'], txt_l) + if filters.get('report_type') == 'report_by_categories': + sheet.merge_range('B5:C5', 'Report Type: ' + + filters.get('report_type'), txt_l) + sheet.write('A7', 'Category', heading) + sheet.write('B7', 'Qty', heading) + sheet.write('C7', 'Amount Total', heading) + sheet.write('D7', 'Amount Total Incl', heading) + lst = [rec for rec in report_data_main[0]] + row = 6 + col = 0 + sheet.set_column(3, 0, 15) + sheet.set_column(4, 1, 15) + sheet.set_column(5, 2, 15) + sheet.set_column(6, 3, 15) + for rec_data in report_data_main: + row += 1 + sheet.write(row, col, rec_data['name'], txt_l) + sheet.write(row, col + 1, rec_data['qty'], txt_l) + sheet.write(row, col + 2, rec_data['amount_total'], txt_l) + sheet.write(row, col + 3, rec_data['total_incl'], txt_l) + if filters.get('report_type') == 'report_by_salesman': + sheet.merge_range('B5:C5', 'Report Type: ' + + filters.get('report_type'), txt_l) + sheet.write('A7', 'Salesman', heading) + sheet.write('B7', 'Total Order', heading) + sheet.write('C7', 'Total Qty', heading) + sheet.write('D7', 'Total Amount', heading) + lst = [rec for rec in report_data_main[0]] + row = 6 + col = 0 + sheet.set_column(3, 0, 15) + sheet.set_column(4, 1, 15) + sheet.set_column(5, 2, 15) + sheet.set_column(6, 3, 15) + for rec_data in report_data_main: + row += 1 + sheet.write(row, col, rec_data['name'], txt_l) + sheet.write(row, col + 1, rec_data['order'], txt_l) + sheet.write(row, col + 2, rec_data['qty'], txt_l) + sheet.write(row, col + 3, rec_data['amount'], txt_l) + if filters.get('report_type') == 'report_by_payment': + sheet.merge_range('B5:C5', 'Report Type: ' + + filters.get('report_type'), txt_l) + sheet.write('A7', 'Point of Sale', heading) + sheet.write('B7', 'PoS Session', heading) + sheet.write('C7', 'Payment', heading) + sheet.write('D7', 'Total Amount', heading) + lst = [rec for rec in report_data_main[0]] + row = 6 + col = 0 + sheet.set_column(3, 0, 15) + sheet.set_column(4, 1, 15) + sheet.set_column(5, 2, 15) + sheet.set_column(6, 3, 15) + for rec_data in report_data_main: + name = list(rec_data['name'].values())[0] + row += 1 + sheet.write(row, col, rec_data['config'], txt_l) + sheet.write(row, col + 1, rec_data['session'], txt_l) + sheet.write(row, col + 2, name, txt_l) + sheet.write(row, col + 3, rec_data['sum'], txt_l) + workbook.close() + output.seek(0) + response.stream.write(output.read()) + output.close() diff --git a/all_in_one_pos_kit/models/pos_session.py b/all_in_one_pos_kit/models/pos_session.py new file mode 100644 index 000000000..d64c09e64 --- /dev/null +++ b/all_in_one_pos_kit/models/pos_session.py @@ -0,0 +1,115 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Ayana KP(odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################### +from odoo import models + + +class PosSession(models.Model): + """Inherit POS Session to load model and fields""" + _inherit = 'pos.session' + + def _pos_ui_models_to_load(self): + """Supering the function for loading res.config.settings to pos + session""" + result = super()._pos_ui_models_to_load() + result += ['res.config.settings', 'pos.order', 'pos.order.line', + 'multi.barcode.products', 'meals.planning'] + return result + + def _loader_params_res_config_settings(self): + """Returning the field required""" + return { + 'search_params': {'fields': ['enable_service_charge', 'visibility', + 'global_selection', 'global_charge', + 'global_product_id', + 'custom_tip_percentage', 'barcode', + 'invoice_number', 'customer_details', + 'customer_name', 'customer_address', + 'customer_mobile', 'customer_phone', + 'customer_email', 'customer_vat', + ], }, } + + def _get_pos_ui_res_config_settings(self, params): + """Returns the model""" + return self.env['res.config.settings'].search_read( + **params['search_params']) + + def _loader_params_account_move(self): + return {'search_params': {'fields': ['account_barcode']}} + def _get_pos_ui_res_account_move(self, params): + """Returns the model""" + return self.env['account.move'].search_read( + **params['search_params']) + + def _loader_params_pos_order(self): + """pos_order model field load in pos session""" + return {'search_params': { + 'domain': [], + 'fields': ['name', 'date_order', 'pos_reference', + 'partner_id', 'lines', 'exchange']}} + + def _get_pos_ui_pos_order(self, params): + """Return the model pos_order""" + return self.env['pos.order'].search_read(**params['search_params']) + + def _loader_params_pos_order_line(self): + """pos_order_line model field load in pos session""" + return {'search_params': {'domain': [], + 'fields': ['product_id', 'qty', + 'price_subtotal', + 'total_cost']}} + + def _get_pos_ui_pos_order_line(self, params): + """Return the model pos_order_line""" + return self.env['pos.order.line'].search_read( + **params['search_params']) + + def _loader_params_product_product(self): + """loaded product template field into pos session""" + result = super()._loader_params_product_product() + result['search_params']['fields'].extend( + ['is_age_restrict', 'product_multi_barcodes_ids', 'name', 'id']) + return result + + def _get_pos_ui_multi_barcode_products(self, params): + """"Return the model multi_barcode_product""" + return self.env['multi.barcode.products'].with_context( + **params['context']).search_read(**params['search_params']) + + def _loader_params_multi_barcode_products(self): + """loaded multi_barcode_product field into pos session""" + return {'search_params': {'fields': ['multi_barcode'], }, + 'context': {'display_default_code': False}, } + + def _loader_params_meals_planning(self): + """Returning corresponding data to pos""" + plans = self.env['meals.planning'].search([ + ('state', '=', 'activated'), + ('pos_ids', 'in', self.id)]) + data = plans.mapped('id') + return { + 'search_params': { + 'domain': [('id', '=', data)], + 'fields': ['name', 'menu_product_ids', 'time_from', 'time_to', + 'state', 'pos_ids']}} + + def _get_pos_ui_meals_planning(self, params): + return self.env['meals.planning'].search_read(**params['search_params']) diff --git a/all_in_one_pos_kit/models/product_product.py b/all_in_one_pos_kit/models/product_product.py new file mode 100644 index 000000000..e6e201039 --- /dev/null +++ b/all_in_one_pos_kit/models/product_product.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Ayana KP(odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################### +from odoo import api, fields, models, _ + + +class ProductProduct(models.Model): + """Inherit the product_product module to add new fields""" + _inherit = 'product.product' + + product_multi_barcodes_ids = fields.One2many('multi.barcode.products', + 'product_id', + string='Barcodes', + help='Add multi barcode for ' + 'product') + + @api.model + def create(self, vals): + """Super the create function to update the field + product_multi_barcodes_ids""" + res = super(ProductProduct, self).create(vals) + res.product_multi_barcodes_ids.update({ + 'product_template_id': res.product_tmpl_id.id + }) + return res + + def write(self, vals): + """Super the write function to update the field + product_multi_barcodes_ids""" + res = super(ProductProduct, self).write(vals) + self.product_multi_barcodes_ids.update({ + 'product_template_id': self.product_tmpl_id.id + }) + return res + + @api.onchange('to_make_mrp') + def _onchange_to_make_mrp(self): + """Function to show raise error if the product doesn't have BOM""" + if self.to_make_mrp and not self.bom_count: + raise Warning(_('Please set Bill of Material for this product.')) diff --git a/all_in_one_pos_kit/models/product_template.py b/all_in_one_pos_kit/models/product_template.py new file mode 100644 index 000000000..492708ef2 --- /dev/null +++ b/all_in_one_pos_kit/models/product_template.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Ayana KP(odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################### +from odoo import api, fields, models +from odoo.exceptions import ValidationError + + +class ProductTemplate(models.Model): + """Inherited product.template to add field""" + _inherit = 'product.template' + + is_age_restrict = fields.Boolean(string="Is Age Restricted", + help="Enable if the product is age " + "restricted") + product_template_ids = fields.One2many('multi.barcode.products', + 'product_template_id', + string='Barcodes', + help='Add multi barcode for the ' + 'module') + to_make_mrp = fields.Boolean(string='To Create MRP Order', + help="Check if the product should be make mrp " + "order") + + @api.model + def create(self, vals): + """Super the create function to update the field product_template_ids""" + res = super(ProductTemplate, self).create(vals) + res.product_template_ids.update({ + 'product_id': res.product_variant_id.id + }) + return res + + def write(self, vals): + """Super the write function to update the field product_template_ids""" + res = super(ProductTemplate, self).write(vals) + if self.product_template_ids: + self.product_template_ids.update({ + 'product_id': self.product_variant_id.id + }) + return res + + @api.onchange('to_make_mrp') + def _onchange_to_make_mrp(self): + """Function to show raise error if the product doesn't have BOM""" + if self.to_make_mrp: + if not self.bom_count: + raise ValidationError( + 'Please set Bill of Material for this product.') diff --git a/all_in_one_pos_kit/models/res_config_settings.py b/all_in_one_pos_kit/models/res_config_settings.py new file mode 100644 index 000000000..69f71d64a --- /dev/null +++ b/all_in_one_pos_kit/models/res_config_settings.py @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Ayana KP(odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################### +from odoo import api, fields, models + + +class ResConfigSettings(models.TransientModel): + """Inherited Configuration Settings""" + _inherit = "res.config.settings" + + enable_service_charge = fields.Boolean( + string="Service Charges", + config_parameter="all_in_one_pos_kit.enable_service_charge", + help="Enable to add service charge") + visibility = fields.Selection( + [('global', 'Global'), ('session', 'Session')], + default='global', string="Visibility", + config_parameter="all_in_one_pos_kit.visibility", + help='Setup the Service charge globally or per session') + global_selection = fields.Selection([ + ('amount', 'Amount'), + ('percentage', 'Percentage')], + string='Type', default='amount', + config_parameter="all_in_one_pos_kit.global_selection", + help='Set the service charge as a amount or percentage') + global_charge = fields.Float( + string='Service Charge', + config_parameter="all_in_one_pos_kit.global_charge", + help='Set a default service charge globally') + global_product_id = fields.Many2one( + 'product.product', string='Service Product', + domain="[('available_in_pos', '=', True),('sale_ok', '=', True)," + "('type', '=', 'service')]", + config_parameter="all_in_one_pos_kit.global_product_id", + help='Set a service product globally') + custom_tip_percentage = fields.Float( + string="Custom Percentage", + config_parameter='all_in_one_pos_kit.custom_tip_percentage', + help="enter the percentage custom tips") + barcode = fields.Boolean(string='Order Barcode', + config_parameter='all_in_one_pos_kit.barcode', + help='Enable or disable the display of order ' + 'barcode') + invoice_number = fields.Boolean( + string='Invoice Number', + config_parameter='all_in_one_pos_kit.invoice_number', + help='Enable or disable the display of invoice number') + customer_details = fields.Boolean( + string='Customer Details', + config_parameter='all_in_one_pos_kit.customer_details', + help='Enable or disable the display of customer details') + customer_name = fields.Boolean( + string='Customer Name', + config_parameter='all_in_one_pos_kit.customer_name', + help='Enable or disable the display of customer name') + customer_address = fields.Boolean( + string='Customer Address', + config_parameter='all_in_one_pos_kit.customer_address', + help='Enable or disable the display of customer address') + customer_mobile = fields.Boolean( + string='Customer Mobile', + config_parameter='all_in_one_pos_kit.customer_mobile', + help='Enable or disable the display of customer mobile number') + customer_phone = fields.Boolean( + string='Customer Phone', + config_parameter='all_in_one_pos_kit.customer_phone', + help='Enable or disable the display of customer phone number') + customer_email = fields.Boolean( + string='Customer Email', + config_parameter='all_in_one_pos_kit.customer_email', + help='Enable or disable the display of customer email') + customer_vat = fields.Boolean( + string='Customer VAT', + config_parameter='all_in_one_pos_kit.customer_vat', + help='Enable or disable the display of customer VAT number') + customer_msg = fields.Boolean('POS Greetings', + config_parameter='all_in_one_pos_kit.customer_msg', + Help='Create an account if you ' + 'ever create an account') + twilio_auth_token = fields.Char('Auth Token', + config_parameter='all_in_one_pos_kit.twilio_auth_token', + Help='Copy the token from your twilio console ' + 'window and paste here') + account_sid = fields.Char('Account SID', + config_parameter='all_in_one_pos_kit.account_sid') + twilio_number = fields.Char('Twilio Number', + config_parameter='all_in_one_pos_kit.twilio_number', + Help='The number provided by ' + 'twilio used to send ' + 'text messages') + sms_body = fields.Char('Body', config_parameter='all_in_one_pos_kit.sms_body') + + def set_values(self): + """Override method to set configuration values. + :return: Result of the super method""" + res = super(ResConfigSettings, self).set_values() + self.env['ir.config_parameter'].set_param( + 'pos.sms_body', self.sms_body) + return res + + def get_values(self): + """Override method to get configuration values. + :return: Dictionary of configuration values""" + res = super(ResConfigSettings, self).get_values() + res.update(sms_body=self.env['ir.config_parameter'].sudo().get_param( + 'pos.sms_body')) + return res diff --git a/all_in_one_pos_kit/models/res_users.py b/all_in_one_pos_kit/models/res_users.py new file mode 100644 index 000000000..21dc3eb83 --- /dev/null +++ b/all_in_one_pos_kit/models/res_users.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Ayana KP(odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################### +from odoo import api, fields, models + + +class ResUsers(models.Model): + """Inherit the class res_users to add field""" + _inherit = "res.users" + + pos_conf_id = fields.Many2one('pos.config', string="POS Configuration", + help='select POS for the user') + pos_config_ids = fields.Many2many('pos.config', string='Allowed Pos', + help='Allowed Pos for this user') + show_users = fields.Boolean(string="Show users of pos", default=True, + help='Show users in dashboard ( for pos ' + 'administrators only)') diff --git a/all_in_one_pos_kit/models/stock_lot.py b/all_in_one_pos_kit/models/stock_lot.py new file mode 100644 index 000000000..6f7fd3881 --- /dev/null +++ b/all_in_one_pos_kit/models/stock_lot.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +################################################################################ +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies(). +# Author: Ayana KP(odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +################################################################################ +from odoo import api, models +from odoo.tools import float_compare + + +class StockLot(models.Model): + """This class inherits from the "stock.lot" model, which represents lots of + products in the inventory.It adds additional methods and fields to enhance + the functionality related to lots.""" + _inherit = "stock.lot" + + @api.model + def get_available_lots_for_pos(self, product_id, ): + """Get available lots for a product suitable for the Point of Sale + (PoS).This method retrieves the available lots for a specific product + that are suitable for the Point of Sale (PoS) based on the configured + removal strategy. The lots are sorted based on the expiration date or + creation date,depending on the removal strategy.""" + company_id = self.env.company.id + removal_strategy_id = (self.env['product.template'].browse( + self.env['product.product'].browse(product_id).product_tmpl_id.id) + .categ_id.removal_strategy_id.method) + if removal_strategy_id == 'fefo': + lots = self.sudo().search( + ["&", ["product_id", "=", product_id], "|", + ["company_id", "=", company_id], + ["company_id", "=", False]], + order='expiration_date asc') + else: + lots = self.sudo().search( + ["&", ["product_id", "=", product_id], "|", + ["company_id", "=", company_id], + ["company_id", "=", False], ], + order='create_date asc') + lots = lots.filtered(lambda l: float_compare( + l.product_qty, 0, + precision_digits=l.product_uom_id.rounding) > 0)[:1] + return lots.mapped("name") diff --git a/all_in_one_pos_kit/report/__init__.py b/all_in_one_pos_kit/report/__init__.py new file mode 100644 index 000000000..94041082f --- /dev/null +++ b/all_in_one_pos_kit/report/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Ayana KP(odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################### +from . import all_in_one_pos_kit_report diff --git a/all_in_one_pos_kit/report/all_in_one_pos_kit_report.py b/all_in_one_pos_kit/report/all_in_one_pos_kit_report.py new file mode 100644 index 000000000..0bf7509ab --- /dev/null +++ b/all_in_one_pos_kit/report/all_in_one_pos_kit_report.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Ayana KP(odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################### +from odoo import api, models + + +class PosOrder(models.AbstractModel): + """Model to generate the POS order report.""" + _name = 'report.all_in_one_pos_kit.pos_order_report' + + @api.model + def _get_report_values(self, docids, data=None): + """Get the report values for generating the POS order report. + :param docids: The IDs of the records to include in the report. + :param data: Additional data for generating the report (optional). + :return: A dictionary containing the report values.""" + if self.env.context.get('pos_order_report'): + if data.get('report_data'): + data.update({ + 'report_main_line_data': data.get('report_data')[ + 'report_lines'], + 'Filters': data.get('report_data')['filters'], + 'company': self.env.company}) + return data diff --git a/all_in_one_pos_kit/report/all_in_one_pos_kit_templates.xml b/all_in_one_pos_kit/report/all_in_one_pos_kit_templates.xml new file mode 100644 index 000000000..e1e3b46c5 --- /dev/null +++ b/all_in_one_pos_kit/report/all_in_one_pos_kit_templates.xml @@ -0,0 +1,432 @@ + + + + + + + + + + + POS All In One Report + pos.report + qweb-pdf + all_in_one_pos_kit.pos_order_report + all_in_one_pos_kit.pos_order_report + + diff --git a/all_in_one_pos_kit/security/all_in_one_pos_kit_security.xml b/all_in_one_pos_kit/security/all_in_one_pos_kit_security.xml new file mode 100644 index 000000000..17b17b0b4 --- /dev/null +++ b/all_in_one_pos_kit/security/all_in_one_pos_kit_security.xml @@ -0,0 +1,43 @@ + + + + + Config User + + [('id','in',user.pos_config_ids.ids)] + + + + + Config Manager + + [] + + + + + Orders User + + + [('config_id','in',user.pos_config_ids.ids)] + + + + + + Orders Manager + + [] + + + + + multi panning multi company rule + + + ['|',('company_id','=',False),('company_id', 'in', company_ids)] + + + diff --git a/all_in_one_pos_kit/security/ir.model.access.csv b/all_in_one_pos_kit/security/ir.model.access.csv new file mode 100644 index 000000000..dc0ddf812 --- /dev/null +++ b/all_in_one_pos_kit/security/ir.model.access.csv @@ -0,0 +1,8 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_multi_barcode_products,access.multi.barcode.products,model_multi_barcode_products,base.group_user,1,1,1,1 +access_mrp_production_pos_user,access.mrp.production.pos.user,model_mrp_production,point_of_sale.group_pos_user,1,1,1,0 +access_mrp_bom_pos_user,access.mrp.bom.pos.user,mrp.model_mrp_bom,point_of_sale.group_pos_user,1,0,0,0 +access_pos_report,access.pos.report,model_pos_report,base.group_user,1,1,1,1 +access_pos_greetings,access.pos.greetings,model_pos_greetings,base.group_user,1,0,1,0 +access_meals_planning_user,access.meals.planning.user,model_meals_planning,point_of_sale.group_pos_user,1,0,0,0 +access_meals_planning_manager,access.meals.planning.manager,model_meals_planning,point_of_sale.group_pos_manager,1,1,1,1 diff --git a/all_in_one_pos_kit/static/description/assets/icons/capture (1).png b/all_in_one_pos_kit/static/description/assets/icons/capture (1).png new file mode 100644 index 000000000..8824deafc Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/icons/capture (1).png differ diff --git a/all_in_one_pos_kit/static/description/assets/icons/check.png b/all_in_one_pos_kit/static/description/assets/icons/check.png new file mode 100644 index 000000000..c8e85f51d Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/icons/check.png differ diff --git a/all_in_one_pos_kit/static/description/assets/icons/chevron.png b/all_in_one_pos_kit/static/description/assets/icons/chevron.png new file mode 100644 index 000000000..2089293d6 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/icons/chevron.png differ diff --git a/all_in_one_pos_kit/static/description/assets/icons/cogs.png b/all_in_one_pos_kit/static/description/assets/icons/cogs.png new file mode 100644 index 000000000..95d0bad62 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/icons/cogs.png differ diff --git a/all_in_one_pos_kit/static/description/assets/icons/consultation.png b/all_in_one_pos_kit/static/description/assets/icons/consultation.png new file mode 100644 index 000000000..8319d4baa Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/icons/consultation.png differ diff --git a/all_in_one_pos_kit/static/description/assets/icons/ecom-black.png b/all_in_one_pos_kit/static/description/assets/icons/ecom-black.png new file mode 100644 index 000000000..a9385ff13 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/icons/ecom-black.png differ diff --git a/all_in_one_pos_kit/static/description/assets/icons/education-black.png b/all_in_one_pos_kit/static/description/assets/icons/education-black.png new file mode 100644 index 000000000..3eb09b27b Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/icons/education-black.png differ diff --git a/all_in_one_pos_kit/static/description/assets/icons/hotel-black.png b/all_in_one_pos_kit/static/description/assets/icons/hotel-black.png new file mode 100644 index 000000000..130f613be Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/icons/hotel-black.png differ diff --git a/all_in_one_pos_kit/static/description/assets/icons/img.png b/all_in_one_pos_kit/static/description/assets/icons/img.png new file mode 100644 index 000000000..70197f477 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/icons/img.png differ diff --git a/all_in_one_pos_kit/static/description/assets/icons/license.png b/all_in_one_pos_kit/static/description/assets/icons/license.png new file mode 100644 index 000000000..a5869797e Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/icons/license.png differ diff --git a/all_in_one_pos_kit/static/description/assets/icons/lifebuoy.png b/all_in_one_pos_kit/static/description/assets/icons/lifebuoy.png new file mode 100644 index 000000000..658d56ccc Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/icons/lifebuoy.png differ diff --git a/all_in_one_pos_kit/static/description/assets/icons/manufacturing-black.png b/all_in_one_pos_kit/static/description/assets/icons/manufacturing-black.png new file mode 100644 index 000000000..697eb0e9f Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/icons/manufacturing-black.png differ diff --git a/all_in_one_pos_kit/static/description/assets/icons/photo-capture.png b/all_in_one_pos_kit/static/description/assets/icons/photo-capture.png new file mode 100644 index 000000000..06c111758 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/icons/photo-capture.png differ diff --git a/all_in_one_pos_kit/static/description/assets/icons/pos-black.png b/all_in_one_pos_kit/static/description/assets/icons/pos-black.png new file mode 100644 index 000000000..97c0f90c1 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/icons/pos-black.png differ diff --git a/all_in_one_pos_kit/static/description/assets/icons/puzzle.png b/all_in_one_pos_kit/static/description/assets/icons/puzzle.png new file mode 100644 index 000000000..65cf854e7 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/icons/puzzle.png differ diff --git a/all_in_one_pos_kit/static/description/assets/icons/restaurant-black.png b/all_in_one_pos_kit/static/description/assets/icons/restaurant-black.png new file mode 100644 index 000000000..4a35eb939 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/icons/restaurant-black.png differ diff --git a/all_in_one_pos_kit/static/description/assets/icons/service-black.png b/all_in_one_pos_kit/static/description/assets/icons/service-black.png new file mode 100644 index 000000000..301ab51cb Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/icons/service-black.png differ diff --git a/all_in_one_pos_kit/static/description/assets/icons/trading-black.png b/all_in_one_pos_kit/static/description/assets/icons/trading-black.png new file mode 100644 index 000000000..9398ba2f1 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/icons/trading-black.png differ diff --git a/all_in_one_pos_kit/static/description/assets/icons/training.png b/all_in_one_pos_kit/static/description/assets/icons/training.png new file mode 100644 index 000000000..884ca024d Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/icons/training.png differ diff --git a/all_in_one_pos_kit/static/description/assets/icons/update.png b/all_in_one_pos_kit/static/description/assets/icons/update.png new file mode 100644 index 000000000..ecbc5a01a Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/icons/update.png differ diff --git a/all_in_one_pos_kit/static/description/assets/icons/user.png b/all_in_one_pos_kit/static/description/assets/icons/user.png new file mode 100644 index 000000000..6ffb23d9f Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/icons/user.png differ diff --git a/all_in_one_pos_kit/static/description/assets/icons/wrench.png b/all_in_one_pos_kit/static/description/assets/icons/wrench.png new file mode 100644 index 000000000..6c04dea0f Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/icons/wrench.png differ diff --git a/all_in_one_pos_kit/static/description/assets/misc/Cybrosys R.png b/all_in_one_pos_kit/static/description/assets/misc/Cybrosys R.png new file mode 100644 index 000000000..da4058087 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/misc/Cybrosys R.png differ diff --git a/all_in_one_pos_kit/static/description/assets/misc/email.svg b/all_in_one_pos_kit/static/description/assets/misc/email.svg new file mode 100644 index 000000000..15291cdc3 --- /dev/null +++ b/all_in_one_pos_kit/static/description/assets/misc/email.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/all_in_one_pos_kit/static/description/assets/misc/phone.svg b/all_in_one_pos_kit/static/description/assets/misc/phone.svg new file mode 100644 index 000000000..b7bd7f251 --- /dev/null +++ b/all_in_one_pos_kit/static/description/assets/misc/phone.svg @@ -0,0 +1,3 @@ + + + diff --git a/all_in_one_pos_kit/static/description/assets/misc/star (1) 2.svg b/all_in_one_pos_kit/static/description/assets/misc/star (1) 2.svg new file mode 100644 index 000000000..5ae9f507a --- /dev/null +++ b/all_in_one_pos_kit/static/description/assets/misc/star (1) 2.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/all_in_one_pos_kit/static/description/assets/misc/support (1) 1.svg b/all_in_one_pos_kit/static/description/assets/misc/support (1) 1.svg new file mode 100644 index 000000000..7d37a8f30 --- /dev/null +++ b/all_in_one_pos_kit/static/description/assets/misc/support (1) 1.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/all_in_one_pos_kit/static/description/assets/misc/support-email.svg b/all_in_one_pos_kit/static/description/assets/misc/support-email.svg new file mode 100644 index 000000000..eb70370d6 --- /dev/null +++ b/all_in_one_pos_kit/static/description/assets/misc/support-email.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/all_in_one_pos_kit/static/description/assets/misc/tick-mark.svg b/all_in_one_pos_kit/static/description/assets/misc/tick-mark.svg new file mode 100644 index 000000000..2dbb40187 --- /dev/null +++ b/all_in_one_pos_kit/static/description/assets/misc/tick-mark.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/all_in_one_pos_kit/static/description/assets/misc/whatsapp 1.svg b/all_in_one_pos_kit/static/description/assets/misc/whatsapp 1.svg new file mode 100644 index 000000000..0bfaf8fc6 --- /dev/null +++ b/all_in_one_pos_kit/static/description/assets/misc/whatsapp 1.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/all_in_one_pos_kit/static/description/assets/misc/whatsapp.svg b/all_in_one_pos_kit/static/description/assets/misc/whatsapp.svg new file mode 100644 index 000000000..b618aea1d --- /dev/null +++ b/all_in_one_pos_kit/static/description/assets/misc/whatsapp.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/all_in_one_pos_kit/static/description/assets/modules/module_image (1).jpeg b/all_in_one_pos_kit/static/description/assets/modules/module_image (1).jpeg new file mode 100644 index 000000000..5ae24843e Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/modules/module_image (1).jpeg differ diff --git a/all_in_one_pos_kit/static/description/assets/modules/module_image (1).png b/all_in_one_pos_kit/static/description/assets/modules/module_image (1).png new file mode 100644 index 000000000..0dea4f332 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/modules/module_image (1).png differ diff --git a/all_in_one_pos_kit/static/description/assets/modules/module_image (2).png b/all_in_one_pos_kit/static/description/assets/modules/module_image (2).png new file mode 100644 index 000000000..a5dc79613 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/modules/module_image (2).png differ diff --git a/all_in_one_pos_kit/static/description/assets/modules/module_image-1.jpeg b/all_in_one_pos_kit/static/description/assets/modules/module_image-1.jpeg new file mode 100644 index 000000000..31f066e9c Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/modules/module_image-1.jpeg differ diff --git a/all_in_one_pos_kit/static/description/assets/modules/module_image.jpeg b/all_in_one_pos_kit/static/description/assets/modules/module_image.jpeg new file mode 100644 index 000000000..0cbac311c Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/modules/module_image.jpeg differ diff --git a/all_in_one_pos_kit/static/description/assets/modules/module_image.png b/all_in_one_pos_kit/static/description/assets/modules/module_image.png new file mode 100644 index 000000000..612be4b77 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/modules/module_image.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/1.png b/all_in_one_pos_kit/static/description/assets/screenshots/1.png new file mode 100644 index 000000000..f2b937cda Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/1.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/10.png b/all_in_one_pos_kit/static/description/assets/screenshots/10.png new file mode 100644 index 000000000..7f3e0ca75 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/10.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/11.png b/all_in_one_pos_kit/static/description/assets/screenshots/11.png new file mode 100644 index 000000000..d02f03985 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/11.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/12.png b/all_in_one_pos_kit/static/description/assets/screenshots/12.png new file mode 100644 index 000000000..4164a58cd Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/12.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/13.png b/all_in_one_pos_kit/static/description/assets/screenshots/13.png new file mode 100644 index 000000000..a6e856297 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/13.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/14.png b/all_in_one_pos_kit/static/description/assets/screenshots/14.png new file mode 100644 index 000000000..88b808de4 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/14.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/15.png b/all_in_one_pos_kit/static/description/assets/screenshots/15.png new file mode 100644 index 000000000..a4013d6d3 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/15.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/16.png b/all_in_one_pos_kit/static/description/assets/screenshots/16.png new file mode 100644 index 000000000..b97d6cbce Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/16.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/17.png b/all_in_one_pos_kit/static/description/assets/screenshots/17.png new file mode 100644 index 000000000..a7c5040bc Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/17.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/18.png b/all_in_one_pos_kit/static/description/assets/screenshots/18.png new file mode 100644 index 000000000..36268495f Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/18.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/19.png b/all_in_one_pos_kit/static/description/assets/screenshots/19.png new file mode 100644 index 000000000..ca49ec662 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/19.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/2.png b/all_in_one_pos_kit/static/description/assets/screenshots/2.png new file mode 100644 index 000000000..1ac7acbb6 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/2.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/20.png b/all_in_one_pos_kit/static/description/assets/screenshots/20.png new file mode 100644 index 000000000..b582eba03 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/20.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/21.png b/all_in_one_pos_kit/static/description/assets/screenshots/21.png new file mode 100644 index 000000000..ce5fdf633 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/21.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/22.png b/all_in_one_pos_kit/static/description/assets/screenshots/22.png new file mode 100644 index 000000000..c126bf0e1 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/22.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/23.png b/all_in_one_pos_kit/static/description/assets/screenshots/23.png new file mode 100644 index 000000000..fd525d67c Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/23.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/24.png b/all_in_one_pos_kit/static/description/assets/screenshots/24.png new file mode 100644 index 000000000..2fe80f791 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/24.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/25.png b/all_in_one_pos_kit/static/description/assets/screenshots/25.png new file mode 100644 index 000000000..dd7d5a304 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/25.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/26.png b/all_in_one_pos_kit/static/description/assets/screenshots/26.png new file mode 100644 index 000000000..b352bbfaf Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/26.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/27.png b/all_in_one_pos_kit/static/description/assets/screenshots/27.png new file mode 100644 index 000000000..991b3e01a Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/27.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/28.png b/all_in_one_pos_kit/static/description/assets/screenshots/28.png new file mode 100644 index 000000000..1e8c9bd39 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/28.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/29.png b/all_in_one_pos_kit/static/description/assets/screenshots/29.png new file mode 100644 index 000000000..830ea224a Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/29.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/3.png b/all_in_one_pos_kit/static/description/assets/screenshots/3.png new file mode 100644 index 000000000..e23bee0a6 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/3.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/30.png b/all_in_one_pos_kit/static/description/assets/screenshots/30.png new file mode 100644 index 000000000..9e762792f Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/30.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/31.png b/all_in_one_pos_kit/static/description/assets/screenshots/31.png new file mode 100644 index 000000000..66c7df9a1 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/31.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/32.png b/all_in_one_pos_kit/static/description/assets/screenshots/32.png new file mode 100644 index 000000000..1ac5f8326 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/32.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/33.png b/all_in_one_pos_kit/static/description/assets/screenshots/33.png new file mode 100644 index 000000000..7f3efcb62 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/33.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/34.png b/all_in_one_pos_kit/static/description/assets/screenshots/34.png new file mode 100644 index 000000000..2925f3bcc Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/34.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/35.png b/all_in_one_pos_kit/static/description/assets/screenshots/35.png new file mode 100644 index 000000000..98e45271d Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/35.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/36.png b/all_in_one_pos_kit/static/description/assets/screenshots/36.png new file mode 100644 index 000000000..5ed1138d3 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/36.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/37.png b/all_in_one_pos_kit/static/description/assets/screenshots/37.png new file mode 100644 index 000000000..3ba10cc3a Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/37.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/38.png b/all_in_one_pos_kit/static/description/assets/screenshots/38.png new file mode 100644 index 000000000..23321b1be Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/38.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/39.png b/all_in_one_pos_kit/static/description/assets/screenshots/39.png new file mode 100644 index 000000000..6d67e8fd0 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/39.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/4.png b/all_in_one_pos_kit/static/description/assets/screenshots/4.png new file mode 100644 index 000000000..dc801d25e Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/4.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/40.png b/all_in_one_pos_kit/static/description/assets/screenshots/40.png new file mode 100644 index 000000000..a07195129 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/40.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/41.png b/all_in_one_pos_kit/static/description/assets/screenshots/41.png new file mode 100644 index 000000000..cee0f3ae7 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/41.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/42.png b/all_in_one_pos_kit/static/description/assets/screenshots/42.png new file mode 100644 index 000000000..186b15cc5 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/42.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/43.png b/all_in_one_pos_kit/static/description/assets/screenshots/43.png new file mode 100644 index 000000000..1df4aa3eb Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/43.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/44.png b/all_in_one_pos_kit/static/description/assets/screenshots/44.png new file mode 100644 index 000000000..10203362e Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/44.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/46.png b/all_in_one_pos_kit/static/description/assets/screenshots/46.png new file mode 100644 index 000000000..6ee6699b1 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/46.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/47.png b/all_in_one_pos_kit/static/description/assets/screenshots/47.png new file mode 100644 index 000000000..1e7850b79 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/47.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/48.png b/all_in_one_pos_kit/static/description/assets/screenshots/48.png new file mode 100644 index 000000000..047efec2c Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/48.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/5.png b/all_in_one_pos_kit/static/description/assets/screenshots/5.png new file mode 100644 index 000000000..020d78d8c Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/5.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/6.png b/all_in_one_pos_kit/static/description/assets/screenshots/6.png new file mode 100644 index 000000000..79cdba35c Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/6.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/7.png b/all_in_one_pos_kit/static/description/assets/screenshots/7.png new file mode 100644 index 000000000..f98c058a5 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/7.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/8.png b/all_in_one_pos_kit/static/description/assets/screenshots/8.png new file mode 100644 index 000000000..d0cbcbaad Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/8.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/9.png b/all_in_one_pos_kit/static/description/assets/screenshots/9.png new file mode 100644 index 000000000..fed0cd807 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/9.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/hero-v17.gif b/all_in_one_pos_kit/static/description/assets/screenshots/hero-v17.gif new file mode 100644 index 000000000..3727fe8a4 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/hero-v17.gif differ diff --git a/all_in_one_pos_kit/static/description/banner.jpg b/all_in_one_pos_kit/static/description/banner.jpg new file mode 100644 index 000000000..df6893991 Binary files /dev/null and b/all_in_one_pos_kit/static/description/banner.jpg differ diff --git a/all_in_one_pos_kit/static/description/icon.png b/all_in_one_pos_kit/static/description/icon.png new file mode 100644 index 000000000..eebf6b4d7 Binary files /dev/null and b/all_in_one_pos_kit/static/description/icon.png differ diff --git a/all_in_one_pos_kit/static/description/index.html b/all_in_one_pos_kit/static/description/index.html new file mode 100644 index 000000000..eeeda042e --- /dev/null +++ b/all_in_one_pos_kit/static/description/index.html @@ -0,0 +1,1673 @@ + + + + + + + Odoo App 3 Index + + + + + + + + +
+
+
+
+
+ +
+
+
+ Community +
+
+ Enterprise +
+
+ Odoo.sh +
+
+
+
+
+
+

+ All in One POS Kit

+

+ This module combines a variety of POS features. +

+
+ +
+
+
+
+
+

+ Key Highlights +

+
+
+
+
+
+ +
+
+

+ POS Dashboard.

+
+
+
+
+
+
+ +
+
+

+ Dynamic POS Report Maker

+
+
+
+
+
+
+ +
+
+

+ Age restricted products in pos.

+
+
+
+
+
+
+ +
+
+

+ Advanced POS Receipt with Customer Details, + Invoice Details and barcode.

+
+
+
+
+
+
+ +
+
+

+ Helps to directly log in to POS.

+
+
+
+
+
+
+ +
+
+

+ Allows to create multiple barcode for a single + product.

+
+
+
+
+
+
+ +
+
+

+ Logo For Every Point of Sale (Screen & + Receipt).

+
+
+
+
+
+
+ +
+
+

+ Category wise receipt for the Point of Sale.

+
+
+
+
+
+
+ +
+
+

+ Apply a fixed percentage of Tip.

+
+
+
+
+
+
+ +
+
+

+ Send Greeting messages to Customers.

+
+
+
+ +
+
+
+ +
+
+

+ Launch automatic MRP orders after selling + through POS.

+
+
+
+
+
+
+ +
+
+

+ Show/Hide NumPad in POS.

+
+
+
+
+
+
+ +
+
+

+ Show product image in POS order lines.

+
+
+
+
+
+
+ +
+
+

+ Easily edit order line.

+
+
+
+
+
+
+ +
+
+

+ Show the count and quantity of items in the POS + screen and POS receipt.

+
+
+
+
+
+
+ +
+
+

+ Create and edit products directly from POS.

+
+
+
+
+
+
+ +
+
+

+ Display time-based products in POS.

+
+
+
+
+
+
+ +
+
+

+ Restricts User access to POS and orders.

+
+
+
+
+
+
+ +
+
+

+ Customer can easily return or exchange their + products

+
+
+
+
+
+
+ +
+
+

+ Allows you to set service charges.

+
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+

+ Custom Dashboard for POS

+
+
+
+
+
+
+ +
+
+

+ Here You can see the total orders, Sessions, top customers, top products etc.

+
+
+
+
+
+
+ +
+
+

+ Custom Dynamic Report for POS sales analysis of a company in many aspects such as by order,order details, payment,salesman and so on. +

+
+
+
+
+
+
+ +
+
+

+ It can be filtered out based on different date ranges too. And also print report in pdf and xlxs format.

+
+
+
+
+
+
+ +
+
+

+ Advanced POS Receipt with Customer Details, Invoice Details and QR Code.

+
+
+
+
+
+
+ +
+
+

+ Customer Details in pos receipt.

+
+
+
+
+
+
+ +
+
+

+ Show/Hide Number pad in the POS Screen.

+
+
+
+
+
+
+ +
+
+

+ Number pad in hide in POS Screen.

+
+
+
+
+
+
+ +
+
+

+ POS Order Line Product Image.

+
+
+
+
+
+
+ +
+
+

+ Order line can be edited through Edit Order Line button.

+
+
+
+
+
+
+ +
+
+

+ A popup will be visible that shows all the order lines and quantities of products. You can edit the quantities. After editing, click on the Confirm button to update the order lines.

+
+
+
+
+
+
+ +
+
+

+ Users can enable the Service Charge feature in the POS configuration settings. It can be set to either global format, amount format, or percentage format. Additionally, users can specify the service charge product and amount.

+
+
+
+
+
+
+ +
+
+

+ After enabling service charge option, Service Charge button will appear in numpad.

+
+
+
+
+
+
+ +
+
+

+ Clicking on the service charge button will open a popup where you can set the service charge. After setting the service charge, click on the Confirm button.

+
+
+
+
+
+
+ +
+
+

+ The Service Charge product will be added to the order line.

+
+
+
+
+
+
+ +
+
+

+ All orders button in Numpad shows the orders in pos.

+
+
+
+
+
+
+ +
+
+

+ User can can select a line to Exchange the products.

+
+
+
+
+
+
+ +
+
+

+ A popup will appear with the details of the selected order line, where you can set the quantity of the product to be exchanged. After setting the quantity, click on the Exchange button.

+
+
+
+
+
+
+ +
+
+

+ The Exchanged product added in Order line.

+
+
+
+
+
+
+ +
+
+

+ Remove each lines from selected order by simply clicking X button.

+
+
+
+
+
+
+ +
+
+

+ User can clear all order with a single + click by using Clear All button.

+
+
+
+
+
+
+ +
+
+

+ You can directly create new product from point of sale by using Create Product button.

+
+
+
+
+
+
+ +
+
+

+ A popup will appear where details of the product can be give and click on confirm, it will create a new product in pos.

+
+
+
+
+
+
+ +
+
+

+ POS Time Based Products: You can display time-based products in POS.

+
+
+
+
+
+
+ +
+
+

+ Set the time and products in form view.

+
+
+
+
+
+
+ +
+
+

+ The products mentioned in the product planning will appear at the specified time in POS.

+
+
+
+
+
+
+ +
+
+

+ POS Direct Login: Directly login to POS no need to access Backend.

+
+
+
+
+
+
+ +
+
+

+ User login to the database.

+
+
+
+
+
+
+ +
+
+

+ Directly login to the POS session.

+
+
+
+
+
+
+ +
+
+

+ POS User Restrict:You can set allowed POS for each user in the user settings form. When the + user logs in, he can have access to the allowed POS only.

+
+
+
+
+
+
+ +
+
+

+ Manager can see the users assigned to each + POS and also can view POS of all users.

+
+
+
+
+
+
+ +
+
+

+ Age Restricted Products in POS: We can make the product as age restricted one in the product form + and whenever we choose this product in POS.

+
+
+
+
+
+
+ +
+
+

+ A popup message will appear , and notify that it is age restricted product and get identity + proof from customer, on approving ,the product it will add to the order line and on rejecting it will cancel the order.

+
+
+
+
+
+
+ +
+
+

+ Set multiple barcodes for a product and product variants.

+
+
+
+
+
+
+ +
+
+

+ Use these barcodes for scanning the product in POS.

+
+
+
+
+
+
+ +
+
+

+ Automatically launches MRP orders from POS.

+
+
+
+
+
+
+ +
+
+

+ If the product doesn't have a BOM, a validation error will appear requiring a BOM to be set.

+
+
+
+
+
+
+ +
+
+

+ Create a BOM for the product.

+
+
+
+
+
+
+ +
+
+

+ Select that product in pos and create sale order.

+
+
+
+
+
+
+ +
+
+

+ Automatically created a Manufacturing order for that product.

+
+
+
+
+
+
+ +
+
+

+ Set Logo for POS.

+
+
+
+
+
+
+ +
+
+

+ Logo appear in POS Screen.

+
+
+
+
+
+
+ +
+
+

+ Logo in POS receipt.

+
+
+
+ +
+
+
+ +
+
+

+ Set POS Custom Tips in configuration settings.

+
+
+
+
+
+
+ +
+
+

+ Set the tip amount in Payment page.

+
+
+
+
+
+
+ +
+
+

+ Send a greeting to the POS customer after validating the POS order. + You should install the Twilio python package. After this module installation you want to check with the trail account.

+
+
+
+ +
+
+
+
    +
  • + Available in + Odoo 17.0 Community, Enterprise and Odoo.sh +
  • +
  • + All in One POS Kit module combines many features in pos. +
  • +
+
+
+
+
+
+
Version + 17.0.1.0.0|Released on:7th May 2025 +
+

+ + Initial Commit for All in One POS Kit.

+
+
+
+
+
+
+
+

+ Related Products

+
+
+ +
+
+

+ Our Services

+ +
+
+
+
+
+
+
+
+ service-icon +
+
+

Odoo + Customization

+
+
+
+
+
+
+ service-icon +
+
+

Odoo + Implementation

+
+
+
+
+
+
+ service-icon +
+
+

Odoo + Support

+
+
+
+
+
+
+ service-icon +
+
+

Hire + Odoo Developer

+
+
+
+
+ +
+
+ service-icon +
+
+

Odoo + Integration

+
+
+
+
+
+
+ service-icon +
+
+

Odoo + Migration

+
+
+
+
+
+
+ service-icon +
+
+

Odoo + Consultancy

+
+
+
+
+
+
+ service-icon +
+
+

Odoo + Implementation

+
+
+
+
+
+
+ service-icon +
+
+

Odoo + Licensing Consultancy

+
+
+
+
+
+
+

+ Our Industries

+ +
+
+
+
+
+
+ +

Trading

+

Easily procure and sell your products

+
+
+
+
+ +

POS

+

Easy configuration and convivial experience

+
+
+
+
+ +

+ Education

+

A platform for educational management

+
+
+
+
+ +

+ Manufacturing

+

Plan, track and schedule your operations

+
+
+
+
+ +

E-commerce & + Website

+

Mobile friendly, awe-inspiring product pages

+
+
+
+
+ +

Service + Management

+

Keep track of services and invoice

+
+
+
+
+ +

+ Restaurant

+

Run your bar or restaurant methodically

+
+
+
+
+ +

Hotel + Management

+

An all-inclusive hotel management application

+
+
+
+
+
+
+

+ Support

+
+
+
+
+
+
+
+ +
+ Need + Help? +

Got + questions or need help? Get in touch.

+
odoo@cybrosys.com +
+
+
+
+
+
+
+
+ +
+ WhatsApp +

Say hi to + us on WhatsApp!

+
+91 + 99456767686 +
+
+
+
+
+
+
+
+
+ + + + + + diff --git a/all_in_one_pos_kit/static/src/advanced_receipt/js/payment.js b/all_in_one_pos_kit/static/src/advanced_receipt/js/payment.js new file mode 100755 index 000000000..44870963e --- /dev/null +++ b/all_in_one_pos_kit/static/src/advanced_receipt/js/payment.js @@ -0,0 +1,40 @@ +/** @odoo-module */ + +import { PaymentScreen } from "@point_of_sale/app/screens/payment_screen/payment_screen"; +import { patch } from "@web/core/utils/patch"; +import { useService } from "@web/core/utils/hooks"; +import { usePos } from "@point_of_sale/app/store/pos_hook"; + +patch(PaymentScreen.prototype, { + setup() { + super.setup(); + this.orm = useService("orm"); + this.pos = usePos(); + }, + async validateOrder(isForceValidate) { +// extending the validate order to add the below fields + let receipt_order = await super.validateOrder(arguments); + let receipt_number = this.pos.selectedOrder.name; + let orders = this.env.services.pos.selectedOrder; + const data = this.env.services.pos.session_orders; + let length = data.length-1; + let order = data[length]; + var mobile = order.customer_mobile; + var phone = order.customer_phone; + var email = order.customer_email; + var vat = order.customer_vat; + var address = order.customer_address; + var name = order.customer_name; + var customer_details = order.customer_details; + this.pos.customer_details = order.customer_details; + this.pos.mobile = order.customer_mobile; + this.pos.phone = order.customer_phone; + this.pos.email = order.customer_email; + this.pos.vat = order.customer_vat; + this.pos.address = order.customer_address; + this.pos.name = order.customer_name; + this.pos.barcode = order.barcode; + this.pos.invoice_number = order.invoice_number; + return receipt_order; + }, +}); diff --git a/all_in_one_pos_kit/static/src/advanced_receipt/js/pos_order_receipt.js b/all_in_one_pos_kit/static/src/advanced_receipt/js/pos_order_receipt.js new file mode 100755 index 000000000..3c8d8e1f9 --- /dev/null +++ b/all_in_one_pos_kit/static/src/advanced_receipt/js/pos_order_receipt.js @@ -0,0 +1,28 @@ +/** @odoo-module */ +import { patch } from "@web/core/utils/patch"; +import { Order } from "@point_of_sale/app/store/models"; +import { PosStore } from "@point_of_sale/app/store/pos_store"; +import { uuidv4} from "@point_of_sale/utils"; + +patch(PosStore.prototype, { + async _processData(loadedData) { + await super._processData(loadedData); + this.session_orders = loadedData['res.config.settings']; + var json = { + access_token: this.access_token || '', + }; + const options = {pos:this}; + this.pos = options.pos; + this.access_token = uuidv4(); + const address = `${this.pos.base_url}/pos/ticket/validate?access_token=${this.access_token}` + var receipt_number = this.selectedOrder + const codeWriter = new window.ZXing.BrowserQRCodeSvgWriter(); + const qr_code_svg = new XMLSerializer().serializeToString( + codeWriter.write(address, 150, 150) + ); + this.pos.qr_image = "data:image/svg+xml;base64," + window.btoa(qr_code_svg); + $(".orderlines").change(function (){ + const address = `${this.base_url}/pos/ticket/validate?access_token=${this.access_token}` + }); + } +}); diff --git a/all_in_one_pos_kit/static/src/advanced_receipt/xml/OrderReceipt.xml b/all_in_one_pos_kit/static/src/advanced_receipt/xml/OrderReceipt.xml new file mode 100755 index 000000000..9b2700096 --- /dev/null +++ b/all_in_one_pos_kit/static/src/advanced_receipt/xml/OrderReceipt.xml @@ -0,0 +1,49 @@ + + + + + + + + +
Customer Name: + +
+
+ +
Customer Address: + +
+
+ +
Customer Mobile: + +
+
+ +
Customer Phone: + +
+
+ +
Customer Email: + +
+
+ +
Customer Vat: + +
+
+
+
+ +
+ +
+
+
+
+
diff --git a/all_in_one_pos_kit/static/src/age_restricted/js/age_restrict.js b/all_in_one_pos_kit/static/src/age_restricted/js/age_restrict.js new file mode 100644 index 000000000..47ee1a457 --- /dev/null +++ b/all_in_one_pos_kit/static/src/age_restricted/js/age_restrict.js @@ -0,0 +1,23 @@ +/** @odoo-module */ +import { patch } from "@web/core/utils/patch"; +import { ProductScreen } from "@point_of_sale/app/screens/product_screen/product_screen"; +import { ConfirmPopup } from "@point_of_sale/app/utils/confirm_popup/confirm_popup"; +import { PosStore } from "@point_of_sale/app/store/pos_store"; + +patch(PosStore.prototype, { + async addProductToCurrentOrder(product, options = {}){ + //if product is age restricted it shows the popup and on confirming the popup, it will adds to the order line, on rejecting it will cancel the order + if(product.is_age_restrict == true ){ + const { confirmed } = await this.popup.add(ConfirmPopup, { + title: ("Age Restricted Product !!!!!!!"), + body:('Please get Identity proof from customer.'), + }); + if (confirmed){ + super.addProductToCurrentOrder(...arguments) + } + } + else{ + super.addProductToCurrentOrder(...arguments) + } + } +}); diff --git a/all_in_one_pos_kit/static/src/age_restricted/js/restrict_popup.js b/all_in_one_pos_kit/static/src/age_restricted/js/restrict_popup.js new file mode 100644 index 000000000..fba570333 --- /dev/null +++ b/all_in_one_pos_kit/static/src/age_restricted/js/restrict_popup.js @@ -0,0 +1,16 @@ +/** @odoo-module */ + +import { AbstractAwaitablePopup } from "@point_of_sale/app/popup/abstract_awaitable_popup"; +import { registry } from "@web/core/registry"; +import { _t } from "@web/core/l10n/translation"; + //Restrict Popup widget by extending the Abstract Awaitable popup widget + export class RestrictPopup extends AbstractAwaitablePopup { + //Defining the template of restrict popup + static template = 'RestrictPopup'; + static defaultProps = { + confirmText: 'Approve', + cancelText: 'Reject', + title: 'Confirm ?', + body: '', + }; + } diff --git a/all_in_one_pos_kit/static/src/age_restricted/xml/restrict_popup.xml b/all_in_one_pos_kit/static/src/age_restricted/xml/restrict_popup.xml new file mode 100644 index 000000000..547605990 --- /dev/null +++ b/all_in_one_pos_kit/static/src/age_restricted/xml/restrict_popup.xml @@ -0,0 +1,25 @@ + + + + + + + + + diff --git a/all_in_one_pos_kit/static/src/custom_tip/js/PaymentScreen.js b/all_in_one_pos_kit/static/src/custom_tip/js/PaymentScreen.js new file mode 100644 index 000000000..605623235 --- /dev/null +++ b/all_in_one_pos_kit/static/src/custom_tip/js/PaymentScreen.js @@ -0,0 +1,45 @@ +/** @odoo-module */ + +import { PaymentScreen } from "@point_of_sale/app/screens/payment_screen/payment_screen"; +import { patch } from "@web/core/utils/patch"; +import { ConfirmPopup } from "@point_of_sale/app/utils/confirm_popup/confirm_popup"; +//import { useService } from "@web/core/utils/hooks"; + +patch(PaymentScreen.prototype, { + onMounted() { + super.onMounted(); + }, + async CustomTipButton(){ + console.log('kkkkkkkkkkkk') + //Custom method to handle the click event of the custom tip button. + var custom_tip_percentage = this.pos.res_config_settings[this.env.pos.res_config_settings.length-1].custom_tip_percentage + if(custom_tip_percentage){ + this.pos.tips = true; + this.pos.custom_tip = custom_tip_percentage + var cust_tip = ((this.currentOrder.get_total_with_tax() + this.currentOrder.get_rounding_applied() ) * parseInt(custom_tip_percentage) /100); + const { confirmed, payload } = await this.showPopup('NumberPopup', { + title: cust_tip ? this.env._t('Change Tip') : this.env._t('Add Tip'), + startingValue: cust_tip === 0 && change > 0 ? change : cust_tip, + isInputSelected: true, + }); + if (confirmed) { + this.currentOrder.set_tip(parse.float(payload.toString())); + } + } + }, + Tips() { + // Getter method to calculate and provide tip-related information to the template. + var custom_tip_percentage = this.env.pos.res_config_settings[this.env.pos.res_config_settings.length-1].custom_tip_percentage + if(custom_tip_percentage){ + this.env.pos.tips = true; + this.env.pos.custom_tip = custom_tip_percentage + } + else{ + this.env.pos.tips = false; + } + return { + tip:custom_tip_percentage, + tip_enable:this.env.pos.tips, + }; + } + }); \ No newline at end of file diff --git a/all_in_one_pos_kit/static/src/custom_tip/xml/PaymentScreen.xml b/all_in_one_pos_kit/static/src/custom_tip/xml/PaymentScreen.xml new file mode 100644 index 000000000..c5cda0f72 --- /dev/null +++ b/all_in_one_pos_kit/static/src/custom_tip/xml/PaymentScreen.xml @@ -0,0 +1,15 @@ + + + + + + +
+ + +
+
+
+
+
diff --git a/all_in_one_pos_kit/static/src/dashboard/css/pos_dashboard.css b/all_in_one_pos_kit/static/src/dashboard/css/pos_dashboard.css new file mode 100644 index 000000000..5b7b8fa71 --- /dev/null +++ b/all_in_one_pos_kit/static/src/dashboard/css/pos_dashboard.css @@ -0,0 +1,897 @@ +.oh_dashboards{ + padding-top :15px; + background-color: #f8faff !important; +} +.oh-card h4 { + font-size: 1.1rem; +} +.breadcrumbs { + margin-top: 0; +} +.buttons button { + margin: 2px 0; } +/* Button Reset */ +.btn, .button { + display: inline-block; + font-weight: 400; + text-align: center; + white-space: nowrap; + vertical-align: middle; + transition: all .15s ease-in-out; + border-radius: 0; + cursor: pointer; } +/* Widget One +---------------------------*/ +.stat-content { + display: inline-block; + width: 66%; +} +.stat-icon{ + display: inline-block; +} +.stat-widget-one .stat-icon { + vertical-align: top; + margin: auto; + width: 100%; + color: #01c490; +} +.stat-widget-one .stat-icon i { + font-size: 30px; + font-weight: 900; + display: inline-block; + color: #01c490;} +.stat-widget-one .stat-text { + font-size: 14px; + color: #868e96; + font-weight: bold; +} +.stat-widget-one .stat-digit { + font-size: 24px; + color: #02448b; } +.stat-count { + font-size: 20px; + text-align: center; + color: #00438b;} +.stat-title { + font-size: 17px; + text-align: center; + color: #00438b; + } +.mb-0 .dash-title { + font-size: 20px; + text-align: center; + color: rgba(255, 255, 255, 0.81); +} +.hr_birthday { + font-size: 28px; + text-align: center; + padding: 20px 0; + color: #00438b; + font-weight: 600; +} +body .text-color { + color: #00438b; +} +.slice { + stroke: #fff; + stroke-width: 0px; +} +/* Leave graph */ +path { stroke: #fff; } +path:hover { opacity:0.9; } +rect:hover { fill:#934da5; } +.axis { font: 10px sans-serif; } +.legend tr{ border-bottom:1px solid grey; } +.legend tr:first-child{ border-top:1px solid grey; } +.axis path, +.axis line { + fill: none; + stroke: #000; + shape-rendering: crispEdges; +} +.x.axis path { display: none; } +.legend{ + border-collapse: collapse; + border-spacing: 0px; + display: inline-block; +} +.legend td, .legend .legend_col{ + padding:4px 5px; + vertical-align:bottom; +} +.legendFreq, .legendPerc{ + align:right; + width:50px; +} +/* Leave broadfactor graph */ +.broad_factor_graph .axis path, +.broad_factor_graph .axis line { + fill: none; + stroke: black; + shape-rendering: crispEdges; +} +.broad_factor_graph .axis text { + font-family: sans-serif; + font-size: 11px; +} +.broad_factor_graph rect { + -moz-transition: all 0.3s; + -webkit-transition: all 0.3s; + -o-transition: all 0.3s; + transition: all 0.3s; +} +.broad_factor_graph rect:hover{ + fill: #ff618a; +} +#broad_factor_pdf { + background-color: #ffffff; + border: 0; + color : #000000; + float: right; +} +#broad_factor_pdf i { + color: red; +} +.leave_broad_factor{ + overflow-x: auto !important; + overflow-y: hidden !important; + height: auto; +} +/*=====================New Dashboard===========================*/ +.oh_dashboards { + background-color: #f8faff !important; + padding: 0px !important; +} +.container-fluid.o_pos_dashboard { + padding: 0px !important; +} +.employee-prof { + padding: 0px; + height: 100%; + background-color: #3e6282; + /*background-image: linear-gradient(180deg, #3e6282, #41666f);*/ + position: fixed; + z-index: 999; +} +.employee-prof .oh-card:hover { + transform:none !important; + box-shadow: none !important; +} +/*.dummy{ + height:130vh; +}*/ +.oh-card { + padding-top: 0px; + padding: 0px; + margin-bottom: 1.5rem; + border-radius: 0px; + box-shadow: none; + background: none; + transition: transform 0.2s ease, box-shadow 0.2s ease; + will-change: transform, box-shadow; +} +.oh-card:hover { + transform: translateY(-2px) translateZ(0) !important; + box-shadow: 0 10px 10px 0 rgba(62, 57, 107, 0.12), 0 0 0 transparent !important; +} +.employee-prof .employee-icon { + float: left; + padding-right: 0px; + width: 100%; + height: 185px; + overflow: hidden; + background: #fff; +} +.employee-prof .employee-icon img{ + width: 100%; + background: #fff; +} +.employee-prof .employee-name h2 { + text-align: center; + font-weight: 300; + text-transform: uppercase; + font-size: 17px; + margin-top: 12px; + margin-bottom: 2px; + color: #fff; +} +.media-body.employee-name { + background: #466b8d; + float: left; + margin: 0; + width: 100% +} +.employee-prof .employee-name p { + margin: 0 0 9px; + text-align: center; + font-size: 12px; + color: #f3f3f3; +} +.employee-prof p { + margin: 0 0 9px; + color: #fff; +} +.employee-gender { + width: 40%; + margin-left: 10%; + padding: 8% 10% 4%; + text-align: center; + border-right: 1px solid #4d769b; + margin-top: 14%; + float: left; + border-bottom: 1px solid #4d769b; +} +.employee-gender p { + margin: 0px 0 4px !important; + color: #fff; +} +.employee-age { + width: 40%; + margin-right: 10%; + padding: 4% 10% 7%; + text-align: center; + margin-top: 18%; + float: left; + border-bottom: 1px solid #4d769b; +} +.employee-age p { + margin: 0 0 1px; + color: #fff; +} +.employee-experience { + width: 100%; + text-align: center; + padding-top: 8%; + float: left; + padding-bottom: 3%; +} +.employee-country { + width: 40%; + margin-left: 10%; + padding: 9% 0% 4%; + text-align: center; + border-right: 1px solid #4d769b; + margin-top: 2%; + float: left; + border-top: 1px solid #4d769b; +} +.employee-country p { + margin: 0px 0 1px !important; + color: #fff; +} +.employee-mobile { + width: 40%; + margin-right: 10%; + padding: 9% 0% 7%; + text-align: center; + margin-top: 2%; + float: left; + border-top: 1px solid #4d769b; +} +.employee-mobile p { + margin: 0 0 1px; + color: #fff; +} +.oh-payslip { + margin-top: 4.5%; +} +.oh-payslip .stat-icon { + width: 30%; + height: 85px; + text-align: center; + background: #ff8762; + color: #fff; + width: 32%; + padding-top: 2%; + font-size: xxx-large; +} +.oh-payslip .oh-card { + transition: transform 0.2s ease, box-shadow 0.2s ease; + will-change: transform, box-shadow; + box-shadow: 0 10px 40px 0 rgba(62,57,107,0.07), 0 2px 9px 0 rgba(62,57,107,0.06); +} +.stat-widget-one .stat-text { + font-size: 14px; + color: #ff8762; + margin-top: 2.3rem; + margin-left: 1rem; +} +.stat-widget-one .stat-digit { + font-size: 26px; + color:#993232; + margin-left: 1rem; + margin-top: -1px; + font-family: initial +} + +.stat-widget-one .stat-icon i { + font-size: 25px; + font-weight: 900; + display: inline-block; + color: #fff; +} +.stat-widget-one { + background-color: white; + text-align: left; +} +.stat-widget-one { + width: 100%; +} +.oh-payslip .stat-icon { + width: 30%; + height: 85px; + text-align: center; + padding-top: 2%; +} +.oh-timesheets .stat-icon{ + background: #5ebade !important; +} +.oh-contracts .stat-icon{ + background: #b298e1 !important; +} +.oh-broad-factor .stat-icon{ + background: #70cac1 !important; +} +.oh-timesheets .stat-widget-one .stat-text { + color: #5ebade; +} +.oh-contracts .stat-widget-one .stat-text { + color: #b298e1; +} +.oh-broad-factor .stat-widget-one .stat-text { + color: #70cac1; +} +.leave-manager { + background-color: #fff; + transition: transform 0.2s ease, box-shadow 0.2s ease; + will-change: transform, box-shadow; + box-shadow: 0 10px 40px 0 rgba(62,57,107,0.07), 0 2px 9px 0 rgba(62,57,107,0.06); + padding: 0px; + margin: 15px; +} +.hr_leave_request_approve { + padding: 0; + padding-bottom: 0em; + padding-top: 0em; + transition: transform 0.2s ease, box-shadow 0.2s ease; + will-change: transform, box-shadow; +} +.leaves_request_month { + padding: 0; + padding-top: 0px; + padding-bottom: 0px; + padding-bottom: 0em; + padding-top: 0em; + transition: transform 0.2s ease, box-shadow 0.2s ease; + will-change: transform, box-shadow; + border-bottom: 1px solid #f1f1f133; +} +.leaves_request_today{ + padding: 0; + padding-bottom: 0em; + padding-top: 0em; + transition: transform 0.2s ease, box-shadow 0.2s ease; + will-change: transform, box-shadow; +} +.hr_leave_request_approve:hover, .leaves_request_month:hover, .leaves_request_today:hover{ + transform: translateY(-2px) translateZ(0) !important; + box-shadow: 0 10px 10px 0 rgba(62, 57, 107, 0.12), 0 0 0 transparent !important; +} +.hr_leave_request_approve p { + font-size: 14px; + color: #ff8762; + margin-left: 1rem; + margin-bottom: 0px; + text-align: left; + width: 64%; + font-weight: bold; + float: left; +} +.leaves_request_today p { + font-size: 14px; + color: #5ebade; + margin-left: 1rem; + margin-bottom: 0px; + text-align: left; + width:64%; + float:left; + font-weight: bold; +} +.leaves_request_month p{ + font-size: 14px; + color: #b298e1; + margin-left: 1rem; + margin-bottom:0px; + text-align: left; + width:64%; + float:left; + font-weight: bold; +} +h4 .stat-count { + font-size: 17px; + text-align: center; + color: #000 !important; + margin-top: 0px; + width: 100%; + float: left; + margin: 0; +} +.leave-manager h4 { + float: left; + width: 23%; +} +.hr_leave_request_approve h4 { + padding: 5.2rem 0; + margin: 0; + background: #ff8762; + color: #fff; +} +.leaves_request_today h4 { + padding: 2.2rem 0; + margin: 0 !important; + background: #5ebade; + color: #fff; +} +.leaves_request_month h4 { + padding: 2.1rem 0; + margin: 0 !important; + background: #b298e1; + color: #fff; +} +.leaves_request_today h4 .stat-count ,.leaves_request_month h4 .stat-count , .hr_leave_request_approve h4 .stat-count +{ + color:#fff !important; +} +.graph_view .legend { + margin-bottom: 27px; + display: inline-block; + border-collapse: collapse; + border-spacing: 0px; + margin-left: 29px; +} +.hr-chart-1{ + margin: 15px 0px; + background: #fff; + padding: 0px !important; + transition: transform 0.2s ease, box-shadow 0.2s ease; + will-change: transform, box-shadow; + box-shadow: 0 10px 40px 0 rgba(62,57,107,0.07), 0 2px 9px 0 rgba(62,57,107,0.06); +} +.hr-chart-1:hover{ + transform: translateY(-2px) translateZ(0) !important; + box-shadow: 0 10px 10px 0 rgba(62, 57, 107, 0.12), 0 0 0 transparent !important; +} +.stat-head { + text-align: left !important; + font-weight: 300; + font-size: 15px; + margin-bottom: 25px; + margin-left: 24px; + width: 100%; +} +.emp_graph { + padding-left: 90px; + height: auto; + padding-bottom: 65px; + text-align: center !important; +} +.hr_leave_allocations_approve p { + font-size: 14px; + color: #ff8762; + margin-left: 1rem; + margin-bottom: 0px; + text-align: left; + width: 70%; + float: left; + font-weight: bold; +} +.hr_leave_allocations_approve h4 { + padding: 2.5rem 0; + margin: 0; + background: #ff8762; + color: #fff; + width: 26%; + float: left; +} +.hr_leave_allocations_approve .stat-count { + font-size: 17px; + text-align: center; + color: #fff !important; + margin-top: 0px; + width: 100%; + float: left; + margin: 0; +} +.hr_leave_allocations_approve { + padding: 0; + padding-top: 0px; + padding-bottom: 0px; + padding-bottom: 0em; + padding-top: 0em; + box-shadow: 0 10px 40px 0 rgba(62,57,107,0.07), 0 2px 9px 0 rgba(62,57,107,0.06); + transition: transform 0.2s ease, box-shadow 0.2s ease; + will-change: transform, box-shadow; + background: #fff; + height: 80px; +} +.hr_leave_allocations_approve:hover{ + transform: translateY(-2px) translateZ(0) !important; + box-shadow: 0 10px 10px 0 rgba(62, 57, 107, 0.12), 0 0 0 transparent !important; +} +.leave-manager { + background-color: #fff; + transition: transform 0.2s ease, box-shadow 0.2s ease; + will-change: transform, box-shadow; + box-shadow: 0 10px 40px 0 rgba(62,57,107,0.07), 0 2px 9px 0 rgba(62,57,107,0.06); + padding: 0px; + margin: 15px; + margin-right: 15px; + margin-right: 0px; + width: 95% !important; + padding: 0; +} +.hr_job_application_approve { + padding: 0; + padding-top: 0px; + padding-bottom: 0px; + padding-top: 0px; + padding-bottom: 0px; + padding-top: 0px; + padding-bottom: 0px; + padding-bottom: 0em; + padding-top: 0em; + box-shadow: 0 10px 40px 0 rgba(62,57,107,0.07), 0 2px 9px 0 rgba(62,57,107,0.06); + transition: transform 0.2s ease, box-shadow 0.2s ease; + will-change: transform, box-shadow; + background: #fff; + margin-top: 15px; + height: 80px; +} +.hr_job_application_approve p { + font-size: 14px; + color: #70cac1; + margin-left: 1rem; + margin-bottom: 0px; + text-align: left; + width: 70%; + float: left; + font-weight: bold; +} +.hr_job_application_approve h4 { + padding: 2.5rem 0; + margin: 0; + background: #70cac1; + color: #fff; + width: 26%; + float: left; +} +.hr_job_application_approve .stat-count { + font-size: 17px !important; + color: #fff !important; + margin-top: 0px !important; + width: 100%; + float: left; + margin: 0; + margin: 0px !important; + text-align: center !important; + width: 100% !important; +} +.hr_job_application_approve:hover{ + transform: translateY(-2px) translateZ(0) !important; + box-shadow: 0 10px 10px 0 rgba(62, 57, 107, 0.12), 0 0 0 transparent !important; +} +.hr_attendance_login .oh-card { + margin: 0; + margin-bottom: 0px; + margin-bottom: 0px; + background: #134c8a; + padding-bottom: 7px; + transition: transform 0.2s ease, box-shadow 0.2s ease; + will-change: transform, box-shadow; + box-shadow: 0 10px 40px 0 rgba(62,57,107,0.07), 0 2px 9px 0 rgba(62,57,107,0.06); +} +.hr_attendance_login .stat-widget-one { + background: none; +} +.hr_attendance_login .stat-widget-one .stat-icon { + text-align: center; + padding-top: 9px; +} +.hr_attendance_login .stat-content { + width: 100%; + color: #fff !important; +} +.hr_attendance_login .stat-widget-one .stat-text { + margin: 0; + text-align: center; + width: 100% !important; + padding: 0; + color: #fff; +} +.hr_attendance_login .stat-widget-one .stat-icon .fa { + font-size: 50px; +} +.hr_attendance_login .stat-widget-one .stat-icon .fa { + font-size: 50px; + margin: 0px; + box-shadow: none; +} +.hr_attendance_login { + margin-top: 1.5%; +} +.monthly_leave_graph_view .oh-card { + background: #fff; + transition: transform 0.2s ease, box-shadow 0.2s ease; + will-change: transform, box-shadow; + box-shadow: 0 10px 40px 0 rgba(62,57,107,0.07), 0 2px 9px 0 rgba(62,57,107,0.06); + padding: 15px; +} +.broad_factor_graph .oh-card { + padding: 15px !important; + background: #fff; + transition: transform 0.2s ease, box-shadow 0.2s ease; + will-change: transform, box-shadow; + box-shadow: 0 10px 40px 0 rgba(62,57,107,0.07), 0 2px 9px 0 rgba(62,57,107,0.06); + padding: 15px; +} +.leave_broad_factor { + overflow-x: auto !important; + overflow-y: hidden !important; + height: 336px; + padding: 0px; + padding-left: 0px; +} +#broad_factor_pdf { + background-color: #ffffff; + float: right; + border-radius: 30px; + transition: transform 0.2s ease, box-shadow 0.2s ease; + will-change: transform, box-shadow; + box-shadow: 0 10px 40px 0 rgba(62,57,107,0.07), 0 2px 9px 0 rgba(62,57,107,0.06); + border: 1px solid #4ec3b7; + color: #757575; + padding-top: 9px; + color: #4ec3b7; +} +#broad_factor_pdf:hover{ + transform: translateY(-2px) translateZ(0) !important; + box-shadow: 0 10px 10px 0 rgba(62, 57, 107, 0.12), 0 0 0 transparent !important; +} +.hr_birthday { + font-size: 17px; + text-align: center; + padding: 20px 0; + color: #00438b; + font-weight: 300; +} +.hr_notification img { + width: 40px; + height: 40px; + border-radius: 100%; +} +.hr_notification { + background: #fff; + transition: transform 0.2s ease, box-shadow 0.2s ease; + will-change: transform, box-shadow; + box-shadow: 0 10px 40px 0 rgba(62,57,107,0.07), 0 2px 9px 0 rgba(62,57,107,0.06); + height: 316px; + overflow-y: auto; + margin-bottom: 15px; +} +.hr_notification .media { + border-bottom: 1px solid #e6e6e6; + padding-bottom: 6px; + margin-bottom: 10px; +} +.hr_notification .text-color.display-6 { + margin: 0px 0 3px; + color: #2d2d2d; +} +.hr_notification p { + margin: 0 0 1px; + color: #666; + font-size: 10px; +} +.hr_notification_head { + font-size: 17px; + text-align: center; + padding: 12px 0; + color: #fff; + font-weight: 300; + background: #de6a5e; + margin-bottom: 9px; +} +.monthly_leave_trend .oh-card{ + background: #fff; + transition: none !important; + will-change: none !important; + box-shadow: none !important; + margin-bottom: 5px; +} + +.monthly_leave_trend path { + stroke: #70cac1; + stroke-width: 2; + fill: none; +} + +.monthly_leave_trend .axis path, +.monthly_leave_trend .axis line { + fill: none; + stroke: grey; + stroke-width: 1; + shape-rendering: crispEdges; +} +.monthly_leave_trend circle{ + fill: #ffffff; + stroke: #44b7ac; + stroke-width: 1.5; +} +.hr-chart-1 { + margin: 15px 0px; + background: #fff; + padding: 0px !important; + padding-top: 0px; + transition: transform 0.2s ease, box-shadow 0.2s ease; + will-change: transform, box-shadow; + box-shadow: 0 10px 40px 0 rgba(62,57,107,0.07), 0 2px 9px 0 rgba(62,57,107,0.06); + padding-top: 3px !important; +} +.monthly_leave_trend { + background: #fff; + transition: transform 0.2s ease, box-shadow 0.2s ease; + will-change: transform, box-shadow; + box-shadow: 0 10px 40px 0 rgba(62,57,107,0.07), 0 2px 9px 0 rgba(62,57,107,0.06); +} +.monthly_leave_trend:hover{ + transform: translateY(-2px) translateZ(0) !important; + box-shadow: 0 10px 10px 0 rgba(62, 57, 107, 0.12), 0 0 0 transparent !important; +} +/*----------------------*/ +.monthly_join_resign_trend{ + padding-right: 0px !important; +} +.monthly_join_resign_trend .oh-card { + background: #fff; + transition: transform 0.2s ease, box-shadow 0.2s ease; + will-change: transform, box-shadow; + box-shadow: 0 10px 40px 0 rgba(62,57,107,0.07), 0 2px 9px 0 rgba(62,57,107,0.06); + padding: 15px; +} +.monthly_join_resign_trend .axis path, +.monthly_join_resign_trend .axis line { + fill: none; + shape-rendering: crispEdges; +} +.monthly_join_resign_trend .line { + fill: none; + stroke-width: 3px; + +} +.monthly_join_resign_trend .area { + fill: steelblue; + opacity: 0.5; +} +.monthly_join_resign_trend .dot { + fill: steelblue; + stroke: steelblue; + stroke-width: 1.5px; +} +/*----------------------------------------*/ +.monthly_attrition_rate path { + stroke: #70cac1; + stroke-width: 2; + fill: none; +} +.monthly_attrition_rate .axis path, +.monthly_attrition_rate .axis line { + fill: none; + stroke: grey; + stroke-width: 1; + shape-rendering: crispEdges; +} +.monthly_attrition_rate circle{ + fill: #ffffff; + stroke: #44b7ac; + stroke-width: 1.5; +} +.monthly_attrition_rate .oh-card { + background: #fff; + transition: transform 0.2s ease, box-shadow 0.2s ease; + will-change: transform, box-shadow; + box-shadow: 0 10px 40px 0 rgba(62,57,107,0.07), 0 2px 9px 0 rgba(62,57,107,0.06); + padding: 15px; +} +.monthly_attrition_rate .oh-card:hover{ + transform: translateY(-2px) translateZ(0) !important; + box-shadow: 0 10px 10px 0 rgba(62, 57, 107, 0.12), 0 0 0 transparent !important; +} +.row.main-section { + margin-right: 0px; !important; +} +/* width */ +.hr_notification::-webkit-scrollbar { + width: 4px; +} +/* Track */ +.hr_notification::-webkit-scrollbar-track { + background: #f1f1f1; +} +/* Handle */ +.hr_notification::-webkit-scrollbar-thumb { + background: #5ebade; +} +/* Handle on hover */ +.hr_notification::-webkit-scrollbar-thumb:hover { + background: #598da1; +} +.oh-card-body { + display: flex; + justify-content: space-between; + align-items: center; +} +.oh-ribbon { + position: absolute; + left: -5px; top: -5px; + z-index: 1; + overflow: hidden; + width: 150px; height: 150px; + text-align: right; +} +.oh-ribbon span { + font-size: 10px; + font-weight: bold; + color: #FFF; + text-transform: uppercase; + text-align: center; + line-height: 20px; + transform: rotate(-45deg); + -webkit-transform: rotate(-45deg); + width: 200px; + display: block; + background: #79A70A; + background: linear-gradient(#2989d8 0%, #1e5799 100%); + box-shadow: 0 3px 10px -5px rgba(0, 0, 0, 1); + position: absolute; + top: 56px; + left: -35px; +} +.oh-ribbon span::before { + content: ""; + position: absolute; left: 0px; top: 100%; + z-index: -1; + border-left: 3px solid #1e5799; + border-right: 3px solid transparent; + border-bottom: 3px solid transparent; + border-top: 3px solid #1e5799; +} +.oh-ribbon span::after { + content: ""; + position: absolute; right: 0px; top: 100%; + z-index: -1; + border-left: 3px solid transparent; + border-right: 3px solid #1e5799; + border-bottom: 3px solid transparent; + border-top: 3px solid #1e5799; +} +.o_action_manager{ + overflow-y: scroll !important; + max-width:100%; + } + .hr_notification { + margin-top: 20px; +} +.stat_count{ + margin-top: -111px; + margin-left: 35px; + font-size: 48px; +} +.stat-head { + text-align: left !important; + font-weight: 300; + font-size: 18px; + margin-bottom: 25px; + margin-left: 24px; + width: 100%; + margin-top: 57px; +} diff --git a/all_in_one_pos_kit/static/src/dashboard/js/pos_dashboard.js b/all_in_one_pos_kit/static/src/dashboard/js/pos_dashboard.js new file mode 100644 index 000000000..52223caec --- /dev/null +++ b/all_in_one_pos_kit/static/src/dashboard/js/pos_dashboard.js @@ -0,0 +1,424 @@ +/** @odoo-module */ +const { Component } = owl; +import { registry } from "@web/core/registry"; +import { download } from "@web/core/network/download"; +import { useService } from "@web/core/utils/hooks"; +import { useRef, useState, onWillStart, onMounted } from "@odoo/owl"; +import { BlockUI } from "@web/core/ui/block_ui"; +const actionRegistry = registry.category("actions"); +import { uiService } from "@web/core/ui/ui_service"; +import { renderToElement } from "@web/core/utils/render"; + +// Extending components for adding purchase report class +class PosDashboard extends Component { + async setup() { + super.setup(...arguments); + this.orm = useService('orm'); + this.user = useService('user'); + this.canvas_1 = useRef('canvas_1'); + this.pos_sales = useRef('pos_sales'); + this.top_customer = useRef('top_customer'); + this.top_product_categories = useRef('top_product_categories'); + this.top_selling_product = useRef('top_selling_product'); + this.action = useService('action'); + this.state = useState({ + data: null, + payment_details : [], + top_salesperson : [], + selling_product : [], + total_sale : [], + total_order_count : [], + total_refund_count : [], + total_session : [], + today_refund_total : [], + today_sale : [], + }); + onWillStart(this.fetch_data) + onMounted(this.render_graphs) + } + async fetch_data() { + //fetch data and call rpc query to create tile. + self = this; + let data = await this.orm.call("pos.order", "get_refund_details", []) + this.state.total_sale = data['total_sale'], + this.state.total_order_count = data['total_order_count'] + this.state.total_refund_count = data['total_refund_count'] + this.state.total_session = data['total_session'] + this.state.today_refund_total = data['today_refund_total'] + this.state.today_sale = data['today_sale'] + + let data2 = await this.orm.call("pos.order", "get_details", []) + this.state.payment_details = data2['payment_details']; + this.state.top_salesperson = data2['salesperson']; + this.state.selling_product = data2['selling_product']; + + } + async pos_order_today(e) { + //Click function returns today's all pos order tree view. + let date = new Date(); + let yesterday = new Date(date.getTime()); + yesterday.setDate(date.getDate() - 1); + e.stopPropagation(); + e.preventDefault(); + let has_group = await this.user.hasGroup("hr.group_hr_user") + if (has_group) { + this.action.doAction({ + name: "Today Order", + type: 'ir.actions.act_window', + res_model: 'pos.order', + view_mode: 'tree,form,calendar', + view_type: 'form', + views: [ + [false, 'list'], + [false, 'form'] + ], + domain: [ + ['date_order', '<=', date], + ['date_order', '>=', yesterday] + ], + target: 'current' + }, { + on_reverse_breadcrumb: this.on_reverse_breadcrumb + }) + } + } + async pos_refund_orders(e) { + //Click function returns all refund pos order tree view. + e.stopPropagation(); + e.preventDefault(); + let has_group = await this.user.hasGroup('hr.group_hr_user') + if (has_group) { + this.action.doAction({ + name: "Refund Orders", + type: 'ir.actions.act_window', + res_model: 'pos.order', + view_mode: 'tree,form,calendar', + view_type: 'form', + views: [ + [false, 'list'], + [false, 'form'] + ], + domain: [ + ['amount_total', '<', 0.0] + ], + target: 'current' + }, { + on_reverse_breadcrumb: this.on_reverse_breadcrumb + }) + } + } + async pos_refund_today_orders(e) { + //Click function returns all today's refund pos order in tree view. + let date = new Date(); + let yesterday = new Date(date.getTime()); + yesterday.setDate(date.getDate() - 1); + e.stopPropagation(); + e.preventDefault(); + let has_group = await this.user.hasGroup('hr.group_hr_user') + if (has_group) { + this.action.doAction({ + name: "Refund Orders", + type: 'ir.actions.act_window', + res_model: 'pos.order', + view_mode: 'tree,form,calendar', + view_type: 'form', + views: [ + [false, 'list'], + [false, 'form'] + ], + domain: [ + ['amount_total', '<', 0.0], + ['date_order', '<=', date], + ['date_order', '>=', yesterday] + ], + target: 'current' + }, { + on_reverse_breadcrumb: this.on_reverse_breadcrumb + }) + } + } + async pos_order(e) { + //Click function returns all pos order in tree view. + e.stopPropagation(); + e.preventDefault(); + let has_group = await this.user.hasGroup('hr.group_hr_user') + if (has_group) { + this.action.doAction({ + name: "Total Order", + type: 'ir.actions.act_window', + res_model: 'pos.order', + view_mode: 'tree,form,calendar', + view_type: 'form', + views: [ + [false, 'list'], + [false, 'form'] + ], + target: 'current' + }, { + on_reverse_breadcrumb: this.on_reverse_breadcrumb + }) + } + } + async pos_session(e) { + //Click function returns all pos session in tree view. + e.stopPropagation(); + e.preventDefault(); + let has_group = await this.user.hasGroup('hr.group_hr_user') + if (has_group) { + this.action.doAction({ + name: "sessions", + type: 'ir.actions.act_window', + res_model: 'pos.session', + view_mode: 'tree,form,calendar', + view_type: 'form', + views: [ + [false, 'list'], + [false, 'form'] + ], + target: 'current' + }, { + on_reverse_breadcrumb: this.on_reverse_breadcrumb + }) + } + } + async onclick_pos_sales(events) { + // Function to add filter in pos sales report + let ctx = this.canvas_1.el; + console.log('ctx',ctx) + let arrays = await this.orm.call("pos.order","get_department", [this.pos_sales.el.value]) + console.log('arrays',arrays) + if (window.myCharts != undefined) + window.myCharts.destroy(); + window.myCharts = new Chart(ctx, { + type: "bar", + data: { + labels: arrays[1], + datasets: [{ + label: arrays[2], + data: arrays[0], + backgroundColor: [ + "rgba(255, 99, 132,1)", + "rgba(54, 162, 235,1)", + "rgba(75, 192, 192,1)", + "rgba(153, 102, 255,1)", + "rgba(10,20,30,1)" + ], + borderColor: [ + "rgba(255, 99, 132, 0.2)", + "rgba(54, 162, 235, 0.2)", + "rgba(75, 192, 192, 0.2)", + "rgba(153, 102, 255, 0.2)", + "rgba(10,20,30,0.3)" + ], + borderWidth: 1 + }, ] + }, + options: { + responsive: true, + title: { + display: true, + position: "top", + text: "SALE DETAILS", + fontSize: 18, + fontColor: "#111" + }, + legend: { + display: true, + position: "bottom", + labels: { + fontColor: "#333", + fontSize: 16 + } + }, + scales: { + yAxes: [{ + ticks: { + min: 0 + } + }] + } + } + }); + } + + async render_graphs() { + //Add function to load in dashboard. + await this.render_top_customer_graph(); + await this.render_top_product_graph(); + await this.render_product_category_graph(); + } + async render_top_customer_graph() { + //Function to create top customers chart + let ctx = this.top_customer.el; + console.log('ctxctx',ctx) + let arrays = await this.orm.call('pos.order','get_the_top_customer',[]) + console.log('arraysarrays',arrays) + console.log('arrays[1]',arrays[1]) + console.log('arrays[0]',arrays[0]) + let chart = new Chart(ctx, { + //create Chart class object + type: "pie", + data: { + labels: arrays[1], + datasets: [{ + label: "", + data: arrays[0], + backgroundColor: [ + "rgb(148, 22, 227)", + "rgba(54, 162, 235)", + "rgba(75, 192, 192)", + "rgba(153, 102, 255)", + "rgba(10,20,30)" + ], + borderColor: [ + "rgba(255, 99, 132,)", + "rgba(54, 162, 235,)", + "rgba(75, 192, 192,)", + "rgba(153, 102, 255,)", + "rgba(10,20,30,)" + ], + borderWidth: 1 + }, + ] + }, + options: { //options + responsive: true, + title: { + display: true, + position: "top", + text: " Top Customer", + fontSize: 18, + fontColor: "#111" + }, + legend: { + display: true, + position: "bottom", + labels: { + fontColor: "#333", + fontSize: 16 + } + }, + scales: { + yAxes: [{ + ticks: { + min: 0 + } + }] + } + } + }); + } + async render_top_product_graph() { + //Function to create top product chart. + let ctx = this.top_selling_product.el; + let arrays = await this.orm.call('pos.order','get_the_top_products',[]) + let chart = new Chart(ctx, { //create Chart class object + type: "horizontalBar", + data: { + labels: arrays[1], + datasets: [{ + label: "Quantity", + data: arrays[0], + backgroundColor: [ + "rgba(255, 99, 132,1)", + "rgba(54, 162, 235,1)", + "rgba(75, 192, 192,1)", + "rgba(153, 102, 255,1)", + "rgba(10,20,30,1)" + ], + borderColor: [ + "rgba(255, 99, 132, 0.2)", + "rgba(54, 162, 235, 0.2)", + "rgba(75, 192, 192, 0.2)", + "rgba(153, 102, 255, 0.2)", + "rgba(10,20,30,0.3)" + ], + borderWidth: 1 + }, ] + }, + options: { //options + responsive: true, + title: { + display: true, + position: "top", + text: " Top products", + fontSize: 18, + fontColor: "#111" + }, + legend: { + display: true, + position: "bottom", + labels: { + fontColor: "#333", + fontSize: 16 + } + }, + scales: { + yAxes: [{ + ticks: { + min: 0 + } + }] + } + } + }); + } + async render_product_category_graph() { + //Function to create top categories chart + var ctx = this.top_product_categories.el; + let arrays = await this.orm.call('pos.order','get_the_top_categories',[]) + let chart = new Chart(ctx, { + //create Chart class object + type: "horizontalBar", + data: { + labels: arrays[1], + datasets: [{ + label: "Quantity", + data: arrays[0], + backgroundColor: [ + "rgba(255, 99, 132,1)", + "rgba(54, 162, 235,1)", + "rgba(75, 192, 192,1)", + "rgba(153, 102, 255,1)", + "rgba(10,20,30,1)" + ], + borderColor: [ + "rgba(255, 99, 132, 0.2)", + "rgba(54, 162, 235, 0.2)", + "rgba(75, 192, 192, 0.2)", + "rgba(153, 102, 255, 0.2)", + "rgba(10,20,30,0.3)" + ], + borderWidth: 1 + }, ] + }, + options: { + responsive: true, + title: { + display: true, + position: "top", + text: " Top product categories", + fontSize: 18, + fontColor: "#111" + }, + legend: { + display: true, + position: "bottom", + labels: { + fontColor: "#333", + fontSize: 16 + } + }, + scales: { + yAxes: [{ + ticks: { + min: 0 + } + }] + } + } + }); + } + } + PosDashboard.template = "PosDashboard"; +actionRegistry.add("pos_dashboard", PosDashboard); \ No newline at end of file diff --git a/all_in_one_pos_kit/static/src/dashboard/xml/pos_dashboard.xml b/all_in_one_pos_kit/static/src/dashboard/xml/pos_dashboard.xml new file mode 100644 index 000000000..d1e6d495c --- /dev/null +++ b/all_in_one_pos_kit/static/src/dashboard/xml/pos_dashboard.xml @@ -0,0 +1,329 @@ + + + + +
+
+
+
+ +
+ +
+
+
+
+
+
+
Today Orders
+
+
+
+
+
+
+ +
+
+
+
+
+
+
Total Orders
+
+
+
+
+
+
+ +
+
+
+
+
+
+
Total Sales
+
+
+
+
+
+
+ +
+
+
+
+
+
+
Sessions
+
+
+
+
+
+
+ +
+
+
+
+
+
+
Total Refund Orders
+
+
+
+
+
+
+ +
+
+
+
+
+
+
Today Refund Order
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+ +

SALE REPORT

+
+
+
+ +
+
+
+
+
+

+

+ +
+
+
+
+
+
+ +
+
+
+

+

+
+ +
+
+ +
+
+
+

+

+
+ +
+
+ +
+
+
+

+

+
+ +
+
+
+
+ +
+
+ +
+
+
+ Sale by Salesperson +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + +
Name   ordersAmount
+

+ +

+
+

+ +

+
+

+ +

+
+
+
+
+
+ +
+
+
+ Payment Method +
+
+
+
+
+
+ + + + + + + + + + + + + + + +
Payment Method   Amount
+

+ +

+
+

+ +

+
+
+
+
+
+ +
+
+
+ Session Status +
+
+
+
+
+
+ + + + + + + + + + + + + + + +
Session   Status
+

+ +

+
+

+ +

+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/all_in_one_pos_kit/static/src/delete_order_line/js/clear_button.js b/all_in_one_pos_kit/static/src/delete_order_line/js/clear_button.js new file mode 100644 index 000000000..3623b50ea --- /dev/null +++ b/all_in_one_pos_kit/static/src/delete_order_line/js/clear_button.js @@ -0,0 +1,52 @@ +/**@odoo-module **/ +import { _t } from "@web/core/l10n/translation"; +import { ProductScreen } from "@point_of_sale/app/screens/product_screen/product_screen"; +import { useService } from "@web/core/utils/hooks"; +import { Component } from "@odoo/owl"; +import { usePos } from "@point_of_sale/app/store/pos_hook"; +import { ConfirmPopup } from "@point_of_sale/app/utils/confirm_popup/confirm_popup"; +/** + * Represents a component to delete all order lines in the Point of Sale. + * @extends Component + */ +export class DeleteOrderLines extends Component { + static template = "pos_delete_orderline.OrderLineClearALL"; + /** + * Set up the DeleteOrderLines component. + * @override + */ + setup() { + this.pos = usePos(); + this.popup = useService("popup"); + this.notification = useService("pos_notification"); + } + /** + * Handle the click event to confirm and delete all order lines. + * @async + */ + async onClick() { + var order = this.pos.get_order(); + var lines = order.get_orderlines(); + if (lines.length) { + await this.popup.add(ConfirmPopup, { + title: 'Clear Orders?', + body: 'Are you sure you want to delete all orders from the cart?', + }).then(({confirmed}) => { + if (confirmed == true) { + lines.filter(line => line.get_product()) + .forEach(line => order.removeOrderline(line)); + }else { + return false; + } + }) + }else{ + this.notification.add(_t("No Items to remove."), 3000); + } + } +} +/** + * Adds the DeleteOrderLines component as a control button to the ProductScreen. + */ +ProductScreen.addControlButton({ + component: DeleteOrderLines, +}); diff --git a/all_in_one_pos_kit/static/src/delete_order_line/js/clear_order_line.js b/all_in_one_pos_kit/static/src/delete_order_line/js/clear_order_line.js new file mode 100644 index 000000000..3c59b605f --- /dev/null +++ b/all_in_one_pos_kit/static/src/delete_order_line/js/clear_order_line.js @@ -0,0 +1,24 @@ +/** @odoo-module */ +import { useService } from "@web/core/utils/hooks"; +import { Orderline } from "@point_of_sale/app/generic_components/orderline/orderline"; +import { patch } from "@web/core/utils/patch"; +import { usePos } from "@point_of_sale/app/store/pos_hook"; +/** + * Enhances the Orderline component with additional functionality. + * @extends Orderline + */ +patch(Orderline.prototype, { + setup() { + super.setup(); + this.pos = usePos(); + this.numberBuffer = useService("number_buffer"); + }, + /** + * Handle the clear button click event by sending Backspace key twice to the number buffer. + * @param {Event} ev - The click event. + */ + async clear_button_fun(ev) { + this.numberBuffer.sendKey('Backspace'); + this.numberBuffer.sendKey('Backspace'); + } +}) diff --git a/all_in_one_pos_kit/static/src/delete_order_line/xml/clear_button_templates.xml b/all_in_one_pos_kit/static/src/delete_order_line/xml/clear_button_templates.xml new file mode 100644 index 000000000..97d23a569 --- /dev/null +++ b/all_in_one_pos_kit/static/src/delete_order_line/xml/clear_button_templates.xml @@ -0,0 +1,12 @@ + + + + + + + diff --git a/all_in_one_pos_kit/static/src/delete_order_line/xml/clear_order_line_templates.xml b/all_in_one_pos_kit/static/src/delete_order_line/xml/clear_order_line_templates.xml new file mode 100644 index 000000000..443d0cffc --- /dev/null +++ b/all_in_one_pos_kit/static/src/delete_order_line/xml/clear_order_line_templates.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/all_in_one_pos_kit/static/src/exchange_product/js/all_order_screen.js b/all_in_one_pos_kit/static/src/exchange_product/js/all_order_screen.js new file mode 100644 index 000000000..e1be59e4d --- /dev/null +++ b/all_in_one_pos_kit/static/src/exchange_product/js/all_order_screen.js @@ -0,0 +1,43 @@ +/** @odoo-module */ +import { ProductScreen } from "@point_of_sale/app/screens/product_screen/product_screen"; +import { Component } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { useService } from "@web/core/utils/hooks"; +import { ExchangeOrder } from "./exchange_order"; + +export class CustomOrderScreen extends Component { //Extended the PosComponent to add button popup function + static template = "CustomOrdrScreen" + setup() { + //Setup method called when the component is mounted. + super.setup(); + console.log('knnn',this.props) + this.orm = useService("orm"); + this.pos = useService('pos') + this.state = { + order: this.props.orders, + pos: this.env.pos + }; + this.popup = useService("popup"); + } + back() { + this.env.services.pos.showScreen('ProductScreen'); + } + async _onClickOrder(order, pos) { + //Function to show popup to show exchange product it that pos order. + console.log('ooo',this) + if (order.exchange == true) { + this.pos.showPopup('ErrorPopup', { + title: 'Exchange order', + body: 'Already created the Exchange order' + }); + } else { + let value = await this.orm.call("pos.order.line", "get_product_details",[order.lines]); + await this.popup.add(ExchangeOrder, { + 'order_line': value, + 'pos': pos, + 'order_id': order.id + }); + } + } + }; +registry.category("pos_screens").add("CustomOrderScreen", CustomOrderScreen); diff --git a/all_in_one_pos_kit/static/src/exchange_product/js/exchange_order.js b/all_in_one_pos_kit/static/src/exchange_product/js/exchange_order.js new file mode 100644 index 000000000..345d69d49 --- /dev/null +++ b/all_in_one_pos_kit/static/src/exchange_product/js/exchange_order.js @@ -0,0 +1,36 @@ +/** @odoo-module **/ +import { AbstractAwaitablePopup } from "@point_of_sale/app/popup/abstract_awaitable_popup"; +import { useState } from "@odoo/owl"; +import { useService } from "@web/core/utils/hooks"; + /** + * Represents an Exchange Order popup in a point of sale system. + * This class extends the AbstractAwaitablePopup class and provides functionality + * for confirming an exchange order. The exchange order allows products to be + * added or removed from the current order based on the provided order line. + * @extends AbstractAwaitablePopup + */ + export class ExchangeOrder extends AbstractAwaitablePopup { + static template = 'ExchangeOrder'; + setup() { + // Super the setup function and set props.line and state.line + super.setup(); + this.orm = useService("orm"); + this.props.line = JSON.parse(JSON.stringify(this.env.services.pos.pos_order_line) || '{}') + this.state = useState({ + line: JSON.parse(JSON.stringify(this.env.services.pos.pos_order_line) || '{}'), + pos: this.env.services.pos + }); + } + async confirm() { + // Iterate over each line in props.line and add or remove products from the CurrentOrder + for(var i = 0; i < this.props.order_line.length; i++){ + this.env.services.pos.get_order().add_product(this.env.services.pos.db.get_product_by_id(this.props.order_line[i].product_id), {quantity: - this.props.order_line[i].qty}) + } + await this.orm.write("pos.order", [this.props.order_id], { + exchange: true, + }); + this.env.services.pos.showScreen('ProductScreen'); + // Show the 'ProductScreen' and invoke the confirm method of the parent class + super.confirm(); + } + } diff --git a/all_in_one_pos_kit/static/src/exchange_product/js/models.js b/all_in_one_pos_kit/static/src/exchange_product/js/models.js new file mode 100644 index 000000000..01d0f48ff --- /dev/null +++ b/all_in_one_pos_kit/static/src/exchange_product/js/models.js @@ -0,0 +1,10 @@ +/** @odoo-module */ +import { patch } from "@web/core/utils/patch"; +import { PosStore } from "@point_of_sale/app/store/pos_store"; +patch(PosStore.prototype, { + async _processData(loadedData) { + await super._processData(...arguments); + this.pos_orders = loadedData['pos.order']; + this.pos_order_lines = loadedData['pos.order.line']; + } +}) diff --git a/all_in_one_pos_kit/static/src/exchange_product/js/order_button.js b/all_in_one_pos_kit/static/src/exchange_product/js/order_button.js new file mode 100644 index 000000000..a64ea6211 --- /dev/null +++ b/all_in_one_pos_kit/static/src/exchange_product/js/order_button.js @@ -0,0 +1,22 @@ +/** @odoo-module **/ +import { ProductScreen } from "@point_of_sale/app/screens/product_screen/product_screen"; +import { _t } from "@web/core/l10n/translation"; +import {ErrorPopup} from "@point_of_sale/app/errors/popups/error_popup"; +import {ExchangeOrder} from "./exchange_order"; + +class OrderLineALLButton extends ProductScreen { + static template = "OrderLineALL"; + setup() { + super.setup(); + } + async onClick() { +// Order line button Onclick() + await this.pos.showScreen('CustomOrderScreen', { + orders: this.env.services.pos.pos_orders, + pos: this.env.services.pos + }); + } +} +ProductScreen.addControlButton({ + component: OrderLineALLButton, +}); diff --git a/all_in_one_pos_kit/static/src/exchange_product/scss/pos.scss b/all_in_one_pos_kit/static/src/exchange_product/scss/pos.scss new file mode 100644 index 000000000..12b50192d --- /dev/null +++ b/all_in_one_pos_kit/static/src/exchange_product/scss/pos.scss @@ -0,0 +1,34 @@ +.order-list{ + font-size: 16px; + width: 100%; + +} +.order-list thead{ + font-size: 23px; + background-color: lightslategrey; + color: white; +} +.order-list th, +.order-list td { + padding: 12px; +} +.order-list tbody :hover{ + background-color: silver; +} +exchange-list{ + font-size: 16px; + width: 100%; +} +.exchange-list th, +.exchange-list td { + padding: 10px; +} +.clientlist-screen{ + background-color:white; +} +.back { + background-color: revert; +} +.exchange-header{ + padding: 10px; +} \ No newline at end of file diff --git a/all_in_one_pos_kit/static/src/exchange_product/xml/all_order_screen.xml b/all_in_one_pos_kit/static/src/exchange_product/xml/all_order_screen.xml new file mode 100644 index 000000000..c6ee82d1e --- /dev/null +++ b/all_in_one_pos_kit/static/src/exchange_product/xml/all_order_screen.xml @@ -0,0 +1,57 @@ + + + + +
+
+
+ + +
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + +
Order ReferenceReceipt ReferenceCustomerOrder Date
+ + + +
+
+
+
+
+
+
+
+
+
diff --git a/all_in_one_pos_kit/static/src/exchange_product/xml/exchange_order.xml b/all_in_one_pos_kit/static/src/exchange_product/xml/exchange_order.xml new file mode 100644 index 000000000..338fdf349 --- /dev/null +++ b/all_in_one_pos_kit/static/src/exchange_product/xml/exchange_order.xml @@ -0,0 +1,38 @@ + + + + + + + diff --git a/all_in_one_pos_kit/static/src/exchange_product/xml/order_button.xml b/all_in_one_pos_kit/static/src/exchange_product/xml/order_button.xml new file mode 100644 index 000000000..f9bea6e56 --- /dev/null +++ b/all_in_one_pos_kit/static/src/exchange_product/xml/order_button.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/all_in_one_pos_kit/static/src/mass_edit/js/pos_mass_edit_button.js b/all_in_one_pos_kit/static/src/mass_edit/js/pos_mass_edit_button.js new file mode 100644 index 000000000..bdacb59bc --- /dev/null +++ b/all_in_one_pos_kit/static/src/mass_edit/js/pos_mass_edit_button.js @@ -0,0 +1,35 @@ +/** @odoo-module **/ +import { ProductScreen } from "@point_of_sale/app/screens/product_screen/product_screen"; +import { _t } from "@web/core/l10n/translation"; +import {ErrorPopup} from "@point_of_sale/app/errors/popups/error_popup"; +import {MassEditPopup} from "./pos_mass_edit_popup"; + +class MassEditButton extends ProductScreen { + static template = "SaleOrderButton"; + setup() { + super.setup(); + } + async onClick() { +// Order line button Onclick() + var order = this.pos.get_order(); + var order_line = order.get_orderlines(); + + if (!order_line.length){ +// Popup if no product in pos order line + await this.popup.add(ErrorPopup, { + title: _t("Order is Empty"), + body: _t("You need to add product."), + }); + } + else { + const { confirmed } = await this.popup.add(MassEditPopup, { + title: _t("Edit Order Line"), + body: order_line + }); + } + } +} +ProductScreen.addControlButton({ + component: MassEditButton, + position:['after','OrderlineCustomerNoteButton'] +}); diff --git a/all_in_one_pos_kit/static/src/mass_edit/js/pos_mass_edit_popup.js b/all_in_one_pos_kit/static/src/mass_edit/js/pos_mass_edit_popup.js new file mode 100644 index 000000000..6d8db1cd5 --- /dev/null +++ b/all_in_one_pos_kit/static/src/mass_edit/js/pos_mass_edit_popup.js @@ -0,0 +1,21 @@ +/** @odoo-module **/ +import { AbstractAwaitablePopup } from "@point_of_sale/app/popup/abstract_awaitable_popup"; + +export class MassEditPopup extends AbstractAwaitablePopup { + static template = "MassEditPopup"; + static defaultProps = { + confirm: "Confirm", + cancel: "Cancel", + }; + async confirm(){ +// function for confirm button inside popup + window.location.reload(); + } + sendInput(key) { + for(var line in this.props.body) { + if (this.props.body[line].id == key){ + this.props.body[line].quantity = 0 + } + } + } +} diff --git a/all_in_one_pos_kit/static/src/mass_edit/xml/pos_mass_edit_button.xml b/all_in_one_pos_kit/static/src/mass_edit/xml/pos_mass_edit_button.xml new file mode 100644 index 000000000..23f96daf8 --- /dev/null +++ b/all_in_one_pos_kit/static/src/mass_edit/xml/pos_mass_edit_button.xml @@ -0,0 +1,12 @@ + + + + + +
+ + Edit Order Line +
+
+
diff --git a/all_in_one_pos_kit/static/src/mass_edit/xml/pos_mass_edit_popup.xml b/all_in_one_pos_kit/static/src/mass_edit/xml/pos_mass_edit_popup.xml new file mode 100644 index 000000000..74dec04ca --- /dev/null +++ b/all_in_one_pos_kit/static/src/mass_edit/xml/pos_mass_edit_popup.xml @@ -0,0 +1,79 @@ + + + + + + + diff --git a/all_in_one_pos_kit/static/src/multi_barcode/js/PosStore.js b/all_in_one_pos_kit/static/src/multi_barcode/js/PosStore.js new file mode 100755 index 000000000..2d3c72e1b --- /dev/null +++ b/all_in_one_pos_kit/static/src/multi_barcode/js/PosStore.js @@ -0,0 +1,13 @@ +/** @odoo-module */ + +import { PosStore } from "@point_of_sale/app/store/pos_store"; +import { patch } from "@web/core/utils/patch"; + +patch(PosStore.prototype, { + async _processData(loadedData) { + //@override + await super._processData(...arguments); + this.product_by_lot = loadedData['multi.barcode.products']; + this.product_by_lot_id = loadedData['multi_barcode']; + } +}); diff --git a/all_in_one_pos_kit/static/src/multi_barcode/js/ProductWidget.js b/all_in_one_pos_kit/static/src/multi_barcode/js/ProductWidget.js new file mode 100755 index 000000000..fc79953d0 --- /dev/null +++ b/all_in_one_pos_kit/static/src/multi_barcode/js/ProductWidget.js @@ -0,0 +1,40 @@ +/** @odoo-module */ + +import { patch } from "@web/core/utils/patch"; +import { PosDB } from "@point_of_sale/app/store/db"; +import { unaccent } from "@web/core/utils/strings"; + +patch(PosDB.prototype, { + search_product_in_category: function(category_id, query){ + var old_query = query + try { + // eslint-disable-next-line no-useless-escape + query = query.replace(/[\[\]\(\)\+\*\?\.\-\!\&\^\$\|\~\_\{\}\:\,\\\/]/g, "."); + query = query.replace(/ /g, ".+"); + var re = RegExp("([0-9]+):.*?" + unaccent(query), "gi"); + } catch { + return []; + } + var results = []; + for(var i = 0; i < this.limit; i++){ + var r = re.exec(this.category_search_string[category_id]); + if (r) { + var id = Number(r[1]); + const product = this.get_product_by_id(id); + if (!this.shouldAddProduct(product, results)) { + continue; + } + results.push(product); + }else if(this.product_by_lot_id[old_query]){ + const product = this.get_product_by_id(this.product_by_lot_id[old_query]); + if (!this.shouldAddProduct(product, results)) continue; + if(!results.includes(product)){ + results.push(product); + } + }else{ + break; + } + } + return results; + } +}); diff --git a/all_in_one_pos_kit/static/src/multi_barcode/js/pos_scan.js b/all_in_one_pos_kit/static/src/multi_barcode/js/pos_scan.js new file mode 100755 index 000000000..0c6485e59 --- /dev/null +++ b/all_in_one_pos_kit/static/src/multi_barcode/js/pos_scan.js @@ -0,0 +1,68 @@ +/** @odoo-module */ + +import { patch } from "@web/core/utils/patch"; +import { PosDB } from "@point_of_sale/app/store/db"; +import { unaccent } from "@web/core/utils/strings"; +import { jsonrpc } from "@web/core/network/rpc_service"; + +patch(PosDB.prototype, { + add_products(products) { + //Extends the add_products method of the PosDB class to include additional functionality for handling product barcodes. + var stored_categories = this.product_by_category_id; + if (!(products instanceof Array)) { + products = [products]; + } + for (var i = 0, len = products.length; i < len; i++) { + var product = products[i]; + if (product.id in this.product_by_id) { + continue; + } + if (product.available_in_pos) { + var search_string = unaccent(this._product_search_string(product)); + const all_categ_ids = product.pos_categ_ids.length + ? product.pos_categ_ids + : [this.root_category_id]; + product.product_tmpl_id = product.product_tmpl_id[0]; + for (const categ_id of all_categ_ids) { + if (!stored_categories[categ_id]) { + stored_categories[categ_id] = []; + } + stored_categories[categ_id].push(product.id); + if (this.category_search_string[categ_id] === undefined) { + this.category_search_string[categ_id] = ""; + } + this.category_search_string[categ_id] += search_string; + var ancestors = this.get_category_ancestors_ids(categ_id) || []; + for (var j = 0, jlen = ancestors.length; j < jlen; j++) { + var ancestor = ancestors[j]; + if (!stored_categories[ancestor]) { + stored_categories[ancestor] = []; + } + stored_categories[ancestor].push(product.id); + + if (this.category_search_string[ancestor] === undefined) { + this.category_search_string[ancestor] = ""; + } + this.category_search_string[ancestor] += search_string; + } + } + } + this.product_by_id[product.id] = product; + if (product.barcode && product.active) { + this.product_by_barcode[product.barcode] = product; + } + for (var t = 0; t < product.product_multi_barcodes_ids.length; t++) { + var self = this; + jsonrpc('/web/dataset/call_kw/multi.barcode.products/get_barcode_val', { + model: 'multi.barcode.products', + method: 'get_barcode_val', + args: [product.product_multi_barcodes_ids[t], product.id], + kwargs: {}, + }).then(function(barcode_val) { + self.product_by_barcode[barcode_val[0]] = self.product_by_id[barcode_val[1]]; + }); + } + } + return super.add_products(products); + }, +}); \ No newline at end of file diff --git a/all_in_one_pos_kit/static/src/order_item_count/js/pos_receipt.js b/all_in_one_pos_kit/static/src/order_item_count/js/pos_receipt.js new file mode 100644 index 000000000..6c8398451 --- /dev/null +++ b/all_in_one_pos_kit/static/src/order_item_count/js/pos_receipt.js @@ -0,0 +1,20 @@ +odoo.define('all_in_one_pos_kit.OrderReceipt', function(require) { + 'use strict'; + var { Order } = require('point_of_sale.models'); + var Registries = require('point_of_sale.Registries'); + // Extends the Order model to include additional information in the exported data for receipt. + const OrderLineCount = (Order) => class CustomOrder extends Order { + export_for_printing() { + //Overrides the export_for_printing() method to include the count and sum of order lines. @returns {Object} - The modified order data for receipt. + var result = super.export_for_printing(...arguments); + result.count = this.orderlines.length; + var sum = 0; + this.orderlines.forEach(function(t) { + sum += t.quantity; + }) + result.sum = sum + return result; + } + } + Registries.Model.extend(Order, OrderLineCount); +}); diff --git a/all_in_one_pos_kit/static/src/order_item_count/js/product_screen.js b/all_in_one_pos_kit/static/src/order_item_count/js/product_screen.js new file mode 100644 index 000000000..79093db79 --- /dev/null +++ b/all_in_one_pos_kit/static/src/order_item_count/js/product_screen.js @@ -0,0 +1,38 @@ +odoo.define('all_in_one_pos_kit.ItemsCount', function(require) { + 'use strict'; + const PosComponent = require('point_of_sale.PosComponent'); + const ProductScreen = require('point_of_sale.ProductScreen'); + const Registries = require('point_of_sale.Registries'); + class ItemsCount extends PosComponent { + setup() { + super.setup(); + } + get_items_count() { + /** + * Get the count of order lines in the current order. + * @returns {number} The number of order lines in the current order. + */ + return this.env.pos.get_order().orderlines.length + } + get_items_qty() { + /** + * Get the total quantity of items in the current order. + * @returns {number} The total quantity of items in the current order. + */ + var sum = 0; + this.env.pos.get_order().orderlines.forEach(function(t) { + sum += t.quantity; + }) + return sum + } + } + ItemsCount.template = 'ItemsCount'; + ProductScreen.addControlButton({ + component: ItemsCount, + condition: function() { + return this.env.pos; + }, + }); + Registries.Component.add(ItemsCount); + return ItemsCount; +}); diff --git a/all_in_one_pos_kit/static/src/order_item_count/xml/pos_items_count.xml b/all_in_one_pos_kit/static/src/order_item_count/xml/pos_items_count.xml new file mode 100644 index 000000000..faaeace27 --- /dev/null +++ b/all_in_one_pos_kit/static/src/order_item_count/xml/pos_items_count.xml @@ -0,0 +1,23 @@ + + + + +
+
+ + + + Total Items: + + +
+
+ +
+ + Total Qty: + + +
+
+
diff --git a/all_in_one_pos_kit/static/src/order_item_count/xml/pos_receipt.xml b/all_in_one_pos_kit/static/src/order_item_count/xml/pos_receipt.xml new file mode 100644 index 000000000..6f4ad47c2 --- /dev/null +++ b/all_in_one_pos_kit/static/src/order_item_count/xml/pos_receipt.xml @@ -0,0 +1,23 @@ + + + + + +
+ Total Items + + + + +
+
+ Total Count + + + +
+
+
+
diff --git a/all_in_one_pos_kit/static/src/order_line_image/css/order_line_image.css b/all_in_one_pos_kit/static/src/order_line_image/css/order_line_image.css new file mode 100755 index 000000000..36f613a30 --- /dev/null +++ b/all_in_one_pos_kit/static/src/order_line_image/css/order_line_image.css @@ -0,0 +1,15 @@ +.pos .order .orderline .pos_product_image{ + float:left; + margin:auto; +} +.pos .order { + padding-top: 0px !important; +} +.pos .order .orderline .product-name { + padding-top: 11px !important; +} +.pos .order .orderline .info-list { + color: #888; + margin-left: 10px; + padding-bottom: 19px; +} diff --git a/all_in_one_pos_kit/static/src/order_line_image/js/pos_order_line.js b/all_in_one_pos_kit/static/src/order_line_image/js/pos_order_line.js new file mode 100644 index 000000000..003f8aa91 --- /dev/null +++ b/all_in_one_pos_kit/static/src/order_line_image/js/pos_order_line.js @@ -0,0 +1,14 @@ +/** @odoo-module */ +import { Order, Orderline } from "@point_of_sale/app/store/models"; +import { patch } from "@web/core/utils/patch"; + +patch(Orderline.prototype, { + getDisplayData() { + /**Add the product id to update the image**/ + return { + ...super.getDisplayData(), + product_id: this.get_product().id, + + }; + } +}); diff --git a/all_in_one_pos_kit/static/src/order_line_image/xml/pos_order_line.xml b/all_in_one_pos_kit/static/src/order_line_image/xml/pos_order_line.xml new file mode 100755 index 000000000..6f657f624 --- /dev/null +++ b/all_in_one_pos_kit/static/src/order_line_image/xml/pos_order_line.xml @@ -0,0 +1,13 @@ + + diff --git a/all_in_one_pos_kit/static/src/pos_auto_lot/js/auto_lot.js b/all_in_one_pos_kit/static/src/pos_auto_lot/js/auto_lot.js new file mode 100644 index 000000000..c03943b48 --- /dev/null +++ b/all_in_one_pos_kit/static/src/pos_auto_lot/js/auto_lot.js @@ -0,0 +1,81 @@ +odoo.define("all_in_one_pos_kit.auto_lot", function (require) { + "use strict"; + const ProductScreen = require("point_of_sale.ProductScreen"); + const Registries = require("point_of_sale.Registries"); + var rpc = require('web.rpc'); + /** + * Extends the ProductScreen component to add automatic lot handling functionality. + * This component overrides the "_getAddProductOptions" method to include automatic lot handling for products + * with tracking enabled. It retrieves available lots for the product and allows the user to select the lot + * during the product addition process. + */ + const PosLotSaleProductScreen = (ProductScreen) => class extends ProductScreen { + async _getAddProductOptions(product, base_code) {//Override the `_getAddProductOptions` method to include lot information retrieval and handling. + let price_extra = 0.0; + let draftPackLotLines, weight, description, packLotLinesToEdit; + if (_.some(product.attribute_line_ids, (id) => id in this.env.pos.attributes_by_ptal_id)) { + let attributes = _.map(product.attribute_line_ids, (id) => this.env.pos.attributes_by_ptal_id[id]) + .filter((attr) => attr !== undefined); + let { confirmed, payload } = await this.showPopup('ProductConfiguratorPopup', { + product: product, + attributes: attributes, + }); + if (confirmed) { + description = payload.selected_attributes.join(', '); + price_extra += payload.price_extra; + } else { + return; + } + } + // Gather lot information if required. + if (['serial', 'lot'].includes(product.tracking) && (this.env.pos.picking_type.use_create_lots || this.env.pos.picking_type.use_existing_lots)) { + const isAllowOnlyOneLot = product.isAllowOnlyOneLot(); + if (isAllowOnlyOneLot) { + packLotLinesToEdit = []; + } else { + const orderline = this.currentOrder + .get_orderlines() + .filter(line => !line.get_discount()) + .find(line => line.product.id === product.id); + if (orderline) { + packLotLinesToEdit = orderline.getPackLotLinesToEdit(); + } else { + packLotLinesToEdit = []; + } + } + await rpc.query({//RPC to get value from the model stock_lot + model: "stock.lot", + method: "get_available_lots_for_pos", + args: [product.id], + }).then(function (result) { + const modifiedPackLotLines = result[0]; + const newPackLotLines = result.map(item => ({ lot_name: result[0] })); + draftPackLotLines = { modifiedPackLotLines, newPackLotLines }; + }); + } + // Take the weight if necessary. + if (product.to_weight && this.env.pos.config.iface_electronic_scale) { + // Show the ScaleScreen to weigh the product. + if (this.isScaleAvailable) { + const { confirmed, payload } = await this.showTempScreen('ScaleScreen', { + product, + }); + if (confirmed) { + weight = payload.weight; + } else { + // do not add the product; + return; + } + } else { + await this._onScaleNotAvailable(); + } + } + if (base_code && this.env.pos.db.product_packaging_by_barcode[base_code.code]) { + weight = this.env.pos.db.product_packaging_by_barcode[base_code.code].qty; + } + return { draftPackLotLines, quantity: weight, description, price_extra }; + } + }; + Registries.Component.extend(ProductScreen, PosLotSaleProductScreen); + return ProductScreen; +}); diff --git a/all_in_one_pos_kit/static/src/pos_auto_lot/js/new.js b/all_in_one_pos_kit/static/src/pos_auto_lot/js/new.js new file mode 100644 index 000000000..1b1674c3a --- /dev/null +++ b/all_in_one_pos_kit/static/src/pos_auto_lot/js/new.js @@ -0,0 +1,2 @@ +/** @odoo-module **/ +import { ProductScreen } from "@point_of_sale/app/screens/product_screen/product_screen"; diff --git a/all_in_one_pos_kit/static/src/pos_logo/xml/navbar_logo.xml b/all_in_one_pos_kit/static/src/pos_logo/xml/navbar_logo.xml new file mode 100755 index 000000000..c36c2cebb --- /dev/null +++ b/all_in_one_pos_kit/static/src/pos_logo/xml/navbar_logo.xml @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/all_in_one_pos_kit/static/src/pos_logo/xml/receipt_logo.xml b/all_in_one_pos_kit/static/src/pos_logo/xml/receipt_logo.xml new file mode 100755 index 000000000..d6a797b79 --- /dev/null +++ b/all_in_one_pos_kit/static/src/pos_logo/xml/receipt_logo.xml @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/all_in_one_pos_kit/static/src/pos_mrp_order/js/payment_screen.js b/all_in_one_pos_kit/static/src/pos_mrp_order/js/payment_screen.js new file mode 100644 index 000000000..b66137ced --- /dev/null +++ b/all_in_one_pos_kit/static/src/pos_mrp_order/js/payment_screen.js @@ -0,0 +1,50 @@ +/** @odoo-module **/ +import { PaymentScreen } from "@point_of_sale/app/screens/payment_screen/payment_screen"; +import { patch } from "@web/core/utils/patch"; +// Patch the PaymentScreen to create manufacturing orders when the order is validated +patch(PaymentScreen.prototype, { + // Creating manufacturing orders + createMRP() { + const order = this.currentOrder; + var order_line = order.get_orderlines() + var due = order.get_due(); + for (var i in order_line){ + var list_product = [] + if (order_line[i].product){ + if (order_line[i].quantity>0){ + var product_dict = { + 'id': order_line[i].product.id, + 'qty': order_line[i].quantity, + 'product_tmpl_id': order_line[i].product.product_tmpl_id, + 'pos_reference': order.name, + 'uom_id': order_line[i].product.uom_id[0], + }; + list_product.push(product_dict); + } + } + if (list_product.length){ + this.orm.call('mrp.production', 'create_mrp_from_pos', [1,list_product]) + } + } + }, + // Override the function validateOrder + async validateOrder(isForceValidate) { + if(this.pos.config.cash_rounding) { + if(!this.pos.get_order().check_paymentlines_rounding()) { + this.showPopup('ErrorPopup', { + title: _t('Rounding error in payment lines'), + body: _t("The amount of your payment lines must be rounded to validate the transaction."), + }); + return; + } + } + if (await this._isOrderValid(isForceValidate)) { + // Remove pending payments before finalizing the validation + for (let line of this.paymentLines) { + if (!line.is_done()) this.currentOrder.remove_paymentline(line); + } + await this._finalizeValidation(); + } + this.createMRP(); + } +}); diff --git a/all_in_one_pos_kit/static/src/pos_num_show_hide/js/pos_numpad.js b/all_in_one_pos_kit/static/src/pos_num_show_hide/js/pos_numpad.js new file mode 100644 index 000000000..817bbbe9a --- /dev/null +++ b/all_in_one_pos_kit/static/src/pos_num_show_hide/js/pos_numpad.js @@ -0,0 +1,18 @@ +///** @odoo-module **/ + +import { ProductScreen } from "@point_of_sale/app/screens/product_screen/product_screen"; +import { patch } from "@web/core/utils/patch"; + + + patch(ProductScreen.prototype, { + NumpadVisibility() { + // hide and show numPad in ProductScreen + const padsElement = document.querySelector('.pads'); + const numpadToggleElement = document.querySelector('.numpad-toggle'); + const isNumpadVisible = padsElement.style.display !== 'none'; + padsElement.style.display = isNumpadVisible ? 'none' : 'block'; + numpadToggleElement.classList.remove('fa-eye', 'fa-eye-slash'); + numpadToggleElement.classList.add(isNumpadVisible ? 'fa-eye-slash' : 'fa-eye'); + } + }) + diff --git a/all_in_one_pos_kit/static/src/pos_num_show_hide/xml/pos.xml b/all_in_one_pos_kit/static/src/pos_num_show_hide/xml/pos.xml new file mode 100644 index 000000000..904aedf33 --- /dev/null +++ b/all_in_one_pos_kit/static/src/pos_num_show_hide/xml/pos.xml @@ -0,0 +1,12 @@ + + + + + +
+ + +
+
+
+
diff --git a/all_in_one_pos_kit/static/src/pos_report/css/pos_report.css b/all_in_one_pos_kit/static/src/pos_report/css/pos_report.css new file mode 100644 index 000000000..e727db464 --- /dev/null +++ b/all_in_one_pos_kit/static/src/pos_report/css/pos_report.css @@ -0,0 +1,46 @@ +.time_range_pos { + width: 125px; + border: 2px solid #ccc; + border-radius: 5px; + padding: 10px; +} +a.dropdown-toggle.report-type { + margin: 10px; +} +.search-Result-Selection { + border: 2px solid #ccc; + width: 257px; + margin-right: 10px; + margin-left: 10px; + border-radius: 5px; +} +.table_pos_head { + background-color: #7c7bad; + color: #fff; + padding: 20px; + margin: 20px; + height: 57px; + width: 100%; + border: 1px solid #000; +} +element.style { + top: 0px; + height: 42px; + color: white; + background-color: #7c7bad; + border-color: #7c7bad; + width: 100px; +} +tr.ps-line { + height: 48px; +} +tr.table_pos_head th { + font-size: 18px; + text-align: center; +} +ul.dropdown-menu.show li { + margin-left: 10px; +} +ul.dropdown-menu.show a{ + color: #221c1c; +} diff --git a/all_in_one_pos_kit/static/src/pos_report/js/pos_report.js b/all_in_one_pos_kit/static/src/pos_report/js/pos_report.js new file mode 100644 index 000000000..f0437efb3 --- /dev/null +++ b/all_in_one_pos_kit/static/src/pos_report/js/pos_report.js @@ -0,0 +1,112 @@ +/** @odoo-module */ +const { Component } = owl; +import { registry } from "@web/core/registry"; +import { download } from "@web/core/network/download"; +import { useService } from "@web/core/utils/hooks"; +import { useRef, useState } from "@odoo/owl"; +import { BlockUI } from "@web/core/ui/block_ui"; +const actionRegistry = registry.category("actions"); +import { uiService } from "@web/core/ui/ui_service"; +import { renderToElement } from "@web/core/utils/render"; + +// Extending components for adding purchase report class +class PosReport extends Component { + async setup() { + super.setup(...arguments); + this.uiService = useService('ui'); + this.initial_render = true; + this.orm = useService('orm'); + this.action = useService('action'); + this.start_date = useRef('date_from'); + this.end_date = useRef('date_to'); + this.order_by = useRef('order_by'); + this.state = useState({ + order_line: [], + data: null, + order:'Report By Sale Order', + order_by : 'report_by_order', + wizard_id : [], + }); + this.load_data(); + } + async load_data(wizard_id = null) { + /** + * Loads the data for the sales report. + */ + let move_lines = '' + try { + if(wizard_id == null){ + this.state.wizard_id = await this.orm.create("pos.report",[{}]); + } + this.state.data = await this.orm.call("pos.report", "pos_report", [this.state.wizard_id]); + this.state.order_line = this.state.data.report_lines + } + catch (el) { + window.location.href + } + } + async print_pdf(e) { + //Prints the POS report as a PDF. + e.preventDefault(); + return this.action.doAction({ + 'type': 'ir.actions.report', + 'report_type': 'qweb-pdf', + 'report_name': 'all_in_one_pos_kit.pos_order_report', + 'report_file': 'all_in_one_pos_kit.pos_order_report', + 'data': { + 'report_data': this.state.data + }, + 'context': { + 'active_model': 'pos.report', + 'landscape': 1, + 'pos_order_report': true + }, + 'display_name': 'PoS Order', + }); + } + async print_xlsx() { + //Prints the POS report as an XLSX file. + var data = this.state.data + var action = { + 'data': { + 'model': 'pos.report', + 'options': JSON.stringify(data['orders']), + 'output_format': 'xlsx', + 'report_data': JSON.stringify(data['report_lines']), + 'report_name': 'PoS Report', + 'dfr_data': JSON.stringify(data), + }, + }; + this.uiService.block(); + await download({ + url: '/pos_dynamic_xlsx_reports', + data: action.data, + complete: this.uiService.unblock(), + error: (error) => this.call('crash_manager', 'rpc_error', error), + }); + } + async button_view_order (event) {//Opens a POS order in a new window. + event.preventDefault(); + return this.action.doAction({ + type: "ir.actions.act_window", + res_model: 'pos.order', + res_id: parseInt(event.target.id), + views: [[false, "form"]], + target: "current", + }); + + } + async apply_filter(ev) { + //Applies the selected filters and reloads the POS report data. + this.initial_render = false; + let filter_data_selected = {}; + this.state.order_by = this.order_by.el.value + filter_data_selected.date_from = this.start_date.el.value + filter_data_selected.date_to = this.end_date.el.value + filter_data_selected.report_type = this.state.order_by + let data = await this.orm.write("pos.report",this.state.wizard_id, filter_data_selected); + this.load_data(this.initial_render) + } + } + PosReport.template = 'PosReport'; +actionRegistry.add("pos_r", PosReport); diff --git a/all_in_one_pos_kit/static/src/pos_report/xml/pos_report_view.xml b/all_in_one_pos_kit/static/src/pos_report/xml/pos_report_view.xml new file mode 100644 index 000000000..5cf858a1f --- /dev/null +++ b/all_in_one_pos_kit/static/src/pos_report/xml/pos_report_view.xml @@ -0,0 +1,497 @@ + + + + +
+
+
+

Point of Sale Report

+
+
+
+
+
+
+
+
+
+ + +
+
+
+ + +
+
+

+
+
+ + + Date Range + + +
+
+ + + Report Type: + + + +
+
+ +
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
PoSOrderDate OrderCustomerSalesmanTotal QtyAmount TotalNote
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PoSOrderDate OrderCustomerSalesmanProduct NameDefault CodePrice unitQtyPrice SubtotalPrice Subtotal Incl
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + +
CategoryProduct CodeProduct NameQtyAmount TotalAmount Total Incl
+ + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + +
CategoryQtyAmount TotalAmount Total Incl
+ + + + + + + + + + + + + + + + +
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + +
SalesmanTotal OrderTotal QtyTotal Amount
+ + + + + + + + + + + + + + + + +
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + +
Point of SalePoS SessionPaymentTotal Amount
+ + + + + + + + + + + + + + + + + + +
+
+
+ + diff --git a/all_in_one_pos_kit/static/src/product_creation/css/ProductList.css b/all_in_one_pos_kit/static/src/product_creation/css/ProductList.css new file mode 100644 index 000000000..b7a3c4eb8 --- /dev/null +++ b/all_in_one_pos_kit/static/src/product_creation/css/ProductList.css @@ -0,0 +1,34 @@ +button.edit_product{ + border-radius:5px; +} +i.fa.fa-pencil{ + font-size:20px; +} +img.product_img { + max-width:40px; +} +label.field_label{ + padding:3%; +} +.field_div{ + margin-bottom:5px; +} +select.form-control{ + text-align: left; + display: inline-block; + overflow: hidden; + background: white; + min-height: 44px; + font-family: "Lato"; + font-size: 20px; + color: #444; + padding: 10px; + border-radius: 3px; + border: none; + box-shadow: 0px 0px 0px 1px gainsboro inset; + box-sizing: border-box; + width: 85%; +} +i.fa.fa-plus.create_btn{ + margin-right:5px; +} diff --git a/all_in_one_pos_kit/static/src/product_creation/js/pos_product_screen.js b/all_in_one_pos_kit/static/src/product_creation/js/pos_product_screen.js new file mode 100644 index 000000000..6c1656c9e --- /dev/null +++ b/all_in_one_pos_kit/static/src/product_creation/js/pos_product_screen.js @@ -0,0 +1,54 @@ +/**@odoo-module **/ +import { _t } from "@web/core/l10n/translation"; +import { ProductScreen } from "@point_of_sale/app/screens/product_screen/product_screen"; +import { useService } from "@web/core/utils/hooks"; +import { Component } from "@odoo/owl"; +import { usePos } from "@point_of_sale/app/store/pos_hook"; +import { CreateProductPopup } from "./product_create_popup"; +/** + * OrderlineProductCreateButton is a component responsible for creating a + product and adding it to the order line. +*/ +export class OrderlineProductCreateButton extends Component { + static template = "point_of_sale.ProductCreateButton"; + /** + * Setup function to initialize the component. + */ + setup() { + this.pos = usePos(); + this.popup = useService("popup"); + } + /** + * Getter function to fetch a list of products based on the search criteria. + * @returns {Object[]} List of products. + */ + get products() { + let list; + if (this.state.search && this.state.search.trim() !== "") { + list = this.env.pos.db.search_product_in_category( + 0, + this.state.search.trim() + ); + } else { + list = this.env.pos.db.get_product_by_category(0); + } + return list.sort(function(a, b) { + return a.display_name.localeCompare(b.display_name); + }); + } + /** + * Click event handler for the create product button. + */ + async onClick() { + this.popup.add(CreateProductPopup, { + product: this.props.product, + }) + } +} +/** + * Add the OrderlineProductCreateButton component to the control buttons in + the ProductScreen. + */ +ProductScreen.addControlButton({ + component: OrderlineProductCreateButton, +}); diff --git a/all_in_one_pos_kit/static/src/product_creation/js/product_create_popup.js b/all_in_one_pos_kit/static/src/product_creation/js/product_create_popup.js new file mode 100644 index 000000000..778b615ee --- /dev/null +++ b/all_in_one_pos_kit/static/src/product_creation/js/product_create_popup.js @@ -0,0 +1,118 @@ +/**@odoo-module **/ +import { AbstractAwaitablePopup } from "@point_of_sale/app/popup/abstract_awaitable_popup"; +import { useService } from "@web/core/utils/hooks"; +import { jsonrpc } from "@web/core/network/rpc_service"; +import { _t } from "@web/core/l10n/translation"; +import { ErrorPopup } from "@point_of_sale/app/errors/popups/error_popup"; +let img = ""; +let base64_img = ""; +/** + * CreateProductPopup is a popup component for creating a new product in the Point of Sale. + */ +export class CreateProductPopup extends AbstractAwaitablePopup { + static template = "CreateProductPopup" + /** + * Setup function to initialize the component. + */ + setup() { + super.setup(); + this.notification = useService("pos_notification"); + this.popup = useService("popup"); + } + /** + * Event handler for changing the image field. + * @param {Event} ev - The event object. + */ + async _onChangeImgField(ev) { + try { + let current = ev.target.files[0]; + const reader = new FileReader(); + reader.readAsDataURL(current); + reader.onload = await + function() { + img = reader.result; + base64_img = reader.result.toString().replace(/^data:(.*,)?/, ""); + const myTimeout = setTimeout(() => { + $("#img_url_tag_create").hide(); + let element = + ""; + $(".product-img-create-popup").append($(element)); + }, 100); + }; + reader.onerror = (error) => + reject(() => { + throw error; + }); + } catch (error) { + throw error; + } + } + /** + * Confirm function to create the product based on the entered details. + * @param {Event} ev - The event object. + */ + async confirm(ev) { + const img = $("#img_field").val(); + const name = $("#display_name").val(); + const price = $("#list_price").val() + const cost = $("#cost_price").val(); + const category = $("#product_category").val(); + const barcode = $("#barcode").val(); + const default_code = $("#default_code").val(); + const type = $("#type").val(); + const values = {}; + if (base64_img) { + values["image_1920"] = base64_img; + } + if (!name) { + this.popup.add(ErrorPopup, { + title: "Error", + body: "Add product name", + }); + return; // Stop execution if name is missing + } + values["name"] = name; + if (cost) { + values["standard_price"] = cost; + } + if (price) { + values["lst_price"] = price; + } + if (category > 0) { + values["pos_categ_ids"] = [[6, false, [category]]]; + } else { + this.popup.add(ErrorPopup, { + title: "Error", + body: "Forgot to select pos category?", + }); + return; // Stop execution if category is missing + } + if (barcode) { + values["barcode"] = barcode; + } + if (default_code) { + values["default_code"] = default_code; + } + if (type) { + values["type"] = type; + } + values["available_in_pos"] = true; + try { + const result = await jsonrpc(`/web/dataset/call_kw/product.product/create`, { + model: "product.product", + method: "create", + args: [values], + kwargs: {}, + }); + if (result) { + this.notification.add(_t("Product Created."), 3000); + this.cancel() + + } else { + this.notification.add(_t("Product Not Created."), 3000); + } + } catch (error) { + throw error; + } + } +} \ No newline at end of file diff --git a/all_in_one_pos_kit/static/src/product_creation/xml/product_create_button_templates.xml b/all_in_one_pos_kit/static/src/product_creation/xml/product_create_button_templates.xml new file mode 100644 index 000000000..3b0235eba --- /dev/null +++ b/all_in_one_pos_kit/static/src/product_creation/xml/product_create_button_templates.xml @@ -0,0 +1,14 @@ + + + + + + + diff --git a/all_in_one_pos_kit/static/src/product_creation/xml/product_create_popup_templates.xml b/all_in_one_pos_kit/static/src/product_creation/xml/product_create_popup_templates.xml new file mode 100644 index 000000000..2224bc457 --- /dev/null +++ b/all_in_one_pos_kit/static/src/product_creation/xml/product_create_popup_templates.xml @@ -0,0 +1,106 @@ + + + + + + + diff --git a/all_in_one_pos_kit/static/src/service_charge/js/pos_load_data.js b/all_in_one_pos_kit/static/src/service_charge/js/pos_load_data.js new file mode 100644 index 000000000..217f966be --- /dev/null +++ b/all_in_one_pos_kit/static/src/service_charge/js/pos_load_data.js @@ -0,0 +1,10 @@ +/** @odoo-module */ +import { patch } from "@web/core/utils/patch"; +import { PosStore } from "@point_of_sale/app/store/pos_store"; +patch(PosStore.prototype, { + async _processData(loadedData) { + // To load data to pos session + await super._processData(...arguments); + this.res_config_settings = loadedData["res.config.settings"]; + } +}); diff --git a/all_in_one_pos_kit/static/src/service_charge/js/service_charge_button.js b/all_in_one_pos_kit/static/src/service_charge/js/service_charge_button.js new file mode 100644 index 000000000..65ae6fe22 --- /dev/null +++ b/all_in_one_pos_kit/static/src/service_charge/js/service_charge_button.js @@ -0,0 +1,100 @@ +/** @odoo-module */ +import { Component } from "@odoo/owl"; +import { useService } from "@web/core/utils/hooks"; +import { ProductScreen } from "@point_of_sale/app/screens/product_screen/product_screen"; +import { usePos } from "@point_of_sale/app/store/pos_hook"; +import { ErrorPopup } from "@point_of_sale/app/errors/popups/error_popup"; +import { NumberPopup } from "@point_of_sale/app/utils/input_popups/number_popup"; +import { _t } from "@web/core/l10n/translation"; + +export class ServiceChargeButton extends Component { + static template = "all_in_one_pos_kit.ServiceChargeButton"; + setup() { + this.pos = usePos(); + this.popup = useService("popup"); + } + async click() { + // To show number pop up and service charge applying functions based on conditions. + let res_config_settings = this.pos.res_config_settings[this.pos.res_config_settings.length -1] + var global_selection = res_config_settings.global_selection + var global_charge = res_config_settings.global_charge + var visibility = res_config_settings.visibility + var global_product = res_config_settings.global_product_id[0] + var order = this.pos.get_order(); + var lines = order.get_orderlines(); + if (visibility == 'global') { + var product = this.pos.db.get_product_by_id(global_product) + if (product === undefined) { + await this.popup.add(ErrorPopup, { + title: _t("No service product found"), + body: _t("The service product seems misconfigured. Make sure it is flagged as 'Can be Sold' and 'Available in Point of Sale'.") + }); + return + } + // Remove existing discounts + lines.filter(line => line.get_product() === product).forEach(line => order.removeOrderline(line)); + const { confirmed, payload } = await this.popup.add(NumberPopup, { + title: _t('Service Charge'), + startingValue: parseInt(global_charge), + isInputSelected: true + }) + if (confirmed) { + if (payload > 0) { + if (global_selection == 'amount') { + order.add_product(product, { + price: payload + }); + } else { + var total_amount = order.get_total_with_tax() + var per_amount = payload / 100 * total_amount + order.add_product(product, { + price: per_amount + }); + } + } + } + } else { + var type = this.pos.config.charge_type + var product = this.pos.db.get_product_by_id(this.pos.config.service_product_id[0]); + if (product === undefined) { + await this.popup.add(ErrorPopup, { + title: _t("No service product found"), + body: _t("The service product seems misconfigured. Make sure it is flagged as 'Can be Sold' and 'Available in Point of Sale'."), + }); + return; + } + lines.filter(line => line.get_product() === product).forEach(line => order.removeOrderline(line)); + const {confirmed, payload } = await this.popup.add(NumberPopup, { + title: _t('Service Charge'), + startingValue: this.pos.config.service_charge, + isInputSelected: true + }) + if (confirmed) { + if (payload > 0) { + if (type == 'amount') { + order.add_product(product, { + price: payload + }); + } else { + var total_amount = order.get_total_with_tax() + var per_amount = payload / 100 * total_amount + order.add_product(product, { + price: per_amount + }); + } + } + } + } + } +} +ProductScreen.addControlButton({ + component: ServiceChargeButton, + condition: function () { + let res_config_settings = this.pos.res_config_settings[this.pos.res_config_settings.length -1] + if (res_config_settings) { + return res_config_settings.enable_service_charge + } else { + return false + } + }, +}); diff --git a/all_in_one_pos_kit/static/src/service_charge/xml/ServiceChargeButton.xml b/all_in_one_pos_kit/static/src/service_charge/xml/ServiceChargeButton.xml new file mode 100644 index 000000000..3cc2df763 --- /dev/null +++ b/all_in_one_pos_kit/static/src/service_charge/xml/ServiceChargeButton.xml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/all_in_one_pos_kit/static/src/time_based_product/1/pos_model_load.js b/all_in_one_pos_kit/static/src/time_based_product/1/pos_model_load.js new file mode 100644 index 000000000..fb4d395b4 --- /dev/null +++ b/all_in_one_pos_kit/static/src/time_based_product/1/pos_model_load.js @@ -0,0 +1,27 @@ +odoo.define('pos_products_based_on_time.planning', function(require){ + "use strict"; + var { PosGlobalState } = require('point_of_sale.models'); + const Registries = require('point_of_sale.Registries'); + // Define a new class that extends PosGlobalState + const planning = (PosGlobalState) => class planning extends PosGlobalState { + /** + * Override the _processData method to preprocess the meals planning data + * before it is saved to the global state. + * @param {Object} loadedData - The data loaded from the backend. + */ + async _processData(loadedData) { + super._processData(...arguments) + // Process the product_ids field of each meals planning record to include + // The actual product data instead of just the product ID. + let new_meal = [] + loadedData['meals.planning'].forEach((data) => { + data.product_ids = loadedData['product.product'].filter((meal) => data.product_ids.includes(meal.id)) + new_meal.push(data) + }) + // Update the meals planning data. + this.meals_planning = new_meal + } + } + // Register the new planning class with the POS registry. + Registries.Model.extend(PosGlobalState, planning); +}); diff --git a/all_in_one_pos_kit/static/src/time_based_product/1/pos_planning.js b/all_in_one_pos_kit/static/src/time_based_product/1/pos_planning.js new file mode 100644 index 000000000..f1ed42a13 --- /dev/null +++ b/all_in_one_pos_kit/static/src/time_based_product/1/pos_planning.js @@ -0,0 +1,54 @@ +odoo.define('pos_products_based_on_time.ProductsPlanning', function(require) { + 'use strict'; + const ProductsWidget = require('point_of_sale.ProductsWidget'); + const Registries = require('point_of_sale.Registries'); + // Define a new class that extends ProductsWidget + const ProductsPlanning = (ProductsWidget) => class extends ProductsWidget { + // Override the setup method to perform any additional setup logic. + setup() { + super.setup(...arguments); + } + /** + * Override the productsToDisplay getter method to filter the list of products + * based on the current time and the meals planning data. + * @returns {Array} A sorted array of product objects to be displayed. + */ + get productsToDisplay() { + let list = []; + if (this.searchWord !== '') { + list = this.env.pos.db.search_product_in_category( + this.selectedCategoryId, + this.searchWord + ); + } else { + list = this.env.pos.db.get_product_by_category(this.selectedCategoryId); + } + // Get the current time in hours and minutes. + const date = new Date(); + let time = Number(date.getHours() + '.' + date.getMinutes()) + // Filter the meals planning data to find the menu products that are available + // for the current time. + let data = []; + this.env.pos.meals_planning.forEach(function(object) { + if (object.time_from < time && time < object.time_to) { + let plan_arr = null; + plan_arr = object.product_ids.flat(1) + data.push(plan_arr.map((meal) => meal.id)) + } + }) + // Filter the list of products to only include products that are part of the + // available menu products for the current time. + if (data.length) { + list = list.filter(product => data.flat(1).includes(product.id)) + } + // Sort the list of products by display name and return the sorted list. + return list.sort(function(a, b) { + return a.display_name.localeCompare(b.display_name) + }); + } + } + // Register the new ProductsPlanning component with the POS registry. + Registries.Component.extend(ProductsWidget, ProductsPlanning); + // Export the new ProductsPlanning class. + return ProductsPlanning; +}); diff --git a/all_in_one_pos_kit/static/src/time_based_product/js/pos_model_load.js b/all_in_one_pos_kit/static/src/time_based_product/js/pos_model_load.js new file mode 100644 index 000000000..59ee12d0f --- /dev/null +++ b/all_in_one_pos_kit/static/src/time_based_product/js/pos_model_load.js @@ -0,0 +1,23 @@ +/** @odoo-module */ + +import { patch } from "@web/core/utils/patch"; +import { PosStore } from "@point_of_sale/app/store/pos_store"; + +patch(PosStore.prototype, { + async setup() { + await super.setup(...arguments);}, + /** + * Override the _processData method to preprocess the meals planning data + * before it is saved to the global state. + * @param {Object} loadedData - The data loaded from the backend. + */ + async _processData(loadedData) { + await super._processData(...arguments); + let new_meal = [] + loadedData['meals.planning'].forEach((data) => { + data.menu_product_ids = loadedData['product.product'].filter((meal) => data.menu_product_ids.includes(meal.id)) + new_meal.push(data) + }) + this.meals_planning = new_meal + }, +}); diff --git a/all_in_one_pos_kit/static/src/time_based_product/js/pos_planning.js b/all_in_one_pos_kit/static/src/time_based_product/js/pos_planning.js new file mode 100644 index 000000000..0de9d237b --- /dev/null +++ b/all_in_one_pos_kit/static/src/time_based_product/js/pos_planning.js @@ -0,0 +1,41 @@ +/** @odoo-module */ + +import { patch } from "@web/core/utils/patch"; +import { ProductsWidget } from "@point_of_sale/app/screens/product_screen/product_list/product_list"; + +patch(ProductsWidget.prototype, { + /** + * Override the productsToDisplay getter method to filter the list of products + * based on the current time and the meals planning data. + * @returns {Array} A sorted array of product objects to be displayed. + */ + get productsToDisplay() { + const { db } = this.pos; + let list = []; + var res_list = new Array(); + if (this.searchWord !== '') { + list = db.search_product_in_category( + this.selectedCategoryId, + this.searchWord + ); + } else { + list = db.get_product_by_category(this.selectedCategoryId); + } + const date = new Date(); + let hoursMin = date.getHours() + '.' + date.getMinutes(); + let time = Number(hoursMin) + let data = []; + this.env.services.pos.meals_planning.forEach(object => { + if (object.time_from <= time && time < object.time_to) { + } + const plan_arr = object.menu_product_ids.flat(1); + data.push(plan_arr.map(meal => meal.id)); + }) + if (data.length) { + list = list.filter(product => data[0].includes(product.id)) + } + return list.sort(function(a, b) { + return a.display_name.localeCompare(b.display_name) + }); + } +}); diff --git a/all_in_one_pos_kit/views/dashboard_views.xml b/all_in_one_pos_kit/views/dashboard_views.xml new file mode 100644 index 000000000..0c0ea49c3 --- /dev/null +++ b/all_in_one_pos_kit/views/dashboard_views.xml @@ -0,0 +1,14 @@ + + + + + Dashboard + pos_dashboard + + + + diff --git a/all_in_one_pos_kit/views/meals_planning_views.xml b/all_in_one_pos_kit/views/meals_planning_views.xml new file mode 100644 index 000000000..6af9739d8 --- /dev/null +++ b/all_in_one_pos_kit/views/meals_planning_views.xml @@ -0,0 +1,95 @@ + + + + meals.planning + + + + + + + + + + + + + + + + meals.planning.view.tree + meals.planning + + + + + + + + + + + + meals.planning.view.form + meals.planning + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+ + + Meals planning + meals.planning + tree,form + + +
\ No newline at end of file diff --git a/all_in_one_pos_kit/views/pos_config_views.xml b/all_in_one_pos_kit/views/pos_config_views.xml new file mode 100644 index 000000000..e6a38f31a --- /dev/null +++ b/all_in_one_pos_kit/views/pos_config_views.xml @@ -0,0 +1,66 @@ + + + + + + pos.config.view.form.inherit.all.in.one.pos.kit + + pos.config + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + +
+ + pos.config.view.kanban.inherit.all.in.one.pos.kit + + pos.config + + + + + + + + + + +
diff --git a/all_in_one_pos_kit/views/pos_greetings_views.xml b/all_in_one_pos_kit/views/pos_greetings_views.xml new file mode 100644 index 000000000..54daf318e --- /dev/null +++ b/all_in_one_pos_kit/views/pos_greetings_views.xml @@ -0,0 +1,57 @@ + + + + + pos.greetings.view.tree + pos.greetings + + + + + + + + + + + + pos.greetings.view.form + pos.greetings + +
+ + + + + + + + + + + + + + + + + +
+
+
+ + + POS Greetings + pos.greetings + tree,form + +

+ POS Greetings +

+
+
+ +
diff --git a/all_in_one_pos_kit/views/pos_order_views.xml b/all_in_one_pos_kit/views/pos_order_views.xml new file mode 100644 index 000000000..ba6089b33 --- /dev/null +++ b/all_in_one_pos_kit/views/pos_order_views.xml @@ -0,0 +1,17 @@ + + + + + + pos.order.view.form.inherit.all.in.one.pos.kit + + pos.order + + + + + + + + + diff --git a/all_in_one_pos_kit/views/pos_report_views.xml b/all_in_one_pos_kit/views/pos_report_views.xml new file mode 100644 index 000000000..6deeef935 --- /dev/null +++ b/all_in_one_pos_kit/views/pos_report_views.xml @@ -0,0 +1,12 @@ + + + + + Pos Report + pos_r + + + diff --git a/all_in_one_pos_kit/views/product_product_views.xml b/all_in_one_pos_kit/views/product_product_views.xml new file mode 100644 index 000000000..4adaf388c --- /dev/null +++ b/all_in_one_pos_kit/views/product_product_views.xml @@ -0,0 +1,23 @@ + + + + + + product.product.view.form.inherit.all.in.one.pos.kit + + product.product + + + + + + + + + + + + + + + diff --git a/all_in_one_pos_kit/views/product_template_views.xml b/all_in_one_pos_kit/views/product_template_views.xml new file mode 100644 index 000000000..9fa0d3fd6 --- /dev/null +++ b/all_in_one_pos_kit/views/product_template_views.xml @@ -0,0 +1,32 @@ + + + + + + product.template.view.form.inherit.all.in.one.pos.kit + + product.template + + + + + + + + + + + + + + + + + + + + + + diff --git a/all_in_one_pos_kit/views/res_config_settings_views.xml b/all_in_one_pos_kit/views/res_config_settings_views.xml new file mode 100644 index 000000000..45b3f0d24 --- /dev/null +++ b/all_in_one_pos_kit/views/res_config_settings_views.xml @@ -0,0 +1,221 @@ + + + + + + res.config.settings.view.form.inherit.all.in.one.pos.kit + + res.config.settings + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
+
+ Add % tip +
+ +
+
+
+ +

POS Greetings

+
+
+
+ +
+
+
+
+
+
+ +

Receipt

+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
diff --git a/all_in_one_pos_kit/views/res_users_views.xml b/all_in_one_pos_kit/views/res_users_views.xml new file mode 100644 index 000000000..7a3eec57e --- /dev/null +++ b/all_in_one_pos_kit/views/res_users_views.xml @@ -0,0 +1,27 @@ + + + + + + res.users.view.form.inherit.all.in.one.pos.kit + + res.users + + + + + + + + + + + + + + + + +