@ -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 <https://cybrosys.com/>`__ |
|||
|
|||
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 <https://cybrosys.com/>`__ |
|||
|
|||
Further information |
|||
=================== |
|||
HTML Description: `<static/description/index.html>`__ |
@ -0,0 +1,24 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################### |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# 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 <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################### |
|||
from . import controllers |
|||
from . import models |
|||
from . import report |
@ -0,0 +1,119 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################### |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# 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 <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################### |
|||
{ |
|||
'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, |
|||
} |
@ -0,0 +1,23 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################### |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# 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 <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################### |
|||
from . import all_in_one_pos_kit |
|||
from . import xlsx_report |
@ -0,0 +1,50 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################### |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# 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 <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################### |
|||
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, |
|||
}) |
@ -0,0 +1,64 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################# |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
|||
# 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 <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################# |
|||
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))) |
@ -0,0 +1,7 @@ |
|||
## Module <all_in_one_pos_kit> |
|||
|
|||
#### 21.03.2025 |
|||
#### Version 15.0.1.0.0 |
|||
#### ADD |
|||
|
|||
- Initial commit for All in One POS Kit |
@ -0,0 +1,36 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################### |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# 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 <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################### |
|||
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 |
@ -0,0 +1,85 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################# |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# 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 <http://www.gnu.org/licenses/>. |
|||
# |
|||
# ############################################################################# |
|||
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)) |
@ -0,0 +1,70 @@ |
|||
# -*- coding: utf-8 -*- |
|||
################################################################################ |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# 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 <http://www.gnu.org/licenses/>. |
|||
# |
|||
################################################################################ |
|||
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' |
|||
}) |
@ -0,0 +1,113 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################### |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# 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 <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################### |
|||
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 |
@ -0,0 +1,48 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################### |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# 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 <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################### |
|||
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 |
@ -0,0 +1,134 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################### |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# 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 <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################### |
|||
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 |
@ -0,0 +1,54 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################### |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# 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 <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################### |
|||
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' |
|||
) |
@ -0,0 +1,439 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################### |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# 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 <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################### |
|||
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 |
@ -0,0 +1,41 @@ |
|||
# -*- coding: utf-8 -*- |
|||
################################################################################ |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# 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 <http://www.gnu.org/licenses/>. |
|||
# |
|||
################################################################################ |
|||
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 |
@ -0,0 +1,666 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################# |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# 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 <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################# |
|||
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() |
@ -0,0 +1,33 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################### |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# 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 <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################### |
|||
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 |
@ -0,0 +1,111 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################### |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# 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 <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################### |
|||
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.') |
@ -0,0 +1,74 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################### |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# 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 <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################### |
|||
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 |
@ -0,0 +1,63 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################### |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# 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 <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################### |
|||
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 |
@ -0,0 +1,47 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################### |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# 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 <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################### |
|||
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) |
@ -0,0 +1,58 @@ |
|||
# -*- coding: utf-8 -*- |
|||
################################################################################ |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# 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 <http://www.gnu.org/licenses/>. |
|||
# |
|||
################################################################################ |
|||
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") |
@ -0,0 +1,22 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################# |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# 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 <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################# |
|||
from . import pos_order_report |
@ -0,0 +1,45 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################# |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# 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 <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################# |
|||
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 |
@ -0,0 +1,411 @@ |
|||
<odoo> |
|||
<template id="pos_order_report"> |
|||
<t t-call="web.html_container"> |
|||
<t t-call="web.internal_layout"> |
|||
<t t-if="Filters.get('report_type')=='Report By Order'"> |
|||
<t t-call="all_in_one_pos_kit.report_order"/> |
|||
</t> |
|||
<t t-if="Filters.get('report_type')=='Report By Order Detail'"> |
|||
<t t-call="all_in_one_pos_kit.report_order_detail"/> |
|||
</t> |
|||
<t t-if="Filters.get('report_type')=='Report By Product'"> |
|||
<t t-call="all_in_one_pos_kit.report_product"/> |
|||
</t> |
|||
<t t-if="Filters.get('report_type')=='Report By Categories'"> |
|||
<t t-call="all_in_one_pos_kit.report_category"/> |
|||
</t> |
|||
<t t-if="Filters.get('report_type')=='Report By Salesman'"> |
|||
<t t-call="all_in_one_pos_kit.report_salesman"/> |
|||
</t> |
|||
<t t-if="Filters.get('report_type')=='Report By Payment'"> |
|||
<t t-call="all_in_one_pos_kit.report_payment"/> |
|||
</t> |
|||
</t> |
|||
</t> |
|||
</template> |
|||
<template id="report_order"> |
|||
<div class="page"> |
|||
<div class="oe_structure"/> |
|||
<span t-if="Filters.get('date_from')"> |
|||
<strong>From:</strong> |
|||
<t t-esc="Filters['date_from']"/> |
|||
</span> |
|||
<span t-if="Filters.get('date_to')"> |
|||
<strong>To:</strong> |
|||
<t t-esc="Filters['date_to']"/> |
|||
</span> |
|||
<div> |
|||
<div style="width:100%;"> |
|||
<div style="text-align:centre;" class="row"> |
|||
<div class="col-2"> |
|||
<strong>Report Type:</strong> |
|||
<t t-esc="Filters.get('report_type')"/> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<br/> |
|||
<table class="table table-sm table-reports"> |
|||
<thead> |
|||
<tr class="text-right"> |
|||
<th>PoS</th> |
|||
<th colspan="6">Order</th> |
|||
<th colspan="6">Date Order</th> |
|||
<th colspan="6">Customer</th> |
|||
<th colspan="6">Salesman</th> |
|||
<th colspan="6">Total Qty</th> |
|||
<th colspan="6">Amount Total</th> |
|||
<th colspan="6">Note</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody> |
|||
<t t-foreach="report_main_line_data" t-as="main"> |
|||
<tr style="font-weight: bold;"> |
|||
<td colspan="6"> |
|||
<span t-esc="main['shop']"/> |
|||
</td> |
|||
<td colspan="6"> |
|||
<span t-esc="main['session']"/> |
|||
</td> |
|||
<td colspan="6"> |
|||
<span t-esc="main['date_order']"/> |
|||
</td> |
|||
<td colspan="6"> |
|||
<span t-esc="main['name']"/> |
|||
</td> |
|||
<td colspan="6"> |
|||
<span t-esc="main['salesman']"/> |
|||
</td> |
|||
<td colspan="6"> |
|||
<span t-esc="main['sum']"/> |
|||
</td> |
|||
<td colspan="6"> |
|||
<span t-esc="main['amount_total']"/> |
|||
</td> |
|||
<td colspan="6"> |
|||
<span t-esc="main['note']"/> |
|||
</td> |
|||
</tr> |
|||
</t> |
|||
</tbody> |
|||
</table> |
|||
</div> |
|||
<br/> |
|||
</div> |
|||
</template> |
|||
|
|||
<template id="report_order_detail"> |
|||
<div class="page"> |
|||
<div class="oe_structure"/> |
|||
<span t-if="Filters.get('date_from')"> |
|||
<strong>From:</strong> |
|||
<t t-esc="Filters['date_from']"/> |
|||
</span> |
|||
<span t-if="Filters.get('date_to')"> |
|||
<strong>To:</strong> |
|||
<t t-esc="Filters['date_to']"/> |
|||
</span> |
|||
<div> |
|||
<div style="width:100%;"> |
|||
<div style="text-align:centre;" class="row"> |
|||
|
|||
<div class="col-2"> |
|||
<strong>Report Type:</strong> |
|||
<t t-esc="Filters.get('report_type')"/> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<br/> |
|||
<table class="table table-sm table-reports"> |
|||
<thead> |
|||
<tr class="text-right"> |
|||
<th>PoS</th> |
|||
<th colspan="6">Order</th> |
|||
<th colspan="6">Date Order</th> |
|||
<th colspan="6">Customer</th> |
|||
<th colspan="6">Salesman</th> |
|||
<th colspan="6">Product Code</th> |
|||
<th colspan="6">Product Name</th> |
|||
<th colspan="6">Price unit</th> |
|||
<th colspan="6">Qty</th> |
|||
<th colspan="6">Price Subtotal</th> |
|||
<th colspan="6">Price Subtotal Incl</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody> |
|||
<t t-foreach="report_main_line_data" t-as="main"> |
|||
<tr style="font-weight: bold;"> |
|||
<td colspan="6"> |
|||
<span t-esc="main['shop']"/> |
|||
</td> |
|||
<td colspan="6"> |
|||
<span t-esc="main['session']"/> |
|||
</td> |
|||
<td colspan="6"> |
|||
<span t-esc="main['date_order']"/> |
|||
</td> |
|||
<td colspan="6"> |
|||
<span t-esc="main['name']"/> |
|||
</td> |
|||
<td colspan="6"> |
|||
<span t-esc="main['salesman']"/> |
|||
</td> |
|||
<td colspan="6"> |
|||
<span t-esc="main['default_code']"/> |
|||
</td> |
|||
<td colspan="6"> |
|||
<span t-esc="main['full_product_name']"/> |
|||
</td> |
|||
<td colspan="6"> |
|||
<span t-esc="main['price_unit']"/> |
|||
</td> |
|||
<td colspan="6"> |
|||
<span t-esc="main['sum']"/> |
|||
</td> |
|||
<td colspan="6"> |
|||
<span t-esc="main['price_subtotal']"/> |
|||
</td> |
|||
<td colspan="6"> |
|||
<span t-esc="main['price_subtotal_incl']"/> |
|||
</td> |
|||
</tr> |
|||
</t> |
|||
</tbody> |
|||
</table> |
|||
</div> |
|||
<br/> |
|||
</div> |
|||
</template> |
|||
|
|||
<template id="report_product"> |
|||
<div class="page"> |
|||
<div class="oe_structure"/> |
|||
<span t-if="Filters.get('date_from')"> |
|||
<strong>From:</strong> |
|||
<t t-esc="Filters['date_from']"/> |
|||
</span> |
|||
<span t-if="Filters.get('date_to')"> |
|||
<strong>To:</strong> |
|||
<t t-esc="Filters['date_to']"/> |
|||
</span> |
|||
<div> |
|||
<div style="width:100%;"> |
|||
<div style="text-align:centre;" class="row"> |
|||
<div class="col-2"> |
|||
<strong>Report Type:</strong> |
|||
<t t-esc="Filters.get('report_type')"/> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<br/> |
|||
<table class="table table-sm table-reports"> |
|||
<thead> |
|||
<tr class="text-right"> |
|||
<th>Category</th> |
|||
<th colspan="6">Product Code</th> |
|||
<th colspan="6">Product Name</th> |
|||
<th colspan="6">Qty</th> |
|||
<th colspan="6">Amount Total</th> |
|||
<th colspan="6">Amount Total Incl</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody> |
|||
<t t-foreach="report_main_line_data" t-as="main"> |
|||
<tr style="font-weight: bold;"> |
|||
<td colspan="6"> |
|||
<span t-esc="main['name']"/> |
|||
</td> |
|||
<td colspan="6"> |
|||
<span t-esc="main['default_code']"/> |
|||
</td> |
|||
<td colspan="6"> |
|||
<span t-esc="main['full_product_name']"/> |
|||
</td> |
|||
<td colspan="6"> |
|||
<span t-esc="main['qty']"/> |
|||
</td> |
|||
<td colspan="6"> |
|||
<span t-esc="main['amount_total']"/> |
|||
</td> |
|||
<td colspan="6"> |
|||
<span t-esc="main['amount_total']"/> |
|||
</td> |
|||
</tr> |
|||
</t> |
|||
</tbody> |
|||
</table> |
|||
</div> |
|||
<br/> |
|||
</div> |
|||
</template> |
|||
|
|||
<template id="report_category"> |
|||
<div class="page"> |
|||
<div class="oe_structure"/> |
|||
<span t-if="Filters.get('date_from')"> |
|||
<strong>From:</strong> |
|||
<t t-esc="Filters['date_from']"/> |
|||
</span> |
|||
<span t-if="Filters.get('date_to')"> |
|||
<strong>To:</strong> |
|||
<t t-esc="Filters['date_to']"/> |
|||
</span> |
|||
<div> |
|||
<div style="width:100%;"> |
|||
<div style="text-align:centre;" class="row"> |
|||
|
|||
<div class="col-2"> |
|||
<strong>Report Type:</strong> |
|||
<t t-esc="Filters.get('report_type')"/> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<br/> |
|||
<table class="table table-sm table-reports"> |
|||
<thead> |
|||
<tr> |
|||
<th colspan="6">Category</th> |
|||
<th colspan="6">Qty</th> |
|||
<th colspan="6">Amount Total</th> |
|||
<th colspan="6">Amount Total Incl</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody> |
|||
<t t-foreach="report_main_line_data" t-as="pos_category"> |
|||
<tr style="font-weight: bold;"> |
|||
<td colspan="6"> |
|||
<span t-esc="pos_category['name']"/> |
|||
</td> |
|||
<td colspan="6"> |
|||
<span t-esc="pos_category['qty']"/> |
|||
</td> |
|||
<td colspan="6"> |
|||
<span t-esc="pos_category['amount_total']"/> |
|||
</td> |
|||
<td colspan="6"> |
|||
<span t-esc="pos_category['total_incl']"/> |
|||
</td> |
|||
</tr> |
|||
</t> |
|||
</tbody> |
|||
</table> |
|||
</div> |
|||
<br/> |
|||
</div> |
|||
</template> |
|||
|
|||
<template id="report_salesman"> |
|||
<div class="page"> |
|||
<div class="oe_structure"/> |
|||
<span t-if="Filters.get('date_from')"> |
|||
<strong>From:</strong> |
|||
<t t-esc="Filters['date_from']"/> |
|||
</span> |
|||
<span t-if="Filters.get('date_to')"> |
|||
<strong>To:</strong> |
|||
<t t-esc="Filters['date_to']"/> |
|||
</span> |
|||
<div> |
|||
<div style="width:100%;"> |
|||
<div style="text-align:centre;" class="row"> |
|||
<div class="col-2"> |
|||
<strong>Report Type:</strong> |
|||
<t t-esc="Filters.get('report_type')"/> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<br/> |
|||
<table class="table table-sm table-reports"> |
|||
<thead> |
|||
<tr> |
|||
<th>Salesman</th> |
|||
<th colspan="6">Total Order</th> |
|||
<th colspan="6">Total Qty</th> |
|||
<th colspan="6">Total Amount</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody> |
|||
<t t-foreach="report_main_line_data" t-as="main"> |
|||
<tr style="font-weight: bold;"> |
|||
<td> |
|||
<span t-esc="main['name']"/> |
|||
</td> |
|||
<td colspan="6"> |
|||
<span t-esc="main['order']"/> |
|||
</td> |
|||
<td colspan="6"> |
|||
<span t-esc="main['qty']"/> |
|||
</td> |
|||
<td colspan="6"> |
|||
<span t-esc="main['amount']"/> |
|||
</td> |
|||
</tr> |
|||
</t> |
|||
</tbody> |
|||
</table> |
|||
</div> |
|||
<br/> |
|||
</div> |
|||
</template> |
|||
|
|||
<template id="report_payment"> |
|||
<div class="page"> |
|||
<div class="oe_structure"/> |
|||
<span t-if="Filters.get('date_from')"> |
|||
<strong>From:</strong> |
|||
<t t-esc="Filters['date_from']"/> |
|||
</span> |
|||
<span t-if="Filters.get('date_to')"> |
|||
<strong>To:</strong> |
|||
<t t-esc="Filters['date_to']"/> |
|||
</span> |
|||
<div> |
|||
<div style="width:100%;"> |
|||
<div style="text-align:centre;" class="row"> |
|||
<div class="col-2"> |
|||
<strong>Report Type:</strong> |
|||
<t t-esc="Filters.get('report_type')"/> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<br/> |
|||
<table class="table table-sm table-reports"> |
|||
<thead> |
|||
<tr> |
|||
<th>Point of Sale</th> |
|||
<th colspan="6">PoS Session</th> |
|||
<th colspan="6">Payment</th> |
|||
<th colspan="6">Total Amount</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody> |
|||
<t t-foreach="report_main_line_data" t-as="main"> |
|||
<tr style="font-weight: bold;"> |
|||
<td> |
|||
<span t-esc="main['config']"/> |
|||
</td> |
|||
<td colspan="6"> |
|||
<span t-esc="main['session']"/> |
|||
</td> |
|||
<td colspan="6"> |
|||
<span t-esc="main['name']"/> |
|||
</td> |
|||
<td colspan="6"> |
|||
<span t-esc="main['sum']"/> |
|||
</td> |
|||
</tr> |
|||
</t> |
|||
</tbody> |
|||
</table> |
|||
</div> |
|||
<br/> |
|||
</div> |
|||
</template> |
|||
|
|||
<record id="action_report_pos_all_in_one" model="ir.actions.report"> |
|||
<field name="name">POS All In One Report</field> |
|||
<field name="model">pos.report</field> |
|||
<field name="report_type">qweb-pdf</field> |
|||
<field name="report_name">all_in_one_pos_kit.pos_order_report</field> |
|||
<field name="report_file">all_in_one_pos_kit.pos_order_report</field> |
|||
</record> |
|||
</odoo> |
@ -0,0 +1,43 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<odoo> |
|||
<record id="restrict_user" model="ir.rule"> |
|||
<!-- Restrict access to Configurations for Users --> |
|||
<field name="name">Config User</field> |
|||
<field name="model_id" ref="point_of_sale.model_pos_config"/> |
|||
<field name="domain_force">[('id','in',user.pos_config_ids.ids)]</field> |
|||
<field name="groups" eval="[(4,ref('point_of_sale.group_pos_user'))]"/> |
|||
</record> |
|||
<record id="restrict_manager" model="ir.rule"> |
|||
<!-- Restrict access to Configurations for Managers --> |
|||
<field name="name">Config Manager</field> |
|||
<field name="model_id" ref="point_of_sale.model_pos_config"/> |
|||
<field name="domain_force">[]</field> |
|||
<field name="groups" |
|||
eval="[(4,ref('point_of_sale.group_pos_manager'))]"/> |
|||
</record> |
|||
<record id="order_user" model="ir.rule"> |
|||
<!-- Restrict access to Orders for Users --> |
|||
<field name="name">Orders User</field> |
|||
<field name="model_id" ref="point_of_sale.model_pos_order"/> |
|||
<field name="domain_force"> |
|||
[('config_id','in',user.pos_config_ids.ids)] |
|||
</field> |
|||
<field name="groups" eval="[(4,ref('point_of_sale.group_pos_user'))]"/> |
|||
</record> |
|||
<record id="order_manager" model="ir.rule"> |
|||
<!-- Orders Manager Rule --> |
|||
<field name="name">Orders Manager</field> |
|||
<field name="model_id" ref="point_of_sale.model_pos_order"/> |
|||
<field name="domain_force">[]</field> |
|||
<field name="groups" |
|||
eval="[(4,ref('point_of_sale.group_pos_manager'))]"/> |
|||
</record> |
|||
<record id="meals_panning_rule_company" model="ir.rule"> |
|||
<field name="name">multi company access for meals planning</field> |
|||
<field name="model_id" ref="model_meals_planning"/> |
|||
<field name="domain_force"> |
|||
['|',('company_id','=',False),('company_id', |
|||
'in', company_ids)] |
|||
</field> |
|||
</record> |
|||
</odoo> |
|
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 310 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 576 B |
After Width: | Height: | Size: 733 B |
After Width: | Height: | Size: 911 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 673 B |
After Width: | Height: | Size: 878 B |
After Width: | Height: | Size: 653 B |
After Width: | Height: | Size: 905 B |
After Width: | Height: | Size: 839 B |
After Width: | Height: | Size: 427 B |
After Width: | Height: | Size: 627 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 988 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 42 KiB |
After Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 70 KiB |
After Width: | Height: | Size: 42 KiB |
After Width: | Height: | Size: 41 KiB |
After Width: | Height: | Size: 66 KiB |
After Width: | Height: | Size: 106 KiB |
After Width: | Height: | Size: 134 KiB |
After Width: | Height: | Size: 134 KiB |
After Width: | Height: | Size: 141 KiB |
After Width: | Height: | Size: 141 KiB |
After Width: | Height: | Size: 139 KiB |
After Width: | Height: | Size: 132 KiB |
After Width: | Height: | Size: 168 KiB |
After Width: | Height: | Size: 175 KiB |
After Width: | Height: | Size: 141 KiB |
After Width: | Height: | Size: 106 KiB |
After Width: | Height: | Size: 96 KiB |
After Width: | Height: | Size: 114 KiB |
After Width: | Height: | Size: 95 KiB |
After Width: | Height: | Size: 134 KiB |
After Width: | Height: | Size: 134 KiB |
After Width: | Height: | Size: 134 KiB |
After Width: | Height: | Size: 111 KiB |
After Width: | Height: | Size: 108 KiB |
After Width: | Height: | Size: 112 KiB |
After Width: | Height: | Size: 513 KiB |
After Width: | Height: | Size: 55 KiB |
After Width: | Height: | Size: 85 KiB |
After Width: | Height: | Size: 94 KiB |
After Width: | Height: | Size: 162 KiB |
After Width: | Height: | Size: 98 KiB |
After Width: | Height: | Size: 144 KiB |
After Width: | Height: | Size: 380 KiB |
After Width: | Height: | Size: 518 KiB |
After Width: | Height: | Size: 103 KiB |
After Width: | Height: | Size: 154 KiB |
After Width: | Height: | Size: 132 KiB |
After Width: | Height: | Size: 105 KiB |
After Width: | Height: | Size: 103 KiB |
After Width: | Height: | Size: 161 KiB |
After Width: | Height: | Size: 149 KiB |
After Width: | Height: | Size: 116 KiB |
After Width: | Height: | Size: 521 KiB |
After Width: | Height: | Size: 110 KiB |
After Width: | Height: | Size: 114 KiB |
After Width: | Height: | Size: 147 KiB |
After Width: | Height: | Size: 77 KiB |
After Width: | Height: | Size: 73 KiB |
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 45 KiB |
After Width: | Height: | Size: 135 KiB |