diff --git a/all_in_one_pos_kit/README.rst b/all_in_one_pos_kit/README.rst new file mode 100755 index 000000000..604d19db8 --- /dev/null +++ b/all_in_one_pos_kit/README.rst @@ -0,0 +1,45 @@ +.. 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. + +Installation +============ + - www.odoo.com/documentation/16.0/setup/install.html + - Install our custom addon + +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 @cybrosys, 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..0749db808 --- /dev/null +++ b/all_in_one_pos_kit/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2023-TODAY Cybrosys Technologies() +# Author: Afra MP (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..9ea824410 --- /dev/null +++ b/all_in_one_pos_kit/__manifest__.py @@ -0,0 +1,101 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2023-TODAY Cybrosys Technologies() +# Author: Afra MP (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': '16.0.1.0.0', + 'category': 'Point of Sale', + 'summary': 'This module combines Different POS features', + 'description': 'This module combines Different POS features', + 'author': 'Cybrosys Techno Solutions', + 'company': 'Cybrosys Techno Solutions', + 'maintainer': 'Cybrosys Techno Solutions', + 'website': "https://www.cybrosys.com", + 'depends': ['hr', 'point_of_sale', 'mrp'], + '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': [ + '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_magnify_image/css/pos_magnify_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/product_magnify_image/js/*', + 'all_in_one_pos_kit/static/src/pos_mrp_order/js/models.js', + 'all_in_one_pos_kit/static/src/pos_num_show_hide/js/pos_numpad.js', + 'all_in_one_pos_kit/static/src/order_item_count/js/*', + 'all_in_one_pos_kit/static/src/product_creation/js/*', + 'all_in_one_pos_kit/static/src/pos_auto_lot/js/auto_lot.js', + 'all_in_one_pos_kit/static/src/advanced_receipt/js/payment.js', + 'all_in_one_pos_kit/static/src/category_wise_receipt/js/pos_receipt.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/product_magnify_image/xml/*', + 'all_in_one_pos_kit/static/src/pos_num_show_hide/xml/pos.xml', + 'all_in_one_pos_kit/static/src/order_item_count/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/category_wise_receipt/xml/pos_receipt.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/js/pos_dashboard.js', + 'all_in_one_pos_kit/static/src/pos_report/js/*', + 'all_in_one_pos_kit/static/src/dashboard/xml/pos_dashboard.xml', + '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..026bc30dc --- /dev/null +++ b/all_in_one_pos_kit/controllers/__init__.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2023-TODAY Cybrosys Technologies() +# Author: Afra MP (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..2431db29e --- /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) 2023-TODAY Cybrosys Technologies() +# Author: Afra MP (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 werkzeug +from odoo import http +from odoo.http import request +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..89b4b9d58 --- /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) 2023-TODAY Cybrosys Technologies() +# Author: Afra MP (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..92c3fbd19 --- /dev/null +++ b/all_in_one_pos_kit/doc/RELEASE_NOTES.md @@ -0,0 +1,7 @@ +## Module + +#### 3.10.2023 +#### Version 16.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..1df47b70e --- /dev/null +++ b/all_in_one_pos_kit/models/__init__.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2023-TODAY Cybrosys Technologies() +# Author: Afra MP (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..f8bcd0729 --- /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) 2023-TODAY Cybrosys Technologies() +# Author: Afra MP (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..5e21c81fa --- /dev/null +++ b/all_in_one_pos_kit/models/meals_planning.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +################################################################################ +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2023-TODAY Cybrosys Technologies(). +# Author: Afra MP (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') + pos_ids = fields.Many2many('pos.config', string='Shops', + 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)') + product_ids = fields.Many2many('product.product', string='Product', + domain=[('available_in_pos', '=', True)], + help='Select the products available for' + ' the menu.') + state = fields.Selection([('activated', 'Activated'), + ('deactivated', 'Deactivated')], + string='State', + default='deactivated', + help='Select the state of the item (activated or ' + 'deactivated).') + company_id = fields.Many2one('res.company', string='Company', + default=lambda self: self.env.company, + help='Select the company associated with the ' + 'item.') + + @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!') + if 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' + }) 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..2b4ce9415 --- /dev/null +++ b/all_in_one_pos_kit/models/mrp_production.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2023-TODAY Cybrosys Technologies() +# Author: Afra MP (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): + """Inherit the class mrp_production to add new function""" + _inherit = 'mrp.production' + + def create_mrp_from_pos(self, products): + """Fetch value from js and create manufacturing order for the product + and return true""" + 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: + if self.env['mrp.bom'].search([('product_tmpl_id', '=', + prod['product_tmpl_id'])]): + if self.env['mrp.bom'].search( + [('product_id', '=', prod['id'])]): + bom = self.env['mrp.bom'].search( + [('product_id', '=', prod['id'])])[0] + elif self.env['mrp.bom'].search([( + 'product_tmpl_id', '=', + prod[ + 'product_tmpl_id']), + ('product_id', '=', + False)]): + bom = self.env['mrp.bom'].search([( + 'product_tmpl_id', + '=', prod[ + 'product_tmpl_id']), + ( + 'product_id', '=', + False)])[0] + else: + bom = [] + if bom: + mrp_order = self.sudo().create({ + '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}) + 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, + '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, + })) + mrp_order.update({'move_raw_ids': list_value}) + 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..04cf6ec57 --- /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) 2023-TODAY Cybrosys Technologies() +# Author: Afra MP (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.product' + _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..26b1d5e17 --- /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) 2023-TODAY Cybrosys Technologies() +# Author: Afra MP (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( + 'service_charges_pos.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..eba331b76 --- /dev/null +++ b/all_in_one_pos_kit/models/pos_greetings.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2023-TODAY Cybrosys Technologies() +# Author: Afra MP (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 POSGreetings(models.Model): + """Model representing POS Greetings.""" + _name = 'pos.greetings' + _description = 'POS Greetings' + _rec_name = 'order_id' + + partner_id = fields.Many2one('res.partner', string='Customer', + help='Select the customer for whom the SMS ' + 'will be sent.') + order_id = fields.Many2one('pos.order', string='Order', + help='Select the order associated with the SMS') + auth_token = fields.Char(string='Token', + help='Enter the authentication token for the SMS ' + 'service provider.') + twilio_number = fields.Char(string='Twilio Number', + help='Enter the Twilio phone number used to' + ' send the SMS.') + to_number = fields.Char(string='Customer Number', + help='Enter the recipients phone number to send ' + 'the SMS to.') + sms_body = fields.Char(string='Body', + help='Enter the content or message of the SMS.') + session_id = fields.Many2one('pos.session', string='Session', + help='Select the session associated with ' + 'the SMS.') + send_sms = fields.Boolean(string='Send SMS',help='Check this box to send ' + 'the SMS when saving the record.') 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..3dd92eefc --- /dev/null +++ b/all_in_one_pos_kit/models/pos_order.py @@ -0,0 +1,264 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2023-TODAY Cybrosys Technologies() +# Author: Afra MP (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 get_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]) + return { + '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, + } + + @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. + This method is called to create POS orders based on the provided + data from the user interface. + :param orders: A list of dictionaries representing the POS orders. + :param draft: Set to True if the orders should be created in the + draft state. + :returns: A list of dictionaries containing the created order + details. + """ + 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() + if params.get_param( + 'pos.customer_msg') and pos_order.partner_id.phone: + try: + # Download the helper library from https://www.twilio.com/docs/python/install + Client(params.get_param('pos.account_sid'), + params.get_param( + 'pos.auth_token')).messages.create( + body=params.get_param('pos.sms_body'), + from_=params.get_param('pos.twilio_number'), + to=str(pos_order.partner_id.phone)) + self.env['pos.greetings'].create({ + 'partner_id': pos_order.partner_id.id, + 'order_id': pos_order.id, + 'auth_token': params.get_param('pos.auth_token'), + 'twilio_number': params.get_param( + 'pos.twilio_number'), + 'to_number': str(pos_order.partner_id.phone), + 'session_id': pos_order.session_id.id, + 'sms_body': params.get_param('pos.sms_body'), + 'send_sms': True, + }) + except Exception: + pass + return res + + +class PosOrderLine(models.Model): + """Inherit the class pos_order_line""" + _inherit = "pos.order.line" + + 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..fdc751189 --- /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) 2023-TODAY Cybrosys Technologies() +# Author: Afra MP (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 PosReportGenerator(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.env['pos.report'].search([('id', '=', 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.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..382212b18 --- /dev/null +++ b/all_in_one_pos_kit/models/pos_session.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2023-TODAY Cybrosys Technologies() +# Author: Afra MP (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.product', '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', + 'barcode_type'], }, } + + 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_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_product(self, params): + """"Return the model multi_barcode_product""" + return self.env['multi.barcode.product'].with_context( + **params['context']).search_read(**params['search_params']) + + def _loader_params_multi_barcode_product(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""" + data = [rec.id for rec in self.env['meals.planning'].search( + [('state', '=', 'activated'), + ('pos_ids', 'in', self.config_id.id)])] + + return {'search_params': {'domain': [('id', '=', data)], + 'fields': ['name', 'product_ids', + 'time_from', 'time_to', 'state', + 'pos_ids']}} + + def _get_pos_ui_meals_planning(self, params): + """"Return the model meals_planning""" + 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..34049fc19 --- /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) 2023-TODAY Cybrosys Technologies() +# Author: Afra MP (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.product', + '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..6e550ac67 --- /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) 2023-TODAY Cybrosys Technologies() +# Author: Afra MP (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.product', + '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..86c17832d --- /dev/null +++ b/all_in_one_pos_kit/models/res_config_settings.py @@ -0,0 +1,146 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2023-TODAY Cybrosys Technologies() +# Author: Afra MP (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') + barcode_type = fields.Selection( + selection=[('barcode', 'Barcode'),('qr_code', 'QRCode')], + string='Barcode Type', + config_parameter='all_in_one_pos_kit.barcode_type', + help='Select the type of barcode to be displayed (Barcode or QRCode)') + customer_msg = fields.Boolean(string='POS Greetings', + config_parameter='pos.customer_msg', + help='Create an account if you ever create an' + 'account') + auth_token = fields.Char(string='Auth Token', + config_parameter='pos.auth_token', + help='Copy the token from your twilio console ' + 'window adn paste here') + account_sid = fields.Char(string='Account SID', + config_parameter='pos.account_sid', + help='Enter the Account SID provided by Twilio ' + 'for authentication.') + twilio_number = fields.Char(string='Twilio Number', + config_parameter='pos.twilio_number', + help='Enter the Twilio phone number used to ' + 'send the SMS.') + sms_body = fields.Text(string='Body', + help='Enter the content or message of the SMS to be' + 'sent.') + + @api.onchange('enable_service_charge') + def _onchange_enable_service_charge(self): + """When the service charge is enabled set service product and amount + by default in globally""" + if self.enable_service_charge and not self.global_product_id: + self.global_product_id = self.env['product.product'].search( + [('available_in_pos', '=', True), ('sale_ok', '=', True), + ('type', '=', 'service')], limit=1) + self.global_charge = 10.0 + else: + self.global_product_id = False + self.global_charge = 0.0 + + 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..4bc79dbd3 --- /dev/null +++ b/all_in_one_pos_kit/models/res_users.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2023-TODAY Cybrosys Technologies() +# Author: Afra MP (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)') + + @api.model + def create(self, vals): + """This method creates a new record for the ResUsers model with the + provided values. It clears the caches before creating the record.""" + self.clear_caches() + return super(ResUsers, self).create(vals) + + def write(self, vals): + """For clearing out existing values and update with new values""" + self.clear_caches() + return super(ResUsers, self).write(vals) 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..84c2e5fa5 --- /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) 2023-TODAY Cybrosys Technologies(). +# Author: Afra MP (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..b75a48159 --- /dev/null +++ b/all_in_one_pos_kit/report/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2023-TODAY Cybrosys Technologies() +# Author: Afra MP (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..f0c7533bf --- /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) 2023-TODAY Cybrosys Technologies() +# Author: Afra MP (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..55bc238dc --- /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..d613ed9df --- /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_product,access.multi.barcode.product,model_multi_barcode_product,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/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/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/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/categories.png b/all_in_one_pos_kit/static/description/assets/misc/categories.png new file mode 100644 index 000000000..bedf1e0b1 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/misc/categories.png differ diff --git a/all_in_one_pos_kit/static/description/assets/misc/check-box.png b/all_in_one_pos_kit/static/description/assets/misc/check-box.png new file mode 100644 index 000000000..42caf24b9 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/misc/check-box.png differ diff --git a/all_in_one_pos_kit/static/description/assets/misc/compass.png b/all_in_one_pos_kit/static/description/assets/misc/compass.png new file mode 100644 index 000000000..d5fed8faa Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/misc/compass.png differ diff --git a/all_in_one_pos_kit/static/description/assets/misc/corporate.png b/all_in_one_pos_kit/static/description/assets/misc/corporate.png new file mode 100644 index 000000000..2eb13edbf Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/misc/corporate.png differ diff --git a/all_in_one_pos_kit/static/description/assets/misc/customer-support.png b/all_in_one_pos_kit/static/description/assets/misc/customer-support.png new file mode 100644 index 000000000..79efc72ed Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/misc/customer-support.png differ diff --git a/all_in_one_pos_kit/static/description/assets/misc/cybrosys-logo.png b/all_in_one_pos_kit/static/description/assets/misc/cybrosys-logo.png new file mode 100644 index 000000000..cc3cc0ccf Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/misc/cybrosys-logo.png differ diff --git a/all_in_one_pos_kit/static/description/assets/misc/features.png b/all_in_one_pos_kit/static/description/assets/misc/features.png new file mode 100644 index 000000000..b41769f77 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/misc/features.png differ diff --git a/all_in_one_pos_kit/static/description/assets/misc/logo.png b/all_in_one_pos_kit/static/description/assets/misc/logo.png new file mode 100644 index 000000000..478462d3e Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/misc/logo.png differ diff --git a/all_in_one_pos_kit/static/description/assets/misc/pictures.png b/all_in_one_pos_kit/static/description/assets/misc/pictures.png new file mode 100644 index 000000000..56d255fe9 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/misc/pictures.png differ diff --git a/all_in_one_pos_kit/static/description/assets/misc/pie-chart.png b/all_in_one_pos_kit/static/description/assets/misc/pie-chart.png new file mode 100644 index 000000000..426e05244 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/misc/pie-chart.png differ diff --git a/all_in_one_pos_kit/static/description/assets/misc/right-arrow.png b/all_in_one_pos_kit/static/description/assets/misc/right-arrow.png new file mode 100644 index 000000000..730984a06 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/misc/right-arrow.png differ diff --git a/all_in_one_pos_kit/static/description/assets/misc/star.png b/all_in_one_pos_kit/static/description/assets/misc/star.png new file mode 100644 index 000000000..2eb9ab29f Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/misc/star.png differ diff --git a/all_in_one_pos_kit/static/description/assets/misc/support.png b/all_in_one_pos_kit/static/description/assets/misc/support.png new file mode 100644 index 000000000..4f18b8b82 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/misc/support.png differ diff --git a/all_in_one_pos_kit/static/description/assets/misc/whatsapp.png b/all_in_one_pos_kit/static/description/assets/misc/whatsapp.png new file mode 100644 index 000000000..d513a5356 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/misc/whatsapp.png differ diff --git a/all_in_one_pos_kit/static/description/assets/modules/1.gif b/all_in_one_pos_kit/static/description/assets/modules/1.gif new file mode 100644 index 000000000..db040e0de Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/modules/1.gif differ diff --git a/all_in_one_pos_kit/static/description/assets/modules/2.png b/all_in_one_pos_kit/static/description/assets/modules/2.png new file mode 100644 index 000000000..3415917c2 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/modules/2.png differ diff --git a/all_in_one_pos_kit/static/description/assets/modules/3.png b/all_in_one_pos_kit/static/description/assets/modules/3.png new file mode 100644 index 000000000..a29119785 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/modules/3.png differ diff --git a/all_in_one_pos_kit/static/description/assets/modules/4.png b/all_in_one_pos_kit/static/description/assets/modules/4.png new file mode 100644 index 000000000..b55ddc812 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/modules/4.png differ diff --git a/all_in_one_pos_kit/static/description/assets/modules/5.png b/all_in_one_pos_kit/static/description/assets/modules/5.png new file mode 100644 index 000000000..25ed3e0b6 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/modules/5.png differ diff --git a/all_in_one_pos_kit/static/description/assets/modules/6.png b/all_in_one_pos_kit/static/description/assets/modules/6.png new file mode 100644 index 000000000..15b141248 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/modules/6.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..fd484ea7f 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..9f333b6ec 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..01d15e744 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..b54068773 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..65b16e03b 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..724bb6488 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..c783ee0db 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..607a59bee 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..4267c2b68 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..d96155aa0 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..456e8d321 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..359a11674 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..536183ef2 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..1961ccbbf 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..e23b5e684 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..4f99eebf5 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..74a48d356 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..fa8c00ee6 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..5bd886f59 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..991a3826a 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..5463f59f5 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..7fc3c806b 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..aa9ba0006 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..4dac0c9a4 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..ccd5a0dee 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..5292da21e 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..b986a0781 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..89e1eb36a 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..240c7f431 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..af2357a1d 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..542586026 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..f17a40d37 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..4d2552430 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..1edd8bc2a 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..008ad74cb 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..ecfc28f1b 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..6b3e7914d 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..400116f0e 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..25267b4bb 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/45.png b/all_in_one_pos_kit/static/description/assets/screenshots/45.png new file mode 100644 index 000000000..833fba963 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/45.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..8eb5ee64a 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..8abafe5fb 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..3587ad08f 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/49.png b/all_in_one_pos_kit/static/description/assets/screenshots/49.png new file mode 100644 index 000000000..dd6945257 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/49.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..b9937ce16 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/50.png b/all_in_one_pos_kit/static/description/assets/screenshots/50.png new file mode 100644 index 000000000..1026b45fb Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/50.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/51.png b/all_in_one_pos_kit/static/description/assets/screenshots/51.png new file mode 100644 index 000000000..999c8affe Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/51.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/52.png b/all_in_one_pos_kit/static/description/assets/screenshots/52.png new file mode 100644 index 000000000..90c03857d Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/52.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/53.png b/all_in_one_pos_kit/static/description/assets/screenshots/53.png new file mode 100644 index 000000000..546b27ca1 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/53.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/54.png b/all_in_one_pos_kit/static/description/assets/screenshots/54.png new file mode 100644 index 000000000..0bbf0a191 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/54.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/55.png b/all_in_one_pos_kit/static/description/assets/screenshots/55.png new file mode 100644 index 000000000..ece714311 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/55.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/56.png b/all_in_one_pos_kit/static/description/assets/screenshots/56.png new file mode 100644 index 000000000..8b2ea2df0 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/56.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/57.png b/all_in_one_pos_kit/static/description/assets/screenshots/57.png new file mode 100644 index 000000000..969ed334f Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/57.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/58.png b/all_in_one_pos_kit/static/description/assets/screenshots/58.png new file mode 100644 index 000000000..ded9db787 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/58.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/59.png b/all_in_one_pos_kit/static/description/assets/screenshots/59.png new file mode 100644 index 000000000..19db5957b Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/59.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..a3aa19395 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/60.png b/all_in_one_pos_kit/static/description/assets/screenshots/60.png new file mode 100644 index 000000000..3363ab71d Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/60.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/61.png b/all_in_one_pos_kit/static/description/assets/screenshots/61.png new file mode 100644 index 000000000..f1648f197 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/61.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..af453cc8f 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..d5aba6fa2 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..83418089d 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.gif b/all_in_one_pos_kit/static/description/assets/screenshots/hero.gif new file mode 100644 index 000000000..25779d46d Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/hero.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..430f29805 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..0a32507e8 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..cd172ea20 --- /dev/null +++ b/all_in_one_pos_kit/static/description/index.html @@ -0,0 +1,1293 @@ +
+ +
+ +
+
+ Community +
+
+ Enterprise +
+
+ Odoo.sh +
+
+
+ +
+
+
+ +

