Начальное наполнение
This commit is contained in:
parent
2b74d9c7b9
commit
16590e0356
109
README.md
109
README.md
@ -1,2 +1,109 @@
|
||||
# point_of_sale
|
||||
Odoo Point of Sale
|
||||
-----------------------------
|
||||
|
||||
Odoo's <a href="https://www.odoo.com/app/point-of-sale-shop">Point of Sale</a>
|
||||
introduces a super clean interface with no installation required that runs
|
||||
online and offline on modern hardwares.
|
||||
|
||||
It's full integration with the company inventory and accounting, gives you real
|
||||
time statistics and consolidations amongst all shops without the hassle of
|
||||
integrating several applications.
|
||||
|
||||
Work with the hardware you already have
|
||||
---------------------------------------
|
||||
|
||||
### In your web browser
|
||||
|
||||
Odoo's POS is a web application that can run on any device that can display
|
||||
websites with little to no setup required.
|
||||
|
||||
### Touchscreen or Keyboard?
|
||||
|
||||
The Point of Sale works perfectly on any kind of touch enabled device, whether
|
||||
it's multi-touch tablets like an iPad or keyboardless resistive touchscreen
|
||||
terminals.
|
||||
|
||||
### Scales and Printers
|
||||
|
||||
Barcode scanners and printers are supported out of the box with no setup
|
||||
required. Scales, cashboxes, and other peripherals can be used with the proxy
|
||||
API.
|
||||
|
||||
Online and Offline
|
||||
------------------
|
||||
|
||||
### Odoo's POS stays reliable even if your connection isn't
|
||||
|
||||
Deploy new stores with just an internet connection: **no installation, no
|
||||
specific hardware required**. It works with any **iPad, Tablet PC, laptop** or
|
||||
industrial POS machine.
|
||||
|
||||
While an internet connection is required to start the Point of Sale, it will
|
||||
stay operational even after a complete disconnection.
|
||||
|
||||
|
||||
A super clean user interface
|
||||
----------------------------
|
||||
|
||||
### Simple and beautiful
|
||||
|
||||
Say goodbye to ugly, outdated POS software and enjoy the Odoo web interface
|
||||
designed for modern retailer.
|
||||
|
||||
### Designed for Productivity
|
||||
|
||||
Whether it's for a restaurant or a shop, you can activate the multiple orders
|
||||
in parallel to not make your customers wait.
|
||||
|
||||
### Blazing fast search
|
||||
|
||||
Scan products, browse through hierarchical categories, or get quick information
|
||||
about products with the blasting fast filter across all your products.
|
||||
|
||||
Integrated Inventory Management
|
||||
-------------------------------
|
||||
|
||||
Consolidate all your Sales Teams in real time: stores, ecommerce, sales
|
||||
teams. Get real time control of the inventory and accurate forecasts to manage
|
||||
procurements.
|
||||
|
||||
A full warehouse management system at your fingertips: get information about
|
||||
products availabilities, trigger procurement requests, etc.
|
||||
|
||||
Deliver in-store customer services
|
||||
----------------------------------
|
||||
|
||||
Give your shopper a strong experience by integrating in-store customer
|
||||
services. Handle reparations, track warantees, follow customer claims, plan
|
||||
delivery orders, etc.
|
||||
|
||||
Invoicing & Accounting Integration
|
||||
----------------------------------
|
||||
|
||||
Produce customer invoices in just a few clicks. Control sales and cash in real
|
||||
time and use Odoo's powerful reporting to make smarter decisions to improve
|
||||
your store's efficiency.
|
||||
|
||||
No more hassle of having to integrate softwares: get all your sales and
|
||||
inventory operations automatically posted in your G/L.
|
||||
|
||||
Unified Data Amongst All Shops
|
||||
------------------------------
|
||||
|
||||
Get new products, pricing strategies and promotions applied automatically to
|
||||
selected stores. Work on a unified customer base. No complex interface is
|
||||
required to pilot a global strategy amongst all your stores.
|
||||
|
||||
With Odoo as a backend, you have a system proven to be perfectly suitable for
|
||||
small stores or large multinationals.
|
||||
|
||||
Know your customers - in store and out
|
||||
--------------------------------------
|
||||
|
||||
Successful brands integrates all their customer relationship accross all their
|
||||
channels to develop accurate customer profile and communicate with shoppers as
|
||||
they make buying decisions, in store or online.
|
||||
|
||||
With Odoo, you get a 360° customer view, including cross-channel sales,
|
||||
interaction history, profiles, and more.
|
||||
|
||||
|
12
__init__.py
Normal file
12
__init__.py
Normal file
@ -0,0 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
from . import controllers
|
||||
from . import report
|
||||
from . import wizard
|
||||
|
||||
|
||||
def uninstall_hook(env):
|
||||
#The search domain is based on how the sequence is defined in the _get_sequence_values method in /addons/point_of_sale/models/stock_warehouse.py
|
||||
env['ir.sequence'].search([('name', 'ilike', '%Picking POS%'), ('prefix', 'ilike', '%/POS/%')]).unlink()
|
225
__manifest__.py
Normal file
225
__manifest__.py
Normal file
@ -0,0 +1,225 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
{
|
||||
'name': 'Point of Sale',
|
||||
'version': '1.0.1',
|
||||
'category': 'Sales/Point of Sale',
|
||||
'sequence': 40,
|
||||
'summary': 'User-friendly PoS interface for shops and restaurants',
|
||||
'depends': ['stock_account', 'barcodes', 'web_editor', 'digest'],
|
||||
'uninstall_hook': 'uninstall_hook',
|
||||
'data': [
|
||||
'security/point_of_sale_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'data/default_barcode_patterns.xml',
|
||||
'data/digest_data.xml',
|
||||
'wizard/pos_details.xml',
|
||||
'wizard/pos_payment.xml',
|
||||
'wizard/pos_close_session_wizard.xml',
|
||||
'wizard/pos_daily_sales_reports.xml',
|
||||
'views/pos_assets_index.xml',
|
||||
'views/pos_assets_qunit.xml',
|
||||
'views/point_of_sale_report.xml',
|
||||
'views/point_of_sale_view.xml',
|
||||
'views/pos_order_view.xml',
|
||||
'views/pos_category_view.xml',
|
||||
'views/pos_combo_view.xml',
|
||||
'views/product_view.xml',
|
||||
'views/account_journal_view.xml',
|
||||
'views/pos_payment_method_views.xml',
|
||||
'views/pos_payment_views.xml',
|
||||
'views/pos_config_view.xml',
|
||||
'views/pos_bill_view.xml',
|
||||
'views/pos_session_view.xml',
|
||||
'views/point_of_sale_sequence.xml',
|
||||
'data/point_of_sale_data.xml',
|
||||
'views/pos_order_report_view.xml',
|
||||
'views/account_statement_view.xml',
|
||||
'views/digest_views.xml',
|
||||
'views/res_partner_view.xml',
|
||||
'views/report_userlabel.xml',
|
||||
'views/report_saledetails.xml',
|
||||
'views/point_of_sale_dashboard.xml',
|
||||
'views/report_invoice.xml',
|
||||
'views/pos_printer_view.xml',
|
||||
'views/pos_ticket_view.xml',
|
||||
'views/res_config_settings_views.xml',
|
||||
],
|
||||
'demo': [
|
||||
'data/point_of_sale_demo.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'application': True,
|
||||
'website': 'https://www.odoo.com/app/point-of-sale-shop',
|
||||
'assets': {
|
||||
|
||||
# In general, you DON'T NEED to declare new assets here, just put the
|
||||
# files in the proper directory. In rare cases, the order of scss files
|
||||
# matter and in that case you'll need to add it to the bundle in the
|
||||
# correct spot.
|
||||
#
|
||||
# Files in /static/src/backend will be loaded in the backend
|
||||
# Files in /static/src/app will be loaded in the PoS UI and unit tests
|
||||
# Files in /static/tests/tours will be loaded in the backend in test mode
|
||||
# Files in /static/tests/unit will be loaded in the qunit tests (/pos/ui/tests)
|
||||
|
||||
# web assets
|
||||
'web.assets_backend': [
|
||||
'point_of_sale/static/src/scss/pos_dashboard.scss',
|
||||
'point_of_sale/static/src/backend/tours/point_of_sale.js',
|
||||
'point_of_sale/static/src/backend/debug_manager.js',
|
||||
],
|
||||
'web.assets_tests': [
|
||||
'point_of_sale/static/tests/tours/**/*',
|
||||
],
|
||||
|
||||
# PoS assets
|
||||
|
||||
# Main PoS assets, they are loaded in the PoS UI and in the PoS unit tests
|
||||
'point_of_sale._assets_pos': [
|
||||
# 'preparation_display' bootstrap customization layer
|
||||
'web/static/src/scss/functions.scss',
|
||||
# 'point_of_sale/static/src/scss/primary_variables.scss', TO DO - CREATE
|
||||
|
||||
# 'webclient' bootstrap customization layer
|
||||
('include', 'web._assets_helpers'),
|
||||
('include', 'web._assets_backend_helpers'),
|
||||
|
||||
'web/static/src/scss/pre_variables.scss',
|
||||
'web/static/lib/bootstrap/scss/_variables.scss',
|
||||
|
||||
# Import Bootstrap
|
||||
('include', 'web._assets_bootstrap_backend'),
|
||||
|
||||
# Icons
|
||||
'web/static/src/libs/fontawesome/css/font-awesome.css',
|
||||
'web/static/lib/odoo_ui_icons/*',
|
||||
'web/static/src/webclient/icons.scss',
|
||||
|
||||
# scss variables and utilities
|
||||
'point_of_sale/static/src/scss/pos_variables_extra.scss',
|
||||
'web/static/src/scss/bootstrap_overridden.scss',
|
||||
'web/static/src/scss/fontawesome_overridden.scss',
|
||||
'web/static/fonts/fonts.scss',
|
||||
# JS boot
|
||||
'web/static/src/module_loader.js',
|
||||
# libs (should be loaded before framework)
|
||||
'point_of_sale/static/lib/**/*',
|
||||
'web/static/lib/luxon/luxon.js',
|
||||
'web/static/lib/owl/owl.js',
|
||||
'web/static/lib/owl/odoo_module.js',
|
||||
'web_editor/static/lib/html2canvas.js',
|
||||
'web/static/lib/zxing-library/zxing-library.js',
|
||||
# JS framework
|
||||
('include', 'web._assets_core'),
|
||||
('remove', 'web/static/src/core/errors/error_handlers.js'), # error handling in PoS is different from the webclient
|
||||
# formatMonetary
|
||||
'web/static/src/views/fields/formatters.js',
|
||||
# barcode scanner
|
||||
'barcodes/static/src/barcode_service.js',
|
||||
'barcodes/static/src/js/barcode_parser.js',
|
||||
'barcodes_gs1_nomenclature/static/src/js/barcode_parser.js',
|
||||
'barcodes_gs1_nomenclature/static/src/js/barcode_service.js',
|
||||
'web/static/src/views/fields/parsers.js',
|
||||
'web/static/src/webclient/barcode/barcode_scanner.*',
|
||||
'web/static/src/webclient/barcode/ZXingBarcodeDetector.js',
|
||||
'web/static/src/webclient/barcode/crop_overlay.*',
|
||||
# bus service
|
||||
'bus/static/src/services/bus_service.js',
|
||||
'bus/static/src/bus_parameters_service.js',
|
||||
'bus/static/src/multi_tab_service.js',
|
||||
'bus/static/src/workers/*',
|
||||
# report download utils
|
||||
'web/static/src/webclient/actions/reports/utils.js',
|
||||
# PoS files
|
||||
'point_of_sale/static/src/**/*',
|
||||
('remove', 'point_of_sale/static/src/backend/**/*'),
|
||||
# main.js boots the pos app, it is only included in the prod bundle as tests mount the app themselves
|
||||
('remove', 'point_of_sale/static/src/app/main.js'),
|
||||
# tour system FIXME: can this be added only in test mode? Are there any onboarding tours in PoS?
|
||||
'web/static/lib/jquery/jquery.js',
|
||||
'web/static/src/legacy/js/libs/jquery.js',
|
||||
'web_tour/static/src/tour_pointer/**/*',
|
||||
'web_tour/static/src/tour_service/**/*',
|
||||
],
|
||||
# Bundle that starts the pos, loaded on /pos/ui
|
||||
'point_of_sale.assets_prod': [
|
||||
('include', 'point_of_sale._assets_pos'),
|
||||
'point_of_sale/static/src/app/main.js',
|
||||
],
|
||||
# Bundle for the unit tests at /pos/ui/tests
|
||||
'point_of_sale.assets_qunit_tests': [
|
||||
('include', 'point_of_sale._assets_pos'),
|
||||
# dependencies of web.tests_assets (in the web tests, these come from assets_backend)
|
||||
'web/static/tests/patch_translations.js',
|
||||
'web/static/lib/jquery/jquery.js',
|
||||
'web/static/src/legacy/js/**/*',
|
||||
('remove', 'web/static/src/legacy/js/libs/**/*'),
|
||||
('remove', 'web/static/src/legacy/js/public/**/*'),
|
||||
'web/static/src/search/**/*',
|
||||
'web/static/src/views/fields/field_tooltip.js',
|
||||
'web/static/src/views/fields/field.js',
|
||||
'web/static/src/views/onboarding_banner.js',
|
||||
'web/static/src/views/utils.js',
|
||||
'web/static/src/views/view_hook.js',
|
||||
'web/static/src/views/view_service.js',
|
||||
'web/static/src/views/view.js',
|
||||
'web/static/src/model/relational_model/utils.js',
|
||||
'web/static/src/webclient/actions/action_container.js',
|
||||
'web/static/src/webclient/actions/action_dialog.js',
|
||||
'web/static/src/webclient/actions/action_hook.js',
|
||||
'web/static/src/webclient/actions/action_service.js',
|
||||
'web/static/src/webclient/actions/reports/report_action.js',
|
||||
'web/static/src/webclient/actions/reports/report_hook.js',
|
||||
'web/static/src/webclient/menus/menu_service.js',
|
||||
'web/static/src/webclient/navbar/navbar.js',
|
||||
'web/static/src/webclient/webclient.js',
|
||||
'web/static/src/views/view_dialogs/form_view_dialog.js',
|
||||
'web/static/src/views/view_dialogs/select_create_dialog.js',
|
||||
# BEGIN copy of web.tests_assets. We don't 'include' it because other modules add their
|
||||
# own test helpers in this module that depend on files that they add in assets_backend
|
||||
'web/static/lib/qunit/qunit-2.9.1.css',
|
||||
'web/static/lib/qunit/qunit-2.9.1.js',
|
||||
'web/static/tests/legacy/helpers/**/*',
|
||||
('remove', 'web/static/tests/legacy/helpers/test_utils_tests.js'),
|
||||
|
||||
'web/static/lib/fullcalendar/core/main.css',
|
||||
'web/static/lib/fullcalendar/daygrid/main.css',
|
||||
'web/static/lib/fullcalendar/timegrid/main.css',
|
||||
'web/static/lib/fullcalendar/list/main.css',
|
||||
'web/static/lib/fullcalendar/core/main.js',
|
||||
'web/static/lib/fullcalendar/interaction/main.js',
|
||||
'web/static/lib/fullcalendar/daygrid/main.js',
|
||||
'web/static/lib/fullcalendar/timegrid/main.js',
|
||||
'web/static/lib/fullcalendar/list/main.js',
|
||||
'web/static/lib/fullcalendar/luxon/main.js',
|
||||
|
||||
'web/static/lib/zxing-library/zxing-library.js',
|
||||
|
||||
'web/static/lib/ace/ace.js',
|
||||
'web/static/lib/ace/javascript_highlight_rules.js',
|
||||
'web/static/lib/ace/mode-python.js',
|
||||
'web/static/lib/ace/mode-xml.js',
|
||||
'web/static/lib/ace/mode-js.js',
|
||||
'web/static/lib/ace/mode-qweb.js',
|
||||
'web/static/lib/stacktracejs/stacktrace.js',
|
||||
('include', "web.chartjs_lib"),
|
||||
|
||||
# 'web/static/tests/legacy/main_tests.js',
|
||||
'web/static/tests/helpers/**/*.js',
|
||||
'web/static/tests/views/helpers.js',
|
||||
'web/static/tests/search/helpers.js',
|
||||
'web/static/tests/views/calendar/helpers.js',
|
||||
'web/static/tests/webclient/**/helpers.js',
|
||||
'web/static/tests/qunit.js',
|
||||
'web/static/tests/main.js',
|
||||
'web/static/tests/setup.js',
|
||||
|
||||
## END copy of web.tests_assets
|
||||
# pos unit tests
|
||||
'point_of_sale/static/tests/unit/**/*',
|
||||
],
|
||||
},
|
||||
'license': 'LGPL-3',
|
||||
}
|
1
controllers/__init__.py
Normal file
1
controllers/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from . import main
|
259
controllers/main.py
Normal file
259
controllers/main.py
Normal file
@ -0,0 +1,259 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
|
||||
from odoo import http, _
|
||||
from odoo.http import request
|
||||
from odoo.osv.expression import AND
|
||||
from odoo.tools import format_amount
|
||||
from odoo.addons.account.controllers.portal import PortalAccount
|
||||
from datetime import timedelta, datetime
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PosController(PortalAccount):
|
||||
|
||||
@http.route(['/pos/web', '/pos/ui'], type='http', auth='user')
|
||||
def pos_web(self, config_id=False, **k):
|
||||
"""Open a pos session for the given config.
|
||||
|
||||
The right pos session will be selected to open, if non is open yet a new session will be created.
|
||||
|
||||
/pos/ui and /pos/web both can be used to access the POS. On the SaaS,
|
||||
/pos/ui uses HTTPS while /pos/web uses HTTP.
|
||||
|
||||
:param debug: The debug mode to load the session in.
|
||||
:type debug: str.
|
||||
:param config_id: id of the config that has to be loaded.
|
||||
:type config_id: str.
|
||||
:returns: object -- The rendered pos session.
|
||||
"""
|
||||
is_internal_user = request.env.user.has_group('base.group_user')
|
||||
if not is_internal_user:
|
||||
return request.not_found()
|
||||
domain = [
|
||||
('state', 'in', ['opening_control', 'opened']),
|
||||
('user_id', '=', request.session.uid),
|
||||
('rescue', '=', False)
|
||||
]
|
||||
if config_id:
|
||||
domain = AND([domain,[('config_id', '=', int(config_id))]])
|
||||
pos_config = request.env['pos.config'].sudo().browse(int(config_id))
|
||||
pos_session = request.env['pos.session'].sudo().search(domain, limit=1)
|
||||
|
||||
# The same POS session can be opened by a different user => search without restricting to
|
||||
# current user. Note: the config must be explicitly given to avoid fallbacking on a random
|
||||
# session.
|
||||
if not pos_session and config_id:
|
||||
domain = [
|
||||
('state', 'in', ['opening_control', 'opened']),
|
||||
('rescue', '=', False),
|
||||
('config_id', '=', int(config_id)),
|
||||
]
|
||||
pos_session = request.env['pos.session'].sudo().search(domain, limit=1)
|
||||
if not pos_session or config_id and not pos_config.active:
|
||||
return request.redirect('/web#action=point_of_sale.action_client_pos_menu')
|
||||
# The POS only works in one company, so we enforce the one of the session in the context
|
||||
company = pos_session.company_id
|
||||
session_info = request.env['ir.http'].session_info()
|
||||
session_info['user_context']['allowed_company_ids'] = company.ids
|
||||
session_info['user_companies'] = {'current_company': company.id, 'allowed_companies': {company.id: session_info['user_companies']['allowed_companies'][company.id]}}
|
||||
session_info['nomenclature_id'] = pos_session.company_id.nomenclature_id.id
|
||||
session_info['fallback_nomenclature_id'] = pos_session._get_pos_fallback_nomenclature_id()
|
||||
context = {
|
||||
'session_info': session_info,
|
||||
'login_number': pos_session.login(),
|
||||
'pos_session_id': pos_session.id,
|
||||
}
|
||||
response = request.render('point_of_sale.index', context)
|
||||
response.headers['Cache-Control'] = 'no-store'
|
||||
return response
|
||||
|
||||
@http.route('/pos/ui/tests', type='http', auth="user")
|
||||
def test_suite(self, mod=None, **kwargs):
|
||||
domain = [
|
||||
('state', '=', 'opened'),
|
||||
('user_id', '=', request.session.uid),
|
||||
('rescue', '=', False)
|
||||
]
|
||||
pos_session = request.env['pos.session'].sudo().search(domain, limit=1)
|
||||
session_info = request.env['ir.http'].session_info()
|
||||
session_info['user_context']['allowed_company_ids'] = pos_session.company_id.ids
|
||||
context = {
|
||||
'session_info': session_info,
|
||||
'pos_session_id': pos_session.id,
|
||||
}
|
||||
return request.render('point_of_sale.qunit_suite', qcontext=context)
|
||||
|
||||
@http.route('/pos/sale_details_report', type='http', auth='user')
|
||||
def print_sale_details(self, date_start=False, date_stop=False, **kw):
|
||||
r = request.env['report.point_of_sale.report_saledetails']
|
||||
pdf, _ = request.env['ir.actions.report'].with_context(date_start=date_start, date_stop=date_stop)._render_qweb_pdf('point_of_sale.sale_details_report', r)
|
||||
pdfhttpheaders = [('Content-Type', 'application/pdf'), ('Content-Length', len(pdf))]
|
||||
return request.make_response(pdf, headers=pdfhttpheaders)
|
||||
|
||||
@http.route(['/pos/ticket'], type='http', auth="public", website=True, sitemap=False)
|
||||
def invoice_request_screen(self, **kwargs):
|
||||
errors = {}
|
||||
form_values = {}
|
||||
if request.httprequest.method == 'POST':
|
||||
for field in ['pos_reference', 'date_order', 'ticket_code']:
|
||||
if not kwargs.get(field):
|
||||
errors[field] = " "
|
||||
else:
|
||||
form_values[field] = kwargs.get(field)
|
||||
|
||||
if errors:
|
||||
errors['generic'] = _("Please fill all the required fields.")
|
||||
elif len(form_values['pos_reference']) < 14:
|
||||
errors['pos_reference'] = _("The Ticket Number should be at least 14 characters long.")
|
||||
else:
|
||||
date_order = datetime(*[int(i) for i in form_values['date_order'].split('-')])
|
||||
order = request.env['pos.order'].sudo().search([
|
||||
('pos_reference', '=like', '%' + form_values['pos_reference'].replace('%', r'\%').replace('_', r'\_')),
|
||||
('date_order', '>=', date_order),
|
||||
('date_order', '<', date_order + timedelta(days=1)),
|
||||
('ticket_code', '=', form_values['ticket_code']),
|
||||
], limit=1)
|
||||
if order:
|
||||
return request.redirect('/pos/ticket/validate?access_token=%s' % (order.access_token))
|
||||
else:
|
||||
errors['generic'] = _("No sale order found.")
|
||||
|
||||
return request.render("point_of_sale.ticket_request_with_code", {
|
||||
'errors': errors,
|
||||
'banner_error': " ".join(errors.values()),
|
||||
'form_values': form_values,
|
||||
})
|
||||
|
||||
@http.route(['/pos/ticket/validate'], type='http', auth="public", website=True, sitemap=False)
|
||||
def show_ticket_validation_screen(self, access_token='', **kwargs):
|
||||
def _parse_additional_values(fields, prefix, kwargs):
|
||||
""" Parse the values in the kwargs by extracting the ones matching the given fields name.
|
||||
:return a dict with the parsed value and the field name as key, and another on with the prefix to
|
||||
re-render the form with previous values if needed.
|
||||
"""
|
||||
res, res_prefixed = {}, {}
|
||||
for field in fields:
|
||||
key = prefix + field.name
|
||||
if key in kwargs:
|
||||
val = kwargs.pop(key)
|
||||
res[field.name] = val
|
||||
res_prefixed[key] = val
|
||||
return res, res_prefixed
|
||||
|
||||
# If the route is called directly, return a 404
|
||||
if not access_token:
|
||||
return request.not_found()
|
||||
# Get the order using the access token. We can't use the id in the route because we may not have it yet when the QR code is generated.
|
||||
pos_order = request.env['pos.order'].sudo().search([('access_token', '=', access_token)])
|
||||
if not pos_order:
|
||||
return request.not_found()
|
||||
|
||||
# Set the proper context in case of unauthenticated user accessing
|
||||
# from the main company website
|
||||
pos_order = pos_order.with_company(pos_order.company_id)
|
||||
|
||||
# If the order was already invoiced, return the invoice directly by forcing the access token so that the non-connected user can see it.
|
||||
if pos_order.account_move and pos_order.account_move.is_sale_document():
|
||||
return request.redirect('/my/invoices/%s?access_token=%s' % (pos_order.account_move.id, pos_order.account_move._portal_ensure_token()))
|
||||
|
||||
# Get the optional extra fields that could be required for a localisation.
|
||||
pos_order_country = pos_order.company_id.account_fiscal_country_id
|
||||
additional_partner_fields = request.env['res.partner'].get_partner_localisation_fields_required_to_invoice(pos_order_country)
|
||||
additional_invoice_fields = request.env['account.move'].get_invoice_localisation_fields_required_to_invoice(pos_order_country)
|
||||
|
||||
user_is_connected = not request.env.user._is_public()
|
||||
|
||||
# Validate the form by ensuring required fields are filled and the VAT is correct.
|
||||
form_values = {'error': {}, 'error_message': {}, 'extra_field_values': {}}
|
||||
if kwargs and request.httprequest.method == 'POST':
|
||||
form_values.update(kwargs)
|
||||
# Extract the additional fields values from the kwargs now as they can't be there when validating the 'regular' partner form.
|
||||
partner_values, prefixed_partner_values = _parse_additional_values(additional_partner_fields, 'partner_', kwargs)
|
||||
form_values['extra_field_values'].update(prefixed_partner_values)
|
||||
# Do the same for invoice values, separately as they are only needed for the invoice creation.
|
||||
invoice_values, prefixed_invoice_values = _parse_additional_values(additional_invoice_fields, 'invoice_', kwargs)
|
||||
form_values['extra_field_values'].update(prefixed_invoice_values)
|
||||
# Check the basic form fields if the user is not connected as we will need these information to create the new user.
|
||||
if not user_is_connected:
|
||||
error, error_message = self.details_form_validate(kwargs, partner_creation=True)
|
||||
else:
|
||||
# Check that the billing information of the user are filled.
|
||||
error, error_message = {}, []
|
||||
partner = request.env.user.partner_id
|
||||
for field in self.MANDATORY_BILLING_FIELDS:
|
||||
if not partner[field]:
|
||||
error[field] = 'error'
|
||||
error_message.append(_('The %s must be filled in your details.', request.env['ir.model.fields']._get('res.partner', field).field_description))
|
||||
# Check that the "optional" additional fields are filled.
|
||||
error, error_message = self.extra_details_form_validate(partner_values, additional_partner_fields, error, error_message)
|
||||
error, error_message = self.extra_details_form_validate(invoice_values, additional_invoice_fields, error, error_message)
|
||||
if not error:
|
||||
return self._get_invoice(partner_values, invoice_values, pos_order, additional_invoice_fields, kwargs)
|
||||
else:
|
||||
form_values.update({'error': error, 'error_message': error_message})
|
||||
|
||||
elif user_is_connected:
|
||||
return self._get_invoice({}, {}, pos_order, additional_invoice_fields, kwargs)
|
||||
|
||||
# Most of the time, the country of the customer will be the same as the order. We can prefill it by default with the country of the company.
|
||||
if 'country_id' not in form_values:
|
||||
form_values['country_id'] = pos_order_country.id
|
||||
|
||||
partner = request.env['res.partner']
|
||||
# Prefill the customer extra values if there is any and an user is connected
|
||||
partner = (user_is_connected and request.env.user.partner_id) or pos_order.partner_id
|
||||
if partner:
|
||||
if additional_partner_fields:
|
||||
form_values['extra_field_values'] = {'partner_' + field.name: partner[field.name] for field in additional_partner_fields if field.name not in form_values['extra_field_values']}
|
||||
|
||||
# This is just to ensure that the user went and filled its information at least once.
|
||||
# Another more thorough check is done upon posting the form.
|
||||
if not partner.country_id or not partner.street:
|
||||
form_values['partner_address'] = False
|
||||
else:
|
||||
form_values['partner_address'] = partner._display_address()
|
||||
|
||||
return request.render("point_of_sale.ticket_validation_screen", {
|
||||
'partner': partner,
|
||||
'address_url': f'/my/account?redirect=/pos/ticket/validate?access_token={access_token}',
|
||||
'user_is_connected': user_is_connected,
|
||||
'format_amount': format_amount,
|
||||
'env': request.env,
|
||||
'countries': request.env['res.country'].sudo().search([]),
|
||||
'states': request.env['res.country.state'].sudo().search([]),
|
||||
'partner_can_edit_vat': True,
|
||||
'pos_order': pos_order,
|
||||
'invoice_required_fields': additional_invoice_fields,
|
||||
'partner_required_fields': additional_partner_fields,
|
||||
'access_token': access_token,
|
||||
**form_values,
|
||||
})
|
||||
|
||||
def _get_invoice(self, partner_values, invoice_values, pos_order, additional_invoice_fields, kwargs):
|
||||
# If the user is not connected, then we will simply create a new partner with the form values.
|
||||
# Matching with existing partner was tried, but we then can't update the values, and it would force the user to use the ones from the first invoicing.
|
||||
if request.env.user._is_public() and not pos_order.partner_id.id:
|
||||
partner_values.update({key: kwargs[key] for key in self.MANDATORY_BILLING_FIELDS})
|
||||
partner_values.update({key: kwargs[key] for key in self.OPTIONAL_BILLING_FIELDS if key in kwargs})
|
||||
for field in {'country_id', 'state_id'} & set(partner_values.keys()):
|
||||
try:
|
||||
partner_values[field] = int(partner_values[field])
|
||||
except Exception:
|
||||
partner_values[field] = False
|
||||
partner_values.update({'zip': partner_values.pop('zipcode', '')})
|
||||
partner = request.env['res.partner'].sudo().create(partner_values) # In this case, partner_values contains the whole partner info form.
|
||||
# If the user is connected, then we can update if needed its fields with the additional localized fields if any, then proceed.
|
||||
else:
|
||||
partner = pos_order.partner_id or (not request.env.user._is_public() and request.env.user.partner_id)
|
||||
partner.write(partner_values) # In this case, partner_values only contains the additional fields that can be updated.
|
||||
|
||||
pos_order.partner_id = partner
|
||||
# Get the required fields for the invoice and add them to the context as default values.
|
||||
with_context = {}
|
||||
for field in additional_invoice_fields:
|
||||
with_context.update({f'default_{field.name}': invoice_values.get(field.name)})
|
||||
# Allowing default values for moves is important for some localizations that would need specific fields to be set on the invoice, such as Mexico.
|
||||
pos_order.with_context(with_context).action_pos_order_invoice()
|
||||
return request.redirect('/my/invoices/%s?access_token=%s' % (pos_order.account_move.id, pos_order.account_move._portal_ensure_token()))
|
38
data/default_barcode_patterns.xml
Normal file
38
data/default_barcode_patterns.xml
Normal file
@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo noupdate="1">
|
||||
<record id="barcode_rule_cashier" model="barcode.rule">
|
||||
<field name="name">Cashier Barcodes</field>
|
||||
<field name="barcode_nomenclature_id" ref="barcodes.default_barcode_nomenclature"/>
|
||||
<field name="sequence">50</field>
|
||||
<field name="type">cashier</field>
|
||||
<field name="encoding">any</field>
|
||||
<field name="pattern">041</field>
|
||||
</record>
|
||||
|
||||
<record id="barcode_rule_client" model="barcode.rule">
|
||||
<field name="name">Customer Barcodes</field>
|
||||
<field name="barcode_nomenclature_id" ref="barcodes.default_barcode_nomenclature"/>
|
||||
<field name="sequence">40</field>
|
||||
<field name="type">client</field>
|
||||
<field name="encoding">any</field>
|
||||
<field name="pattern">042</field>
|
||||
</record>
|
||||
|
||||
<record id="barcode_rule_discount" model="barcode.rule">
|
||||
<field name="name">Discount Barcodes</field>
|
||||
<field name="barcode_nomenclature_id" ref="barcodes.default_barcode_nomenclature"/>
|
||||
<field name="sequence">20</field>
|
||||
<field name="type">discount</field>
|
||||
<field name="encoding">any</field>
|
||||
<field name="pattern">22{NN}</field>
|
||||
</record>
|
||||
|
||||
<record id="barcode_rule_price_two_dec" model="barcode.rule">
|
||||
<field name="name">Price Barcodes 2 Decimals</field>
|
||||
<field name="barcode_nomenclature_id" ref="barcodes.default_barcode_nomenclature"/>
|
||||
<field name="sequence">14</field>
|
||||
<field name="type">price</field>
|
||||
<field name="encoding">ean13</field>
|
||||
<field name="pattern">23.....{NNNDD}</field>
|
||||
</record>
|
||||
</odoo>
|
8
data/digest_data.xml
Normal file
8
data/digest_data.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="digest.digest_digest_default" model="digest.digest">
|
||||
<field name="kpi_pos_total">True</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
134
data/point_of_sale_data.xml
Normal file
134
data/point_of_sale_data.xml
Normal file
@ -0,0 +1,134 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<function model="stock.warehouse" name="_create_missing_pos_picking_types"/>
|
||||
</data>
|
||||
|
||||
<data noupdate="1">
|
||||
<!-- After closing the PoS, open the dashboard menu -->
|
||||
<record id="action_client_pos_menu" model="ir.actions.client">
|
||||
<field name="name">Reload POS Menu</field>
|
||||
<field name="tag">reload</field>
|
||||
<field name="params" eval="{'menu_id': ref('menu_point_root')}"/>
|
||||
</record>
|
||||
|
||||
<record id="action_client_product_menu" model="ir.actions.client">
|
||||
<field name="name">Load Product Menu</field>
|
||||
<field name="tag">reload</field>
|
||||
<field name="params" eval="{'menu_id': ref('point_of_sale.menu_pos_products')}"/>
|
||||
</record>
|
||||
|
||||
<record id="product_category_pos" model="product.category">
|
||||
<field name="parent_id" ref="product.product_category_1"/>
|
||||
<field name="name">PoS</field>
|
||||
</record>
|
||||
|
||||
<record id="product_product_tip" model="product.product">
|
||||
<field name="name">Tips</field>
|
||||
<field name="categ_id" ref="point_of_sale.product_category_pos"/>
|
||||
<field name="default_code">TIPS</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="available_in_pos">False</field>
|
||||
<field name="taxes_id" eval="[(5,)]"/>
|
||||
</record>
|
||||
|
||||
<record model="pos.config" id="pos_config_main" forcecreate="0">
|
||||
<field name="name">Shop</field>
|
||||
</record>
|
||||
|
||||
<record id="product_product_consumable" model="product.product">
|
||||
<field name="name">Discount</field>
|
||||
<field name="available_in_pos">True</field>
|
||||
<field name="standard_price">0.00</field>
|
||||
<field name="list_price">0.00</field>
|
||||
<field name="weight">0.00</field>
|
||||
<field name="type">consu</field>
|
||||
<field name="categ_id" ref="point_of_sale.product_category_pos"/>
|
||||
<field name="uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit"/>
|
||||
<field name="default_code">DISC</field>
|
||||
<field name="purchase_ok">False</field>
|
||||
</record>
|
||||
|
||||
<record id="uom.product_uom_categ_unit" model="uom.category">
|
||||
<field name="is_pos_groupable">True</field>
|
||||
</record>
|
||||
|
||||
<record model="pos.bill" id="0_01" forcecreate="0">
|
||||
<field name="name">0.01</field>
|
||||
<field name="value">0.01</field>
|
||||
</record>
|
||||
|
||||
<record model="pos.bill" id="0_02" forcecreate="0">
|
||||
<field name="name">0.02</field>
|
||||
<field name="value">0.02</field>
|
||||
</record>
|
||||
|
||||
<record model="pos.bill" id="0_05" forcecreate="0">
|
||||
<field name="name">0.05</field>
|
||||
<field name="value">0.05</field>
|
||||
</record>
|
||||
|
||||
<record model="pos.bill" id="0_10" forcecreate="0">
|
||||
<field name="name">0.10</field>
|
||||
<field name="value">0.10</field>
|
||||
</record>
|
||||
|
||||
<record model="pos.bill" id="0_20" forcecreate="0">
|
||||
<field name="name">0.20</field>
|
||||
<field name="value">0.20</field>
|
||||
</record>
|
||||
|
||||
<record model="pos.bill" id="0_25" forcecreate="0">
|
||||
<field name="name">0.25</field>
|
||||
<field name="value">0.25</field>
|
||||
</record>
|
||||
|
||||
<record model="pos.bill" id="0_50" forcecreate="0">
|
||||
<field name="name">0.50</field>
|
||||
<field name="value">0.50</field>
|
||||
</record>
|
||||
|
||||
<record model="pos.bill" id="1_00" forcecreate="0">
|
||||
<field name="name">1.00</field>
|
||||
<field name="value">1.00</field>
|
||||
</record>
|
||||
|
||||
<record model="pos.bill" id="2_00" forcecreate="0">
|
||||
<field name="name">2.00</field>
|
||||
<field name="value">2.00</field>
|
||||
</record>
|
||||
|
||||
<record model="pos.bill" id="5_00" forcecreate="0">
|
||||
<field name="name">5.00</field>
|
||||
<field name="value">5.00</field>
|
||||
</record>
|
||||
|
||||
<record model="pos.bill" id="10_00" forcecreate="0">
|
||||
<field name="name">10.00</field>
|
||||
<field name="value">10.00</field>
|
||||
</record>
|
||||
|
||||
<record model="pos.bill" id="20_00" forcecreate="0">
|
||||
<field name="name">20.00</field>
|
||||
<field name="value">20.00</field>
|
||||
</record>
|
||||
|
||||
<record model="pos.bill" id="50_00" forcecreate="0">
|
||||
<field name="name">50.00</field>
|
||||
<field name="value">50.00</field>
|
||||
</record>
|
||||
|
||||
<record model="pos.bill" id="100_00" forcecreate="0">
|
||||
<field name="name">100.00</field>
|
||||
<field name="value">100.00</field>
|
||||
</record>
|
||||
|
||||
<record model="pos.bill" id="200_00" forcecreate="0">
|
||||
<field name="name">200.00</field>
|
||||
<field name="value">200.00</field>
|
||||
</record>
|
||||
|
||||
<function model="pos.config" name="post_install_pos_localisation" />
|
||||
</data>
|
||||
</odoo>
|
684
data/point_of_sale_demo.xml
Normal file
684
data/point_of_sale_demo.xml
Normal file
@ -0,0 +1,684 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="base.user_demo" model="res.users">
|
||||
<field name="groups_id" eval="[(3, ref('point_of_sale.group_pos_manager'))]"/>
|
||||
</record>
|
||||
|
||||
<!-- Partners with Barcodes -->
|
||||
<record id='base.res_partner_1' model='res.partner'>
|
||||
<field name='barcode'>0420100000005</field>
|
||||
</record>
|
||||
<record id='base.res_partner_2' model='res.partner'>
|
||||
<field name='barcode'>0420200000004</field>
|
||||
</record>
|
||||
<record id='base.res_partner_3' model='res.partner'>
|
||||
<field name='barcode'>0420300000003</field>
|
||||
</record>
|
||||
<record id='base.res_partner_4' model='res.partner'>
|
||||
<field name='barcode'>0420700000009</field>
|
||||
</record>
|
||||
<record id='base.res_partner_10' model='res.partner'>
|
||||
<field name='barcode'>0421000000003</field>
|
||||
</record>
|
||||
<record id='base.res_partner_12' model='res.partner'>
|
||||
<field name='barcode'>0420800000008</field>
|
||||
</record>
|
||||
<record id='base.res_partner_18' model='res.partner'>
|
||||
<field name='barcode'>0421800000005</field>
|
||||
</record>
|
||||
|
||||
<record id="base.user_root" model="res.users">
|
||||
<field name="barcode">0410100000006</field>
|
||||
<field name="groups_id" eval="[(4,ref('group_pos_manager'))]" />
|
||||
</record>
|
||||
|
||||
<record id="base.user_demo" model="res.users">
|
||||
<field name="groups_id" eval="[(4, ref('group_pos_user'))]" />
|
||||
</record>
|
||||
|
||||
|
||||
<!-- Resource: pos.category -->
|
||||
<record id="pos_category_miscellaneous" model="pos.category">
|
||||
<field name="name">Misc</field>
|
||||
<field name="image_128" type="base64" file="point_of_sale/static/img/misc_category.png" />
|
||||
</record>
|
||||
<record id="pos_category_desks" model="pos.category">
|
||||
<field name="name">Desks</field>
|
||||
<field name="image_128" type="base64" file="point_of_sale/static/img/desk_category.png" />
|
||||
</record>
|
||||
<record id="pos_category_chairs" model="pos.category">
|
||||
<field name="name">Chairs</field>
|
||||
<field name="image_128" type="base64" file="point_of_sale/static/img/chair_category.png" />
|
||||
</record>
|
||||
|
||||
<record model="pos.config" id="pos_config_main">
|
||||
<field name="iface_start_categ_id" ref="pos_category_desks" />
|
||||
<field name="start_category">True</field>
|
||||
<field name="limit_categories">True</field>
|
||||
<field name="iface_available_categ_ids"
|
||||
eval="[(6, 0, [ref('pos_category_miscellaneous'), ref('pos_category_desks'), ref('pos_category_chairs')])]" />
|
||||
</record>
|
||||
<function model="pos.config" name="add_cash_payment_method" />
|
||||
|
||||
<!-- Preparation Printer -->
|
||||
<record id="preparation_printer" model="pos.printer">
|
||||
<field name="name">Preparation Printer</field>
|
||||
<field name="proxy_ip">localhost</field>
|
||||
<field name="product_categories_ids" eval="[(6, 0, [ref('point_of_sale.pos_category_miscellaneous')])]" />
|
||||
<field name="printer_type" eval="False" />
|
||||
</record>
|
||||
|
||||
<!-- Taxes -->
|
||||
|
||||
<record id="pos_taxes_0" model="account.tax">
|
||||
<field name="name">Default Tax for PoS</field>
|
||||
<field name="amount">0</field>
|
||||
<field name="amount_type">percent</field>
|
||||
</record>
|
||||
|
||||
<!-- Products -->
|
||||
|
||||
<!-- Old -->
|
||||
|
||||
<record id="stock.product_cable_management_box" model="product.product">
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('point_of_sale.pos_category_miscellaneous')])]" />
|
||||
</record>
|
||||
<record id="wall_shelf" model="product.product">
|
||||
<field name="available_in_pos">True</field>
|
||||
<field name="list_price">1.98</field>
|
||||
<field name="name">Wall Shelf Unit</field>
|
||||
<field name="default_code">FURN_0009</field>
|
||||
<field name="type">product</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="to_weight">True</field>
|
||||
<field name="barcode">2100002000003</field>
|
||||
<field name="taxes_id" eval="[(5,)]" />
|
||||
<field name="categ_id" ref="product.product_category_5" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]" />
|
||||
<field name="uom_id" ref="uom.product_uom_unit" />
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit" />
|
||||
<field name="image_1920" type="base64" file="point_of_sale/static/img/wall_shelf_unit.png" />
|
||||
</record>
|
||||
<record id="small_shelf" model="product.product">
|
||||
<field name="available_in_pos">True</field>
|
||||
<field name="list_price">2.83</field>
|
||||
<field name="name">Small Shelf</field>
|
||||
<field name="default_code">FURN_0008</field>
|
||||
<field name="type">product</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="taxes_id" eval="[(5,)]" />
|
||||
<field name="categ_id" ref="product.product_category_5" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]" />
|
||||
<field name="to_weight">True</field>
|
||||
<field name="uom_id" ref="uom.product_uom_unit" />
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit" />
|
||||
<field name="image_1920" type="base64" file="point_of_sale/static/img/small_shelf.png" />
|
||||
</record>
|
||||
|
||||
<record id="letter_tray" model="product.product">
|
||||
<field name="available_in_pos">True</field>
|
||||
<field name="list_price">4.80</field>
|
||||
<field name="name">Letter Tray</field>
|
||||
<field name="default_code">FURN_0004</field>
|
||||
<field name="type">product</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="to_weight">True</field>
|
||||
<field name="categ_id" ref="product.product_category_5" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]" />
|
||||
<field name="uom_id" ref="uom.product_uom_unit" />
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit" />
|
||||
<field name="image_1920" type="base64" file="point_of_sale/static/img/letter_tray.png" />
|
||||
</record>
|
||||
<record id="desk_organizer" model="product.product">
|
||||
<field name="available_in_pos">True</field>
|
||||
<field name="list_price">5.10</field>
|
||||
<field name="name">Desk Organizer</field>
|
||||
<field name="description_sale">The desk organiser is perfect for storing all kinds of small things and since the 5 boxes are loose, you can move and place them in the way that suits you and your things best.</field>
|
||||
<field name="default_code">FURN_0001</field>
|
||||
<field name="to_weight">True</field>
|
||||
<field name="barcode">2300001000008</field>
|
||||
<field name="type">product</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="categ_id" ref="product.product_category_5" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]" />
|
||||
<field name="uom_id" ref="uom.product_uom_unit" />
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit" />
|
||||
<field name="image_1920" type="base64" file="point_of_sale/static/img/desk_organizer.png" />
|
||||
<field name="taxes_id" eval="[(5,)]" /> <!-- no taxes -->
|
||||
</record>
|
||||
|
||||
<function model="ir.model.data" name="_update_xmlids">
|
||||
<value model="base" eval="[{
|
||||
'xml_id': 'point_of_sale.desk_organizer_product_template',
|
||||
'record': obj().env.ref('point_of_sale.desk_organizer').product_tmpl_id,
|
||||
'noupdate': True,
|
||||
}]" />
|
||||
</function>
|
||||
|
||||
<record id="size_attribute" model="product.attribute">
|
||||
<field name="name">Size</field>
|
||||
<field name="sequence">30</field>
|
||||
<field name="display_type">radio</field>
|
||||
<field name="create_variant">no_variant</field>
|
||||
</record>
|
||||
<record id="size_attribute_s" model="product.attribute.value">
|
||||
<field name="name">S</field>
|
||||
<field name="sequence">1</field>
|
||||
<field name="attribute_id" ref="size_attribute" />
|
||||
</record>
|
||||
<record id="size_attribute_m" model="product.attribute.value">
|
||||
<field name="name">M</field>
|
||||
<field name="sequence">2</field>
|
||||
<field name="attribute_id" ref="size_attribute" />
|
||||
</record>
|
||||
<record id="size_attribute_l" model="product.attribute.value">
|
||||
<field name="name">L</field>
|
||||
<field name="sequence">3</field>
|
||||
<field name="attribute_id" ref="size_attribute" />
|
||||
</record>
|
||||
<record id="desk_organizer_size" model="product.template.attribute.line">
|
||||
<field name="product_tmpl_id" ref="point_of_sale.desk_organizer_product_template" />
|
||||
<field name="attribute_id" ref="size_attribute" />
|
||||
<field name="value_ids"
|
||||
eval="[(6, 0, [ref('size_attribute_s'), ref('size_attribute_m'), ref('size_attribute_l')])]" />
|
||||
</record>
|
||||
|
||||
<record id="fabric_attribute" model="product.attribute">
|
||||
<field name="name">Fabric</field>
|
||||
<field name="sequence">40</field>
|
||||
<field name="display_type">select</field>
|
||||
<field name="create_variant">no_variant</field>
|
||||
</record>
|
||||
<record id="fabric_attribute_plastic" model="product.attribute.value">
|
||||
<field name="name">Plastic</field>
|
||||
<field name="sequence">1</field>
|
||||
<field name="attribute_id" ref="fabric_attribute" />
|
||||
</record>
|
||||
<record id="fabric_attribute_leather" model="product.attribute.value">
|
||||
<field name="name">Leather</field>
|
||||
<field name="sequence">2</field>
|
||||
<field name="attribute_id" ref="fabric_attribute" />
|
||||
</record>
|
||||
<record id="fabric_attribute_custom" model="product.attribute.value">
|
||||
<field name="name">Custom</field>
|
||||
<field name="sequence">3</field>
|
||||
<field name="attribute_id" ref="fabric_attribute" />
|
||||
<field name="is_custom">True</field>
|
||||
</record>
|
||||
<record id="desk_organizer_fabric" model="product.template.attribute.line">
|
||||
<field name="product_tmpl_id" ref="point_of_sale.desk_organizer_product_template" />
|
||||
<field name="attribute_id" ref="fabric_attribute" />
|
||||
<field name="value_ids"
|
||||
eval="[(6, 0, [ref('fabric_attribute_plastic'), ref('fabric_attribute_leather'), ref('fabric_attribute_custom')])]" />
|
||||
</record>
|
||||
|
||||
<record id="magnetic_board" model="product.product">
|
||||
<field name="available_in_pos">True</field>
|
||||
<field name="list_price">1.98</field>
|
||||
<field name="name">Magnetic Board</field>
|
||||
<field name="default_code">FURN_0005</field>
|
||||
<field name="type">product</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="barcode">2301000000006</field>
|
||||
<field name="to_weight">True</field>
|
||||
<field name="categ_id" ref="product.product_category_5" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]" />
|
||||
<field name="uom_id" ref="uom.product_uom_unit" />
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit" />
|
||||
<field name="image_1920" type="base64" file="point_of_sale/static/img/magnetic_board.png" />
|
||||
</record>
|
||||
<record id="monitor_stand" model="product.product">
|
||||
<field name="available_in_pos">True</field>
|
||||
<field name="list_price">3.19</field>
|
||||
<field name="name">Monitor Stand</field>
|
||||
<field name="default_code">FURN_0006</field>
|
||||
<field name="type">product</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="to_weight">True</field>
|
||||
<field name="categ_id" ref="product.product_category_5" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]" />
|
||||
<field name="uom_id" ref="uom.product_uom_unit" />
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit" />
|
||||
<field name="image_1920" type="base64" file="point_of_sale/static/img/monitor_stand.png" />
|
||||
</record>
|
||||
<record id="desk_pad" model="product.product">
|
||||
<field name="available_in_pos">True</field>
|
||||
<field name="list_price">1.98</field>
|
||||
<field name="name">Desk Pad</field>
|
||||
<field name="default_code">FURN_0002</field>
|
||||
<field name="type">product</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="to_weight">True</field>
|
||||
<field name="categ_id" ref="product.product_category_5" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]" />
|
||||
<field name="uom_id" ref="uom.product_uom_unit" />
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit" />
|
||||
<field name="image_1920" type="base64" file="point_of_sale/static/img/desk_pad.png" />
|
||||
</record>
|
||||
|
||||
<record id="whiteboard" model="product.product">
|
||||
<field name="available_in_pos">True</field>
|
||||
<field name="list_price">1.70</field>
|
||||
<field name="name">Whiteboard</field>
|
||||
<field name="to_weight">True</field>
|
||||
<field name="type">product</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="categ_id" ref="product.product_category_5" />
|
||||
<field name="uom_id" ref="uom.product_uom_unit" />
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit" />
|
||||
<field name="image_1920" type="base64" file="point_of_sale/static/img/whiteboard.png" />
|
||||
</record>
|
||||
|
||||
<record id="led_lamp" model="product.product">
|
||||
<field name="available_in_pos">True</field>
|
||||
<field name="list_price">0.90</field>
|
||||
<field name="name">LED Lamp</field>
|
||||
<field name="default_code">FURN_0003</field>
|
||||
<field name="type">product</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="to_weight">True</field>
|
||||
<field name="categ_id" ref="product.product_category_5" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]" />
|
||||
<field name="uom_id" ref="uom.product_uom_unit" />
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit" />
|
||||
<field name="image_1920" type="base64" file="point_of_sale/static/img/led_lamp.png" />
|
||||
</record>
|
||||
|
||||
<record id="newspaper_rack" model="product.product">
|
||||
<field name="available_in_pos">True</field>
|
||||
<field name="list_price">1.28</field>
|
||||
<field name="name">Newspaper Rack</field>
|
||||
<field name="default_code">FURN_0007</field>
|
||||
<field name="type">product</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="to_weight">True</field>
|
||||
<field name="barcode">2100001000004</field>
|
||||
<field name="categ_id" ref="product.product_category_5" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]" />
|
||||
<field name="uom_id" ref="uom.product_uom_unit" />
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit" />
|
||||
<field name="image_1920" type="base64" file="point_of_sale/static/img/newspaper_stand.png" />
|
||||
</record>
|
||||
|
||||
<record id="whiteboard_pen" model="product.product">
|
||||
<field name="available_in_pos">True</field>
|
||||
<field name="list_price">1.20</field>
|
||||
<field name="name">Whiteboard Pen</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="default_code">CONS_0001</field>
|
||||
<field name="to_weight">True</field>
|
||||
<field name="categ_id" ref="product.product_category_consumable" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]" />
|
||||
<field name="uom_id" ref="uom.product_uom_unit" />
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit" />
|
||||
<field name="image_1920" type="base64" file="point_of_sale/static/img/whiteboard_pen.png" />
|
||||
</record>
|
||||
|
||||
<record id="product.product_product_1" model="product.product">
|
||||
<field name="available_in_pos" eval="True" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]" />
|
||||
</record>
|
||||
<record id="product.product_product_2" model="product.product">
|
||||
<field name="available_in_pos" eval="True" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]" />
|
||||
</record>
|
||||
<record id="product.product_delivery_01" model="product.product">
|
||||
<field name="available_in_pos" eval="True" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_chairs')])]" />
|
||||
</record>
|
||||
<record id="product.product_delivery_02" model="product.product">
|
||||
<field name="available_in_pos" eval="True" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]" />
|
||||
</record>
|
||||
<record id="product.product_order_01" model="product.product">
|
||||
<field name="available_in_pos" eval="True" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]" />
|
||||
</record>
|
||||
<record id="product.product_product_3" model="product.product">
|
||||
<field name="available_in_pos" eval="True" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_desks')])]" />
|
||||
</record>
|
||||
<record id="product.product_product_4_product_template" model="product.template">
|
||||
<field name="available_in_pos" eval="True" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_desks')])]" />
|
||||
</record>
|
||||
<record id="product.product_product_5" model="product.product">
|
||||
<field name="available_in_pos" eval="True" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_desks')])]" />
|
||||
</record>
|
||||
<record id="product.product_product_6" model="product.product">
|
||||
<field name="available_in_pos" eval="True" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]" />
|
||||
</record>
|
||||
<record id="product.product_product_7" model="product.product">
|
||||
<field name="available_in_pos" eval="True" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]" />
|
||||
</record>
|
||||
<record id="product.product_product_8" model="product.product">
|
||||
<field name="available_in_pos" eval="True" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_desks')])]" />
|
||||
</record>
|
||||
<record id="product.product_product_9" model="product.product">
|
||||
<field name="available_in_pos" eval="True" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]" />
|
||||
</record>
|
||||
<record id="product.product_product_10" model="product.product">
|
||||
<field name="available_in_pos" eval="True" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]" />
|
||||
</record>
|
||||
<record id="product.product_product_11" model="product.product">
|
||||
<field name="available_in_pos" eval="True" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_chairs')])]" />
|
||||
</record>
|
||||
<record id="product.product_product_11b" model="product.product">
|
||||
<field name="available_in_pos" eval="True" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_chairs')])]" />
|
||||
</record>
|
||||
<record id="product.product_product_12" model="product.product">
|
||||
<field name="available_in_pos" eval="True" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_chairs')])]" />
|
||||
</record>
|
||||
<record id="product.product_product_13" model="product.product">
|
||||
<field name="available_in_pos" eval="True" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_desks')])]" />
|
||||
</record>
|
||||
<record id="product.product_product_16" model="product.product">
|
||||
<field name="available_in_pos" eval="True" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]" />
|
||||
</record>
|
||||
<record id="product.product_product_20" model="product.product">
|
||||
<field name="available_in_pos" eval="True" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]" />
|
||||
</record>
|
||||
<record id="product.product_product_22" model="product.product">
|
||||
<field name="available_in_pos" eval="True" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]" />
|
||||
</record>
|
||||
<record id="product.product_product_24" model="product.product">
|
||||
<field name="available_in_pos" eval="True" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]" />
|
||||
</record>
|
||||
<record id="product.product_product_25" model="product.product">
|
||||
<field name="available_in_pos" eval="True" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]" />
|
||||
</record>
|
||||
<record id="product.product_product_27" model="product.product">
|
||||
<field name="available_in_pos" eval="True" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]" />
|
||||
</record>
|
||||
<record id="product.consu_delivery_03" model="product.product">
|
||||
<field name="available_in_pos" eval="True" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_desks')])]" />
|
||||
</record>
|
||||
<record id="product.consu_delivery_02" model="product.product">
|
||||
<field name="available_in_pos" eval="True" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]" />
|
||||
</record>
|
||||
<record id="product.consu_delivery_01" model="product.product">
|
||||
<field name="available_in_pos" eval="True" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]" />
|
||||
</record>
|
||||
|
||||
<record id="desk_organizer_combo_line" model="pos.combo.line">
|
||||
<field name="product_id" ref="desk_organizer"/>
|
||||
<field name="combo_price">0</field>
|
||||
</record>
|
||||
<record id="desk_pad_combo_line" model="pos.combo.line">
|
||||
<field name="product_id" ref="desk_pad"/>
|
||||
<field name="combo_price">0</field>
|
||||
</record>
|
||||
<record id="monitor_stand_combo_line" model="pos.combo.line">
|
||||
<field name="product_id" ref="monitor_stand"/>
|
||||
<field name="combo_price">2</field>
|
||||
</record>
|
||||
<record id="desk_accessories_combo" model="pos.combo">
|
||||
<field name="name">Desk Accessories Combo</field>
|
||||
<field name="combo_line_ids" eval="[(6, 0, [ref('desk_organizer_combo_line'), ref('desk_pad_combo_line'), ref('monitor_stand_combo_line')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="product_3_combo_line" model="pos.combo.line">
|
||||
<field name="product_id" ref="product.product_product_3"/>
|
||||
<field name="combo_price">0</field>
|
||||
</record>
|
||||
<record id="product_5_combo_line" model="pos.combo.line">
|
||||
<field name="product_id" ref="product.product_product_5"/>
|
||||
<field name="combo_price">0</field>
|
||||
</record>
|
||||
<record id="desks_combo" model="pos.combo">
|
||||
<field name="name">Desks Combo</field>
|
||||
<field name="combo_line_ids" eval="[(6, 0, [ref('product_3_combo_line'), ref('product_5_combo_line')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="product_11_combo_line" model="pos.combo.line">
|
||||
<field name="product_id" ref="product.product_product_11"/>
|
||||
<field name="combo_price">0</field>
|
||||
</record>
|
||||
<record id="product_11b_combo_line" model="pos.combo.line">
|
||||
<field name="product_id" ref="product.product_product_11b"/>
|
||||
<field name="combo_price">0</field>
|
||||
</record>
|
||||
<record id="product_12_combo_line" model="pos.combo.line">
|
||||
<field name="product_id" ref="product.product_product_12"/>
|
||||
<field name="combo_price">0</field>
|
||||
</record>
|
||||
<record id="chairs_combo" model="pos.combo">
|
||||
<field name="name">Chairs Combo</field>
|
||||
<field name="combo_line_ids" eval="[(6, 0, [ref('product_11_combo_line'), ref('product_11b_combo_line'), ref('product_12_combo_line')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="office_combo" model="product.product">
|
||||
<field name="available_in_pos">True</field>
|
||||
<field name="list_price">160</field>
|
||||
<field name="name">Office combo</field>
|
||||
<field name="type">combo</field>
|
||||
<field name="categ_id" ref="product.product_category_5"/>
|
||||
<field name="uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit"/>
|
||||
<field name="image_1920" type="base64" file="point_of_sale/static/img/office_combo.jpg"/>
|
||||
<field name="combo_ids" eval="[(6, 0, [ref('desks_combo'), ref('chairs_combo'), ref('desk_accessories_combo')])]"/>
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous'), ref('pos_category_desks')])]"/>
|
||||
<field name="taxes_id" eval="[(5,)]"/> <!-- no taxes -->
|
||||
</record>
|
||||
|
||||
<!-- Closed Sessions -->
|
||||
<!-- forcecreate is set to false in order to not create record when updating the db -->
|
||||
|
||||
<function model="ir.model.data" name="_update_xmlids">
|
||||
<value model="base" eval="[{
|
||||
'xml_id': 'point_of_sale.payment_method',
|
||||
'record': obj().env.ref('point_of_sale.pos_config_main')._get_payment_method('cash'),
|
||||
'noupdate': True,
|
||||
}]" />
|
||||
</function>
|
||||
|
||||
<!-- Closed Session 1 -->
|
||||
|
||||
<record id="pos_closed_session_1" model="pos.session" forcecreate="False">
|
||||
<field name="config_id" ref="pos_config_main" />
|
||||
<field name="user_id" ref="base.user_admin" />
|
||||
<field name="start_at" eval="(DateTime.today() + relativedelta(days=-1)).strftime('%Y-%m-%d %H:%M:%S')" />
|
||||
<field name="stop_at"
|
||||
eval="(DateTime.today() + relativedelta(days=-1, hours=1)).strftime('%Y-%m-%d %H:%M:%S')" />
|
||||
</record>
|
||||
|
||||
<record id="pos_closed_order_1_1" model="pos.order" forcecreate="False">
|
||||
<field name="session_id" ref="pos_closed_session_1" />
|
||||
<field name="company_id" ref="base.main_company" />
|
||||
<field name="name">ClosedDemo/0001</field>
|
||||
<field name="state">paid</field>
|
||||
<field name="amount_total">4.81</field>
|
||||
<field name="amount_tax">0.0</field>
|
||||
<field name="amount_paid">4.81</field>
|
||||
<field name="amount_return">0.0</field>
|
||||
<field name="pos_reference">Order 00000-001-1001</field>
|
||||
<field name="partner_id" ref="base.res_partner_1" />
|
||||
</record>
|
||||
|
||||
<record id="pos_closed_orderline_1_1_1" model="pos.order.line" forcecreate="False">
|
||||
<field name="product_id" ref="wall_shelf" />
|
||||
<field name="price_subtotal">1.98</field>
|
||||
<field name="price_subtotal_incl">1.98</field>
|
||||
<field name="price_unit">1.98</field>
|
||||
<field name="order_id" ref="pos_closed_order_1_1" />
|
||||
<field name="full_product_name">Wall Shelf</field>
|
||||
</record>
|
||||
|
||||
<record id="pos_closed_orderline_1_1_2" model="pos.order.line" forcecreate="False">
|
||||
<field name="product_id" ref="small_shelf" />
|
||||
<field name="price_subtotal">2.83</field>
|
||||
<field name="price_subtotal_incl">2.83</field>
|
||||
<field name="price_unit">2.83</field>
|
||||
<field name="order_id" ref="pos_closed_order_1_1" />
|
||||
<field name="full_product_name">Small Shelf</field>
|
||||
</record>
|
||||
|
||||
<record id="pos_payment_1" model="pos.payment" forcecreate="False">
|
||||
<field name="payment_method_id" ref="point_of_sale.payment_method" />
|
||||
<field name="pos_order_id" ref="pos_closed_order_1_1" />
|
||||
<field name="amount">4.81</field>
|
||||
</record>
|
||||
|
||||
<record id="pos_closed_order_1_2" model="pos.order" forcecreate="False">
|
||||
<field name="session_id" ref="pos_closed_session_1" />
|
||||
<field name="company_id" ref="base.main_company" />
|
||||
<field name="name">ClosedDemo/0002</field>
|
||||
<field name="state">paid</field>
|
||||
<field name="amount_total">2220.50</field>
|
||||
<field name="amount_tax">0.0</field>
|
||||
<field name="amount_paid">2220.50</field>
|
||||
<field name="amount_return">0.0</field>
|
||||
<field name="pos_reference">Order 00000-001-1002</field>
|
||||
<field name="partner_id" ref="base.res_partner_1" />
|
||||
</record>
|
||||
|
||||
<record id="pos_closed_orderline_1_2_1" model="pos.order.line" forcecreate="False">
|
||||
<field name="product_id" ref="product.product_product_12" />
|
||||
<field name="price_subtotal">120.50</field>
|
||||
<field name="price_subtotal_incl">120.5</field>
|
||||
<field name="price_unit">120.50</field>
|
||||
<field name="order_id" ref="pos_closed_order_1_2" />
|
||||
<field name="full_product_name">Office Chair Black</field>
|
||||
</record>
|
||||
|
||||
<record id="pos_closed_orderline_1_2_2" model="pos.order.line" forcecreate="False">
|
||||
<field name="product_id" ref="product.product_product_22" />
|
||||
<field name="price_subtotal">2100.0</field>
|
||||
<field name="price_subtotal_incl">2100.0</field>
|
||||
<field name="price_unit">2100.0</field>
|
||||
<field name="order_id" ref="pos_closed_order_1_2" />
|
||||
<field name="full_product_name">Desk Stand with Screen</field>
|
||||
</record>
|
||||
|
||||
<record id="pos_payment_2" model="pos.payment" forcecreate="False">
|
||||
<field name="payment_method_id" ref="point_of_sale.payment_method" />
|
||||
<field name="pos_order_id" ref="pos_closed_order_1_2" />
|
||||
<field name="amount">2220.50</field>
|
||||
</record>
|
||||
|
||||
<function model="pos.session" name="post_closing_cash_details" eval="[[ref('pos_closed_session_1')], 2225.31]" />
|
||||
|
||||
<function model="pos.session" name="update_closing_control_state_session"
|
||||
eval="[[ref('pos_closed_session_1')], '']" />
|
||||
|
||||
<function model="pos.session" name="action_pos_session_closing_control"
|
||||
eval="[[ref('pos_closed_session_1')]]" />
|
||||
|
||||
<!-- Closed Session 2 -->
|
||||
|
||||
<record id="pos_closed_session_2" model="pos.session" forcecreate="False">
|
||||
<field name="config_id" ref="pos_config_main" />
|
||||
<field name="user_id" ref="base.user_admin" />
|
||||
<field name="start_at" eval="(DateTime.today() + relativedelta(hours=-3)).strftime('%Y-%m-%d %H:%M:%S')" />
|
||||
<field name="stop_at" eval="(DateTime.today() + relativedelta(hours=-2)).strftime('%Y-%m-%d %H:%M:%S')" />
|
||||
</record>
|
||||
|
||||
<record id="pos_closed_order_2_1" model="pos.order" forcecreate="False">
|
||||
<field name="session_id" ref="pos_closed_session_2" />
|
||||
<field name="company_id" ref="base.main_company" />
|
||||
<field name="name">ClosedDemo/0003</field>
|
||||
<field name="state">paid</field>
|
||||
<field name="amount_total">9.90</field>
|
||||
<field name="amount_tax">0.0</field>
|
||||
<field name="amount_paid">9.90</field>
|
||||
<field name="amount_return">0.0</field>
|
||||
<field name="pos_reference">Order 00000-002-1001</field>
|
||||
<field name="partner_id" ref="base.res_partner_12" />
|
||||
</record>
|
||||
|
||||
<record id="pos_closed_orderline_2_1_1" model="pos.order.line" forcecreate="False">
|
||||
<field name="name">Closed Orderline 2.1.1</field>
|
||||
<field name="product_id" ref="letter_tray" />
|
||||
<field name="price_subtotal">4.80</field>
|
||||
<field name="price_subtotal_incl">4.80</field>
|
||||
<field name="price_unit">4.80</field>
|
||||
<field name="order_id" ref="pos_closed_order_2_1" />
|
||||
<field name="full_product_name">Letter Tray</field>
|
||||
</record>
|
||||
|
||||
<record id="pos_closed_orderline_2_1_2" model="pos.order.line" forcecreate="False">
|
||||
<field name="name">Closed Orderline 2.1.2</field>
|
||||
<field name="product_id" ref="desk_organizer" />
|
||||
<field name="price_subtotal">5.10</field>
|
||||
<field name="price_subtotal_incl">5.10</field>
|
||||
<field name="price_unit">5.10</field>
|
||||
<field name="order_id" ref="pos_closed_order_2_1" />
|
||||
<field name="full_product_name">Desk Organizer</field>
|
||||
</record>
|
||||
|
||||
<record id="pos_payment_3" model="pos.payment" forcecreate="False">
|
||||
<field name="payment_method_id" ref="point_of_sale.payment_method" />
|
||||
<field name="pos_order_id" ref="pos_closed_order_2_1" />
|
||||
<field name="amount">9.90</field>
|
||||
</record>
|
||||
|
||||
<record id="pos_closed_order_2_2" model="pos.order" forcecreate="False">
|
||||
<field name="session_id" ref="pos_closed_session_2" />
|
||||
<field name="company_id" ref="base.main_company" />
|
||||
<field name="name">ClosedDemo/0004</field>
|
||||
<field name="state">paid</field>
|
||||
<field name="amount_total">8.36</field>
|
||||
<field name="amount_tax">0.0</field>
|
||||
<field name="amount_paid">8.36</field>
|
||||
<field name="amount_return">0.0</field>
|
||||
<field name="pos_reference">Order 00000-002-1002</field>
|
||||
<field name="partner_id" ref="base.res_partner_12" />
|
||||
</record>
|
||||
|
||||
<record id="pos_closed_orderline_2_2_1" model="pos.order.line" forcecreate="False">
|
||||
<field name="name">Closed Orderline 2.2.1</field>
|
||||
<field name="product_id" ref="magnetic_board" />
|
||||
<field name="price_subtotal">1.98</field>
|
||||
<field name="price_subtotal_incl">1.98</field>
|
||||
<field name="price_unit">1.98</field>
|
||||
<field name="order_id" ref="pos_closed_order_2_2" />
|
||||
<field name="full_product_name">Magnetic Board</field>
|
||||
</record>
|
||||
|
||||
<record id="pos_closed_orderline_2_2_2" model="pos.order.line" forcecreate="False">
|
||||
<field name="name">Closed Orderline 2.1.2</field>
|
||||
<field name="product_id" ref="monitor_stand" />
|
||||
<field name="price_subtotal">6.38</field>
|
||||
<field name="price_subtotal_incl">6.38</field>
|
||||
<field name="qty">2</field>
|
||||
<field name="price_unit">3.19</field>
|
||||
<field name="order_id" ref="pos_closed_order_2_2" />
|
||||
<field name="full_product_name">Monitor Stand</field>
|
||||
</record>
|
||||
|
||||
<record id="pos_payment_4" model="pos.payment" forcecreate="False">
|
||||
<field name="payment_method_id" ref="point_of_sale.payment_method" />
|
||||
<field name="pos_order_id" ref="pos_closed_order_2_2" />
|
||||
<field name="amount">8.36</field>
|
||||
</record>
|
||||
|
||||
<function model="pos.session" name="post_closing_cash_details"
|
||||
eval="[[ref('pos_closed_session_2')], 2243.57]" />
|
||||
|
||||
<function model="pos.session" name="update_closing_control_state_session"
|
||||
eval="[[ref('pos_closed_session_2')], '']" />
|
||||
|
||||
<function model="pos.session" name="action_pos_session_closing_control"
|
||||
eval="[[ref('pos_closed_session_2')]]" />
|
||||
</data>
|
||||
</odoo>
|
894
data/point_of_sale_onboarding.xml
Normal file
894
data/point_of_sale_onboarding.xml
Normal file
@ -0,0 +1,894 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Resource: pos.category -->
|
||||
<record id="pos_category_miscellaneous" model="pos.category">
|
||||
<field name="name">Misc</field>
|
||||
<field name="image_128" type="base64" file="point_of_sale/static/img/misc_category.png" />
|
||||
</record>
|
||||
<record id="pos_category_desks" model="pos.category">
|
||||
<field name="name">Desks</field>
|
||||
<field name="image_128" type="base64" file="point_of_sale/static/img/desk_category.png" />
|
||||
</record>
|
||||
<record id="pos_category_chairs" model="pos.category">
|
||||
<field name="name">Chairs</field>
|
||||
<field name="image_128" type="base64" file="point_of_sale/static/img/chair_category.png" />
|
||||
</record>
|
||||
|
||||
<function model="pos.config" name="add_cash_payment_method" />
|
||||
|
||||
<!-- Preparation Printer -->
|
||||
<record id="preparation_printer" model="pos.printer">
|
||||
<field name="name">Preparation Printer</field>
|
||||
<field name="proxy_ip">localhost</field>
|
||||
<field name="product_categories_ids" eval="[(6, 0, [ref('point_of_sale.pos_category_miscellaneous')])]" />
|
||||
</record>
|
||||
|
||||
<!-- Products -->
|
||||
|
||||
<!-- Old -->
|
||||
<record id="wall_shelf" model="product.product">
|
||||
<field name="available_in_pos">True</field>
|
||||
<field name="list_price">1.98</field>
|
||||
<field name="name">Wall Shelf Unit</field>
|
||||
<field name="default_code">FURN_0009</field>
|
||||
<field name="type">product</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="to_weight">True</field>
|
||||
<field name="barcode">2100002000003</field>
|
||||
<field name="taxes_id" eval="[(5,)]" />
|
||||
<field name="categ_id" ref="product.product_category_1" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]" />
|
||||
<field name="uom_id" ref="uom.product_uom_unit" />
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit" />
|
||||
<field name="image_1920" type="base64" file="point_of_sale/static/img/wall_shelf_unit.png" />
|
||||
</record>
|
||||
<record id="small_shelf" model="product.product">
|
||||
<field name="available_in_pos">True</field>
|
||||
<field name="list_price">2.83</field>
|
||||
<field name="name">Small Shelf</field>
|
||||
<field name="default_code">FURN_0008</field>
|
||||
<field name="type">product</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="taxes_id" eval="[(5,)]" />
|
||||
<field name="categ_id" ref="product.product_category_1" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]" />
|
||||
<field name="to_weight">True</field>
|
||||
<field name="uom_id" ref="uom.product_uom_unit" />
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit" />
|
||||
<field name="image_1920" type="base64" file="point_of_sale/static/img/small_shelf.png" />
|
||||
</record>
|
||||
|
||||
<record id="letter_tray" model="product.product">
|
||||
<field name="available_in_pos">True</field>
|
||||
<field name="list_price">4.80</field>
|
||||
<field name="name">Letter Tray</field>
|
||||
<field name="default_code">FURN_0004</field>
|
||||
<field name="type">product</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="to_weight">True</field>
|
||||
<field name="categ_id" ref="product.product_category_1" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]" />
|
||||
<field name="uom_id" ref="uom.product_uom_unit" />
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit" />
|
||||
<field name="image_1920" type="base64" file="point_of_sale/static/img/letter_tray.png" />
|
||||
</record>
|
||||
<record id="desk_organizer" model="product.product">
|
||||
<field name="available_in_pos">True</field>
|
||||
<field name="list_price">5.10</field>
|
||||
<field name="name">Desk Organizer</field>
|
||||
<field name="default_code">FURN_0001</field>
|
||||
<field name="to_weight">True</field>
|
||||
<field name="barcode">2300001000008</field>
|
||||
<field name="type">product</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="categ_id" ref="product.product_category_1" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]" />
|
||||
<field name="uom_id" ref="uom.product_uom_unit" />
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit" />
|
||||
<field name="image_1920" type="base64" file="point_of_sale/static/img/desk_organizer.png" />
|
||||
<field name="taxes_id" eval="[(5,)]" /> <!-- no taxes -->
|
||||
</record>
|
||||
|
||||
<function model="ir.model.data" name="_update_xmlids">
|
||||
<value model="base" eval="[{
|
||||
'xml_id': 'point_of_sale.desk_organizer_product_template',
|
||||
'record': obj().env.ref('point_of_sale.desk_organizer').product_tmpl_id,
|
||||
'noupdate': True,
|
||||
}]" />
|
||||
</function>
|
||||
|
||||
<record id="size_attribute" model="product.attribute">
|
||||
<field name="name">Size</field>
|
||||
<field name="sequence">30</field>
|
||||
<field name="display_type">radio</field>
|
||||
<field name="create_variant">no_variant</field>
|
||||
</record>
|
||||
<record id="size_attribute_s" model="product.attribute.value">
|
||||
<field name="name">S</field>
|
||||
<field name="sequence">1</field>
|
||||
<field name="attribute_id" ref="size_attribute" />
|
||||
</record>
|
||||
<record id="size_attribute_m" model="product.attribute.value">
|
||||
<field name="name">M</field>
|
||||
<field name="sequence">2</field>
|
||||
<field name="attribute_id" ref="size_attribute" />
|
||||
</record>
|
||||
<record id="size_attribute_l" model="product.attribute.value">
|
||||
<field name="name">L</field>
|
||||
<field name="sequence">3</field>
|
||||
<field name="attribute_id" ref="size_attribute" />
|
||||
</record>
|
||||
<record id="desk_organizer_size" model="product.template.attribute.line">
|
||||
<field name="product_tmpl_id" ref="point_of_sale.desk_organizer_product_template" />
|
||||
<field name="attribute_id" ref="size_attribute" />
|
||||
<field name="value_ids"
|
||||
eval="[(6, 0, [ref('size_attribute_s'), ref('size_attribute_m'), ref('size_attribute_l')])]" />
|
||||
</record>
|
||||
|
||||
<record id="fabric_attribute" model="product.attribute">
|
||||
<field name="name">Fabric</field>
|
||||
<field name="sequence">40</field>
|
||||
<field name="display_type">select</field>
|
||||
<field name="create_variant">no_variant</field>
|
||||
</record>
|
||||
<record id="fabric_attribute_plastic" model="product.attribute.value">
|
||||
<field name="name">Plastic</field>
|
||||
<field name="sequence">1</field>
|
||||
<field name="attribute_id" ref="fabric_attribute" />
|
||||
</record>
|
||||
<record id="fabric_attribute_leather" model="product.attribute.value">
|
||||
<field name="name">Leather</field>
|
||||
<field name="sequence">2</field>
|
||||
<field name="attribute_id" ref="fabric_attribute" />
|
||||
</record>
|
||||
<record id="fabric_attribute_custom" model="product.attribute.value">
|
||||
<field name="name">Custom</field>
|
||||
<field name="sequence">3</field>
|
||||
<field name="attribute_id" ref="fabric_attribute" />
|
||||
<field name="is_custom">True</field>
|
||||
</record>
|
||||
<record id="desk_organizer_fabric" model="product.template.attribute.line">
|
||||
<field name="product_tmpl_id" ref="point_of_sale.desk_organizer_product_template" />
|
||||
<field name="attribute_id" ref="fabric_attribute" />
|
||||
<field name="value_ids"
|
||||
eval="[(6, 0, [ref('fabric_attribute_plastic'), ref('fabric_attribute_leather'), ref('fabric_attribute_custom')])]" />
|
||||
</record>
|
||||
|
||||
<record id="magnetic_board" model="product.product">
|
||||
<field name="available_in_pos">True</field>
|
||||
<field name="list_price">1.98</field>
|
||||
<field name="name">Magnetic Board</field>
|
||||
<field name="default_code">FURN_0005</field>
|
||||
<field name="type">product</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="barcode">2301000000006</field>
|
||||
<field name="to_weight">True</field>
|
||||
<field name="categ_id" ref="product.product_category_1" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]" />
|
||||
<field name="uom_id" ref="uom.product_uom_unit" />
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit" />
|
||||
<field name="image_1920" type="base64" file="point_of_sale/static/img/magnetic_board.png" />
|
||||
</record>
|
||||
<record id="monitor_stand" model="product.product">
|
||||
<field name="available_in_pos">True</field>
|
||||
<field name="list_price">3.19</field>
|
||||
<field name="name">Monitor Stand</field>
|
||||
<field name="default_code">FURN_0006</field>
|
||||
<field name="type">product</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="to_weight">True</field>
|
||||
<field name="categ_id" ref="product.product_category_1" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]" />
|
||||
<field name="uom_id" ref="uom.product_uom_unit" />
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit" />
|
||||
<field name="image_1920" type="base64" file="point_of_sale/static/img/monitor_stand.png" />
|
||||
</record>
|
||||
<record id="desk_pad" model="product.product">
|
||||
<field name="available_in_pos">True</field>
|
||||
<field name="list_price">1.98</field>
|
||||
<field name="name">Desk Pad</field>
|
||||
<field name="default_code">FURN_0002</field>
|
||||
<field name="type">product</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="to_weight">True</field>
|
||||
<field name="categ_id" ref="product.product_category_1" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]" />
|
||||
<field name="uom_id" ref="uom.product_uom_unit" />
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit" />
|
||||
<field name="image_1920" type="base64" file="point_of_sale/static/img/desk_pad.png" />
|
||||
</record>
|
||||
|
||||
<record id="whiteboard" model="product.product">
|
||||
<field name="available_in_pos">True</field>
|
||||
<field name="list_price">1.70</field>
|
||||
<field name="name">Whiteboard</field>
|
||||
<field name="to_weight">True</field>
|
||||
<field name="type">product</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="categ_id" ref="product.product_category_1" />
|
||||
<field name="uom_id" ref="uom.product_uom_unit" />
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit" />
|
||||
<field name="image_1920" type="base64" file="point_of_sale/static/img/whiteboard.png" />
|
||||
</record>
|
||||
|
||||
<record id="led_lamp" model="product.product">
|
||||
<field name="available_in_pos">True</field>
|
||||
<field name="list_price">0.90</field>
|
||||
<field name="name">LED Lamp</field>
|
||||
<field name="default_code">FURN_0003</field>
|
||||
<field name="type">product</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="to_weight">True</field>
|
||||
<field name="categ_id" ref="product.product_category_1" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]" />
|
||||
<field name="uom_id" ref="uom.product_uom_unit" />
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit" />
|
||||
<field name="image_1920" type="base64" file="point_of_sale/static/img/led_lamp.png" />
|
||||
</record>
|
||||
|
||||
<record id="newspaper_rack" model="product.product">
|
||||
<field name="available_in_pos">True</field>
|
||||
<field name="list_price">1.28</field>
|
||||
<field name="name">Newspaper Rack</field>
|
||||
<field name="default_code">FURN_0007</field>
|
||||
<field name="type">product</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="to_weight">True</field>
|
||||
<field name="barcode">2100001000004</field>
|
||||
<field name="categ_id" ref="product.product_category_1" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]" />
|
||||
<field name="uom_id" ref="uom.product_uom_unit" />
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit" />
|
||||
<field name="image_1920" type="base64" file="point_of_sale/static/img/newspaper_stand.png" />
|
||||
</record>
|
||||
|
||||
<record id="whiteboard_pen" model="product.product">
|
||||
<field name="available_in_pos">True</field>
|
||||
<field name="list_price">1.20</field>
|
||||
<field name="name">Whiteboard Pen</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="default_code">CONS_0001</field>
|
||||
<field name="to_weight">True</field>
|
||||
<field name="categ_id" ref="product.product_category_1" />
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]" />
|
||||
<field name="uom_id" ref="uom.product_uom_unit" />
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit" />
|
||||
<field name="image_1920" type="base64" file="point_of_sale/static/img/whiteboard_pen.png" />
|
||||
</record>
|
||||
|
||||
<!--
|
||||
Those following products come from product demo data.
|
||||
As this file is not considered as demo data and its content is red and added to the db
|
||||
apart from demo data, we cannot override the product from the product demo data
|
||||
but we have to rewrite them here.
|
||||
-->
|
||||
|
||||
<!-- Service products -->
|
||||
<record id="product_product_1" model="product.product">
|
||||
<field name="name">Virtual Interior Design</field>
|
||||
<field name="categ_id" ref="product.product_category_1"/>
|
||||
<field name="standard_price">20.5</field>
|
||||
<field name="list_price">30.75</field>
|
||||
<field name="detailed_type">service</field>
|
||||
<field name="uom_id" ref="uom.product_uom_hour"/>
|
||||
<field name="uom_po_id" ref="uom.product_uom_hour"/>
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]"/>
|
||||
<field name="available_in_pos" eval="True"/>
|
||||
</record>
|
||||
|
||||
<record id="product_product_2" model="product.product">
|
||||
<field name="name">Virtual Home Staging</field>
|
||||
<field name="categ_id" ref="product.product_category_1"/>
|
||||
<field name="standard_price">25.5</field>
|
||||
<field name="list_price">38.25</field>
|
||||
<field name="detailed_type">service</field>
|
||||
<field name="uom_id" ref="uom.product_uom_hour"/>
|
||||
<field name="uom_po_id" ref="uom.product_uom_hour"/>
|
||||
<field name="available_in_pos" eval="True"/>
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="product_delivery_01" model="product.product">
|
||||
<field name="name">Office Chair</field>
|
||||
<field name="categ_id" ref="product.product_category_1"/>
|
||||
<field name="standard_price">55.0</field>
|
||||
<field name="list_price">70.0</field>
|
||||
<field name="detailed_type">consu</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit"/>
|
||||
<field name="default_code">FURN_7777</field>
|
||||
<field name="image_1920" type="base64" file="product/static/img/product_chair.jpg"/>
|
||||
<field name="available_in_pos" eval="True"/>
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_chairs')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="product_delivery_02" model="product.product">
|
||||
<field name="name">Office Lamp</field>
|
||||
<field name="categ_id" ref="product.product_category_1"/>
|
||||
<field name="standard_price">35.0</field>
|
||||
<field name="list_price">40.0</field>
|
||||
<field name="detailed_type">consu</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit"/>
|
||||
<field name="default_code">FURN_8888</field>
|
||||
<field name="image_1920" type="base64" file="product/static/img/product_lamp.png"/>
|
||||
<field name="available_in_pos" eval="True"/>
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="product_order_01" model="product.product">
|
||||
<field name="name">Office Design Software</field>
|
||||
<field name="categ_id" ref="product.product_category_1"/>
|
||||
<field name="standard_price">235.0</field>
|
||||
<field name="list_price">280.0</field>
|
||||
<field name="detailed_type">consu</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit"/>
|
||||
<field name="default_code">FURN_9999</field>
|
||||
<field name="image_1920" type="base64" file="product/static/img/product_product_43-image.jpg"/>
|
||||
<field name="available_in_pos" eval="True"/>
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="product_product_3" model="product.product">
|
||||
<field name="available_in_pos" eval="True"/>
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]"/>
|
||||
<field name="name">Desk Combination</field>
|
||||
<field name="categ_id" ref="product.product_category_1"/>
|
||||
<field name="list_price">450.0</field>
|
||||
<field name="standard_price">300.0</field>
|
||||
<field name="detailed_type">consu</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit"/>
|
||||
<field name="description_sale">Desk combination, black-brown: chair + desk + drawer.</field>
|
||||
<field name="default_code">FURN_7800</field>
|
||||
<field name="image_1920" type="base64" file="product/static/img/product_product_3-image.jpg"/>
|
||||
</record>
|
||||
|
||||
<record id="product_attribute_1" model="product.attribute">
|
||||
<field name="name">Legs</field>
|
||||
<field name="sequence">10</field>
|
||||
</record>
|
||||
<record id="product_attribute_value_1" model="product.attribute.value">
|
||||
<field name="name">Steel</field>
|
||||
<field name="attribute_id" ref="product_attribute_1"/>
|
||||
<field name="sequence">1</field>
|
||||
</record>
|
||||
<record id="product_attribute_value_2" model="product.attribute.value">
|
||||
<field name="name">Aluminium</field>
|
||||
<field name="attribute_id" ref="product_attribute_1"/>
|
||||
<field name="sequence">2</field>
|
||||
</record>
|
||||
|
||||
<record id="product_attribute_2" model="product.attribute">
|
||||
<field name="name">Color</field>
|
||||
<field name="sequence">20</field>
|
||||
</record>
|
||||
<record id="product_attribute_value_3" model="product.attribute.value">
|
||||
<field name="name">White</field>
|
||||
<field name="attribute_id" ref="product_attribute_2"/>
|
||||
<field name="sequence">1</field>
|
||||
</record>
|
||||
<record id="product_attribute_value_4" model="product.attribute.value">
|
||||
<field name="name">Black</field>
|
||||
<field name="attribute_id" ref="product_attribute_2"/>
|
||||
<field name="sequence">2</field>
|
||||
</record>
|
||||
|
||||
<record id="product_attribute_3" model="product.attribute">
|
||||
<field name="name">Duration</field>
|
||||
<field name="sequence">30</field>
|
||||
</record>
|
||||
<record id="product_attribute_value_5" model="product.attribute.value">
|
||||
<field name="name">1 year</field>
|
||||
<field name="attribute_id" ref="product_attribute_3"/>
|
||||
</record>
|
||||
<record id="product_attribute_value_6" model="product.attribute.value">
|
||||
<field name="name">2 year</field>
|
||||
<field name="attribute_id" ref="product_attribute_3"/>
|
||||
</record>
|
||||
|
||||
<record id="product_product_4_product_template" model="product.template">
|
||||
<field name="available_in_pos" eval="True"/>
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_desks')])]"/>
|
||||
<field name="name">Customizable Desk</field>
|
||||
<field name="categ_id" ref="product.product_category_1"/>
|
||||
<field name="standard_price">500.0</field>
|
||||
<field name="list_price">750.0</field>
|
||||
<field name="detailed_type">consu</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit"/>
|
||||
<field name="description_sale">160x80cm, with large legs.</field>
|
||||
</record>
|
||||
|
||||
<record id="product_4_attribute_1_product_template_attribute_line" model="product.template.attribute.line">
|
||||
<field name="product_tmpl_id" ref="product_product_4_product_template"/>
|
||||
<field name="attribute_id" ref="product_attribute_1"/>
|
||||
<field name="value_ids" eval="[(6, 0, [ref('point_of_sale.product_attribute_value_1'), ref('point_of_sale.product_attribute_value_2')])]"/>
|
||||
</record>
|
||||
<record id="product_4_attribute_2_product_template_attribute_line" model="product.template.attribute.line">
|
||||
<field name="product_tmpl_id" ref="product_product_4_product_template"/>
|
||||
<field name="attribute_id" ref="product_attribute_2"/>
|
||||
<field name="value_ids" eval="[(6, 0, [ref('point_of_sale.product_attribute_value_3'), ref('point_of_sale.product_attribute_value_4')])]"/>
|
||||
</record>
|
||||
|
||||
<!--
|
||||
Handle automatically created product.template.attribute.value.
|
||||
Meaning that the combination between the "customizable desk" and the attribute value "black" will be materialized
|
||||
into a "product.template.attribute.value" with the ref "point_of_sale.product_4_attribute_1_value_1".
|
||||
This will allow setting fields like "price_extra" and "exclude_for"
|
||||
-->
|
||||
<function model="ir.model.data" name="_update_xmlids">
|
||||
<value model="base" eval="[{
|
||||
'xml_id': 'point_of_sale.product_4_attribute_1_value_1',
|
||||
'record': obj().env.ref('point_of_sale.product_4_attribute_1_product_template_attribute_line').product_template_value_ids[0],
|
||||
'noupdate': True,
|
||||
}, {
|
||||
'xml_id': 'point_of_sale.product_4_attribute_1_value_2',
|
||||
'record': obj().env.ref('point_of_sale.product_4_attribute_1_product_template_attribute_line').product_template_value_ids[1],
|
||||
'noupdate': True,
|
||||
}, {
|
||||
'xml_id': 'point_of_sale.product_4_attribute_2_value_1',
|
||||
'record': obj().env.ref('point_of_sale.product_4_attribute_2_product_template_attribute_line').product_template_value_ids[0],
|
||||
'noupdate': True,
|
||||
}, {
|
||||
'xml_id': 'point_of_sale.product_4_attribute_2_value_2',
|
||||
'record': obj().env.ref('point_of_sale.product_4_attribute_2_product_template_attribute_line').product_template_value_ids[1],
|
||||
'noupdate': True,
|
||||
},]"/>
|
||||
</function>
|
||||
|
||||
<function model="ir.model.data" name="_update_xmlids">
|
||||
<value model="base" eval="[{
|
||||
'xml_id': 'point_of_sale.product_product_4',
|
||||
'record': obj().env.ref('point_of_sale.product_product_4_product_template')._get_variant_for_combination(obj().env.ref('point_of_sale.product_4_attribute_1_value_1') + obj().env.ref('point_of_sale.product_4_attribute_2_value_1')),
|
||||
'noupdate': True,
|
||||
}, {
|
||||
'xml_id': 'point_of_sale.product_product_4b',
|
||||
'record': obj().env.ref('point_of_sale.product_product_4_product_template')._get_variant_for_combination(obj().env.ref('point_of_sale.product_4_attribute_1_value_1') + obj().env.ref('point_of_sale.product_4_attribute_2_value_2')),
|
||||
'noupdate': True,
|
||||
}, {
|
||||
'xml_id': 'point_of_sale.product_product_4c',
|
||||
'record': obj().env.ref('point_of_sale.product_product_4_product_template')._get_variant_for_combination(obj().env.ref('point_of_sale.product_4_attribute_1_value_2') + obj().env.ref('point_of_sale.product_4_attribute_2_value_1')),
|
||||
'noupdate': True,
|
||||
},]"/>
|
||||
</function>
|
||||
|
||||
<record id="product_product_4" model="product.product">
|
||||
<field name="default_code">FURN_0096</field>
|
||||
<field name="standard_price">500.0</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="image_1920" type="base64" file="product/static/img/table02.jpg"/>
|
||||
</record>
|
||||
<record id="product_product_4b" model="product.product">
|
||||
<field name="default_code">FURN_0097</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="standard_price">500.0</field>
|
||||
<field name="image_1920" type="base64" file="product/static/img/table04.jpg"/>
|
||||
</record>
|
||||
<record id="product_product_4c" model="product.product">
|
||||
<field name="default_code">FURN_0098</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="standard_price">500.0</field>
|
||||
<field name="image_1920" type="base64" file="product/static/img/table03.jpg"/>
|
||||
</record>
|
||||
|
||||
<record id="product_product_5" model="product.product">
|
||||
<field name="name">Corner Desk Right Sit</field>
|
||||
<field name="categ_id" ref="product.product_category_1"/>
|
||||
<field name="standard_price">600.0</field>
|
||||
<field name="list_price">147.0</field>
|
||||
<field name="detailed_type">consu</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit"/>
|
||||
<field name="default_code">E-COM06</field>
|
||||
<field name="image_1920" type="base64" file="product/static/img/product_product_5-image.jpg"/>
|
||||
<field name="available_in_pos" eval="True"/>
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_desks')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="product_product_6" model="product.product">
|
||||
<field name="name">Large Cabinet</field>
|
||||
<field name="categ_id" ref="product.product_category_1"/>
|
||||
<field name="standard_price">800.0</field>
|
||||
<field name="list_price">320.0</field>
|
||||
<field name="detailed_type">consu</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit"/>
|
||||
<field name="default_code">E-COM07</field>
|
||||
<field name='weight'>0.330</field>
|
||||
<field name="image_1920" type="base64" file="product/static/img/product_product_6-image.jpg"/>
|
||||
<field name="available_in_pos" eval="True"/>
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="product_product_7" model="product.product">
|
||||
<field name="name">Storage Box</field>
|
||||
<field name="categ_id" ref="product.product_category_1"/>
|
||||
<field name="standard_price">14.0</field>
|
||||
<field name="list_price">15.8</field>
|
||||
<field name="detailed_type">consu</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit"/>
|
||||
<field name="default_code">E-COM08</field>
|
||||
<field name="image_1920" type="base64" file="product/static/img/product_product_7-image.png"/>
|
||||
<field name="available_in_pos" eval="True"/>
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="product_product_8" model="product.product">
|
||||
<field name="name">Large Desk</field>
|
||||
<field name="categ_id" ref="product.product_category_1"/>
|
||||
<field name="standard_price">1299.0</field>
|
||||
<field name="list_price">1799.0</field>
|
||||
<field name="detailed_type">consu</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit"/>
|
||||
<field name="default_code">E-COM09</field>
|
||||
<field name='weight'>9.54</field>
|
||||
<field name="image_1920" type="base64" file="product/static/img/product_product_8-image.png"/>
|
||||
<field name="available_in_pos" eval="True"/>
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_desks')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="product_product_9" model="product.product">
|
||||
<field name="name">Pedal Bin</field>
|
||||
<field name="categ_id" ref="product.product_category_1"/>
|
||||
<field name="standard_price">10.0</field>
|
||||
<field name="list_price">47.0</field>
|
||||
<field name="detailed_type">consu</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit"/>
|
||||
<field name="default_code">E-COM10</field>
|
||||
<field name="image_1920" type="base64" file="product/static/img/product_product_9-image.jpg"/>
|
||||
<field name="available_in_pos" eval="True"/>
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="product_product_10" model="product.product">
|
||||
<field name="name">Cabinet with Doors</field>
|
||||
<field name="categ_id" ref="product.product_category_1"/>
|
||||
<field name="standard_price">120.50</field>
|
||||
<field name="list_price">140</field>
|
||||
<field name="detailed_type">consu</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit"/>
|
||||
<field name="default_code">E-COM11</field>
|
||||
<field name="image_1920" type="base64" file="product/static/img/product_product_10-image.jpg"/>
|
||||
<field name="available_in_pos" eval="True"/>
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="product_product_11_product_template" model="product.template">
|
||||
<field name="name">Conference Chair</field>
|
||||
<field name="categ_id" ref="product.product_category_1"/>
|
||||
<field name="standard_price">28</field>
|
||||
<field name="list_price">33</field>
|
||||
<field name="detailed_type">consu</field>
|
||||
<field name="uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit"/>
|
||||
<field name="image_1920" type="base64" file="product/static/img/product_product_11-image.png"/>
|
||||
</record>
|
||||
|
||||
<!-- the product template attribute lines have to be defined before creating the variants -->
|
||||
<record id="product_11_attribute_1_product_template_attribute_line" model="product.template.attribute.line">
|
||||
<field name="product_tmpl_id" ref="product_product_11_product_template"/>
|
||||
<field name="attribute_id" ref="product_attribute_1"/>
|
||||
<field name="value_ids" eval="[(6,0,[ref('point_of_sale.product_attribute_value_1'), ref('point_of_sale.product_attribute_value_2')])]"/>
|
||||
</record>
|
||||
|
||||
<function model="ir.model.data" name="_update_xmlids">
|
||||
<value model="base" eval="[{
|
||||
'xml_id': 'point_of_sale.product_11_attribute_1_value_1',
|
||||
'record': obj().env.ref('point_of_sale.product_11_attribute_1_product_template_attribute_line').product_template_value_ids[0],
|
||||
'noupdate': True,
|
||||
}, {
|
||||
'xml_id': 'point_of_sale.product_11_attribute_1_value_2',
|
||||
'record': obj().env.ref('point_of_sale.product_11_attribute_1_product_template_attribute_line').product_template_value_ids[1],
|
||||
'noupdate': True,
|
||||
}]"/>
|
||||
</function>
|
||||
|
||||
<function model="ir.model.data" name="_update_xmlids">
|
||||
<value model="base" eval="[{
|
||||
'xml_id': 'point_of_sale.product_product_11',
|
||||
'record': obj().env.ref('point_of_sale.product_product_11_product_template')._get_variant_for_combination(obj().env.ref('point_of_sale.product_11_attribute_1_value_1')),
|
||||
'noupdate': True,
|
||||
}, {
|
||||
'xml_id': 'point_of_sale.product_product_11b',
|
||||
'record': obj().env.ref('point_of_sale.product_product_11_product_template')._get_variant_for_combination(obj().env.ref('point_of_sale.product_11_attribute_1_value_2')),
|
||||
'noupdate': True,
|
||||
},]"/>
|
||||
</function>
|
||||
|
||||
<record id="product_product_11" model="product.product">
|
||||
<field name="default_code">E-COM12</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="available_in_pos" eval="True"/>
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_chairs')])]"/>
|
||||
</record>
|
||||
<record id="product_product_11b" model="product.product">
|
||||
<field name="default_code">E-COM13</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="available_in_pos" eval="True"/>
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_chairs')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="point_of_sale.product_4_attribute_1_value_2" model="product.template.attribute.value">
|
||||
<field name="price_extra">50.40</field>
|
||||
</record>
|
||||
|
||||
<record id="point_of_sale.product_11_attribute_1_value_2" model="product.template.attribute.value">
|
||||
<field name="price_extra">6.40</field>
|
||||
</record>
|
||||
|
||||
<record id="product_template_attribute_exclusion_1" model="product.template.attribute.exclusion">
|
||||
<field name="product_tmpl_id" ref="point_of_sale.product_product_4_product_template" />
|
||||
<field name="value_ids" eval="[(6, 0, [ref('point_of_sale.product_4_attribute_2_value_2')])]"/>
|
||||
</record>
|
||||
<record id="product_template_attribute_exclusion_2" model="product.template.attribute.exclusion">
|
||||
<field name="product_tmpl_id" ref="point_of_sale.product_product_11_product_template" />
|
||||
<field name="value_ids" eval="[(6, 0, [ref('point_of_sale.product_11_attribute_1_value_1')])]"/>
|
||||
</record>
|
||||
<record id="product_template_attribute_exclusion_3" model="product.template.attribute.exclusion">
|
||||
<field name="product_tmpl_id" ref="point_of_sale.product_product_11_product_template" />
|
||||
<field name="value_ids" eval="[(6, 0, [ref('point_of_sale.product_11_attribute_1_value_2')])]"/>
|
||||
</record>
|
||||
|
||||
<!--
|
||||
The "Customizable Desk's Aluminium" attribute value will excude:
|
||||
- The "Customizable Desk's Black" attribute
|
||||
- The "Office Chair's Steel" attribute
|
||||
-->
|
||||
<record id="product_4_attribute_1_value_2" model="product.template.attribute.value">
|
||||
<field name="exclude_for" eval="[(6, 0, [ref('point_of_sale.product_template_attribute_exclusion_1'), ref('point_of_sale.product_template_attribute_exclusion_2')])]" />
|
||||
</record>
|
||||
<!--
|
||||
The "Customizable Desk's Steel" attribute value will excude:
|
||||
- The "Office Chair's Aluminium" attribute
|
||||
-->
|
||||
<record id="product_4_attribute_1_value_1" model="product.template.attribute.value">
|
||||
<field name="exclude_for" eval="[(6, 0, [ref('point_of_sale.product_template_attribute_exclusion_3')])]" />
|
||||
</record>
|
||||
|
||||
<record id="product_product_12" model="product.product">
|
||||
<field name="name">Office Chair Black</field>
|
||||
<field name="categ_id" ref="product.product_category_1"/>
|
||||
<field name="standard_price">180</field>
|
||||
<field name="list_price">120.50</field>
|
||||
<field name="detailed_type">consu</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit"/>
|
||||
<field name="default_code">FURN_0269</field>
|
||||
<field name="image_1920" type="base64" file="product/static/img/product_product_12-image.png"/>
|
||||
<field name="available_in_pos" eval="True"/>
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_chairs')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="product_product_13" model="product.product">
|
||||
<field name="name">Corner Desk Left Sit</field>
|
||||
<field name="categ_id" ref="product.product_category_1"/>
|
||||
<field name="standard_price">78.0</field>
|
||||
<field name="list_price">85.0</field>
|
||||
<field name="detailed_type">consu</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit"/>
|
||||
<field name="default_code">FURN_1118</field>
|
||||
<field name="image_1920" type="base64" file="product/static/img/product_product_13-image.jpg"/>
|
||||
<field name="available_in_pos" eval="True"/>
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_desks')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="product_product_16" model="product.product">
|
||||
<field name="name">Drawer Black</field>
|
||||
<field name="categ_id" ref="product.product_category_1"/>
|
||||
<field name="standard_price">20.0</field>
|
||||
<field name="list_price">25.0</field>
|
||||
<field name="detailed_type">consu</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit"/>
|
||||
<field name="default_code">FURN_8900</field>
|
||||
<field name="image_1920" type="base64" file="product/static/img/product_product_16-image.jpg"/>
|
||||
<field name="available_in_pos" eval="True"/>
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="product_product_20" model="product.product">
|
||||
<field name="name">Flipover</field>
|
||||
<field name="categ_id" ref="product.product_category_1"/>
|
||||
<field name="standard_price">1700.0</field>
|
||||
<field name="list_price">1950.0</field>
|
||||
<field name="detailed_type">consu</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit"/>
|
||||
<field name="default_code">FURN_9001</field>
|
||||
<field name="image_1920" type="base64" file="product/static/img/product_product_20-image.png"/>
|
||||
<field name="available_in_pos" eval="True"/>
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="product_product_22" model="product.product">
|
||||
<field name="name">Desk Stand with Screen</field>
|
||||
<field name="categ_id" ref="product.product_category_1"/>
|
||||
<field name="standard_price">2010.0</field>
|
||||
<field name="list_price">2100.0</field>
|
||||
<field name="detailed_type">consu</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit"/>
|
||||
<field name="default_code">FURN_7888</field>
|
||||
<field name="image_1920" type="base64" file="product/static/img/product_product_22-image.png"/>
|
||||
<field name="available_in_pos" eval="True"/>
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="product_product_24" model="product.product">
|
||||
<field name="name">Individual Workplace</field>
|
||||
<field name="categ_id" ref="product.product_category_1"/>
|
||||
<field name="standard_price">876.0</field>
|
||||
<field name="list_price">885.0</field>
|
||||
<field name="detailed_type">consu</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit"/>
|
||||
<field name="default_code">FURN_0789</field>
|
||||
<field name="image_1920" type="base64" file="product/static/img/product_product_24-image.jpg"/>
|
||||
<field name="available_in_pos" eval="True"/>
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="product_product_25" model="product.product">
|
||||
<field name="name">Acoustic Bloc Screens</field>
|
||||
<field name="categ_id" ref="product.product_category_1"/>
|
||||
<field name="standard_price">287.0</field>
|
||||
<field name="list_price">295.0</field>
|
||||
<field name="detailed_type">consu</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit"/>
|
||||
<field name="default_code">FURN_6666</field>
|
||||
<field name="image_1920" type="base64" file="product/static/img/product_product_25-image.png"/>
|
||||
<field name="available_in_pos" eval="True"/>
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="product_product_27" model="product.product">
|
||||
<field name="name">Drawer</field>
|
||||
<field name="categ_id" ref="product.product_category_1"/>
|
||||
<field name="standard_price">100.0</field>
|
||||
<field name="list_price">110.50</field>
|
||||
<field name="detailed_type">consu</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit"/>
|
||||
<field name="description">Drawer with two routing possiblities.</field>
|
||||
<field name="default_code">FURN_8855</field>
|
||||
<field name="image_1920" type="base64" file="product/static/img/product_product_27-image.jpg"/>
|
||||
<field name="available_in_pos" eval="True"/>
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="consu_delivery_03" model="product.product">
|
||||
<field name="name">Four Person Desk</field>
|
||||
<field name="categ_id" ref="product.product_category_1"/>
|
||||
<field name="standard_price">2500.0</field>
|
||||
<field name="list_price">2350.0</field>
|
||||
<field name="detailed_type">consu</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit"/>
|
||||
<field name="description_sale">Four person modern office workstation</field>
|
||||
<field name="default_code">FURN_8220</field>
|
||||
<field name="image_1920" type="base64" file="product/static/img/product_product_d03-image.png"/>
|
||||
<field name="available_in_pos" eval="True"/>
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_desks')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="consu_delivery_02" model="product.product">
|
||||
<field name="name">Large Meeting Table</field>
|
||||
<field name="categ_id" ref="product.product_category_1"/>
|
||||
<field name="standard_price">4500.0</field>
|
||||
<field name="list_price">4000.0</field>
|
||||
<field name="detailed_type">consu</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit"/>
|
||||
<field name="description_sale">Conference room table</field>
|
||||
<field name="default_code">FURN_6741</field>
|
||||
<field name="image_1920" type="base64" file="product/static/img/product_product_46-image.jpg"/>
|
||||
<field name="available_in_pos" eval="True"/>
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="consu_delivery_01" model="product.product">
|
||||
<field name="name">Three-Seat Sofa</field>
|
||||
<field name="categ_id" ref="product.product_category_1"/>
|
||||
<field name="standard_price">1000</field>
|
||||
<field name="list_price">1500</field>
|
||||
<field name="detailed_type">consu</field>
|
||||
<field name="weight">0.01</field>
|
||||
<field name="uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit"/>
|
||||
<field name="description_sale">Three Seater Sofa with Lounger in Steel Grey Colour</field>
|
||||
<field name="default_code">FURN_8999</field>
|
||||
<field name="image_1920" type="base64" file="product/static/img/product_product_d01-image.jpg"/>
|
||||
<field name="available_in_pos" eval="True"/>
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="desk_organizer_combo_line" model="pos.combo.line">
|
||||
<field name="product_id" ref="desk_organizer"/>
|
||||
<field name="combo_price">0</field>
|
||||
</record>
|
||||
<record id="desk_pad_combo_line" model="pos.combo.line">
|
||||
<field name="product_id" ref="desk_pad"/>
|
||||
<field name="combo_price">0</field>
|
||||
</record>
|
||||
<record id="monitor_stand_combo_line" model="pos.combo.line">
|
||||
<field name="product_id" ref="monitor_stand"/>
|
||||
<field name="combo_price">2</field>
|
||||
</record>
|
||||
<record id="desk_accessories_combo" model="pos.combo">
|
||||
<field name="name">Desk Accessories Combo</field>
|
||||
<field name="combo_line_ids" eval="[(6, 0, [ref('desk_organizer_combo_line'), ref('desk_pad_combo_line'), ref('monitor_stand_combo_line')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="product_3_combo_line" model="pos.combo.line">
|
||||
<field name="product_id" ref="product_product_3"/>
|
||||
<field name="combo_price">0</field>
|
||||
</record>
|
||||
<record id="product_5_combo_line" model="pos.combo.line">
|
||||
<field name="product_id" ref="product_product_5"/>
|
||||
<field name="combo_price">0</field>
|
||||
</record>
|
||||
<record id="desks_combo" model="pos.combo">
|
||||
<field name="name">Desks Combo</field>
|
||||
<field name="combo_line_ids" eval="[(6, 0, [ref('product_3_combo_line'), ref('product_5_combo_line')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="product_11_combo_line" model="pos.combo.line">
|
||||
<field name="product_id" ref="product_product_11"/>
|
||||
<field name="combo_price">0</field>
|
||||
</record>
|
||||
<record id="product_11b_combo_line" model="pos.combo.line">
|
||||
<field name="product_id" ref="product_product_11b"/>
|
||||
<field name="combo_price">0</field>
|
||||
</record>
|
||||
<record id="product_12_combo_line" model="pos.combo.line">
|
||||
<field name="product_id" ref="product_product_12"/>
|
||||
<field name="combo_price">0</field>
|
||||
</record>
|
||||
<record id="chairs_combo" model="pos.combo">
|
||||
<field name="name">Chairs Combo</field>
|
||||
<field name="combo_line_ids" eval="[(6, 0, [ref('product_11_combo_line'), ref('product_11b_combo_line'), ref('product_12_combo_line')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="office_combo" model="product.product">
|
||||
<field name="available_in_pos">True</field>
|
||||
<field name="list_price">160</field>
|
||||
<field name="name">Office combo</field>
|
||||
<field name="type">combo</field>
|
||||
<field name="categ_id" ref="product.product_category_1"/>
|
||||
<field name="uom_id" ref="uom.product_uom_unit"/>
|
||||
<field name="uom_po_id" ref="uom.product_uom_unit"/>
|
||||
<field name="image_1920" type="base64" file="point_of_sale/static/img/office_combo.jpg"/>
|
||||
<field name="combo_ids" eval="[(6, 0, [ref('desks_combo'), ref('chairs_combo'), ref('desk_accessories_combo')])]"/>
|
||||
<field name="taxes_id" eval="[(5,)]"/> <!-- no taxes -->
|
||||
<field name="pos_categ_ids" eval="[(6, 0, [ref('pos_category_miscellaneous')])]"/>
|
||||
</record>
|
||||
</odoo>
|
200
data/point_of_sale_onboarding_main_config.xml
Normal file
200
data/point_of_sale_onboarding_main_config.xml
Normal file
@ -0,0 +1,200 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record model="pos.config" id="pos_config_main">
|
||||
<field name="iface_start_categ_id" ref="pos_category_desks" />
|
||||
<field name="start_category">True</field>
|
||||
</record>
|
||||
|
||||
<function model="ir.model.data" name="_update_xmlids">
|
||||
<value model="base" eval="[{
|
||||
'xml_id': 'point_of_sale.payment_method',
|
||||
'record': obj().env.ref('point_of_sale.pos_config_main')._get_payment_method('cash'),
|
||||
'noupdate': True,
|
||||
}]" />
|
||||
</function>
|
||||
<!-- Closed Session 1 -->
|
||||
|
||||
<record id="pos_closed_session_1" model="pos.session" forcecreate="False" context="{'onboarding_creation': True}">
|
||||
<field name="config_id" ref="pos_config_main" />
|
||||
<field name="user_id" ref="base.user_admin" />
|
||||
<field name="start_at" eval="(DateTime.today() + relativedelta(days=-1)).strftime('%Y-%m-%d %H:%M:%S')" />
|
||||
<field name="stop_at"
|
||||
eval="(DateTime.today() + relativedelta(days=-1, hours=1)).strftime('%Y-%m-%d %H:%M:%S')" />
|
||||
</record>
|
||||
|
||||
<record id="pos_closed_order_1_1" model="pos.order" forcecreate="False">
|
||||
<field name="session_id" ref="pos_closed_session_1" />
|
||||
<field name="company_id" ref="base.main_company" />
|
||||
<field name="name">ClosedDemo/0001</field>
|
||||
<field name="state">paid</field>
|
||||
<field name="amount_total">4.81</field>
|
||||
<field name="amount_tax">0.0</field>
|
||||
<field name="amount_paid">4.81</field>
|
||||
<field name="amount_return">0.0</field>
|
||||
<field name="pos_reference">Order 00000-001-1001</field>
|
||||
</record>
|
||||
|
||||
<record id="pos_closed_orderline_1_1_1" model="pos.order.line" forcecreate="False">
|
||||
<field name="product_id" ref="wall_shelf" />
|
||||
<field name="price_subtotal">1.98</field>
|
||||
<field name="price_subtotal_incl">1.98</field>
|
||||
<field name="price_unit">1.98</field>
|
||||
<field name="order_id" ref="pos_closed_order_1_1" />
|
||||
<field name="full_product_name">Wall Shelf</field>
|
||||
</record>
|
||||
|
||||
<record id="pos_closed_orderline_1_1_2" model="pos.order.line" forcecreate="False">
|
||||
<field name="product_id" ref="small_shelf" />
|
||||
<field name="price_subtotal">2.83</field>
|
||||
<field name="price_subtotal_incl">2.83</field>
|
||||
<field name="price_unit">2.83</field>
|
||||
<field name="order_id" ref="pos_closed_order_1_1" />
|
||||
<field name="full_product_name">Small Shelf</field>
|
||||
</record>
|
||||
|
||||
<record id="pos_payment_1" model="pos.payment" forcecreate="False">
|
||||
<field name="payment_method_id" ref="point_of_sale.payment_method" />
|
||||
<field name="pos_order_id" ref="pos_closed_order_1_1" />
|
||||
<field name="amount">4.81</field>
|
||||
</record>
|
||||
|
||||
<record id="pos_closed_order_1_2" model="pos.order" forcecreate="False">
|
||||
<field name="session_id" ref="pos_closed_session_1" />
|
||||
<field name="company_id" ref="base.main_company" />
|
||||
<field name="name">ClosedDemo/0002</field>
|
||||
<field name="state">paid</field>
|
||||
<field name="amount_total">2220.50</field>
|
||||
<field name="amount_tax">0.0</field>
|
||||
<field name="amount_paid">2220.50</field>
|
||||
<field name="amount_return">0.0</field>
|
||||
<field name="pos_reference">Order 00000-001-1002</field>
|
||||
</record>
|
||||
|
||||
<record id="pos_closed_orderline_1_2_1" model="pos.order.line" forcecreate="False">
|
||||
<field name="product_id" ref="product_product_12" />
|
||||
<field name="price_subtotal">120.50</field>
|
||||
<field name="price_subtotal_incl">120.5</field>
|
||||
<field name="price_unit">120.50</field>
|
||||
<field name="order_id" ref="pos_closed_order_1_2" />
|
||||
<field name="full_product_name">Office Chair Black</field>
|
||||
</record>
|
||||
|
||||
<record id="pos_closed_orderline_1_2_2" model="pos.order.line" forcecreate="False">
|
||||
<field name="product_id" ref="product_product_22" />
|
||||
<field name="price_subtotal">2100.0</field>
|
||||
<field name="price_subtotal_incl">2100.0</field>
|
||||
<field name="price_unit">2100.0</field>
|
||||
<field name="order_id" ref="pos_closed_order_1_2" />
|
||||
<field name="full_product_name">Desk Stand with Screen</field>
|
||||
</record>
|
||||
|
||||
<record id="pos_payment_2" model="pos.payment" forcecreate="False">
|
||||
<field name="payment_method_id" ref="point_of_sale.payment_method" />
|
||||
<field name="pos_order_id" ref="pos_closed_order_1_2" />
|
||||
<field name="amount">2220.50</field>
|
||||
</record>
|
||||
|
||||
<function model="pos.session" name="post_closing_cash_details" eval="[[ref('pos_closed_session_1')], 2225.31]" />
|
||||
|
||||
<function model="pos.session" name="update_closing_control_state_session"
|
||||
eval="[[ref('pos_closed_session_1')], '']" />
|
||||
|
||||
<function model="pos.session" name="action_pos_session_closing_control"
|
||||
eval="[[ref('pos_closed_session_1')]]" />
|
||||
|
||||
<!-- Closed Session 2 -->
|
||||
|
||||
<record id="pos_closed_session_2" model="pos.session" forcecreate="False" context="{'onboarding_creation': True}">
|
||||
<field name="config_id" ref="pos_config_main" />
|
||||
<field name="user_id" ref="base.user_admin" />
|
||||
<field name="start_at" eval="(DateTime.today() + relativedelta(hours=-3)).strftime('%Y-%m-%d %H:%M:%S')" />
|
||||
<field name="stop_at" eval="(DateTime.today() + relativedelta(hours=-2)).strftime('%Y-%m-%d %H:%M:%S')" />
|
||||
</record>
|
||||
|
||||
<record id="pos_closed_order_2_1" model="pos.order" forcecreate="False">
|
||||
<field name="session_id" ref="pos_closed_session_2" />
|
||||
<field name="company_id" ref="base.main_company" />
|
||||
<field name="name">ClosedDemo/0003</field>
|
||||
<field name="state">paid</field>
|
||||
<field name="amount_total">9.90</field>
|
||||
<field name="amount_tax">0.0</field>
|
||||
<field name="amount_paid">9.90</field>
|
||||
<field name="amount_return">0.0</field>
|
||||
<field name="pos_reference">Order 00000-002-1001</field>
|
||||
</record>
|
||||
|
||||
<record id="pos_closed_orderline_2_1_1" model="pos.order.line" forcecreate="False">
|
||||
<field name="name">Closed Orderline 2.1.1</field>
|
||||
<field name="product_id" ref="letter_tray" />
|
||||
<field name="price_subtotal">4.80</field>
|
||||
<field name="price_subtotal_incl">4.80</field>
|
||||
<field name="price_unit">4.80</field>
|
||||
<field name="order_id" ref="pos_closed_order_2_1" />
|
||||
<field name="full_product_name">Letter Tray</field>
|
||||
</record>
|
||||
|
||||
<record id="pos_closed_orderline_2_1_2" model="pos.order.line" forcecreate="False">
|
||||
<field name="name">Closed Orderline 2.1.2</field>
|
||||
<field name="product_id" ref="desk_organizer" />
|
||||
<field name="price_subtotal">5.10</field>
|
||||
<field name="price_subtotal_incl">5.10</field>
|
||||
<field name="price_unit">5.10</field>
|
||||
<field name="order_id" ref="pos_closed_order_2_1" />
|
||||
<field name="full_product_name">Desk Organizer</field>
|
||||
</record>
|
||||
|
||||
<record id="pos_payment_3" model="pos.payment" forcecreate="False">
|
||||
<field name="payment_method_id" ref="point_of_sale.payment_method" />
|
||||
<field name="pos_order_id" ref="pos_closed_order_2_1" />
|
||||
<field name="amount">9.90</field>
|
||||
</record>
|
||||
|
||||
<record id="pos_closed_order_2_2" model="pos.order" forcecreate="False">
|
||||
<field name="session_id" ref="pos_closed_session_2" />
|
||||
<field name="company_id" ref="base.main_company" />
|
||||
<field name="name">ClosedDemo/0004</field>
|
||||
<field name="state">paid</field>
|
||||
<field name="amount_total">8.36</field>
|
||||
<field name="amount_tax">0.0</field>
|
||||
<field name="amount_paid">8.36</field>
|
||||
<field name="amount_return">0.0</field>
|
||||
<field name="pos_reference">Order 00000-002-1002</field>
|
||||
</record>
|
||||
|
||||
<record id="pos_closed_orderline_2_2_1" model="pos.order.line" forcecreate="False">
|
||||
<field name="name">Closed Orderline 2.2.1</field>
|
||||
<field name="product_id" ref="magnetic_board" />
|
||||
<field name="price_subtotal">1.98</field>
|
||||
<field name="price_subtotal_incl">1.98</field>
|
||||
<field name="price_unit">1.98</field>
|
||||
<field name="order_id" ref="pos_closed_order_2_2" />
|
||||
<field name="full_product_name">Magnetic Board</field>
|
||||
</record>
|
||||
|
||||
<record id="pos_closed_orderline_2_2_2" model="pos.order.line" forcecreate="False">
|
||||
<field name="name">Closed Orderline 2.1.2</field>
|
||||
<field name="product_id" ref="monitor_stand" />
|
||||
<field name="price_subtotal">6.38</field>
|
||||
<field name="price_subtotal_incl">6.38</field>
|
||||
<field name="qty">2</field>
|
||||
<field name="price_unit">3.19</field>
|
||||
<field name="order_id" ref="pos_closed_order_2_2" />
|
||||
<field name="full_product_name">Monitor Stand</field>
|
||||
</record>
|
||||
|
||||
<record id="pos_payment_4" model="pos.payment" forcecreate="False">
|
||||
<field name="payment_method_id" ref="point_of_sale.payment_method" />
|
||||
<field name="pos_order_id" ref="pos_closed_order_2_2" />
|
||||
<field name="amount">8.36</field>
|
||||
</record>
|
||||
|
||||
<function model="pos.session" name="post_closing_cash_details"
|
||||
eval="[[ref('pos_closed_session_2')], 2243.57]" />
|
||||
|
||||
<function model="pos.session" name="update_closing_control_state_session"
|
||||
eval="[[ref('pos_closed_session_2')], '']" />
|
||||
|
||||
<function model="pos.session" name="action_pos_session_closing_control"
|
||||
eval="[[ref('pos_closed_session_2')]]" />
|
||||
|
||||
</odoo>
|
BIN
doc/barcode_test_sheet.pdf
Normal file
BIN
doc/barcode_test_sheet.pdf
Normal file
Binary file not shown.
525
doc/barcode_test_sheet.svg
Normal file
525
doc/barcode_test_sheet.svg
Normal file
@ -0,0 +1,525 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="744.09448819"
|
||||
height="1052.3622047"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.48.3.1 r9886"
|
||||
sodipodi:docname="barcode_test_sheet.svg">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="1"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.86706515"
|
||||
inkscape:cx="381.55817"
|
||||
inkscape:cy="505.31292"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1855"
|
||||
inkscape:window-height="1176"
|
||||
inkscape:window-x="65"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<g
|
||||
id="g4158"
|
||||
transform="matrix(0.92675002,0,0,0.92675002,27.597551,38.360312)">
|
||||
<text
|
||||
sodipodi:linespacing="125%"
|
||||
id="text2985"
|
||||
y="72.12545"
|
||||
x="140.86679"
|
||||
style="font-size:46.51613998px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium"
|
||||
y="72.12545"
|
||||
x="140.86679"
|
||||
id="tspan2987"
|
||||
sodipodi:role="line">BARCODE TEST SHEET</tspan></text>
|
||||
<g
|
||||
transform="translate(0,-5.3405762e-5)"
|
||||
id="g3347">
|
||||
<image
|
||||
y="124.14642"
|
||||
x="388.17426"
|
||||
id="image3083"
|
||||
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOYAAACYCAIAAAB/IAJdAAAAA3NCSVQICAjb4U/gAAADqUlEQVR4 nO3a226jMABFUYjy/7/MPCChDBfHARM4zVpP1dQY4u66JEw/DEO3S9/3XdeNh79+vRwz2hr5OmZr 5JH5t0YuX0X51W0dW3PNNa9xOWf5au8wsrxu9Ud95HHkYPg+yRJGsoSRLGEkSxjJEkayhJEsYSRL GMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJEkayhJEsYSRLGMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJ EkayhJEsYSRLGMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJEkayhJEsYSRLGMkSRrKEkSxhJEsYyRJG soSRLGEkSxjJEkayhJEsYSRLGMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJEkayhJEsYSRLGMkSRrKE kSxhJEsYyRJGsoSRLGEkSxjJEkayhJEsYSRLGMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJEkayhJEs YSRLGMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJEkayhJEsYSRLGMkSRrKEkSxhJEsYyRJGsoSRLGEk SxjJEkayhJEsYSRLGMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJEkayhJEsYSRLGMkSRrKEkSxh+mEY dh7Z913XjYe/fv3LrMlS8zWxyxJGsjuNGwbf91z911Z/1GY/18oJ689+cOTb7LZmPtJr/ZrcfOTu 1SucseaQlV221f6xnKdm5vqznzHy1Nn6vq9fk5SRrYzzry7RzHyXbd7r9HszXVP9b3bN/EdGlq/k 0y250uu0W2tSv3oXjvxo9d4ahmE6b/mP58ou2/BNbuUr3Hf2M0aOzthRVn8M5QurX71rR87sXr1h GGa/z6tT/Zds2y12+TpfP+nYOqp+/rYjZ4esXvzk0zkLlmtSv3rXjlx1/F3QMtzZgMfse1d9jvi1 t1zND2HScPUK2+2j+cl2uEmvl/Bh2Zat+4TH64gLruvzs599C3vSOqz+Yf1LvZ60estwn93ehdt9 ez5zk1vYJmrW5C9lWqNVJ5OnW4IjR31kudFWvqe5v/NWb/lE47n6jbOvo3xZhbOfMfLL7n97fRNb j9/WH9j+mgtTtsUW5hzNN6PVMx2/iMLO93ba798tfPp6d398tjyqfqFu+I9vv7XD1uY6Ofd/ci0/ o/5ZhR/2lvrVu3ZkK7PPX7ee2px1YzA9Mn77S/Nrlj/+1UdNlat37ciGCncCMyfuslsP/e7mazey y22j8Pi3fvWuHdlq9cYZap6H/wMqFm5yx9XfMwAAAABJRU5ErkJggg== "
|
||||
height="107.96206"
|
||||
width="163.36363" />
|
||||
<rect
|
||||
style="fill:none;stroke:#c3c3c3;stroke-width:1.07903957;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
id="rect3086"
|
||||
width="417.54495"
|
||||
height="126.4052"
|
||||
x="141.08452"
|
||||
y="114.51707"
|
||||
rx="2.0839903"
|
||||
ry="2.0839903" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:27.53050041px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
x="201.46712"
|
||||
y="147.73848"
|
||||
id="text3856"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3858"
|
||||
x="201.46712"
|
||||
y="147.73848"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">CASHIER</tspan></text>
|
||||
<text
|
||||
sodipodi:linespacing="125%"
|
||||
id="text3860"
|
||||
y="182.10147"
|
||||
x="154.5717"
|
||||
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium"
|
||||
y="182.10147"
|
||||
x="154.5717"
|
||||
id="tspan3862"
|
||||
sodipodi:role="line">Prefix: 40</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
x="154.5717"
|
||||
y="200.85837"
|
||||
id="text3149"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3151"
|
||||
x="154.5717"
|
||||
y="200.85837"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">ID: 4447190000</tspan></text>
|
||||
</g>
|
||||
<g
|
||||
transform="translate(0,0.97855377)"
|
||||
id="g3335">
|
||||
<g
|
||||
id="g3173"
|
||||
transform="translate(0,-3.2620699)">
|
||||
<text
|
||||
sodipodi:linespacing="125%"
|
||||
id="text3153"
|
||||
y="341.12738"
|
||||
x="154.5717"
|
||||
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium"
|
||||
y="341.12738"
|
||||
x="154.5717"
|
||||
id="tspan3155"
|
||||
sodipodi:role="line">Prefix: 42</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
x="154.5717"
|
||||
y="359.88428"
|
||||
id="text3157"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3159"
|
||||
x="154.5717"
|
||||
y="359.88428"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">ID: 1182180000</tspan></text>
|
||||
</g>
|
||||
<g
|
||||
id="g3954"
|
||||
transform="translate(0,-18.756902)">
|
||||
<rect
|
||||
style="fill:none;stroke:#c3c3c3;stroke-width:1.07903957;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
id="rect3876"
|
||||
width="417.54495"
|
||||
height="126.4052"
|
||||
x="141.08452"
|
||||
y="284.96024"
|
||||
rx="2.0839903"
|
||||
ry="2.0839903" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:27.53050041px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
x="209.6223"
|
||||
y="318.18164"
|
||||
id="text3878"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3880"
|
||||
x="209.6223"
|
||||
y="318.18164"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">CLIENT</tspan></text>
|
||||
<image
|
||||
width="162.31204"
|
||||
height="107.26709"
|
||||
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOYAAACYCAIAAAB/IAJdAAAAA3NCSVQICAjb4U/gAAADyUlEQVR4 nO3a3Y6qShSFUTC+/yvXuSAxbCgQsPiZp8e46mgJq/ETxe6+lNId0vd913XDw8c/z+8d3zIYP2p8 y9LKLbcvbXN8y5Zt/jLn0jHZcnzm29z+m961cmn+vY/a5fXLg+F6kiWMZAkjWcJIljCSJYxkCSNZ wkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJI ljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYw kiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIl jGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxk CSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkj WcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nC 9KWUg4/s+67rhoePf/7LHJO55sfEWZYwj0t2eCG2XXmGe/f+l72rt7Z6U5s8r1832LbX+ZqlAc6b 85qpbln59SDs7afvN31MrZxlm5w/+r6fb6d644H9Huu1euOpcx6eqnrj0n7vXdnKsP31Iz+YnmWb TzZ+3Wx/wqoOrJzvfeml3HDO36ear1wa/saV62fovafYUspnv+tv8pWzbMPPA5NNjS8bL1Dd+9gt c36dqnrX+pNy78qJw8etlDJ53VY39U+ybZ+kjb9nGWm1cpfmc/5o6RwzfyHdu3LX8NvNw50seE3u a/J8XPC8bjH5bavXPdfP+XWqXG0TGm92fJTezXf2BJ8PRtsvzy/wzKkea3Je/yT6mqw41e0vjI27 vnjO/0evJx20+eeEd3f0vWnXx/OlC52TVA/f8P6yPsCPc64fk8NTRTt8GbfkffYZ5eJYu9ULiJU+ zp7z2FQpzqtocj4tpbyrdzSZY7zNJz8fKXP+NfNYhx/qf7BtuL+HR5Ay55OdcYpdOY+8Ss1n6Y9z pHTwqDmXvgGdl3HvypNMvtKa7/GU/+S695uB7Qf9yjkPpDD/hn/JvStbmcdaPSxnfTDoVn/PK783 +GXZBdcTS/utfoO7dPV218qGtl9RPO7/ZZuovkBv/5vcrqmqK5c2e+PKVu9Uwxa2PEf/AXnknj+O sxSLAAAAAElFTkSuQmCC "
|
||||
id="image3951"
|
||||
x="388.17426"
|
||||
y="294.58957" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
transform="translate(0,0.3261795)"
|
||||
id="g3322">
|
||||
<rect
|
||||
style="fill:none;stroke:#c3c3c3;stroke-width:1.07903957;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
id="rect3964"
|
||||
width="417.54495"
|
||||
height="126.4052"
|
||||
x="141.08452"
|
||||
y="419.52063"
|
||||
rx="2.0839903"
|
||||
ry="2.0839903" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:27.53050041px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
x="209.6223"
|
||||
y="452.74203"
|
||||
id="text3966"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3968"
|
||||
x="209.6223"
|
||||
y="452.74203"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">WEIGHT</tspan></text>
|
||||
<image
|
||||
width="162.12962"
|
||||
height="107.14654"
|
||||
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOYAAACYCAIAAAB/IAJdAAAAA3NCSVQICAjb4U/gAAAERElEQVR4 nO3a0ZKaQBRFUbX8/182D1QZInRzadDxTNZ6mjgNNrhBYHJ9PB6XIdfr9XK5TIvPf57/drJ8/ayR rVdaG9Wfc+Vd+vPc+3N/hv3XK68st2i5Xa09XNnS+sjKthfdjiwMnydZwkiWMJIljGQJI1nCSJYw kiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIl jGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxk CSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkj WcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nC SJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiW MJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCS Jcz18XgMLnm9Xi6XafH5z/8z+2Tp9H3iLEuYDyU7HV7njuT/dH/+9NLKid9o5/Zan+fYFnW+vFrT 27uv+hPb3AnLt1su0prSO/beWeVcr6XL1Ns0dLnNqy+OzeOskfV5Dm9RZ8BZe2N1YgfXWXlx11u/ Y2TftFTlM7rP/zFv/B378eDIp+I8V89enUO5MpMjXz7P9S/nP59V/3zWOiX317k6/9aw5TqPj9z0 eDyea+vfpd1Wt/ny7y3eN6jP88jX9wdu8Jfzn2wuWD84W1cOm8Navz1rZN/LfmidcW/H36nz9pUP oz7yUs6uuLa5dx+cB5/vvOmRWeuAb52ex0bunVL/W/Se8uDw+Dw3r1MHTnVjs5qvpP6m9QuG7/lu HNa5Trg3F/oVKrfSR05ge5dt3awMH5DPj7b+0KA/n+/xcrZ+7qhmsm/6JjpdfZ6dMZuLd+5yBuxa ydgHsbxsnS4N407Gy3BXkm3d6KyuqzLyTSrzXH5Cex+Cdt5ifoabBlT2SSWmutZGtVb4+UxP7+Sf v34979EGbl8+aWye9RuIuoMnv039Ge66AVruqy//lCcvzw3+XhjsvSH4Ke+YZ+dR/DfvijFBW9S6 071fylcCP644z19T2wc25GsvZzvnpnvrF1/rxHl2rvZWL3lbTxuK77V6ifmZaPpH+8vlZmeeYyPH ptqa8C3lnPTjj5OW6xz7dlr9Q93YrFoP7Vfz6s+kOM+DI/uWl63r095c0VnP8A8+M9/cF8XHN8Xn WfUT6nCvm2uo7Lf6lu6af32eu7aoo36X8jv/i/fqAXrwBrm1zr0rKa6heJzXt3TXE4P6PI/vk/lS lc/oD6RX6TSeNJuDAAAAAElFTkSuQmCC "
|
||||
id="image4065"
|
||||
x="387.35876"
|
||||
y="429.14993" />
|
||||
<g
|
||||
transform="translate(0,140.26901)"
|
||||
id="g3179">
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
x="154.5717"
|
||||
y="341.12738"
|
||||
id="text3181"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3183"
|
||||
x="154.5717"
|
||||
y="341.12738"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">Prefix: 21</tspan></text>
|
||||
<text
|
||||
sodipodi:linespacing="125%"
|
||||
id="text3185"
|
||||
y="359.88428"
|
||||
x="154.5717"
|
||||
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium"
|
||||
y="359.88428"
|
||||
x="154.5717"
|
||||
id="tspan3187"
|
||||
sodipodi:role="line">ID: 12345</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
x="154.5717"
|
||||
y="379.4567"
|
||||
id="text3189"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3191"
|
||||
x="154.5717"
|
||||
y="379.4567"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">Value: 67.89Kg</tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
transform="translate(0,-1.9572678)"
|
||||
id="g3308">
|
||||
<rect
|
||||
ry="2.0839903"
|
||||
rx="2.0839903"
|
||||
y="574.46893"
|
||||
x="141.08452"
|
||||
height="126.4052"
|
||||
width="417.54495"
|
||||
id="rect4081"
|
||||
style="fill:none;stroke:#c3c3c3;stroke-width:1.07903957;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<g
|
||||
id="g3296">
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:27.53050041px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
x="195.7585"
|
||||
y="607.69037"
|
||||
id="text4083"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan4085"
|
||||
x="195.7585"
|
||||
y="607.69037"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">DISCOUNT</tspan></text>
|
||||
<image
|
||||
y="584.09827"
|
||||
x="385.72772"
|
||||
id="image4202"
|
||||
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOYAAACYCAIAAAB/IAJdAAAAA3NCSVQICAjb4U/gAAADsElEQVR4 nO3aXXOiMACGUej4//8ye+EMZYHECBF96zlXHTd8iE8jYTtO0zQcMo7jMAz3zUs/b8ffLUcuXymN bHn92ZHtR3l2ZOnnpfazOvauS3toP3rLyPr5tGx1wM+ZjeF6kiWMZAkjWcJIljCSJYxkCSNZwkiW MJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCS JYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWM ZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJ I1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZ wkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJI ljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCjNM0 HdxyHIdhuG++/PmbuSZb3a+JWZYwH5fs/RfxL42kr9vuq6/4UmvZZ0qFZ3pdbVu5II0jSyezO777 0XdP4Fg549h0m7ozy75i/mjZZ0qFHXst7W0cx92RjZufPPrJkc+e1XKT3fe4sp5lr+81pb/zV2be wzyXzJ9TaXZZvl45gZbJqf3oZ0ZWBtdN0zTvrf6FvDPLvmKRa+E8W16K+nf36l+Xi+7XHf3kyIeD 6/tZpb/7Zv+bZd9yS9A4kQSN3FWaOe6zy3ZOumYhsT36s+fZ3eo3c3vE32TfteRi5cDlal8qvcKq qi4TX+U+4bb7ahd6fZfLrvwcVq+HBts9bMO9rUb0pdeOtiFWVj/v1fFz34Z7G46+yfrt+SdcuOu1 L1meUl+Qbc/h2Jr98IltHyMsD939mvy4Jfhk86p5tZquu+bKVxZqQ785a/Xc4PfGoPJA+OTfzfTd 5/dYfU5vPJN3Ka0p9//Dlvcq3Ql8j8pv7K3yNPvMM+Hu+0xXeq5ZuSwPr1Vp2931e+PRD5xnXw8f 2H3cX3L9ecuPpMu902on9Rn64dHbR5buWQ/Hvb1t3d2JG4PrlJ5itvzJ1e74px6Lth/9qfOsn2q7 9nt3s+ylSuvrMzts32f3kaWjH3hT901atv0Hm+wpnHOLKX0AAAAASUVORK5CYII= "
|
||||
height="108.34499"
|
||||
width="163.94308"
|
||||
inkscape:transform-center-x="-24.4145"
|
||||
inkscape:transform-center-y="6.3942733" />
|
||||
<g
|
||||
transform="translate(0,296.03285)"
|
||||
id="g3210">
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
x="154.5717"
|
||||
y="341.12738"
|
||||
id="text3212"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3214"
|
||||
x="154.5717"
|
||||
y="341.12738"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">Prefix: 44</tspan></text>
|
||||
<text
|
||||
sodipodi:linespacing="125%"
|
||||
id="text3216"
|
||||
y="359.88428"
|
||||
x="154.5717"
|
||||
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium"
|
||||
y="359.88428"
|
||||
x="154.5717"
|
||||
id="tspan3218"
|
||||
sodipodi:role="line">ID: 11111</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
x="154.5717"
|
||||
y="379.4567"
|
||||
id="text3220"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3222"
|
||||
x="154.5717"
|
||||
y="379.4567"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">Value: 25%</tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
transform="translate(0,-0.97866058)"
|
||||
id="g3283">
|
||||
<rect
|
||||
style="fill:none;stroke:#c3c3c3;stroke-width:1.07903957;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
id="rect4115"
|
||||
width="417.54495"
|
||||
height="126.4052"
|
||||
x="141.08452"
|
||||
y="726.15521"
|
||||
rx="2.0839903"
|
||||
ry="2.0839903" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:27.53050041px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
x="212.88437"
|
||||
y="759.37659"
|
||||
id="text4117"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan4119"
|
||||
x="212.88437"
|
||||
y="759.37659"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">PRICE</tspan></text>
|
||||
<image
|
||||
width="163.36363"
|
||||
height="107.96206"
|
||||
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOYAAACYCAIAAAB/IAJdAAAAA3NCSVQICAjb4U/gAAAD70lEQVR4 nO3Z3XKbOhiGUezJ/d8yPfBsbwaB+Phz/aZrHaWukDB+goE8xnEcDnk8HsMwvDaf/tz+7/SVl3bR drbEkWvHZG3O/gytyvxrq6yt265emWfvyMq7K3qe2Rg+T7KEkSxhJEsYyRJGsoSRLGEkSxjJEkay hJEsYSRLGMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJEkayhJEsYSRLGMkSRrKEkSxhJEsYyRJGsoSR LGEkSxjJEkayhJEsYSRLGMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJEkayhJEsYSRLGMkSRrKEkSxh JEsYyRJGsoSRLGEkSxjJEkayhJEsYSRLGMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJEkayhJEsYSRL GMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJEkayhJEsYSRLGMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJ EkayhJEsYSRLGMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJEkayhJEsYSRLGMkSRrKEkSxhJEsYyRJG soSRLGEkSxjJEkayhJEsYSRLGMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJEkayhJEsYSRLmMc4jge3 fDyGYXhtPv35X+aYtC4/Js6yhJHsX/M65bDXz/un2RG85ButPufvG9lX6fXYWp0v37VFK4M/c0wq 2z4XV118Ze/yi3PWX6zP+W0jN21uVT9Q9Zl37Wr9nV57TCrv8We6xrvx9/bnz7XTGTp70w5bWz1l ZGvvZzmbdnO5yvyVXa33cGE54zi+Z+vfpT2n2yz+fED7Tqb/nB7ZxZ3rfE99/8jzdn19HxhTVO/h qnLGcZydERYPxXOt6DavA3twctv6aeP7Rw7/fSSzD6YzrDLn21U3c/UebiqnDXc24Ge4gYeRd9i8 Tt31pf/ytZ9U5zrhlmTPq/+Opow8pp2/jaze69r83xnu7Gz93tWPPpdtD9Dil8hiBykj79O5Wths bnp3cezC429prxOOn2V3XXSv3ZC1AzZ988gLb2HbSRbPiPXL6MUXL3w0VFn6klVuP8u+7/vWfrPb 19NH3qR+A7R3zm82e24wjuON17KzlfqD68cuZeTHrP3hY/jKva1bu1O8K9nNK4Hi5r9mZN0vqO28 zvnu53VBs/aHjZMH7uQ17uLzuS8f+UmL63YuedeeNszmLPZwUzmbj+Gei0NPnjzqO9056KEjr9JO fv4ksvbEY/Pd9d/pVeW0l63LO7b4+Oa9zeG1+wMWT2Cbq6eMrOj31zmAxS+uk/t/x294X/3O5/+z +oV/F9mVbLvJxh6HjOyrnDKPfSK7fhk6c94xsq/4rO0Pc0/7XuEpWE8AAAAASUVORK5CYII= "
|
||||
id="image4264"
|
||||
x="386.54324"
|
||||
y="734.96906" />
|
||||
<g
|
||||
transform="translate(0,442.82599)"
|
||||
id="g3224">
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
x="154.5717"
|
||||
y="341.12738"
|
||||
id="text3226"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3228"
|
||||
x="154.5717"
|
||||
y="341.12738"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">Prefix: 02</tspan></text>
|
||||
<text
|
||||
sodipodi:linespacing="125%"
|
||||
id="text3230"
|
||||
y="359.88428"
|
||||
x="154.5717"
|
||||
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium"
|
||||
y="359.88428"
|
||||
x="154.5717"
|
||||
id="tspan3232"
|
||||
sodipodi:role="line">ID: 99999</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
x="154.5717"
|
||||
y="379.4567"
|
||||
id="text3234"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3236"
|
||||
x="154.5717"
|
||||
y="379.4567"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">Value: 134.50€</tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g3269">
|
||||
<g
|
||||
id="g3061"
|
||||
transform="translate(0,284.6156)">
|
||||
<g
|
||||
id="g3063"
|
||||
transform="translate(0,154.94832)">
|
||||
<rect
|
||||
ry="2.0839903"
|
||||
rx="2.0839903"
|
||||
y="438.27753"
|
||||
x="141.08452"
|
||||
height="126.4052"
|
||||
width="417.54495"
|
||||
id="rect3065"
|
||||
style="fill:none;stroke:#c3c3c3;stroke-width:1.07903957;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<text
|
||||
sodipodi:linespacing="125%"
|
||||
id="text3067"
|
||||
y="471.49893"
|
||||
x="216.61778"
|
||||
style="font-size:27.53050041px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium"
|
||||
y="471.49893"
|
||||
x="216.61778"
|
||||
sodipodi:role="line"
|
||||
id="tspan3083">UNIT</tspan></text>
|
||||
<g
|
||||
transform="translate(0,156.57936)"
|
||||
id="g3071" />
|
||||
</g>
|
||||
</g>
|
||||
<image
|
||||
y="887.47076"
|
||||
x="386.54324"
|
||||
id="image3146"
|
||||
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOYAAACYCAIAAAB/IAJdAAAAA3NCSVQICAjb4U/gAAADz0lEQVR4
|
||||
nO3Z0ZKaMACGUdnx/V+ZXjBjGWKyEQjyt+fcdIsBY/ZTgZ3meX7sMk3T4/FYdq/9XI5frB9db18/
|
||||
Wh6nfJZyr/Yz9s+t/Yp65tw/22+N3Pe6asevhdQ/stPPkZ3hepIljGQJI1nCSJYwkiWMZAkjWcJI
|
||||
ljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYw
|
||||
kiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIl
|
||||
jGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxk
|
||||
CSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkj
|
||||
WcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nC
|
||||
SJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkzz
|
||||
PO/cc5oej8ey+/rn/5k1KZ2+Jj5lCSNZwjyXf5ZP7NKJ32v9XwrtkZupNg743ZH3nNWgV3p8WZaD
|
||||
9Oz4Uz7fCP1P0Rg5TVP5aG38d0fWfHFW/av36TofX5bXLm+PtvF8/TTuQuGUXl/W83y9zs3kX8d5
|
||||
bb9yZM0dZtWzev0jy2f/9bA18zy/jtb+mr3oXLZ/9u3zgc2j7cOuH71+5K1m1b96B9d5+e+isUvt
|
||||
OJv0336EDU/23I/Y0vruyfo45ZJdM7LmnrM6MnLQXbwy3M2AZ+2xU6Zy1iUXN7eO55RfYuM84Vnb
|
||||
53hD1/R6wbXjP+z4d2Dt2ut4uJvP9ddhf9Zn0LvPQtrPesrIt19heu3Uv3r71nlEPOsjr2fyrF0t
|
||||
/nrd1z7lH3cKe+dMj1+cjTbi91Jefi1XTsv209ekevl1w1OC8k084m39r+pfvY/WefT6b+4bzPNc
|
||||
PZc95clqGzevs3/k2y30O/FUbbTa/YCByV7jzucJ9zf6FuRujVsQb/56sdnnrHfbwbOF2nwag7+1
|
||||
ccfrGr2xf/XOWudye6dfb7b+vB13eq/HvZ1J+61f3ve+cuStZtW/emet845yytPW95NZ37PdOLfX
|
||||
49dkH83zst/cR6v0xVn1r96gdW7r/2PE33tYI/76Vc7p+G2ED15b9ysaMfKes9q3emetc1vjjura
|
||||
Hwkp3Xa7HcbMAAAAAElFTkSuQmCC
|
||||
"
|
||||
height="108.34499"
|
||||
width="163.94308" />
|
||||
<g
|
||||
id="g3255"
|
||||
transform="translate(0,598.58983)">
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
x="154.5717"
|
||||
y="351.81107"
|
||||
id="text3261"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3263"
|
||||
x="154.5717"
|
||||
y="351.81107"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">ID: 5449000000996</tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(0,-1,1,0,261.82237,1622.2884)"
|
||||
id="g4150">
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Droid Sans;-inkscape-font-specification:Droid Sans"
|
||||
x="618.97778"
|
||||
y="320.0275"
|
||||
id="text3357"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3359"
|
||||
x="618.97778"
|
||||
y="320.0275">This is a test sheet intended to help you test the codebar aquisition in OpenERP's Point of Sale module. </tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="618.97778"
|
||||
y="330.0275"
|
||||
id="tspan3363">The codes provided in this list are randomly chosen and are encoded in the EAN13 format. Their codes</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="618.97778"
|
||||
y="340.0275"
|
||||
id="tspan3367">and prefixes are not intended to match any specified standard. </tspan></text>
|
||||
<text
|
||||
sodipodi:linespacing="125%"
|
||||
id="text3369"
|
||||
y="349.54001"
|
||||
x="618.99207"
|
||||
style="font-size:6.53564215px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#b3b3b3;fill-opacity:1;stroke:none;font-family:Droid Sans;-inkscape-font-specification:Droid Sans"
|
||||
xml:space="preserve"><tspan
|
||||
id="tspan3375"
|
||||
y="349.54001"
|
||||
x="618.99207"
|
||||
sodipodi:role="line"
|
||||
style="fill:#b3b3b3;fill-opacity:1">This document is licensed by OpenERP S.A. under the Creative Commons CC BY 3.0 license htttp://www.creativecommons.org</tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 31 KiB |
1108
doc/barcode_test_sheet2.svg
Normal file
1108
doc/barcode_test_sheet2.svg
Normal file
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 70 KiB |
BIN
doc/barcode_test_sheet4.pdf
Normal file
BIN
doc/barcode_test_sheet4.pdf
Normal file
Binary file not shown.
1210
doc/barcode_test_sheet4.svg
Normal file
1210
doc/barcode_test_sheet4.svg
Normal file
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 75 KiB |
506
doc/barcode_test_sheet_new.svg
Normal file
506
doc/barcode_test_sheet_new.svg
Normal file
@ -0,0 +1,506 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="744.09448819"
|
||||
height="1052.3622047"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.48.3.1 r9886"
|
||||
sodipodi:docname="barcode_test_sheet.svg">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="1"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.86706515"
|
||||
inkscape:cx="381.55817"
|
||||
inkscape:cy="597.57819"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1301"
|
||||
inkscape:window-height="744"
|
||||
inkscape:window-x="1985"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<g
|
||||
id="g4158"
|
||||
transform="matrix(0.92675002,0,0,0.92675002,27.597551,38.360312)">
|
||||
<text
|
||||
sodipodi:linespacing="125%"
|
||||
id="text2985"
|
||||
y="72.12545"
|
||||
x="140.86679"
|
||||
style="font-size:46.51613998px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium"
|
||||
y="72.12545"
|
||||
x="140.86679"
|
||||
id="tspan2987"
|
||||
sodipodi:role="line">BARCODE TEST SHEET</tspan></text>
|
||||
<g
|
||||
transform="translate(0,-5.3405762e-5)"
|
||||
id="g3347">
|
||||
<image
|
||||
y="124.14642"
|
||||
x="388.17426"
|
||||
id="image3083"
|
||||
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOYAAACYCAIAAAB/IAJdAAAAA3NCSVQICAjb4U/gAAADqUlEQVR4 nO3a226jMABFUYjy/7/MPCChDBfHARM4zVpP1dQY4u66JEw/DEO3S9/3XdeNh79+vRwz2hr5OmZr 5JH5t0YuX0X51W0dW3PNNa9xOWf5au8wsrxu9Ud95HHkYPg+yRJGsoSRLGEkSxjJEkayhJEsYSRL GMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJEkayhJEsYSRLGMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJ EkayhJEsYSRLGMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJEkayhJEsYSRLGMkSRrKEkSxhJEsYyRJG soSRLGEkSxjJEkayhJEsYSRLGMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJEkayhJEsYSRLGMkSRrKE kSxhJEsYyRJGsoSRLGEkSxjJEkayhJEsYSRLGMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJEkayhJEs YSRLGMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJEkayhJEsYSRLGMkSRrKEkSxhJEsYyRJGsoSRLGEk SxjJEkayhJEsYSRLGMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJEkayhJEsYSRLGMkSRrKEkSxh+mEY dh7Z913XjYe/fv3LrMlS8zWxyxJGsjuNGwbf91z911Z/1GY/18oJ689+cOTb7LZmPtJr/ZrcfOTu 1SucseaQlV221f6xnKdm5vqznzHy1Nn6vq9fk5SRrYzzry7RzHyXbd7r9HszXVP9b3bN/EdGlq/k 0y250uu0W2tSv3oXjvxo9d4ahmE6b/mP58ou2/BNbuUr3Hf2M0aOzthRVn8M5QurX71rR87sXr1h GGa/z6tT/Zds2y12+TpfP+nYOqp+/rYjZ4esXvzk0zkLlmtSv3rXjlx1/F3QMtzZgMfse1d9jvi1 t1zND2HScPUK2+2j+cl2uEmvl/Bh2Zat+4TH64gLruvzs599C3vSOqz+Yf1LvZ60estwn93ehdt9 ez5zk1vYJmrW5C9lWqNVJ5OnW4IjR31kudFWvqe5v/NWb/lE47n6jbOvo3xZhbOfMfLL7n97fRNb j9/WH9j+mgtTtsUW5hzNN6PVMx2/iMLO93ba798tfPp6d398tjyqfqFu+I9vv7XD1uY6Ofd/ci0/ o/5ZhR/2lvrVu3ZkK7PPX7ee2px1YzA9Mn77S/Nrlj/+1UdNlat37ciGCncCMyfuslsP/e7mazey y22j8Pi3fvWuHdlq9cYZap6H/wMqFm5yx9XfMwAAAABJRU5ErkJggg== "
|
||||
height="107.96206"
|
||||
width="163.36363" />
|
||||
<rect
|
||||
style="fill:none;stroke:#c3c3c3;stroke-width:1.07903957;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
id="rect3086"
|
||||
width="417.54495"
|
||||
height="126.4052"
|
||||
x="141.08452"
|
||||
y="114.51707"
|
||||
rx="2.0839903"
|
||||
ry="2.0839903" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:27.53050041px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
x="201.46712"
|
||||
y="147.73848"
|
||||
id="text3856"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3858"
|
||||
x="201.46712"
|
||||
y="147.73848"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">CASHIER</tspan></text>
|
||||
<text
|
||||
sodipodi:linespacing="125%"
|
||||
id="text3860"
|
||||
y="182.10147"
|
||||
x="154.5717"
|
||||
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium"
|
||||
y="182.10147"
|
||||
x="154.5717"
|
||||
id="tspan3862"
|
||||
sodipodi:role="line">Prefix: 40</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
x="154.5717"
|
||||
y="200.85837"
|
||||
id="text3149"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3151"
|
||||
x="154.5717"
|
||||
y="200.85837"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">ID: 4447190000</tspan></text>
|
||||
</g>
|
||||
<g
|
||||
transform="translate(0,0.97855377)"
|
||||
id="g3335">
|
||||
<g
|
||||
id="g3173"
|
||||
transform="translate(0,-3.2620699)">
|
||||
<text
|
||||
sodipodi:linespacing="125%"
|
||||
id="text3153"
|
||||
y="341.12738"
|
||||
x="154.5717"
|
||||
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium"
|
||||
y="341.12738"
|
||||
x="154.5717"
|
||||
id="tspan3155"
|
||||
sodipodi:role="line">Prefix: 42</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
x="154.5717"
|
||||
y="359.88428"
|
||||
id="text3157"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3159"
|
||||
x="154.5717"
|
||||
y="359.88428"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">ID: 1182180000</tspan></text>
|
||||
</g>
|
||||
<g
|
||||
id="g3954"
|
||||
transform="translate(0,-18.756902)">
|
||||
<rect
|
||||
style="fill:none;stroke:#c3c3c3;stroke-width:1.07903957;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
id="rect3876"
|
||||
width="417.54495"
|
||||
height="126.4052"
|
||||
x="141.08452"
|
||||
y="284.96024"
|
||||
rx="2.0839903"
|
||||
ry="2.0839903" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:27.53050041px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
x="209.6223"
|
||||
y="318.18164"
|
||||
id="text3878"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3880"
|
||||
x="209.6223"
|
||||
y="318.18164"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">CLIENT</tspan></text>
|
||||
<image
|
||||
width="162.31204"
|
||||
height="107.26709"
|
||||
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOYAAACYCAIAAAB/IAJdAAAAA3NCSVQICAjb4U/gAAADyUlEQVR4 nO3a3Y6qShSFUTC+/yvXuSAxbCgQsPiZp8e46mgJq/ETxe6+lNId0vd913XDw8c/z+8d3zIYP2p8 y9LKLbcvbXN8y5Zt/jLn0jHZcnzm29z+m961cmn+vY/a5fXLg+F6kiWMZAkjWcJIljCSJYxkCSNZ wkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJI ljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYw kiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIl jGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxk CSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkj WcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nC 9KWUg4/s+67rhoePf/7LHJO55sfEWZYwj0t2eCG2XXmGe/f+l72rt7Z6U5s8r1832LbX+ZqlAc6b 85qpbln59SDs7afvN31MrZxlm5w/+r6fb6d644H9Huu1euOpcx6eqnrj0n7vXdnKsP31Iz+YnmWb TzZ+3Wx/wqoOrJzvfeml3HDO36ear1wa/saV62fovafYUspnv+tv8pWzbMPPA5NNjS8bL1Dd+9gt c36dqnrX+pNy78qJw8etlDJ53VY39U+ybZ+kjb9nGWm1cpfmc/5o6RwzfyHdu3LX8NvNw50seE3u a/J8XPC8bjH5bavXPdfP+XWqXG0TGm92fJTezXf2BJ8PRtsvzy/wzKkea3Je/yT6mqw41e0vjI27 vnjO/0evJx20+eeEd3f0vWnXx/OlC52TVA/f8P6yPsCPc64fk8NTRTt8GbfkffYZ5eJYu9ULiJU+ zp7z2FQpzqtocj4tpbyrdzSZY7zNJz8fKXP+NfNYhx/qf7BtuL+HR5Ay55OdcYpdOY+8Ss1n6Y9z pHTwqDmXvgGdl3HvypNMvtKa7/GU/+S695uB7Qf9yjkPpDD/hn/JvStbmcdaPSxnfTDoVn/PK783 +GXZBdcTS/utfoO7dPV218qGtl9RPO7/ZZuovkBv/5vcrqmqK5c2e+PKVu9Uwxa2PEf/AXnknj+O sxSLAAAAAElFTkSuQmCC "
|
||||
id="image3951"
|
||||
x="388.17426"
|
||||
y="294.58957" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
transform="translate(0,0.3261795)"
|
||||
id="g3322">
|
||||
<rect
|
||||
style="fill:none;stroke:#c3c3c3;stroke-width:1.07903957;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
id="rect3964"
|
||||
width="417.54495"
|
||||
height="126.4052"
|
||||
x="141.08452"
|
||||
y="419.52063"
|
||||
rx="2.0839903"
|
||||
ry="2.0839903" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:27.53050041px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
x="209.6223"
|
||||
y="452.74203"
|
||||
id="text3966"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3968"
|
||||
x="209.6223"
|
||||
y="452.74203"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">WEIGHT</tspan></text>
|
||||
<image
|
||||
width="162.12962"
|
||||
height="107.14654"
|
||||
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOYAAACYCAIAAAB/IAJdAAAAA3NCSVQICAjb4U/gAAAERElEQVR4 nO3a0ZKaQBRFUbX8/182D1QZInRzadDxTNZ6mjgNNrhBYHJ9PB6XIdfr9XK5TIvPf57/drJ8/ayR rVdaG9Wfc+Vd+vPc+3N/hv3XK68st2i5Xa09XNnS+sjKthfdjiwMnydZwkiWMJIljGQJI1nCSJYw kiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIl jGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxk CSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkj WcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nC SJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiW MJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCS Jcz18XgMLnm9Xi6XafH5z/8z+2Tp9H3iLEuYDyU7HV7njuT/dH/+9NLKid9o5/Zan+fYFnW+vFrT 27uv+hPb3AnLt1su0prSO/beWeVcr6XL1Ns0dLnNqy+OzeOskfV5Dm9RZ8BZe2N1YgfXWXlx11u/ Y2TftFTlM7rP/zFv/B378eDIp+I8V89enUO5MpMjXz7P9S/nP59V/3zWOiX317k6/9aw5TqPj9z0 eDyea+vfpd1Wt/ny7y3eN6jP88jX9wdu8Jfzn2wuWD84W1cOm8Navz1rZN/LfmidcW/H36nz9pUP oz7yUs6uuLa5dx+cB5/vvOmRWeuAb52ex0bunVL/W/Se8uDw+Dw3r1MHTnVjs5qvpP6m9QuG7/lu HNa5Trg3F/oVKrfSR05ge5dt3awMH5DPj7b+0KA/n+/xcrZ+7qhmsm/6JjpdfZ6dMZuLd+5yBuxa ydgHsbxsnS4N407Gy3BXkm3d6KyuqzLyTSrzXH5Cex+Cdt5ifoabBlT2SSWmutZGtVb4+UxP7+Sf v34979EGbl8+aWye9RuIuoMnv039Ge66AVruqy//lCcvzw3+XhjsvSH4Ke+YZ+dR/DfvijFBW9S6 071fylcCP644z19T2wc25GsvZzvnpnvrF1/rxHl2rvZWL3lbTxuK77V6ifmZaPpH+8vlZmeeYyPH ptqa8C3lnPTjj5OW6xz7dlr9Q93YrFoP7Vfz6s+kOM+DI/uWl63r095c0VnP8A8+M9/cF8XHN8Xn WfUT6nCvm2uo7Lf6lu6af32eu7aoo36X8jv/i/fqAXrwBrm1zr0rKa6heJzXt3TXE4P6PI/vk/lS lc/oD6RX6TSeNJuDAAAAAElFTkSuQmCC "
|
||||
id="image4065"
|
||||
x="387.35876"
|
||||
y="429.14993" />
|
||||
<g
|
||||
transform="translate(0,140.26901)"
|
||||
id="g3179">
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
x="154.5717"
|
||||
y="341.12738"
|
||||
id="text3181"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3183"
|
||||
x="154.5717"
|
||||
y="341.12738"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">Prefix: 21</tspan></text>
|
||||
<text
|
||||
sodipodi:linespacing="125%"
|
||||
id="text3185"
|
||||
y="359.88428"
|
||||
x="154.5717"
|
||||
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium"
|
||||
y="359.88428"
|
||||
x="154.5717"
|
||||
id="tspan3187"
|
||||
sodipodi:role="line">ID: 12345</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
x="154.5717"
|
||||
y="379.4567"
|
||||
id="text3189"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3191"
|
||||
x="154.5717"
|
||||
y="379.4567"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">Value: 67.89Kg</tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
transform="translate(0,-1.9572678)"
|
||||
id="g3308">
|
||||
<rect
|
||||
ry="2.0839903"
|
||||
rx="2.0839903"
|
||||
y="574.46893"
|
||||
x="141.08452"
|
||||
height="126.4052"
|
||||
width="417.54495"
|
||||
id="rect4081"
|
||||
style="fill:none;stroke:#c3c3c3;stroke-width:1.07903957;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<g
|
||||
id="g3296">
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:27.53050041px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
x="195.7585"
|
||||
y="607.69037"
|
||||
id="text4083"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan4085"
|
||||
x="195.7585"
|
||||
y="607.69037"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">DISCOUNT</tspan></text>
|
||||
<image
|
||||
y="584.09827"
|
||||
x="385.72772"
|
||||
id="image4202"
|
||||
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOYAAACYCAIAAAB/IAJdAAAAA3NCSVQICAjb4U/gAAADsElEQVR4 nO3aXXOiMACGUej4//8ye+EMZYHECBF96zlXHTd8iE8jYTtO0zQcMo7jMAz3zUs/b8ffLUcuXymN bHn92ZHtR3l2ZOnnpfazOvauS3toP3rLyPr5tGx1wM+ZjeF6kiWMZAkjWcJIljCSJYxkCSNZwkiW MJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCS JYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWM ZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJ I1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZ wkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJI ljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCjNM0 HdxyHIdhuG++/PmbuSZb3a+JWZYwH5fs/RfxL42kr9vuq6/4UmvZZ0qFZ3pdbVu5II0jSyezO777 0XdP4Fg549h0m7ozy75i/mjZZ0qFHXst7W0cx92RjZufPPrJkc+e1XKT3fe4sp5lr+81pb/zV2be wzyXzJ9TaXZZvl45gZbJqf3oZ0ZWBtdN0zTvrf6FvDPLvmKRa+E8W16K+nf36l+Xi+7XHf3kyIeD 6/tZpb/7Zv+bZd9yS9A4kQSN3FWaOe6zy3ZOumYhsT36s+fZ3eo3c3vE32TfteRi5cDlal8qvcKq qi4TX+U+4bb7ahd6fZfLrvwcVq+HBts9bMO9rUb0pdeOtiFWVj/v1fFz34Z7G46+yfrt+SdcuOu1 L1meUl+Qbc/h2Jr98IltHyMsD939mvy4Jfhk86p5tZquu+bKVxZqQ785a/Xc4PfGoPJA+OTfzfTd 5/dYfU5vPJN3Ka0p9//Dlvcq3Ql8j8pv7K3yNPvMM+Hu+0xXeq5ZuSwPr1Vp2931e+PRD5xnXw8f 2H3cX3L9ecuPpMu902on9Rn64dHbR5buWQ/Hvb1t3d2JG4PrlJ5itvzJ1e74px6Lth/9qfOsn2q7 9nt3s+ylSuvrMzts32f3kaWjH3hT901atv0Hm+wpnHOLKX0AAAAASUVORK5CYII= "
|
||||
height="108.34499"
|
||||
width="163.94308"
|
||||
inkscape:transform-center-x="-24.4145"
|
||||
inkscape:transform-center-y="6.3942733" />
|
||||
<g
|
||||
transform="translate(0,296.03285)"
|
||||
id="g3210">
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
x="154.5717"
|
||||
y="341.12738"
|
||||
id="text3212"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3214"
|
||||
x="154.5717"
|
||||
y="341.12738"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">Prefix: 44</tspan></text>
|
||||
<text
|
||||
sodipodi:linespacing="125%"
|
||||
id="text3216"
|
||||
y="359.88428"
|
||||
x="154.5717"
|
||||
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium"
|
||||
y="359.88428"
|
||||
x="154.5717"
|
||||
id="tspan3218"
|
||||
sodipodi:role="line">ID: 11111</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
x="154.5717"
|
||||
y="379.4567"
|
||||
id="text3220"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3222"
|
||||
x="154.5717"
|
||||
y="379.4567"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">Value: 25%</tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
transform="translate(0,-0.97866058)"
|
||||
id="g3283">
|
||||
<rect
|
||||
style="fill:none;stroke:#c3c3c3;stroke-width:1.07903957;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
id="rect4115"
|
||||
width="417.54495"
|
||||
height="126.4052"
|
||||
x="141.08452"
|
||||
y="726.15521"
|
||||
rx="2.0839903"
|
||||
ry="2.0839903" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:27.53050041px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
x="212.88437"
|
||||
y="759.37659"
|
||||
id="text4117"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan4119"
|
||||
x="212.88437"
|
||||
y="759.37659"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">PRICE</tspan></text>
|
||||
<image
|
||||
width="163.36363"
|
||||
height="107.96206"
|
||||
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOYAAACYCAIAAAB/IAJdAAAAA3NCSVQICAjb4U/gAAAD70lEQVR4 nO3Z3XKbOhiGUezJ/d8yPfBsbwaB+Phz/aZrHaWukDB+goE8xnEcDnk8HsMwvDaf/tz+7/SVl3bR drbEkWvHZG3O/gytyvxrq6yt265emWfvyMq7K3qe2Rg+T7KEkSxhJEsYyRJGsoSRLGEkSxjJEkay hJEsYSRLGMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJEkayhJEsYSRLGMkSRrKEkSxhJEsYyRJGsoSR LGEkSxjJEkayhJEsYSRLGMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJEkayhJEsYSRLGMkSRrKEkSxh JEsYyRJGsoSRLGEkSxjJEkayhJEsYSRLGMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJEkayhJEsYSRL GMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJEkayhJEsYSRLGMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJ EkayhJEsYSRLGMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJEkayhJEsYSRLGMkSRrKEkSxhJEsYyRJG soSRLGEkSxjJEkayhJEsYSRLGMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJEkayhJEsYSRLmMc4jge3 fDyGYXhtPv35X+aYtC4/Js6yhJHsX/M65bDXz/un2RG85ButPufvG9lX6fXYWp0v37VFK4M/c0wq 2z4XV118Ze/yi3PWX6zP+W0jN21uVT9Q9Zl37Wr9nV57TCrv8We6xrvx9/bnz7XTGTp70w5bWz1l ZGvvZzmbdnO5yvyVXa33cGE54zi+Z+vfpT2n2yz+fED7Tqb/nB7ZxZ3rfE99/8jzdn19HxhTVO/h qnLGcZydERYPxXOt6DavA3twctv6aeP7Rw7/fSSzD6YzrDLn21U3c/UebiqnDXc24Ge4gYeRd9i8 Tt31pf/ytZ9U5zrhlmTPq/+Opow8pp2/jaze69r83xnu7Gz93tWPPpdtD9Dil8hiBykj79O5Wths bnp3cezC429prxOOn2V3XXSv3ZC1AzZ988gLb2HbSRbPiPXL6MUXL3w0VFn6klVuP8u+7/vWfrPb 19NH3qR+A7R3zm82e24wjuON17KzlfqD68cuZeTHrP3hY/jKva1bu1O8K9nNK4Hi5r9mZN0vqO28 zvnu53VBs/aHjZMH7uQ17uLzuS8f+UmL63YuedeeNszmLPZwUzmbj+Gei0NPnjzqO9056KEjr9JO fv4ksvbEY/Pd9d/pVeW0l63LO7b4+Oa9zeG1+wMWT2Cbq6eMrOj31zmAxS+uk/t/x294X/3O5/+z +oV/F9mVbLvJxh6HjOyrnDKPfSK7fhk6c94xsq/4rO0Pc0/7XuEpWE8AAAAASUVORK5CYII= "
|
||||
id="image4264"
|
||||
x="386.54324"
|
||||
y="734.96906" />
|
||||
<g
|
||||
transform="translate(0,442.82599)"
|
||||
id="g3224">
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
x="154.5717"
|
||||
y="341.12738"
|
||||
id="text3226"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3228"
|
||||
x="154.5717"
|
||||
y="341.12738"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">Prefix: 02</tspan></text>
|
||||
<text
|
||||
sodipodi:linespacing="125%"
|
||||
id="text3230"
|
||||
y="359.88428"
|
||||
x="154.5717"
|
||||
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium"
|
||||
y="359.88428"
|
||||
x="154.5717"
|
||||
id="tspan3232"
|
||||
sodipodi:role="line">ID: 99999</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
x="154.5717"
|
||||
y="379.4567"
|
||||
id="text3234"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3236"
|
||||
x="154.5717"
|
||||
y="379.4567"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">Value: 134.50€</tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g3269">
|
||||
<g
|
||||
id="g3061"
|
||||
transform="translate(0,284.6156)">
|
||||
<g
|
||||
id="g3063"
|
||||
transform="translate(0,154.94832)">
|
||||
<rect
|
||||
ry="2.0839903"
|
||||
rx="2.0839903"
|
||||
y="438.27753"
|
||||
x="141.08452"
|
||||
height="126.4052"
|
||||
width="417.54495"
|
||||
id="rect3065"
|
||||
style="fill:none;stroke:#c3c3c3;stroke-width:1.07903957;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<text
|
||||
sodipodi:linespacing="125%"
|
||||
id="text3067"
|
||||
y="471.49893"
|
||||
x="216.61778"
|
||||
style="font-size:27.53050041px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium"
|
||||
y="471.49893"
|
||||
x="216.61778"
|
||||
sodipodi:role="line"
|
||||
id="tspan3083">UNIT</tspan></text>
|
||||
<g
|
||||
transform="translate(0,156.57936)"
|
||||
id="g3071" />
|
||||
</g>
|
||||
</g>
|
||||
<image
|
||||
y="887.47076"
|
||||
x="386.54324"
|
||||
id="image3146"
|
||||
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOYAAACYCAIAAAB/IAJdAAAAA3NCSVQICAjb4U/gAAADz0lEQVR4 nO3Z0ZKaMACGUdnx/V+ZXjBjGWKyEQjyt+fcdIsBY/ZTgZ3meX7sMk3T4/FYdq/9XI5frB9db18/ Wh6nfJZyr/Yz9s+t/Yp65tw/22+N3Pe6asevhdQ/stPPkZ3hepIljGQJI1nCSJYwkiWMZAkjWcJI ljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYw kiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIl jGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxk CSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkj WcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nC SJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkzz PO/cc5oej8ey+/rn/5k1KZ2+Jj5lCSNZwjyXf5ZP7NKJ32v9XwrtkZupNg743ZH3nNWgV3p8WZaD 9Oz4Uz7fCP1P0Rg5TVP5aG38d0fWfHFW/av36TofX5bXLm+PtvF8/TTuQuGUXl/W83y9zs3kX8d5 bb9yZM0dZtWzev0jy2f/9bA18zy/jtb+mr3oXLZ/9u3zgc2j7cOuH71+5K1m1b96B9d5+e+isUvt OJv0336EDU/23I/Y0vruyfo45ZJdM7LmnrM6MnLQXbwy3M2AZ+2xU6Zy1iUXN7eO55RfYuM84Vnb 53hD1/R6wbXjP+z4d2Dt2ut4uJvP9ddhf9Zn0LvPQtrPesrIt19heu3Uv3r71nlEPOsjr2fyrF0t /nrd1z7lH3cKe+dMj1+cjTbi91Jefi1XTsv209ekevl1w1OC8k084m39r+pfvY/WefT6b+4bzPNc PZc95clqGzevs3/k2y30O/FUbbTa/YCByV7jzucJ9zf6FuRujVsQb/56sdnnrHfbwbOF2nwag7+1 ccfrGr2xf/XOWudye6dfb7b+vB13eq/HvZ1J+61f3ve+cuStZtW/emet845yytPW95NZ37PdOLfX 49dkH83zst/cR6v0xVn1r96gdW7r/2PE33tYI/76Vc7p+G2ED15b9ysaMfKes9q3emetc1vjjura Hwkp3Xa7HcbMAAAAAElFTkSuQmCC "
|
||||
height="108.34499"
|
||||
width="163.94308" />
|
||||
<g
|
||||
id="g3255"
|
||||
transform="translate(0,598.58983)">
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
x="154.5717"
|
||||
y="351.81107"
|
||||
id="text3261"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3263"
|
||||
x="154.5717"
|
||||
y="351.81107"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">ID: 5449000000996</tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(0,-1,1,0,261.82237,1622.2884)"
|
||||
id="g4150">
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Droid Sans;-inkscape-font-specification:Droid Sans"
|
||||
x="618.97778"
|
||||
y="320.0275"
|
||||
id="text3357"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3359"
|
||||
x="618.97778"
|
||||
y="320.0275">This is a test sheet intended to help you test the codebar aquisition in OpenERP's Point of Sale module. </tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="618.97778"
|
||||
y="330.0275"
|
||||
id="tspan3363">The codes provided in this list are randomly chosen and are encoded in the EAN13 format. Their codes</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="618.97778"
|
||||
y="340.0275"
|
||||
id="tspan3367">and prefixes are not intended to match any specified standard. </tspan></text>
|
||||
<text
|
||||
sodipodi:linespacing="125%"
|
||||
id="text3369"
|
||||
y="349.54001"
|
||||
x="618.99207"
|
||||
style="font-size:6.53564215px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#b3b3b3;fill-opacity:1;stroke:none;font-family:Droid Sans;-inkscape-font-specification:Droid Sans"
|
||||
xml:space="preserve"><tspan
|
||||
id="tspan3375"
|
||||
y="349.54001"
|
||||
x="618.99207"
|
||||
sodipodi:role="line"
|
||||
style="fill:#b3b3b3;fill-opacity:1">This document is licensed by OpenERP S.A. under the Creative Commons CC BY 3.0 license htttp://www.creativecommons.org</tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 31 KiB |
7447
i18n/af.po
Normal file
7447
i18n/af.po
Normal file
File diff suppressed because it is too large
Load Diff
8541
i18n/ar.po
Normal file
8541
i18n/ar.po
Normal file
File diff suppressed because it is too large
Load Diff
7452
i18n/az.po
Normal file
7452
i18n/az.po
Normal file
File diff suppressed because it is too large
Load Diff
8354
i18n/bg.po
Normal file
8354
i18n/bg.po
Normal file
File diff suppressed because it is too large
Load Diff
7451
i18n/bs.po
Normal file
7451
i18n/bs.po
Normal file
File diff suppressed because it is too large
Load Diff
8601
i18n/ca.po
Normal file
8601
i18n/ca.po
Normal file
File diff suppressed because it is too large
Load Diff
8540
i18n/cs.po
Normal file
8540
i18n/cs.po
Normal file
File diff suppressed because it is too large
Load Diff
8590
i18n/da.po
Normal file
8590
i18n/da.po
Normal file
File diff suppressed because it is too large
Load Diff
8663
i18n/de.po
Normal file
8663
i18n/de.po
Normal file
File diff suppressed because it is too large
Load Diff
7451
i18n/el.po
Normal file
7451
i18n/el.po
Normal file
File diff suppressed because it is too large
Load Diff
8640
i18n/es.po
Normal file
8640
i18n/es.po
Normal file
File diff suppressed because it is too large
Load Diff
8644
i18n/es_419.po
Normal file
8644
i18n/es_419.po
Normal file
File diff suppressed because it is too large
Load Diff
7445
i18n/es_BO.po
Normal file
7445
i18n/es_BO.po
Normal file
File diff suppressed because it is too large
Load Diff
7450
i18n/es_CL.po
Normal file
7450
i18n/es_CL.po
Normal file
File diff suppressed because it is too large
Load Diff
7456
i18n/es_CO.po
Normal file
7456
i18n/es_CO.po
Normal file
File diff suppressed because it is too large
Load Diff
7445
i18n/es_CR.po
Normal file
7445
i18n/es_CR.po
Normal file
File diff suppressed because it is too large
Load Diff
7454
i18n/es_PE.po
Normal file
7454
i18n/es_PE.po
Normal file
File diff suppressed because it is too large
Load Diff
7445
i18n/es_PY.po
Normal file
7445
i18n/es_PY.po
Normal file
File diff suppressed because it is too large
Load Diff
7449
i18n/es_VE.po
Normal file
7449
i18n/es_VE.po
Normal file
File diff suppressed because it is too large
Load Diff
8511
i18n/et.po
Normal file
8511
i18n/et.po
Normal file
File diff suppressed because it is too large
Load Diff
7447
i18n/eu.po
Normal file
7447
i18n/eu.po
Normal file
File diff suppressed because it is too large
Load Diff
8475
i18n/fa.po
Normal file
8475
i18n/fa.po
Normal file
File diff suppressed because it is too large
Load Diff
8549
i18n/fi.po
Normal file
8549
i18n/fi.po
Normal file
File diff suppressed because it is too large
Load Diff
8677
i18n/fr.po
Normal file
8677
i18n/fr.po
Normal file
File diff suppressed because it is too large
Load Diff
7445
i18n/fr_CA.po
Normal file
7445
i18n/fr_CA.po
Normal file
File diff suppressed because it is too large
Load Diff
7445
i18n/gl.po
Normal file
7445
i18n/gl.po
Normal file
File diff suppressed because it is too large
Load Diff
7452
i18n/gu.po
Normal file
7452
i18n/gu.po
Normal file
File diff suppressed because it is too large
Load Diff
8400
i18n/he.po
Normal file
8400
i18n/he.po
Normal file
File diff suppressed because it is too large
Load Diff
7486
i18n/hr.po
Normal file
7486
i18n/hr.po
Normal file
File diff suppressed because it is too large
Load Diff
8339
i18n/hu.po
Normal file
8339
i18n/hu.po
Normal file
File diff suppressed because it is too large
Load Diff
8592
i18n/id.po
Normal file
8592
i18n/id.po
Normal file
File diff suppressed because it is too large
Load Diff
7455
i18n/is.po
Normal file
7455
i18n/is.po
Normal file
File diff suppressed because it is too large
Load Diff
8643
i18n/it.po
Normal file
8643
i18n/it.po
Normal file
File diff suppressed because it is too large
Load Diff
8351
i18n/ja.po
Normal file
8351
i18n/ja.po
Normal file
File diff suppressed because it is too large
Load Diff
7449
i18n/ka.po
Normal file
7449
i18n/ka.po
Normal file
File diff suppressed because it is too large
Load Diff
7445
i18n/kab.po
Normal file
7445
i18n/kab.po
Normal file
File diff suppressed because it is too large
Load Diff
7451
i18n/km.po
Normal file
7451
i18n/km.po
Normal file
File diff suppressed because it is too large
Load Diff
8379
i18n/ko.po
Normal file
8379
i18n/ko.po
Normal file
File diff suppressed because it is too large
Load Diff
7447
i18n/lb.po
Normal file
7447
i18n/lb.po
Normal file
File diff suppressed because it is too large
Load Diff
8391
i18n/lt.po
Normal file
8391
i18n/lt.po
Normal file
File diff suppressed because it is too large
Load Diff
8306
i18n/lv.po
Normal file
8306
i18n/lv.po
Normal file
File diff suppressed because it is too large
Load Diff
7455
i18n/mk.po
Normal file
7455
i18n/mk.po
Normal file
File diff suppressed because it is too large
Load Diff
7483
i18n/mn.po
Normal file
7483
i18n/mn.po
Normal file
File diff suppressed because it is too large
Load Diff
7474
i18n/nb.po
Normal file
7474
i18n/nb.po
Normal file
File diff suppressed because it is too large
Load Diff
8637
i18n/nl.po
Normal file
8637
i18n/nl.po
Normal file
File diff suppressed because it is too large
Load Diff
8581
i18n/pl.po
Normal file
8581
i18n/pl.po
Normal file
File diff suppressed because it is too large
Load Diff
8271
i18n/point_of_sale.pot
Normal file
8271
i18n/point_of_sale.pot
Normal file
File diff suppressed because it is too large
Load Diff
8326
i18n/pt.po
Normal file
8326
i18n/pt.po
Normal file
File diff suppressed because it is too large
Load Diff
8617
i18n/pt_BR.po
Normal file
8617
i18n/pt_BR.po
Normal file
File diff suppressed because it is too large
Load Diff
7496
i18n/ro.po
Normal file
7496
i18n/ro.po
Normal file
File diff suppressed because it is too large
Load Diff
8629
i18n/ru.po
Normal file
8629
i18n/ru.po
Normal file
File diff suppressed because it is too large
Load Diff
8381
i18n/sk.po
Normal file
8381
i18n/sk.po
Normal file
File diff suppressed because it is too large
Load Diff
8329
i18n/sl.po
Normal file
8329
i18n/sl.po
Normal file
File diff suppressed because it is too large
Load Diff
8537
i18n/sr.po
Normal file
8537
i18n/sr.po
Normal file
File diff suppressed because it is too large
Load Diff
7449
i18n/sr@latin.po
Normal file
7449
i18n/sr@latin.po
Normal file
File diff suppressed because it is too large
Load Diff
8379
i18n/sv.po
Normal file
8379
i18n/sv.po
Normal file
File diff suppressed because it is too large
Load Diff
8539
i18n/th.po
Normal file
8539
i18n/th.po
Normal file
File diff suppressed because it is too large
Load Diff
8562
i18n/tr.po
Normal file
8562
i18n/tr.po
Normal file
File diff suppressed because it is too large
Load Diff
8604
i18n/uk.po
Normal file
8604
i18n/uk.po
Normal file
File diff suppressed because it is too large
Load Diff
8588
i18n/vi.po
Normal file
8588
i18n/vi.po
Normal file
File diff suppressed because it is too large
Load Diff
8349
i18n/zh_CN.po
Normal file
8349
i18n/zh_CN.po
Normal file
File diff suppressed because it is too large
Load Diff
8348
i18n/zh_TW.po
Normal file
8348
i18n/zh_TW.po
Normal file
File diff suppressed because it is too large
Load Diff
29
models/__init__.py
Normal file
29
models/__init__.py
Normal file
@ -0,0 +1,29 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import account_bank_statement
|
||||
from . import account_payment
|
||||
from . import account_journal
|
||||
from . import account_tax
|
||||
from . import account_move
|
||||
from . import barcode_rule
|
||||
from . import chart_template
|
||||
from . import digest
|
||||
from . import pos_category
|
||||
from . import pos_config
|
||||
from . import pos_order
|
||||
from . import pos_session
|
||||
from . import pos_combo
|
||||
from . import pos_combo_line
|
||||
from . import product
|
||||
from . import res_partner
|
||||
from . import res_company
|
||||
from . import res_config_settings
|
||||
from . import stock_picking
|
||||
from . import stock_rule
|
||||
from . import stock_warehouse
|
||||
from . import pos_payment
|
||||
from . import pos_payment_method
|
||||
from . import pos_bill
|
||||
from . import report_sale_details
|
||||
from . import pos_printer
|
11
models/account_bank_statement.py
Normal file
11
models/account_bank_statement.py
Normal file
@ -0,0 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
# Copyright (C) 2004-2008 PC Solutions (<http://pcsol.be>). All Rights Reserved
|
||||
from odoo import fields, models, api, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class AccountBankStatementLine(models.Model):
|
||||
_inherit = 'account.bank.statement.line'
|
||||
|
||||
pos_session_id = fields.Many2one('pos.session', string="Session", copy=False)
|
23
models/account_journal.py
Normal file
23
models/account_journal.py
Normal file
@ -0,0 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
# Copyright (C) 2004-2008 PC Solutions (<http://pcsol.be>). All Rights Reserved
|
||||
from odoo import fields, models, api, _
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
class AccountJournal(models.Model):
|
||||
_inherit = 'account.journal'
|
||||
|
||||
pos_payment_method_ids = fields.One2many('pos.payment.method', 'journal_id', string='Point of Sale Payment Methods')
|
||||
|
||||
@api.constrains('type')
|
||||
def _check_type(self):
|
||||
methods = self.env['pos.payment.method'].sudo().search([("journal_id", "in", self.ids)])
|
||||
if methods:
|
||||
raise ValidationError(_("This journal is associated with a payment method. You cannot modify its type"))
|
||||
|
||||
def _get_journal_inbound_outstanding_payment_accounts(self):
|
||||
res = super()._get_journal_inbound_outstanding_payment_accounts()
|
||||
account_ids = set(res.ids)
|
||||
for payment_method in self.sudo().pos_payment_method_ids:
|
||||
account_ids.add(payment_method.outstanding_account_id.id or self.company_id.account_journal_payment_debit_account_id.id)
|
||||
return self.env['account.account'].browse(account_ids)
|
72
models/account_move.py
Normal file
72
models/account_move.py
Normal file
@ -0,0 +1,72 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models, api
|
||||
|
||||
|
||||
class AccountMove(models.Model):
|
||||
_inherit = 'account.move'
|
||||
|
||||
pos_order_ids = fields.One2many('pos.order', 'account_move')
|
||||
pos_payment_ids = fields.One2many('pos.payment', 'account_move_id')
|
||||
pos_refunded_invoice_ids = fields.Many2many('account.move', 'refunded_invoices', 'refund_account_move', 'original_account_move')
|
||||
|
||||
def _stock_account_get_last_step_stock_moves(self):
|
||||
stock_moves = super(AccountMove, self)._stock_account_get_last_step_stock_moves()
|
||||
for invoice in self.filtered(lambda x: x.move_type == 'out_invoice'):
|
||||
stock_moves += invoice.sudo().mapped('pos_order_ids.picking_ids.move_ids').filtered(lambda x: x.state == 'done' and x.location_dest_id.usage == 'customer')
|
||||
for invoice in self.filtered(lambda x: x.move_type == 'out_refund'):
|
||||
stock_moves += invoice.sudo().mapped('pos_order_ids.picking_ids.move_ids').filtered(lambda x: x.state == 'done' and x.location_id.usage == 'customer')
|
||||
return stock_moves
|
||||
|
||||
|
||||
def _get_invoiced_lot_values(self):
|
||||
self.ensure_one()
|
||||
|
||||
lot_values = super(AccountMove, self)._get_invoiced_lot_values()
|
||||
|
||||
if self.state == 'draft':
|
||||
return lot_values
|
||||
|
||||
# user may not have access to POS orders, but it's ok if they have
|
||||
# access to the invoice
|
||||
for order in self.sudo().pos_order_ids:
|
||||
for line in order.lines:
|
||||
lots = line.pack_lot_ids or False
|
||||
if lots:
|
||||
for lot in lots:
|
||||
lot_values.append({
|
||||
'product_name': lot.product_id.name,
|
||||
'quantity': line.qty if lot.product_id.tracking == 'lot' else 1.0,
|
||||
'uom_name': line.product_uom_id.name,
|
||||
'lot_name': lot.lot_name,
|
||||
})
|
||||
|
||||
return lot_values
|
||||
|
||||
def _compute_payments_widget_reconciled_info(self):
|
||||
"""Add pos_payment_name field in the reconciled vals to be able to show the payment method in the invoice."""
|
||||
super()._compute_payments_widget_reconciled_info()
|
||||
for move in self:
|
||||
if move.invoice_payments_widget:
|
||||
if move.state == 'posted' and move.is_invoice(include_receipts=True):
|
||||
reconciled_partials = move._get_all_reconciled_invoice_partials()
|
||||
for i, reconciled_partial in enumerate(reconciled_partials):
|
||||
counterpart_line = reconciled_partial['aml']
|
||||
pos_payment = counterpart_line.move_id.sudo().pos_payment_ids
|
||||
move.invoice_payments_widget['content'][i].update({
|
||||
'pos_payment_name': pos_payment.payment_method_id.name,
|
||||
})
|
||||
|
||||
class AccountMoveLine(models.Model):
|
||||
_inherit = 'account.move.line'
|
||||
|
||||
def _stock_account_get_anglo_saxon_price_unit(self):
|
||||
self.ensure_one()
|
||||
if not self.product_id:
|
||||
return self.price_unit
|
||||
price_unit = super(AccountMoveLine, self)._stock_account_get_anglo_saxon_price_unit()
|
||||
sudo_order = self.move_id.sudo().pos_order_ids
|
||||
if sudo_order:
|
||||
price_unit = sudo_order._get_pos_anglo_saxon_price_unit(self.product_id, self.move_id.partner_id.id, self.quantity)
|
||||
return price_unit
|
24
models/account_payment.py
Normal file
24
models/account_payment.py
Normal file
@ -0,0 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models, fields, api
|
||||
|
||||
|
||||
class AccountPayment(models.Model):
|
||||
_inherit = 'account.payment'
|
||||
|
||||
pos_payment_method_id = fields.Many2one('pos.payment.method', "POS Payment Method")
|
||||
force_outstanding_account_id = fields.Many2one("account.account", "Forced Outstanding Account", check_company=True)
|
||||
pos_session_id = fields.Many2one('pos.session', "POS Session")
|
||||
|
||||
def _get_valid_liquidity_accounts(self):
|
||||
result = super()._get_valid_liquidity_accounts()
|
||||
return result | self.pos_payment_method_id.outstanding_account_id
|
||||
|
||||
@api.depends("force_outstanding_account_id")
|
||||
def _compute_outstanding_account_id(self):
|
||||
"""When force_outstanding_account_id is set, we use it as the outstanding_account_id."""
|
||||
super()._compute_outstanding_account_id()
|
||||
for payment in self:
|
||||
if payment.force_outstanding_account_id:
|
||||
payment.outstanding_account_id = payment.force_outstanding_account_id
|
51
models/account_tax.py
Normal file
51
models/account_tax.py
Normal file
@ -0,0 +1,51 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import _, api, models
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tools import split_every
|
||||
|
||||
|
||||
class AccountTax(models.Model):
|
||||
_inherit = 'account.tax'
|
||||
|
||||
def write(self, vals):
|
||||
forbidden_fields = {
|
||||
'amount_type', 'amount', 'type_tax_use', 'tax_group_id', 'price_include',
|
||||
'include_base_amount', 'is_base_affected',
|
||||
}
|
||||
if forbidden_fields & set(vals.keys()):
|
||||
lines = self.env['pos.order.line'].sudo().search([
|
||||
('order_id.session_id.state', '!=', 'closed')
|
||||
])
|
||||
self_ids = set(self.ids)
|
||||
for lines_chunk in map(self.env['pos.order.line'].sudo().browse, split_every(100000, lines.ids)):
|
||||
if any(tid in self_ids for ts in lines_chunk.read(['tax_ids']) for tid in ts['tax_ids']):
|
||||
raise UserError(_(
|
||||
'It is forbidden to modify a tax used in a POS order not posted. '
|
||||
'You must close the POS sessions before modifying the tax.'
|
||||
))
|
||||
lines_chunk.invalidate_recordset(['tax_ids'])
|
||||
return super(AccountTax, self).write(vals)
|
||||
|
||||
def _hook_compute_is_used(self, taxes_to_compute):
|
||||
# OVERRIDE in order to fetch taxes used in pos
|
||||
|
||||
used_taxes = super()._hook_compute_is_used(taxes_to_compute)
|
||||
taxes_to_compute -= used_taxes
|
||||
|
||||
if taxes_to_compute:
|
||||
self.env['pos.order.line'].flush_model(['tax_ids'])
|
||||
self.env.cr.execute("""
|
||||
SELECT id
|
||||
FROM account_tax
|
||||
WHERE EXISTS(
|
||||
SELECT 1
|
||||
FROM account_tax_pos_order_line_rel AS pos
|
||||
WHERE account_tax_id IN %s
|
||||
AND account_tax.id = pos.account_tax_id
|
||||
)
|
||||
""", [tuple(taxes_to_compute)])
|
||||
|
||||
used_taxes.update([tax[0] for tax in self.env.cr.fetchall()])
|
||||
|
||||
return used_taxes
|
23
models/barcode_rule.py
Normal file
23
models/barcode_rule.py
Normal file
@ -0,0 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models, fields
|
||||
from odoo.tools.translate import _
|
||||
|
||||
|
||||
class BarcodeRule(models.Model):
|
||||
_inherit = 'barcode.rule'
|
||||
|
||||
type = fields.Selection(selection_add=[
|
||||
('weight', 'Weighted Product'),
|
||||
('price', 'Priced Product'),
|
||||
('discount', 'Discounted Product'),
|
||||
('client', 'Client'),
|
||||
('cashier', 'Cashier')
|
||||
], ondelete={
|
||||
'weight': 'set default',
|
||||
'price': 'set default',
|
||||
'discount': 'set default',
|
||||
'client': 'set default',
|
||||
'cashier': 'set default',
|
||||
})
|
24
models/chart_template.py
Normal file
24
models/chart_template.py
Normal file
@ -0,0 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import models
|
||||
|
||||
|
||||
class AccountChartTemplate(models.AbstractModel):
|
||||
_inherit = 'account.chart.template'
|
||||
|
||||
def _load(self, template_code, company, install_demo):
|
||||
"""Remove the payment methods that are created for the company and unset journals before installing the chart of accounts.
|
||||
|
||||
Keeping these existing pos.payment.method records and pos.config journals interferes with the installation of chart of accounts
|
||||
because pos.payment.method model has fields linked to account.journal and account.account records that are
|
||||
deleted during the loading of chart of accounts.
|
||||
"""
|
||||
reload_template = template_code == company.chart_template
|
||||
if not reload_template:
|
||||
self.env['pos.payment.method'].with_context(active_test=False).search(self.env['pos.payment.method']._check_company_domain(company)).unlink()
|
||||
self.env["pos.config"].with_context(active_test=False).search(self.env['pos.config']._check_company_domain(company)).write({
|
||||
'journal_id': False,
|
||||
'invoice_journal_id': False,
|
||||
})
|
||||
result = super()._load(template_code, company, install_demo)
|
||||
self.env['pos.config'].post_install_pos_localisation(companies=company)
|
||||
return result
|
29
models/digest.py
Normal file
29
models/digest.py
Normal file
@ -0,0 +1,29 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models, _
|
||||
from odoo.exceptions import AccessError
|
||||
|
||||
|
||||
class Digest(models.Model):
|
||||
_inherit = 'digest.digest'
|
||||
|
||||
kpi_pos_total = fields.Boolean('POS Sales')
|
||||
kpi_pos_total_value = fields.Monetary(compute='_compute_kpi_pos_total_value')
|
||||
|
||||
def _compute_kpi_pos_total_value(self):
|
||||
if not self.env.user.has_group('point_of_sale.group_pos_user'):
|
||||
raise AccessError(_("Do not have access, skip this data for user's digest email"))
|
||||
|
||||
self._calculate_company_based_kpi(
|
||||
'pos.order',
|
||||
'kpi_pos_total_value',
|
||||
date_field='date_order',
|
||||
additional_domain=[('state', 'not in', ['draft', 'cancel', 'invoiced'])],
|
||||
sum_field='amount_total',
|
||||
)
|
||||
|
||||
def _compute_kpis_actions(self, company, user):
|
||||
res = super(Digest, self)._compute_kpis_actions(company, user)
|
||||
res['kpi_pos_total'] = 'point_of_sale.action_pos_sale_graph&menu_id=%s' % self.env.ref('point_of_sale.menu_point_root').id
|
||||
return res
|
21
models/pos_bill.py
Normal file
21
models/pos_bill.py
Normal file
@ -0,0 +1,21 @@
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class Bill(models.Model):
|
||||
_name = "pos.bill"
|
||||
_order = "value"
|
||||
_description = "Coins/Bills"
|
||||
|
||||
name = fields.Char("Name")
|
||||
value = fields.Float("Coin/Bill Value", required=True, digits=0)
|
||||
pos_config_ids = fields.Many2many("pos.config", string="Point of Sales")
|
||||
|
||||
@api.model
|
||||
def name_create(self, name):
|
||||
try:
|
||||
value = float(name)
|
||||
except ValueError:
|
||||
raise UserError(_("The name of the Coins/Bills must be a number."))
|
||||
result = super().create({"name": name, "value": value})
|
||||
return result.id, result.display_name
|
49
models/pos_category.py
Normal file
49
models/pos_category.py
Normal file
@ -0,0 +1,49 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from typing import List, Tuple
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import ValidationError, UserError
|
||||
|
||||
|
||||
class PosCategory(models.Model):
|
||||
_name = "pos.category"
|
||||
_description = "Point of Sale Category"
|
||||
_order = "sequence, name"
|
||||
|
||||
@api.constrains('parent_id')
|
||||
def _check_category_recursion(self):
|
||||
if not self._check_recursion():
|
||||
raise ValidationError(_('Error! You cannot create recursive categories.'))
|
||||
|
||||
name = fields.Char(string='Category Name', required=True, translate=True)
|
||||
parent_id = fields.Many2one('pos.category', string='Parent Category', index=True)
|
||||
child_id = fields.One2many('pos.category', 'parent_id', string='Children Categories')
|
||||
sequence = fields.Integer(help="Gives the sequence order when displaying a list of product categories.")
|
||||
image_128 = fields.Image("Image", max_width=128, max_height=128)
|
||||
|
||||
# During loading of data, the image is not loaded so we expose a lighter
|
||||
# field to determine whether a pos.category has an image or not.
|
||||
has_image = fields.Boolean(compute='_compute_has_image')
|
||||
|
||||
def _get_hierarchy(self) -> List[str]:
|
||||
""" Returns a list representing the hierarchy of the categories. """
|
||||
self.ensure_one()
|
||||
return (self.parent_id._get_hierarchy() if self.parent_id else []) + [(self.name or '')]
|
||||
|
||||
@api.depends('parent_id')
|
||||
def _compute_display_name(self):
|
||||
for cat in self:
|
||||
cat.display_name = " / ".join(cat._get_hierarchy())
|
||||
|
||||
@api.ondelete(at_uninstall=False)
|
||||
def _unlink_except_session_open(self):
|
||||
if self.search_count([('id', 'in', self.ids)]):
|
||||
if self.env['pos.session'].sudo().search_count([('state', '!=', 'closed')]):
|
||||
raise UserError(_('You cannot delete a point of sale category while a session is still opened.'))
|
||||
|
||||
@api.depends('has_image')
|
||||
def _compute_has_image(self):
|
||||
for category in self:
|
||||
category.has_image = bool(category.image_128)
|
57
models/pos_combo.py
Normal file
57
models/pos_combo.py
Normal file
@ -0,0 +1,57 @@
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
class PosCombo(models.Model):
|
||||
"""
|
||||
This model is used to allow the pos user to create menus.
|
||||
This means that products can be grouped together and sold as a combo.
|
||||
|
||||
ex: Create a product called `Burger Menu`
|
||||
- This product will have multiple combos associated with it, for ex:
|
||||
- Drinks - will contain the list of drinks from which the customer can choose
|
||||
- Main Course - will contain the list of main courses from which the customer can choose
|
||||
- Dessert - will contain the list of desserts from which the customer can choose
|
||||
The `Burger Menu` will have a certain price, for example 20$ and the rest of the
|
||||
products will be listed with a price of 0$.
|
||||
In the event that one of the products inside one of the combos is to be more expensive,
|
||||
this product will have a specific `combo_price` which will be added to the total price
|
||||
"""
|
||||
_name = "pos.combo"
|
||||
_description = "Product combo choices"
|
||||
_order = "sequence, id"
|
||||
name = fields.Char(string="Name", required=True)
|
||||
combo_line_ids = fields.One2many("pos.combo.line", "combo_id", string="Products in Combo", copy=True)
|
||||
num_of_products = fields.Integer("No of Products", compute="_compute_num_of_products")
|
||||
sequence = fields.Integer(copy=False)
|
||||
base_price = fields.Float(
|
||||
compute="_compute_base_price",
|
||||
string="Product Price",
|
||||
help="The value from which pro-rating of the component price is based. This is to ensure that whatever product the user chooses for a component, it will always be they same price."
|
||||
)
|
||||
|
||||
@api.depends("combo_line_ids")
|
||||
def _compute_num_of_products(self):
|
||||
"""
|
||||
the read_group only returns info for the combos that have at least one line.
|
||||
This is normally fine, because all the combos will have at least one line.
|
||||
The problem is that this function is also run when the user creates a new combo,
|
||||
and at that point, the combo doesn't have any lines, so the read_group will return
|
||||
nothing and the function will fail to set the value of `num_of_products` to 0, thus
|
||||
resulting in an error.
|
||||
"""
|
||||
for rec in self:
|
||||
rec.num_of_products = 0
|
||||
# optimization trick to count the number of products in each combo
|
||||
for combo, num_of_products in self.env["pos.combo.line"]._read_group([("combo_id", "in", self.ids)], groupby=["combo_id"], aggregates=["__count"]):
|
||||
combo.num_of_products = num_of_products
|
||||
|
||||
@api.constrains("combo_line_ids")
|
||||
def _check_combo_line_ids_is_not_null(self):
|
||||
if any(not rec.combo_line_ids for rec in self):
|
||||
raise ValidationError(_("Please add products in combo."))
|
||||
|
||||
@api.depends("combo_line_ids")
|
||||
def _compute_base_price(self):
|
||||
for rec in self:
|
||||
# Use the lowest price of the combo lines as the base price
|
||||
rec.base_price = min(rec.combo_line_ids.mapped("product_id.lst_price")) if rec.combo_line_ids else 0
|
11
models/pos_combo_line.py
Normal file
11
models/pos_combo_line.py
Normal file
@ -0,0 +1,11 @@
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class PosComboLine(models.Model):
|
||||
_name = "pos.combo.line"
|
||||
_description = "Product Combo Items"
|
||||
|
||||
product_id = fields.Many2one("product.product", string="Product", required=True)
|
||||
combo_price = fields.Float("Price Extra", default=0.0)
|
||||
lst_price = fields.Float("Original Price", related="product_id.lst_price")
|
||||
combo_id = fields.Many2one("pos.combo")
|
905
models/pos_config.py
Normal file
905
models/pos_config.py
Normal file
@ -0,0 +1,905 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import datetime
|
||||
from uuid import uuid4
|
||||
import pytz
|
||||
|
||||
from odoo import api, fields, models, _, Command
|
||||
from odoo.osv.expression import OR, AND
|
||||
from odoo.exceptions import AccessError, ValidationError, UserError
|
||||
|
||||
|
||||
class PosConfig(models.Model):
|
||||
_name = 'pos.config'
|
||||
_description = 'Point of Sale Configuration'
|
||||
_check_company_auto = True
|
||||
|
||||
def _default_warehouse_id(self):
|
||||
return self.env['stock.warehouse'].search(self.env['stock.warehouse']._check_company_domain(self.env.company), limit=1).id
|
||||
|
||||
def _default_picking_type_id(self):
|
||||
return self.env['stock.warehouse'].search(self.env['stock.warehouse']._check_company_domain(self.env.company), limit=1).pos_type_id.id
|
||||
|
||||
def _default_sale_journal(self):
|
||||
return self.env['account.journal'].search([
|
||||
*self.env['account.journal']._check_company_domain(self.env.company),
|
||||
('type', 'in', ('sale', 'general')),
|
||||
('code', '=', 'POSS'),
|
||||
], limit=1)
|
||||
|
||||
def _default_invoice_journal(self):
|
||||
return self.env['account.journal'].search([
|
||||
*self.env['account.journal']._check_company_domain(self.env.company),
|
||||
('type', '=', 'sale'),
|
||||
], limit=1)
|
||||
|
||||
def _default_payment_methods(self):
|
||||
""" Should only default to payment methods that are compatible to this config's company and currency.
|
||||
"""
|
||||
domain = [
|
||||
*self.env['pos.payment.method']._check_company_domain(self.env.company),
|
||||
('split_transactions', '=', False),
|
||||
'|',
|
||||
('journal_id', '=', False),
|
||||
('journal_id.currency_id', 'in', (False, self.env.company.currency_id.id)),
|
||||
]
|
||||
non_cash_pm = self.env['pos.payment.method'].search(domain + [('is_cash_count', '=', False)])
|
||||
available_cash_pm = self.env['pos.payment.method'].search(domain + [('is_cash_count', '=', True),
|
||||
('config_ids', '=', False)], limit=1)
|
||||
return non_cash_pm | available_cash_pm
|
||||
|
||||
def _get_group_pos_manager(self):
|
||||
return self.env.ref('point_of_sale.group_pos_manager')
|
||||
|
||||
def _get_group_pos_user(self):
|
||||
return self.env.ref('point_of_sale.group_pos_user')
|
||||
|
||||
def _get_default_tip_product(self):
|
||||
tip_product_id = self.env.ref("point_of_sale.product_product_tip", raise_if_not_found=False)
|
||||
if not tip_product_id:
|
||||
tip_product_id = self.env['product.product'].search([('default_code', '=', 'TIPS')], limit=1)
|
||||
return tip_product_id
|
||||
|
||||
name = fields.Char(string='Point of Sale', required=True, help="An internal identification of the point of sale.")
|
||||
printer_ids = fields.Many2many('pos.printer', 'pos_config_printer_rel', 'config_id', 'printer_id', string='Order Printers')
|
||||
is_order_printer = fields.Boolean('Order Printer')
|
||||
is_installed_account_accountant = fields.Boolean(string="Is the Full Accounting Installed",
|
||||
compute="_compute_is_installed_account_accountant")
|
||||
picking_type_id = fields.Many2one(
|
||||
'stock.picking.type',
|
||||
string='Operation Type',
|
||||
default=_default_picking_type_id,
|
||||
required=True,
|
||||
domain="[('code', '=', 'outgoing'), ('warehouse_id.company_id', '=', company_id)]",
|
||||
ondelete='restrict')
|
||||
journal_id = fields.Many2one(
|
||||
'account.journal', string='Point of Sale Journal',
|
||||
domain=[('type', 'in', ('general', 'sale'))],
|
||||
check_company=True,
|
||||
help="Accounting journal used to post POS session journal entries and POS invoice payments.",
|
||||
default=_default_sale_journal,
|
||||
ondelete='restrict')
|
||||
invoice_journal_id = fields.Many2one(
|
||||
'account.journal', string='Invoice Journal',
|
||||
check_company=True,
|
||||
domain=[('type', '=', 'sale')],
|
||||
help="Accounting journal used to create invoices.",
|
||||
default=_default_invoice_journal)
|
||||
currency_id = fields.Many2one('res.currency', compute='_compute_currency', compute_sudo=True, string="Currency")
|
||||
iface_cashdrawer = fields.Boolean(string='Cashdrawer', help="Automatically open the cashdrawer.")
|
||||
iface_electronic_scale = fields.Boolean(string='Electronic Scale', help="Enables Electronic Scale integration.")
|
||||
iface_customer_facing_display = fields.Boolean(compute='_compute_customer_facing_display')
|
||||
iface_customer_facing_display_via_proxy = fields.Boolean(string='Customer Facing Display', help="Show checkout to customers with a remotely-connected screen.")
|
||||
iface_customer_facing_display_local = fields.Boolean(string='Local Customer Facing Display', help="Show checkout to customers.")
|
||||
iface_customer_facing_display_background_image_1920 = fields.Image(string='Background Image', max_width=1920, max_height=1920, compute='_compute_iface_customer_facing_display_background_image_1920', store=True)
|
||||
iface_print_via_proxy = fields.Boolean(string='Print via Proxy', help="Bypass browser printing and prints via the hardware proxy.")
|
||||
iface_scan_via_proxy = fields.Boolean(string='Scan via Proxy', help="Enable barcode scanning with a remotely connected barcode scanner and card swiping with a Vantiv card reader.")
|
||||
iface_big_scrollbars = fields.Boolean('Large Scrollbars', help='For imprecise industrial touchscreens.')
|
||||
iface_print_auto = fields.Boolean(string='Automatic Receipt Printing', default=False,
|
||||
help='The receipt will automatically be printed at the end of each order.')
|
||||
iface_print_skip_screen = fields.Boolean(string='Skip Preview Screen', default=True,
|
||||
help='The receipt screen will be skipped if the receipt can be printed automatically.')
|
||||
iface_tax_included = fields.Selection([('subtotal', 'Tax-Excluded Price'), ('total', 'Tax-Included Price')], string="Tax Display", default='total', required=True)
|
||||
iface_start_categ_id = fields.Many2one('pos.category', string='Initial Category',
|
||||
help='The point of sale will display this product category by default. If no category is specified, all available products will be shown.')
|
||||
iface_available_categ_ids = fields.Many2many('pos.category', string='Available PoS Product Categories',
|
||||
help='The point of sale will only display products which are within one of the selected category trees. If no category is specified, all available products will be shown')
|
||||
restrict_price_control = fields.Boolean(string='Restrict Price Modifications to Managers',
|
||||
help="Only users with Manager access rights for PoS app can modify the product prices on orders.")
|
||||
is_margins_costs_accessible_to_every_user = fields.Boolean(string='Margins & Costs', default=False,
|
||||
help='When disabled, only PoS manager can view the margin and cost of product among the Product info.')
|
||||
cash_control = fields.Boolean(string='Advanced Cash Control', compute='_compute_cash_control', help="Check the amount of the cashbox at opening and closing.")
|
||||
set_maximum_difference = fields.Boolean('Set Maximum Difference', help="Set a maximum difference allowed between the expected and counted money during the closing of the session.")
|
||||
receipt_header = fields.Text(string='Receipt Header', help="A short text that will be inserted as a header in the printed receipt.")
|
||||
receipt_footer = fields.Text(string='Receipt Footer', help="A short text that will be inserted as a footer in the printed receipt.")
|
||||
proxy_ip = fields.Char(string='IP Address', size=45,
|
||||
help='The hostname or ip address of the hardware proxy, Will be autodetected if left empty.')
|
||||
active = fields.Boolean(default=True)
|
||||
uuid = fields.Char(readonly=True, default=lambda self: str(uuid4()), copy=False,
|
||||
help='A globally unique identifier for this pos configuration, used to prevent conflicts in client-generated data.')
|
||||
sequence_id = fields.Many2one('ir.sequence', string='Order IDs Sequence', readonly=True,
|
||||
help="This sequence is automatically created by Odoo but you can change it "
|
||||
"to customize the reference numbers of your orders.", copy=False, ondelete='restrict')
|
||||
sequence_line_id = fields.Many2one('ir.sequence', string='Order Line IDs Sequence', readonly=True,
|
||||
help="This sequence is automatically created by Odoo but you can change it "
|
||||
"to customize the reference numbers of your orders lines.", copy=False)
|
||||
session_ids = fields.One2many('pos.session', 'config_id', string='Sessions')
|
||||
current_session_id = fields.Many2one('pos.session', compute='_compute_current_session', string="Current Session")
|
||||
current_session_state = fields.Char(compute='_compute_current_session')
|
||||
number_of_rescue_session = fields.Integer(string="Number of Rescue Session", compute='_compute_current_session')
|
||||
last_session_closing_cash = fields.Float(compute='_compute_last_session')
|
||||
last_session_closing_date = fields.Date(compute='_compute_last_session')
|
||||
pos_session_username = fields.Char(compute='_compute_current_session_user')
|
||||
pos_session_state = fields.Char(compute='_compute_current_session_user')
|
||||
pos_session_duration = fields.Char(compute='_compute_current_session_user')
|
||||
pricelist_id = fields.Many2one('product.pricelist', string='Default Pricelist',
|
||||
help="The pricelist used if no customer is selected or if the customer has no Sale Pricelist configured if any.")
|
||||
available_pricelist_ids = fields.Many2many('product.pricelist', string='Available Pricelists',
|
||||
help="Make several pricelists available in the Point of Sale. You can also apply a pricelist to specific customers from their contact form (in Sales tab). To be valid, this pricelist must be listed here as an available pricelist. Otherwise the default pricelist will apply.")
|
||||
company_id = fields.Many2one('res.company', string='Company', required=True, default=lambda self: self.env.company)
|
||||
group_pos_manager_id = fields.Many2one('res.groups', string='Point of Sale Manager Group', default=_get_group_pos_manager,
|
||||
help='This field is there to pass the id of the pos manager group to the point of sale client.')
|
||||
group_pos_user_id = fields.Many2one('res.groups', string='Point of Sale User Group', default=_get_group_pos_user,
|
||||
help='This field is there to pass the id of the pos user group to the point of sale client.')
|
||||
iface_tipproduct = fields.Boolean(string="Product tips")
|
||||
tip_product_id = fields.Many2one('product.product', string='Tip Product', default=_get_default_tip_product, help="This product is used as reference on customer receipts.")
|
||||
fiscal_position_ids = fields.Many2many('account.fiscal.position', string='Fiscal Positions', help='This is useful for restaurants with onsite and take-away services that imply specific tax rates.')
|
||||
default_fiscal_position_id = fields.Many2one('account.fiscal.position', string='Default Fiscal Position')
|
||||
default_bill_ids = fields.Many2many('pos.bill', string="Coins/Bills")
|
||||
use_pricelist = fields.Boolean("Use a pricelist.")
|
||||
tax_regime_selection = fields.Boolean("Tax Regime Selection value")
|
||||
start_category = fields.Boolean("Start Category", default=False)
|
||||
limit_categories = fields.Boolean("Restrict Categories")
|
||||
module_pos_restaurant = fields.Boolean("Is a Bar/Restaurant")
|
||||
module_pos_discount = fields.Boolean("Global Discounts")
|
||||
module_pos_mercury = fields.Boolean(string="Integrated Card Payments")
|
||||
is_posbox = fields.Boolean("PosBox")
|
||||
is_header_or_footer = fields.Boolean("Custom Header & Footer")
|
||||
module_pos_hr = fields.Boolean(help="Show employee login screen")
|
||||
amount_authorized_diff = fields.Float('Amount Authorized Difference',
|
||||
help="This field depicts the maximum difference allowed between the ending balance and the theoretical cash when "
|
||||
"closing a session, for non-POS managers. If this maximum is reached, the user will have an error message at "
|
||||
"the closing of his session saying that he needs to contact his manager.")
|
||||
payment_method_ids = fields.Many2many('pos.payment.method', string='Payment Methods', default=lambda self: self._default_payment_methods(), copy=False)
|
||||
company_has_template = fields.Boolean(string="Company has chart of accounts", compute="_compute_company_has_template")
|
||||
current_user_id = fields.Many2one('res.users', string='Current Session Responsible', compute='_compute_current_session_user')
|
||||
other_devices = fields.Boolean(string="Other Devices", help="Connect devices to your PoS without an IoT Box.")
|
||||
rounding_method = fields.Many2one('account.cash.rounding', string="Cash rounding")
|
||||
cash_rounding = fields.Boolean(string="Cash Rounding")
|
||||
only_round_cash_method = fields.Boolean(string="Only apply rounding on cash")
|
||||
has_active_session = fields.Boolean(compute='_compute_current_session')
|
||||
manual_discount = fields.Boolean(string="Line Discounts", default=True)
|
||||
ship_later = fields.Boolean(string="Ship Later")
|
||||
warehouse_id = fields.Many2one('stock.warehouse', default=_default_warehouse_id, ondelete='restrict')
|
||||
route_id = fields.Many2one('stock.route', string="Spefic route for products delivered later.")
|
||||
picking_policy = fields.Selection([
|
||||
('direct', 'As soon as possible'),
|
||||
('one', 'When all products are ready')],
|
||||
string='Shipping Policy', required=True, default='direct',
|
||||
help="If you deliver all products at once, the delivery order will be scheduled based on the greatest "
|
||||
"product lead time. Otherwise, it will be based on the shortest.")
|
||||
auto_validate_terminal_payment = fields.Boolean(default=True, help="Automatically validates orders paid with a payment terminal.")
|
||||
trusted_config_ids = fields.Many2many("pos.config", relation="pos_config_trust_relation", column1="is_trusting",
|
||||
column2="is_trusted", string="Trusted Point of Sale Configurations",
|
||||
domain="[('id', '!=', pos_config_id), ('module_pos_restaurant', '=', False)]")
|
||||
|
||||
@api.depends('payment_method_ids')
|
||||
def _compute_cash_control(self):
|
||||
for config in self:
|
||||
config.cash_control = bool(config.payment_method_ids.filtered('is_cash_count'))
|
||||
|
||||
@api.depends('company_id')
|
||||
def _compute_company_has_template(self):
|
||||
for config in self:
|
||||
config.company_has_template = config.company_id.root_id.sudo()._existing_accounting() or config.company_id.chart_template
|
||||
|
||||
def _compute_is_installed_account_accountant(self):
|
||||
account_accountant = self.env['ir.module.module'].sudo().search([('name', '=', 'account_accountant'), ('state', '=', 'installed')])
|
||||
for pos_config in self:
|
||||
pos_config.is_installed_account_accountant = account_accountant and account_accountant.id
|
||||
|
||||
@api.depends('journal_id.currency_id', 'journal_id.company_id.currency_id', 'company_id', 'company_id.currency_id')
|
||||
def _compute_currency(self):
|
||||
for pos_config in self:
|
||||
if pos_config.journal_id:
|
||||
pos_config.currency_id = pos_config.journal_id.currency_id.id or pos_config.journal_id.company_id.sudo().currency_id.id
|
||||
else:
|
||||
pos_config.currency_id = pos_config.company_id.sudo().currency_id.id
|
||||
|
||||
@api.depends('session_ids', 'session_ids.state')
|
||||
def _compute_current_session(self):
|
||||
"""If there is an open session, store it to current_session_id / current_session_State.
|
||||
"""
|
||||
for pos_config in self:
|
||||
opened_sessions = pos_config.session_ids.filtered(lambda s: s.state != 'closed')
|
||||
rescue_sessions = opened_sessions.filtered('rescue')
|
||||
session = pos_config.session_ids.filtered(lambda s: s.state != 'closed' and not s.rescue)
|
||||
# sessions ordered by id desc
|
||||
pos_config.has_active_session = opened_sessions and True or False
|
||||
pos_config.current_session_id = session and session[0].id or False
|
||||
pos_config.current_session_state = session and session[0].state or False
|
||||
pos_config.number_of_rescue_session = len(rescue_sessions)
|
||||
|
||||
@api.depends('session_ids')
|
||||
def _compute_last_session(self):
|
||||
PosSession = self.env['pos.session']
|
||||
for pos_config in self:
|
||||
session = PosSession.search_read(
|
||||
[('config_id', '=', pos_config.id), ('state', '=', 'closed')],
|
||||
['cash_register_balance_end_real', 'stop_at'],
|
||||
order="stop_at desc", limit=1)
|
||||
if session:
|
||||
timezone = pytz.timezone(self._context.get('tz') or self.env.user.tz or 'UTC')
|
||||
pos_config.last_session_closing_date = session[0]['stop_at'].astimezone(timezone).date()
|
||||
pos_config.last_session_closing_cash = session[0]['cash_register_balance_end_real']
|
||||
else:
|
||||
pos_config.last_session_closing_cash = 0
|
||||
pos_config.last_session_closing_date = False
|
||||
|
||||
@api.depends('session_ids')
|
||||
def _compute_current_session_user(self):
|
||||
for pos_config in self:
|
||||
session = pos_config.session_ids.filtered(lambda s: s.state in ['opening_control', 'opened', 'closing_control'] and not s.rescue)
|
||||
if session:
|
||||
pos_config.pos_session_username = session[0].user_id.sudo().name
|
||||
pos_config.pos_session_state = session[0].state
|
||||
pos_config.pos_session_duration = (
|
||||
datetime.now() - session[0].start_at
|
||||
).days if session[0].start_at else 0
|
||||
pos_config.current_user_id = session[0].user_id
|
||||
else:
|
||||
pos_config.pos_session_username = False
|
||||
pos_config.pos_session_state = False
|
||||
pos_config.pos_session_duration = 0
|
||||
pos_config.current_user_id = False
|
||||
|
||||
@api.depends('iface_customer_facing_display_via_proxy', 'iface_customer_facing_display_local')
|
||||
def _compute_customer_facing_display(self):
|
||||
for config in self:
|
||||
config.iface_customer_facing_display = config.iface_customer_facing_display_via_proxy or config.iface_customer_facing_display_local
|
||||
|
||||
@api.depends('iface_customer_facing_display')
|
||||
def _compute_iface_customer_facing_display_background_image_1920(self):
|
||||
for config in self:
|
||||
if not config.iface_customer_facing_display:
|
||||
config.iface_customer_facing_display_background_image_1920 = False
|
||||
|
||||
@api.constrains('rounding_method')
|
||||
def _check_rounding_method_strategy(self):
|
||||
for config in self:
|
||||
if config.cash_rounding and config.rounding_method.strategy != 'add_invoice_line':
|
||||
selection_value = "Add a rounding line"
|
||||
for key, val in self.env["account.cash.rounding"]._fields["strategy"]._description_selection(config.env):
|
||||
if key == "add_invoice_line":
|
||||
selection_value = val
|
||||
break
|
||||
raise ValidationError(_(
|
||||
"The cash rounding strategy of the point of sale %(pos)s must be: '%(value)s'",
|
||||
pos=config.name,
|
||||
value=selection_value,
|
||||
))
|
||||
|
||||
def _check_profit_loss_cash_journal(self):
|
||||
if self.cash_control and self.payment_method_ids:
|
||||
for method in self.payment_method_ids:
|
||||
if method.is_cash_count and (not method.journal_id.loss_account_id or not method.journal_id.profit_account_id):
|
||||
raise ValidationError(_("You need a loss and profit account on your cash journal."))
|
||||
|
||||
@api.constrains('company_id', 'payment_method_ids')
|
||||
def _check_company_payment(self):
|
||||
for config in self:
|
||||
if self.env['pos.payment.method'].search_count([('id', 'in', config.payment_method_ids.ids), ('company_id', '!=', config.company_id.id)]):
|
||||
raise ValidationError(_("The payment methods for the point of sale %s must belong to its company.", self.name))
|
||||
|
||||
@api.constrains('pricelist_id', 'use_pricelist', 'available_pricelist_ids', 'journal_id', 'invoice_journal_id', 'payment_method_ids')
|
||||
def _check_currencies(self):
|
||||
for config in self:
|
||||
if config.use_pricelist and config.pricelist_id and config.pricelist_id not in config.available_pricelist_ids:
|
||||
raise ValidationError(_("The default pricelist must be included in the available pricelists."))
|
||||
|
||||
# Check if the config's payment methods are compatible with its currency
|
||||
for pm in config.payment_method_ids:
|
||||
if pm.journal_id and pm.journal_id.currency_id and pm.journal_id.currency_id != config.currency_id:
|
||||
raise ValidationError(_("All payment methods must be in the same currency as the Sales Journal or the company currency if that is not set."))
|
||||
|
||||
if config.use_pricelist and config.pricelist_id and any(config.available_pricelist_ids.mapped(lambda pricelist: pricelist.currency_id != config.currency_id)):
|
||||
raise ValidationError(_("All available pricelists must be in the same currency as the company or"
|
||||
" as the Sales Journal set on this point of sale if you use"
|
||||
" the Accounting application."))
|
||||
if config.invoice_journal_id.currency_id and config.invoice_journal_id.currency_id != config.currency_id:
|
||||
raise ValidationError(_("The invoice journal must be in the same currency as the Sales Journal or the company currency if that is not set."))
|
||||
|
||||
@api.constrains('iface_start_categ_id', 'iface_available_categ_ids')
|
||||
def _check_start_categ(self):
|
||||
for config in self:
|
||||
allowed_categ_ids = config.iface_available_categ_ids or self.env['pos.category'].search([])
|
||||
if config.iface_start_categ_id and config.iface_start_categ_id not in allowed_categ_ids:
|
||||
raise ValidationError(_("Start category should belong in the available categories."))
|
||||
|
||||
def _check_payment_method_ids(self):
|
||||
self.ensure_one()
|
||||
if not self.payment_method_ids:
|
||||
raise ValidationError(
|
||||
_("You must have at least one payment method configured to launch a session.")
|
||||
)
|
||||
|
||||
@api.constrains('pricelist_id', 'available_pricelist_ids')
|
||||
def _check_pricelists(self):
|
||||
self._check_companies()
|
||||
self = self.sudo()
|
||||
if self.pricelist_id.company_id and self.pricelist_id.company_id != self.company_id:
|
||||
raise ValidationError(
|
||||
_("The default pricelist must belong to no company or the company of the point of sale."))
|
||||
|
||||
@api.constrains('company_id', 'available_pricelist_ids')
|
||||
def _check_companies(self):
|
||||
for config in self:
|
||||
if any(pricelist.company_id.id not in [False, config.company_id.id] for pricelist in config.available_pricelist_ids):
|
||||
raise ValidationError(_("The selected pricelists must belong to no company or the company of the point of sale."))
|
||||
|
||||
def _check_company_has_template(self):
|
||||
self.ensure_one()
|
||||
if not self.company_has_template:
|
||||
raise ValidationError(_("No chart of account configured, go to the \"configuration / settings\" menu, and "
|
||||
"install one from the Invoicing tab."))
|
||||
|
||||
@api.constrains('payment_method_ids')
|
||||
def _check_payment_method_ids_journal(self):
|
||||
for cash_method in self.payment_method_ids.filtered(lambda m: m.journal_id.type == 'cash'):
|
||||
if self.env['pos.config'].search([('id', '!=', self.id), ('payment_method_ids', 'in', cash_method.ids)]):
|
||||
raise ValidationError(_("This cash payment method is already used in another Point of Sale.\n"
|
||||
"A new cash payment method should be created for this Point of Sale."))
|
||||
if len(cash_method.journal_id.pos_payment_method_ids) > 1:
|
||||
raise ValidationError(_("You cannot use the same journal on multiples cash payment methods."))
|
||||
|
||||
@api.constrains('trusted_config_ids')
|
||||
def _check_trusted_config_ids_currency(self):
|
||||
for config in self:
|
||||
for trusted_config in config.trusted_config_ids:
|
||||
if trusted_config.currency_id != config.currency_id:
|
||||
raise ValidationError(_("You cannot share open orders with configuration that does not use the same currency."))
|
||||
|
||||
def _compute_display_name(self):
|
||||
for config in self:
|
||||
last_session = self.env['pos.session'].search([('config_id', '=', config.id)], limit=1)
|
||||
if (not last_session) or (last_session.state == 'closed'):
|
||||
config.display_name = _("%(pos_name)s (not used)", pos_name=config.name)
|
||||
else:
|
||||
config.display_name = f"{config.name} ({last_session.user_id.name})"
|
||||
|
||||
def _check_header_footer(self, values):
|
||||
if not self.env.is_admin() and {'is_header_or_footer', 'receipt_header', 'receipt_footer'} & values.keys():
|
||||
raise AccessError(_('Only administrators can edit receipt headers and footers'))
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
for vals in vals_list:
|
||||
self._check_header_footer(vals)
|
||||
IrSequence = self.env['ir.sequence'].sudo()
|
||||
val = {
|
||||
'name': _('POS Order %s', vals['name']),
|
||||
'padding': 4,
|
||||
'prefix': "%s/" % vals['name'],
|
||||
'code': "pos.order",
|
||||
'company_id': vals.get('company_id', False),
|
||||
}
|
||||
# force sequence_id field to new pos.order sequence
|
||||
vals['sequence_id'] = IrSequence.create(val).id
|
||||
|
||||
val.update(name=_('POS order line %s', vals['name']), code='pos.order.line')
|
||||
vals['sequence_line_id'] = IrSequence.create(val).id
|
||||
pos_configs = super().create(vals_list)
|
||||
pos_configs.sudo()._check_modules_to_install()
|
||||
pos_configs.sudo()._check_groups_implied()
|
||||
pos_configs._update_preparation_printers_menuitem_visibility()
|
||||
# If you plan to add something after this, use a new environment. The one above is no longer valid after the modules install.
|
||||
return pos_configs
|
||||
|
||||
def _reset_default_on_vals(self, vals):
|
||||
if 'tip_product_id' in vals and not vals['tip_product_id'] and 'iface_tipproduct' in vals and vals['iface_tipproduct']:
|
||||
default_product = self.env.ref('point_of_sale.product_product_tip', False)
|
||||
if default_product:
|
||||
vals['tip_product_id'] = default_product.id
|
||||
else:
|
||||
raise UserError(_('The default tip product is missing. Please manually specify the tip product. (See Tips field.)'))
|
||||
|
||||
def _update_preparation_printers_menuitem_visibility(self):
|
||||
prepa_printers_menuitem = self.sudo().env.ref('point_of_sale.menu_pos_preparation_printer', raise_if_not_found=False)
|
||||
if prepa_printers_menuitem:
|
||||
prepa_printers_menuitem.active = self.sudo().env['pos.config'].search_count([('is_order_printer', '=', True)], limit=1) > 0
|
||||
|
||||
def write(self, vals):
|
||||
self._check_header_footer(vals)
|
||||
self._reset_default_on_vals(vals)
|
||||
if ('is_order_printer' in vals and not vals['is_order_printer']):
|
||||
vals['printer_ids'] = [fields.Command.clear()]
|
||||
|
||||
bypass_categories_forbidden_change = self.env.context.get('bypass_categories_forbidden_change', False)
|
||||
bypass_payment_method_ids_forbidden_change = self.env.context.get('bypass_payment_method_ids_forbidden_change', False)
|
||||
|
||||
opened_session = self.mapped('session_ids').filtered(lambda s: s.state != 'closed')
|
||||
if opened_session:
|
||||
forbidden_fields = []
|
||||
for key in self._get_forbidden_change_fields():
|
||||
if key in vals.keys():
|
||||
if bypass_categories_forbidden_change and key in ('limit_categories', 'iface_available_categ_ids'):
|
||||
continue
|
||||
if bypass_payment_method_ids_forbidden_change and key == 'payment_method_ids':
|
||||
continue
|
||||
if key == 'use_pricelist' and vals[key]:
|
||||
continue
|
||||
if key == 'available_pricelist_ids':
|
||||
removed_pricelist = set(self.available_pricelist_ids.ids) - set(vals[key][0][2])
|
||||
if len(removed_pricelist) == 0:
|
||||
continue
|
||||
field_name = self._fields[key].get_description(self.env)["string"]
|
||||
forbidden_fields.append(field_name)
|
||||
if len(forbidden_fields) > 0:
|
||||
raise UserError(_(
|
||||
"Unable to modify this PoS Configuration because you can't modify %s while a session is open.",
|
||||
", ".join(forbidden_fields)
|
||||
))
|
||||
|
||||
self._preprocess_x2many_vals_from_settings_view(vals)
|
||||
vals = self._keep_new_vals(vals)
|
||||
result = super(PosConfig, self).write(vals)
|
||||
|
||||
self.sudo()._set_fiscal_position()
|
||||
self.sudo()._check_modules_to_install()
|
||||
self.sudo()._check_groups_implied()
|
||||
if 'is_order_printer' in vals:
|
||||
self._update_preparation_printers_menuitem_visibility()
|
||||
return result
|
||||
|
||||
def _preprocess_x2many_vals_from_settings_view(self, vals):
|
||||
""" From the res.config.settings view, changes in the x2many fields always result to an array of link commands or a single set command.
|
||||
- As a result, the items that should be unlinked are not properly unlinked.
|
||||
- So before doing the write, we inspect the commands to determine which records should be unlinked.
|
||||
- We only care about the link command.
|
||||
- We can consider set command as absolute as it will replace all.
|
||||
"""
|
||||
from_settings_view = self.env.context.get('from_settings_view')
|
||||
if not from_settings_view:
|
||||
# If vals is not from the settings view, we don't need to preprocess.
|
||||
return
|
||||
|
||||
# Only ensure one when write is from settings view.
|
||||
self.ensure_one()
|
||||
|
||||
fields_to_preprocess = []
|
||||
for f in self.fields_get([]).values():
|
||||
if f['type'] in ['many2many', 'one2many']:
|
||||
fields_to_preprocess.append(f['name'])
|
||||
|
||||
for x2many_field in fields_to_preprocess:
|
||||
if x2many_field in vals:
|
||||
linked_ids = set(self[x2many_field].ids)
|
||||
|
||||
for command in vals[x2many_field]:
|
||||
if command[0] == 4:
|
||||
_id = command[1]
|
||||
if _id in linked_ids:
|
||||
linked_ids.remove(_id)
|
||||
|
||||
# Remaining items in linked_ids should be unlinked.
|
||||
unlink_commands = [Command.unlink(_id) for _id in linked_ids]
|
||||
|
||||
vals[x2many_field] = unlink_commands + vals[x2many_field]
|
||||
|
||||
def _keep_new_vals(self, vals):
|
||||
""" Keep values in vals that are different than
|
||||
self's values.
|
||||
"""
|
||||
from_settings_view = self.env.context.get('from_settings_view')
|
||||
if not from_settings_view:
|
||||
return vals
|
||||
new_vals = {}
|
||||
for field, val in vals.items():
|
||||
config_field = self._fields.get(field)
|
||||
if config_field:
|
||||
cache_value = config_field.convert_to_cache(val, self)
|
||||
record_value = config_field.convert_to_record(cache_value, self)
|
||||
if record_value != self[field]:
|
||||
new_vals[field] = val
|
||||
return new_vals
|
||||
|
||||
def _get_forbidden_change_fields(self):
|
||||
forbidden_keys = ['module_pos_hr', 'module_pos_restaurant', 'available_pricelist_ids',
|
||||
'limit_categories', 'iface_available_categ_ids', 'use_pricelist', 'module_pos_discount',
|
||||
'payment_method_ids', 'iface_tipproduc']
|
||||
return forbidden_keys
|
||||
|
||||
def unlink(self):
|
||||
# Delete the pos.config records first then delete the sequences linked to them
|
||||
sequences_to_delete = self.sequence_id | self.sequence_line_id
|
||||
res = super(PosConfig, self).unlink()
|
||||
sequences_to_delete.unlink()
|
||||
return res
|
||||
|
||||
# TODO-JCB: Maybe we can move this logic in `_reset_default_on_vals`
|
||||
def _set_fiscal_position(self):
|
||||
for config in self:
|
||||
if config.tax_regime_selection and config.default_fiscal_position_id and (config.default_fiscal_position_id.id not in config.fiscal_position_ids.ids):
|
||||
config.fiscal_position_ids = [(4, config.default_fiscal_position_id.id)]
|
||||
elif not config.tax_regime_selection and config.fiscal_position_ids.ids:
|
||||
config.fiscal_position_ids = [(5, 0, 0)]
|
||||
|
||||
def _check_modules_to_install(self):
|
||||
# determine modules to install
|
||||
expected = [
|
||||
fname[7:] # 'module_account' -> 'account'
|
||||
for fname in self._fields
|
||||
if fname.startswith('module_')
|
||||
if any(pos_config[fname] for pos_config in self)
|
||||
]
|
||||
if expected:
|
||||
STATES = ('installed', 'to install', 'to upgrade')
|
||||
modules = self.env['ir.module.module'].sudo().search([('name', 'in', expected)])
|
||||
modules = modules.filtered(lambda module: module.state not in STATES)
|
||||
if modules:
|
||||
modules.button_immediate_install()
|
||||
# just in case we want to do something if we install a module. (like a refresh ...)
|
||||
return True
|
||||
return False
|
||||
|
||||
def _check_groups_implied(self):
|
||||
for pos_config in self:
|
||||
for field_name in [f for f in pos_config._fields if f.startswith('group_')]:
|
||||
field = pos_config._fields[field_name]
|
||||
if field.type in ('boolean', 'selection') and hasattr(field, 'implied_group'):
|
||||
field_group_xmlids = getattr(field, 'group', 'base.group_user').split(',')
|
||||
field_groups = self.env['res.groups'].concat(*(self.env.ref(it) for it in field_group_xmlids))
|
||||
field_groups.write({'implied_ids': [(4, self.env.ref(field.implied_group).id)]})
|
||||
|
||||
|
||||
def execute(self):
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'reload',
|
||||
}
|
||||
|
||||
def _force_http(self):
|
||||
enforce_https = self.env['ir.config_parameter'].sudo().get_param('point_of_sale.enforce_https')
|
||||
if not enforce_https and (self.other_devices or self.printer_ids.filtered(lambda pt: pt.printer_type == 'epson_epos')):
|
||||
return True
|
||||
return False
|
||||
|
||||
# Methods to open the POS
|
||||
def _action_to_open_ui(self):
|
||||
if not self.current_session_id:
|
||||
self.env['pos.session'].create({'user_id': self.env.uid, 'config_id': self.id})
|
||||
path = '/pos/web' if self._force_http() else '/pos/ui'
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'url': path + '?config_id=%d' % self.id,
|
||||
'target': 'self',
|
||||
}
|
||||
|
||||
def _check_before_creating_new_session(self):
|
||||
self._check_company_has_template()
|
||||
self._check_pricelists()
|
||||
self._check_company_payment()
|
||||
self._check_currencies()
|
||||
self._check_profit_loss_cash_journal()
|
||||
self._check_payment_method_ids()
|
||||
|
||||
def open_ui(self):
|
||||
"""Open the pos interface with config_id as an extra argument.
|
||||
|
||||
In vanilla PoS each user can only have one active session, therefore it was not needed to pass the config_id
|
||||
on opening a session. It is also possible to login to sessions created by other users.
|
||||
|
||||
:returns: dict
|
||||
"""
|
||||
self.ensure_one()
|
||||
if not self.current_session_id:
|
||||
self._check_before_creating_new_session()
|
||||
self._validate_fields(self._fields)
|
||||
|
||||
return self._action_to_open_ui()
|
||||
|
||||
def open_existing_session_cb(self):
|
||||
""" close session button
|
||||
|
||||
access session form to validate entries
|
||||
"""
|
||||
self.ensure_one()
|
||||
return self._open_session(self.current_session_id.id)
|
||||
|
||||
def _open_session(self, session_id):
|
||||
self._check_pricelists() # The pricelist company might have changed after the first opening of the session
|
||||
return {
|
||||
'name': _('Session'),
|
||||
'view_mode': 'form,tree',
|
||||
'res_model': 'pos.session',
|
||||
'res_id': session_id,
|
||||
'view_id': False,
|
||||
'type': 'ir.actions.act_window',
|
||||
}
|
||||
|
||||
def open_opened_rescue_session_form(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
'res_model': 'pos.session',
|
||||
'view_mode': 'form',
|
||||
'res_id': self.session_ids.filtered(lambda s: s.state != 'closed' and s.rescue).id,
|
||||
'type': 'ir.actions.act_window',
|
||||
}
|
||||
|
||||
# All following methods are made to create data needed in POS, when a localisation
|
||||
# is installed, or if POS is installed on database having companies that already have
|
||||
# a localisation installed
|
||||
@api.model
|
||||
def post_install_pos_localisation(self, companies=False):
|
||||
self = self.sudo()
|
||||
if not companies:
|
||||
companies = self.env['res.company'].search([])
|
||||
for company in companies.filtered('chart_template'):
|
||||
domain = AND([
|
||||
[('company_id', '=', company.id), ('module_pos_restaurant', '=', False)],
|
||||
OR([[('active', '=', True)], [('active', '=', False)]]),
|
||||
])
|
||||
pos_configs = self.search(domain)
|
||||
if not pos_configs:
|
||||
self = self.with_company(company)
|
||||
pos_configs = self.env['pos.config'].create({
|
||||
'name': _('Shop'),
|
||||
'company_id': company.id,
|
||||
'module_pos_restaurant': False,
|
||||
})
|
||||
pos_configs.setup_defaults(company)
|
||||
|
||||
def setup_defaults(self, company):
|
||||
"""Extend this method to customize the existing pos.config of the company during the installation
|
||||
of a localisation.
|
||||
|
||||
:param self pos.config: pos.config records present in the company during the installation of localisation.
|
||||
:param company res.company: the single company where the pos.config defaults will be setup.
|
||||
"""
|
||||
self.assign_payment_journals(company)
|
||||
self.generate_pos_journal(company)
|
||||
self.setup_invoice_journal(company)
|
||||
|
||||
def assign_payment_journals(self, company):
|
||||
for pos_config in self:
|
||||
if pos_config.payment_method_ids or pos_config.has_active_session:
|
||||
continue
|
||||
cash_journal = self.env['account.journal'].search([
|
||||
*self.env['account.journal']._check_company_domain(company),
|
||||
('type', '=', 'cash'),
|
||||
('currency_id', 'in', [pos_config.currency_id.id, False]),
|
||||
], limit=1)
|
||||
bank_journal = self.env['account.journal'].search([
|
||||
*self.env['account.journal']._check_company_domain(company),
|
||||
('type', '=', 'bank'),
|
||||
('currency_id', 'in', [pos_config.currency_id.id, False]),
|
||||
], limit=1)
|
||||
payment_methods = self.env['pos.payment.method']
|
||||
if cash_journal and len(cash_journal.pos_payment_method_ids.ids) == 0:
|
||||
payment_methods |= payment_methods.create({
|
||||
'name': _('Cash'),
|
||||
'journal_id': cash_journal.id,
|
||||
'company_id': company.id,
|
||||
})
|
||||
if bank_journal:
|
||||
payment_methods |= payment_methods.create({
|
||||
'name': _('Bank'),
|
||||
'journal_id': bank_journal.id,
|
||||
'company_id': company.id,
|
||||
})
|
||||
payment_methods |= payment_methods.create({
|
||||
'name': _('Customer Account'),
|
||||
'company_id': company.id,
|
||||
'split_transactions': True,
|
||||
})
|
||||
pos_config.write({'payment_method_ids': [(6, 0, payment_methods.ids)]})
|
||||
|
||||
def generate_pos_journal(self, company):
|
||||
for pos_config in self:
|
||||
if pos_config.journal_id:
|
||||
continue
|
||||
pos_journal = self.env['account.journal'].search([
|
||||
*self.env['account.journal']._check_company_domain(company),
|
||||
('code', '=', 'POSS'),
|
||||
])
|
||||
if not pos_journal:
|
||||
pos_journal = self.env['account.journal'].create({
|
||||
'type': 'general',
|
||||
'name': _('Point of Sale'),
|
||||
'code': 'POSS',
|
||||
'company_id': company.id,
|
||||
'sequence': 20
|
||||
})
|
||||
pos_config.write({'journal_id': pos_journal.id})
|
||||
|
||||
def setup_invoice_journal(self, company):
|
||||
for pos_config in self:
|
||||
invoice_journal_id = pos_config.invoice_journal_id or self.env['account.journal'].search([
|
||||
*self.env['account.journal']._check_company_domain(company),
|
||||
('type', '=', 'sale'),
|
||||
], limit=1)
|
||||
if invoice_journal_id:
|
||||
pos_config.write({'invoice_journal_id': invoice_journal_id.id})
|
||||
|
||||
def _get_available_product_domain(self):
|
||||
domain = [
|
||||
*self.env['product.product']._check_company_domain(self.company_id),
|
||||
('available_in_pos', '=', True),
|
||||
('sale_ok', '=', True),
|
||||
]
|
||||
if self.limit_categories and self.iface_available_categ_ids:
|
||||
domain.append(('pos_categ_ids', 'in', self.iface_available_categ_ids.ids))
|
||||
if self.iface_tipproduct:
|
||||
domain = OR([domain, [('id', '=', self.tip_product_id.id)]])
|
||||
return domain
|
||||
|
||||
def _link_same_non_cash_payment_methods_if_exists(self, source_config_ref_id):
|
||||
src_cfg = self.env.ref(source_config_ref_id, raise_if_not_found=False)
|
||||
if src_cfg and src_cfg.company_id == self.company_id:
|
||||
self._link_same_non_cash_payment_methods(src_cfg)
|
||||
|
||||
def _link_same_non_cash_payment_methods(self, source_config):
|
||||
pms = source_config.payment_method_ids.filtered(lambda pm: not pm.is_cash_count)
|
||||
if pms:
|
||||
self.payment_method_ids = [Command.link(pm.id) for pm in pms]
|
||||
|
||||
def _is_journal_exist(self, journal_code, name, company_id):
|
||||
account_journal = self.env['account.journal']
|
||||
existing_journal = account_journal.search([
|
||||
('name', '=', name),
|
||||
('code', '=', journal_code),
|
||||
('company_id', '=', company_id),
|
||||
], limit=1)
|
||||
|
||||
return existing_journal.id or account_journal.create({
|
||||
'name': name,
|
||||
'code': journal_code,
|
||||
'type': 'cash',
|
||||
'company_id': company_id,
|
||||
}).id
|
||||
|
||||
def _is_pos_pm_exist(self, name, journal_id, company_id):
|
||||
pos_payment = self.env['pos.payment.method']
|
||||
existing_pos_cash_pm = pos_payment.search([
|
||||
('name', '=', name),
|
||||
('journal_id', '=', journal_id),
|
||||
('company_id', '=', company_id),
|
||||
], limit=1)
|
||||
|
||||
return existing_pos_cash_pm.id or pos_payment.create({
|
||||
'name': name,
|
||||
'journal_id': journal_id,
|
||||
'company_id': company_id,
|
||||
}).id
|
||||
|
||||
def _ensure_cash_payment_method(self, journal_code, name):
|
||||
self.ensure_one()
|
||||
if not self.company_id.chart_template or self.payment_method_ids.filtered('is_cash_count'):
|
||||
return
|
||||
company_id = self.company_id.id
|
||||
cash_journal_id = self._is_journal_exist(journal_code, name, company_id)
|
||||
cash_pm_id = self._is_pos_pm_exist(name, cash_journal_id, company_id)
|
||||
self.payment_method_ids = [Command.link(cash_pm_id)]
|
||||
|
||||
def get_limited_products_loading(self, fields):
|
||||
tables, where_clause, params = self.env['product.product']._where_calc(
|
||||
self._get_available_product_domain()
|
||||
).get_sql()
|
||||
query = f"""
|
||||
WITH pm AS (
|
||||
SELECT product_id,
|
||||
Max(write_date) date
|
||||
FROM stock_move_line
|
||||
GROUP BY product_id
|
||||
ORDER BY date DESC
|
||||
)
|
||||
SELECT product_product.id
|
||||
FROM {tables}
|
||||
LEFT JOIN pm ON product_product.id=pm.product_id
|
||||
WHERE {where_clause}
|
||||
ORDER BY product_product__product_tmpl_id.priority DESC,
|
||||
case when product_product__product_tmpl_id.detailed_type = 'service' then 1 else 0 end DESC,
|
||||
pm.date DESC NULLS LAST,
|
||||
product_product.write_date
|
||||
LIMIT %s
|
||||
"""
|
||||
self.env.cr.execute(query, params + [self.get_limited_product_count()])
|
||||
product_ids = self.env.cr.fetchall()
|
||||
products = self.env['product.product'].search([('id', 'in', product_ids)])
|
||||
product_combo = products.filtered(lambda p: p['detailed_type'] == 'combo')
|
||||
product_in_combo = product_combo.combo_ids.combo_line_ids.product_id
|
||||
products_available = products | product_in_combo
|
||||
return products_available.read(fields)
|
||||
|
||||
def get_limited_product_count(self):
|
||||
default_limit = 20000
|
||||
config_param = self.env['ir.config_parameter'].sudo().get_param('point_of_sale.limited_product_count', default_limit)
|
||||
try:
|
||||
return int(config_param)
|
||||
except (TypeError, ValueError, OverflowError):
|
||||
return default_limit
|
||||
|
||||
def toggle_images(self, for_products, for_categories):
|
||||
self.env['ir.config_parameter'].sudo().set_param('point_of_sale.show_product_images', for_products)
|
||||
self.env['ir.config_parameter'].sudo().set_param('point_of_sale.show_category_images', for_categories)
|
||||
|
||||
def get_limited_partners_loading(self):
|
||||
self.env.cr.execute("""
|
||||
WITH pm AS
|
||||
(
|
||||
SELECT partner_id,
|
||||
Count(partner_id) order_count
|
||||
FROM pos_order
|
||||
GROUP BY partner_id)
|
||||
SELECT id
|
||||
FROM res_partner AS partner
|
||||
LEFT JOIN pm
|
||||
ON (
|
||||
partner.id = pm.partner_id)
|
||||
WHERE (
|
||||
partner.company_id=%s OR partner.company_id IS NULL
|
||||
)
|
||||
ORDER BY COALESCE(pm.order_count, 0) DESC,
|
||||
NAME limit %s;
|
||||
""", [self.company_id.id, str(100)])
|
||||
result = self.env.cr.fetchall()
|
||||
return result
|
||||
|
||||
def action_pos_config_modal_edit(self):
|
||||
return {
|
||||
'view_mode': 'form',
|
||||
'res_model': 'pos.config',
|
||||
'type': 'ir.actions.act_window',
|
||||
'target': 'new',
|
||||
'res_id': self.id,
|
||||
'context': {'pos_config_open_modal': True},
|
||||
}
|
||||
|
||||
def _add_trusted_config_id(self, config_id):
|
||||
self.trusted_config_ids += config_id
|
||||
|
||||
def _remove_trusted_config_id(self, config_id):
|
||||
self.trusted_config_ids -= config_id
|
||||
|
||||
@api.model
|
||||
def add_cash_payment_method(self):
|
||||
companies = self.env['res.company'].search([])
|
||||
for company in companies.filtered('chart_template'):
|
||||
pos_configs = self.search([
|
||||
*self._check_company_domain(company),
|
||||
])
|
||||
journal_counter = 1
|
||||
for pos_config in pos_configs:
|
||||
if pos_config.payment_method_ids.filtered('is_cash_count'):
|
||||
continue
|
||||
journal_counter += self.env['account.journal'].search_count([
|
||||
*self.env['account.journal']._check_company_domain(company),
|
||||
('type', '=', 'cash'),
|
||||
('pos_payment_method_ids', '=', False),
|
||||
])
|
||||
cash_journal = self.env['account.journal'].create({
|
||||
'name': _('Cash %s', journal_counter),
|
||||
'code': 'RCSH%s' % journal_counter,
|
||||
'type': 'cash',
|
||||
'company_id': company.id
|
||||
})
|
||||
journal_counter += 1
|
||||
payment_methods = pos_config.payment_method_ids
|
||||
payment_methods |= self.env['pos.payment.method'].create({
|
||||
'name': _('Cash %s', pos_config.name),
|
||||
'journal_id': cash_journal.id,
|
||||
'company_id': company.id,
|
||||
})
|
||||
pos_config.with_context(bypass_payment_method_ids_forbidden_change=True).write({'payment_method_ids': [(6, 0, payment_methods.ids)]})
|
||||
|
||||
def _get_payment_method(self, payment_type):
|
||||
for pm in self.payment_method_ids:
|
||||
if pm.type == payment_type:
|
||||
return pm
|
||||
return False
|
||||
|
||||
def _get_special_products(self):
|
||||
default_discount_product = self.env.ref('point_of_sale.product_product_consumable', raise_if_not_found=False) or self.env['product.product']
|
||||
default_tip_product = self.env.ref('point_of_sale.product_product_tip', raise_if_not_found=False) or self.env['product.product']
|
||||
return default_tip_product | default_discount_product
|
1581
models/pos_order.py
Normal file
1581
models/pos_order.py
Normal file
File diff suppressed because it is too large
Load Diff
96
models/pos_payment.py
Normal file
96
models/pos_payment.py
Normal file
@ -0,0 +1,96 @@
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.tools import formatLang, float_is_zero
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class PosPayment(models.Model):
|
||||
""" Used to register payments made in a pos.order.
|
||||
|
||||
See `payment_ids` field of pos.order model.
|
||||
The main characteristics of pos.payment can be read from
|
||||
`payment_method_id`.
|
||||
"""
|
||||
|
||||
_name = "pos.payment"
|
||||
_description = "Point of Sale Payments"
|
||||
_order = "id desc"
|
||||
|
||||
name = fields.Char(string='Label', readonly=True)
|
||||
pos_order_id = fields.Many2one('pos.order', string='Order', required=True, index=True)
|
||||
amount = fields.Monetary(string='Amount', required=True, currency_field='currency_id', readonly=True, help="Total amount of the payment.")
|
||||
payment_method_id = fields.Many2one('pos.payment.method', string='Payment Method', required=True)
|
||||
payment_date = fields.Datetime(string='Date', required=True, readonly=True, default=lambda self: fields.Datetime.now())
|
||||
currency_id = fields.Many2one('res.currency', string='Currency', related='pos_order_id.currency_id')
|
||||
currency_rate = fields.Float(string='Conversion Rate', related='pos_order_id.currency_rate', help='Conversion rate from company currency to order currency.')
|
||||
partner_id = fields.Many2one('res.partner', string='Customer', related='pos_order_id.partner_id')
|
||||
session_id = fields.Many2one('pos.session', string='Session', related='pos_order_id.session_id', store=True, index=True)
|
||||
company_id = fields.Many2one('res.company', string='Company', related='pos_order_id.company_id', store=True)
|
||||
card_type = fields.Char('Type of card used')
|
||||
cardholder_name = fields.Char('Cardholder Name')
|
||||
transaction_id = fields.Char('Payment Transaction ID')
|
||||
payment_status = fields.Char('Payment Status')
|
||||
ticket = fields.Char('Payment Receipt Info')
|
||||
is_change = fields.Boolean(string='Is this payment change?', default=False)
|
||||
account_move_id = fields.Many2one('account.move')
|
||||
|
||||
@api.depends('amount', 'currency_id')
|
||||
def _compute_display_name(self):
|
||||
for payment in self:
|
||||
if payment.name:
|
||||
payment.display_name = f'{payment.name} {formatLang(self.env, payment.amount, currency_obj=payment.currency_id)}'
|
||||
else:
|
||||
payment.display_name = formatLang(self.env, payment.amount, currency_obj=payment.currency_id)
|
||||
|
||||
@api.constrains('payment_method_id')
|
||||
def _check_payment_method_id(self):
|
||||
for payment in self:
|
||||
if payment.payment_method_id not in payment.session_id.config_id.payment_method_ids:
|
||||
raise ValidationError(_('The payment method selected is not allowed in the config of the POS session.'))
|
||||
|
||||
def _export_for_ui(self, payment):
|
||||
return {
|
||||
'payment_method_id': payment.payment_method_id.id,
|
||||
'amount': payment.amount,
|
||||
'pos_order_id': payment.pos_order_id.id,
|
||||
'payment_status': payment.payment_status,
|
||||
'card_type': payment.card_type,
|
||||
'cardholder_name': payment.cardholder_name,
|
||||
'transaction_id': payment.transaction_id,
|
||||
'ticket': payment.ticket,
|
||||
'is_change': payment.is_change,
|
||||
}
|
||||
|
||||
def export_for_ui(self):
|
||||
return self.mapped(self._export_for_ui) if self else []
|
||||
|
||||
def _create_payment_moves(self):
|
||||
result = self.env['account.move']
|
||||
for payment in self:
|
||||
order = payment.pos_order_id
|
||||
payment_method = payment.payment_method_id
|
||||
if payment_method.type == 'pay_later' or float_is_zero(payment.amount, precision_rounding=order.currency_id.rounding):
|
||||
continue
|
||||
accounting_partner = self.env["res.partner"]._find_accounting_partner(payment.partner_id)
|
||||
pos_session = order.session_id
|
||||
journal = pos_session.config_id.journal_id
|
||||
payment_move = self.env['account.move'].with_context(default_journal_id=journal.id).create({
|
||||
'journal_id': journal.id,
|
||||
'date': fields.Date.context_today(order, order.date_order),
|
||||
'ref': _('Invoice payment for %s (%s) using %s', order.name, order.account_move.name, payment_method.name),
|
||||
'pos_payment_ids': payment.ids,
|
||||
})
|
||||
result |= payment_move
|
||||
payment.write({'account_move_id': payment_move.id})
|
||||
amounts = pos_session._update_amounts({'amount': 0, 'amount_converted': 0}, {'amount': payment.amount}, payment.payment_date)
|
||||
credit_line_vals = pos_session._credit_amounts({
|
||||
'account_id': accounting_partner.with_company(order.company_id).property_account_receivable_id.id, # The field being company dependant, we need to make sure the right value is received.
|
||||
'partner_id': accounting_partner.id,
|
||||
'move_id': payment_move.id,
|
||||
}, amounts['amount'], amounts['amount_converted'])
|
||||
debit_line_vals = pos_session._debit_amounts({
|
||||
'account_id': pos_session.company_id.account_default_pos_receivable_account_id.id,
|
||||
'move_id': payment_move.id,
|
||||
}, amounts['amount'], amounts['amount_converted'])
|
||||
self.env['account.move.line'].create([credit_line_vals, debit_line_vals])
|
||||
payment_move._post()
|
||||
return result
|
99
models/pos_payment_method.py
Normal file
99
models/pos_payment_method.py
Normal file
@ -0,0 +1,99 @@
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class PosPaymentMethod(models.Model):
|
||||
_name = "pos.payment.method"
|
||||
_description = "Point of Sale Payment Methods"
|
||||
_order = "sequence, id"
|
||||
|
||||
def _get_payment_terminal_selection(self):
|
||||
return []
|
||||
|
||||
name = fields.Char(string="Method", required=True, translate=True, help='Defines the name of the payment method that will be displayed in the Point of Sale when the payments are selected.')
|
||||
sequence = fields.Integer(copy=False)
|
||||
outstanding_account_id = fields.Many2one('account.account',
|
||||
string='Outstanding Account',
|
||||
ondelete='restrict',
|
||||
help='Leave empty to use the default account from the company setting.\n'
|
||||
'Account used as outstanding account when creating accounting payment records for bank payments.')
|
||||
receivable_account_id = fields.Many2one('account.account',
|
||||
string='Intermediary Account',
|
||||
ondelete='restrict',
|
||||
domain=[('reconcile', '=', True), ('account_type', '=', 'asset_receivable')],
|
||||
help="Leave empty to use the default account from the company setting.\n"
|
||||
"Overrides the company's receivable account (for Point of Sale) used in the journal entries.")
|
||||
is_cash_count = fields.Boolean(string='Cash', compute="_compute_is_cash_count", store=True)
|
||||
journal_id = fields.Many2one('account.journal',
|
||||
string='Journal',
|
||||
domain=['|', '&', ('type', '=', 'cash'), ('pos_payment_method_ids', '=', False), ('type', '=', 'bank')],
|
||||
ondelete='restrict',
|
||||
help='Leave empty to use the receivable account of customer.\n'
|
||||
'Defines the journal where to book the accumulated payments (or individual payment if Identify Customer is true) after closing the session.\n'
|
||||
'For cash journal, we directly write to the default account in the journal via statement lines.\n'
|
||||
'For bank journal, we write to the outstanding account specified in this payment method.\n'
|
||||
'Only cash and bank journals are allowed.')
|
||||
split_transactions = fields.Boolean(
|
||||
string='Identify Customer',
|
||||
default=False,
|
||||
help='Forces to set a customer when using this payment method and splits the journal entries for each customer. It could slow down the closing process.')
|
||||
open_session_ids = fields.Many2many('pos.session', string='Pos Sessions', compute='_compute_open_session_ids', help='Open PoS sessions that are using this payment method.')
|
||||
config_ids = fields.Many2many('pos.config', string='Point of Sale')
|
||||
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)
|
||||
use_payment_terminal = fields.Selection(selection=lambda self: self._get_payment_terminal_selection(), string='Use a Payment Terminal', help='Record payments with a terminal on this journal.')
|
||||
# used to hide use_payment_terminal when no payment interfaces are installed
|
||||
hide_use_payment_terminal = fields.Boolean(compute='_compute_hide_use_payment_terminal')
|
||||
active = fields.Boolean(default=True)
|
||||
type = fields.Selection(selection=[('cash', 'Cash'), ('bank', 'Bank'), ('pay_later', 'Customer Account')], compute="_compute_type")
|
||||
image = fields.Image("Image", max_width=50, max_height=50)
|
||||
|
||||
@api.depends('type')
|
||||
def _compute_hide_use_payment_terminal(self):
|
||||
no_terminals = not bool(self._fields['use_payment_terminal'].selection(self))
|
||||
for payment_method in self:
|
||||
payment_method.hide_use_payment_terminal = no_terminals or payment_method.type in ('cash', 'pay_later')
|
||||
|
||||
@api.onchange('use_payment_terminal')
|
||||
def _onchange_use_payment_terminal(self):
|
||||
"""Used by inheriting model to unset the value of the field related to the unselected payment terminal."""
|
||||
pass
|
||||
|
||||
@api.depends('config_ids')
|
||||
def _compute_open_session_ids(self):
|
||||
for payment_method in self:
|
||||
payment_method.open_session_ids = self.env['pos.session'].search([('config_id', 'in', payment_method.config_ids.ids), ('state', '!=', 'closed')])
|
||||
|
||||
@api.depends('journal_id', 'split_transactions')
|
||||
def _compute_type(self):
|
||||
for pm in self:
|
||||
if pm.journal_id.type in {'cash', 'bank'}:
|
||||
pm.type = pm.journal_id.type
|
||||
else:
|
||||
pm.type = 'pay_later'
|
||||
|
||||
@api.onchange('journal_id')
|
||||
def _onchange_journal_id(self):
|
||||
for pm in self:
|
||||
if pm.journal_id and pm.journal_id.type not in ['cash', 'bank']:
|
||||
raise UserError(_("Only journals of type 'Cash' or 'Bank' could be used with payment methods."))
|
||||
if self.is_cash_count:
|
||||
self.use_payment_terminal = False
|
||||
|
||||
@api.depends('type')
|
||||
def _compute_is_cash_count(self):
|
||||
for pm in self:
|
||||
pm.is_cash_count = pm.type == 'cash'
|
||||
|
||||
def _is_write_forbidden(self, fields):
|
||||
whitelisted_fields = {'sequence'}
|
||||
return bool(fields - whitelisted_fields and self.open_session_ids)
|
||||
|
||||
def write(self, vals):
|
||||
if self._is_write_forbidden(set(vals.keys())):
|
||||
raise UserError(_('Please close and validate the following open PoS Sessions before modifying this payment method.\n'
|
||||
'Open sessions: %s', (' '.join(self.open_session_ids.mapped('name')),)))
|
||||
return super(PosPaymentMethod, self).write(vals)
|
||||
|
||||
def copy(self, default=None):
|
||||
default = dict(default or {}, config_ids=[(5, 0, 0)])
|
||||
return super().copy(default)
|
15
models/pos_printer.py
Normal file
15
models/pos_printer.py
Normal file
@ -0,0 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
class PosPrinter(models.Model):
|
||||
|
||||
_name = 'pos.printer'
|
||||
_description = 'Point of Sale Printer'
|
||||
|
||||
name = fields.Char('Printer Name', required=True, default='Printer', help='An internal identification of the printer')
|
||||
printer_type = fields.Selection(string='Printer Type', default='iot',
|
||||
selection=[('iot', ' Use a printer connected to the IoT Box')])
|
||||
proxy_ip = fields.Char('Proxy IP Address', help="The IP Address or hostname of the Printer's hardware proxy")
|
||||
product_categories_ids = fields.Many2many('pos.category', 'printer_category_rel', 'printer_id', 'category_id', string='Printed Product Categories')
|
2298
models/pos_session.py
Normal file
2298
models/pos_session.py
Normal file
File diff suppressed because it is too large
Load Diff
139
models/product.py
Normal file
139
models/product.py
Normal file
@ -0,0 +1,139 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
from itertools import groupby
|
||||
from operator import itemgetter
|
||||
from datetime import date
|
||||
|
||||
|
||||
class ProductTemplate(models.Model):
|
||||
_inherit = 'product.template'
|
||||
|
||||
available_in_pos = fields.Boolean(string='Available in POS', help='Check if you want this product to appear in the Point of Sale.', default=False)
|
||||
to_weight = fields.Boolean(string='To Weigh With Scale', help="Check if the product should be weighted using the hardware scale integration.")
|
||||
pos_categ_ids = fields.Many2many(
|
||||
'pos.category', string='Point of Sale Category',
|
||||
help="Category used in the Point of Sale.")
|
||||
combo_ids = fields.Many2many('pos.combo', string='Combinations')
|
||||
detailed_type = fields.Selection(selection_add=[
|
||||
('combo', 'Combo')
|
||||
], ondelete={'combo': 'set consu'})
|
||||
type = fields.Selection(selection_add=[
|
||||
('combo', 'Combo')
|
||||
], ondelete={'combo': 'set consu'})
|
||||
|
||||
@api.ondelete(at_uninstall=False)
|
||||
def _unlink_except_open_session(self):
|
||||
product_ctx = dict(self.env.context or {}, active_test=False)
|
||||
if self.with_context(product_ctx).search_count([('id', 'in', self.ids), ('available_in_pos', '=', True)]):
|
||||
if self.env['pos.session'].sudo().search_count([('state', '!=', 'closed')]):
|
||||
raise UserError(_("To delete a product, make sure all point of sale sessions are closed.\n\n"
|
||||
"Deleting a product available in a session would be like attempting to snatch a"
|
||||
"hamburger from a customer’s hand mid-bite; chaos will ensue as ketchup and mayo go flying everywhere!"))
|
||||
|
||||
@api.onchange('sale_ok')
|
||||
def _onchange_sale_ok(self):
|
||||
if not self.sale_ok:
|
||||
self.available_in_pos = False
|
||||
|
||||
@api.constrains('available_in_pos')
|
||||
def _check_combo_inclusions(self):
|
||||
for product in self:
|
||||
if not product.available_in_pos:
|
||||
combo_name = self.env['pos.combo.line'].search([('product_id', 'in', product.product_variant_ids.ids)], limit=1).combo_id.name
|
||||
if combo_name:
|
||||
raise UserError(_('You must first remove this product from the %s combo', combo_name))
|
||||
|
||||
class ProductProduct(models.Model):
|
||||
_inherit = 'product.product'
|
||||
|
||||
@api.ondelete(at_uninstall=False)
|
||||
def _unlink_except_active_pos_session(self):
|
||||
product_ctx = dict(self.env.context or {}, active_test=False)
|
||||
if self.env['pos.session'].sudo().search_count([('state', '!=', 'closed')]):
|
||||
if self.with_context(product_ctx).search_count([('id', 'in', self.ids), ('product_tmpl_id.available_in_pos', '=', True)]):
|
||||
raise UserError(_("To delete a product, make sure all point of sale sessions are closed.\n\n"
|
||||
"Deleting a product available in a session would be like attempting to snatch a"
|
||||
"hamburger from a customer’s hand mid-bite; chaos will ensue as ketchup and mayo go flying everywhere!"))
|
||||
|
||||
def get_product_info_pos(self, price, quantity, pos_config_id):
|
||||
self.ensure_one()
|
||||
config = self.env['pos.config'].browse(pos_config_id)
|
||||
|
||||
# Tax related
|
||||
taxes = self.taxes_id.compute_all(price, config.currency_id, quantity, self)
|
||||
grouped_taxes = {}
|
||||
for tax in taxes['taxes']:
|
||||
if tax['id'] in grouped_taxes:
|
||||
grouped_taxes[tax['id']]['amount'] += tax['amount']/quantity if quantity else 0
|
||||
else:
|
||||
grouped_taxes[tax['id']] = {
|
||||
'name': tax['name'],
|
||||
'amount': tax['amount']/quantity if quantity else 0
|
||||
}
|
||||
|
||||
all_prices = {
|
||||
'price_without_tax': taxes['total_excluded']/quantity if quantity else 0,
|
||||
'price_with_tax': taxes['total_included']/quantity if quantity else 0,
|
||||
'tax_details': list(grouped_taxes.values()),
|
||||
}
|
||||
|
||||
# Pricelists
|
||||
if config.use_pricelist:
|
||||
pricelists = config.available_pricelist_ids
|
||||
else:
|
||||
pricelists = config.pricelist_id
|
||||
price_per_pricelist_id = pricelists._price_get(self, quantity) if pricelists else False
|
||||
pricelist_list = [{'name': pl.name, 'price': price_per_pricelist_id[pl.id]} for pl in pricelists]
|
||||
|
||||
# Warehouses
|
||||
warehouse_list = [
|
||||
{'name': w.name,
|
||||
'available_quantity': self.with_context({'warehouse': w.id}).qty_available,
|
||||
'forecasted_quantity': self.with_context({'warehouse': w.id}).virtual_available,
|
||||
'uom': self.uom_name}
|
||||
for w in self.env['stock.warehouse'].search([])]
|
||||
|
||||
# Suppliers
|
||||
key = itemgetter('partner_id')
|
||||
supplier_list = []
|
||||
for key, group in groupby(sorted(self.seller_ids, key=key), key=key):
|
||||
for s in list(group):
|
||||
if not((s.date_start and s.date_start > date.today()) or (s.date_end and s.date_end < date.today()) or (s.min_qty > quantity)):
|
||||
supplier_list.append({
|
||||
'name': s.partner_id.name,
|
||||
'delay': s.delay,
|
||||
'price': s.price
|
||||
})
|
||||
break
|
||||
|
||||
# Variants
|
||||
variant_list = [{'name': attribute_line.attribute_id.name,
|
||||
'values': list(map(lambda attr_name: {'name': attr_name, 'search': '%s %s' % (self.name, attr_name)}, attribute_line.value_ids.mapped('name')))}
|
||||
for attribute_line in self.attribute_line_ids]
|
||||
|
||||
return {
|
||||
'all_prices': all_prices,
|
||||
'pricelists': pricelist_list,
|
||||
'warehouses': warehouse_list,
|
||||
'suppliers': supplier_list,
|
||||
'variants': variant_list
|
||||
}
|
||||
|
||||
class ProductAttributeCustomValue(models.Model):
|
||||
_inherit = "product.attribute.custom.value"
|
||||
|
||||
pos_order_line_id = fields.Many2one('pos.order.line', string="PoS Order Line", ondelete='cascade')
|
||||
|
||||
class UomCateg(models.Model):
|
||||
_inherit = 'uom.category'
|
||||
|
||||
is_pos_groupable = fields.Boolean(string='Group Products in POS',
|
||||
help="Check if you want to group products of this category in point of sale orders")
|
||||
|
||||
|
||||
class Uom(models.Model):
|
||||
_inherit = 'uom.uom'
|
||||
|
||||
is_pos_groupable = fields.Boolean(related='category_id.is_pos_groupable', readonly=False)
|
346
models/report_sale_details.py
Normal file
346
models/report_sale_details.py
Normal file
@ -0,0 +1,346 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from datetime import timedelta
|
||||
|
||||
import pytz
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.osv.expression import AND
|
||||
|
||||
class ReportSaleDetails(models.AbstractModel):
|
||||
|
||||
_name = 'report.point_of_sale.report_saledetails'
|
||||
_description = 'Point of Sale Details'
|
||||
|
||||
|
||||
@api.model
|
||||
def get_sale_details(self, date_start=False, date_stop=False, config_ids=False, session_ids=False):
|
||||
""" Serialise the orders of the requested time period, configs and sessions.
|
||||
:param date_start: The dateTime to start, default today 00:00:00.
|
||||
:type date_start: str.
|
||||
:param date_stop: The dateTime to stop, default date_start + 23:59:59.
|
||||
:type date_stop: str.
|
||||
:param config_ids: Pos Config id's to include.
|
||||
:type config_ids: list of numbers.
|
||||
:param session_ids: Pos Config id's to include.
|
||||
:type session_ids: list of numbers.
|
||||
:returns: dict -- Serialised sales.
|
||||
"""
|
||||
domain = [('state', 'in', ['paid', 'invoiced', 'done'])]
|
||||
if (session_ids):
|
||||
domain = AND([domain, [('session_id', 'in', session_ids)]])
|
||||
else:
|
||||
if date_start:
|
||||
date_start = fields.Datetime.from_string(date_start)
|
||||
else:
|
||||
# start by default today 00:00:00
|
||||
user_tz = pytz.timezone(self.env.context.get('tz') or self.env.user.tz or 'UTC')
|
||||
today = user_tz.localize(fields.Datetime.from_string(fields.Date.context_today(self)))
|
||||
date_start = today.astimezone(pytz.timezone('UTC')).replace(tzinfo=None)
|
||||
|
||||
if date_stop:
|
||||
date_stop = fields.Datetime.from_string(date_stop)
|
||||
# avoid a date_stop smaller than date_start
|
||||
if (date_stop < date_start):
|
||||
date_stop = date_start + timedelta(days=1, seconds=-1)
|
||||
else:
|
||||
# stop by default today 23:59:59
|
||||
date_stop = date_start + timedelta(days=1, seconds=-1)
|
||||
|
||||
domain = AND([domain,
|
||||
[('date_order', '>=', fields.Datetime.to_string(date_start)),
|
||||
('date_order', '<=', fields.Datetime.to_string(date_stop))]
|
||||
])
|
||||
|
||||
if config_ids:
|
||||
domain = AND([domain, [('config_id', 'in', config_ids)]])
|
||||
|
||||
orders = self.env['pos.order'].search(domain)
|
||||
|
||||
if config_ids:
|
||||
config_currencies = self.env['pos.config'].search([('id', 'in', config_ids)]).mapped('currency_id')
|
||||
else:
|
||||
config_currencies = self.env['pos.session'].search([('id', 'in', session_ids)]).mapped('config_id.currency_id')
|
||||
# If all the pos.config have the same currency, we can use it, else we use the company currency
|
||||
if config_currencies and all(i == config_currencies.ids[0] for i in config_currencies.ids):
|
||||
user_currency = config_currencies[0]
|
||||
else:
|
||||
user_currency = self.env.company.currency_id
|
||||
|
||||
total = 0.0
|
||||
products_sold = {}
|
||||
taxes = {}
|
||||
refund_done = {}
|
||||
refund_taxes = {}
|
||||
for order in orders:
|
||||
if user_currency != order.pricelist_id.currency_id:
|
||||
total += order.pricelist_id.currency_id._convert(
|
||||
order.amount_total, user_currency, order.company_id, order.date_order or fields.Date.today())
|
||||
else:
|
||||
total += order.amount_total
|
||||
currency = order.session_id.currency_id
|
||||
|
||||
for line in order.lines:
|
||||
if line.qty >= 0:
|
||||
products_sold, taxes = self._get_products_and_taxes_dict(line, products_sold, taxes, currency)
|
||||
else:
|
||||
refund_done, refund_taxes = self._get_products_and_taxes_dict(line, refund_done, refund_taxes, currency)
|
||||
|
||||
taxes_info = self._get_taxes_info(taxes)
|
||||
refund_taxes_info = self._get_taxes_info(refund_taxes)
|
||||
|
||||
payment_ids = self.env["pos.payment"].search([('pos_order_id', 'in', orders.ids)]).ids
|
||||
if payment_ids:
|
||||
self.env.cr.execute("""
|
||||
SELECT method.id as id, payment.session_id as session, COALESCE(method.name->>%s, method.name->>'en_US') as name, method.is_cash_count as cash,
|
||||
sum(amount) total, method.journal_id journal_id
|
||||
FROM pos_payment AS payment,
|
||||
pos_payment_method AS method
|
||||
WHERE payment.payment_method_id = method.id
|
||||
AND payment.id IN %s
|
||||
GROUP BY method.name, method.is_cash_count, payment.session_id, method.id, journal_id
|
||||
""", (self.env.lang, tuple(payment_ids),))
|
||||
payments = self.env.cr.dictfetchall()
|
||||
else:
|
||||
payments = []
|
||||
|
||||
configs = []
|
||||
sessions = []
|
||||
if config_ids:
|
||||
configs = self.env['pos.config'].search([('id', 'in', config_ids)])
|
||||
if session_ids:
|
||||
sessions = self.env['pos.session'].search([('id', 'in', session_ids)])
|
||||
else:
|
||||
sessions = self.env['pos.session'].search([('config_id', 'in', configs.ids), ('start_at', '>=', date_start), ('stop_at', '<=', date_stop)])
|
||||
else:
|
||||
sessions = self.env['pos.session'].search([('id', 'in', session_ids)])
|
||||
for session in sessions:
|
||||
configs.append(session.config_id)
|
||||
|
||||
for payment in payments:
|
||||
payment['count'] = False
|
||||
|
||||
for session in sessions:
|
||||
cash_counted = 0
|
||||
if session.cash_register_balance_end_real:
|
||||
cash_counted = session.cash_register_balance_end_real
|
||||
is_cash_method = False
|
||||
for payment in payments:
|
||||
if payment['session'] == session.id:
|
||||
if not payment['cash']:
|
||||
ref_value = "Closing difference in %s (%s)" % (payment['name'], session.name)
|
||||
account_move = self.env['account.move'].search([("ref", "=", ref_value)], limit=1)
|
||||
if account_move:
|
||||
payment_method = self.env['pos.payment.method'].browse(payment['id'])
|
||||
is_loss = any(l.account_id == payment_method.journal_id.loss_account_id for l in account_move.line_ids)
|
||||
is_profit = any(l.account_id == payment_method.journal_id.profit_account_id for l in account_move.line_ids)
|
||||
payment['final_count'] = payment['total']
|
||||
payment['money_difference'] = -account_move.amount_total if is_loss else account_move.amount_total
|
||||
payment['money_counted'] = payment['final_count'] + payment['money_difference']
|
||||
payment['cash_moves'] = []
|
||||
if is_profit:
|
||||
move_name = 'Difference observed during the counting (Profit)'
|
||||
payment['cash_moves'] = [{'name': move_name, 'amount': payment['money_difference']}]
|
||||
elif is_loss:
|
||||
move_name = 'Difference observed during the counting (Loss)'
|
||||
payment['cash_moves'] = [{'name': move_name, 'amount': payment['money_difference']}]
|
||||
payment['count'] = True
|
||||
else:
|
||||
is_cash_method = True
|
||||
previous_session = self.env['pos.session'].search([('id', '<', session.id), ('state', '=', 'closed'), ('config_id', '=', session.config_id.id)], limit=1)
|
||||
payment['final_count'] = payment['total'] + previous_session.cash_register_balance_end_real + session.cash_real_transaction
|
||||
payment['money_counted'] = cash_counted
|
||||
payment['money_difference'] = payment['money_counted'] - payment['final_count']
|
||||
cash_moves = self.env['account.bank.statement.line'].search([('pos_session_id', '=', session.id)])
|
||||
cash_in_out_list = []
|
||||
cash_in_count = 0
|
||||
cash_out_count = 0
|
||||
if session.cash_register_balance_start > 0:
|
||||
cash_in_out_list.append({
|
||||
'name': _('Cash Opening'),
|
||||
'amount': session.cash_register_balance_start,
|
||||
})
|
||||
for cash_move in cash_moves:
|
||||
if cash_move.amount > 0:
|
||||
cash_in_count += 1
|
||||
name = f'Cash in {cash_in_count}'
|
||||
else:
|
||||
cash_out_count += 1
|
||||
name = f'Cash out {cash_out_count}'
|
||||
if cash_move.move_id.journal_id.id == payment['journal_id']:
|
||||
cash_in_out_list.append({
|
||||
'name': cash_move.payment_ref if cash_move.payment_ref else name,
|
||||
'amount': cash_move.amount
|
||||
})
|
||||
payment['cash_moves'] = cash_in_out_list
|
||||
payment['count'] = True
|
||||
if not is_cash_method:
|
||||
cash_name = 'Cash ' + str(session.name)
|
||||
payments.insert(0, {
|
||||
'name': cash_name,
|
||||
'total': 0,
|
||||
'final_count': session.cash_register_balance_start,
|
||||
'money_counted': session.cash_register_balance_end_real,
|
||||
'money_difference': session.cash_register_balance_end_real - session.cash_register_balance_start,
|
||||
'cash_moves': [],
|
||||
'count': True,
|
||||
'session': session.id,
|
||||
})
|
||||
products = []
|
||||
refund_products = []
|
||||
for category_name, product_list in products_sold.items():
|
||||
category_dictionnary = {
|
||||
'name': category_name,
|
||||
'products': sorted([{
|
||||
'product_id': product.id,
|
||||
'product_name': product.name,
|
||||
'code': product.default_code,
|
||||
'quantity': qty,
|
||||
'price_unit': price_unit,
|
||||
'discount': discount,
|
||||
'uom': product.uom_id.name
|
||||
} for (product, price_unit, discount), qty in product_list.items()], key=lambda l: l['product_name']),
|
||||
}
|
||||
products.append(category_dictionnary)
|
||||
products = sorted(products, key=lambda l: str(l['name']))
|
||||
|
||||
for category_name, product_list in refund_done.items():
|
||||
category_dictionnary = {
|
||||
'name': category_name,
|
||||
'products': sorted([{
|
||||
'product_id': product.id,
|
||||
'product_name': product.name,
|
||||
'code': product.default_code,
|
||||
'quantity': qty,
|
||||
'price_unit': price_unit,
|
||||
'discount': discount,
|
||||
'uom': product.uom_id.name
|
||||
} for (product, price_unit, discount), qty in product_list.items()], key=lambda l: l['product_name']),
|
||||
}
|
||||
refund_products.append(category_dictionnary)
|
||||
refund_products = sorted(refund_products, key=lambda l: str(l['name']))
|
||||
|
||||
products, products_info = self._get_total_and_qty_per_category(products)
|
||||
refund_products, refund_info = self._get_total_and_qty_per_category(refund_products)
|
||||
|
||||
currency = {
|
||||
'symbol': user_currency.symbol,
|
||||
'position': True if user_currency.position == 'after' else False,
|
||||
'total_paid': user_currency.round(total),
|
||||
'precision': user_currency.decimal_places,
|
||||
}
|
||||
|
||||
session_name = False
|
||||
if len(sessions) == 1:
|
||||
state = sessions[0].state
|
||||
date_start = sessions[0].start_at
|
||||
date_stop = sessions[0].stop_at
|
||||
session_name = sessions[0].name
|
||||
else:
|
||||
state = "multiple"
|
||||
|
||||
config_names = []
|
||||
for config in configs:
|
||||
config_names.append(config.name)
|
||||
|
||||
discount_number = 0
|
||||
discount_amount = 0
|
||||
invoiceList = []
|
||||
invoiceTotal = 0
|
||||
for session in sessions:
|
||||
discount_number += len(session.order_ids.filtered(lambda o: o.lines.filtered(lambda l: l.discount > 0)))
|
||||
discount_amount += session.get_total_discount()
|
||||
invoiceList.append({
|
||||
'name': session.name,
|
||||
'invoices': session._get_invoice_total_list(),
|
||||
})
|
||||
invoiceTotal += session._get_total_invoice()
|
||||
|
||||
return {
|
||||
'opening_note': sessions[0].opening_notes if len(sessions) == 1 else False,
|
||||
'closing_note': sessions[0].closing_notes if len(sessions) == 1 else False,
|
||||
'state': state,
|
||||
'currency': currency,
|
||||
'nbr_orders': len(orders),
|
||||
'date_start': date_start,
|
||||
'date_stop': date_stop,
|
||||
'session_name': session_name if session_name else False,
|
||||
'config_names': config_names,
|
||||
'payments': payments,
|
||||
'company_name': self.env.company.name,
|
||||
'taxes': list(taxes.values()),
|
||||
'taxes_info': taxes_info,
|
||||
'products': products,
|
||||
'products_info': products_info,
|
||||
'refund_taxes': list(refund_taxes.values()),
|
||||
'refund_taxes_info': refund_taxes_info,
|
||||
'refund_info': refund_info,
|
||||
'refund_products': refund_products,
|
||||
'discount_number': discount_number,
|
||||
'discount_amount': discount_amount,
|
||||
'invoiceList': invoiceList,
|
||||
'invoiceTotal': invoiceTotal,
|
||||
}
|
||||
|
||||
def _get_products_and_taxes_dict(self, line, products, taxes, currency):
|
||||
key2 = (line.product_id, line.price_unit, line.discount)
|
||||
keys1 = line.product_id.product_tmpl_id.pos_categ_ids.mapped("name") or [_('Not Categorized')]
|
||||
for key1 in keys1:
|
||||
products.setdefault(key1, {})
|
||||
products[key1].setdefault(key2, 0.0)
|
||||
products[key1][key2] += line.qty
|
||||
|
||||
if line.tax_ids_after_fiscal_position:
|
||||
line_taxes = line.tax_ids_after_fiscal_position.sudo().compute_all(line.price_unit * (1-(line.discount or 0.0)/100.0), currency, line.qty, product=line.product_id, partner=line.order_id.partner_id or False)
|
||||
for tax in line_taxes['taxes']:
|
||||
taxes.setdefault(tax['id'], {'name': tax['name'], 'tax_amount':0.0, 'base_amount':0.0})
|
||||
taxes[tax['id']]['tax_amount'] += tax['amount']
|
||||
taxes[tax['id']]['base_amount'] += tax['base']
|
||||
else:
|
||||
taxes.setdefault(0, {'name': _('No Taxes'), 'tax_amount':0.0, 'base_amount':0.0})
|
||||
taxes[0]['base_amount'] += line.price_subtotal_incl
|
||||
|
||||
return products, taxes
|
||||
|
||||
def _get_total_and_qty_per_category(self, categories):
|
||||
all_qty = 0
|
||||
all_total = 0
|
||||
total = lambda product: (product['quantity'] * product['price_unit']) * (100 - product['discount']) / 100
|
||||
for category_dict in categories:
|
||||
qty_cat = 0
|
||||
total_cat = 0
|
||||
for product in category_dict['products']:
|
||||
qty_cat += product['quantity']
|
||||
product['total_paid'] = total(product)
|
||||
total_cat += product['total_paid']
|
||||
category_dict['total'] = total_cat
|
||||
category_dict['qty'] = qty_cat
|
||||
# IMPROVEMENT: It would be better if the `products` are grouped by pos.order.line.id.
|
||||
unique_products = list({tuple(sorted(product.items())): product for category in categories for product in category['products']}.values())
|
||||
all_qty = sum([product['quantity'] for product in unique_products])
|
||||
all_total = sum([total(product) for product in unique_products])
|
||||
|
||||
return categories, {'total': all_total, 'qty': all_qty}
|
||||
|
||||
@api.model
|
||||
def _get_report_values(self, docids, data=None):
|
||||
data = dict(data or {})
|
||||
# initialize data keys with their value if provided, else None
|
||||
data.update({
|
||||
#If no data is provided it means that the report is called from the PoS, and docids represent the session_id
|
||||
'session_ids': data.get('session_ids') or (docids if not data.get('config_ids') and not data.get('date_start') and not data.get('date_stop') else None),
|
||||
'config_ids': data.get('config_ids'),
|
||||
'date_start': data.get('date_start'),
|
||||
'date_stop': data.get('date_stop')
|
||||
})
|
||||
configs = self.env['pos.config'].browse(data['config_ids'])
|
||||
data.update(self.get_sale_details(data['date_start'], data['date_stop'], configs.ids, data['session_ids']))
|
||||
return data
|
||||
|
||||
def _get_taxes_info(self, taxes):
|
||||
total_tax_amount = 0
|
||||
total_base_amount = 0
|
||||
for tax in taxes.values():
|
||||
total_tax_amount += tax['tax_amount']
|
||||
total_base_amount += tax['base_amount']
|
||||
return {'tax_amount': total_tax_amount, 'base_amount': total_base_amount}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user