[REF] point_of_sale: split files per model

This commit is contained in:
Darshan Kalola
2016-02-17 17:04:22 +01:00
committed by Martin Trigaux
parent d1c3c6518d
commit fcdd43010a
22 changed files with 1818 additions and 1776 deletions
+23 -16
View File
@@ -30,28 +30,35 @@ Main Features
""",
'depends': ['sale_stock', 'barcodes'],
'data': [
'data/default_barcode_patterns.xml',
'security/point_of_sale_security.xml',
'security/ir.model.access.csv',
'data/default_barcode_patterns.xml',
'wizard/pos_box.xml',
'wizard/pos_confirm.xml',
'wizard/pos_details.xml',
'wizard/pos_discount.xml',
'wizard/pos_open_statement.xml',
'wizard/pos_payment.xml',
'views/templates.xml',
'point_of_sale.xml',
'point_of_sale_report.xml',
'point_of_sale_view.xml',
'point_of_sale_sequence.xml',
'point_of_sale_data.xml',
'report/pos_order_report_view.xml',
'point_of_sale_workflow.xml',
'account_statement_view.xml',
'account_statement_report.xml',
'res_users_view.xml',
'res_partner_view.xml',
'res_config_view.xml',
'views/pos_templates.xml',
'views/point_of_sale_template.xml',
'views/point_of_sale_report.xml',
'views/point_of_sale_view.xml',
'views/pos_order_view.xml',
'views/product_view.xml',
'views/pos_category_view.xml',
'views/account_journal_view.xml',
'views/pos_config_view.xml',
'views/pos_session_view.xml',
'views/account_invoice_view.xml',
'views/point_of_sale_sequence.xml',
'data/point_of_sale_data.xml',
'data/point_of_sale_workflow.xml',
'views/pos_order_report_view.xml',
'views/account_statement_view.xml',
'views/account_statement_report.xml',
'views/res_users_view.xml',
'views/res_partner_view.xml',
'views/res_config_view.xml',
'views/report_statement.xml',
'views/report_usersproduct.xml',
'views/report_saleslines.xml',
@@ -60,10 +67,10 @@ Main Features
'views/report_sessionsummary.xml',
'views/report_userlabel.xml',
'views/point_of_sale.xml',
'point_of_sale_dashboard.xml',
'views/point_of_sale_dashboard.xml',
],
'demo': [
'point_of_sale_demo.xml',
'data/point_of_sale_demo.xml',
],
'test': [
'../account/test/account_minimal_test.xml',
+7 -1
View File
@@ -2,7 +2,13 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import account_bank_statement
import point_of_sale
import account_journal
import barcode_rule
import pos_category
import pos_config
import pos_order
import pos_session
import product
import res_config
import res_partner
import res_users
@@ -5,14 +5,6 @@
from openerp.osv import fields, osv
class account_journal(osv.osv):
_inherit = 'account.journal'
_columns = {
'journal_user': fields.boolean('Active in Point of Sale', help="Check this box if this journal define a payment method that can be used in a point of sale."),
'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."),
}
class account_bank_statement(osv.osv):
_inherit = 'account.bank.statement'
@@ -20,3 +12,10 @@ class account_bank_statement(osv.osv):
'pos_session_id' : fields.many2one('pos.session', string="Session", copy=False),
'account_id': fields.related('journal_id', 'default_debit_account_id', type='many2one', relation='account.account', readonly=True),
}
class account_bank_statement_line(osv.osv):
_inherit = 'account.bank.statement.line'
_columns= {
'pos_statement_id': fields.many2one('pos.order', string="POS statement", ondelete='cascade'),
}
@@ -0,0 +1,27 @@
# -*- 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 openerp.osv import fields, osv
class account_journal(osv.osv):
_inherit = 'account.journal'
_columns = {
'journal_user': fields.boolean('Active in Point of Sale', help="Check this box if this journal define a payment method that can be used in a point of sale."),
'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."),
}
def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
if not context:
context = {}
session_id = context.get('pos_session_id', False) or False
if session_id:
session = self.pool.get('pos.session').browse(cr, uid, session_id, context=context)
if session:
journal_ids = [journal.id for journal in session.config_id.journal_ids]
args += [('id', 'in', journal_ids)]
return super(account_journal, self).search(cr, uid, args, offset=offset, limit=limit, order=order, context=context, count=count)
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import sets
from openerp import models
from openerp.tools.translate import _
class barcode_rule(models.Model):
_inherit = 'barcode.rule'
def _get_type_selection(self):
types = sets.Set(super(barcode_rule,self)._get_type_selection())
types.update([
('weight', _('Weighted Product')),
('price', _('Priced Product')),
('discount', _('Discounted Product')),
('client', _('Client')),
('cashier', _('Cashier'))
])
return list(types)
@@ -0,0 +1,71 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp import tools
from openerp.osv import fields, osv
class pos_category(osv.osv):
_name = "pos.category"
_description = "Public Category"
_order = "sequence, name"
_constraints = [
(osv.osv._check_recursion, 'Error ! You cannot create recursive categories.', ['parent_id'])
]
def name_get(self, cr, uid, ids, context=None):
res = []
for cat in self.browse(cr, uid, ids, context=context):
names = [cat.name]
pcat = cat.parent_id
while pcat:
names.append(pcat.name)
pcat = pcat.parent_id
res.append((cat.id, ' / '.join(reversed(names))))
return res
def _name_get_fnc(self, cr, uid, ids, prop, unknow_none, context=None):
res = self.name_get(cr, uid, ids, context=context)
return dict(res)
_columns = {
'name': fields.char('Name', required=True, translate=True),
'complete_name': fields.function(_name_get_fnc, type="char", string='Name'),
'parent_id': fields.many2one('pos.category','Parent Category', select=True),
'child_id': fields.one2many('pos.category', 'parent_id', string='Children Categories'),
'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of product categories."),
}
# NOTE: there is no 'default image', because by default we don't show
# thumbnails for categories. However if we have a thumbnail for at least one
# category, then we display a default image on the other, so that the
# buttons have consistent styling.
# In this case, the default image is set by the js code.
image = openerp.fields.Binary("Image", attachment=True,
help="This field holds the image used as image for the cateogry, limited to 1024x1024px.")
image_medium = openerp.fields.Binary("Medium-sized image",
compute='_compute_images', inverse='_inverse_image_medium', store=True, attachment=True,
help="Medium-sized image of the category. It is automatically "\
"resized as a 128x128px image, with aspect ratio preserved. "\
"Use this field in form views or some kanban views.")
image_small = openerp.fields.Binary("Small-sized image",
compute='_compute_images', inverse='_inverse_image_small', store=True, attachment=True,
help="Small-sized image of the category. It is automatically "\
"resized as a 64x64px image, with aspect ratio preserved. "\
"Use this field anywhere a small image is required.")
@openerp.api.depends('image')
def _compute_images(self):
for rec in self:
rec.image_medium = tools.image_resize_image_medium(rec.image)
rec.image_small = tools.image_resize_image_small(rec.image)
def _inverse_image_medium(self):
for rec in self:
rec.image = tools.image_resize_image_big(rec.image_medium)
def _inverse_image_small(self):
for rec in self:
rec.image = tools.image_resize_image_big(rec.image_small)
+324
View File
@@ -0,0 +1,324 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import uuid
from openerp import SUPERUSER_ID
from openerp.osv import fields, osv
from openerp.tools.translate import _
class pos_config(osv.osv):
_name = 'pos.config'
POS_CONFIG_STATE = [
('active', 'Active'),
('inactive', 'Inactive'),
('deprecated', 'Deprecated')
]
def _get_currency(self, cr, uid, ids, fieldnames, args, context=None):
result = dict.fromkeys(ids, False)
for pos_config in self.browse(cr, uid, ids, context=context):
if pos_config.journal_id:
currency_id = pos_config.journal_id.currency_id.id or pos_config.journal_id.company_id.currency_id.id
else:
currency_id = self.pool['res.users'].browse(cr, uid, uid, context=context).company_id.currency_id.id
result[pos_config.id] = currency_id
return result
def _get_current_session(self, cr, uid, ids, fieldnames, args, context=None):
result = dict()
for record in self.browse(cr, uid, ids, context=context):
session_id = record.session_ids.filtered(lambda r: r.user_id.id == uid and not r.state == 'closed')
result[record.id] = {
'current_session_id': session_id,
'current_session_state': session_id.state,
}
return result
def _get_last_session(self, cr, uid, ids, fieldnames, args, context=None):
result = dict()
for record in self.browse(cr, uid, ids, context=context):
session_ids = self.pool['pos.session'].search_read(
cr, uid,
[('config_id', '=', record.id), ('state', '=', 'closed')],
['cash_register_balance_end_real', 'stop_at'],
order="stop_at desc", limit=1, context=context)
if session_ids:
result[record.id] = {
'last_session_closing_cash': session_ids[0]['cash_register_balance_end_real'],
'last_session_closing_date': session_ids[0]['stop_at'],
}
else:
result[record.id] = {
'last_session_closing_cash': 0,
'last_session_closing_date': None,
}
return result
def _get_current_session_user(self, cr, uid, ids, fieldnames, args, context=None):
result = dict()
for record in self.browse(cr, uid, ids, context=context):
result[record.id] = record.session_ids.filtered(lambda r: r.state == 'opened').user_id.name
return result
_columns = {
'name' : fields.char('Point of Sale Name', select=1,
required=True, help="An internal identification of the point of sale"),
'journal_ids' : fields.many2many('account.journal', 'pos_config_journal_rel',
'pos_config_id', 'journal_id', 'Available Payment Methods',
domain="[('journal_user', '=', True ), ('type', 'in', ['bank', 'cash'])]",),
'picking_type_id': fields.many2one('stock.picking.type', 'Picking Type'),
'stock_location_id': fields.many2one('stock.location', 'Stock Location', domain=[('usage', '=', 'internal')], required=True),
'journal_id' : fields.many2one('account.journal', 'Sale Journal',
domain=[('type', '=', 'sale')],
help="Accounting journal used to post sales entries."),
'currency_id' : fields.function(_get_currency, type="many2one", string="Currency", relation="res.currency"),
'iface_cashdrawer' : fields.boolean('Cashdrawer', help="Automatically open the cashdrawer"),
'iface_payment_terminal' : fields.boolean('Payment Terminal', help="Enables Payment Terminal integration"),
'iface_electronic_scale' : fields.boolean('Electronic Scale', help="Enables Electronic Scale integration"),
'iface_vkeyboard' : fields.boolean('Virtual KeyBoard', help="Enables an integrated Virtual Keyboard"),
'iface_print_via_proxy' : fields.boolean('Print via Proxy', help="Bypass browser printing and prints via the hardware proxy"),
'iface_scan_via_proxy' : fields.boolean('Scan via Proxy', help="Enable barcode scanning with a remotely connected barcode scanner"),
'iface_invoicing': fields.boolean('Invoicing',help='Enables invoice generation from the Point of Sale'),
'iface_big_scrollbars': fields.boolean('Large Scrollbars',help='For imprecise industrial touchscreens'),
# TODO master: remove the `iface_fullscreen` field. This is no longer used.
'iface_fullscreen': fields.boolean('Fullscreen', help='Display the Point of Sale in full screen mode'),
'iface_print_auto': fields.boolean('Automatic Receipt Printing', help='The receipt will automatically be printed at the end of each order'),
'iface_print_skip_screen': fields.boolean('Skip Receipt Screen', help='The receipt screen will be skipped if the receipt can be printed automatically.'),
'iface_precompute_cash': fields.boolean('Prefill Cash Payment', help='The payment input will behave similarily to bank payment input, and will be prefilled with the exact due amount'),
'iface_tax_included': fields.boolean('Include Taxes in Prices', help='The displayed prices will always include all taxes, even if the taxes have been setup differently'),
'iface_start_categ_id': fields.many2one('pos.category','Start 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_display_categ_images': fields.boolean('Display Category Pictures', help="The product categories will be displayed with pictures."),
'cash_control': fields.boolean('Cash Control', help="Check the amount of the cashbox at opening and closing."),
'receipt_header': fields.text('Receipt Header',help="A short text that will be inserted as a header in the printed receipt"),
'receipt_footer': fields.text('Receipt Footer',help="A short text that will be inserted as a footer in the printed receipt"),
'proxy_ip': fields.char('IP Address', help='The hostname or ip address of the hardware proxy, Will be autodetected if left empty', size=45),
'state' : fields.selection(POS_CONFIG_STATE, 'Status', required=True, readonly=True, copy=False),
'uuid' : fields.char('uuid', readonly=True, help='A globally unique identifier for this pos configuration, used to prevent conflicts in client-generated data'),
'sequence_id' : fields.many2one('ir.sequence', '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),
'session_ids': fields.one2many('pos.session', 'config_id', 'Sessions'),
'current_session_id': fields.function(_get_current_session, multi="session", type="many2one", relation="pos.session", string="Current Session"),
'current_session_state': fields.function(_get_current_session, multi="session", type='char'),
'last_session_closing_cash': fields.function(_get_last_session, multi="last_session", type='float'),
'last_session_closing_date': fields.function(_get_last_session, multi="last_session", type='date'),
'pos_session_username': fields.function(_get_current_session_user, type='char'),
'group_by' : fields.boolean('Group Journal Items', help="Check this if you want to group the Journal Items by Product while closing a Session"),
'pricelist_id': fields.many2one('product.pricelist','Pricelist', required=True),
'company_id': fields.many2one('res.company', 'Company', required=True),
'barcode_nomenclature_id': fields.many2one('barcode.nomenclature','Barcodes', help='Defines what kind of barcodes are available and how they are assigned to products, customers and cashiers', required=True),
'group_pos_manager_id': fields.many2one('res.groups','Point of Sale Manager Group', 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','Point of Sale User Group', help='This field is there to pass the id of the pos user group to the point of sale client'),
'tip_product_id': fields.many2one('product.product','Tip Product', help="The product used to encode the customer tip. Leave empty if you do not accept tips."),
'fiscal_position_ids': fields.many2many('account.fiscal.position', string='Fiscal Positions')
}
def _check_company_location(self, cr, uid, ids, context=None):
for config in self.browse(cr, uid, ids, context=context):
if config.stock_location_id.company_id and config.stock_location_id.company_id.id != config.company_id.id:
return False
return True
def _check_company_journal(self, cr, uid, ids, context=None):
for config in self.browse(cr, uid, ids, context=context):
if config.journal_id and config.journal_id.company_id.id != config.company_id.id:
return False
return True
def _check_company_payment(self, cr, uid, ids, context=None):
for config in self.browse(cr, uid, ids, context=context):
journal_ids = [j.id for j in config.journal_ids]
if self.pool['account.journal'].search(cr, uid, [
('id', 'in', journal_ids),
('company_id', '!=', config.company_id.id)
], count=True, context=context):
return False
return True
_constraints = [
(_check_company_location, "The company of the stock location is different than the one of point of sale", ['company_id', 'stock_location_id']),
(_check_company_journal, "The company of the sale journal is different than the one of point of sale", ['company_id', 'journal_id']),
(_check_company_payment, "The company of a payment method is different than the one of point of sale", ['company_id', 'journal_ids']),
]
def name_get(self, cr, uid, ids, context=None):
result = []
states = {
'opening_control': _('Opening Control'),
'opened': _('In Progress'),
'closing_control': _('Closing Control'),
'closed': _('Closed & Posted'),
}
for record in self.browse(cr, uid, ids, context=context):
if (not record.session_ids) or (record.session_ids[0].state=='closed'):
result.append((record.id, record.name+' ('+_('not used')+')'))
continue
session = record.session_ids[0]
result.append((record.id, record.name + ' ('+session.user_id.name+')')) #, '+states[session.state]+')'))
return result
def _default_sale_journal(self, cr, uid, context=None):
company_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id
res = self.pool.get('account.journal').search(cr, uid, [('type', '=', 'sale'), ('company_id', '=', company_id)], limit=1, context=context)
return res and res[0] or False
def _default_pricelist(self, cr, uid, context=None):
res = self.pool.get('product.pricelist').search(cr, uid, [], limit=1, context=context)
return res and res[0] or False
def _get_default_location(self, cr, uid, context=None):
wh_obj = self.pool.get('stock.warehouse')
user = self.pool.get('res.users').browse(cr, uid, uid, context)
res = wh_obj.search(cr, uid, [('company_id', '=', user.company_id.id)], limit=1, context=context)
if res and res[0]:
return wh_obj.browse(cr, uid, res[0], context=context).lot_stock_id.id
return False
def _get_default_company(self, cr, uid, context=None):
company_id = self.pool.get('res.users')._get_company(cr, uid, context=context)
return company_id
def _get_default_nomenclature(self, cr, uid, context=None):
nom_obj = self.pool.get('barcode.nomenclature')
res = nom_obj.search(cr, uid, [], limit=1, context=context)
return res and res[0] or False
def _get_group_pos_manager(self, cr, uid, context=None):
group = self.pool.get('ir.model.data').get_object_reference(cr,uid,'point_of_sale','group_pos_manager')
if group:
return group[1]
else:
return False
def _get_group_pos_user(self, cr, uid, context=None):
group = self.pool.get('ir.model.data').get_object_reference(cr,uid,'point_of_sale','group_pos_user')
if group:
return group[1]
else:
return False
_defaults = {
'uuid' : lambda self, cr, uid, context={}: str(uuid.uuid4()),
'state' : POS_CONFIG_STATE[0][0],
'journal_id': _default_sale_journal,
'group_by' : True,
'pricelist_id': _default_pricelist,
'iface_invoicing': True,
'iface_print_auto': False,
'iface_print_skip_screen': True,
'stock_location_id': _get_default_location,
'company_id': _get_default_company,
'barcode_nomenclature_id': _get_default_nomenclature,
'group_pos_manager_id': _get_group_pos_manager,
'group_pos_user_id': _get_group_pos_user,
}
def onchange_picking_type_id(self, cr, uid, ids, picking_type_id, context=None):
p_type_obj = self.pool.get("stock.picking.type")
p_type = p_type_obj.browse(cr, uid, picking_type_id, context=context)
if p_type.default_location_src_id and p_type.default_location_src_id.usage == 'internal' and p_type.default_location_dest_id and p_type.default_location_dest_id.usage == 'customer':
return {'value': {'stock_location_id': p_type.default_location_src_id.id}}
return False
def onchange_iface_print_via_proxy(self, cr, uid, ids, print_via_proxy, context=None):
return {'value': {'iface_print_auto': print_via_proxy}}
def set_active(self, cr, uid, ids, context=None):
return self.write(cr, uid, ids, {'state' : 'active'}, context=context)
def set_inactive(self, cr, uid, ids, context=None):
return self.write(cr, uid, ids, {'state' : 'inactive'}, context=context)
def set_deprecate(self, cr, uid, ids, context=None):
return self.write(cr, uid, ids, {'state' : 'deprecated'}, context=context)
def create(self, cr, uid, values, context=None):
ir_sequence = self.pool.get('ir.sequence')
# force sequence_id field to new pos.order sequence
values['sequence_id'] = ir_sequence.create(cr, SUPERUSER_ID, {
'name': 'POS Order %s' % values['name'],
'padding': 4,
'prefix': "%s/" % values['name'],
'code': "pos.order",
'company_id': values.get('company_id', False),
}, context=context)
# TODO master: add field sequence_line_id on model
# this make sure we always have one available per company
ir_sequence.create(cr, SUPERUSER_ID, {
'name': 'POS order line %s' % values['name'],
'padding': 4,
'prefix': "%s/" % values['name'],
'code': "pos.order.line",
'company_id': values.get('company_id', False),
}, context=context)
return super(pos_config, self).create(cr, uid, values, context=context)
def unlink(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
if obj.sequence_id:
obj.sequence_id.unlink()
return super(pos_config, self).unlink(cr, uid, ids, context=context)
# Methods to open the POS
def open_ui(self, cr, uid, ids, context=None):
assert len(ids) == 1, "you can open only one session at a time"
record = self.browse(cr, uid, ids[0], context=context)
context = dict(context or {})
context['active_id'] = record.current_session_id.id
return {
'type': 'ir.actions.act_url',
'url': '/pos/web/',
'target': 'self',
}
def open_existing_session_cb_close(self, cr, uid, ids, context=None):
assert len(ids) == 1, "you can open only one session at a time"
record = self.browse(cr, uid, ids[0], context=context)
record.current_session_id.signal_workflow('cashbox_control')
return self.open_session_cb(cr, uid, ids, context)
def open_session_cb(self, cr, uid, ids, context=None):
assert len(ids) == 1, "you can open only one session at a time"
proxy = self.pool.get('pos.session')
record = self.browse(cr, uid, ids[0], context=context)
current_session_id = record.current_session_id
if not current_session_id:
values = {
'user_id': uid,
'config_id': record.id,
}
session_id = proxy.create(cr, uid, values, context=context)
self.write(cr, SUPERUSER_ID, record.id, {'current_session_id': session_id}, context=context)
if record.current_session_id.state == 'opened':
return self.open_ui(cr, uid, ids, context=context)
return self._open_session(session_id)
return self._open_session(current_session_id.id)
def open_existing_session_cb(self, cr, uid, ids, context=None):
assert len(ids) == 1, "you can open only one session at a time"
record = self.browse(cr, uid, ids[0], context=context)
return self._open_session(record.current_session_id.id)
def _open_session(self, session_id):
return {
'name': _('Session'),
'view_type': 'form',
'view_mode': 'form,tree',
'res_model': 'pos.session',
'res_id': session_id,
'view_id': False,
'type': 'ir.actions.act_window',
}
@@ -2,16 +2,15 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
import time
from datetime import datetime
import uuid
import sets
from functools import partial
import openerp
import openerp.addons.decimal_precision as dp
from openerp import tools, models, SUPERUSER_ID
from openerp import tools, SUPERUSER_ID
from openerp.osv import fields, osv
from openerp.tools import float_is_zero
from openerp.tools.translate import _
@@ -20,623 +19,6 @@ from openerp.exceptions import UserError
_logger = logging.getLogger(__name__)
class pos_config(osv.osv):
_name = 'pos.config'
POS_CONFIG_STATE = [
('active', 'Active'),
('inactive', 'Inactive'),
('deprecated', 'Deprecated')
]
def _get_currency(self, cr, uid, ids, fieldnames, args, context=None):
result = dict.fromkeys(ids, False)
for pos_config in self.browse(cr, uid, ids, context=context):
if pos_config.journal_id:
currency_id = pos_config.journal_id.currency_id.id or pos_config.journal_id.company_id.currency_id.id
else:
currency_id = self.pool['res.users'].browse(cr, uid, uid, context=context).company_id.currency_id.id
result[pos_config.id] = currency_id
return result
def _get_current_session(self, cr, uid, ids, fieldnames, args, context=None):
result = dict()
for record in self.browse(cr, uid, ids, context=context):
session_id = record.session_ids.filtered(lambda r: r.user_id.id == uid and not r.state == 'closed')
result[record.id] = {
'current_session_id': session_id,
'current_session_state': session_id.state,
}
return result
def _get_last_session(self, cr, uid, ids, fieldnames, args, context=None):
result = dict()
for record in self.browse(cr, uid, ids, context=context):
session_ids = self.pool['pos.session'].search_read(
cr, uid,
[('config_id', '=', record.id), ('state', '=', 'closed')],
['cash_register_balance_end_real', 'stop_at'],
order="stop_at desc", limit=1, context=context)
if session_ids:
result[record.id] = {
'last_session_closing_cash': session_ids[0]['cash_register_balance_end_real'],
'last_session_closing_date': session_ids[0]['stop_at'],
}
else:
result[record.id] = {
'last_session_closing_cash': 0,
'last_session_closing_date': None,
}
return result
def _get_current_session_user(self, cr, uid, ids, fieldnames, args, context=None):
result = dict()
for record in self.browse(cr, uid, ids, context=context):
result[record.id] = record.session_ids.filtered(lambda r: r.state == 'opened').user_id.name
return result
_columns = {
'name' : fields.char('Point of Sale Name', select=1,
required=True, help="An internal identification of the point of sale"),
'journal_ids' : fields.many2many('account.journal', 'pos_config_journal_rel',
'pos_config_id', 'journal_id', 'Available Payment Methods',
domain="[('journal_user', '=', True ), ('type', 'in', ['bank', 'cash'])]",),
'picking_type_id': fields.many2one('stock.picking.type', 'Picking Type'),
'stock_location_id': fields.many2one('stock.location', 'Stock Location', domain=[('usage', '=', 'internal')], required=True),
'journal_id' : fields.many2one('account.journal', 'Sale Journal',
domain=[('type', '=', 'sale')],
help="Accounting journal used to post sales entries."),
'currency_id' : fields.function(_get_currency, type="many2one", string="Currency", relation="res.currency"),
'iface_cashdrawer' : fields.boolean('Cashdrawer', help="Automatically open the cashdrawer"),
'iface_payment_terminal' : fields.boolean('Payment Terminal', help="Enables Payment Terminal integration"),
'iface_electronic_scale' : fields.boolean('Electronic Scale', help="Enables Electronic Scale integration"),
'iface_vkeyboard' : fields.boolean('Virtual KeyBoard', help="Enables an integrated Virtual Keyboard"),
'iface_print_via_proxy' : fields.boolean('Print via Proxy', help="Bypass browser printing and prints via the hardware proxy"),
'iface_scan_via_proxy' : fields.boolean('Scan via Proxy', help="Enable barcode scanning with a remotely connected barcode scanner"),
'iface_invoicing': fields.boolean('Invoicing',help='Enables invoice generation from the Point of Sale'),
'iface_big_scrollbars': fields.boolean('Large Scrollbars',help='For imprecise industrial touchscreens'),
# TODO master: remove the `iface_fullscreen` field. This is no longer used.
'iface_fullscreen': fields.boolean('Fullscreen', help='Display the Point of Sale in full screen mode'),
'iface_print_auto': fields.boolean('Automatic Receipt Printing', help='The receipt will automatically be printed at the end of each order'),
'iface_print_skip_screen': fields.boolean('Skip Receipt Screen', help='The receipt screen will be skipped if the receipt can be printed automatically.'),
'iface_precompute_cash': fields.boolean('Prefill Cash Payment', help='The payment input will behave similarily to bank payment input, and will be prefilled with the exact due amount'),
'iface_tax_included': fields.boolean('Include Taxes in Prices', help='The displayed prices will always include all taxes, even if the taxes have been setup differently'),
'iface_start_categ_id': fields.many2one('pos.category','Start 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_display_categ_images': fields.boolean('Display Category Pictures', help="The product categories will be displayed with pictures."),
'cash_control': fields.boolean('Cash Control', help="Check the amount of the cashbox at opening and closing."),
'receipt_header': fields.text('Receipt Header',help="A short text that will be inserted as a header in the printed receipt"),
'receipt_footer': fields.text('Receipt Footer',help="A short text that will be inserted as a footer in the printed receipt"),
'proxy_ip': fields.char('IP Address', help='The hostname or ip address of the hardware proxy, Will be autodetected if left empty', size=45),
'state' : fields.selection(POS_CONFIG_STATE, 'Status', required=True, readonly=True, copy=False),
'uuid' : fields.char('uuid', readonly=True, help='A globally unique identifier for this pos configuration, used to prevent conflicts in client-generated data'),
'sequence_id' : fields.many2one('ir.sequence', '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),
'session_ids': fields.one2many('pos.session', 'config_id', 'Sessions'),
'current_session_id': fields.function(_get_current_session, multi="session", type="many2one", relation="pos.session", string="Current Session"),
'current_session_state': fields.function(_get_current_session, multi="session", type='char'),
'last_session_closing_cash': fields.function(_get_last_session, multi="last_session", type='float'),
'last_session_closing_date': fields.function(_get_last_session, multi="last_session", type='date'),
'pos_session_username': fields.function(_get_current_session_user, type='char'),
'group_by' : fields.boolean('Group Journal Items', help="Check this if you want to group the Journal Items by Product while closing a Session"),
'pricelist_id': fields.many2one('product.pricelist','Pricelist', required=True),
'company_id': fields.many2one('res.company', 'Company', required=True),
'barcode_nomenclature_id': fields.many2one('barcode.nomenclature','Barcodes', help='Defines what kind of barcodes are available and how they are assigned to products, customers and cashiers', required=True),
'group_pos_manager_id': fields.many2one('res.groups','Point of Sale Manager Group', 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','Point of Sale User Group', help='This field is there to pass the id of the pos user group to the point of sale client'),
'tip_product_id': fields.many2one('product.product','Tip Product', help="The product used to encode the customer tip. Leave empty if you do not accept tips."),
'fiscal_position_ids': fields.many2many('account.fiscal.position', string='Fiscal Positions')
}
def _check_company_location(self, cr, uid, ids, context=None):
for config in self.browse(cr, uid, ids, context=context):
if config.stock_location_id.company_id and config.stock_location_id.company_id.id != config.company_id.id:
return False
return True
def _check_company_journal(self, cr, uid, ids, context=None):
for config in self.browse(cr, uid, ids, context=context):
if config.journal_id and config.journal_id.company_id.id != config.company_id.id:
return False
return True
def _check_company_payment(self, cr, uid, ids, context=None):
for config in self.browse(cr, uid, ids, context=context):
journal_ids = [j.id for j in config.journal_ids]
if self.pool['account.journal'].search(cr, uid, [
('id', 'in', journal_ids),
('company_id', '!=', config.company_id.id)
], count=True, context=context):
return False
return True
_constraints = [
(_check_company_location, "The company of the stock location is different than the one of point of sale", ['company_id', 'stock_location_id']),
(_check_company_journal, "The company of the sale journal is different than the one of point of sale", ['company_id', 'journal_id']),
(_check_company_payment, "The company of a payment method is different than the one of point of sale", ['company_id', 'journal_ids']),
]
def name_get(self, cr, uid, ids, context=None):
result = []
states = {
'opening_control': _('Opening Control'),
'opened': _('In Progress'),
'closing_control': _('Closing Control'),
'closed': _('Closed & Posted'),
}
for record in self.browse(cr, uid, ids, context=context):
if (not record.session_ids) or (record.session_ids[0].state=='closed'):
result.append((record.id, record.name+' ('+_('not used')+')'))
continue
session = record.session_ids[0]
result.append((record.id, record.name + ' ('+session.user_id.name+')')) #, '+states[session.state]+')'))
return result
def _default_sale_journal(self, cr, uid, context=None):
company_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id
res = self.pool.get('account.journal').search(cr, uid, [('type', '=', 'sale'), ('company_id', '=', company_id)], limit=1, context=context)
return res and res[0] or False
def _default_pricelist(self, cr, uid, context=None):
res = self.pool.get('product.pricelist').search(cr, uid, [], limit=1, context=context)
return res and res[0] or False
def _get_default_location(self, cr, uid, context=None):
wh_obj = self.pool.get('stock.warehouse')
user = self.pool.get('res.users').browse(cr, uid, uid, context)
res = wh_obj.search(cr, uid, [('company_id', '=', user.company_id.id)], limit=1, context=context)
if res and res[0]:
return wh_obj.browse(cr, uid, res[0], context=context).lot_stock_id.id
return False
def _get_default_company(self, cr, uid, context=None):
company_id = self.pool.get('res.users')._get_company(cr, uid, context=context)
return company_id
def _get_default_nomenclature(self, cr, uid, context=None):
nom_obj = self.pool.get('barcode.nomenclature')
res = nom_obj.search(cr, uid, [], limit=1, context=context)
return res and res[0] or False
def _get_group_pos_manager(self, cr, uid, context=None):
group = self.pool.get('ir.model.data').get_object_reference(cr,uid,'point_of_sale','group_pos_manager')
if group:
return group[1]
else:
return False
def _get_group_pos_user(self, cr, uid, context=None):
group = self.pool.get('ir.model.data').get_object_reference(cr,uid,'point_of_sale','group_pos_user')
if group:
return group[1]
else:
return False
_defaults = {
'uuid' : lambda self, cr, uid, context={}: str(uuid.uuid4()),
'state' : POS_CONFIG_STATE[0][0],
'journal_id': _default_sale_journal,
'group_by' : True,
'pricelist_id': _default_pricelist,
'iface_invoicing': True,
'iface_print_auto': False,
'iface_print_skip_screen': True,
'stock_location_id': _get_default_location,
'company_id': _get_default_company,
'barcode_nomenclature_id': _get_default_nomenclature,
'group_pos_manager_id': _get_group_pos_manager,
'group_pos_user_id': _get_group_pos_user,
}
def onchange_picking_type_id(self, cr, uid, ids, picking_type_id, context=None):
p_type_obj = self.pool.get("stock.picking.type")
p_type = p_type_obj.browse(cr, uid, picking_type_id, context=context)
if p_type.default_location_src_id and p_type.default_location_src_id.usage == 'internal' and p_type.default_location_dest_id and p_type.default_location_dest_id.usage == 'customer':
return {'value': {'stock_location_id': p_type.default_location_src_id.id}}
return False
def onchange_iface_print_via_proxy(self, cr, uid, ids, print_via_proxy, context=None):
return {'value': {'iface_print_auto': print_via_proxy}}
def set_active(self, cr, uid, ids, context=None):
return self.write(cr, uid, ids, {'state' : 'active'}, context=context)
def set_inactive(self, cr, uid, ids, context=None):
return self.write(cr, uid, ids, {'state' : 'inactive'}, context=context)
def set_deprecate(self, cr, uid, ids, context=None):
return self.write(cr, uid, ids, {'state' : 'deprecated'}, context=context)
def create(self, cr, uid, values, context=None):
ir_sequence = self.pool.get('ir.sequence')
# force sequence_id field to new pos.order sequence
values['sequence_id'] = ir_sequence.create(cr, SUPERUSER_ID, {
'name': 'POS Order %s' % values['name'],
'padding': 4,
'prefix': "%s/" % values['name'],
'code': "pos.order",
'company_id': values.get('company_id', False),
}, context=context)
# TODO master: add field sequence_line_id on model
# this make sure we always have one available per company
ir_sequence.create(cr, SUPERUSER_ID, {
'name': 'POS order line %s' % values['name'],
'padding': 4,
'prefix': "%s/" % values['name'],
'code': "pos.order.line",
'company_id': values.get('company_id', False),
}, context=context)
return super(pos_config, self).create(cr, uid, values, context=context)
def unlink(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
if obj.sequence_id:
obj.sequence_id.unlink()
return super(pos_config, self).unlink(cr, uid, ids, context=context)
# Methods to open the POS
def open_ui(self, cr, uid, ids, context=None):
assert len(ids) == 1, "you can open only one session at a time"
record = self.browse(cr, uid, ids[0], context=context)
context = dict(context or {})
context['active_id'] = record.current_session_id.id
return {
'type': 'ir.actions.act_url',
'url': '/pos/web/',
'target': 'self',
}
def open_existing_session_cb_close(self, cr, uid, ids, context=None):
assert len(ids) == 1, "you can open only one session at a time"
record = self.browse(cr, uid, ids[0], context=context)
record.current_session_id.signal_workflow('cashbox_control')
return self.open_session_cb(cr, uid, ids, context)
def open_session_cb(self, cr, uid, ids, context=None):
assert len(ids) == 1, "you can open only one session at a time"
proxy = self.pool.get('pos.session')
record = self.browse(cr, uid, ids[0], context=context)
current_session_id = record.current_session_id
if not current_session_id:
values = {
'user_id': uid,
'config_id': record.id,
}
session_id = proxy.create(cr, uid, values, context=context)
self.write(cr, SUPERUSER_ID, record.id, {'current_session_id': session_id}, context=context)
if record.current_session_id.state == 'opened':
return self.open_ui(cr, uid, ids, context=context)
return self._open_session(session_id)
return self._open_session(current_session_id.id)
def open_existing_session_cb(self, cr, uid, ids, context=None):
assert len(ids) == 1, "you can open only one session at a time"
record = self.browse(cr, uid, ids[0], context=context)
return self._open_session(record.current_session_id.id)
def _open_session(self, session_id):
return {
'name': _('Session'),
'view_type': 'form',
'view_mode': 'form,tree',
'res_model': 'pos.session',
'res_id': session_id,
'view_id': False,
'type': 'ir.actions.act_window',
}
class pos_session(osv.osv):
_name = 'pos.session'
_order = 'id desc'
POS_SESSION_STATE = [
('opening_control', 'Opening Control'), # Signal open
('opened', 'In Progress'), # Signal closing
('closing_control', 'Closing Control'), # Signal close
('closed', 'Closed & Posted'),
]
def _compute_cash_all(self, cr, uid, ids, fieldnames, args, context=None):
result = dict()
for record in self.browse(cr, uid, ids, context=context):
result[record.id] = {
'cash_journal_id' : False,
'cash_register_id' : False,
'cash_control' : False,
}
if record.config_id.cash_control:
for st in record.statement_ids:
if st.journal_id.type == 'cash':
result[record.id]['cash_control'] = True
result[record.id]['cash_journal_id'] = st.journal_id.id
result[record.id]['cash_register_id'] = st.id
if not result[record.id]['cash_control']:
raise UserError(_("Cash control can only be applied to cash journals."))
return result
_columns = {
'config_id' : fields.many2one('pos.config', 'Point of Sale',
help="The physical point of sale you will use.",
required=True,
select=1,
domain="[('state', '=', 'active')]",
),
'name' : fields.char('Session ID', required=True, readonly=True),
'user_id' : fields.many2one('res.users', 'Responsible',
required=True,
select=1,
readonly=True,
states={'opening_control' : [('readonly', False)]}
),
'currency_id' : fields.related('config_id', 'currency_id', type="many2one", relation='res.currency', string="Currency"),
'start_at' : fields.datetime('Opening Date', readonly=True),
'stop_at' : fields.datetime('Closing Date', readonly=True, copy=False),
'state' : fields.selection(POS_SESSION_STATE, 'Status',
required=True, readonly=True,
select=1, copy=False),
'sequence_number': fields.integer('Order Sequence Number', help='A sequence number that is incremented with each order'),
'login_number': fields.integer('Login Sequence Number', help='A sequence number that is incremented each time a user resumes the pos session'),
'cash_control' : fields.function(_compute_cash_all,
multi='cash',
type='boolean', string='Has Cash Control'),
'cash_journal_id' : fields.function(_compute_cash_all,
multi='cash',
type='many2one', relation='account.journal',
string='Cash Journal', store=True),
'cash_register_id' : fields.function(_compute_cash_all,
multi='cash',
type='many2one', relation='account.bank.statement',
string='Cash Register', store=True),
'cash_register_balance_end_real' : fields.related('cash_register_id', 'balance_end_real',
type='float',
digits=0,
string="Ending Balance",
help="Total of closing cash control lines.",
readonly=True),
'cash_register_balance_start' : fields.related('cash_register_id', 'balance_start',
type='float',
digits=0,
string="Starting Balance",
help="Total of opening cash control lines.",
readonly=True),
'cash_register_total_entry_encoding' : fields.related('cash_register_id', 'total_entry_encoding',
string='Total Cash Transaction',
readonly=True,
help="Total of all paid sale orders"),
'cash_register_balance_end' : fields.related('cash_register_id', 'balance_end',
type='float',
digits=0,
string="Theoretical Closing Balance",
help="Sum of opening balance and transactions.",
readonly=True),
'cash_register_difference' : fields.related('cash_register_id', 'difference',
type='float',
string='Difference',
help="Difference between the theoretical closing balance and the real closing balance.",
readonly=True),
'journal_ids' : fields.related('config_id', 'journal_ids',
type='many2many',
readonly=True,
relation='account.journal',
string='Available Payment Methods'),
'order_ids' : fields.one2many('pos.order', 'session_id', 'Orders'),
'statement_ids' : fields.one2many('account.bank.statement', 'pos_session_id', 'Bank Statement', readonly=True),
}
_defaults = {
'name' : '/',
'user_id' : lambda obj, cr, uid, context: uid,
'state' : 'opening_control',
'sequence_number': 1,
'login_number': 0,
}
_sql_constraints = [
('uniq_name', 'unique(name)', "The name of this POS Session must be unique !"),
]
def _check_unicity(self, cr, uid, ids, context=None):
for session in self.browse(cr, uid, ids, context=None):
# open if there is no session in 'opening_control', 'opened', 'closing_control' for one user
domain = [
('state', 'not in', ('closed','closing_control')),
('user_id', '=', session.user_id.id)
]
count = self.search_count(cr, uid, domain, context=context)
if count>1:
return False
return True
def _check_pos_config(self, cr, uid, ids, context=None):
for session in self.browse(cr, uid, ids, context=None):
domain = [
('state', '!=', 'closed'),
('config_id', '=', session.config_id.id)
]
count = self.search_count(cr, uid, domain, context=context)
if count>1:
return False
return True
_constraints = [
(_check_unicity, "You cannot create two active sessions with the same responsible!", ['user_id', 'state']),
(_check_pos_config, "You cannot create two active sessions related to the same point of sale!", ['config_id']),
]
def create(self, cr, uid, values, context=None):
context = dict(context or {})
config_id = values.get('config_id', False) or context.get('default_config_id', False)
if not config_id:
raise UserError(_("You should assign a Point of Sale to your session."))
# journal_id is not required on the pos_config because it does not
# exists at the installation. If nothing is configured at the
# installation we do the minimal configuration. Impossible to do in
# the .xml files as the CoA is not yet installed.
jobj = self.pool.get('pos.config')
pos_config = jobj.browse(cr, uid, config_id, context=context)
context.update({'company_id': pos_config.company_id.id})
if not pos_config.journal_id:
jid = jobj.default_get(cr, uid, ['journal_id'], context=context)['journal_id']
if jid:
jobj.write(cr, SUPERUSER_ID, [pos_config.id], {'journal_id': jid}, context=context)
else:
raise UserError(_("Unable to open the session. You have to assign a sale journal to your point of sale."))
# define some cash journal if no payment method exists
if not pos_config.journal_ids:
journal_proxy = self.pool.get('account.journal')
cashids = journal_proxy.search(cr, uid, [('journal_user', '=', True), ('type','=','cash')], context=context)
if not cashids:
cashids = journal_proxy.search(cr, uid, [('type', '=', 'cash')], context=context)
if not cashids:
cashids = journal_proxy.search(cr, uid, [('journal_user','=',True)], context=context)
journal_proxy.write(cr, SUPERUSER_ID, cashids, {'journal_user': True})
jobj.write(cr, SUPERUSER_ID, [pos_config.id], {'journal_ids': [(6,0, cashids)]})
pos_config = jobj.browse(cr, uid, config_id, context=context)
statements = [(0, 0, {
'journal_id': journal.id,
'user_id': uid,
'company_id': pos_config.company_id.id
}) for journal in pos_config.journal_ids]
values.update({
'name': self.pool['ir.sequence'].next_by_code(cr, uid, 'pos.session', context=context),
'statement_ids': statements,
'config_id': config_id
})
# set the journal_id which should be used by
# account.bank.statement to set the opening balance of the
# newly created bank statement
if pos_config.cash_control:
for journal in pos_config.journal_ids:
if journal.type == 'cash':
context.update({'journal_id': journal.id})
return super(pos_session, self).create(cr, uid, values, context=context)
def unlink(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
self.pool.get('account.bank.statement').unlink(cr, uid, obj.statement_ids.ids, context=context)
return super(pos_session, self).unlink(cr, uid, ids, context=context)
def login(self, cr, uid, ids, context=None):
this_record = self.browse(cr, uid, ids[0], context=context)
this_record.write({
'login_number': this_record.login_number+1,
})
def wkf_action_open(self, cr, uid, ids, context=None):
# second browse because we need to refetch the data from the DB for cash_register_id
for record in self.browse(cr, uid, ids, context=context):
values = {}
if not record.start_at:
values['start_at'] = time.strftime('%Y-%m-%d %H:%M:%S')
values['state'] = 'opened'
record.write(values)
for st in record.statement_ids:
st.button_open()
return True
def wkf_action_opening_control(self, cr, uid, ids, context=None):
return self.write(cr, uid, ids, {'state' : 'opening_control'}, context=context)
def wkf_action_closing_control(self, cr, uid, ids, context=None):
for session in self.browse(cr, uid, ids, context=context):
for statement in session.statement_ids:
if (statement != session.cash_register_id) and (statement.balance_end != statement.balance_end_real):
self.pool.get('account.bank.statement').write(cr, uid, [statement.id], {'balance_end_real': statement.balance_end})
return self.write(cr, uid, ids, {'state' : 'closing_control', 'stop_at' : time.strftime('%Y-%m-%d %H:%M:%S')}, context=context)
def wkf_action_close(self, cr, uid, ids, context=None):
# Close CashBox
local_context = dict(context)
for record in self.browse(cr, uid, ids, context=context):
company_id = record.config_id.company_id.id
local_context.update({'force_company': company_id, 'company_id': company_id})
for st in record.statement_ids:
if abs(st.difference) > st.journal_id.amount_authorized_diff:
# The pos manager can close statements with maximums.
if not self.pool.get('ir.model.access').check_groups(cr, uid, "point_of_sale.group_pos_manager"):
raise UserError(_("Your ending balance is too different from the theoretical cash closing (%.2f), the maximum allowed is: %.2f. You can contact your manager to force it.") % (st.difference, st.journal_id.amount_authorized_diff))
if (st.journal_id.type not in ['bank', 'cash']):
raise UserError(_("The type of the journal for your payment method should be bank or cash "))
self.pool['account.bank.statement'].button_confirm_bank(cr, SUPERUSER_ID, [st.id], context=local_context)
self._confirm_orders(cr, uid, ids, context=local_context)
self.write(cr, uid, ids, {'state' : 'closed'}, context=local_context)
obj = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'point_of_sale', 'menu_point_root')[1]
return {
'type' : 'ir.actions.client',
'name' : 'Point of Sale Menu',
'tag' : 'reload',
'params' : {'menu_id': obj},
}
def _confirm_orders(self, cr, uid, ids, context=None):
pos_order_obj = self.pool.get('pos.order')
for session in self.browse(cr, uid, ids, context=context):
company_id = session.config_id.journal_id.company_id.id
local_context = dict(context or {}, force_company=company_id)
order_ids = [order.id for order in session.order_ids if order.state == 'paid']
move_id = pos_order_obj._create_account_move(cr, uid, session.start_at, session.name, session.config_id.journal_id.id, company_id, context=context)
pos_order_obj._create_account_move_line(cr, uid, order_ids, session, move_id, context=local_context)
for order in session.order_ids:
if order.state == 'done':
continue
if order.state not in ('paid', 'invoiced'):
raise UserError(_("You cannot confirm all orders of this session, because they have not the 'paid' status"))
else:
pos_order_obj.signal_workflow(cr, uid, [order.id], 'done')
return True
def open_frontend_cb(self, cr, uid, ids, context=None):
if not context:
context = {}
if not ids:
return {}
for session in self.browse(cr, uid, ids, context=context):
if session.user_id.id != uid:
raise UserError(_("You cannot use the session of another users. This session is owned by %s. "
"Please first close this one to use this point of sale.") % session.user_id.name)
context.update({'active_id': ids[0]})
return {
'type' : 'ir.actions.act_url',
'target': 'self',
'url': '/pos/web/',
}
class pos_order(osv.osv):
_name = "pos.order"
@@ -1377,12 +759,6 @@ class pos_order(osv.osv):
self.create_account_move(cr, uid, ids, context=context)
return True
class account_bank_statement_line(osv.osv):
_inherit = 'account.bank.statement.line'
_columns= {
'pos_statement_id': fields.many2one('pos.order', string="POS statement", ondelete='cascade'),
}
class pos_order_line(osv.osv):
_name = "pos.order.line"
_description = "Lines of Point of Sale"
@@ -1480,125 +856,3 @@ class pos_order_line(osv.osv):
'discount': lambda *a: 0.0,
'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
}
class pos_category(osv.osv):
_name = "pos.category"
_description = "Public Category"
_order = "sequence, name"
_constraints = [
(osv.osv._check_recursion, 'Error ! You cannot create recursive categories.', ['parent_id'])
]
def name_get(self, cr, uid, ids, context=None):
res = []
for cat in self.browse(cr, uid, ids, context=context):
names = [cat.name]
pcat = cat.parent_id
while pcat:
names.append(pcat.name)
pcat = pcat.parent_id
res.append((cat.id, ' / '.join(reversed(names))))
return res
def _name_get_fnc(self, cr, uid, ids, prop, unknow_none, context=None):
res = self.name_get(cr, uid, ids, context=context)
return dict(res)
_columns = {
'name': fields.char('Name', required=True, translate=True),
'complete_name': fields.function(_name_get_fnc, type="char", string='Name'),
'parent_id': fields.many2one('pos.category','Parent Category', select=True),
'child_id': fields.one2many('pos.category', 'parent_id', string='Children Categories'),
'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of product categories."),
}
# NOTE: there is no 'default image', because by default we don't show
# thumbnails for categories. However if we have a thumbnail for at least one
# category, then we display a default image on the other, so that the
# buttons have consistent styling.
# In this case, the default image is set by the js code.
image = openerp.fields.Binary("Image", attachment=True,
help="This field holds the image used as image for the cateogry, limited to 1024x1024px.")
image_medium = openerp.fields.Binary("Medium-sized image",
compute='_compute_images', inverse='_inverse_image_medium', store=True, attachment=True,
help="Medium-sized image of the category. It is automatically "\
"resized as a 128x128px image, with aspect ratio preserved. "\
"Use this field in form views or some kanban views.")
image_small = openerp.fields.Binary("Small-sized image",
compute='_compute_images', inverse='_inverse_image_small', store=True, attachment=True,
help="Small-sized image of the category. It is automatically "\
"resized as a 64x64px image, with aspect ratio preserved. "\
"Use this field anywhere a small image is required.")
@openerp.api.depends('image')
def _compute_images(self):
for rec in self:
rec.image_medium = tools.image_resize_image_medium(rec.image)
rec.image_small = tools.image_resize_image_small(rec.image)
def _inverse_image_medium(self):
for rec in self:
rec.image = tools.image_resize_image_big(rec.image_medium)
def _inverse_image_small(self):
for rec in self:
rec.image = tools.image_resize_image_big(rec.image_small)
class product_template(osv.osv):
_inherit = 'product.template'
_columns = {
'available_in_pos': fields.boolean('Available in the Point of Sale', help='Check if you want this product to appear in the Point of Sale'),
'to_weight' : fields.boolean('To Weigh With Scale', help="Check if the product should be weighted using the hardware scale integration"),
'pos_categ_id': fields.many2one('pos.category','Point of Sale Category', help="Those categories are used to group similar products for point of sale."),
}
_defaults = {
'to_weight' : False,
'available_in_pos': True,
}
def unlink(self, cr, uid, ids, context=None):
product_ctx = dict(context or {}, active_test=False)
if self.search_count(cr, uid, [('id', 'in', ids), ('available_in_pos', '=', True)], context=product_ctx):
if self.pool['pos.session'].search_count(cr, uid, [('state', '!=', 'closed')], context=context):
raise osv.except_osv(_('Error!'),
_('You cannot delete a product saleable in point of sale while a session is still opened.'))
return super(product_template, self).unlink(cr, uid, ids, context=context)
class res_partner(osv.osv):
_inherit = 'res.partner'
def create_from_ui(self, cr, uid, partner, context=None):
""" create or modify a partner from the point of sale ui.
partner contains the partner's fields. """
#image is a dataurl, get the data after the comma
if partner.get('image',False):
img = partner['image'].split(',')[1]
partner['image'] = img
if partner.get('id',False): # Modifying existing partner
partner_id = partner['id']
del partner['id']
self.write(cr, uid, [partner_id], partner, context=context)
else:
partner_id = self.create(cr, uid, partner, context=context)
return partner_id
class barcode_rule(models.Model):
_inherit = 'barcode.rule'
def _get_type_selection(self):
types = sets.Set(super(barcode_rule,self)._get_type_selection())
types.update([
('weight', _('Weighted Product')),
('price', _('Priced Product')),
('discount', _('Discounted Product')),
('client', _('Client')),
('cashier', _('Cashier'))
])
return list(types)
+307
View File
@@ -0,0 +1,307 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import time
from openerp.osv import fields, osv
from openerp.tools.translate import _
class pos_session(osv.osv):
_name = 'pos.session'
_order = 'id desc'
POS_SESSION_STATE = [
('opening_control', 'Opening Control'), # Signal open
('opened', 'In Progress'), # Signal closing
('closing_control', 'Closing Control'), # Signal close
('closed', 'Closed & Posted'),
]
def _compute_cash_all(self, cr, uid, ids, fieldnames, args, context=None):
result = dict()
for record in self.browse(cr, uid, ids, context=context):
result[record.id] = {
'cash_journal_id' : False,
'cash_register_id' : False,
'cash_control' : False,
}
if record.config_id.cash_control:
for st in record.statement_ids:
if st.journal_id.type == 'cash':
result[record.id]['cash_control'] = True
result[record.id]['cash_journal_id'] = st.journal_id.id
result[record.id]['cash_register_id'] = st.id
if not result[record.id]['cash_control']:
raise UserError(_("Cash control can only be applied to cash journals."))
return result
_columns = {
'config_id' : fields.many2one('pos.config', 'Point of Sale',
help="The physical point of sale you will use.",
required=True,
select=1,
domain="[('state', '=', 'active')]",
),
'name' : fields.char('Session ID', required=True, readonly=True),
'user_id' : fields.many2one('res.users', 'Responsible',
required=True,
select=1,
readonly=True,
states={'opening_control' : [('readonly', False)]}
),
'currency_id' : fields.related('config_id', 'currency_id', type="many2one", relation='res.currency', string="Currency"),
'start_at' : fields.datetime('Opening Date', readonly=True),
'stop_at' : fields.datetime('Closing Date', readonly=True, copy=False),
'state' : fields.selection(POS_SESSION_STATE, 'Status',
required=True, readonly=True,
select=1, copy=False),
'sequence_number': fields.integer('Order Sequence Number', help='A sequence number that is incremented with each order'),
'login_number': fields.integer('Login Sequence Number', help='A sequence number that is incremented each time a user resumes the pos session'),
'cash_control' : fields.function(_compute_cash_all,
multi='cash',
type='boolean', string='Has Cash Control'),
'cash_journal_id' : fields.function(_compute_cash_all,
multi='cash',
type='many2one', relation='account.journal',
string='Cash Journal', store=True),
'cash_register_id' : fields.function(_compute_cash_all,
multi='cash',
type='many2one', relation='account.bank.statement',
string='Cash Register', store=True),
'cash_register_balance_end_real' : fields.related('cash_register_id', 'balance_end_real',
type='float',
digits=0,
string="Ending Balance",
help="Total of closing cash control lines.",
readonly=True),
'cash_register_balance_start' : fields.related('cash_register_id', 'balance_start',
type='float',
digits=0,
string="Starting Balance",
help="Total of opening cash control lines.",
readonly=True),
'cash_register_total_entry_encoding' : fields.related('cash_register_id', 'total_entry_encoding',
string='Total Cash Transaction',
readonly=True,
help="Total of all paid sale orders"),
'cash_register_balance_end' : fields.related('cash_register_id', 'balance_end',
type='float',
digits=0,
string="Theoretical Closing Balance",
help="Sum of opening balance and transactions.",
readonly=True),
'cash_register_difference' : fields.related('cash_register_id', 'difference',
type='float',
string='Difference',
help="Difference between the theoretical closing balance and the real closing balance.",
readonly=True),
'journal_ids' : fields.related('config_id', 'journal_ids',
type='many2many',
readonly=True,
relation='account.journal',
string='Available Payment Methods'),
'order_ids' : fields.one2many('pos.order', 'session_id', 'Orders'),
'statement_ids' : fields.one2many('account.bank.statement', 'pos_session_id', 'Bank Statement', readonly=True),
}
_defaults = {
'name' : '/',
'user_id' : lambda obj, cr, uid, context: uid,
'state' : 'opening_control',
'sequence_number': 1,
'login_number': 0,
}
_sql_constraints = [
('uniq_name', 'unique(name)', "The name of this POS Session must be unique !"),
]
def _check_unicity(self, cr, uid, ids, context=None):
for session in self.browse(cr, uid, ids, context=None):
# open if there is no session in 'opening_control', 'opened', 'closing_control' for one user
domain = [
('state', 'not in', ('closed','closing_control')),
('user_id', '=', session.user_id.id)
]
count = self.search_count(cr, uid, domain, context=context)
if count>1:
return False
return True
def _check_pos_config(self, cr, uid, ids, context=None):
for session in self.browse(cr, uid, ids, context=None):
domain = [
('state', '!=', 'closed'),
('config_id', '=', session.config_id.id)
]
count = self.search_count(cr, uid, domain, context=context)
if count>1:
return False
return True
_constraints = [
(_check_unicity, "You cannot create two active sessions with the same responsible!", ['user_id', 'state']),
(_check_pos_config, "You cannot create two active sessions related to the same point of sale!", ['config_id']),
]
def create(self, cr, uid, values, context=None):
context = dict(context or {})
config_id = values.get('config_id', False) or context.get('default_config_id', False)
if not config_id:
raise UserError(_("You should assign a Point of Sale to your session."))
# journal_id is not required on the pos_config because it does not
# exists at the installation. If nothing is configured at the
# installation we do the minimal configuration. Impossible to do in
# the .xml files as the CoA is not yet installed.
jobj = self.pool.get('pos.config')
pos_config = jobj.browse(cr, uid, config_id, context=context)
context.update({'company_id': pos_config.company_id.id})
if not pos_config.journal_id:
jid = jobj.default_get(cr, uid, ['journal_id'], context=context)['journal_id']
if jid:
jobj.write(cr, SUPERUSER_ID, [pos_config.id], {'journal_id': jid}, context=context)
else:
raise UserError(_("Unable to open the session. You have to assign a sale journal to your point of sale."))
# define some cash journal if no payment method exists
if not pos_config.journal_ids:
journal_proxy = self.pool.get('account.journal')
cashids = journal_proxy.search(cr, uid, [('journal_user', '=', True), ('type','=','cash')], context=context)
if not cashids:
cashids = journal_proxy.search(cr, uid, [('type', '=', 'cash')], context=context)
if not cashids:
cashids = journal_proxy.search(cr, uid, [('journal_user','=',True)], context=context)
journal_proxy.write(cr, SUPERUSER_ID, cashids, {'journal_user': True})
jobj.write(cr, SUPERUSER_ID, [pos_config.id], {'journal_ids': [(6,0, cashids)]})
pos_config = jobj.browse(cr, uid, config_id, context=context)
statements = [(0, 0, {
'journal_id': journal.id,
'user_id': uid,
'company_id': pos_config.company_id.id
}) for journal in pos_config.journal_ids]
values.update({
'name': self.pool['ir.sequence'].next_by_code(cr, uid, 'pos.session', context=context),
'statement_ids': statements,
'config_id': config_id
})
# set the journal_id which should be used by
# account.bank.statement to set the opening balance of the
# newly created bank statement
if pos_config.cash_control:
for journal in pos_config.journal_ids:
if journal.type == 'cash':
context.update({'journal_id': journal.id})
return super(pos_session, self).create(cr, uid, values, context=context)
def unlink(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
self.pool.get('account.bank.statement').unlink(cr, uid, obj.statement_ids.ids, context=context)
return super(pos_session, self).unlink(cr, uid, ids, context=context)
def login(self, cr, uid, ids, context=None):
this_record = self.browse(cr, uid, ids[0], context=context)
this_record.write({
'login_number': this_record.login_number+1,
})
def wkf_action_open(self, cr, uid, ids, context=None):
# second browse because we need to refetch the data from the DB for cash_register_id
for record in self.browse(cr, uid, ids, context=context):
values = {}
if not record.start_at:
values['start_at'] = time.strftime('%Y-%m-%d %H:%M:%S')
values['state'] = 'opened'
record.write(values)
for st in record.statement_ids:
st.button_open()
return True
def wkf_action_opening_control(self, cr, uid, ids, context=None):
return self.write(cr, uid, ids, {'state' : 'opening_control'}, context=context)
def wkf_action_closing_control(self, cr, uid, ids, context=None):
for session in self.browse(cr, uid, ids, context=context):
for statement in session.statement_ids:
if (statement != session.cash_register_id) and (statement.balance_end != statement.balance_end_real):
self.pool.get('account.bank.statement').write(cr, uid, [statement.id], {'balance_end_real': statement.balance_end})
return self.write(cr, uid, ids, {'state' : 'closing_control', 'stop_at' : time.strftime('%Y-%m-%d %H:%M:%S')}, context=context)
def wkf_action_close(self, cr, uid, ids, context=None):
# Close CashBox
local_context = dict(context)
for record in self.browse(cr, uid, ids, context=context):
company_id = record.config_id.company_id.id
local_context.update({'force_company': company_id, 'company_id': company_id})
for st in record.statement_ids:
if abs(st.difference) > st.journal_id.amount_authorized_diff:
# The pos manager can close statements with maximums.
if not self.pool.get('ir.model.access').check_groups(cr, uid, "point_of_sale.group_pos_manager"):
raise UserError(_("Your ending balance is too different from the theoretical cash closing (%.2f), the maximum allowed is: %.2f. You can contact your manager to force it.") % (st.difference, st.journal_id.amount_authorized_diff))
if (st.journal_id.type not in ['bank', 'cash']):
raise UserError(_("The type of the journal for your payment method should be bank or cash "))
self.pool['account.bank.statement'].button_confirm_bank(cr, SUPERUSER_ID, [st.id], context=local_context)
self._confirm_orders(cr, uid, ids, context=local_context)
self.write(cr, uid, ids, {'state' : 'closed'}, context=local_context)
obj = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'point_of_sale', 'menu_point_root')[1]
return {
'type' : 'ir.actions.client',
'name' : 'Point of Sale Menu',
'tag' : 'reload',
'params' : {'menu_id': obj},
}
def _confirm_orders(self, cr, uid, ids, context=None):
pos_order_obj = self.pool.get('pos.order')
for session in self.browse(cr, uid, ids, context=context):
company_id = session.config_id.journal_id.company_id.id
local_context = dict(context or {}, force_company=company_id)
order_ids = [order.id for order in session.order_ids if order.state == 'paid']
move_id = pos_order_obj._create_account_move(cr, uid, session.start_at, session.name, session.config_id.journal_id.id, company_id, context=context)
pos_order_obj._create_account_move_line(cr, uid, order_ids, session, move_id, context=local_context)
for order in session.order_ids:
if order.state == 'done':
continue
if order.state not in ('paid', 'invoiced'):
raise UserError(_("You cannot confirm all orders of this session, because they have not the 'paid' status"))
else:
pos_order_obj.signal_workflow(cr, uid, [order.id], 'done')
return True
def open_frontend_cb(self, cr, uid, ids, context=None):
if not context:
context = {}
if not ids:
return {}
for session in self.browse(cr, uid, ids, context=context):
if session.user_id.id != uid:
raise UserError(_("You cannot use the session of another users. This session is owned by %s. "
"Please first close this one to use this point of sale.") % session.user_id.name)
context.update({'active_id': ids[0]})
return {
'type' : 'ir.actions.act_url',
'target': 'self',
'url': '/pos/web/',
}
+27
View File
@@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp.osv import fields, osv
from openerp.tools.translate import _
class product_template(osv.osv):
_inherit = 'product.template'
_columns = {
'available_in_pos': fields.boolean('Available in the Point of Sale', help='Check if you want this product to appear in the Point of Sale'),
'to_weight' : fields.boolean('To Weigh With Scale', help="Check if the product should be weighted using the hardware scale integration"),
'pos_categ_id': fields.many2one('pos.category','Point of Sale Category', help="Those categories are used to group similar products for point of sale."),
}
_defaults = {
'to_weight' : False,
'available_in_pos': True,
}
def unlink(self, cr, uid, ids, context=None):
product_ctx = dict(context or {}, active_test=False)
if self.search_count(cr, uid, [('id', 'in', ids), ('available_in_pos', '=', True)], context=product_ctx):
if self.pool['pos.session'].search_count(cr, uid, [('state', '!=', 'closed')], context=context):
raise osv.except_osv(_('Error!'),
_('You cannot delete a product saleable in point of sale while a session is still opened.'))
return super(product_template, self).unlink(cr, uid, ids, context=context)
+19 -1
View File
@@ -6,4 +6,22 @@ class res_users(osv.osv):
_inherit = 'res.partner'
_columns = {
'barcode' : fields.char('Barcode', help="BarCode", oldname='ean13'),
}
}
def create_from_ui(self, cr, uid, partner, context=None):
""" create or modify a partner from the point of sale ui.
partner contains the partner's fields. """
#image is a dataurl, get the data after the comma
if partner.get('image',False):
img = partner['image'].split(',')[1]
partner['image'] = img
if partner.get('id',False): # Modifying existing partner
partner_id = partner['id']
del partner['id']
self.write(cr, uid, [partner_id], partner, context=context)
else:
partner_id = self.create(cr, uid, partner, context=context)
return partner_id
@@ -0,0 +1,13 @@
<?xml version="1.0"?>
<openerp>
<data>
<record model="ir.actions.act_window" id="action_pos_invoice">
<field name="name">Invoices</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">account.invoice</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="domain">[('origin','like','POS')]</field>
</record>
</data>
</openerp>
@@ -0,0 +1,29 @@
<?xml version="1.0"?>
<openerp>
<data>
<record id="action_account_journal_form" model="ir.actions.act_window">
<field name="name">Payment Methods</field>
<field name="res_model">account.journal</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="context" eval="{'default_journal_user': 1, 'default_type': 'cash'}"/>
<field name="domain">[('journal_user','=', 1)]</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to add a payment method.
</p><p>
Payment methods are defined by accounting journals having the
field <i>PoS Payment Method</i> checked. In order to be useable
from the touchscreen interface, you must set the payment method
on the <i>Point of Sale</i> configuration.
</p>
</field>
</record>
<menuitem action="action_account_journal_form"
id="menu_action_account_journal_form_open"
parent="menu_point_config_product"
sequence="20"/>
</data>
</openerp>
@@ -56,5 +56,223 @@
name="point_of_sale.report_sessionsummary"
file="point_of_sale.report_sessionsummary"
/>
<!-- report -->
<record model="ir.ui.view" id="view_pos_trans_user_form">
<field name="name">report.trans.pos.user.form</field>
<field name="model">report.transaction.pos</field>
<field name="arch" type="xml">
<form string="POS ">
<group col="4">
<field name="user_id"/>
<field name="journal_id"/>
<field name="jl_id"/>
<field name="date_create" widget="date"/>
<field name="no_trans"/>
<field name="amount"/>
<field name="invoice_id"/>
</group>
</form>
</field>
</record>
<record model="ir.ui.view" id="view_trans_pos_user_tree">
<field name="name">Sales by user</field>
<field name="model">report.transaction.pos</field>
<field name="arch" type="xml">
<tree string="POS">
<field name="date_create" widget="date" />
<field name="user_id"/>
<field name="journal_id"/>
<field name="jl_id"/>
<field name="no_trans" sum="Total Transaction"/>
<field name="amount" sum="Amount total"/>
<field name="product_nb" />
<field name="invoice_id"/>
<field name="disc"/>
</tree>
</field>
</record>
<record model="ir.ui.view" id="view_report_transaction_pos_calendar">
<field name="name">report.transaction.pos.calendar</field>
<field name="model">report.transaction.pos</field>
<field eval="2" name="priority"/>
<field name="arch" type="xml">
<calendar color="user_id" date_start="date_create" string="POS Report">
<field name="journal_id"/>
<field name="amount"/>
</calendar>
</field>
</record>
<record model="ir.ui.view" id="view_report_transaction_pos_graph">
<field name="name">report.transaction.pos.graph</field>
<field name="model">report.transaction.pos</field>
<field eval="2" name="priority"/>
<field name="arch" type="xml">
<graph type="bar" string="POS Report">
<field name="user_id" />
<field name="amount"/>
<field group="True" name="journal_id"/>
</graph>
</field>
</record>
<record model="ir.actions.act_window" id="action_trans_pos_tree_today">
<field name="name">Sales by day</field>
<field name="res_model">report.transaction.pos</field>
<field name="view_type">form</field>
<field name="view_mode">tree,calendar,form,graph</field>
<field name="domain">[('date_create','=',time.strftime('%Y-%m-%d'))]</field>
</record>
<record model="ir.actions.act_window" id="action_trans_pos_tree_month">
<field name="name">Sales by month</field>
<field name="res_model">report.transaction.pos</field>
<field name="view_type">form</field>
<field name="view_mode">tree,calendar,form,graph</field>
<field name="domain">[('date_create','like',time.strftime('%Y-%m'))]</field>
</record>
<record model="ir.actions.act_window" id="action_trans_pos_tree">
<field name="name">Sales by user</field>
<field name="res_model">report.transaction.pos</field>
<field name="view_type">form</field>
<field name="view_mode">tree,calendar,form,graph</field>
<field name="view_id" ref="view_trans_pos_user_tree"/>
</record>
<record model="ir.ui.view" id="view_report_sales_by_user_pos_form">
<field name="name">report.sales.by.user.pos.form</field>
<field name="model">report.sales.by.user.pos</field>
<field name="arch" type="xml">
<form string="POS ">
<group col="4">
<field name="date_order" widget="date"/>
<field name="qty"/>
<field name="amount"/>
<field name="user_id"/>
</group>
</form>
</field>
</record>
<record model="ir.ui.view" id="view_report_sales_by_user_pos_tree">
<field name="name">report.sales.by.user.pos.tree</field>
<field name="model">report.sales.by.user.pos</field>
<field name="arch" type="xml">
<tree string="POS">
<field name="date_order" widget="date"/>
<field name="qty"/>
<field name="amount"/>
<field name="user_id"/>
</tree>
</field>
</record>
<record model="ir.ui.view" id="view_report_sales_by_user_pos_calendar">
<field name="name">report.sales.by.user.pos.calendar</field>
<field name="model">report.sales.by.user.pos</field>
<field eval="2" name="priority"/>
<field name="arch" type="xml">
<calendar color="user_id" date_start="date_order" string="POS Report">
<field name="qty"/>
<field name="amount" />
</calendar>
</field>
</record>
<record model="ir.ui.view" id="view_report_sales_by_user_pos_graph">
<field name="name">report.sales.by.user.pos.graph</field>
<field name="model">report.sales.by.user.pos</field>
<field eval="2" name="priority"/>
<field name="arch" type="xml">
<graph type="bar" string="Sales by User">
<field name="user_id" />
<field name="amount"/>
</graph>
</field>
</record>
<record model="ir.actions.act_window" id="action_report_sales_by_user_pos_today">
<field name="name">Sales by User</field>
<field name="res_model">report.sales.by.user.pos</field>
<field name="view_type">form</field>
<field name="view_mode">tree,calendar,form,graph</field>
<field name="domain">[('date_order','=',time.strftime('%Y-%m-%d'))]</field>
</record>
<!-- Sales of user by month -->
<record model="ir.ui.view" id="view_report_sales_by_user_pos_month_form">
<field name="name">report.sales.by.user.pos.month.form</field>
<field name="model">report.sales.by.user.pos.month</field>
<field name="arch" type="xml">
<form string="POS ">
<group col="4">
<field name="date_order" widget="date"/>
<field name="qty"/>
<field name="amount"/>
<field name="user_id"/>
</group>
</form>
</field>
</record>
<record model="ir.ui.view" id="view_report_sales_by_user_pos_month_tree">
<field name="name">report.sales.by.user.pos.month.tree</field>
<field name="model">report.sales.by.user.pos.month</field>
<field name="arch" type="xml">
<tree string="POS">
<field name="date_order" widget="date"/>
<field name="qty"/>
<field name="amount"/>
<field name="user_id"/>
</tree>
</field>
</record>
<record model="ir.ui.view" id="view_report_sales_by_user_pos_month_calendar">
<field name="name">report.sales.by.user.pos.month.calendar</field>
<field name="model">report.sales.by.user.pos.month</field>
<field eval="2" name="priority"/>
<field name="arch" type="xml">
<calendar color="user_id" date_start="date_order" string="POS Report">
<field name="qty"/>
<field name="amount" />
</calendar>
</field>
</record>
<record model="ir.ui.view" id="view_report_sales_by_user_pos_month_graph">
<field name="name">report.sales.by.user.pos.month.graph</field>
<field name="model">report.sales.by.user.pos.month</field>
<field eval="2" name="priority"/>
<field name="arch" type="xml">
<graph type="bar" string="Sales by User">
<field name="user_id" />
<field name="amount"/>
</graph>
</field>
</record>
<record model="ir.actions.act_window" id="action_report_sales_by_user_pos_month">
<field name="name">Sales by User Monthly</field>
<field name="res_model">report.sales.by.user.pos.month</field>
<field name="view_type">form</field>
<field name="view_mode">tree,calendar,form,graph</field>
<field name="domain">[('date_order','like',time.strftime('%Y-%m'))]</field>
</record>
</data>
</openerp>
@@ -10,989 +10,5 @@
web_icon="point_of_sale,static/description/icon.png"
sequence="20"/>
<record model="ir.ui.view" id="view_pos_pos_form">
<field name="name">pos.order</field>
<field name="model">pos.order</field>
<field name="arch" type="xml">
<form string="Point of Sale Orders">
<header>
<button name="%(action_pos_payment)d" string="Payment" class="oe_highlight" type="action" states="draft" context="{'pos_session_id' : session_id}"/>
<button name="action_invoice" string="Invoice" type="object" states="paid" attrs="{'readonly': [('partner_id','=',False)]}"/>
<button name="refund" string="Return Products" type="object"
attrs="{'invisible':[('state','=','draft')]}"/>
<field name="state" widget="statusbar" statusbar_visible="draft,paid,done" statusbar_colors='{"cancel":"red"}'/>
</header>
<sheet>
<group col="4" colspan="4" name="order_fields">
<field name="name"/>
<field name="date_order"/>
<field name="session_id" />
<field name="partner_id" on_change="onchange_partner_id(partner_id, context)" domain="[('customer', '=', True)]" context="{'search_default_customer':1}" attrs="{'readonly': [('state','=','invoiced')]}"/>
<field name="fiscal_position_id" options="{'no_create': True}"/>
</group>
<notebook colspan="4">
<page string="Products">
<field name="lines" colspan="4" nolabel="1">
<tree string="Order lines" editable="bottom">
<field name="product_id" on_change="onchange_product_id(parent.pricelist_id,product_id,qty,parent.partner_id)"/>
<field name="qty" on_change="onchange_qty(parent.pricelist_id,product_id, discount, qty, price_unit, context)"/>
<field name="price_unit" on_change="onchange_qty(parent.pricelist_id,product_id, discount, qty, price_unit, context)" widget="monetary"/>
<field name="discount" on_change="onchange_qty(parent.pricelist_id,product_id, discount, qty, price_unit, context)" widget="monetary"/>
<field name="tax_ids_after_fiscal_position" widget="many2many_tags"/>
<field name="price_subtotal" widget="monetary"/>
<field name="price_subtotal_incl" widget="monetary"/>
</tree>
<form string="Order lines">
<group col="4">
<field name="product_id" on_change="onchange_product_id(parent.pricelist_id,product_id,qty,parent.partner_id)"/>
<field name="qty" on_change="onchange_qty(parent.pricelist_id,product_id, discount, qty, price_unit, context)"/>
<field name="discount" on_change="onchange_qty(parent.pricelist_id,product_id, discount, qty, price_unit, context)" widget="monetary"/>
<field name="price_unit" on_change="onchange_qty(parent.pricelist_id,product_id, discount, qty, price_unit, context)" widget="monetary"/>
<field name="price_subtotal" invisible="1" widget="monetary"/>
<field name="price_subtotal_incl" invisible="1" widget="monetary"/>
<field name="tax_ids" widget="many2many_tags"/>
<field name="notice"/>
</group>
</form>
</field>
<group class="oe_subtotal_footer oe_right" colspan="2" name="order_total">
<field name="amount_tax" widget="monetary"/>
<div class="oe_subtotal_footer_separator oe_inline">
<label for="amount_total" />
<button name="button_dummy"
states="draft" string="(update)" class="oe_edit_only oe_link"/>
</div>
<field name="amount_total" nolabel="1" class="oe_subtotal_footer_separator" widget="monetary"/>
</group>
<div class="oe_clear"/>
</page>
<page string="Payments">
<field name="statement_ids" colspan="4" nolabel="1">
<tree editable="bottom" string="Statement lines">
<field name="journal_id"/>
<field name="statement_id"/>
<field name="amount"/>
</tree>
</field>
</page>
<page name="extra" string="Extra Info">
<group string="General Information">
<field name="company_id" groups="base.group_multi_company"/>
<field name="location_id" widget="selection" groups="stock.group_stock_multi_locations"/>
<field name="user_id"/>
<field name="pricelist_id" groups="product.group_sale_pricelist"/>
<field name="picking_id" readonly="1"/>
<field name="pos_reference"/>
</group>
<group string="Accounting Information">
<group>
<field name="sale_journal" domain="[('type','=','sale')]"/>
<field name="invoice_id" readonly="1"
attrs="{'invisible':[('state','&lt;&gt;','invoiced')]}"/>
<field name="account_move" readonly="1"
attrs="{'invisible':[('state','&lt;&gt;','done')]}"/>
</group>
<group>
<button name="%(pos_invoice_report)d" string="Re-Print"
icon="gtk-print" type="action" attrs="{'invisible':[('state','&lt;&gt;','invoiced')]}"/>
</group>
</group>
</page>
<page string="Notes" >
<field name="note"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record model="ir.actions.act_window" id="action_pos_pos_form">
<field name="name">Orders</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">pos.order</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="view_id" eval="False"/>
<field name="domain">[]</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to create a new order.
</p><p>
Use this menu to browse previous orders. To record new
orders, you may use the menu <i>Your Session</i> for
the touchscreen interface.
</p>
</field>
</record>
<record model="ir.ui.view" id="view_pos_order_tree">
<field name="name">Orders</field>
<field name="model">pos.order</field>
<field name="arch" type="xml">
<tree string="POS Orders" decoration-info="state == 'draft'" decoration-muted="state in ('done','cancel')">
<field name="name"/>
<field name="pos_reference"/>
<field name="partner_id"/>
<field name="date_order"/>
<field name="user_id"/>
<field name="invoice_id"/>
<field name="amount_total" sum="Amount total" widget="monetary"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="state"/>
<field name="session_id" />
</tree>
</field>
</record>
<record id="view_pos_order_filter" model="ir.ui.view">
<field name="name">pos.order.list.select</field>
<field name="model">pos.order</field>
<field name="arch" type="xml">
<search string="Search Sales Order">
<field name="name" string="Sales Order"/>
<field name="date_order"/>
<field name="pos_reference"/>
<filter string="New" domain="[('state','=','draft')]"/>
<filter string="Done" domain="[('state','in',('paid','invoiced','done'))]"/>
<filter string="Invoiced" domain="[('state','=','invoiced')]"/>
<filter string="Posted" domain="[('state','=','done')]"/>
<field name="user_id"/>
<group expand="0" string="Group By">
<filter string="Customer" domain="[]" context="{'group_by':'partner_id'}"/>
<filter string="Salesperson" domain="[]" context="{'group_by':'user_id'}"/>
<filter string="Status" domain="[]" context="{'group_by':'state'}"/>
<filter string="Order Month" domain="[]" context="{'group_by':'date_order'}"/>
</group>
</search>
</field>
</record>
<record model="ir.ui.view" id="view_pos_order_search">
<field name="name">pos.order.search.view</field>
<field name="model">pos.order</field>
<field name="arch" type="xml">
<search string="Point of Sale Orders">
<field name="name"/>
<field name="config_id"/>
</search>
</field>
</record>
<menuitem name="Orders" id="menu_point_of_sale" parent="menu_point_root" sequence="10"/>
<menuitem parent="menu_point_of_sale" id="menu_point_ofsale" action="action_pos_pos_form" sequence="1" groups="group_pos_manager,group_pos_user"/>
<record id="product_template_action" model="ir.actions.act_window">
<field name="name">Products</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">product.template</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,kanban</field>
<field name="context" eval="{}"/>
<field name="domain" eval="[('available_in_pos','&lt;&gt;',False)]"/>
<field name="search_view_id" eval="False"/> <!-- Force empty -->
<field name="view_id" eval="False"/> <!-- Force empty -->
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to add a new product.
</p><p>
You must define a product for everything you sell through
the point of sale interface.
</p><p>
Do not forget to set the price and the point of sale category
in which it should appear. If a product has no point of sale
category, you can not sell it through the point of sale
interface.
</p>
</field>
</record>
<menuitem
action="product_template_action"
id="menu_pos_products"
parent="menu_point_of_sale" sequence="3"/>
<record model="ir.ui.view" id="view_pos_order_line">
<field name="name">Sale lines</field>
<field name="model">pos.order.line</field>
<field name="arch" type="xml">
<tree string="POS Order lines">
<field name="product_id" readonly="1"/>
<field name="qty" readonly="1" sum="Total qty"/>
<field name="discount" readonly="1" widget="monetary"/>
<field name="price_unit" readonly="1" widget="monetary"/>
<field name="price_subtotal" readonly="1" sum="Sum of subtotals" widget="monetary"/>
<field name="price_subtotal_incl" readonly="1" sum="Sum of subtotals" widget="monetary"/>
<field name="create_date" readonly="1"/>
</tree>
</field>
</record>
<record model="ir.ui.view" id="view_pos_order_line_form">
<field name="name">Sale line</field>
<field name="model">pos.order.line</field>
<field name="arch" type="xml">
<form string="POS Order line">
<group col="4">
<field name="product_id" />
<field name="qty" />
<field name="discount" widget="monetary"/>
<field name="price_unit" widget="monetary"/>
<field name="create_date" />
</group>
</form>
</field>
</record>
<record model="ir.actions.act_window" id="action_pos_order_line">
<field name="name">Sale line</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">pos.order.line</field>
<field name="view_type">form</field>
<field name="view_mode">tree</field>
<field name="view_id" ref="view_pos_order_line"/>
</record>
<record model="ir.actions.act_window" id="action_pos_order_line_form">
<field name="name">Sale line</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">pos.order.line</field>
<field name="view_type">form</field>
<field name="view_mode">form,tree</field>
<field name="view_id" ref="view_pos_order_line_form"/>
</record>
<record model="ir.actions.act_window" id="action_pos_order_line_day">
<field name="name">Sale line</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">pos.order.line</field>
<field name="view_type">form</field>
<field name="view_mode">tree</field>
<field name="view_id" ref="view_pos_order_line"/>
<field name="domain">[('create_date', '&gt;=', time.strftime('%Y-%m-%d 00:00:00')),('create_date', '&lt;=', time.strftime('%Y-%m-%d 23:59:59'))]</field>
</record>
<!-- report -->
<record model="ir.ui.view" id="view_pos_trans_user_form">
<field name="name">report.trans.pos.user.form</field>
<field name="model">report.transaction.pos</field>
<field name="arch" type="xml">
<form string="POS ">
<group col="4">
<field name="user_id"/>
<field name="journal_id"/>
<field name="jl_id"/>
<field name="date_create" widget="date"/>
<field name="no_trans"/>
<field name="amount"/>
<field name="invoice_id"/>
</group>
</form>
</field>
</record>
<record model="ir.ui.view" id="view_trans_pos_user_tree">
<field name="name">Sales by user</field>
<field name="model">report.transaction.pos</field>
<field name="arch" type="xml">
<tree string="POS">
<field name="date_create" widget="date" />
<field name="user_id"/>
<field name="journal_id"/>
<field name="jl_id"/>
<field name="no_trans" sum="Total Transaction"/>
<field name="amount" sum="Amount total"/>
<field name="product_nb" />
<field name="invoice_id"/>
<field name="disc"/>
</tree>
</field>
</record>
<record model="ir.ui.view" id="view_report_transaction_pos_calendar">
<field name="name">report.transaction.pos.calendar</field>
<field name="model">report.transaction.pos</field>
<field eval="2" name="priority"/>
<field name="arch" type="xml">
<calendar color="user_id" date_start="date_create" string="POS Report">
<field name="journal_id"/>
<field name="amount"/>
</calendar>
</field>
</record>
<record model="ir.ui.view" id="view_report_transaction_pos_graph">
<field name="name">report.transaction.pos.graph</field>
<field name="model">report.transaction.pos</field>
<field eval="2" name="priority"/>
<field name="arch" type="xml">
<graph type="bar" string="POS Report">
<field name="user_id" />
<field name="amount"/>
<field group="True" name="journal_id"/>
</graph>
</field>
</record>
<record model="ir.actions.act_window" id="action_trans_pos_tree_today">
<field name="name">Sales by day</field>
<field name="res_model">report.transaction.pos</field>
<field name="view_type">form</field>
<field name="view_mode">tree,calendar,form,graph</field>
<field name="domain">[('date_create','=',time.strftime('%Y-%m-%d'))]</field>
</record>
<record model="ir.actions.act_window" id="action_trans_pos_tree_month">
<field name="name">Sales by month</field>
<field name="res_model">report.transaction.pos</field>
<field name="view_type">form</field>
<field name="view_mode">tree,calendar,form,graph</field>
<field name="domain">[('date_create','like',time.strftime('%Y-%m'))]</field>
</record>
<record model="ir.actions.act_window" id="action_trans_pos_tree">
<field name="name">Sales by user</field>
<field name="res_model">report.transaction.pos</field>
<field name="view_type">form</field>
<field name="view_mode">tree,calendar,form,graph</field>
<field name="view_id" ref="view_trans_pos_user_tree"/>
</record>
<record model="ir.ui.view" id="view_report_sales_by_user_pos_form">
<field name="name">report.sales.by.user.pos.form</field>
<field name="model">report.sales.by.user.pos</field>
<field name="arch" type="xml">
<form string="POS ">
<group col="4">
<field name="date_order" widget="date"/>
<field name="qty"/>
<field name="amount"/>
<field name="user_id"/>
</group>
</form>
</field>
</record>
<record model="ir.ui.view" id="view_report_sales_by_user_pos_tree">
<field name="name">report.sales.by.user.pos.tree</field>
<field name="model">report.sales.by.user.pos</field>
<field name="arch" type="xml">
<tree string="POS">
<field name="date_order" widget="date"/>
<field name="qty"/>
<field name="amount"/>
<field name="user_id"/>
</tree>
</field>
</record>
<record model="ir.ui.view" id="view_report_sales_by_user_pos_calendar">
<field name="name">report.sales.by.user.pos.calendar</field>
<field name="model">report.sales.by.user.pos</field>
<field eval="2" name="priority"/>
<field name="arch" type="xml">
<calendar color="user_id" date_start="date_order" string="POS Report">
<field name="qty"/>
<field name="amount" />
</calendar>
</field>
</record>
<record model="ir.ui.view" id="view_report_sales_by_user_pos_graph">
<field name="name">report.sales.by.user.pos.graph</field>
<field name="model">report.sales.by.user.pos</field>
<field eval="2" name="priority"/>
<field name="arch" type="xml">
<graph type="bar" string="Sales by User">
<field name="user_id" />
<field name="amount"/>
</graph>
</field>
</record>
<record model="ir.actions.act_window" id="action_report_sales_by_user_pos_today">
<field name="name">Sales by User</field>
<field name="res_model">report.sales.by.user.pos</field>
<field name="view_type">form</field>
<field name="view_mode">tree,calendar,form,graph</field>
<field name="domain">[('date_order','=',time.strftime('%Y-%m-%d'))]</field>
</record>
<!-- Sales of user by month -->
<record model="ir.ui.view" id="view_report_sales_by_user_pos_month_form">
<field name="name">report.sales.by.user.pos.month.form</field>
<field name="model">report.sales.by.user.pos.month</field>
<field name="arch" type="xml">
<form string="POS ">
<group col="4">
<field name="date_order" widget="date"/>
<field name="qty"/>
<field name="amount"/>
<field name="user_id"/>
</group>
</form>
</field>
</record>
<record model="ir.ui.view" id="view_report_sales_by_user_pos_month_tree">
<field name="name">report.sales.by.user.pos.month.tree</field>
<field name="model">report.sales.by.user.pos.month</field>
<field name="arch" type="xml">
<tree string="POS">
<field name="date_order" widget="date"/>
<field name="qty"/>
<field name="amount"/>
<field name="user_id"/>
</tree>
</field>
</record>
<record model="ir.ui.view" id="view_report_sales_by_user_pos_month_calendar">
<field name="name">report.sales.by.user.pos.month.calendar</field>
<field name="model">report.sales.by.user.pos.month</field>
<field eval="2" name="priority"/>
<field name="arch" type="xml">
<calendar color="user_id" date_start="date_order" string="POS Report">
<field name="qty"/>
<field name="amount" />
</calendar>
</field>
</record>
<record model="ir.ui.view" id="view_report_sales_by_user_pos_month_graph">
<field name="name">report.sales.by.user.pos.month.graph</field>
<field name="model">report.sales.by.user.pos.month</field>
<field eval="2" name="priority"/>
<field name="arch" type="xml">
<graph type="bar" string="Sales by User">
<field name="user_id" />
<field name="amount"/>
</graph>
</field>
</record>
<record model="ir.actions.act_window" id="action_report_sales_by_user_pos_month">
<field name="name">Sales by User Monthly</field>
<field name="res_model">report.sales.by.user.pos.month</field>
<field name="view_type">form</field>
<field name="view_mode">tree,calendar,form,graph</field>
<field name="domain">[('date_order','like',time.strftime('%Y-%m'))]</field>
</record>
<!-- Product Public Categories -->
<record id="product_pos_category_form_view" model="ir.ui.view">
<field name="name">pos.category.form</field>
<field name="model">pos.category</field>
<field name="arch" type="xml">
<form string="Pos Categories">
<sheet>
<field name="image_medium" widget='image' class="oe_avatar oe_right"/>
<div class="oe_left">
<group>
<field name="name"/>
<field name="parent_id"/>
<field name="sequence"/>
</group>
</div>
</sheet>
</form>
</field>
</record>
<record id="product_pos_category_tree_view" model="ir.ui.view">
<field name="name">pos.category.tree</field>
<field name="model">pos.category</field>
<field name="field_parent" eval="False"/>
<field name="arch" type="xml">
<tree string="Product Product Categories">
<field name="sequence" widget="handle"/>
<field name="complete_name"/>
</tree>
</field>
</record>
<record id="product_pos_category_action" model="ir.actions.act_window">
<field name="name">Pos Product Categories</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">pos.category</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="view_id" eval="False"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to define a new category.
</p><p>
Categories are used to browse your products through the
touchscreen interface.
</p><p>
If you put a photo on the category and enable the option in the
point of sale configuration, the layout of the touchscreen interface
will automatically be displayed. We suggest not to put a photo
on categories for small (1024x768) screens.
</p>
</field>
</record>
<menuitem name="Configuration" parent="menu_point_root"
id="menu_point_config_product" sequence="100" groups="group_pos_manager,base.group_configuration"/>
<menuitem action="product_pos_category_action" id="menu_product_pos_category" parent="menu_point_config_product" sequence="20" />
<!-- END -->
<record id="product_template_form_view" model="ir.ui.view">
<field name="name">product.template.form.inherit</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_form_view"/>
<field name="arch" type="xml">
<group name="sale_condition" position="after">
<group name="pos" string="Point of Sale">
<field name="available_in_pos"/>
<field name="pos_categ_id" attrs="{'invisible': [('available_in_pos', '=', False)]}"/>
<field name="to_weight" attrs="{'invisible': [('available_in_pos', '=', False)]}"/>
</group>
</group>
</field>
</record>
<record id="product_template_tree_view" model="ir.ui.view">
<field name="name">product.template.tree.inherit</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_tree_view"/>
<field name="arch" type="xml">
<field name="name" position="before">
<field name="sequence" widget="handle" />
</field>
</field>
</record>
<!-- END -->
<record id="action_account_journal_form" model="ir.actions.act_window">
<field name="name">Payment Methods</field>
<field name="res_model">account.journal</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="context" eval="{'default_journal_user': 1, 'default_type': 'cash'}"/>
<field name="domain">[('journal_user','=', 1)]</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to add a payment method.
</p><p>
Payment methods are defined by accounting journals having the
field <i>PoS Payment Method</i> checked. In order to be useable
from the touchscreen interface, you must set the payment method
on the <i>Point of Sale</i> configuration.
</p>
</field>
</record>
<menuitem action="action_account_journal_form"
id="menu_action_account_journal_form_open"
parent="menu_point_config_product"
sequence="20"/>
<record model="ir.ui.view" id="view_pos_order_tree_all_sales_lines">
<field name="name">POS Sales Lines</field>
<field name="model">pos.order.line</field>
<field name="arch" type="xml">
<tree string="POS Orders lines">
<field name="order_id" />
<field name="create_date" />
<field name="product_id" />
<field name="qty" />
<field name="price_unit" widget="monetary"/>
</tree>
</field>
</record>
<record model="ir.actions.act_window" id="action_pos_all_sales_lines">
<field name="name">All sales lines</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">pos.order.line</field>
<field name="view_type">form</field>
<field name="view_id" ref="view_pos_order_tree_all_sales_lines" />
</record>
<!-- Miscellaneous Operations/Reporting -->
<menuitem name="Reports" parent="menu_point_root" id="menu_point_rep" sequence="99" groups="group_pos_manager"/>
<!-- Invoice -->
<record model="ir.actions.act_window" id="action_pos_invoice">
<field name="name">Invoices</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">account.invoice</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="domain">[('origin','like','POS')]</field>
</record>
<menuitem action="action_report_pos_details"
id="menu_pos_details" parent="menu_point_rep" sequence="6" />
<record model="ir.actions.act_url" id="action_pos_pos">
<field name="name">Start Point of Sale</field>
<field name="url">/pos/web/</field>
<field name="target">self</field>
</record>
<!-- Top menu item -->
<record model="ir.ui.view" id="view_pos_config_form">
<field name="name">pos.config.form.view</field>
<field name="model">pos.config</field>
<field name="arch" type="xml">
<form string="Point of Sale Configuration">
<header>
<button string="Set to Active" name="set_active" type="object" states="inactive,deprecated"/>
<button string="Set to Inactive" name="set_inactive" type="object" states="active" />
<button string="Set to Deprecated" name="set_deprecate" type="object" states="active,inactive" />
<field name="state" widget="statusbar" statusbar_visible="active,inactive,deprecated" statusbar_colors='{"deprecated" : "red"}' nolabel="1"/>
</header>
<sheet>
<label for="name" class="oe_edit_only"/>
<h1>
<field name="name"/>
</h1>
<group col="4">
<field name="picking_type_id" widget="selection" groups="stock.group_stock_multi_locations"
on_change="onchange_picking_type_id(picking_type_id)"/>
<field name="stock_location_id" groups="stock.group_stock_multi_locations"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="pricelist_id" groups="product.group_sale_pricelist"/>
<field name="journal_id" widget="selection"/>
<field name="group_by" groups="account.group_account_user"/>
<field name="barcode_nomenclature_id" />
<field name="sequence_id" readonly="1" groups="base.group_no_one"/>
<field name="fiscal_position_ids" widget="many2many_tags" options="{'no_create': True}"/>
<field name="currency_id" invisible="1"/>
</group>
<separator string="Available Payment Methods" colspan="4"/>
<field name="journal_ids" colspan="4" nolabel="1" context="{'default_journal_user': 1}">
<tree string="Journals">
<field name="code" />
<field name="name" />
<field name="type" />
</tree>
</field>
<group name="features" string="Features" >
<group>
<field name="iface_vkeyboard" />
<field name="iface_invoicing" />
<field name="iface_precompute_cash" />
<field name="iface_start_categ_id" />
<field name="tip_product_id" />
<field name="iface_tax_included" />
</group>
<group>
<field name="iface_big_scrollbars" />
<field name="iface_print_auto" />
<field name="iface_display_categ_images" />
<field name="iface_print_skip_screen" attrs="{'invisible' : [('iface_print_auto', '=', False)]}" />
<field name="cash_control" />
</group>
</group>
<group string="Hardware Proxy / PosBox" >
<field name="proxy_ip" />
<field name="iface_print_via_proxy" string="Receipt Printer" on_change="onchange_iface_print_via_proxy(iface_print_via_proxy)"/>
<field name="iface_scan_via_proxy" string="Barcode Scanner"/>
<field name="iface_electronic_scale" />
<field name="iface_cashdrawer" />
</group>
<group name="receipt" string="Receipt" >
<field name="receipt_header" string="Header" placeholder="A custom receipt header message"/>
<field name="receipt_footer" string="Footer" placeholder="A custom receipt footer message"/>
</group>
</sheet>
</form>
</field>
</record>
<record model="ir.ui.view" id="view_pos_config_tree">
<field name="name">pos.config.tree.view</field>
<field name="model">pos.config</field>
<field name="arch" type="xml">
<tree string="Point of Sale Configuration" decoration-muted="state == 'inactive'">
<field name="name" />
<field name="stock_location_id" groups="stock.group_stock_multi_locations"/>
<field name="state" />
</tree>
</field>
</record>
<record model="ir.ui.view" id="view_pos_config_search">
<field name="name">pos.config.search.view</field>
<field name="model">pos.config</field>
<field name="arch" type="xml">
<search string="Point of Sale Config">
<field name="name" />
<filter string="Active" domain="[('state', '=', 'active')]" />
<filter string="Inactive" domain="[('state', '=', 'inactive')]" />
<field name="stock_location_id" groups="stock.group_stock_multi_locations" />
</search>
</field>
</record>
<!-- Sessions -->
<act_window
id="act_pos_config_sessions"
name="Sessions"
src_model="pos.config"
res_model="pos.session"
domain="[('config_id', '=', active_id)]" />
<record model="ir.actions.act_window" id="action_pos_config_pos">
<field name="name">Point of Sale</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">pos.config</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="search_view_id" ref="view_pos_config_search" />
</record>
<menuitem
parent="menu_point_config_product"
action="action_pos_config_pos"
id="menu_pos_config_pos"
groups="group_pos_manager"/>
<act_window
id="act_pos_session_orders"
name="Orders"
src_model="pos.session"
res_model="pos.order"
context="{'search_default_session_id': active_id, 'default_session_id' : active_id }" />
<record model="ir.ui.view" id="view_pos_session_form">
<field name="name">pos.session.form.view</field>
<field name="model">pos.session</field>
<field name="arch" type="xml">
<form string="Point of Sale Session">
<header>
<button name="open" type="workflow" string="Open Session"
attrs="{'invisible' : ['|', ('cash_control', '=', False), ('state', '!=', 'opening_control')]}"
class="oe_highlight"/>
<button name="open_frontend_cb" type="object" string="Continue Selling" states="opened"
class="oe_highlight"/>
<button name="cashbox_control" type="workflow" string="End of Session"
attrs="{'invisible' : ['|', ('cash_control', '=', False),('state', '!=', 'opened')]}"
class="oe_highlight" />
<button name="close" type="workflow" string="Validate Closing &amp; Post Entries"
attrs="{'invisible' : ['|', ('cash_control', '=', True),('state', '!=', 'opened')]}"
class="oe_highlight" />
<button name="close" type="workflow" string="Validate Closing &amp; Post Entries" states="closing_control"
class="oe_highlight" />
<field name="state" widget="statusbar" statusbar_visible="opening_control,opened,closing_control,closed" nolabel="1" />
</header>
<sheet>
<field name="cash_register_id" invisible="1"/>
<div class="oe_button_box" name="button_box">
<button class="oe_stat_button" name="%(action_pos_box_in)d"
type="action" icon="fa-level-down"
attrs="{'invisible': ['|', ('cash_control', '=', False), ('state', 'not in', ['opened', 'closing_control'])]}">
<div class="o_form_field o_stat_info">
<span class="o_stat_text">Put</span>
<span class="o_stat_text">Money In</span>
</div>
</button>
<button class="oe_stat_button" name="%(action_pos_box_out)d"
type="action" icon="fa-level-up"
attrs="{'invisible': ['|', ('cash_control', '=', False), ('state', 'not in', ['opened', 'closing_control'])]}">
<div class="o_form_field o_stat_info">
<span class="o_stat_text">Take</span>
<span class="o_stat_text">Money Out</span>
</div>
</button>
<button name="%(account.action_view_account_bnk_stmt_cashbox)d"
class="oe_stat_button"
attrs="{'invisible':['|', ('cash_control', '=', False), ('state', '!=', 'opening_control')]}"
icon="fa-money"
type="action" context="{'balance': 'start', 'bank_statement_id': cash_register_id}">
<span class="o_stat_text">Set Opening Balance</span>
</button>
<button name="%(account.action_view_account_bnk_stmt_cashbox)d"
class="oe_stat_button"
attrs="{'invisible':['|', '|', ('cash_control', '=', False), ('state', '=', 'opening_control'), ('state', '=', 'closed')]}"
icon="fa-money"
type="action" context="{'balance': 'end', 'bank_statement_id': cash_register_id}">
<span class="o_stat_text">Set Closing Balance</span>
</button>
</div>
<h1 class="oe_title">
Session:
<field name="name" attrs="{'invisible': [('name','=','/')]}" class="oe_inline"/>
</h1>
<group>
<field name="cash_control" invisible="1" />
<group>
<field name="user_id"/>
<field name="currency_id" invisible="1"/>
<field name="config_id"/>
</group>
<group>
<field name="start_at" attrs="{'invisible' : [('state', '=', 'opening_control')]}"/>
<field name="stop_at" attrs="{'invisible' : [('state', '!=', 'closed')]}"/>
</group>
<newline/>
<div attrs="{'invisible' : [('cash_control', '=', False)]}">
<group class="oe_subtotal_footer oe_right">
<field name="cash_register_balance_start" readonly="1" string="Opening Balance" class="oe_subtotal_footer_separator" widget="monetary" options="{'currency_field': 'currency_id'}"/>
<label for="cash_register_total_entry_encoding" attrs="{'invisible' : [('state', '=', 'opening_control')]}" string="+ Transactions" class="oe_force_bold oe_opening_total"/>
<field name="cash_register_total_entry_encoding" nolabel="1" attrs="{'invisible' : [('state', '=', 'opening_control')]}" class="oe_bold oe_pos_total" widget="monetary" options="{'currency_field': 'currency_id'}"/>
<label for="cash_register_balance_end" attrs="{'invisible' : [('state', '=', 'opening_control')]}" string="= Theoretical Closing Balance" class="oe_force_bold oe_opening_total"/>
<field name="cash_register_balance_end" nolabel="1" attrs="{'invisible' : [('state', '=', 'opening_control')]}" class="oe_bold oe_pos_total" widget="monetary" options="{'currency_field': 'currency_id'}"/>
</group>
</div>
<div>
<group class="oe_subtotal_footer oe_right" attrs="{'invisible': ['|', ('cash_control', '=', False), ('state', '=', 'opening_control')]}">
<field name="cash_register_balance_end_real" string="Real Closing Balance" class="oe_subtotal_footer_separator" widget="monetary" options="{'currency_field': 'currency_id'}"/>
</group>
<group/>
<group/>
<group class="oe_subtotal_footer oe_right" attrs="{'invisible': ['|', ('cash_control', '=', False), ('state', '=', 'opening_control')]}">
<field name="cash_register_difference" class="oe_subtotal_footer_separator oe_right oe_pos_total oe_pos_difference" widget="monetary" options="{'currency_field': 'currency_id'}"/>
</group>
</div>
</group>
<separator string="Summary by Payment Methods" attrs="{'invisible' : [('state', '=', 'opening_control')]}"/>
<field name="statement_ids" attrs="{'invisible' : [('state', '=', 'opening_control')]}">
<tree string="Statements">
<field name="name" />
<field name="journal_id" />
<field name="balance_start" />
<field name="total_entry_encoding" />
<field name="balance_end_real" />
<field name="difference" />
<field name="currency_id" groups="base.group_multi_currency" />
<field name="state" />
</tree>
</field>
</sheet>
</form>
</field>
</record>
<record model="ir.ui.view" id="view_pos_session_tree">
<field name="name">pos.session.tree.view</field>
<field name="model">pos.session</field>
<field name="arch" type="xml">
<tree string="Point of Sale Session">
<field name="config_id" />
<field name="name" />
<field name="user_id" />
<field name="start_at" />
<field name="stop_at" />
<field name="state" />
</tree>
</field>
</record>
<record model="ir.ui.view" id="view_pos_session_search">
<field name="name">pos.session.search.view</field>
<field name="model">pos.session</field>
<field name="arch" type="xml">
<search string="Point of Sale Session">
<field name="name" />
<filter string="Open" domain="[('state', '=', 'opened')]" />
<separator/>
<filter string="Today" domain="[('start_at', '>=', datetime.datetime.combine(context_today(), datetime.time(0,0,0)))]" />
<field name="config_id" />
<field name="user_id" />
<group expand="0" string="Group By">
<filter string="User" domain="[]" context="{'group_by' : 'user_id'}" />
<filter string="Point of Sale" domain="[]" context="{'group_by': 'user_id'}" />
</group>
</search>
</field>
</record>
<record model="ir.actions.act_window" id="action_pos_session">
<field name="name">Sessions</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">pos.session</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="search_view_id" ref="view_pos_session_search" />
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to start a new session.
</p><p>
A session is a period of time, usually one day, during which
you sell through the point of sale. The user has to check the
currencies in your cash registers at the beginning and the end
of each session.
</p><p>
Note that you may use the menu <i>Your Session</i>
to quickly open a new session.
</p>
</field>
</record>
<menuitem
parent="menu_point_of_sale"
action="action_pos_session"
id="menu_pos_session_all"
sequence="2"
groups="group_pos_manager"/>
<record id="view_pos_order_filter" model="ir.ui.view">
<field name="name">pos.order.list.select</field>
<field name="model">pos.order</field>
<field name="arch" type="xml">
<search string="Search Sales Order">
<field name="name"/>
<field name="date_order"/>
<filter string="New" domain="[('state','=','draft')]"/>
<filter string="Done" domain="[('state','in',('paid','invoiced','done'))]"/>
<filter string="Invoiced" domain="[('state','=','invoiced')]"/>
<filter string="Posted" domain="[('state','=','done')]"/>
<field name="user_id"/>
<field name="session_id"/>
<group expand="0" string="Group By">
<filter string="Customer" domain="[]" context="{'group_by':'partner_id'}"/>
<filter string="Salesman" domain="[]" context="{'group_by':'user_id'}"/>
<filter string="Session" domain="[]" context="{'group_by':'session_id'}"/>
<filter string="Status" domain="[]" context="{'group_by':'state'}"/>
<filter string="Order Month" domain="[]" context="{'group_by':'date_order'}"/>
</group>
</search>
</field>
</record>
<record model="ir.actions.act_window" id="action_pos_config_kanban">
<field name="name">Point of Sale</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">pos.config</field>
<field name="view_type">form</field>
<field name="view_mode">kanban,tree,form</field>
<field name="search_view_id" ref="view_pos_config_search" />
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to create a new PoS config.
</p><p>
Configure at least one Point of Sale to be able to sell through the PoS interface.
</p>
</field>
</record>
<menuitem action="action_pos_config_kanban"
parent="menu_point_root" name="Dashboard"
id="menu_pos_dashboard" sequence="1"/>
<!-- Barcode Nomenclatures -->
<menuitem parent="menu_point_config_product" action="barcodes.action_barcode_nomenclature_form" id="menu_pos_barcode_nomenclature_all"
sequence="30"/>
</data>
</openerp>
@@ -0,0 +1,59 @@
<?xml version="1.0"?>
<openerp>
<data>
<record id="product_pos_category_form_view" model="ir.ui.view">
<field name="name">pos.category.form</field>
<field name="model">pos.category</field>
<field name="arch" type="xml">
<form string="Pos Categories">
<sheet>
<field name="image_medium" widget='image' class="oe_avatar oe_right"/>
<div class="oe_left">
<group>
<field name="name"/>
<field name="parent_id"/>
<field name="sequence"/>
</group>
</div>
</sheet>
</form>
</field>
</record>
<record id="product_pos_category_tree_view" model="ir.ui.view">
<field name="name">pos.category.tree</field>
<field name="model">pos.category</field>
<field name="field_parent" eval="False"/>
<field name="arch" type="xml">
<tree string="Product Product Categories">
<field name="sequence" widget="handle"/>
<field name="complete_name"/>
</tree>
</field>
</record>
<record id="product_pos_category_action" model="ir.actions.act_window">
<field name="name">Pos Product Categories</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">pos.category</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="view_id" eval="False"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to define a new category.
</p><p>
Categories are used to browse your products through the
touchscreen interface.
</p><p>
If you put a photo on the category and enable the option in the
point of sale configuration, the layout of the touchscreen interface
will automatically be displayed. We suggest not to put a photo
on categories for small (1024x768) screens.
</p>
</field>
</record>
<menuitem action="product_pos_category_action" id="menu_product_pos_category" parent="menu_point_config_product" sequence="20" />
</data>
</openerp>
@@ -0,0 +1,147 @@
<?xml version="1.0"?>
<openerp>
<data>
<record model="ir.ui.view" id="view_pos_config_form">
<field name="name">pos.config.form.view</field>
<field name="model">pos.config</field>
<field name="arch" type="xml">
<form string="Point of Sale Configuration">
<header>
<button string="Set to Active" name="set_active" type="object" states="inactive,deprecated"/>
<button string="Set to Inactive" name="set_inactive" type="object" states="active" />
<button string="Set to Deprecated" name="set_deprecate" type="object" states="active,inactive" />
<field name="state" widget="statusbar" statusbar_visible="active,inactive,deprecated" statusbar_colors='{"deprecated" : "red"}' nolabel="1"/>
</header>
<sheet>
<label for="name" class="oe_edit_only"/>
<h1>
<field name="name"/>
</h1>
<group col="4">
<field name="picking_type_id" widget="selection" groups="stock.group_stock_multi_locations"
on_change="onchange_picking_type_id(picking_type_id)"/>
<field name="stock_location_id" groups="stock.group_stock_multi_locations"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="pricelist_id" groups="product.group_sale_pricelist"/>
<field name="journal_id" widget="selection"/>
<field name="group_by" groups="account.group_account_user"/>
<field name="barcode_nomenclature_id" />
<field name="sequence_id" readonly="1" groups="base.group_no_one"/>
<field name="currency_id" invisible="1"/>
</group>
<separator string="Available Payment Methods" colspan="4"/>
<field name="journal_ids" colspan="4" nolabel="1" context="{'default_journal_user': 1}">
<tree string="Journals">
<field name="code" />
<field name="name" />
<field name="type" />
</tree>
</field>
<group name="features" string="Features" >
<group>
<field name="iface_vkeyboard" />
<field name="iface_invoicing" />
<field name="iface_precompute_cash" />
<field name="iface_start_categ_id" />
<field name="tip_product_id" />
<field name="iface_tax_included" />
</group>
<group>
<field name="iface_big_scrollbars" />
<field name="iface_print_auto" />
<field name="iface_display_categ_images" />
<field name="iface_print_skip_screen" attrs="{'invisible' : [('iface_print_auto', '=', False)]}" />
</group>
</group>
<group string="Hardware Proxy / PosBox" >
<field name="proxy_ip" />
<field name="iface_print_via_proxy" string="Receipt Printer"/>
<field name="iface_scan_via_proxy" string="Barcode Scanner"/>
<field name="iface_electronic_scale" />
<field name="iface_cashdrawer" />
</group>
<group name="receipt" string="Receipt" >
<field name="receipt_header" string="Header" placeholder="A custom receipt header message"/>
<field name="receipt_footer" string="Footer" placeholder="A custom receipt footer message"/>
</group>
</sheet>
</form>
</field>
</record>
<record model="ir.ui.view" id="view_pos_config_tree">
<field name="name">pos.config.tree.view</field>
<field name="model">pos.config</field>
<field name="arch" type="xml">
<tree string="Point of Sale Configuration" decoration-muted="state == 'inactive'">
<field name="name" />
<field name="stock_location_id" groups="stock.group_stock_multi_locations"/>
<field name="state" />
</tree>
</field>
</record>
<record model="ir.ui.view" id="view_pos_config_search">
<field name="name">pos.config.search.view</field>
<field name="model">pos.config</field>
<field name="arch" type="xml">
<search string="Point of Sale Config">
<field name="name" />
<filter string="Active" domain="[('state', '=', 'active')]" />
<filter string="Inactive" domain="[('state', '=', 'inactive')]" />
<field name="stock_location_id" groups="stock.group_stock_multi_locations" />
</search>
</field>
</record>
<act_window
id="act_pos_config_sessions"
name="Sessions"
src_model="pos.config"
res_model="pos.session"
domain="[('config_id', '=', active_id)]" />
<record model="ir.actions.act_window" id="action_pos_config_pos">
<field name="name">Point of Sale</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">pos.config</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="search_view_id" ref="view_pos_config_search" />
</record>
<menuitem
parent="menu_point_config_product"
action="action_pos_config_pos"
id="menu_pos_config_pos"
groups="group_pos_manager"/>
<record model="ir.actions.act_window" id="action_pos_config_kanban">
<field name="name">Point of Sale</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">pos.config</field>
<field name="view_type">form</field>
<field name="view_mode">kanban,tree,form</field>
<field name="search_view_id" ref="view_pos_config_search" />
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to create a new PoS config.
</p><p>
Configure at least one Point of Sale to be able to sell through the PoS interface.
</p>
</field>
</record>
<!-- Barcode Nomenclatures -->
<menuitem parent="menu_point_config_product" action="barcodes.action_barcode_nomenclature_form" id="menu_pos_barcode_nomenclature_all"
sequence="30"/>
<menuitem action="action_pos_config_kanban"
parent="menu_point_root" name="Dashboard"
id="menu_pos_dashboard" sequence="1"/>
</data>
</openerp>
@@ -0,0 +1,279 @@
<?xml version="1.0"?>
<openerp>
<data>
<record model="ir.ui.view" id="view_pos_pos_form">
<field name="name">pos.order</field>
<field name="model">pos.order</field>
<field name="arch" type="xml">
<form string="Point of Sale Orders">
<header>
<button name="%(action_pos_payment)d" string="Payment" class="oe_highlight" type="action" states="draft" context="{'pos_session_id' : session_id}"/>
<button name="action_invoice" string="Invoice" type="object" states="paid" attrs="{'readonly': [('partner_id','=',False)]}"/>
<button name="refund" string="Return Products" type="object"
attrs="{'invisible':[('state','=','draft')]}"/>
<field name="state" widget="statusbar" statusbar_visible="draft,paid,done" statusbar_colors='{"cancel":"red"}'/>
</header>
<sheet>
<group col="4" colspan="4" name="order_fields">
<field name="name"/>
<field name="date_order"/>
<field name="session_id" />
<field name="partner_id" on_change="onchange_partner_id(partner_id, context)" domain="[('customer', '=', True)]" context="{'search_default_customer':1}" attrs="{'readonly': [('state','=','invoiced')]}"/>
</group>
<notebook colspan="4">
<page string="Products">
<field name="lines" colspan="4" nolabel="1">
<tree string="Order lines" editable="bottom">
<field name="product_id" on_change="onchange_product_id(parent.pricelist_id,product_id,qty,parent.partner_id)"/>
<field name="qty" on_change="onchange_qty(parent.pricelist_id,product_id, discount, qty, price_unit, context)"/>
<field name="price_unit" on_change="onchange_qty(parent.pricelist_id,product_id, discount, qty, price_unit, context)" widget="monetary"/>
<field name="discount" on_change="onchange_qty(parent.pricelist_id,product_id, discount, qty, price_unit, context)" widget="monetary"/>
<field name="tax_ids" widget="many2many_tags"/>
<field name="price_subtotal" widget="monetary"/>
<field name="price_subtotal_incl" widget="monetary"/>
</tree>
<form string="Order lines">
<group col="4">
<field name="product_id" on_change="onchange_product_id(parent.pricelist_id,product_id,qty,parent.partner_id)"/>
<field name="qty" on_change="onchange_qty(parent.pricelist_id,product_id, discount, qty, price_unit, context)"/>
<field name="discount" on_change="onchange_qty(parent.pricelist_id,product_id, discount, qty, price_unit, context)" widget="monetary"/>
<field name="price_unit" on_change="onchange_qty(parent.pricelist_id,product_id, discount, qty, price_unit, context)" widget="monetary"/>
<field name="price_subtotal" invisible="1" widget="monetary"/>
<field name="price_subtotal_incl" invisible="1" widget="monetary"/>
<field name="notice"/>
</group>
</form>
</field>
<group class="oe_subtotal_footer oe_right" colspan="2" name="order_total">
<field name="amount_tax" widget="monetary"/>
<div class="oe_subtotal_footer_separator oe_inline">
<label for="amount_total" />
<button name="button_dummy"
states="draft" string="(update)" class="oe_edit_only oe_link"/>
</div>
<field name="amount_total" nolabel="1" class="oe_subtotal_footer_separator" widget="monetary"/>
</group>
<div class="oe_clear"/>
</page>
<page string="Payments">
<field name="statement_ids" colspan="4" nolabel="1">
<tree editable="bottom" string="Statement lines">
<field name="journal_id"/>
<field name="statement_id"/>
<field name="amount"/>
</tree>
</field>
</page>
<page name="extra" string="Extra Info">
<group string="General Information">
<field name="company_id" groups="base.group_multi_company"/>
<field name="location_id" widget="selection" groups="stock.group_stock_multi_locations"/>
<field name="user_id"/>
<field name="pricelist_id" groups="product.group_sale_pricelist" domain="[('type','=','sale')]"/>
<field name="picking_id" readonly="1"/>
<field name="pos_reference"/>
</group>
<group string="Accounting Information">
<group>
<field name="sale_journal" domain="[('type','=','sale')]"/>
<field name="invoice_id" readonly="1" attrs="{'invisible':[('state','&lt;&gt;','invoiced')]}"/>
<field name="account_move" readonly="1" attrs="{'invisible':[('state','&lt;&gt;','done')]}"/>
</group>
</group>
</page>
<page string="Notes" >
<field name="note"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record model="ir.actions.act_window" id="action_pos_pos_form">
<field name="name">Orders</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">pos.order</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="view_id" eval="False"/>
<field name="domain">[]</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to create a new order.
</p><p>
Use this menu to browse previous orders. To record new
orders, you may use the menu <i>Your Session</i> for
the touchscreen interface.
</p>
</field>
</record>
<record model="ir.ui.view" id="view_pos_order_tree">
<field name="name">Orders</field>
<field name="model">pos.order</field>
<field name="arch" type="xml">
<tree string="POS Orders" decoration-info="state == 'draft'" decoration-muted="state in ('done','cancel')">
<field name="name"/>
<field name="pos_reference"/>
<field name="partner_id"/>
<field name="date_order"/>
<field name="user_id"/>
<field name="invoice_id"/>
<field name="amount_total" sum="Amount total" widget="monetary"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="state"/>
<field name="session_id" />
</tree>
</field>
</record>
<record id="view_pos_order_filter" model="ir.ui.view">
<field name="name">pos.order.list.select</field>
<field name="model">pos.order</field>
<field name="arch" type="xml">
<search string="Search Sales Order">
<field name="name" string="Sales Order"/>
<field name="date_order"/>
<field name="pos_reference"/>
<filter string="New" domain="[('state','=','draft')]"/>
<filter string="Done" domain="[('state','in',('paid','invoiced','done'))]"/>
<filter string="Invoiced" domain="[('state','=','invoiced')]"/>
<filter string="Posted" domain="[('state','=','done')]"/>
<field name="user_id"/>
<group expand="0" string="Group By">
<filter string="Customer" domain="[]" context="{'group_by':'partner_id'}"/>
<filter string="Salesperson" domain="[]" context="{'group_by':'user_id'}"/>
<filter string="Status" domain="[]" context="{'group_by':'state'}"/>
<filter string="Order Month" domain="[]" context="{'group_by':'date_order'}"/>
</group>
</search>
</field>
</record>
<record model="ir.ui.view" id="view_pos_order_search">
<field name="name">pos.order.search.view</field>
<field name="model">pos.order</field>
<field name="arch" type="xml">
<search string="Point of Sale Orders">
<field name="name"/>
<field name="config_id"/>
</search>
</field>
</record>
<menuitem parent="menu_point_of_sale" id="menu_point_ofsale" action="action_pos_pos_form" sequence="1" groups="group_pos_manager,group_pos_user"/>
<record model="ir.ui.view" id="view_pos_order_line">
<field name="name">Sale lines</field>
<field name="model">pos.order.line</field>
<field name="arch" type="xml">
<tree string="POS Order lines">
<field name="product_id" readonly="1"/>
<field name="qty" readonly="1" sum="Total qty"/>
<field name="discount" readonly="1" widget="monetary"/>
<field name="price_unit" readonly="1" widget="monetary"/>
<field name="price_subtotal" readonly="1" sum="Sum of subtotals" widget="monetary"/>
<field name="price_subtotal_incl" readonly="1" sum="Sum of subtotals" widget="monetary"/>
<field name="create_date" readonly="1"/>
</tree>
</field>
</record>
<record model="ir.ui.view" id="view_pos_order_line_form">
<field name="name">Sale line</field>
<field name="model">pos.order.line</field>
<field name="arch" type="xml">
<form string="POS Order line">
<group col="4">
<field name="product_id" />
<field name="qty" />
<field name="discount" widget="monetary"/>
<field name="price_unit" widget="monetary"/>
<field name="create_date" />
</group>
</form>
</field>
</record>
<record model="ir.actions.act_window" id="action_pos_order_line">
<field name="name">Sale line</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">pos.order.line</field>
<field name="view_type">form</field>
<field name="view_mode">tree</field>
<field name="view_id" ref="view_pos_order_line"/>
</record>
<record model="ir.actions.act_window" id="action_pos_order_line_form">
<field name="name">Sale line</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">pos.order.line</field>
<field name="view_type">form</field>
<field name="view_mode">form,tree</field>
<field name="view_id" ref="view_pos_order_line_form"/>
</record>
<record model="ir.actions.act_window" id="action_pos_order_line_day">
<field name="name">Sale line</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">pos.order.line</field>
<field name="view_type">form</field>
<field name="view_mode">tree</field>
<field name="view_id" ref="view_pos_order_line"/>
<field name="domain">[('create_date', '&gt;=', time.strftime('%Y-%m-%d 00:00:00')),('create_date', '&lt;=', time.strftime('%Y-%m-%d 23:59:59'))]</field>
</record>
<record model="ir.ui.view" id="view_pos_order_tree_all_sales_lines">
<field name="name">POS Sales Lines</field>
<field name="model">pos.order.line</field>
<field name="arch" type="xml">
<tree string="POS Orders lines">
<field name="order_id" />
<field name="create_date" />
<field name="product_id" />
<field name="qty" />
<field name="price_unit" widget="monetary"/>
</tree>
</field>
</record>
<record model="ir.actions.act_window" id="action_pos_all_sales_lines">
<field name="name">All sales lines</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">pos.order.line</field>
<field name="view_type">form</field>
<field name="view_id" ref="view_pos_order_tree_all_sales_lines" />
</record>
<menuitem action="action_report_pos_details"
id="menu_pos_details" parent="menu_point_rep" sequence="6" />
<record id="view_pos_order_filter" model="ir.ui.view">
<field name="name">pos.order.list.select</field>
<field name="model">pos.order</field>
<field name="arch" type="xml">
<search string="Search Sales Order">
<field name="name"/>
<field name="date_order"/>
<filter string="New" domain="[('state','=','draft')]"/>
<filter string="Done" domain="[('state','in',('paid','invoiced','done'))]"/>
<filter string="Invoiced" domain="[('state','=','invoiced')]"/>
<filter string="Posted" domain="[('state','=','done')]"/>
<field name="user_id"/>
<field name="session_id"/>
<group expand="0" string="Group By">
<filter string="Customer" domain="[]" context="{'group_by':'partner_id'}"/>
<filter string="Salesman" domain="[]" context="{'group_by':'user_id'}"/>
<filter string="Session" domain="[]" context="{'group_by':'session_id'}"/>
<filter string="Status" domain="[]" context="{'group_by':'state'}"/>
<filter string="Order Month" domain="[]" context="{'group_by':'date_order'}"/>
</group>
</search>
</field>
</record>
<record model="ir.actions.act_url" id="action_pos_pos">
<field name="name">Start Point of Sale</field>
<field name="url">/pos/web/</field>
<field name="target">self</field>
</record>
</data>
</openerp>
@@ -0,0 +1,179 @@
<?xml version="1.0"?>
<openerp>
<data>
<act_window
id="act_pos_session_orders"
name="Orders"
src_model="pos.session"
res_model="pos.order"
context="{'search_default_session_id': active_id, 'default_session_id' : active_id }" />
<record model="ir.ui.view" id="view_pos_session_form">
<field name="name">pos.session.form.view</field>
<field name="model">pos.session</field>
<field name="arch" type="xml">
<form string="Point of Sale Session">
<header>
<button name="open_cb" type="object" string="Validate &amp; Open Session" states="opening_control" class="oe_highlight"/>
<button name="open_frontend_cb" type="object" string="Continue Selling" states="opened"
class="oe_highlight"/>
<button name="cashbox_control" type="workflow" string="End of Session"
attrs="{'invisible' : ['|', ('cash_control', '=', False),('state', '!=', 'opened')]}"
class="oe_highlight" />
<button name="close" type="workflow" string="Validate Closing &amp; Post Entries"
attrs="{'invisible' : ['|', ('cash_control', '=', True),('state', '!=', 'opened')]}"
class="oe_highlight" />
<button name="close" type="workflow" string="Validate Closing &amp; Post Entries" states="closing_control"
class="oe_highlight" />
<field name="state" widget="statusbar" statusbar_visible="opening_control,opened,closing_control,closed" nolabel="1" />
</header>
<sheet>
<div class="oe_button_box" name="button_box">
<button class="oe_stat_button" name="%(action_pos_box_in)d"
type="action" states="opened,closing_control" icon="fa-level-down">
<div class="o_form_field o_stat_info">
<span class="o_stat_text">Put</span>
<span class="o_stat_text">Money In</span>
</div>
</button>
<button class="oe_stat_button" name="%(action_pos_box_out)d"
type="action" states="opened,closing_control" icon="fa-level-up">
<div class="o_form_field o_stat_info">
<span class="o_stat_text">Take</span>
<span class="o_stat_text">Money Out</span>
</div>
</button>
</div>
<h1 class="oe_title">
Session:
<field name="name" attrs="{'invisible': [('name','=','/')]}" class="oe_inline"/>
</h1>
<group>
<field name="cash_control" invisible="1" />
<group>
<field name="user_id"/>
<field name="currency_id" invisible="1"/>
<field name="config_id"/>
</group>
<group>
<field name="start_at" attrs="{'invisible' : [('state', '=', 'opening_control')]}"/>
<field name="stop_at" attrs="{'invisible' : [('state', '!=', 'closed')]}"/>
</group>
<newline/>
<div attrs="{'invisible' : [('cash_control', '=', False)]}">
<group class="oe_subtotal_footer oe_right">
<field name="cash_register_balance_start" readonly="1" string="Opening Balance" class="oe_subtotal_footer_separator" widget="monetary" options="{'currency_field': 'currency_id'}"/>
<label for="cash_register_total_entry_encoding" attrs="{'invisible' : [('state', '=', 'opening_control')]}" string="+ Transactions" class="oe_force_bold oe_opening_total"/>
<field name="cash_register_total_entry_encoding" nolabel="1" attrs="{'invisible' : [('state', '=', 'opening_control')]}" class="oe_bold oe_pos_total" widget="monetary" options="{'currency_field': 'currency_id'}"/>
<label for="cash_register_balance_end" attrs="{'invisible' : [('state', '=', 'opening_control')]}" string="= Theoretical Closing Balance" class="oe_force_bold oe_opening_total"/>
<field name="cash_register_balance_end" nolabel="1" attrs="{'invisible' : [('state', '=', 'opening_control')]}" class="oe_bold oe_pos_total" widget="monetary" options="{'currency_field': 'currency_id'}"/>
</group>
<div class="oe_clear"/>
<div attrs="{'invisible' : ['|', ('cash_journal_id', '=', False), ('state', '!=', 'opening_control')]}" class="oe_view_nocontent" groups="point_of_sale.group_pos_manager">
<p class="oe_view_nocontent_create">
You can define another list of available currencies on the
<i>Cash Registers</i> tab of the <b><field name="cash_journal_id" class="oe_inline"/></b>
payment method.
</p>
</div>
</div>
<div>
<group class="oe_subtotal_footer oe_right" attrs="{'invisible': ['|', ('cash_control', '=', False), ('state', '=', 'opening_control')]}">
<field name="cash_register_balance_end_real" string="Real Closing Balance" class="oe_subtotal_footer_separator" widget="monetary" options="{'currency_field': 'currency_id'}"/>
</group>
<group/>
<group/>
<group class="oe_subtotal_footer oe_right" attrs="{'invisible': ['|', ('cash_control', '=', False), ('state', '=', 'opening_control')]}">
<field name="cash_register_difference" class="oe_subtotal_footer_separator oe_right oe_pos_total oe_pos_difference" widget="monetary" options="{'currency_field': 'currency_id'}"/>
</group>
</div>
</group>
<separator string="Summary by Payment Methods" attrs="{'invisible' : [('state', '=', 'opening_control')]}"/>
<field name="statement_ids" attrs="{'invisible' : [('state', '=', 'opening_control')]}">
<tree string="Statements">
<field name="name" />
<field name="journal_id" />
<field name="balance_start" />
<field name="total_entry_encoding" />
<field name="balance_end_real" />
<field name="difference" />
<field name="currency_id" groups="base.group_multi_currency" />
<field name="state" />
</tree>
</field>
</sheet>
</form>
</field>
</record>
<record model="ir.ui.view" id="view_pos_session_tree">
<field name="name">pos.session.tree.view</field>
<field name="model">pos.session</field>
<field name="arch" type="xml">
<tree string="Point of Sale Session">
<field name="config_id" />
<field name="name" />
<field name="user_id" />
<field name="start_at" />
<field name="stop_at" />
<field name="state" />
</tree>
</field>
</record>
<record model="ir.ui.view" id="view_pos_session_search">
<field name="name">pos.session.search.view</field>
<field name="model">pos.session</field>
<field name="arch" type="xml">
<search string="Point of Sale Session">
<field name="name" />
<filter string="Open" domain="[('state', '=', 'opened')]" />
<separator/>
<filter string="Today" domain="[('start_at', '>=', datetime.datetime.combine(context_today(), datetime.time(0,0,0)))]" />
<field name="config_id" />
<field name="user_id" />
<group expand="0" string="Group By">
<filter string="User" domain="[]" context="{'group_by' : 'user_id'}" />
<filter string="Point of Sale" domain="[]" context="{'group_by': 'user_id'}" />
</group>
</search>
</field>
</record>
<record model="ir.actions.act_window" id="action_pos_session">
<field name="name">Sessions</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">pos.session</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="search_view_id" ref="view_pos_session_search" />
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to start a new session.
</p><p>
A session is a period of time, usually one day, during which
you sell through the point of sale. The user has to check the
currencies in your cash registers at the beginning and the end
of each session.
</p><p>
Note that you may use the menu <i>Your Session</i>
to quickly open a new session.
</p>
</field>
</record>
<menuitem
parent="menu_point_of_sale"
action="action_pos_session"
id="menu_pos_session_all"
sequence="2"
groups="group_pos_manager"/>
</data>
</openerp>
@@ -0,0 +1,59 @@
<?xml version="1.0"?>
<openerp>
<data>
<record id="product_template_action" model="ir.actions.act_window">
<field name="name">Products</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">product.template</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,kanban</field>
<field name="context" eval="{}"/>
<field name="domain" eval="[('available_in_pos','&lt;&gt;',False)]"/>
<field name="search_view_id" eval="False"/> <!-- Force empty -->
<field name="view_id" eval="False"/> <!-- Force empty -->
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to add a new product.
</p><p>
You must define a product for everything you sell through
the point of sale interface.
</p><p>
Do not forget to set the price and the point of sale category
in which it should appear. If a product has no point of sale
category, you can not sell it through the point of sale
interface.
</p>
</field>
</record>
<menuitem
action="product_template_action"
id="menu_pos_products"
parent="menu_point_of_sale" sequence="3"/>
<record id="product_template_form_view" model="ir.ui.view">
<field name="name">product.template.form.inherit</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_form_view"/>
<field name="arch" type="xml">
<group name="sale_condition" position="after">
<group name="pos" string="Point of Sale">
<field name="available_in_pos"/>
<field name="pos_categ_id" attrs="{'invisible': [('available_in_pos', '=', False)]}"/>
<field name="to_weight" attrs="{'invisible': [('available_in_pos', '=', False)]}"/>
</group>
</group>
</field>
</record>
<record id="product_template_tree_view" model="ir.ui.view">
<field name="name">product.template.tree.inherit</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_tree_view"/>
<field name="arch" type="xml">
<field name="name" position="before">
<field name="sequence" widget="handle" />
</field>
</field>
</record>
</data>
</openerp>
+1 -18
View File
@@ -7,27 +7,10 @@ from openerp.osv import osv, fields
from openerp.tools.translate import _
class account_journal(osv.osv):
_inherit = 'account.journal'
def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
if not context:
context = {}
session_id = context.get('pos_session_id', False) or False
if session_id:
session = self.pool.get('pos.session').browse(cr, uid, session_id, context=context)
if session:
journal_ids = [journal.id for journal in session.config_id.journal_ids]
args += [('id', 'in', journal_ids)]
return super(account_journal, self).search(cr, uid, args, offset=offset, limit=limit, order=order, context=context, count=count)
class pos_make_payment(osv.osv_memory):
_name = 'pos.make.payment'
_description = 'Point of Sale Payment'
def check(self, cr, uid, ids, context=None):
"""Check the order:
if the order is not paid: continue payment,