+ All in One POS Kit

+

This module combines a variety of POS features.

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

Explore This + Module

+
+ + + + +
+
+ +
+

Overview +

+
+
+
+ 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, Logo For Every Point of Sale (Screen & Receipt), Category wise receipt for the Point of Sale, + Apply a fixed percentage of Tip, Remove each lines from selected order by simply clicking X button or clear all order with a single click, Send Greeting messages to Customers, + Magnify Product Image, Launch automatic MRP orders after selling through POS, Show/Hide NumPad in POS, Show Product image in POS order lines, Edit order line, + Show the count & quantity of items in the POS screen and POS receipt, Create and Edit Products directly from POS, Display time-based Products in POS. The item will vary as time goes on, + Restricts User access to POS and orders, customer can easily return or exchange their products, Allows you to set service charges. +
+
+ + + +
+
+ +
+

Features +

+
+
+
+
+ + 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. +
+
+ + Clear all order with a single click. +
+
+ + Send Greeting messages to Customers. +
+
+ + Magnify Product Image. +
+
+
+ +
+ + Available in Odoo 16.0 + Community and Enterprise. +
+
+ + 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. +
+ +
+
+ + + +
+
+ +
+

Screenshots +

+
+
+
+
+

POS Dashboard +

+

This module helps you to see the Overview of POS, here You can see the total orders, Sessions, top + customers, top products etc.

