diff --git a/all_in_one_pos_kit/README.rst b/all_in_one_pos_kit/README.rst new file mode 100755 index 000000000..67751c46f --- /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. + +Configuration +============= +* No additional configurations needed + +Company +------- +* `Cybrosys Techno Solutions `__ + +License +------- +GNU AFFERO GENERAL PUBLIC LICENSE Version 3 (AGPL v3) +(https://www.gnu.org/licenses/agpl-3.0-standalone.html) + +Credits +------- +* Developer: (V15) Jumana Haseen @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 further information, please visit `Our Website `__ + +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..023a31e06 --- /dev/null +++ b/all_in_one_pos_kit/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Jumana Haseen (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..4cafbd73b --- /dev/null +++ b/all_in_one_pos_kit/__manifest__.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Jumana Haseen (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': '15.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', 'sale', 'account','web','stock'], + 'external_dependencies': {'python': ['twilio', 'pandas']}, + 'data': [ + 'security/all_in_one_pos_kit_security.xml', + 'security/ir.model.access.csv', + 'views/pos_report_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_greetings_views.xml', + 'views/meals_planning_views.xml', + 'report/pos_order_report.xml', + ], + 'assets': { + 'point_of_sale.assets': [ + 'web/static/lib/zxing-library/zxing-library.js', + 'all_in_one_pos_kit/static/src/advanced_receipt/js/payment.js', + 'all_in_one_pos_kit/static/src/custom_tip/js/PaymentScreen.js', + 'all_in_one_pos_kit/static/src/exchange_product/js/models.js', + 'all_in_one_pos_kit/static/src/exchange_product/js/AllOrderScreen.js', + 'all_in_one_pos_kit/static/src/exchange_product/js/OrderButton.js', + 'all_in_one_pos_kit/static/src/exchange_product/js/ExchangeOrder.js', + 'all_in_one_pos_kit/static/src/exchange_product/scss/pos.scss', + 'all_in_one_pos_kit/static/src/multi_barcode/js/search_bar.js', + 'all_in_one_pos_kit/static/src/multi_barcode/js/ProductsWidgetControlPanel.js', + 'all_in_one_pos_kit/static/src/multi_barcode/js/pos_scan.js', + 'all_in_one_pos_kit/static/src/order_item_count/js/*', + 'all_in_one_pos_kit/static/src/order_line_image/css/order_line_image.css', + 'all_in_one_pos_kit/static/src/pos_auto_lot/js/auto_lot.js', + 'all_in_one_pos_kit/static/src/pos_logo/js/pos_image_field.js', + 'all_in_one_pos_kit/static/src/pos_logo/js/pos_receipt_image.js', + 'all_in_one_pos_kit/static/src/pos_mrp_order/js/models.js', + 'all_in_one_pos_kit/static/src/product_creation/js/product_create_popup.js', + 'all_in_one_pos_kit/static/src/product_creation/js/product_create_button.js', + 'all_in_one_pos_kit/static/src/product_magnify_image/js/MagnifyProductPopup.js', + 'all_in_one_pos_kit/static/src/product_magnify_image/js/ProductScreen.js', + 'all_in_one_pos_kit/static/src/product_magnify_image/css/pos_magnify_image.css', + 'all_in_one_pos_kit/static/src/time_based_product/js/*', + 'all_in_one_pos_kit/static/src/age_restricted/js/*', + 'all_in_one_pos_kit/static/src/service_charge/js/service_charge_button.js', + 'all_in_one_pos_kit/static/src/age_restricted/js/age_restrict.js', + 'all_in_one_pos_kit/static/src/age_restricted/js/restrict_popup.js', + ], + 'web.assets_backend': [ + 'all_in_one_pos_kit/static/src/category_wise_receipt/js/Screens/ReceiptScreen/OrderReceipt.js', + 'all_in_one_pos_kit/static/src/delete_order_line/js/*', + 'all_in_one_pos_kit/static/src/dashboard/js/pos_dashboard.js', + 'all_in_one_pos_kit/static/src/dashboard/css/pos_dashboard.css', + 'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.bundle.js', + 'all_in_one_pos_kit/static/src/mass_edit/js/pos_mass_edit_popup.js', + 'all_in_one_pos_kit/static/src/mass_edit/js/pos_mass_edit_button.js', + 'all_in_one_pos_kit/static/src/pos_num_show_hide/js/pos_numpad.js', + 'all_in_one_pos_kit/static/src/pos_report/css/*', + 'all_in_one_pos_kit/static/src/pos_report/js/*', + ], + 'web.assets_qweb': [ + 'all_in_one_pos_kit/static/src/advanced_receipt/xml/order_receipt_templates.xml', + 'all_in_one_pos_kit/static/src/category_wise_receipt/xml/Screens/ReceiptScreen/OrderReceipt.xml', + 'all_in_one_pos_kit/static/src/custom_tip/xml/PaymentScreen.xml', + 'all_in_one_pos_kit/static/src/delete_order_line/xml/*', + 'all_in_one_pos_kit/static/src/dashboard/xml/pos_dashboard.xml', + 'all_in_one_pos_kit/static/src/exchange_product/xml/AllOrderScreen.xml', + 'all_in_one_pos_kit/static/src/exchange_product/xml/ExchangeOrder.xml', + 'all_in_one_pos_kit/static/src/exchange_product/xml/OrderButton.xml', + 'all_in_one_pos_kit/static/src/mass_edit/xml/pos_mass_edit_popup.xml', + 'all_in_one_pos_kit/static/src/mass_edit/xml/pos_mass_edit_button.xml', + 'all_in_one_pos_kit/static/src/order_item_count/xml/*', + 'all_in_one_pos_kit/static/src/order_line_image/xml/pos_order_line.xml', + 'all_in_one_pos_kit/static/src/pos_logo/xml/pos_screen_image_views.xml', + 'all_in_one_pos_kit/static/src/pos_logo/xml/pos_ticket_views.xml', + 'all_in_one_pos_kit/static/src/pos_num_show_hide/xml/pos.xml', + 'all_in_one_pos_kit/static/src/pos_report/xml/*', + 'all_in_one_pos_kit/static/src/product_creation/xml/product_create_button.xml', + 'all_in_one_pos_kit/static/src/product_creation/xml/product_create_popup.xml', + 'all_in_one_pos_kit/static/src/product_magnify_image/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' + ] + }, + 'images': ['static/description/banner.jpg'], + 'license': 'AGPL-3', + 'installable': True, + 'auto_install': False, + 'application': 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..eeb4f909c --- /dev/null +++ b/all_in_one_pos_kit/controllers/__init__.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Jumana Haseen (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..c15da0293 --- /dev/null +++ b/all_in_one_pos_kit/controllers/all_in_one_pos_kit.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Jumana Haseen (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +############################################################################### +from odoo import http +from odoo.http import request + + +class PosProductCreation(http.Controller): + + @http.route('/create_product', type="json", auth="none") + def create_product(self, category, name, price, product_reference, + unit_measure, product_categories, barcode): + """Function to create a product""" + if category == 'Consumable': + product_category = 'consu' + elif category == 'Service': + product_category = 'service' + elif category == 'Stockable': + product_category = 'product' + else: + product_category = '' + request.env['product.template'].sudo().create({ + 'name': name, + 'type': product_category, + 'default_code': product_reference, + 'list_price': float(price), + 'uom_id': int(unit_measure), + 'uom_po_id': int(unit_measure), + 'categ_id': int(product_categories), + 'available_in_pos': True, + 'barcode': barcode, + }) 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..7b91a0ca0 --- /dev/null +++ b/all_in_one_pos_kit/controllers/xlsx_report.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies(). +# Author: Jumana Haseen (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.addons.web.controllers.main import _serialize_exception +from odoo.tools import html_escape + + +class TBXLSXReportController(http.Controller): + """A controller for generating and serving dynamic XLSX reports for POS + (Point of Sale) in Odoo.""" + @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 return a dynamic XLSX report based on the provided + parameters. + """ + uid = request.session.uid + report_obj = request.env[model].with_user(uid) + options = options + token = 'dummy-because-api-expects-one' + 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) + response.set_cookie('fileToken', token) + return response + except Exception as e: + se = _serialize_exception(e) + error = { + 'code': 200, + 'message': 'Odoo Server Error', + 'data': se + } + 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..2b5e04e59 --- /dev/null +++ b/all_in_one_pos_kit/doc/RELEASE_NOTES.md @@ -0,0 +1,7 @@ +## Module + +#### 21.03.2025 +#### Version 15.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..d65c0fec2 --- /dev/null +++ b/all_in_one_pos_kit/models/__init__.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Jumana Haseen (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_order_line +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_production_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..acf56a683 --- /dev/null +++ b/all_in_one_pos_kit/models/account_move.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Jumana Haseen (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . +# +# ############################################################################# +import math +import re +from odoo import api, fields, models + + +class AccountMove(models.Model): + """Extends the 'account.move' model to include additional fields and + methods for managing account barcodes.""" + _inherit = "account.move" + + account_barcode = fields.Char(string='Account Barcode', + help='Enter the barcode associated with this' + 'account.') + + @api.model + def create(self, vals): + """Changed variable name from res.account_barcode to + self.account_barcode""" + res = super(AccountMove, self).create(vals) + ean = self.generate_ean(str(res.id)) + self.account_barcode = ean + return res + + def ean_checksum(self, ean_code): + """Returns the checksum of an ean string of length 13, returns -1 if + the string has the wrong length.""" + if len(ean_code) != 13: + return -1 + odd_sum = 0 + even_sum = 0 + ean_value = ean_code + reverse_value = ean_value[::-1] + final_ean = reverse_value[1:] + for i in range(len(final_ean)): + if i % 2 == 0: + odd_sum += int(final_ean[i]) + else: + even_sum += int(final_ean[i]) + total = (odd_sum * 3) + even_sum + check = int(10 - math.ceil(total % 10.0)) % 10 + return check + + def check_ean(self, ean_code): + """Returns True if ean_code is a valid ean13 string, or null""" + if not ean_code: + return True + if len(ean_code) != 13: + return False + try: + int(ean_code) + except: + return False + return self.ean_checksum(ean_code) == int(ean_code[-1]) + + def generate_ean(self, ean): + """Creates and returns a valid ean13 from an invalid one""" + if not ean: + return "0000000000000" + ean_code = re.sub("[A-Za-z]", "0", ean) + ean_code = re.sub("[^0-9]", "", ean_code) + ean_code = ean_code[:13] + if len(ean_code) < 13: + ean_code = ean_code + '0' * (13 - len(ean_code)) + return ean_code[:-1] + str(self.ean_checksum(ean_code)) 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..ccf7e11a0 --- /dev/null +++ b/all_in_one_pos_kit/models/meals_planning.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +################################################################################ +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Jumana Haseen (odoo@cybrosys.com) +# +# You can modify it under the terms of the GNU AFFERO +# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. +# +# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE +# (AGPL v3) along with this program. +# If not, see . +# +################################################################################ +from odoo import api, fields, models, _ +from odoo.exceptions import ValidationError + + +class MealsPlanning(models.Model): + """By using this model user can specify the time range and pos session""" + _name = 'meals.planning' + _description = "Product Planning" + _inherit = ['mail.thread', 'mail.activity.mixin'] + + name = fields.Char(string='Name', required=True, + help='Name for Product Planning', copy=False) + pos_ids = fields.Many2many('pos.config', string='Shops', copy=False, + help='Choose PoS Sessions', required=True) + time_from = fields.Float(string='From', required=True, + help='Add from time(24hr)') + time_to = fields.Float(string='To', required=True, help='Add to time(24hr)') + product_ids = fields.Many2many('product.product', string='Product', + domain=[('available_in_pos', '=', True)], + help='Choose products that should be ' + 'displayed') + state = fields.Selection([('activated', 'Activated'), + ('deactivated', 'Deactivated')], + default='deactivated', string='Status', + help='Status of meals planning') + company_id = fields.Many2one('res.company', string='Company', + help='Choose the company', + default=lambda self: self.env.company) + + @api.constrains('time_from', 'time_to') + def _check_time_range(self): + """Validation for from time and to time""" + if self.time_from >= self.time_to: + raise ValidationError(_('From time must be less than to time!')) + 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..036569ba5 --- /dev/null +++ b/all_in_one_pos_kit/models/mrp_production.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Jumana Haseen (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): + # Inheriting model mrp_production + _inherit = 'mrp.production' + + def create_mrp_from_pos(self, products): + """ Function to create mrp order from pos""" + product_ids = [] + if products: + for product in products: + flag = 1 + if product_ids: + for product_id in product_ids: + if product_id['id'] == product['id']: + product_id['qty'] += product['qty'] + flag = 0 + if flag: + product_ids.append(product) + for prod in product_ids: + if prod['qty'] > 0: + bom_count = self.env['mrp.bom'].search( + [('product_tmpl_id', '=', prod['product_tmpl_id'])]) + if bom_count: + bom_temp = self.env['mrp.bom'].search( + [('product_tmpl_id', '=', prod['product_tmpl_id']), + ('product_id', '=', False)]) + bom_prod = self.env['mrp.bom'].search( + [('product_id', '=', prod['id'])]) + if bom_prod: + bom = bom_prod[0] + elif bom_temp: + bom = bom_temp[0] + else: + bom = [] + if bom: + vals = { + 'origin': 'POS-' + prod['pos_reference'], + 'state': 'confirmed', + 'product_id': prod['id'], + 'product_tmpl_id': prod['product_tmpl_id'], + 'product_uom_id': prod['uom_id'], + 'product_qty': prod['qty'], + 'bom_id': bom.id, + } + mrp_order = self.sudo().create(vals) + list_value = [] + for bom_line in mrp_order.bom_id.bom_line_ids: + list_value.append((0, 0, { + 'raw_material_production_id': mrp_order.id, + 'name': mrp_order.name, + 'product_id': bom_line.product_id.id, + 'product_uom': bom_line.product_uom_id.id, + 'product_uom_qty': (bom_line.product_qty + * mrp_order.product_qty) / + self.env[ + 'mrp.bom'].search([( + "product_tmpl_id", "=", prod[ + 'product_tmpl_id'])]).product_qty, + '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, + 'state': 'draft', + 'quantity_done': 0, + 'operation_id': False + })) + finished_vals = { + 'product_id': prod['id'], + 'product_uom_qty': prod['qty'], + 'product_uom': prod['uom_id'], + 'name': mrp_order.name, + 'date_deadline': mrp_order.date_deadline, + 'picking_type_id': mrp_order.picking_type_id.id, + 'location_id': mrp_order.location_src_id.id, + 'location_dest_id': mrp_order.location_dest_id.id, + 'company_id': mrp_order.company_id.id, + 'production_id': mrp_order.id, + 'warehouse_id': mrp_order.location_dest_id. + warehouse_id.id, + 'origin': mrp_order.name, + 'group_id': mrp_order.procurement_group_id.id, + 'propagate_cancel': mrp_order.propagate_cancel, + } + mrp_order.update({'move_raw_ids': list_value, + 'move_finished_ids': [ + (0, 0, finished_vals)] + }) + return True diff --git a/all_in_one_pos_kit/models/multi_barcode_product.py b/all_in_one_pos_kit/models/multi_barcode_product.py new file mode 100644 index 000000000..d23d4d5e1 --- /dev/null +++ b/all_in_one_pos_kit/models/multi_barcode_product.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Jumana Haseen (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 MultiProductBarcode(models.Model): + """ + A model for managing multiple barcodes for products in Odoo. + """ + _name = 'multi.barcode.product' + + multi_barcode = fields.Char(string="Barcode", + help="Provide alternate barcodes " + "for this product") + product_multi = fields.Many2one('product.product') + template_multi = fields.Many2one('product.template') + + def get_barcode_val(self, product): + """ + Retrieve the barcode value along with the associated product. + + Args: + product (recordset): The product record associated with the barcode. + + Returns: + tuple: A tuple containing the barcode value (str) and the product + (recordset). + """ + 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..f36caed68 --- /dev/null +++ b/all_in_one_pos_kit/models/pos_config.py @@ -0,0 +1,134 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Jumana Haseen (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_qr_code = fields.Boolean(string='Order QRCode', + help='Enable this field to show the ' + 'qr code to pos receipt') + is_invoice_number = fields.Boolean(string='Invoice Number', + help='Enable this field to show the ' + 'invoice number to pos receipt') + is_customer_details = fields.Boolean(string='Customer Details', + help='Enable this field to show the ' + 'customer number to pos receipt') + is_customer_name = fields.Boolean(string='Customer Name', + help='Enable this field to show ' + 'the customer name to pos receipt') + is_customer_address = fields.Boolean(string='Customer Address', + help='Enable this field to show ' + 'the customer address code to ' + 'pos receipt') + is_customer_mobile = fields.Boolean(string='Customer Mobile', + help='Enable this field to show the ' + 'customer mobile to pos receipt') + is_customer_phone = fields.Boolean(string='Customer Phone', + help='Enable this field to show the ' + 'customer phone to pos receipt') + is_customer_email = fields.Boolean(string='Customer Email', + help='Enable this field to show the ' + 'customer email to pos receipt') + is_customer_vat = fields.Boolean(string='Customer VAT', + help='Enable this field to show the ' + 'customer vat to pos receipt') + custom_tip_percentage = fields.Float(string="Custom Percentage", + help="Enter the percentage custom tips" + "you want to apply") + image = fields.Binary(string='Image', help='Add logo for pos session') + is_session = fields.Boolean(string="Session", + compute='_compute_check_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") + enable_service_charge = fields.Boolean( + string="Service Charges", + help="Enable to add service charge") + visibility = fields.Selection( + [('global', 'Global'), ('session', 'Session')], + default='global', string="Visibility", + help='Setup the Service charge globally or per session') + global_selection = fields.Selection([ + ('amount', 'Amount'), + ('percentage', 'Percentage')], + string='Type', default='amount', + help='Set the service charge as a amount or percentage') + global_charge = fields.Float( + string='Service 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')]", + help='Set a service product globally') + customer_msg = fields.Boolean('POS Greetings', + Help='Create an account if you ever create ' + 'an account') + auth_token = fields.Char('Auth Token', + Help='Copy the token from your twilio console ' + 'window adn paste here') + account_sid = fields.Char('Account SID') + twilio_number = fields.Char('Twilio Number', + Help='The number provided by twilio used to ' + 'send text messages') + sms_body = fields.Text('Body') + + def _compute_check_session(self): + """To check the service charge is set up for session wise or globally""" + check_session = self.env['ir.config_parameter'].sudo().get_param( + 'service_charges_pos.visibility') + if check_session == '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 enable set service product + and amount by default per session""" + if self.is_service_charges: + if not self.service_product_id: + domain = [('available_in_pos', '=', True), + ('sale_ok', '=', True), ('type', '=', 'service')] + self.service_product_id = self.env[ + 'product.product'].search( + domain, limit=1) + self.service_charge = 10.0 + else: + self.service_product_id = False + self.service_charge = 0.0 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..7a07c3050 --- /dev/null +++ b/all_in_one_pos_kit/models/pos_greetings.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Jumana Haseen (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' + + customer_id = fields.Many2one( + comodel_name='res.partner', string='Customer', help="Customer" + ) + order_id = fields.Many2one( + comodel_name='pos.order', string='Order', help="Order" + ) + auth_token = fields.Char( + string='Token', help='Token' + ) + twilio_number = fields.Char( + string='Twilio Number', help='Twilio Number' + ) + to_number = fields.Char( + string='Customer Number', help='Customer Number' + ) + sms_body = fields.Char( + string='Body', required=True, help='Body' + ) + session_id = fields.Many2one( + comodel_name='pos.session', string='Session', help='Session' + ) + send_sms = fields.Boolean( + string='Send SMS', default=False, help='Send SMS' + ) 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..f2e56e9d3 --- /dev/null +++ b/all_in_one_pos_kit/models/pos_order.py @@ -0,0 +1,439 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Jumana Haseen (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 +from datetime import datetime + +try: + import qrcode +except ImportError: + qrcode = None +try: + import base64 +except ImportError: + base64 = None + + +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' + is_exchange = fields.Boolean(string='Exchange', + help='Will enable when create an exchange' + 'order') + + @api.model + def pos_exchange_order(self, order_id): + """Mark order a exchanged""" + self.browse(order_id).is_exchange = True + + @api.model + def get_invoice(self, id): + """Retrieve the corresponding invoice details based on the provided ID. + Args: + id (int): The ID of the invoice. + Returns: + dict: A dictionary containing the invoice details. + """ + pos_id = self.search([('pos_reference', '=', id)]) + base_url = self.env['ir.config_parameter'].get_param('web.base.url') + invoice_id = self.env['account.move'].search( + [('invoice_origin', '=', pos_id.name)]) + return { + 'invoice_id': invoice_id.id, + 'invoice_name': invoice_id.name, + 'base_url': base_url, + 'is_qr_code': invoice_id.account_barcode} + + @api.model + def get_department(self, option): + """ + Retrieve sales data based on the specified option for POS (Point of Sale) orders. + + This method generates a sales report based on the given option, which can be + 'pos_hourly_sales', 'pos_monthly_sales', or default to annual sales grouped by month. + The report is generated for the current company and for the current month or year + as appropriate. + + Args: + option (str): The type of sales report to generate. Possible values are: + - 'pos_hourly_sales': Hourly sales data for the current month. + - 'pos_monthly_sales': Daily sales data for the current month. + - Any other value: Monthly sales data for the current year. + + Returns: + list: A list containing three elements: + - order (list): A list of total sales amounts. + - today (list): A list of time labels corresponding to the sales amounts + (hours, days, or months). + - label (str): A string indicating the type of time label used ('HOURS', 'DAYS', 'MONTHS'). + + Raises: + Exception: If an error occurs while executing the database query. + """ + company_id = self.env.company.id + if option == 'pos_hourly_sales': + user_tz = self.env.user.tz if self.env.user.tz else pytz.UTC + 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(user_tz) + 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 = [] + for record in docs: + order.append(record.get('sum')) + today = [] + for record in docs: + today.append(record.get('date_month')) + final = [order, today, label] + return final + + @api.model + def get_details(self): + """ + Retrieve various details related to POS (Point of Sale) operations for the current company. + + This method fetches payment details, salesperson performance, and session statuses + from the POS system. The details include the total amount collected per payment method, + the total sales and order count per salesperson, and the status of active POS sessions. + + Returns: + dict: A dictionary containing the following keys: + - 'payment_details': A list of tuples, each containing the name of the payment + method and the total amount collected, formatted with the + company currency symbol. + - 'salesperson': A list of tuples, each containing the name of the salesperson, + the total sales amount formatted with the company currency + symbol, and the number of orders processed. + - 'selling_product': A list of dictionaries, each representing a POS session with + its name and status (e.g., 'Closed', 'Opened'). + + Raises: + Exception: If an error occurs during database queries or data formatting. + """ + company_id = self.env.company.id + cr = self._cr + cr.execute( + """select pos_payment_method.name,sum(amount) from pos_payment inner + join pos_payment_method on pos_payment_method.id=pos_payment. + payment_method_id group by pos_payment_method.name ORDER + BY sum(amount) DESC; """) + payment_details = cr.fetchall() + 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) + '''GROUP BY hr_employee.name order by total + DESC;''') + salesperson = cr.fetchall() + total_sales = [] + for rec in salesperson: + rec = list(rec) + sym_id = rec[1] + company = self.env.company + if company.currency_id.position == 'after': + rec[1] = "%s %s" % (sym_id, company.currency_id.symbol) + else: + rec[1] = "%s %s" % (company.currency_id.symbol, sym_id) + rec = tuple(rec) + total_sales.append(rec) + cr.execute( + '''select DISTINCT(product_template.name) 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( + company_id) + ''' group by product_template.id ORDER + BY total_quantity DESC Limit 10 ''') + sessions = self.env['pos.config'].search([]) + sessions_list = [] + dict = { + 'closing_control': 'Closed', + 'opened': 'Opened', + 'new_session': 'New Session', + 'opening_control': "Opening Control" + } + for session in sessions: + sessions_list.append({ + 'session': session.name, + 'status': dict.get(session.pos_session_state) + }) + payments = [] + for rec in payment_details: + rec = list(rec) + sym_id = rec[1] + company = self.env.company + if company.currency_id.position == 'after': + rec[1] = "%s %s" % (sym_id, company.currency_id.symbol) + else: + rec[1] = "%s %s" % (company.currency_id.symbol, sym_id) + rec = tuple(rec) + payments.append(rec) + return { + 'payment_details': payments, + 'salesperson': total_sales, + 'selling_product': sessions_list, + } + + @api.model + def get_refund_details(self): + """ + Retrieve details about sales, refunds, and POS sessions for the current date. + + This method calculates various metrics related to POS (Point of Sale) operations, + including the total sales amount, number of orders, refund counts, and session + information. It also formats the total sales with appropriate magnitude suffixes + (e.g., K for thousand, M for million) for easier readability. + + Returns: + dict: A dictionary containing the following keys: + - 'total_sale': A formatted string representing the total sales amount with + an appropriate suffix (e.g., '1.2K' for 1200). + - 'total_order_count': An integer representing the total number of orders. + - 'total_refund_count': An integer representing the total number of refund orders. + - 'total_session': An integer representing the total number of POS sessions. + - 'today_refund_total': An integer representing the number of refunds made today. + - 'today_sale': An integer representing the number of sales made today. + + Raises: + Exception: If an error occurs while querying the database or calculating the metrics. + """ + default_date = datetime.today().date() + pos_order = self.env['pos.order'].search([]) + total = 0 + today_refund_total = 0 + total_order_count = 0 + total_refund_count = 0 + today_sale = 0 + a = 0 + for rec in pos_order: + if rec.amount_total < 0.0 and rec.date_order.date() == default_date: + today_refund_total = today_refund_total + 1 + total_sales = rec.amount_total + total = total + total_sales + total_order_count = total_order_count + 1 + if rec.date_order.date() == default_date: + today_sale = today_sale + 1 + if rec.amount_total < 0.0: + total_refund_count = total_refund_count + 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]) + pos_session = self.env['pos.session'].search([]) + total_session = 0 + for record in pos_session: + total_session = total_session + 1 + return { + 'total_sale': val, + 'total_order_count': total_order_count, + 'total_refund_count': total_refund_count, + 'total_session': total_session, + 'today_refund_total': today_refund_total, + 'today_sale': today_sale, + } + + @api.model + def get_the_top_customer(self): + """ + Retrieve the top 10 customers based on the total amount spent in POS (Point of Sale) orders. + + This method fetches the top customers who have made the highest total payments for + POS orders in the current company. It returns a list of the top 10 customers along + with the corresponding total amounts they have spent. + + Returns: + list: A list containing two elements: + - order (list): A list of total amounts spent by the top 10 customers. + - day (list): A list of customer names corresponding to the total amounts. + + Raises: + Exception: If an error occurs during the database query execution. + """ + company_id = self.env.company.id + query = '''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( + company_id) + ''' GROUP BY pos_order.partner_id, + res_partner.name ORDER BY amount_total DESC LIMIT 10;''' + self._cr.execute(query) + docs = self._cr.dictfetchall() + order = [] + for record in docs: + order.append(record.get('amount_total')) + day = [] + for record in docs: + day.append(record.get('customer')) + final = [order, day] + return final + + @api.model + def get_the_top_products(self): + """ + Retrieve the top 10 products based on the total quantity sold in POS (Point of Sale) orders. + + This method fetches the top-selling products in terms of quantity for the current company + and returns a list of the top 10 products along with their corresponding total quantities sold. + + Returns: + list: A list containing two elements: + - total_quantity (list): A list of quantities for the top 10 products. + - product_name (list): A list of product names corresponding to the quantities. + + Raises: + Exception: If an error occurs during the database query execution. + """ + company_id = self.env.company.id + query = '''select DISTINCT(product_template.name) 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( + company_id) + ''' group by product_template.id ORDER + BY total_quantity DESC Limit 10 ''' + self._cr.execute(query) + top_product = self._cr.dictfetchall() + total_quantity = [] + for record in top_product: + total_quantity.append(record.get('total_quantity')) + product_name = [] + for record in top_product: + product_name.append(record.get('product_name')) + final = [total_quantity, product_name] + return final + + @api.model + def get_the_top_categories(self): + """ + Retrieve the top product categories based on the total quantity sold in POS (Point of Sale) orders. + + This method fetches the top-selling product categories for the current company and returns + a list containing the total quantities sold for each category, sorted in descending order. + + Returns: + list: A list containing two elements: + - total_quantity (list): A list of quantities for the top-selling product categories. + - product_categ (list): A list of category names corresponding to the quantities. + + Raises: + Exception: If an error occurs during the database query execution. + """ + company_id = self.env.company.id + 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(company_id) + ''' group by product_category ORDER BY + total_quantity DESC ''') + self._cr.execute(query) + top_product = self._cr.dictfetchall() + total_quantity = [] + for record in top_product: + total_quantity.append(record.get('total_quantity')) + product_categ = [] + for record in top_product: + product_categ.append(record.get('product_category')) + final = [total_quantity, product_categ] + return final + + @api.model + def create_from_ui(self, orders, draft=False): + """ + Create POS orders from the UI and send SMS notifications to customers if configured. + + This method extends the default behavior of creating POS orders from the user interface. + After the orders are created, it checks for configurations that allow SMS notifications + and sends an SMS to the customer using Twilio if applicable. The SMS message details + are also logged in the 'pos.greetings' model. + + Args: + orders (list): A list of order dictionaries coming from the POS UI. + + Returns: + list: The result from the parent 'create_from_ui' method, representing the + created orders. + + Raises: + Exception: If an error occurs while sending the SMS, it is caught and suppressed. + """ + res = super(PosOrder, self).create_from_ui(orders, draft) + ids = [] + for line in res: + if line['id']: + ids.append(line['id']) + backend_order = self.search([('id', 'in', ids)]) + if backend_order: + for pos_order in backend_order: + config_id = pos_order.session_id.config_id + if config_id.customer_msg: + if pos_order.partner_id.phone: + try: + customer_phone = str(pos_order.partner_id.phone) + twilio_number = config_id.twilio_number + auth_token = config_id.auth_token + account_sid = config_id.account_sid + body = config_id.sms_body + client = Client(account_sid, auth_token) + client.messages.create( + body=body, + from_=twilio_number, + to=customer_phone + ) + pos_greeting_obj = self.env['pos.greetings'] + pos_greeting_obj.create({ + 'customer_id': pos_order.partner_id.id, + 'order_id': pos_order.id, + 'auth_token': auth_token, + 'twilio_number': twilio_number, + 'to_number': customer_phone, + 'session_id': pos_order.session_id.id, + 'sms_body': body, + 'send_sms': True, + }) + except: + pass + return res diff --git a/all_in_one_pos_kit/models/pos_order_line.py b/all_in_one_pos_kit/models/pos_order_line.py new file mode 100644 index 000000000..75fc44164 --- /dev/null +++ b/all_in_one_pos_kit/models/pos_order_line.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +################################################################################ +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Jumana Haseen (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 PosOrderLine(models.Model): + """Inherited pos order line to get the product details""" + _inherit = "pos.order.line" + + @api.model + def get_product_details(self, ids): + """To get the product details""" + lines = self.env['pos.order.line'].browse(ids) + res = [] + for rec in lines: + res.append({ + 'product_id': rec.product_id.id, + 'name': rec.product_id.name, + 'qty': rec.qty, + 'order_id': rec.order_id.id, + }) + return res 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..b4ff84b85 --- /dev/null +++ b/all_in_one_pos_kit/models/pos_report.py @@ -0,0 +1,666 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Jumana Haseen (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, fields, api +import io +import json + +try: + from odoo.tools.misc import xlsxwriter +except ImportError: + import xlsxwriter + + +class PosReport(models.Model): + """ + Inheriting POS Report + """ + _name = "pos.report" + + pos_report = fields.Char(string="PoS Report", help="PoS Report") + date_from = fields.Datetime(string="Date From", help="Date From") + date_to = fields.Datetime(string="Date to", help="Date to") + 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="Report Type") + + @api.model + def pos_report(self, option): + """ + Generate a Point of Sale (POS) report based on the provided options. + + This method retrieves the POS report details for the specified report + ID and + gathers the necessary data for generating the report, including date range + and report type. It then retrieves filters and report lines to be used in + displaying the report. + + Args: + option (list): A list containing the ID of the POS report to be + generated. + + Returns: + dict: A dictionary containing the report details, including: + - name (str): The name of the report. + - type (str): The type of action for the report. + - tag (str): The tag to identify the report action. + - orders (dict): The report data, including report type and date + range. + - filters (dict): Filters applied to the report. + - report_lines (list): The detailed report lines. + - report_main_line (list): The main summary line of the report. + + Raises: + Exception: If an error occurs during report generation or data + retrieval. + """ + 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, + }) + filters = self.get_filter(option) + lines = self._get_report_values(data).get('POS') + main_line = self._get_report_values(data).get('pos_main') + return { + 'name': "PoS Orders", + 'type': 'ir.actions.client', + 'tag': 'pos_r', + 'orders': data, + 'filters': filters, + 'report_lines': lines, + 'report_main_line': main_line, + } + + def get_filter(self, option): + """ + Retrieve the filters for generating a report based on the specified + options. + + This method analyzes the report type obtained from the filter data and + constructs a dictionary of filters that will be applied to the report. + + Args: + option (list): A list containing options that influence the report + generation. + + Returns: + dict: A dictionary containing the report filters, including: + - report_type (str): A description of the report type being + generated. + + """ + 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): + """ + Retrieve filter data for the specified report option. + + This method searches for a POS report using the provided option and + returns a dictionary containing the report type and any default filters + associated with the report. + + Args: + option (list): A list containing options, where the first element + is expected to be the ID of the POS report. + + Returns: + dict: A dictionary containing the filter data for the report, + including: + - report_type (str): The type of report retrieved from the + database. + """ + report = self.env['pos.report'].search([('id', '=', option[0])]) + default_filters = {} + filter_dict = { + 'report_type': report.report_type, + } + filter_dict.update(default_filters) + return filter_dict + + @api.model + def create(self, vals): + """ + Create a new POS report record with the provided values. + + This method overrides the default create method to extend + the functionality of creating a POS report. It calls the + super method to handle the actual record creation and + returns the resulting record. + + Args: + vals (dict): A dictionary of field values to create the new + POS report record. + + Returns: + recordset: The created POS report record. +. + """ + res = super(PosReport, self).create(vals) + return res + + def write(self, vals): + """ + Update existing POS report records with the provided values. + + This method overrides the default write method to extend + the functionality of updating POS report records. It calls + the super method to handle the actual record update and + returns the result of the operation. + + Args: + vals (dict): A dictionary of field values to update the + existing POS report records. + + Returns: + bool: True if the update was successful, False otherwise. + """ + res = super(PosReport, self).write(vals) + return res + + def _get_report_sub_lines(self, data, report, date_from, date_to): + 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_by_order = self._cr.dictfetchall() + report_sub_lines.append(report_by_order) + 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_by_order_details = self._cr.dictfetchall() + report_sub_lines.append(report_by_order_details) + 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_by_product = self._cr.dictfetchall() + report_sub_lines.append(report_by_product) + 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_by_categories = self._cr.dictfetchall() + report_sub_lines.append(report_by_categories) + 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_by_salesman = self._cr.dictfetchall() + report_sub_lines.append(report_by_salesman) + 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_by_payment = self._cr.dictfetchall() + report_sub_lines.append(report_by_payment) + return report_sub_lines + + def _get_report_total_value(self, data, report): + """ + Generate detailed sub-reports based on the provided report type and date range. + + This method constructs and executes SQL queries to fetch sub-report data + according to the specified report type. It gathers information about orders, + products, categories, salesmen, and payment methods, grouping the results + accordingly. + + Args: + data (dict): A dictionary containing filter parameters, such as + 'report_type' and date range (date_from, date_to). + report (recordset): The report record for which sub-lines are being + fetched. + date_from (str): The start date for filtering the records. + date_to (str): The end date for filtering the records. + + Returns: + list: A list of dictionaries, where each dictionary represents + the result of a sub-report query based on the specified report + type. + """ + 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_by_order = self._cr.dictfetchall() + report_main_lines.append(report_by_order) + 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_by_order_detail = self._cr.dictfetchall() + report_main_lines.append(report_by_order_detail) + 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_by_product = self._cr.dictfetchall() + report_main_lines.append(report_by_product) + else: + report_main_lines = False + return report_main_lines + + def _get_report_values(self, data): + """ + Retrieve report values based on the specified report type and date range. + + This method processes the given report type and calls the appropriate + sub-methods to gather the report data and total values. + + Args: + data (dict): A dictionary containing report parameters, such as + 'model', 'report_type', 'date_from', and 'date_to'. + + Returns: + dict: A dictionary containing the report data, including document + IDs, model data, sub-report values (POS), and total values + (pos_main). + """ + docs = data['model'] + date_from = data.get('date_from') + date_to = data.get('date_to') + if data['report_type'] == 'report_by_order_detail': + report = ['Report By Order Detail'] + elif data['report_type'] == 'report_by_product': + report = ['Report By Product'] + elif data['report_type'] == 'report_by_categories': + report = ['Report By Categories'] + elif data['report_type'] == 'report_by_salesman': + report = ['Report By Salesman'] + elif data['report_type'] == 'report_by_payment': + report = ['Report By Payment'] + else: + report = ['Report By Order'] + report_res_total = self._get_report_total_value(data, report) + if data.get('report_type'): + report_res = \ + self._get_report_sub_lines(data, report, date_from, date_to)[0] + else: + report_res = self._get_report_sub_lines(data, report, date_from, + date_to) + if data.get('report_type') == 'report_by_order': + report_res_total = self._get_report_total_value(data, report)[0] + return { + 'doc_ids': self.ids, + 'docs': docs, + 'POS': report_res, + 'pos_main': report_res_total, + } + + def get_pos_xlsx_report(self, data, response, report_data): + """ + Generate an Excel report for the Point of Sale (PoS) based on the + provided filters and report data. + + Args: + data (str): JSON string containing report filters. + response: The HTTP response object to stream the output. + report_data (str): JSON string containing the main report data. + + Returns: + None: Writes the generated report directly to the response stream. + """ + 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() + head = workbook.add_format({'align': 'center', 'bold': True, + 'font_size': '20px'}) + 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}) + sheet.merge_range('A2:H3', + 'Point of Sale Report', + head) + if filters.get('report_type') == 'report_by_order': + sheet.merge_range('B5:D5', '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 = [] + for rec in report_data_main[0]: + lst.append(rec) + 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('B5:D5', '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 = [] + for rec in report_data_main[0]: + lst.append(rec) + 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 = [] + for rec in report_data_main[0]: + lst.append(rec) + 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:D5', '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 = [] + for rec in report_data_main[0]: + lst.append(rec) + 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:D5', '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 = [] + for rec in report_data_main[0]: + lst.append(rec) + 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:D5', '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 = [] + for rec in report_data_main[0]: + lst.append(rec) + 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['config'], txt_l) + sheet.write(row, col + 1, rec_data['session'], txt_l) + sheet.write(row, col + 2, rec_data['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..38c2d94ad --- /dev/null +++ b/all_in_one_pos_kit/models/pos_session.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Jumana Haseen (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 _loader_params_product_product(self): + """loaded the field to pos""" + result = super()._loader_params_product_product() + result['search_params']['fields'].extend(['is_age_restrict']) + return result 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..5b77f37f2 --- /dev/null +++ b/all_in_one_pos_kit/models/product_product.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Jumana Haseen (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.osv import expression + + +class ProductProduct(models.Model): + """ + Inherit poduct_product model + """ + _inherit = 'product.product' + + product_multi_barcodes = fields.One2many( + comodel_name='multi.barcode.product', + inverse_name='product_multi', string='Barcodes' + ) + + @api.model + def create(self, vals): + """ + Create a new product and update the associated multi barcodes record. + + Args: + vals (dict): The values to create the product. + + Returns: + recordset: The created product record. + """ + res = super(ProductProduct, self).create(vals) + res.product_multi_barcodes.update({ + 'template_multi': res.product_tmpl_id.id + }) + return res + + def write(self, vals): + """ + Update the product and its associated multi barcodes record. + + Args: + vals (dict): The values to update the product. + + Returns: + bool: True if the update was successful. + """ + res = super(ProductProduct, self).write(vals) + self.product_multi_barcodes.update({ + 'template_multi': self.product_tmpl_id.id + }) + return res + + @api.model + def _name_search(self, name, args=None, operator='ilike', limit=100, + name_get_uid=None): + """ + Custom name search for products based on various fields. + + Args: + name (str): The name to search for. + args (list): Additional search criteria. + operator (str): The operator for the search (default is 'ilike'). + limit (int): The maximum number of records to return. + name_get_uid (int): The UID for access rights. + + Returns: + list: List of product IDs that match the search criteria. + """ + args = args or [] + domain = [] + if name: + domain = ['|', '|', ('name', operator, name), + ('default_code', operator, name), + '|', ('barcode', operator, name), + ('product_multi_barcodes', operator, name)] + product_id = self._search(expression.AND([domain, args]), limit=limit, + access_rights_uid=name_get_uid) + return product_id + + @api.onchange('to_make_mrp') + def onchange_to_make_mrp(self): + """ + Triggered when the `to_make_mrp` field is changed. + + If the `to_make_mrp` field is set to True, this method checks if the + product has an associated Bill of Materials (BoM). If no BoM is found, + it raises a warning prompting the user to set a BoM for the product. + + Raises: + Warning: If `to_make_mrp` is True and `bom_count` is 0. + """ + if self.to_make_mrp: + if 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..5af35bb9f --- /dev/null +++ b/all_in_one_pos_kit/models/product_template.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Jumana Haseen (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") + template_multi_barcodes = fields.One2many('multi.barcode.product', + 'template_multi', + string='Barcodes') + to_make_mrp = fields.Boolean(string='To Create MRP Order', + help="Check if the product should be make " + "mrp order") + + @api.onchange('to_make_mrp') + def onchange_to_make_mrp(self): + """ + Checks if the product has a Bill of Materials set when 'to_make_mrp' + is True. + Raises a ValidationError if no BOM exists. + """ + if self.to_make_mrp: + if not self.bom_count: + raise ValidationError( + 'Please set Bill of Material for this product.') + + @api.model + def create(self, vals): + """ + Creates a new product template. Updates 'template_multi_barcodes' with + the new product variant ID. + """ + res = super(ProductTemplate, self).create(vals) + res.template_multi_barcodes.update({ + 'product_multi': res.product_variant_id.id + }) + return res + + def write(self, vals): + """ + Updates an existing product template. Ensures + 'template_multi_barcodes' is updated if present. + """ + res = super(ProductTemplate, self).write(vals) + if self.template_multi_barcodes: + self.template_multi_barcodes.update({ + 'product_multi': self.product_variant_id.id + }) + return res 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..1d86dd389 --- /dev/null +++ b/all_in_one_pos_kit/models/res_config_settings.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Jumana Haseen (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 ResConfigSettings(models.TransientModel): + """Inherited Configuration Settings""" + _inherit = "res.config.settings" + + 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.') + + 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..05f2bea7a --- /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) 2025-TODAY Cybrosys Technologies() +# Author: Jumana Haseen (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_production_lot.py b/all_in_one_pos_kit/models/stock_production_lot.py new file mode 100644 index 000000000..01d73df36 --- /dev/null +++ b/all_in_one_pos_kit/models/stock_production_lot.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +################################################################################ +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Jumana Haseen (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.production.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..ea402c1a0 --- /dev/null +++ b/all_in_one_pos_kit/report/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Jumana Haseen (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 pos_order_report diff --git a/all_in_one_pos_kit/report/pos_order_report.py b/all_in_one_pos_kit/report/pos_order_report.py new file mode 100644 index 000000000..4c46d798a --- /dev/null +++ b/all_in_one_pos_kit/report/pos_order_report.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2025-TODAY Cybrosys Technologies() +# Author: Jumana Haseen (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): + _name = 'report.all_in_one_pos_kit.pos_order_report' + + @api.model + def _get_report_values(self, docids, data=None): + """ + Retrieves report values based on the given docids and data. + If the context indicates that this is a POS order report, it processes the report data. + + :param docids: List of document IDs for which to generate the report. + :param data: Dictionary containing report data and filters. + :return: Updated data dictionary for report generation. + """ + 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/pos_order_report.xml b/all_in_one_pos_kit/report/pos_order_report.xml new file mode 100644 index 000000000..f449175a5 --- /dev/null +++ b/all_in_one_pos_kit/report/pos_order_report.xml @@ -0,0 +1,411 @@ + + + + + + + + + + + + + + + + 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..a1e8581ce --- /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 company access for meals planning + + + ['|',('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..b0cc8d5bc --- /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,mrp.production.pos.user,mrp.model_mrp_production,point_of_sale.group_pos_user,1,1,1,0 +access_mrp_bom_pos_user,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_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 +access_pos_greetings,access.pos.greetings,model_pos_greetings,base.group_user,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/logo.png b/all_in_one_pos_kit/static/description/assets/icons/logo.png new file mode 100644 index 000000000..478462d3e Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/icons/logo.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/modules/1.png b/all_in_one_pos_kit/static/description/assets/modules/1.png new file mode 100644 index 000000000..a8a43002a Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/modules/1.png 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..a1f360332 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..e0a27437e 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..4d868d82c 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..26494b157 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..37cd5c9cb 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..51168cfeb 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..792454c0a 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..92d7fac12 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..2ac228441 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..9bf2be6a7 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..0dd54f9a1 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..d4dc9f2c7 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..861a164f4 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..39e687f2d 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..79a00c290 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..ad6e220e0 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..8a1f2277e 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..64176c742 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..0feb5220f 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..8cdbfe564 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..ad2827200 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..96d565911 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..dad70d166 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..2bd6276ef 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..e273419a6 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..e709c1587 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..3c9076481 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..363216e87 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..1e3be9ada 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..d9512de29 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..001bd2903 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..faa1ee792 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..e06604130 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..3dc8bed3f 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..b25ccc094 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..03c26a42e 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..12890e04e 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..c1bb7307e 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..8464a3878 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..6d548ec52 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..902acd77d 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..4a30cd96d 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..f3458059d 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..50127ac53 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..c78ad750a 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..6791813a4 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..88b41103e 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..d2ef4d427 Binary files /dev/null and b/all_in_one_pos_kit/static/description/assets/screenshots/48.png differ diff --git a/all_in_one_pos_kit/static/description/assets/screenshots/5.png b/all_in_one_pos_kit/static/description/assets/screenshots/5.png new file mode 100644 index 000000000..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/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/7.png b/all_in_one_pos_kit/static/description/assets/screenshots/7.png new file mode 100644 index 000000000..39737cbdf 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..7c2242003 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..c6ba73471 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..d65557ffc 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..76271d540 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..f93daeb90 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..6b035530e --- /dev/null +++ b/all_in_one_pos_kit/static/description/index.html @@ -0,0 +1,1109 @@ +
+
+
+
+ +
+
+
+ Community +
+
+ Enterprise +
+ +
+
+
+
+ +
+
+
+

+ All in One POS Kit

+

+ This module combines a variety of POS features. +

+ +
+
+ + + +
+
+

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

+
+
+
+
+ +
+
+

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

+
+
+
+
+ +
+
+

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

+ +
+
+ +
+
+

Suggested 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

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

Need Help?

+
+
+
+ + +
+ +
+ + +
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+
+
+ + +
\ No newline at end of file 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..d1c8c9ff2 --- /dev/null +++ b/all_in_one_pos_kit/static/src/advanced_receipt/js/payment.js @@ -0,0 +1,56 @@ +odoo.define('all_in_one_pos_kit.PosPaymentReceiptExtend', function (require) { + 'use strict'; + var rpc = require('web.rpc'); + const PaymentScreen = require('point_of_sale.PaymentScreen'); + const Registries = require('point_of_sale.Registries'); + var models = require('point_of_sale.models'); + + // Load models to retrieve configuration and account move data + models.load_models([{ + model: 'pos.config', + fields: ['is_customer_details', 'is_customer_name', 'is_customer_address', 'is_customer_mobile', 'is_customer_phone', 'is_customer_email', 'is_customer_vat', 'is_qr_code', 'is_invoice_number', 'enable_service_charge', 'visibility', 'global_selection', 'global_charge', 'global_product_id'], + loaded: function (self, pos_config) { + self.pos_config = Object.assign({}, self.pos_config, pos_config[0]); + } + }]); + + // Load 'account.move' model fields + models.load_models([{ + model: 'account.move', + fields: ['name'], + loaded: function (self, account_move) { + self.account_move = account_move; + } + }]); + // Define the extended PaymentScreen component + const PosPaymentReceiptExtend = PaymentScreen => class extends PaymentScreen { + setup() { + super.setup(); + } + + async validateOrder(isForceValidate) { + // Retrieve receipt number from the selected order + var receipt_number = this.env.pos._previousAttributes.selectedOrder.name; + const receipt_order = await super.validateOrder(...arguments); + // Generate QR code and store it + const codeWriter = new window.ZXing.BrowserQRCodeSvgWriter(); + var self = this; + rpc.query({ + model: 'pos.order', + method: 'get_invoice', + args: [receipt_number] + }).then(function (result) { + self.env.pos.inv = result['invoice_name']; + 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); + }); + return receipt_order; + } + }; + + PosPaymentReceiptExtend.template = 'PosPaymentReceiptExtend'; + // Extend the PaymentScreen component with the custom functionality + Registries.Component.extend(PaymentScreen, PosPaymentReceiptExtend); + return PaymentScreen; +}); \ No newline at end of file diff --git a/all_in_one_pos_kit/static/src/advanced_receipt/xml/order_receipt_templates.xml b/all_in_one_pos_kit/static/src/advanced_receipt/xml/order_receipt_templates.xml new file mode 100644 index 000000000..5c33fc028 --- /dev/null +++ b/all_in_one_pos_kit/static/src/advanced_receipt/xml/order_receipt_templates.xml @@ -0,0 +1,61 @@ + + + + + + + + +
Customer Name: + +
+
+ +
Customer Address: + +
+
+ +
Customer Mobile: + +
+
+ +
Customer Phone: + +
+
+ +
Customer Email: + +
+
+ +
Customer 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..aca4242a1 --- /dev/null +++ b/all_in_one_pos_kit/static/src/age_restricted/js/age_restrict.js @@ -0,0 +1,32 @@ +odoo.define('age_restricted_product_pos.age_restrict', function (require) { +"use strict"; + const Registries = require('point_of_sale.Registries'); + const ProductScreen = require('point_of_sale.ProductScreen'); + var models = require('point_of_sale.models'); + models.load_fields('product.product', 'is_age_restrict'); + //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..e1ece001d --- /dev/null +++ b/all_in_one_pos_kit/static/src/age_restricted/js/restrict_popup.js @@ -0,0 +1,20 @@ +odoo.define('point_of_sale.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..18e82a8e2 --- /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/Screens/ReceiptScreen/OrderReceipt.js b/all_in_one_pos_kit/static/src/category_wise_receipt/js/Screens/ReceiptScreen/OrderReceipt.js new file mode 100644 index 000000000..f8b749643 --- /dev/null +++ b/all_in_one_pos_kit/static/src/category_wise_receipt/js/Screens/ReceiptScreen/OrderReceipt.js @@ -0,0 +1,24 @@ +odoo.define('pos_category_wise_receipt.receipt', function(require) { + 'use strict'; +// Extending OrderReceipt for printing category wise receipts of products + const OrderReceipt =require('point_of_sale.OrderReceipt'); + const Registries = require('point_of_sale.Registries'); + const CategoryOrderReceipt = OrderReceipt => + class extends OrderReceipt { + get orderlines() { + var order_lines = this.receiptEnv.orderlines; + var categ = { + 'category': [], + 'orderlines': order_lines + } + 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/Screens/ReceiptScreen/OrderReceipt.xml b/all_in_one_pos_kit/static/src/category_wise_receipt/xml/Screens/ReceiptScreen/OrderReceipt.xml new file mode 100644 index 000000000..926933949 --- /dev/null +++ b/all_in_one_pos_kit/static/src/category_wise_receipt/xml/Screens/ReceiptScreen/OrderReceipt.xml @@ -0,0 +1,58 @@ + + + + + +
+ + + + +

+

+ +

+

+ + + + + + + + +
+ + + + + +
+ With a + % + discount +
+
+ +
+ + x + +
+
+
+ + + +
+ Subtotal + +
+
+
+
+
+
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..9c3d72a1c --- /dev/null +++ b/all_in_one_pos_kit/static/src/custom_tip/js/PaymentScreen.js @@ -0,0 +1,35 @@ +odoo.define('all_in_one_pos_kit.tips', function (require) { + 'use strict'; + + const Registries = require('point_of_sale.Registries'); + const PaymentScreen = require('point_of_sale.PaymentScreen'); + const { parse } = require('web.field_utils'); +/* Add new percentage custom tip in payment screen */ + const CustomButtonPaymentScreen = (PaymentScreen) => + class extends PaymentScreen { + setup() { + super.setup(...arguments); + } + /* Click function of custom tip button */ + async CustomTipButton(){ + // It is used to update the tip to payment + var custom_tip_percentage = this.env.pos.config.custom_tip_percentage + if(custom_tip_percentage){ + var cust_tip = ((this.currentOrder.get_total_with_tax() + + this.currentOrder.get_rounding_applied() ) + * parseInt(custom_tip_percentage) /100); + let value = cust_tip === 0 && change > 0 ? change : cust_tip; + const { confirmed, payload } = await this.showPopup('NumberPopup', { + title: cust_tip ? this.env._t('Change Tip') : this.env._t('Add Tip'), + startingValue: value, + isInputSelected: true, + }); + if (confirmed) { + this.currentOrder.set_tip(parse.float(payload.toString())); + } + } + } + }; + 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..0c2254195 --- /dev/null +++ b/all_in_one_pos_kit/static/src/custom_tip/xml/PaymentScreen.xml @@ -0,0 +1,20 @@ + + + + + + + +
+ + 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..ec1b234cc --- /dev/null +++ b/all_in_one_pos_kit/static/src/dashboard/css/pos_dashboard.css @@ -0,0 +1,1009 @@ +.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{ + font-size: 20px; + position: relative; + text-align: center; +} +.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; +} \ No newline at end of file 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..d673d4d2e --- /dev/null +++ b/all_in_one_pos_kit/static/src/dashboard/js/pos_dashboard.js @@ -0,0 +1,490 @@ +odoo.define('dashboard_pos.Dashboard', function (require) { +"use strict"; + +var AbstractAction = require('web.AbstractAction'); +var ajax = require('web.ajax'); +var core = require('web.core'); +var rpc = require('web.rpc'); +var session = require('web.session'); +var web_client = require('web.web_client'); +var _t = core._t; +var QWeb = core.qweb; + +var PosDashboard = AbstractAction.extend({ + 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) { + 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() { + var self = this; + return $.when(ajax.loadLibs(this), this._super()).then(function() { + return self.fetch_data(); + }); + }, + + start: function() { + var 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() { + var 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() { + var self = this; + _.each(this.dashboards_templates, function(template) { + self.$('.o_pos_dashboard').append(QWeb.render(template, {widget: self})); + }); + }, + render_graphs: function(){ + var self = this; + self.render_top_customer_graph(); + self.render_top_product_graph(); + self.render_product_category_graph(); + }, + pos_order_today: function(e){ + var self = this; + var date = new Date(); + var yesterday = new Date(date.getTime()); + yesterday.setDate(date.getDate() - 1); + e.stopPropagation(); + e.preventDefault(); + + session.user_has_group('hr.group_hr_user').then(function(has_group){ + if(has_group){ + var options = { + on_reverse_breadcrumb: self.on_reverse_breadcrumb, + }; + 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' + }, options) + } + }); + + }, + + + pos_refund_orders: function(e){ + var self = this; + var date = new Date(); + var yesterday = new Date(date.getTime()); + yesterday.setDate(date.getDate() - 1); + e.stopPropagation(); + e.preventDefault(); + + session.user_has_group('hr.group_hr_user').then(function(has_group){ + if(has_group){ + var options = { + on_reverse_breadcrumb: self.on_reverse_breadcrumb, + }; + 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' + }, options) + } + }); + + }, + pos_refund_today_orders: function(e){ + var self = this; + var date = new Date(); + var yesterday = new Date(date.getTime()); + yesterday.setDate(date.getDate() - 1); + e.stopPropagation(); + e.preventDefault(); + + session.user_has_group('hr.group_hr_user').then(function(has_group){ + if(has_group){ + var options = { + on_reverse_breadcrumb: self.on_reverse_breadcrumb, + }; + 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' + }, options) + } + }); + + }, + + pos_order: function(e){ + var self = this; + var date = new Date(); + var yesterday = new Date(date.getTime()); + yesterday.setDate(date.getDate() - 1); + e.stopPropagation(); + e.preventDefault(); + session.user_has_group('hr.group_hr_user').then(function(has_group){ + if(has_group){ + var options = { + on_reverse_breadcrumb: self.on_reverse_breadcrumb, + }; + 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' + }, options) + } + }); + + }, + pos_session: function(e){ + var self = this; + e.stopPropagation(); + e.preventDefault(); + session.user_has_group('hr.group_hr_user').then(function(has_group){ + if(has_group){ + var options = { + on_reverse_breadcrumb: self.on_reverse_breadcrumb, + }; + 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' + }, options) + } + }); + + }, + onclick_pos_sales:function(events){ + var option = $(events.target).val(); + var self = this + var ctx = self.$("#canvas_1"); + rpc.query({ + model: "pos.order", + method: "get_department", + args: [option], + }).then(function (arrays) { + var 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 + }, + ] + }; + var 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 + } + }] + } + }; + if (window.myCharts != undefined) + window.myCharts.destroy(); + window.myCharts = new Chart(ctx, { + type: "bar", + data: data, + options: options + }); + }); + }, + render_top_customer_graph:function(){ + var self = this + var ctx = self.$(".top_customer"); + rpc.query({ + model: "pos.order", + method: "get_the_top_customer", + }).then(function (arrays) { + var 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 + }] + }; + var 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 + } + }] + } + }; + //create Chart class object + var chart = new Chart(ctx, { + type: "pie", + data: data, + options: options + }); + }); + }, + + render_top_product_graph:function(){ + var self = this + var ctx = self.$(".top_selling_product"); + rpc.query({ + model: "pos.order", + method: "get_the_top_products", + }).then(function (arrays) { + var 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 + }] + }; + var 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 + } + }] + } + }; + var chart = new Chart(ctx, { + type: "horizontalBar", + data: data, + options: options + }); + }); + }, + render_product_category_graph:function(){ + var self = this + var ctx = self.$(".top_product_categories"); + rpc.query({ + model: "pos.order", + method: "get_the_top_categories", + }).then(function (arrays) { + + + var 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 + var 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 + } + }] + } + }; + + //create Chart class object + var chart = new Chart(ctx, { + type: "horizontalBar", + data: data, + options: options + }); + + }); + }, +}); +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..433ef8971 --- /dev/null +++ b/all_in_one_pos_kit/static/src/dashboard/xml/pos_dashboard.xml @@ -0,0 +1,293 @@ + + + +
+
+
+
+
+ +
+
+
+
+
+
+
+
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..15c2b0183 --- /dev/null +++ b/all_in_one_pos_kit/static/src/delete_order_line/js/clear_button.js @@ -0,0 +1,36 @@ +odoo.define('pos_delete_orderline.DeleteOrderLinesAll', function(require) { +'use strict'; + const { Gui } = require('point_of_sale.Gui'); + const PosComponent = require('point_of_sale.PosComponent'); + const { posbus } = require('point_of_sale.utils'); + const ProductScreen = require('point_of_sale.ProductScreen'); + const { useListener } = require('web.custom_hooks'); + const Registries = require('point_of_sale.Registries'); + const PaymentScreen = require('point_of_sale.PaymentScreen'); + class OrderLineClearALL extends PosComponent { + constructor() { + super(...arguments); + useListener('click', this.onClick); + } + async onClick() { + 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){ + + const order = this.env.pos.get_order(); + order.remove_orderline(order.get_orderlines()); + } + } + } + OrderLineClearALL.template = 'OrderLineClearALL'; + ProductScreen.addControlButton({ + 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..79f27098f --- /dev/null +++ b/all_in_one_pos_kit/static/src/delete_order_line/js/clear_order_line.js @@ -0,0 +1,22 @@ +odoo.define('pos_delete_orderline.DeleteOrderLines', function(require) { +'use strict'; + const { useState, useRef, onPatched } = owl.hooks; + const { useListener } = require('web.custom_hooks'); + const { onChangeOrder } = require('point_of_sale.custom_hooks'); + const PosComponent = require('point_of_sale.PosComponent'); + const Registries = require('point_of_sale.Registries'); + const Orderline = require('point_of_sale.Orderline'); + const ProductScreen = require('point_of_sale.ProductScreen'); + const OrderWidget = require('point_of_sale.OrderWidget'); + const OrderLineDelete = (Orderline) => + class extends Orderline { + async clear_button_fun() { + 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..2ac2cefa1 --- /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..2db7f073a --- /dev/null +++ b/all_in_one_pos_kit/static/src/delete_order_line/xml/clear_order_line.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/all_in_one_pos_kit/static/src/exchange_product/js/AllOrderScreen.js b/all_in_one_pos_kit/static/src/exchange_product/js/AllOrderScreen.js new file mode 100644 index 000000000..4f0f9dcd1 --- /dev/null +++ b/all_in_one_pos_kit/static/src/exchange_product/js/AllOrderScreen.js @@ -0,0 +1,61 @@ +odoo.define('product_exchange_pos_sys.all_order_screen', 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'); + const { Gui } = require('point_of_sale.Gui'); + var rpc = require('web.rpc'); + var core = require('web.core'); + + class CustomOrdrScreen extends PosComponent { + setup() { + super.setup(); + useListener('click-order', this._onClickOrder); + this.state = { + order: this.props.orders, + pos: this.env.pos + }; + } +// Going back to the product screen + back() { + this.showScreen('ProductScreen'); + } +// Refresh window + reload(){ + window.location.reload(); + } +// Function for fetching all orders + async _onClickOrder(order, pos) { + if (order.is_exchange == true) { + await Gui.showPopup('ErrorPopup', { + title: this.env._t('Exchange order'), + body: this.env._t('Already created the Exchange order') + }); + } else { + var lines = [] + var self = this; + this.rpc({ + model: 'pos.order.line', + method: 'get_product_details', + args: [order.lines], + }).then(async function(value) { + for (var i = 0; i < self.state.order.length; i++) { + lines.push(self.state.order[i]) + } + var _t = core._t; + await self.trigger('close-temp-screen'); + Gui.showPopup("ExchangeOrder", { + title: _t("Exchange Order"), + cancelText: 'Cancel', + 'order_line': value, + }); + }); + } + } + }; + CustomOrdrScreen.template = 'CustomOrdrScreen'; + Registries.Component.add(CustomOrdrScreen); + return CustomOrdrScreen; +}); diff --git a/all_in_one_pos_kit/static/src/exchange_product/js/ExchangeOrder.js b/all_in_one_pos_kit/static/src/exchange_product/js/ExchangeOrder.js new file mode 100644 index 000000000..9ceeedbaf --- /dev/null +++ b/all_in_one_pos_kit/static/src/exchange_product/js/ExchangeOrder.js @@ -0,0 +1,39 @@ +odoo.define('product_exchange_pos_sys.ExchangeOrder', function(require) { + 'use strict'; + + const AbstractAwaitablePopup = require('point_of_sale.AbstractAwaitablePopup'); + const Registries = require('point_of_sale.Registries'); + + class ExchangeOrder extends AbstractAwaitablePopup { + constructor() { + super(...arguments); + } +// Confirming the exchange returns to the product screen + async confirm() { + const CurrentOrder = this.env.pos.get_order() + for (var i = 0; i < this.props.order_line.length; i++) { + var order_id = this.props.order_line[i].order_id + var product = this.env.pos.db.get_product_by_id(this.props.order_line[i].product_id) + CurrentOrder.add_product(product, { + quantity: -this.props.order_line[i].qty + }) + } + this.rpc({ + model: 'pos.order', + method: 'pos_exchange_order', + args: [this.props.order_line[0].order_id], + }) + this.showScreen('ProductScreen'); + super.confirm(); + } + } + ExchangeOrder.template = 'ExchangeOrder'; + ExchangeOrder.defaultProps = { + confirmText: 'Ok', + cancelText: 'Cancel', + title: 'Confirm ?', + body: '', + }; + Registries.Component.add(ExchangeOrder); + return ExchangeOrder; +}); diff --git a/all_in_one_pos_kit/static/src/exchange_product/js/OrderButton.js b/all_in_one_pos_kit/static/src/exchange_product/js/OrderButton.js new file mode 100644 index 000000000..eec7edc48 --- /dev/null +++ b/all_in_one_pos_kit/static/src/exchange_product/js/OrderButton.js @@ -0,0 +1,30 @@ +odoo.define('product_exchange_pos_sys.Orders', 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 AllOrders extends PosComponent { + setup() { + super.setup(); + useListener('click', this.onClick); + } +// Adding the button All order's + onClick() { + this.showScreen('CustomOrdrScreen', { + orders: this.env.pos.pos_orders, + pos: this.env.pos + }); + } + } + AllOrders.template = 'AllOrders'; + ProductScreen.addControlButton({ + component: AllOrders, + condition: function() { + return true; + }, + }); + Registries.Component.add(AllOrders); + return AllOrders; +}); 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..5c29a169c --- /dev/null +++ b/all_in_one_pos_kit/static/src/exchange_product/js/models.js @@ -0,0 +1,22 @@ +odoo.define('product_exchange_pos_sys.models', function(require) { + "use strict"; + var models = require('point_of_sale.models'); + /** + * Loading the required model and fields + **/ + models.load_models({ + model: 'pos.order', + fields: ['name', 'date_order', 'pos_reference', + 'partner_id', 'lines', 'is_exchange' + ], + loaded: function(self, pos_orders) { + self.pos_orders = pos_orders; + } + }, { + model: 'pos.order.line', + fields: ['product_id', 'qty', 'price_subtotal', 'total_cost'], + loaded: function(self, pos_order_lines) { + self.pos_order_lines = pos_order_lines; + } + }); +}); 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/AllOrderScreen.xml b/all_in_one_pos_kit/static/src/exchange_product/xml/AllOrderScreen.xml new file mode 100644 index 000000000..42dc477c8 --- /dev/null +++ b/all_in_one_pos_kit/static/src/exchange_product/xml/AllOrderScreen.xml @@ -0,0 +1,52 @@ + + + + +
+
+
+
+ Back +
+ + + Refresh + +
+
+
+
+
+
+
+ + + + + + + + + + + + + +
Order ReferenceReceipt ReferenceCustomerOrder Date
+ + + +
+
+
+
+
+
+
+
+
+
diff --git a/all_in_one_pos_kit/static/src/exchange_product/xml/ExchangeOrder.xml b/all_in_one_pos_kit/static/src/exchange_product/xml/ExchangeOrder.xml new file mode 100644 index 000000000..4d6b38af3 --- /dev/null +++ b/all_in_one_pos_kit/static/src/exchange_product/xml/ExchangeOrder.xml @@ -0,0 +1,37 @@ + + + + + + + diff --git a/all_in_one_pos_kit/static/src/exchange_product/xml/OrderButton.xml b/all_in_one_pos_kit/static/src/exchange_product/xml/OrderButton.xml new file mode 100644 index 000000000..ab1ad35c3 --- /dev/null +++ b/all_in_one_pos_kit/static/src/exchange_product/xml/OrderButton.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..694263672 --- /dev/null +++ b/all_in_one_pos_kit/static/src/mass_edit/js/pos_mass_edit_button.js @@ -0,0 +1,47 @@ +odoo.define('pos_order_line_mass_edit.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 { + setup() { + super.setup(); + useListener('click', this.onClick); + } + + async onClick() { + const order = this.env.pos.get_order(); + const orderLines = order.get_orderlines(); + if (!orderLines.length) { + 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', { + title: this.env._t('Edit Order Line'), + body: orderLines.map(line => ({ + id: line.id, + product: line.product, + quantity: line.quantity, + price: line.price, + discount: line.discount, + })), + }); + } + } + 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..d1ab46352 --- /dev/null +++ b/all_in_one_pos_kit/static/src/mass_edit/js/pos_mass_edit_popup.js @@ -0,0 +1,40 @@ +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() { + // Get the current order + const order = this.env.pos.get_order(); + // Iterate through each order line and update the quantity and other fields + this.props.body.forEach(line => { + let orderLine = order.get_orderline(line.id); + if (orderLine) { + orderLine.set_quantity(line.quantity); + orderLine.set_unit_price(line.price); + if (this.env.pos.config.manual_discount) { + orderLine.set_discount(line.discount); + } + } + }); + // Close the popup + this.trigger('close-popup'); + } + sendInput(key) { + this.props.body.forEach(edit => { + if (edit.id == key) { + edit.quantity = 0; + } + }); + this.render(); + } + } + MassEditPopup.template = 'MassEditPopup'; + MassEditPopup.defaultProps = { + confirmText: "Confirm", + cancelText: "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..cedc0f52f --- /dev/null +++ b/all_in_one_pos_kit/static/src/mass_edit/xml/pos_mass_edit_popup.xml @@ -0,0 +1,63 @@ + + + + + + diff --git a/all_in_one_pos_kit/static/src/multi_barcode/js/ProductsWidgetControlPanel.js b/all_in_one_pos_kit/static/src/multi_barcode/js/ProductsWidgetControlPanel.js new file mode 100644 index 000000000..1b26d70ca --- /dev/null +++ b/all_in_one_pos_kit/static/src/multi_barcode/js/ProductsWidgetControlPanel.js @@ -0,0 +1,36 @@ +odoo.define('multi_barcodes_pos.barcode_search_db', function(require) { + "use strict"; + +var PosDB = require('point_of_sale.DB'); + +PosDB.include({ + +_product_search_string: function(product) { + var result = product.pos.product_barcodes.filter(function(dataObj){ + return dataObj.product_multi[0] === product.id + }) + var str = product.display_name; + if (product.barcode) { + str += '|' + product.barcode; + } + if (product.default_code) { + str += '|' + product.default_code; + } + if (product.description) { + str += '|' + product.description; + } + if (product.description_sale) { + str += '|' + product.description_sale; + } + if (product.barcode_carton) { + str += '|' + product.barcode_carton; + } + if (result.length !=0) { + result.forEach(item => str += '|' + item.multi_barcode); + } + str = product.id + ':' + str.replace(/:/g, '') + '\n'; + return str; +}, + +}); +}); 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..74851a67f --- /dev/null +++ b/all_in_one_pos_kit/static/src/multi_barcode/js/pos_scan.js @@ -0,0 +1,61 @@ +odoo.define('multi_barcodes_pos.product', function (require) { + "use strict"; +var rpc = require('web.rpc'); +var models = require('point_of_sale.models'); +var DB = require('point_of_sale.DB'); +models.load_fields("product.product", ['product_multi_barcodes']); +DB.include({ + + init: function(options){ + this._super.apply(this, arguments); + + }, + add_products: function(products){ + 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]; + var search_string = 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.length;t++){ + var self = this; + rpc.query({ + model: 'multi.barcode.product', + method: 'get_barcode_val', + args: [product.product_multi_barcodes[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/multi_barcode/js/search_bar.js b/all_in_one_pos_kit/static/src/multi_barcode/js/search_bar.js new file mode 100644 index 000000000..f9e3e1b61 --- /dev/null +++ b/all_in_one_pos_kit/static/src/multi_barcode/js/search_bar.js @@ -0,0 +1,14 @@ +odoo.define('multi_barcodes_pos.barcode_search', function(require) { + "use strict"; + + var models = require('point_of_sale.models'); + models.load_models({ + model: 'multi.barcode.product', + fields: ['id','multi_barcode','product_multi'], + loaded: function(self, barcodes){ + self.set('multi_barcode',barcodes); + self.product_barcodes = barcodes; + self.db.product_barcodes = barcodes; + }, + },{'before': 'product.product'}); +}); 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..d9af6e1e1 --- /dev/null +++ b/all_in_one_pos_kit/static/src/order_item_count/js/pos_receipt.js @@ -0,0 +1,24 @@ +odoo.define('all_in_one_pos_kit.OrderReceipt', function(require) { + 'use strict'; + + const OrderReceipt = require('point_of_sale.OrderReceipt') + const Registries = require('point_of_sale.Registries'); + var { Order } = require('point_of_sale.models'); + const OrderReceiptCount = OrderReceipt => + // extending the pos receipt screen + class extends OrderReceipt { + get receiptEnv() { + let receipt_render_env = super.receiptEnv; + let receipt = receipt_render_env.receipt; + receipt.count = this._receiptEnv.orderlines.length; + var sum = 0; + this._receiptEnv.orderlines.forEach(function(t) { + sum += t.quantity; + }) + receipt.sum = sum + return receipt_render_env; + } + } + Registries.Component.extend(OrderReceipt, OrderReceiptCount); + return OrderReceiptCount + }); 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..584968f55 --- /dev/null +++ b/all_in_one_pos_kit/static/src/order_item_count/js/product_screen.js @@ -0,0 +1,63 @@ +odoo.define('all_in_one_pos_kit.ItemsCount', function(require) { + 'use strict'; + const { Gui } = require('point_of_sale.Gui'); + 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'); + const PaymentScreen = require('point_of_sale.PaymentScreen'); + + class ItemsCount extends PosComponent { + setup() { + super.setup(); + } + get order() { + return this.env.pos.get_order(); + } + mounted() { + this.order.on('change', () => { + this.get_items_count(); + this.get_items_qty(); + this.render(); + }); + this.order.orderlines.on('change', () => { + this.get_items_count(); + this.get_items_qty(); + this.render(); + }); + } + get_items_count() { + /** + * Get the count of order lines in the current order. + * + * @returns {number} The number of order lines in the current order. + */ + var order = this.env.pos.get_order(); + var count = order.orderlines.length; + return count + } + 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 order = this.env.pos.get_order(); + var sum = 0; + 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..c2330c8ac --- /dev/null +++ b/all_in_one_pos_kit/static/src/order_item_count/xml/pos_items_count.xml @@ -0,0 +1,19 @@ + + + + +
+
+ + 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..1918233cd --- /dev/null +++ b/all_in_one_pos_kit/static/src/order_item_count/xml/pos_receipt.xml @@ -0,0 +1,20 @@ + + + + + +
+ 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..e7e337209 --- /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; +} \ No newline at end of file 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..5b2330e88 --- /dev/null +++ b/all_in_one_pos_kit/static/src/order_line_image/xml/pos_order_line.xml @@ -0,0 +1,11 @@ + + 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..e2e502b0f --- /dev/null +++ b/all_in_one_pos_kit/static/src/pos_auto_lot/js/auto_lot.js @@ -0,0 +1,84 @@ +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'); +//Extending ProductScreen + const PosLotSaleProductScreen = (ProductScreen) => + class extends ProductScreen { + async _getAddProductOptions(product, code) { + let price_extra = 0.0; + let draftPackLotLines, weight, description, packLotLinesToEdit; + let productConfiguratorPayload; + if (this.env.pos.config.product_configurator && _.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); + if (attributes.length > 0) { + 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 = []; + } + } + try { + const result = await rpc.query({ + model: "stock.production.lot", + method: "get_available_lots_for_pos", + args: [product.id], + }); + + const modifiedPackLotLines = result[0]; + const newPackLotLines = result.map(item => ({ lot_name: result[0] })); + draftPackLotLines = { modifiedPackLotLines, newPackLotLines }; + } catch (error) { + console.error('Error fetching lots:', error); + return; + } + } + // Take the weight if necessary. + if (product.to_weight && this.env.pos.config.iface_electronic_scale) { + if (this.isScaleAvailable) { + const { confirmed, payload } = await this.showTempScreen('ScaleScreen', { product }); + if (confirmed) { + weight = payload.weight; + } else { + return; + } + } else { + await this._onScaleNotAvailable(); + } + } + if (code && this.env.pos.db.product_packaging_by_barcode[code.code]) { + weight = this.env.pos.db.product_packaging_by_barcode[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/js/pos_image_field.js b/all_in_one_pos_kit/static/src/pos_logo/js/pos_image_field.js new file mode 100755 index 000000000..b89d5f3be --- /dev/null +++ b/all_in_one_pos_kit/static/src/pos_logo/js/pos_image_field.js @@ -0,0 +1,24 @@ +odoo.define("all_in_one_pos_kit.pos_image_field", function (require) { + "use strict"; + const Registries = require('point_of_sale.Registries'); + const Chrome = require('point_of_sale.Chrome'); +// Extending Chrome + const PosLogoChrome = (Chrome) => + class extends Chrome { + get imageUrl() { + if (this.env.pos){ + if (this.env.pos.config){ + if (this.env.pos.config.image != false){ + return `/web/image?model=pos.config&field=image&id=${this.env.pos.config_id}&unique=1`; + + + }else{ + return false + } + } + } + } + }; + Registries.Component.extend(Chrome, PosLogoChrome); + return Chrome; +}); diff --git a/all_in_one_pos_kit/static/src/pos_logo/js/pos_receipt_image.js b/all_in_one_pos_kit/static/src/pos_logo/js/pos_receipt_image.js new file mode 100644 index 000000000..2cbe488be --- /dev/null +++ b/all_in_one_pos_kit/static/src/pos_logo/js/pos_receipt_image.js @@ -0,0 +1,22 @@ +odoo.define("all_in_one_pos_kit.pos_receipt_image", function (require) { + "use strict"; + const Registries = require('point_of_sale.Registries'); + const OrderReceipt = require('point_of_sale.OrderReceipt'); +// Extending OrderReceipt + const PosReceiptLogoChrome = (OrderReceipt) => + class extends OrderReceipt { + get imageUrl() { + if (this.env.pos){ + if (this.env.pos.config){ + if (this.env.pos.config.image != false){ + return `/web/image?model=pos.config&field=image&id=${this.env.pos.config_id}&unique=1`; + }else{ + return false + } + } + } + } + }; + Registries.Component.extend(OrderReceipt, PosReceiptLogoChrome); + return OrderReceipt; +}); diff --git a/all_in_one_pos_kit/static/src/pos_logo/xml/pos_screen_image_views.xml b/all_in_one_pos_kit/static/src/pos_logo/xml/pos_screen_image_views.xml new file mode 100755 index 000000000..6260896a8 --- /dev/null +++ b/all_in_one_pos_kit/static/src/pos_logo/xml/pos_screen_image_views.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + diff --git a/all_in_one_pos_kit/static/src/pos_logo/xml/pos_ticket_views.xml b/all_in_one_pos_kit/static/src/pos_logo/xml/pos_ticket_views.xml new file mode 100755 index 000000000..0a8ecb06b --- /dev/null +++ b/all_in_one_pos_kit/static/src/pos_logo/xml/pos_ticket_views.xml @@ -0,0 +1,20 @@ + + + + +
+ + + + + + +
+
+ +
+
+
+
+
+
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..b2c6cfd39 --- /dev/null +++ b/all_in_one_pos_kit/static/src/pos_mrp_order/js/models.js @@ -0,0 +1,71 @@ +odoo.define('all_in_one_pos_kit.models_mrp_order', function (require) { +"use strict"; +var models = require('point_of_sale.models'); +const PaymentScreen = require('point_of_sale.PaymentScreen'); +const Registries = require('point_of_sale.Registries'); +var rpc = require('web.rpc'); + var existing_models = models.PosModel.prototype.models; + var product_index = _.findIndex(existing_models, function (model) { + return model.model === "product.product"; + }); + var product_model = existing_models[product_index]; + product_model.fields.push('to_make_mrp'); + const MRPPaymentScreen = (PaymentScreen) => + class extends PaymentScreen { + constructor() { + super(...arguments); + } + createMRP(){ + const order = this.currentOrder; + var order_line = order.orderlines.models; + var due = order.get_due(); + for (var i in order_line) + { + var list_product = [] + if (order_line[i].product.to_make_mrp) + { + 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], + }); + } + } + } + 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..e6d499fac --- /dev/null +++ b/all_in_one_pos_kit/static/src/pos_num_show_hide/js/pos_numpad.js @@ -0,0 +1,12 @@ +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(ProductScreen.prototype, 'hide_show_numpad', { + NumpadVisibility() { + $('.pads').slideToggle('slow', function() { + $('.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..1a923c9ac --- /dev/null +++ b/all_in_one_pos_kit/static/src/pos_num_show_hide/xml/pos.xml @@ -0,0 +1,12 @@ + + + + + +
+ + +
+
+
+
diff --git a/all_in_one_pos_kit/static/src/pos_report/css/pos_report.css b/all_in_one_pos_kit/static/src/pos_report/css/pos_report.css new file mode 100644 index 000000000..a93d0512d --- /dev/null +++ b/all_in_one_pos_kit/static/src/pos_report/css/pos_report.css @@ -0,0 +1,43 @@ +.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; +} +html .o_web_client > .o_action_manager { + overflow:scroll; +} \ No newline at end of file diff --git a/all_in_one_pos_kit/static/src/pos_report/js/action_manager.js b/all_in_one_pos_kit/static/src/pos_report/js/action_manager.js new file mode 100644 index 000000000..985c785bc --- /dev/null +++ b/all_in_one_pos_kit/static/src/pos_report/js/action_manager.js @@ -0,0 +1,21 @@ +/** @odoo-module */ + +import { registry } from "@web/core/registry"; +import { download } from "@web/core/network/download"; +import framework from 'web.framework'; +import session from 'web.session'; +//Excel report +registry.category("ir.actions.report handlers").add("xlsx", async (action) => { + if (action.report_type === 'xlsx') { + framework.blockUI(); + var def = $.Deferred(); + session.get_file({ + url: '/xlsx_reports', + data: action.data, + success: def.resolve.bind(def), + error: (error) => this.call('crash_manager', 'rpc_error', error), + complete: framework.unblockUI, + }); + return def; + } +}); 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..f5525ab55 --- /dev/null +++ b/all_in_one_pos_kit/static/src/pos_report/js/pos_report.js @@ -0,0 +1,225 @@ +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 datepicker = require('web.datepicker'); + var time = require('web.time'); + var framework = require('web.framework'); + var session = require('web.session'); + + 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) { + this._super(parent, action); + this.report_lines = action.report_lines; + this.wizard_id = action.context.wizard | null; + + }, + start: function() { + 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) { + var $calendarInputGroup = $(ev.currentTarget); + + var calendarOptions = { + + 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, + }; + $calendarInputGroup.datetimepicker(calendarOptions); + }, + load_data: function(initial_render = true) { + var 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) { + e.preventDefault(); + var self = this; + var action_title = self._title; + 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() { + var 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){ + 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) { + event.preventDefault(); + var self = this; + var context = {}; + 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() { + var 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 ($(".report_type").length) { + var report_res = document.getElementById("report_res") + filter_data_selected.report_type = $(".report_type")[1].value + report_res.value = $(".report_type")[1].value + report_res.innerHTML = report_res.value; + if ($(".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..63808a80f --- /dev/null +++ b/all_in_one_pos_kit/static/src/pos_report/xml/pos_report_view.xml @@ -0,0 +1,476 @@ + + +
+
+
+

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/img/content.png b/all_in_one_pos_kit/static/src/product_creation/img/content.png new file mode 100644 index 000000000..a2d397083 Binary files /dev/null and b/all_in_one_pos_kit/static/src/product_creation/img/content.png differ diff --git a/all_in_one_pos_kit/static/src/product_creation/js/product_create_button.js b/all_in_one_pos_kit/static/src/product_creation/js/product_create_button.js new file mode 100644 index 000000000..93f9743ed --- /dev/null +++ b/all_in_one_pos_kit/static/src/product_creation/js/product_create_button.js @@ -0,0 +1,74 @@ +odoo.define('all_in_one_pos_kit.product_create_button', function(require) { + 'use strict'; + const { Gui } = require('point_of_sale.Gui'); + const PosComponent = require('point_of_sale.PosComponent'); + const { posbus } = require('point_of_sale.utils'); + const ProductScreen = require('point_of_sale.ProductScreen'); + const { useListener } = require('web.custom_hooks'); + const Registries = require('point_of_sale.Registries'); + const PaymentScreen = require('point_of_sale.PaymentScreen'); + const ajax = require('web.ajax'); + + class ProductCreateButton extends PosComponent { + constructor() { + super(...arguments); + useListener('click', this.onClick); + } + async onClick() { + var self = this; + const { + confirmed, + payload + } = await this.showPopup('ProductCreatePopup', { + title: this.env._t('POS Product Creation'), + body: this.env._t('You can Create The product.'), + }); + if (confirmed) { + var product_category = payload[0]; + var product_name = payload[1]; + var product_reference = payload[2]; + var product_price = payload[3]; + var unit_measure = payload[4]; + var product_categories = payload[5]; + var barcode = "" + if (payload[6]) { + barcode = payload[6]; + } + if (!product_name){ + return this.showPopup('ErrorPopup', { + title: _('Product Name Is Required'), + }); + } + if (!unit_measure){ + return this.showPopup('ErrorPopup', { + title: _('A Unit Of Measure Is Required'), + }); + } + if (!product_categories){ + return this.showPopup('ErrorPopup', { + title: _('Product Category is Required'), + }); + } + ajax.jsonRpc('/create_product', 'call', { + 'category': product_category, + 'name': product_name, + 'price': product_price, + 'product_reference': product_reference, + 'unit_measure': unit_measure, + 'product_categories': product_categories, + 'barcode': barcode, + }).then(function(response) {}); + } + } + } + ProductCreateButton.template = 'ProductCreateButton'; + ProductScreen.addControlButton({ + component: ProductCreateButton, + condition: function() { + return true; + }, + position: ['before', 'SetPricelistButton'], + }); + Registries.Component.add(ProductCreateButton); + return ProductCreateButton; +}); diff --git a/all_in_one_pos_kit/static/src/product_creation/js/product_create_popup.js b/all_in_one_pos_kit/static/src/product_creation/js/product_create_popup.js new file mode 100644 index 000000000..56bfd7d2b --- /dev/null +++ b/all_in_one_pos_kit/static/src/product_creation/js/product_create_popup.js @@ -0,0 +1,53 @@ +odoo.define('all_in_one_pos_kit.product_create_popup', function(require) { + 'use strict'; + const { + useState, + useRef + } = owl.hooks; + const AbstractAwaitablePopup = require('point_of_sale.AbstractAwaitablePopup'); + const Registries = require('point_of_sale.Registries'); + + class ProductCreatePopup extends AbstractAwaitablePopup { + constructor() { + super(...arguments); + this.state = useState({ + typeValue: this.props.startingValue, + productValue: this.props.startingValue, + priceValue: this.props.priceValue, + productRef: this.props.startingValue + }); + } + getPayload() { + var selected_vals = []; + var category = this.state.typeValue; + var product = this.state.productValue; + var product_reference = this.state.productRef; + var price = this.state.priceValue; + var unit = this.state.unitValue; + var product_category = this.state.categoryValue; + var barcode = this.state.barcodeValue; + selected_vals.push(category); + selected_vals.push(product); + selected_vals.push(product_reference); + selected_vals.push(price); + selected_vals.push(unit); + selected_vals.push(product_category); + selected_vals.push(barcode); + return selected_vals + } + } + + ProductCreatePopup.defaultProps = { + confirmText: 'Ok', + cancelText: 'Cancel', + array: [], + title: 'Create ?', + body: '', + startingValue: '', + priceValue: 1, + list: [], + }; + ProductCreatePopup.template = 'ProductCreatePopup'; + Registries.Component.add(ProductCreatePopup); + return ProductCreatePopup; +}); diff --git a/all_in_one_pos_kit/static/src/product_creation/xml/product_create_button.xml b/all_in_one_pos_kit/static/src/product_creation/xml/product_create_button.xml new file mode 100644 index 000000000..94654fd37 --- /dev/null +++ b/all_in_one_pos_kit/static/src/product_creation/xml/product_create_button.xml @@ -0,0 +1,11 @@ + + + + + + + Create Product + + + + diff --git a/all_in_one_pos_kit/static/src/product_creation/xml/product_create_popup.xml b/all_in_one_pos_kit/static/src/product_creation/xml/product_create_popup.xml new file mode 100644 index 000000000..f6eb0ac2c --- /dev/null +++ b/all_in_one_pos_kit/static/src/product_creation/xml/product_create_popup.xml @@ -0,0 +1,102 @@ + + + + + + diff --git a/all_in_one_pos_kit/static/src/product_magnify_image/css/pos_magnify_image.css b/all_in_one_pos_kit/static/src/product_magnify_image/css/pos_magnify_image.css new file mode 100755 index 000000000..bc3b472d0 --- /dev/null +++ b/all_in_one_pos_kit/static/src/product_magnify_image/css/pos_magnify_image.css @@ -0,0 +1,17 @@ +.pos-product-magnify { + position: absolute; + top: 0px; + left: 20px; + line-height: 13px; + border-radius: 2px; + z-index: 1000; +} +.product-large-image{ + margin: auto; + max-width: auto; + max-height: 250px !important; +} +.product-large-image img{ + max-width: 100%; + max-height: 250px !important; +} \ No newline at end of file diff --git a/all_in_one_pos_kit/static/src/product_magnify_image/js/MagnifyProductPopup.js b/all_in_one_pos_kit/static/src/product_magnify_image/js/MagnifyProductPopup.js new file mode 100644 index 000000000..f9dd76ae3 --- /dev/null +++ b/all_in_one_pos_kit/static/src/product_magnify_image/js/MagnifyProductPopup.js @@ -0,0 +1,23 @@ +odoo.define('all_in_one_pos_kit.MagnifyProductPopup', function (require) { + "use strict"; + + const AbstractAwaitablePopup = require('point_of_sale.AbstractAwaitablePopup'); + const Registries = require('point_of_sale.Registries'); + + class MagnifyProductPopup extends AbstractAwaitablePopup { + constructor() { + super(...arguments); + } + get largeImageUrl() { + const product = this.props['body']; + return `/web/image?model=product.product&field=image_1920&id=${product.id}&write_date=${product.write_date}&unique=1`; + } + } + MagnifyProductPopup.template = 'MagnifyProductPopup'; + MagnifyProductPopup.defaultProps = { + cancelText: 'Close', + }; + MagnifyProductPopup.template = 'MagnifyProductPopup'; + Registries.Component.add(MagnifyProductPopup); + return MagnifyProductPopup; +}); diff --git a/all_in_one_pos_kit/static/src/product_magnify_image/js/ProductScreen.js b/all_in_one_pos_kit/static/src/product_magnify_image/js/ProductScreen.js new file mode 100755 index 000000000..099fd86ef --- /dev/null +++ b/all_in_one_pos_kit/static/src/product_magnify_image/js/ProductScreen.js @@ -0,0 +1,34 @@ +odoo.define('all_in_one_pos_kit.ProductScreen', function (require) { + "use strict"; + + const { useListener } = require('web.custom_hooks'); + const Registries = require('point_of_sale.Registries'); + const ProductScreen = require('point_of_sale.ProductScreen'); + var core = require('web.core'); + var _t = core._t; + + const MagnifyProduct = ProductScreen => + class extends ProductScreen { + constructor() { + super(...arguments); + useListener('click-magnify-product', this._clickMagnifyProduct); + this.magnifyProduct = false; + } + async _clickMagnifyProduct(event) { + this.magnifyProduct = true; + const { confirmed } = await this.showPopup('MagnifyProductPopup', { + title: this.env._t(event.detail.display_name), + body: this.env._t(event.detail), + }); + this.magnifyProduct = false; + } + async _clickProduct(event) { + if (this.magnifyProduct == true) { + return false; + } + super._clickProduct(event); + } + } + Registries.Component.extend(ProductScreen, MagnifyProduct); + return MagnifyProduct; +}); diff --git a/all_in_one_pos_kit/static/src/product_magnify_image/xml/MagnifyProductPopup.xml b/all_in_one_pos_kit/static/src/product_magnify_image/xml/MagnifyProductPopup.xml new file mode 100644 index 000000000..eba9d912a --- /dev/null +++ b/all_in_one_pos_kit/static/src/product_magnify_image/xml/MagnifyProductPopup.xml @@ -0,0 +1,22 @@ + + + + + + diff --git a/all_in_one_pos_kit/static/src/product_magnify_image/xml/ProductItem.xml b/all_in_one_pos_kit/static/src/product_magnify_image/xml/ProductItem.xml new file mode 100755 index 000000000..f1e2c5b54 --- /dev/null +++ b/all_in_one_pos_kit/static/src/product_magnify_image/xml/ProductItem.xml @@ -0,0 +1,10 @@ + + + + +
+ +
+
+
+
diff --git a/all_in_one_pos_kit/static/src/service_charge/js/service_charge_button.js b/all_in_one_pos_kit/static/src/service_charge/js/service_charge_button.js new file mode 100644 index 000000000..b81160c40 --- /dev/null +++ b/all_in_one_pos_kit/static/src/service_charge/js/service_charge_button.js @@ -0,0 +1,109 @@ +odoo.define('all_in_one_pos_kit.ServiceChargeButton', function (require) { + 'use strict'; + + const models = require('point_of_sale.models'); + const PosComponent = require('point_of_sale.PosComponent'); + const ProductScreen = require('point_of_sale.ProductScreen'); + const Registries = require('point_of_sale.Registries'); + const { useListener } = require("@web/core/utils/hooks"); + // Load custom configuration settings fields + models.load_models([{ + model: 'pos.config', + fields: ['enable_service_charge', 'visibility', 'global_selection', 'global_charge', 'global_product_id', 'is_customer_details', 'is_customer_name', 'is_customer_address', 'is_customer_mobile', 'is_customer_phone', 'is_customer_email', 'is_customer_vat', 'is_qr_code', 'is_invoice_number'], + loaded: function (self, pos_config) { + self.pos_config = Object.assign({}, self.pos_config, pos_config[0]); + } + }]); + + class ServiceChargeButton extends PosComponent { + setup() { + super.setup(); + useListener('click', this._onClick); + } + + async _onClick(event) { + var self = this; + let res_config_settings = await this.rpc({ + model: 'pos.config', + method: 'search_read', + args: [[], ['enable_service_charge', 'visibility', 'global_selection', 'global_charge', 'global_product_id']], + limit: 1, + }); + if (res_config_settings.length === 0) { + await this.showPopup('ErrorPopup', { + title: this.env._t("Configuration Error"), + body: this.env._t("No configuration settings found."), + }); + return; + } + res_config_settings = res_config_settings[0]; + if (!res_config_settings.enable_service_charge) { + await this.showPopup('ErrorPopup', { + title: this.env._t("Service Charge Disabled"), + body: this.env._t("Service charge is not enabled in the settings."), + }); + return; + } + var order = this.env.pos.get_order(); + var lines = order.get_orderlines(); + + if (res_config_settings.visibility === 'global') { + var product = this.env.pos.db.get_product_by_id(res_config_settings.global_product_id[0]); + if (!product) { + await this.showPopup('ErrorPopup', { + title: this.env._t("No service product found"), + body: this.env._t("The service product seems misconfigured. Make sure it is flagged as 'Can be Sold' and 'Available in Point of Sale'."), + }); + return; + } + lines.filter(line => line.get_product() === product) + .forEach(line => order.remove_orderline(line)); + const { confirmed, payload } = await this.showPopup('NumberPopup', { + title: this.env._t('Service Charge'), + startingValue: res_config_settings.global_charge, + isInputSelected: true + }); + if (confirmed && payload > 0) { + if (res_config_settings.global_selection === 'amount') { + order.add_product(product, { price: payload }); + } else { + order.add_product(product, { price: payload / 100 * order.get_total_with_tax() }); + } + } + } else { + var product = this.env.pos.db.get_product_by_id(this.env.pos.config.service_product_id[0]); + if (!product) { + await this.showPopup('ErrorPopup', { + title: this.env._t("No service product found"), + body: this.env._t("The service product seems misconfigured. Make sure it is flagged as 'Can be Sold' and 'Available in Point of Sale'."), + }); + return; + } + lines.filter(line => line.get_product() === product) + .forEach(line => order.remove_orderline(line)); + const { confirmed, payload } = await this.showPopup('NumberPopup', { + title: this.env._t('Service Charge'), + startingValue: this.env.pos.config.service_charge, + isInputSelected: true + }); + if (confirmed && payload > 0) { + if (this.env.pos.config.charge_type === 'amount') { + order.add_product(product, { price: payload }); + } else { + order.add_product(product, { price: payload / 100 * order.get_total_with_tax() }); + } + } + } + } + } + ServiceChargeButton.template = 'ServiceChargeButton'; + + ProductScreen.addControlButton({ + component: ServiceChargeButton, + condition: function() { + return true; + }, + }); + Registries.Component.add(ServiceChargeButton); + return ServiceChargeButton; +}); \ No newline at end of file diff --git a/all_in_one_pos_kit/static/src/service_charge/xml/ServiceChargeButton.xml b/all_in_one_pos_kit/static/src/service_charge/xml/ServiceChargeButton.xml new file mode 100644 index 000000000..9c29ee5d5 --- /dev/null +++ b/all_in_one_pos_kit/static/src/service_charge/xml/ServiceChargeButton.xml @@ -0,0 +1,10 @@ + + + + +
+ + Service Charge +
+
+
diff --git a/all_in_one_pos_kit/static/src/time_based_product/js/pos_model_load.js b/all_in_one_pos_kit/static/src/time_based_product/js/pos_model_load.js new file mode 100644 index 000000000..3ebf200b4 --- /dev/null +++ b/all_in_one_pos_kit/static/src/time_based_product/js/pos_model_load.js @@ -0,0 +1,19 @@ +odoo.define('pos_products_based_on_time.planning', function(require){ + "use strict"; +// To load new model 'meals.planning' to pos + var models = require('point_of_sale.models'); + models.load_models([{ + model: 'meals.planning', + fields: ['time_from', 'time_to','product_ids','state','pos_ids'], + // The domain filter function checks for records where the given + // conditions are satisfied + domain: function(self){ + return ['&',['state','=','activated'], + ['pos_ids','in',self.config_id]]; + }, + // Load specific fields from this model based on a domain filter, + loaded: function(self, meals) { + self.meals_planning = meals; + }, + }]); +}); diff --git a/all_in_one_pos_kit/static/src/time_based_product/js/pos_planning.js b/all_in_one_pos_kit/static/src/time_based_product/js/pos_planning.js new file mode 100644 index 000000000..4cedc63b5 --- /dev/null +++ b/all_in_one_pos_kit/static/src/time_based_product/js/pos_planning.js @@ -0,0 +1,48 @@ +odoo.define('pos_products_based_on_time.ProductsPlanning', function(require) { + 'use strict'; + + const ProductsWidget = require('point_of_sale.ProductsWidget'); + const Registries = require('point_of_sale.Registries'); + // Define a new class that extends ProductsWidget + const ProductsPlanning = (ProductsWidget) => class extends ProductsWidget { +// Override the setup method to perform any additional setup logic. + setup() { + super.setup(...arguments); + } + /** + * Override the productsToDisplay getter method to filter the list of products + * based on the current time and the meals planning data. + */ + get productsToDisplay() { + let allProducts = []; + if (this.searchWord !== '') { + allProducts = this.env.pos.db.search_product_in_category( + this.selectedCategoryId, + this.searchWord + ); + } else { + allProducts = this.env.pos.db.get_product_by_category(this.selectedCategoryId); + } + const date = new Date(); + let hoursMin = date.getHours() + '.' + date.getMinutes(); + let time = Number(hoursMin) + let currentProducts = []; + this.env.pos.meals_planning.forEach(function(object) { + if (object.time_from < time && time < object.time_to) { + currentProducts = object.product_ids.flat(1) + } + }) + // Filter the list of products to only include products that are part of the + // Available menu products for the current time. + if (currentProducts.length) { + allProducts = allProducts.filter(product => currentProducts.flat(1).includes(product.id)) + } + // Sort the list of products by display name and return the sorted list. + return allProducts.sort(function(firstProduct, secondProduct) { + return firstProduct.display_name.localeCompare(secondProduct.display_name) + }); + } + } + Registries.Component.extend(ProductsWidget, ProductsPlanning); + return ProductsPlanning; +}); diff --git a/all_in_one_pos_kit/views/dashboard_views.xml b/all_in_one_pos_kit/views/dashboard_views.xml new file mode 100644 index 000000000..fb9c60a93 --- /dev/null +++ b/all_in_one_pos_kit/views/dashboard_views.xml @@ -0,0 +1,14 @@ + + + + + + Dashboard + pos_dashboard + + + + diff --git a/all_in_one_pos_kit/views/meals_planning_views.xml b/all_in_one_pos_kit/views/meals_planning_views.xml new file mode 100644 index 000000000..0ed779990 --- /dev/null +++ b/all_in_one_pos_kit/views/meals_planning_views.xml @@ -0,0 +1,97 @@ + + + + + meals.planning.view.search + meals.planning + + + + + + + + + + + + + + + + meals.planning.view.tree + meals.planning + + + + + + + + + + + + meals.planning.view.form + meals.planning + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+ + + Product Planning + meals.planning + tree,form + + +
diff --git a/all_in_one_pos_kit/views/pos_config_views.xml b/all_in_one_pos_kit/views/pos_config_views.xml new file mode 100644 index 000000000..2df2ff814 --- /dev/null +++ b/all_in_one_pos_kit/views/pos_config_views.xml @@ -0,0 +1,221 @@ + + + + + pos.config.view.form.inherit.all.in.one.pos.kit + pos.config + + + +
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ +
+ Mention fixed percentage of tip here +
+
+
+ +

POS Greetings

+
+
+
+ +
+
+
+
+
+
+ + + + +
+
diff --git a/all_in_one_pos_kit/views/pos_greetings_views.xml b/all_in_one_pos_kit/views/pos_greetings_views.xml new file mode 100644 index 000000000..97312ceae --- /dev/null +++ b/all_in_one_pos_kit/views/pos_greetings_views.xml @@ -0,0 +1,55 @@ + + + + pos.greetings.view.tree + pos.greetings + + + + + + + + + + + + pos.greetings.view.form + pos.greetings + +
+ + + + + + + + + + + + + + + + + +
+
+
+ + + POS Greetings + pos.greetings + tree,form + +

POS Greetings

+
+
+ + +
\ No newline at end of file diff --git a/all_in_one_pos_kit/views/pos_order_views.xml b/all_in_one_pos_kit/views/pos_order_views.xml new file mode 100644 index 000000000..58d8eba33 --- /dev/null +++ b/all_in_one_pos_kit/views/pos_order_views.xml @@ -0,0 +1,15 @@ + + + + + pos.order.view.form.inherit.all.in.one.pos.kit + pos.order + + + + + + + + + diff --git a/all_in_one_pos_kit/views/pos_report_views.xml b/all_in_one_pos_kit/views/pos_report_views.xml new file mode 100644 index 000000000..8a4cd308d --- /dev/null +++ b/all_in_one_pos_kit/views/pos_report_views.xml @@ -0,0 +1,11 @@ + + + + + Pos Report + pos_r + + + diff --git a/all_in_one_pos_kit/views/product_product_views.xml b/all_in_one_pos_kit/views/product_product_views.xml new file mode 100644 index 000000000..adf04b59c --- /dev/null +++ b/all_in_one_pos_kit/views/product_product_views.xml @@ -0,0 +1,24 @@ + + + + + product.product.view.form.inherit.all.in.one.pos.kit + product.product + + + + + + + + + + + + + + + + + + diff --git a/all_in_one_pos_kit/views/product_template_views.xml b/all_in_one_pos_kit/views/product_template_views.xml new file mode 100644 index 000000000..9f507b51f --- /dev/null +++ b/all_in_one_pos_kit/views/product_template_views.xml @@ -0,0 +1,29 @@ + + + + + product.template.view.form.inherit.all.in.one.pos.kit + product.template + + + + + + + + + + + + + + + + + + + + + diff --git a/all_in_one_pos_kit/views/res_config_settings_views.xml b/all_in_one_pos_kit/views/res_config_settings_views.xml new file mode 100644 index 000000000..337d929d1 --- /dev/null +++ b/all_in_one_pos_kit/views/res_config_settings_views.xml @@ -0,0 +1,66 @@ + + + + + res.config.settings.view.form.inherit.all.in.one.pos.kit + res.config.settings + + + + +
+
+

POS Greetings

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