Начальное наполнение

This commit is contained in:
parent 2b74d9c7b9
commit 16590e0356
504 changed files with 550991 additions and 1 deletions

109
README.md
View File

@ -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
View 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
View 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
View File

@ -0,0 +1 @@
from . import main

259
controllers/main.py Normal file
View 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()))

View 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
View 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
View 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
View 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>

View 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>

View 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

Binary file not shown.

525
doc/barcode_test_sheet.svg Normal file
View 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

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 70 KiB

BIN
doc/barcode_test_sheet4.pdf Normal file

Binary file not shown.

1210
doc/barcode_test_sheet4.svg Normal file

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 75 KiB

View 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

File diff suppressed because it is too large Load Diff

8541
i18n/ar.po Normal file

File diff suppressed because it is too large Load Diff

7452
i18n/az.po Normal file

File diff suppressed because it is too large Load Diff

8354
i18n/bg.po Normal file

File diff suppressed because it is too large Load Diff

7451
i18n/bs.po Normal file

File diff suppressed because it is too large Load Diff

8601
i18n/ca.po Normal file

File diff suppressed because it is too large Load Diff

8540
i18n/cs.po Normal file

File diff suppressed because it is too large Load Diff

8590
i18n/da.po Normal file

File diff suppressed because it is too large Load Diff

8663
i18n/de.po Normal file

File diff suppressed because it is too large Load Diff

7451
i18n/el.po Normal file

File diff suppressed because it is too large Load Diff

8640
i18n/es.po Normal file

File diff suppressed because it is too large Load Diff

8644
i18n/es_419.po Normal file

File diff suppressed because it is too large Load Diff

7445
i18n/es_BO.po Normal file

File diff suppressed because it is too large Load Diff

7450
i18n/es_CL.po Normal file

File diff suppressed because it is too large Load Diff

7456
i18n/es_CO.po Normal file

File diff suppressed because it is too large Load Diff

7445
i18n/es_CR.po Normal file

File diff suppressed because it is too large Load Diff

7454
i18n/es_PE.po Normal file

File diff suppressed because it is too large Load Diff

7445
i18n/es_PY.po Normal file

File diff suppressed because it is too large Load Diff

7449
i18n/es_VE.po Normal file

File diff suppressed because it is too large Load Diff

8511
i18n/et.po Normal file

File diff suppressed because it is too large Load Diff

7447
i18n/eu.po Normal file

File diff suppressed because it is too large Load Diff

8475
i18n/fa.po Normal file

File diff suppressed because it is too large Load Diff

8549
i18n/fi.po Normal file

File diff suppressed because it is too large Load Diff

8677
i18n/fr.po Normal file

File diff suppressed because it is too large Load Diff

7445
i18n/fr_CA.po Normal file

File diff suppressed because it is too large Load Diff

7445
i18n/gl.po Normal file

File diff suppressed because it is too large Load Diff

7452
i18n/gu.po Normal file

File diff suppressed because it is too large Load Diff

8400
i18n/he.po Normal file

File diff suppressed because it is too large Load Diff

7486
i18n/hr.po Normal file

File diff suppressed because it is too large Load Diff

8339
i18n/hu.po Normal file

File diff suppressed because it is too large Load Diff

8592
i18n/id.po Normal file

File diff suppressed because it is too large Load Diff

7455
i18n/is.po Normal file

File diff suppressed because it is too large Load Diff

8643
i18n/it.po Normal file

File diff suppressed because it is too large Load Diff

8351
i18n/ja.po Normal file

File diff suppressed because it is too large Load Diff

7449
i18n/ka.po Normal file

File diff suppressed because it is too large Load Diff

7445
i18n/kab.po Normal file

File diff suppressed because it is too large Load Diff

7451
i18n/km.po Normal file

File diff suppressed because it is too large Load Diff

8379
i18n/ko.po Normal file

File diff suppressed because it is too large Load Diff

7447
i18n/lb.po Normal file

File diff suppressed because it is too large Load Diff

8391
i18n/lt.po Normal file

File diff suppressed because it is too large Load Diff

8306
i18n/lv.po Normal file

File diff suppressed because it is too large Load Diff

7455
i18n/mk.po Normal file

File diff suppressed because it is too large Load Diff

7483
i18n/mn.po Normal file

File diff suppressed because it is too large Load Diff

7474
i18n/nb.po Normal file

File diff suppressed because it is too large Load Diff

8637
i18n/nl.po Normal file

File diff suppressed because it is too large Load Diff

8581
i18n/pl.po Normal file

File diff suppressed because it is too large Load Diff

8271
i18n/point_of_sale.pot Normal file

File diff suppressed because it is too large Load Diff

8326
i18n/pt.po Normal file

File diff suppressed because it is too large Load Diff

8617
i18n/pt_BR.po Normal file

File diff suppressed because it is too large Load Diff

7496
i18n/ro.po Normal file

File diff suppressed because it is too large Load Diff

8629
i18n/ru.po Normal file

File diff suppressed because it is too large Load Diff

8381
i18n/sk.po Normal file

File diff suppressed because it is too large Load Diff

8329
i18n/sl.po Normal file

File diff suppressed because it is too large Load Diff

8537
i18n/sr.po Normal file

File diff suppressed because it is too large Load Diff

7449
i18n/sr@latin.po Normal file

File diff suppressed because it is too large Load Diff

8379
i18n/sv.po Normal file

File diff suppressed because it is too large Load Diff

8539
i18n/th.po Normal file

File diff suppressed because it is too large Load Diff

8562
i18n/tr.po Normal file

File diff suppressed because it is too large Load Diff

8604
i18n/uk.po Normal file

File diff suppressed because it is too large Load Diff

8588
i18n/vi.po Normal file

File diff suppressed because it is too large Load Diff

8349
i18n/zh_CN.po Normal file

File diff suppressed because it is too large Load Diff

8348
i18n/zh_TW.po Normal file

File diff suppressed because it is too large Load Diff

29
models/__init__.py Normal file
View 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

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

96
models/pos_payment.py Normal file
View 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

View 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
View 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

File diff suppressed because it is too large Load Diff

139
models/product.py Normal file
View 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 customers 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 customers 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)

View 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