+ + + + +
+
+

POS Report +

+

It shows the 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.

+ + + + +
+
+

Advanced POS Receipt +

+

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

+ + + + +
+
+

Product Magnify Image +

+ + + + +
+
+

Show/Hide Number pad in the POS Screen +

+ + + +
+
+

POS Order Line Product Image +

+ + + +
+
+

POS Edit Order Line +

+

Edit order line through a popup. +

+ + + + +
+
+

POS Service Charges +

+

You can set service charges for an order by globally and session wise. +

+ + + + +
+
+

POS Product Exchange +

+

POS Product Exchange facilitates your customers to return or exchange their products. +

+ + + + +
+
+

Remove Order Line +

+

Remove each lines from selected order by simply clicking X button or clear all order with a single + click.

+ + + +
+
+

POS Order Line Items Count +

+

Shows the items count and total quantity of the POS order. +

+ + + + +
+
+

Product Create & Edit From POS +

+

You can directly edit or create new product from point of sale. +

+ + + + +
+
+

POS Time Based Products +

+

You can display time-based products in POS. With time, the item will change. + The product can be mentioned by the user in the pos configuration. The user can specify the product there. +

+ + + + + +
+
+

POS Direct Login +

+

Directly login to POS no need to access Backend. +

+ + + + +
+
+

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. +

+ + + + +
+
+

POS Product Multi Barcode +

+

Set multiple barcodes for a product and product variants and use these barcodes for scanning the product in POS. +

+ + + + +
+
+

Automatically launches MRP orders from POS. +

+ + + +
+
+

POS Automatic lot selection +

+

If the product category Force Removal Strategy is FEFO then first expiry product automatically add in POS order line. + If the remaining options is set then first created automatically add in POS order line. +

+ + + + +
+
+

POS Logo +

+

Logo in POS screen and POS receipt. +

+ + + + +
+
+

POS Category wise receipt +

+ + + +
+
+

POS Custom Tips +

+ + + + +
+
+

POS Customer Greeting Messages +

+

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. +

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

+ Related + Products +

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

Our Services +

+
+ +
+
+
+
+ +
+
+ Odoo + Customization
+
+ +
+
+ +
+
+ Odoo + Implementation
+
+ +
+
+ +
+
+ Odoo + Support
+
+ + +
+
+ +
+
+ Hire + Odoo + Developer
+
+ +
+
+ +
+
+ Odoo + Integration
+
+ +
+
+ +
+
+ Odoo + Migration
+
+ + +
+
+ +
+
+ Odoo + Consultancy
+
+ +
+
+ +
+
+ Odoo + Implementation
+
+ +
+
+ +
+
+ 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 86068 + 27707

+
+
+
+
+
+
+
+ +
+
+
+ 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 100644 index 000000000..18483219e --- /dev/null +++ b/all_in_one_pos_kit/static/src/advanced_receipt/js/payment.js @@ -0,0 +1,46 @@ +odoo.define('all_in_one_pos_kit.PaymentScreen', function(require) { + 'use strict'; + var rpc = require('web.rpc') + const PaymentScreen = require('point_of_sale.PaymentScreen'); + const Registries = require('point_of_sale.Registries'); + const PosPaymentReceiptExtend = PaymentScreen => class extends PaymentScreen { + //Extends the PaymentScreen component to add custom functionality. + setup() { + //Performs setup operations for the extended component. + super.setup(); + } + async validateOrder(isForceValidate) { + //Validates the order and performs additional checks if customer details are required. + if (this.env.pos.res_config_settings[this.env.pos.res_config_settings.length - 1].customer_details == true && !this.currentOrder.get_partner()) { + const { + confirmed + } = await this.showPopup('ConfirmPopup', { + title: ('Customer Required') + }); + if (confirmed) { + this.selectPartner(); + } + return false; + } + const receipt_order = await super.validateOrder(...arguments); // Call the original validateOrder() method + const codeWriter = new window.ZXing.BrowserQRCodeSvgWriter(); // Generate QR code and retrieve additional information from the server + var self = this; + rpc.query({ + model: 'pos.order', + method: 'get_invoice', + args: [this.env.pos.selectedOrder.name] + }).then(function(result) { + const address = `${result.base_url}/my/invoices/${result.invoice_id}?` + let qr_code_svg = new XMLSerializer().serializeToString(codeWriter.write(address, 150, 150)); + self.env.pos.qr_image = "data:image/svg+xml;base64," + window.btoa(qr_code_svg); + let barcode_svg = new XMLSerializer().serializeToString(codeWriter.write(result.barcode, 150, 150)); + self.env.pos.barcode_image = "data:image/svg+xml;base64," + window.btoa(barcode_svg); + self.env.pos.barcode = result.barcode + self.env.pos.invoice = result.invoice_name + }); + return receipt_order + } + } + Registries.Component.extend(PaymentScreen, PosPaymentReceiptExtend); // Extend the PaymentScreen component with the custom functionality + return PaymentScreen; +}); 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 100644 index 000000000..e54d8807c --- /dev/null +++ b/all_in_one_pos_kit/static/src/advanced_receipt/xml/OrderReceipt.xml @@ -0,0 +1,63 @@ + + + + + + +
Customer Details: +
+ +
Name: + +
+
+ +
Address: + +
+
+ +
Mobile: + +
+
+ +
Phone: + +
+
+ +
Email: + +
+
+ +
Vat: + +
+
+
+ +
Invoice Number: + +
+
+ + +
+ +
+
+ +
+ +
+
+
+
+
+
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..a3022a49e --- /dev/null +++ b/all_in_one_pos_kit/static/src/age_restricted/js/age_restrict.js @@ -0,0 +1,26 @@ +odoo.define('all_in_one_pos_kit.age_restrict', function (require) { +"use strict"; + const Registries = require('point_of_sale.Registries'); + const ProductScreen = require('point_of_sale.ProductScreen'); + //Extending the product screen and on click function of the products in product screen ,it shows warnings as age restricted product,if it is age restricted one. + const _product = (ProductScreen) => class extends ProductScreen { + //Click function of the products. + async _clickProduct(event) { + //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(event.detail.is_age_restrict == true ){ + const { confirmed } = await this.showPopup('RestrictPopup', + { + title: ('Age Restricted Product !!!!!!!'), + body:('Please get Identity proof from customer.'), + }); + if (confirmed){ + super._clickProduct(event) + } + } + else{ + super._clickProduct(event) + } + } + } + Registries.Component.extend(ProductScreen, _product); +}); 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..a4c2663b9 --- /dev/null +++ b/all_in_one_pos_kit/static/src/age_restricted/js/restrict_popup.js @@ -0,0 +1,20 @@ +odoo.define('all_in_one_pos_kit.restrict_popup', function(require) { + 'use strict'; + const AbstractAwaitablePopup = + require('point_of_sale.AbstractAwaitablePopup'); + const Registries = require('point_of_sale.Registries'); + const { _lt } = require('@web/core/l10n/translation'); + //Restrict Popup widget by extending the Abstract Awaitable popup widget + class RestrictPopup extends AbstractAwaitablePopup { + } + //Defining the template of restrict popup + RestrictPopup.template = 'RestrictPopup'; + RestrictPopup.defaultProps = { + confirmText: _lt('Approve'), + cancelText: _lt('Reject'), + title: _lt('Confirm ?'), + body: '', + }; + Registries.Component.add(RestrictPopup); + return RestrictPopup; +}); 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/category_wise_receipt/js/pos_receipt.js b/all_in_one_pos_kit/static/src/category_wise_receipt/js/pos_receipt.js new file mode 100644 index 000000000..9fc3e84a6 --- /dev/null +++ b/all_in_one_pos_kit/static/src/category_wise_receipt/js/pos_receipt.js @@ -0,0 +1,21 @@ +odoo.define('all_in_one_pos_kit.receipt', function(require) { + 'use strict'; + const OrderReceipt =require('point_of_sale.OrderReceipt'); + const Registries = require('point_of_sale.Registries'); + const CategoryOrderReceipt = OrderReceipt => + class extends OrderReceipt {//Extends the OrderReceipt component to modify the behavior of the orderlines property. + get orderlines() {//Overrides the orderlines property to categorize the order lines. + var order_lines = this.receiptEnv.orderlines; + var categ = {'category': [],'orderlines': order_lines} + // Iterate over order lines and categorize them based on product category + for (var i = 0; i <= order_lines.length - 1; i++){ + if(!categ.category.includes(order_lines[i].product.pos_categ_id[1])){ + categ.category.push(order_lines[i].product.pos_categ_id[1]); + } + } + return categ; + } + } + Registries.Component.extend(OrderReceipt, CategoryOrderReceipt); + return OrderReceipt; +}); diff --git a/all_in_one_pos_kit/static/src/category_wise_receipt/xml/pos_receipt.xml b/all_in_one_pos_kit/static/src/category_wise_receipt/xml/pos_receipt.xml new file mode 100644 index 000000000..e406cfb86 --- /dev/null +++ b/all_in_one_pos_kit/static/src/category_wise_receipt/xml/pos_receipt.xml @@ -0,0 +1,56 @@ + + + + + + +
+ + + + + +

+ +

+ +

+

+ + + + + + + + + +
+ + + + + +
+ With a + % discount +
+
+ - + +
+ + + +
+ +
+
+
+
+
+
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..9224b234c --- /dev/null +++ b/all_in_one_pos_kit/static/src/custom_tip/js/PaymentScreen.js @@ -0,0 +1,46 @@ +odoo.define('all_in_one_pos_kit.tips', function (require) { + 'use strict'; + // Extends the PaymentScreen component to add custom methods and properties for tips functionality. + const Registries = require('point_of_sale.Registries'); + const PaymentScreen = require('point_of_sale.PaymentScreen'); + const { parse } = require('web.field_utils'); + const CustomButtonPaymentScreen = (PaymentScreen) =>class extends PaymentScreen { + // Overrides the base method to perform additional setup. + setup() { + super.setup(...arguments); + } + async CustomTipButton(){//Custom method to handle the click event of the custom tip button. + 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 + 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())); + } + } + } + get 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, + }; + } + }; + // Extend the PaymentScreen component with the custom behavior + Registries.Component.extend(PaymentScreen, CustomButtonPaymentScreen); + return CustomButtonPaymentScreen; +}); 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..b69ca1ed6 --- /dev/null +++ b/all_in_one_pos_kit/static/src/custom_tip/xml/PaymentScreen.xml @@ -0,0 +1,15 @@ + + + + + + +
+ + Tip ()% +
+
+
+
+
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..1fb9ad1d0 --- /dev/null +++ b/all_in_one_pos_kit/static/src/dashboard/js/pos_dashboard.js @@ -0,0 +1,461 @@ +odoo.define('all_in_one_pos_kit.Dashboard', function(require) { + "use strict"; + var AbstractAction = require('web.AbstractAction'); + var core = require('web.core'); + const { + loadBundle + } = require("@web/core/assets"); + var session = require('web.session'); + var rpc = require('web.rpc'); + var _t = core._t; + var QWeb = core.qweb; + var date = new Date(); + var yesterday = new Date(date.getTime()); + var PosDashboard = AbstractAction.extend({ //Extended the AbstractAction to create dashboard in pos. + template: 'PosDashboard', + events: { + 'click .pos_order_today': 'pos_order_today', + 'click .pos_order': 'pos_order', + 'click .pos_total_sales': 'pos_order', + 'click .pos_session': 'pos_session', + 'click .pos_refund_orders': 'pos_refund_orders', + 'click .pos_refund_today_orders': 'pos_refund_today_orders', + 'change #pos_sales': 'onclick_pos_sales', + }, + init: function(parent, context) { //Function to Initializes all the values while loading the file + this._super(parent, context); + this.dashboards_templates = ['PosOrders', 'PosChart', 'PosCustomer']; + this.payment_details = []; + this.top_salesperson = []; + this.selling_product = []; + this.total_sale = []; + this.total_order_count = []; + this.total_refund_count = []; + this.total_session = []; + this.today_refund_total = []; + this.today_sale = []; + }, + willStart: function() { // returns the function fetch_data when page load. + var self = this; + return $.when(loadBundle(this), this._super()).then(function() { + return self.fetch_data(); + }); + }, + start: function() { //Function return render_dashboards() and render_graphs() + self = this; + this.set("title", 'Dashboard'); + return this._super().then(function() { + self.render_dashboards(); + self.render_graphs(); + self.$el.parent().addClass('oe_background_grey'); + }); + }, + fetch_data: function() { //fetch data and call rpc query to create tile. + self = this; + var def1 = this._rpc({ + model: 'pos.order', + method: 'get_refund_details' + }).then(function(result) { + self.total_sale = result['total_sale'], + self.total_order_count = result['total_order_count'] + self.total_refund_count = result['total_refund_count'] + self.total_session = result['total_session'] + self.today_refund_total = result['today_refund_total'] + self.today_sale = result['today_sale'] + }); + var def2 = self._rpc({ + model: "pos.order", + method: "get_details", + }) + .then(function(res) { + self.payment_details = res['payment_details']; + self.top_salesperson = res['salesperson']; + self.selling_product = res['selling_product']; + }); + return $.when(def1, def2); + }, + render_dashboards: function() { //return value to show in tile. + self = this; + _.each(this.dashboards_templates, function(template) { + self.$('.o_pos_dashboard').append(QWeb.render(template, { + widget: self + })); + }); + }, + render_graphs: function() { //Add function to load in dashboard. + self = this; + self.render_top_customer_graph(); + self.render_top_product_graph(); + self.render_product_category_graph(); + }, + pos_order_today: function(e) { //Click function returns today's all pos order tree view. + self = this; + yesterday.setDate(date.getDate() - 1); + e.stopPropagation(); + e.preventDefault(); + session.user_has_group('hr.group_hr_user').then(function(has_group) { + if (has_group) { + self.do_action({ + name: _t("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: self.on_reverse_breadcrumb + }) + } + }); + }, + pos_refund_orders: function(e) { //Click function returns all refund pos order tree view. + self = this; + e.stopPropagation(); + e.preventDefault(); + session.user_has_group('hr.group_hr_user').then(function(has_group) { + if (has_group) { + self.do_action({ + name: _t("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: self.on_reverse_breadcrumb + }) + } + }); + }, + pos_refund_today_orders: function(e) { //Click function returns all today's refund pos order in tree view. + self = this; + yesterday.setDate(date.getDate() - 1); + e.stopPropagation(); + e.preventDefault(); + session.user_has_group('hr.group_hr_user').then(function(has_group) { + if (has_group) { + self.do_action({ + name: _t("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: self.on_reverse_breadcrumb + }) + } + }); + }, + pos_order: function(e) { //Click function returns all pos order in tree view. + self = this; + e.stopPropagation(); + e.preventDefault(); + session.user_has_group('hr.group_hr_user').then(function(has_group) { + if (has_group) { + self.do_action({ + name: _t("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: self.on_reverse_breadcrumb + }) + } + }); + + }, + pos_session: function(e) { //Click function returns all pos session in tree view. + self = this; + e.stopPropagation(); + e.preventDefault(); + session.user_has_group('hr.group_hr_user').then(function(has_group) { + if (has_group) { + self.do_action({ + name: _t("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: self.on_reverse_breadcrumb + }) + } + }); + }, + onclick_pos_sales: function(events) { // Function to add filter in pos sales report + var ctx = this.$("#canvas_1"); + rpc.query({ + model: "pos.order", + method: "get_department", + args: [$(events.target).val()], + }).then(function(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 + } + }] + } + } + }); + }); + }, + render_top_customer_graph: function() { //Function to create top customers chart + var ctx = this.$(".top_customer"); + rpc.query({ + model: "pos.order", + method: "get_the_top_customer", + }).then(function(arrays) { + var 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 + } + }] + } + } + }); + }); + }, + render_top_product_graph: function() { //Function to create top product chart. + var ctx = this.$(".top_selling_product"); + rpc.query({ + model: "pos.order", + method: "get_the_top_products", + }).then(function(arrays) { + var 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 + } + }] + } + } + }); + }); + }, + render_product_category_graph: function() { //Function to create top categories chart + var ctx = this.$(".top_product_categories"); + rpc.query({ + model: "pos.order", + method: "get_the_top_categories", + }).then(function(arrays) { + var 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 + } + }] + } + } + }); + }); + }, + }); + core.action_registry.add('pos_dashboard', PosDashboard); + return PosDashboard; +}); 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..63f0068cb --- /dev/null +++ b/all_in_one_pos_kit/static/src/dashboard/xml/pos_dashboard.xml @@ -0,0 +1,322 @@ + + + + +
+
+
+
+
+ + +
+ +
+
+
+
+
+
+
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..a1a143090 --- /dev/null +++ b/all_in_one_pos_kit/static/src/delete_order_line/js/clear_button.js @@ -0,0 +1,35 @@ +odoo.define('all_in_one_pos_kit.DeleteOrderLinesAll', function(require) { + 'use strict'; + const PosComponent = require('point_of_sale.PosComponent'); + const ProductScreen = require('point_of_sale.ProductScreen'); + const {useListener} = require("@web/core/utils/hooks"); + const Registries = require('point_of_sale.Registries'); + class OrderLineClearALL extends PosComponent { //Represents a custom control button for clearing all orders in the Point of Sale. + setup() { //Super setup for the component. + super.setup(); + useListener('click', this.onClick); + } + async onClick() { //Handles the click event when the button is clicked. + const { + confirmed + } = await this.showPopup("ConfirmPopup", { + title: this.env._t('Clear Orders?'), + body: this.env._t('Are you sure you want to delete all orders from the cart?'), + }); + if (confirmed) { + var order = this.env.pos.get_order(); + order.get_orderlines().filter(line => line.get_product()) + .forEach(line => order.remove_orderline(line)); + } + } + } + OrderLineClearALL.template = 'OrderLineClearALL'; + ProductScreen.addControlButton({ // Add the control button to the ProductScreen component + component: OrderLineClearALL, + condition: function() { + return this.env.pos; + }, + }); + Registries.Component.add(OrderLineClearALL); + return OrderLineClearALL; +}); 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..52f1ef852 --- /dev/null +++ b/all_in_one_pos_kit/static/src/delete_order_line/js/clear_order_line.js @@ -0,0 +1,16 @@ +odoo.define('all_in_one_pos_kit.DeleteOrderLines', function(require) { + //Represents a custom component for deleting order lines in the Point of Sale. + 'use strict'; + // Import required dependencies + const Registries = require('point_of_sale.Registries'); + const Orderline = require('point_of_sale.Orderline'); + const OrderWidget = require('point_of_sale.OrderWidget'); + const OrderLineDelete = (Orderline) =>class extends Orderline {// Extend the Orderline component with custom functionality + async clear_button_fun() {//Function to clear order line product + this.trigger('numpad-click-input', { key: 'Backspace' }); + this.trigger('numpad-click-input', { key: 'Backspace' }); + } + }; + Registries.Component.extend(Orderline, OrderLineDelete); + return OrderWidget; +}); diff --git a/all_in_one_pos_kit/static/src/delete_order_line/xml/clear_button.xml b/all_in_one_pos_kit/static/src/delete_order_line/xml/clear_button.xml new file mode 100644 index 000000000..43390ac99 --- /dev/null +++ b/all_in_one_pos_kit/static/src/delete_order_line/xml/clear_button.xml @@ -0,0 +1,11 @@ + + + + +
+ + + Clear All +
+
+
diff --git a/all_in_one_pos_kit/static/src/delete_order_line/xml/clear_order_line.xml b/all_in_one_pos_kit/static/src/delete_order_line/xml/clear_order_line.xml new file mode 100644 index 000000000..86f3972f1 --- /dev/null +++ b/all_in_one_pos_kit/static/src/delete_order_line/xml/clear_order_line.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + 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..fd63991e9 --- /dev/null +++ b/all_in_one_pos_kit/static/src/exchange_product/js/all_order_screen.js @@ -0,0 +1,50 @@ +odoo.define('all_in_one_pos_kit.all_order_screen', function(require) { + const PosComponent = require('point_of_sale.PosComponent'); + const ProductScreen = require('point_of_sale.ProductScreen'); + const { useListener } = require("@web/core/utils/hooks"); + const Registries = require('point_of_sale.Registries'); + var rpc = require('web.rpc'); + class CustomOrdrScreen extends PosComponent { //Extended the PosComponent to add button popup function + setup() { //Setup method called when the component is mounted. + super.setup(); + useListener('click-order', this._onClickOrder); + this.state = { + order: this.props.orders, + pos: this.env.pos + }; + } + back() { + this.showScreen('ProductScreen'); + } + _onClickOrder(order, pos) { //Function to show popup to show exchange product it that pos order. + if (order.exchange == true) { + this.showPopup('ErrorPopup', { + title: this.env._t('Exchange order'), + body: this.env._t('Already created the Exchange order') + }); + } else { + var lines = [] + for (var i = 0; i < order.lines.length; i++) { + lines.push(order.lines[i]) + } + var self = this; + this.rpc({ + model: 'pos.order.line', + method: 'get_product_details', + args: [ + [], order.lines + ], + }).then(function(value) { + self.showPopup('ExchangeOrder', { + 'order_line': value, + 'pos': pos, + 'order_id': order.id + }); + }); + } + } + }; + CustomOrdrScreen.template = 'CustomOrdrScreen'; + Registries.Component.add(CustomOrdrScreen); + return CustomOrdrScreen; +}); 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..4b7d5e2cc --- /dev/null +++ b/all_in_one_pos_kit/static/src/exchange_product/js/exchange_order.js @@ -0,0 +1,39 @@ +odoo.define('all_in_one_pos_kit.ExchangeOrder', function(require) { + 'use strict'; + const AbstractAwaitablePopup = require('point_of_sale.AbstractAwaitablePopup'); + const Registries = require('point_of_sale.Registries'); + const { useState } = owl; + /** + * 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 + */ + class ExchangeOrder extends AbstractAwaitablePopup { + setup() {// Super the setup function and set props.line and state.line + super.setup(); + this.props.line = JSON.parse(JSON.stringify(this.props.order_line)) + this.state = useState({ + line: JSON.parse(JSON.stringify(this.props.order_line)), + pos: this.props.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.line.length; i++){ + this.env.pos.get_order().add_product(this.env.pos.db.get_product_by_id(this.props.line[i].product_id), {quantity: - this.props.line[i].qty}) + } + this.rpc({// Perform a remote procedure call (RPC) to handle the exchange order on the server side + model: 'pos.order', + method: 'get_pos_exchange_order', + args: [this.props.order_id], + }) + this.showScreen('ProductScreen');// Show the 'ProductScreen' and invoke the confirm method of the parent class + super.confirm(); + } + } + ExchangeOrder.template = 'ExchangeOrder'; + ExchangeOrder.defaultProps = { cancelKey: false }; + Registries.Component.add(ExchangeOrder); + return ExchangeOrder; +}); 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..29bbd4e76 --- /dev/null +++ b/all_in_one_pos_kit/static/src/exchange_product/js/models.js @@ -0,0 +1,19 @@ +odoo.define('all_in_one_pos_kit.models', function (require) { + "use strict"; + var { PosGlobalState, Order} = require('point_of_sale.models'); + const Registries = require('point_of_sale.Registries'); + // Extension of the PosGlobalState class to handle POS session orders. + const PosSessionOrdersPosGlobalState = (PosGlobalState) => class PosSessionOrdersPosGlobalState extends PosGlobalState { + /* + * Process the loaded data and update the POS session orders. + * @override + * @param {Object} loadedData - The loaded data containing POS orders and order lines. + */ + async _processData(loadedData) { + await super._processData(...arguments); + this.pos_orders = loadedData['pos.order']; + this.pos_order_lines = loadedData['pos.order.line']; + } + } + Registries.Model.extend(PosGlobalState, PosSessionOrdersPosGlobalState);// Extend the PosGlobalState class with the PosSessionOrdersPosGlobalState extension +}); 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..f4d4b43b9 --- /dev/null +++ b/all_in_one_pos_kit/static/src/exchange_product/js/order_button.js @@ -0,0 +1,36 @@ +odoo.define('all_in_one_pos_kit.Orders', function (require) { + 'use strict'; + const PosComponent = require('point_of_sale.PosComponent'); + const { identifyError } = require('point_of_sale.utils'); + const ProductScreen = require('point_of_sale.ProductScreen'); + const { useListener } = require("@web/core/utils/hooks"); + const Registries = require('point_of_sale.Registries'); + class OrderLineALL extends PosComponent {//Extension of the PosComponent class to add custom functionality to the order line component. + setup() {// Perform setup tasks for the component. + super.setup(); + // Attach a click listener to the component + useListener('click', this.onClick); + } + /** + * Handle the click event on the component. + * Show the 'CustomOrdrScreen' with relevant data when clicked. + */ + onClick() { + this.showScreen('CustomOrdrScreen', { + orders: this.env.pos.pos_orders, + pos: this.env.pos + }); + } + } + OrderLineALL.template = 'OrderLineALL'; + // Add the OrderLineALL component as a control button to the ProductScreen + ProductScreen.addControlButton({ + component: OrderLineALL, + condition: function() { + return this.env.pos; + }, + }); + // Register the OrderLineALL component with the Registries + Registries.Component.add(OrderLineALL); + return OrderLineALL; +}); 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..9a833e60e --- /dev/null +++ b/all_in_one_pos_kit/static/src/exchange_product/scss/pos.scss @@ -0,0 +1,16 @@ +.order-list{ + font-size: 16px; + width: 100%; +} +.order-list th, +.order-list td { + padding: 12px; +} +exchange-list{ + font-size: 16px; + width: 100%; +} +.exchange-list th, +.exchange-list td { + padding: 10px; +} 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..77ffb6a42 --- /dev/null +++ b/all_in_one_pos_kit/static/src/exchange_product/xml/all_order_screen.xml @@ -0,0 +1,53 @@ + + + + +
+
+
+ +
+ Back +
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + +
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..0f35fbbd0 --- /dev/null +++ b/all_in_one_pos_kit/static/src/exchange_product/xml/exchange_order.xml @@ -0,0 +1,36 @@ + + + + + + + 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..3d7b9d2e9 --- /dev/null +++ b/all_in_one_pos_kit/static/src/exchange_product/xml/order_button.xml @@ -0,0 +1,10 @@ + + + + +
+ + All Order's +
+
+
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..4b35283d8 --- /dev/null +++ b/all_in_one_pos_kit/static/src/mass_edit/js/pos_mass_edit_button.js @@ -0,0 +1,40 @@ +odoo.define('all_in_one_pos_kit.pos_mass_edit_button', function(require) { +'use strict'; + const Registries = require('point_of_sale.Registries'); + const PosComponent = require('point_of_sale.PosComponent'); + const ProductScreen = require('point_of_sale.ProductScreen'); + const { useListener } = require("@web/core/utils/hooks"); + class MassEditButton extends PosComponent { +// Extend the POS session to add order line edit button + setup() { + super.setup(); + useListener('click', this.onClick); + } + async onClick() { +// Order line button Onclick() + var order_line = this.env.pos.get_order().get_orderlines(); + if (!order_line.length){ + // Popup if no product in pos order line + return this.showPopup('ErrorPopup', { + title: this.env._t('Order is Empty'), + body: this.env._t('You need to add product.'), + }); + } + const {confirmed} = await + this.showPopup("MassEditPopup", { +// Edit order line button popup action + title: this.env._t('Edit Order Line'), + body: order_line, + }); + } + } + MassEditButton.template = 'MassEditButton'; + ProductScreen.addControlButton({ + component: MassEditButton, + condition: function() { + return this.env.pos; + }, + }); + Registries.Component.add(MassEditButton); + return MassEditButton; +}); 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..37894e04c --- /dev/null +++ b/all_in_one_pos_kit/static/src/mass_edit/js/pos_mass_edit_popup.js @@ -0,0 +1,26 @@ +odoo.define('all_in_one_pos_kit.pos_mass_edit_popup', function(require) { + 'use strict'; + const AbstractAwaitablePopup = require('point_of_sale.AbstractAwaitablePopup'); + const Registries = require('point_of_sale.Registries'); + class MassEditPopup extends AbstractAwaitablePopup { + async confirm(){ + // Function for confirm button inside popup + window.location.reload(); + } + sendInput(key) { + // Function to change quantity into 0 + _.each(this.props.body, function(edit) { + if (edit.id == key){ + edit.quantity = 0 + } + }); + } + } + MassEditPopup.template = 'MassEditPopup'; + MassEditPopup.defaultProps = { + confirm: "Confirm", + cancel: "Cancel", + }; + Registries.Component.add(MassEditPopup); + return MassEditPopup; +}); 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..cbf4f92e6 --- /dev/null +++ b/all_in_one_pos_kit/static/src/mass_edit/xml/pos_mass_edit_button.xml @@ -0,0 +1,10 @@ + + + + +
+ + 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..b2ba5cabe --- /dev/null +++ b/all_in_one_pos_kit/static/src/mass_edit/xml/pos_mass_edit_popup.xml @@ -0,0 +1,76 @@ + + + + + + + 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 100644 index 000000000..7798b58e8 --- /dev/null +++ b/all_in_one_pos_kit/static/src/multi_barcode/js/pos_scan.js @@ -0,0 +1,61 @@ +odoo.define('all_in_one_pos_kit.product', function(require) { + "use strict"; + var rpc = require('web.rpc'); + var DB = require('point_of_sale.DB'); + var utils = require('web.utils'); + const Registries = require('point_of_sale.Registries'); + DB.include({ // Extends the DB object to add multi barcode to product. + init: function(options) { //Initializes the object. + this._super.apply(this, arguments); + }, + add_products: function(products) { //Function to add multi barcode product in pos session. + 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 = utils.unaccent(this._product_search_string(product)); + var categ_id = product.pos_categ_id ? product.pos_categ_id[0] : this.root_category_id; + product.product_tmpl_id = product.product_tmpl_id[0]; + 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) { + this.product_by_barcode[product.barcode] = product; + } + for (var t = 0; t < product.product_multi_barcodes_ids.length; t++) { + var self = this; + rpc.query({ + model: 'multi.barcode.product', + method: 'get_barcode_val', + args: [product.product_multi_barcodes_ids[t], product.id], + }).then(function(barcode_val) { + self.product_by_barcode[barcode_val[0]] = self.product_by_id[barcode_val[1]]; + }); + } + } + }, + }); +}); 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/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..7d1251525 --- /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..6aec94759 --- /dev/null +++ b/all_in_one_pos_kit/static/src/pos_auto_lot/js/auto_lot.js @@ -0,0 +1,82 @@ +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_logo/xml/pos_recipt_views.xml b/all_in_one_pos_kit/static/src/pos_logo/xml/pos_recipt_views.xml new file mode 100755 index 000000000..dd4e27c72 --- /dev/null +++ b/all_in_one_pos_kit/static/src/pos_logo/xml/pos_recipt_views.xml @@ -0,0 +1,15 @@ + + + + + +
+ + + +
+
+
+
diff --git a/all_in_one_pos_kit/static/src/pos_logo/xml/pos_screen_image_view.xml b/all_in_one_pos_kit/static/src/pos_logo/xml/pos_screen_image_view.xml new file mode 100755 index 000000000..f5b85f9e1 --- /dev/null +++ b/all_in_one_pos_kit/static/src/pos_logo/xml/pos_screen_image_view.xml @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/all_in_one_pos_kit/static/src/pos_mrp_order/js/models.js b/all_in_one_pos_kit/static/src/pos_mrp_order/js/models.js new file mode 100644 index 000000000..8e68a10ff --- /dev/null +++ b/all_in_one_pos_kit/static/src/pos_mrp_order/js/models.js @@ -0,0 +1,63 @@ +odoo.define('all_in_one_pos_kit.models_mrp_order', function (require) { + "use strict"; + const PaymentScreen = require('point_of_sale.PaymentScreen'); + const Registries = require('point_of_sale.Registries'); + var rpc = require('web.rpc'); + //Extends the PaymentScreen to include functionality for creating manufacturing orders from pos. + const MRPPaymentScreen = (PaymentScreen) => + class extends PaymentScreen { + constructor() { + super(...arguments); + } + // Creates a manufacturing order based on the order lines with positive quantities. + createMRP(){ + const order = this.currentOrder; + var order_line = order.get_orderlines() + 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){ + rpc.query({ + model: 'mrp.production', + method: 'create_mrp_from_pos', + args: [1,list_product], + }); + } + } + } + //Validates the order and creates manufacturing orders if applicable. Overrides the existing validateOrder method. + async validateOrder(isForceValidate) { + if(this.env.pos.config.cash_rounding) { + if(!this.env.pos.get_order().check_paymentlines_rounding()) { + this.showPopup('ErrorPopup', { + title: this.env._t('Rounding error in payment lines'), + body: this.env._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(); + } + }; + Registries.Component.extend(PaymentScreen, MRPPaymentScreen); + return MRPPaymentScreen; +}); 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..e887fe428 --- /dev/null +++ b/all_in_one_pos_kit/static/src/pos_num_show_hide/js/pos_numpad.js @@ -0,0 +1,13 @@ +odoo.define('all_in_one_pos_kit.show_hide_numpad', function (require) { + "use strict"; + const ProductScreen = require('point_of_sale.ProductScreen'); + const { patch } = require('web.utils'); + //Patch the ProductScreen component to add a method for showing or hiding the numpad. + patch(ProductScreen.prototype, 'hide_show_numpad', { + NumpadVisibility(ev) { + $(ev.target).parents().find('.pads').slideToggle('slow', function() { + $(ev.target).parents().find('.numpad-toggle').toggleClass('fa-eye fa-eye-slash'); + }); + } + }) +}); 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..6ee1c4fc0 --- /dev/null +++ b/all_in_one_pos_kit/static/src/pos_num_show_hide/xml/pos.xml @@ -0,0 +1,13 @@ + + + + + +
+ + +
+
+
+
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..0f880b6e6 --- /dev/null +++ b/all_in_one_pos_kit/static/src/pos_report/js/pos_report.js @@ -0,0 +1,187 @@ +odoo.define('all_in_one_pos_kit.pos_report', function(require) { + 'use strict'; + var AbstractAction = require('web.AbstractAction'); + var core = require('web.core'); + var rpc = require('web.rpc'); + var QWeb = core.qweb; + var _t = core._t; + var time = require('web.time'); + var framework = require('web.framework'); + var session = require('web.session'); + /** + * PosReport is a custom Odoo action for generating and displaying POS reports. + * It provides functionality for applying filters, printing PDF reports, printing XLSX reports, + * viewing individual POS orders, and loading report data from the server. + */ + var PosReport = AbstractAction.extend({ + template: 'PosReport', + events: { + 'click #apply_filter': 'apply_filter', + 'click #pdf': 'print_pdf', + 'click #xlsx': 'print_xlsx', + 'click .view_pos_order': 'button_view_order', + 'mousedown div.input-group.date[data-target-input="nearest"]': '_onCalendarIconClick', + }, + init: function(parent, action) {// Initializes the PosReport instance. @param {Object} parent - The parent widget. @param {Object} action - The action data. + this._super(parent, action); + this.report_lines = action.report_lines; + this.wizard_id = action.context.wizard | null; + }, + start: function() {//Starts the PosReport. Creates a new POS report and loads the initial data. + var self = this; + self.initial_render = true; + rpc.query({ + model: 'pos.report', + method: 'create', + args: [{}] + }).then(function(res) { + self.wizard_id = res; + self.load_data(self.initial_render); + self.apply_filter(); + }) + }, + _onCalendarIconClick: function(ev) {//Initializes the datetime picker when the calendar icon is clicked. @param {Event} ev - The click event. + $(ev.currentTarget).datetimepicker({ + minDate: moment({ + y: 1000 + }), + maxDate: moment().add(200, 'y'), + calendarWeeks: true, + defaultDate: moment().format(), + sideBySide: true, + buttons: { + showClear: true, + showClose: true, + showToday: true, + }, + icons: { + date: 'fa fa-calendar', + }, + locale: moment.locale(), + format: time.getLangDateFormat(), + widgetParent: 'body', + allowInputToggle: true, + }); + }, + load_data: function(initial_render = true) {//Loads data for the POS report. @param {boolean} [initial_render=true] - Whether it's the initial render. + self = this; + self._rpc({ + model: 'pos.report', + method: 'pos_report', + args: [[this.wizard_id]], + }).then(function(datas) { + if (initial_render) { + self.$('.filter_view_pr').html(QWeb.render('posFilterView', { + filter_data: datas['filters'], + })); + self.$el.find('.report_type').select2({ + placeholder: ' Report Type...', + }); + } + if (datas['orders']) + self.$('.table_view_pr').html(QWeb.render('PosOrderTable', { + filter: datas['filters'], + order: datas['orders'], + report_lines: datas['report_lines'], + main_lines: datas['report_main_line'] + })); + }) + }, + print_pdf: function(e) {//Prints the POS report as a PDF. + e.preventDefault(); + self = this; + self._rpc({ + model: 'pos.report', + method: 'pos_report', + args: [[self.wizard_id]], + }).then(function(data) { + var action = { + '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': data}, + 'context': { + 'active_model': 'pos.report', + 'landscape': 1, + 'pos_order_report': true + }, + 'display_name': 'PoS Order', + }; + return self.do_action(action); + }); + }, + print_xlsx: function() {//Prints the POS report as an XLSX file. + self = this; + self._rpc({ + model: 'pos.report', + method: 'pos_report', + args: [[self.wizard_id]], + }).then(function(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), + }, + }; + self.downloadXlsx(action); + }); + }, + downloadXlsx: function (action){//Downloads the XLSX file from the server.@param {Object} action - The action data. + framework.blockUI(); + session.get_file({ + url: '/pos_dynamic_xlsx_reports', + data: action.data, + complete: framework.unblockUI, + error: (error) => this.call('crash_manager', 'rpc_error', error), + }); + }, + button_view_order: function(event) {//Opens a POS order in a new window. + event.preventDefault(); + this.do_action({ + name: _t("PoS Order"), + type: 'ir.actions.act_window', + res_model: 'pos.order', + view_type: 'form', + domain: [['id', '=', $(event.target).closest('.view_pos_order').attr('id')]], + views: [[false, 'list'], + [false, 'form']], + target: 'current' + }); + }, + apply_filter: function() {//Applies the selected filters and reloads the POS report data. + self = this; + self.initial_render = false; + var filter_data_selected = {}; + if (this.$el.find('.datetimepicker-input[name="date_from"]').val()) { + filter_data_selected.date_from = moment(this.$el.find('.datetimepicker-input[name="date_from"]').val(), time.getLangDateFormat()).locale('en').format('YYYY-MM-DD'); + } + if (this.$el.find('.datetimepicker-input[name="date_to"]').val()) { + filter_data_selected.date_to = moment(this.$el.find('.datetimepicker-input[name="date_to"]').val(), time.getLangDateFormat()).locale('en').format('YYYY-MM-DD'); + } + if (this.$el.find(".report_type").length) { + var report_res = this.el.querySelector("#report_res") + filter_data_selected.report_type = this.$el.find(".report_type")[1].value + report_res.value = this.$el.find(".report_type")[1].value + report_res.innerHTML = report_res.value; + if (this.$el.find(".report_type")[1].value == "") { + report_res.innerHTML = "report_by_order"; + } + } + rpc.query({ + model: 'pos.report', + method: 'write', + args: [self.wizard_id, filter_data_selected], + }).then(function(res) { + self.initial_render = false; + self.load_data(self.initial_render); + }); + }, + }); + core.action_registry.add("pos_r", PosReport); + return 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..cda1dec88 --- /dev/null +++ b/all_in_one_pos_kit/static/src/pos_report/xml/pos_report_view.xml @@ -0,0 +1,501 @@ + + + + +
+
+
+

Point of Sale Report

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

+
+
+ + + Date Range + + +
+
+ + + Report Type: + + + +
+
+ +
+
+
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PoSOrderDate OrderCustomerSalesmanTotal QtyAmount TotalNote
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PoSOrderDate OrderCustomerSalesmanProduct CodeProduct NamePrice 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/ProductCreatePopup.js b/all_in_one_pos_kit/static/src/product_creation/js/ProductCreatePopup.js new file mode 100644 index 000000000..50f49ca1b --- /dev/null +++ b/all_in_one_pos_kit/static/src/product_creation/js/ProductCreatePopup.js @@ -0,0 +1,95 @@ +/**@odoo-module **/ +import AbstractAwaitablePopup from "point_of_sale.AbstractAwaitablePopup"; +import Registries from "point_of_sale.Registries"; +import { isConnectionError } from "point_of_sale.utils"; +import {useListener} from "@web/core/utils/hooks"; +let img = ""; +let base64_img = ""; +class CreateProductPopup extends AbstractAwaitablePopup {// A custom popup component for creating a new product. + setup() {//Sets up the component by initializing the event listener. + super.setup(); + useListener("change", "#img_field", this._onChangeImgField); + } + async _onChangeImgField(ev) { + // This function will work when adding image to the image field + try { + const reader = new FileReader(); + reader.readAsDataURL(ev.target.files[0]); + reader.onload = await + function () { + img = reader.result; + base64_img = reader.result.toString().replace(/^data:(.*,)?/, ""); + const myTimeout = setTimeout(() => { + let element = + ""; + $(ev.srcElement.offsetParent).find('.product-img-create-popup').append($(element)); + }, 100); + }; + reader.onerror = (error) => + reject(() => { + console.log("error", error); + }); + } catch (error) { + if (isConnectionError(error)) { + this.showPopup("ErrorPopup", { + title: this.env._t("Network Error"), + body: this.env._t("Cannot access Product screen if offline."), + }); + } else { + throw error; + } + } + } + async confirm(ev) {//Confirms the creation of the product. + let img = $(this.el).find("#img_field")[0].value; + let name = $(this.el).find("#display_name")[0].value; + let price = $(this.el).find("#list_price")[0].value; + let cost = $(this.el).find("#cost_price")[0].value; + let category = $(this.el).find("#product_category")[0].value; + let barcode = $(this.el).find("#barcode")[0].value; + let default_code = $(this.el).find("#default_code")[0].value; + let values = {}; + if (base64_img) { + values["image_1920"] = base64_img; + } + if (name) { + values["name"] = name; + } + if (cost) { + values["standard_price"] = cost; + } + if (price) { + values["lst_price"] = price; + } + if (category) { + values["pos_categ_id"] = category; + } + if (barcode) { + values["barcode"] = barcode; + } + if (default_code) { + values["default_code"] = default_code; + } + values["available_in_pos"] = true; + await this.rpc({ + model: "product.product", + method: "create", + args: [values], + }).then((result) => { + if (result) { + this.showNotification(_.str.sprintf(this.env._t('%s - Product Created'), name), 3000); + } else { + this.showNotification(_.str.sprintf(this.env._t('%s - Product Creation Failed'), name), 3000); + } + }); + this.env.posbus.trigger("close-popup", { + popupId: this.props.id, + response: { + confirmed: false, + payload: null, + }, + }); + } +} +CreateProductPopup.template = "CreateProductPopup"; +Registries.Component.add(CreateProductPopup); diff --git a/all_in_one_pos_kit/static/src/product_creation/js/ProductEditPopup.js b/all_in_one_pos_kit/static/src/product_creation/js/ProductEditPopup.js new file mode 100644 index 000000000..7b43efa49 --- /dev/null +++ b/all_in_one_pos_kit/static/src/product_creation/js/ProductEditPopup.js @@ -0,0 +1,109 @@ +/**@odoo-module **/ +import AbstractAwaitablePopup from "point_of_sale.AbstractAwaitablePopup"; +import Registries from "point_of_sale.Registries"; +import { useListener } from "@web/core/utils/hooks"; +let base64_img = ""; +let img = ""; +// A custom popup component for editing a product in the Point of Sale. +class EditProductPopup extends AbstractAwaitablePopup { + setup() {//Sets up the component by registering the change event listener for the image field. + super.setup(); + useListener("change", "#img_field", this._onChangeImgField); + } + //Handles the change event of the image field and updates the image preview. @param {Event} ev - The change event. + async _onChangeImgField(ev) { + try { + // This function will work when adding image to the image field + var self = this; + 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(() => { + let element = + ""; + $(ev.srcElement.offsetParent).find('.product-img-create-popup').append($(element)); + }, 100); + }; + reader.onerror = (error) => + reject(() => { + console.log("error", error); + }); + } catch (error) { + if (isConnectionError(error)) { + this.showPopup("ErrorPopup", { + title: this.env._t("Network Error"), + body: this.env._t("Cannot access Product screen if offline."), + }); + } else { + throw error; + } + } + } + confirm() {//Performs the product update based on the modified values and closes the popup. + let values = {}; + if (base64_img) { + values["image_1920"] = base64_img; + } + if ($(this.el).find("#display_name")[0].value != this.props.product.display_name) { + values["name"] = $(this.el).find("#display_name")[0].value; + } + if ($(this.el).find("#list_price")[0].value != this.props.product.list_price) { + values["lst_price"] = $(this.el).find("#list_price")[0].value; + } + if ($(this.el).find("#product_category")[0].value) { + values["pos_categ_id"] = parseInt($(this.el).find("#product_category")[0].value); + } + if ($(this.el).find("#barcode")[0].value) { + values["barcode"] = parseInt($(this.el).find("#barcode")[0].value); + } + if ($(this.el).find("#default_code")[0].value) { + values["default_code"] = parseInt($(this.el).find("#default_code")[0].value); + } + this.rpc({ + model: "product.product", + method: "write", + args: [this.props.product.id, values], + }).then((result) => { + if (result) { + this.props.product.display_name = $(this.el).find("#display_name")[0].value; + this.props.product.lst_price = $(this.el).find("#list_price")[0].value; + this.props.product.barcode = $(this.el).find("#barcode")[0].value; + this.props.product.default_code = $(this.el).find("#default_code")[0].value; + this.props.product.pos_categ_id = [ + parseInt($(this.el).find("#product_category")[0].value), + $(this.el).find("#product_category")[0].selectedOptions[0].title, + ]; + this.showNotification(_.str.sprintf(this.env._t("%s - Product Updated"),$(this.el).find("#display_name")[0].value),3000); + } else { + this.showNotification(_.str.sprintf(this.env._t("%s - Product Updation Failed"),$(this.el).find("#display_name")[0].value),3000); + } + this.env.posbus.trigger("close-popup", { + popupId: this.props.id, + response: { + confirmed: false, + payload: null, + }, + }); + }); + } + cancel() {//Cancels the editing and closes the popup. + this.env.posbus.trigger("close-popup", { + popupId: this.props.id, + response: { + confirmed: false, + payload: null, + }, + }); + } + get imageUrl() {//Retrieves the URL of the product image. @returns {string} The URL of the product image. + const product = this.props.product; + return `/web/image?model=product.product&field=image_128&id=${product.id}&unique=${product.write_date}`; + } +} +EditProductPopup.template = "EditProductPopup"; +Registries.Component.add(EditProductPopup); diff --git a/all_in_one_pos_kit/static/src/product_creation/js/ProductLine.js b/all_in_one_pos_kit/static/src/product_creation/js/ProductLine.js new file mode 100644 index 000000000..0c2983340 --- /dev/null +++ b/all_in_one_pos_kit/static/src/product_creation/js/ProductLine.js @@ -0,0 +1,17 @@ +/**@odoo-module **/ +import PosComponent from "point_of_sale.PosComponent"; +import Registries from "point_of_sale.Registries"; +import { Gui } from 'point_of_sale.Gui'; +class ProductLine extends PosComponent {//A custom component representing a single line in the product list view. + get imageUrl() {//Retrieves the URL of the product's image. @returns {string} The URL of the product's image. + const product = this.props.product; + return `/web/image?model=product.product&field=image_128&id=${product.id}&unique=${product.write_date}`; + } + async editCurrentProduct() {//Opens the edit product popup for the current product. + await Gui.showPopup("EditProductPopup", { + product: this.props.product, + }); + } +} +ProductLine.template = "ProductLine"; +Registries.Component.add(ProductLine); 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..f74867194 --- /dev/null +++ b/all_in_one_pos_kit/static/src/product_creation/js/pos_product_screen.js @@ -0,0 +1,47 @@ +/**@odoo-module **/ +import PosComponent from "point_of_sale.PosComponent"; +import ProductScreen from "point_of_sale.ProductScreen"; +import { useListener } from "@web/core/utils/hooks"; +import Registries from "point_of_sale.Registries"; +import { Gui } from "point_of_sale.Gui"; +import { isConnectionError } from "point_of_sale.utils"; +//A custom component that adds a control button to set the product list based on the selected category. +export class SetProductListButton extends PosComponent { + setup() {//Sets up the component by initializing the event listener. + super.setup(); + useListener("click", this.onClick); + } + get productsList() {//Retrieves the product list based on the selected category. @returns {Array} The sorted product list. + let list = []; + list = this.env.pos.db.get_product_by_category( + this.env.pos.selectedCategoryId + ); + return list.sort(function (a, b) { + return a.display_name.localeCompare(b.display_name); + }); + } + async onClick() {//Handles the click event when the button is clicked. + try { + let list = this.productsList; + const screen = "ProductListScreen"; + Gui.showScreen(screen); + } catch (error) { + if (isConnectionError(error)) { + this.showPopup("ErrorPopup", { + title: this.env._t("Network Error"), + body: this.env._t("Cannot access Product screen if offline."), + }); + } else { + throw error; + } + } + } +} +SetProductListButton.template = "SetProductListButton"; +ProductScreen.addControlButton({ + component: SetProductListButton, + condition: function () { + return true; + }, +}); +Registries.Component.add(SetProductListButton); diff --git a/all_in_one_pos_kit/static/src/product_creation/js/productScreen.js b/all_in_one_pos_kit/static/src/product_creation/js/productScreen.js new file mode 100644 index 000000000..332c0c6df --- /dev/null +++ b/all_in_one_pos_kit/static/src/product_creation/js/productScreen.js @@ -0,0 +1,66 @@ +/**@odoo-module **/ +import Registries from "point_of_sale.Registries"; +import PosComponent from "point_of_sale.PosComponent"; +import { useRef } from "@odoo/owl"; +//A custom component representing the screen for displaying a list of products. +class ProductListScreen extends PosComponent { + setup() {//Sets up the component. + super.setup(); + this.searchWordInputRef = useRef("search-word-input-product"); + this.state = { + search: null, + }; + } + createProduct() {//Opens the create product popup. + this.showPopup("CreateProductPopup", { + product: this.props.product, + }); + } + get products() {//Retrieves the list of products based on the search input and category. @returns {Array} The list of 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); + }); + } + async updateProductList(event) {//Updates the product list based on the search input. + this.state.search = event.target.value; + if (event.code === "Enter") { + this._onPressEnterKey(); + } else { + this.render(true); + } + } + async _onPressEnterKey() {//Handles the "Enter" key press event. + if (!this.state.search) return; + if (!this.env.pos.isEveryProductLoaded) { + const result = await this.products; + this.showNotification( + _.str.sprintf( + this.env._t('%s Product(s) found for "%s".'), + result.length, + this.state.search + ), + 3000 + ); + if (!result.length) this._clearSearch(); + } + } + _clearSearch() {//Clears the search input and resets the state + this.searchWordInputRef.el.value = ""; + this.state.search = ""; + this.render(true); + } + back() {//Navigates back to the product screen. + this.showScreen('ProductScreen'); + } +} +ProductListScreen.template = "ProductListScreen"; +Registries.Component.add(ProductListScreen); diff --git a/all_in_one_pos_kit/static/src/product_creation/xml/ProductCreatePopup.xml b/all_in_one_pos_kit/static/src/product_creation/xml/ProductCreatePopup.xml new file mode 100644 index 000000000..68bd41a75 --- /dev/null +++ b/all_in_one_pos_kit/static/src/product_creation/xml/ProductCreatePopup.xml @@ -0,0 +1,90 @@ + + + + + + + diff --git a/all_in_one_pos_kit/static/src/product_creation/xml/ProductEditPopup.xml b/all_in_one_pos_kit/static/src/product_creation/xml/ProductEditPopup.xml new file mode 100644 index 000000000..fd92ce762 --- /dev/null +++ b/all_in_one_pos_kit/static/src/product_creation/xml/ProductEditPopup.xml @@ -0,0 +1,93 @@ + + + + + + + diff --git a/all_in_one_pos_kit/static/src/product_creation/xml/ProductLine.xml b/all_in_one_pos_kit/static/src/product_creation/xml/ProductLine.xml new file mode 100644 index 000000000..6c363db5a --- /dev/null +++ b/all_in_one_pos_kit/static/src/product_creation/xml/ProductLine.xml @@ -0,0 +1,49 @@ + + + + + + +
+ + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+