[MERGE] migration of point_of_sale to the new API

This commit is contained in:
Martin Trigaux
2016-02-17 17:31:38 +01:00
82 changed files with 2713 additions and 4503 deletions
+1 -5
View File
@@ -1,11 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import account_bank_statement
import models
import controllers
import point_of_sale
import report
import res_users
import res_partner
import wizard
import res_config
+22 -32
View File
@@ -30,53 +30,43 @@ 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/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',
'views/report_detailsofsales.xml',
'views/report_payment.xml',
'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',
],
'test': [
'../account/test/account_minimal_test.xml',
'test/tests_before.xml',
'test/00_register_open.yml',
'test/01_order_to_payment.yml',
'test/02_order_to_invoice.yml',
'test/point_of_sale_report.yml',
'test/account_statement_reports.yml',
'data/point_of_sale_demo.xml',
],
'installable': True,
'application': True,
'qweb': ['static/src/xml/pos.xml'],
'website': 'https://www.odoo.com/page/point-of-sale',
'auto_install': False,
}
@@ -1,22 +0,0 @@
# -*- 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."),
}
class account_bank_statement(osv.osv):
_inherit = 'account.bank.statement'
_columns = {
'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),
}
+6 -11
View File
@@ -2,8 +2,8 @@
import logging
import werkzeug.utils
from openerp import http
from openerp.http import request
from odoo import http
from odoo.http import request
_logger = logging.getLogger(__name__)
@@ -11,15 +11,10 @@ _logger = logging.getLogger(__name__)
class PosController(http.Controller):
@http.route('/pos/web', type='http', auth='user')
def a(self, debug=False, **k):
cr, uid, context, session = request.cr, request.uid, request.context, request.session
def pos_web(self, debug=False, **k):
# if user not logged in, log him in
PosSession = request.registry['pos.session']
pos_session_ids = PosSession.search(cr, uid, [('state','=','opened'),('user_id','=',session.uid)], context=context)
if not pos_session_ids:
pos_sessions = request.env['pos.session'].search([('state', '=', 'opened'), ('user_id', '=', request.session.uid)])
if not pos_sessions:
return werkzeug.utils.redirect('/web#action=point_of_sale.action_client_pos_menu')
PosSession.login(cr, uid, pos_session_ids, context=context)
pos_sessions.login()
return request.render('point_of_sale.index')
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<odoo>
<record id="barcode_rule_cashier" model="barcode.rule">
<field name="name">Cashier Barcodes</field>
<field name="barcode_nomenclature_id" ref="barcodes.default_barcode_nomenclature"/>
@@ -36,5 +35,4 @@
<field name="encoding">ean13</field>
<field name="pattern">23.....{NNNDD}</field>
</record>
</data>
</openerp>
</odoo>
@@ -1,5 +1,5 @@
<?xml version="1.0" ?>
<openerp>
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="0">
<record id="seq_picking_type_posout" model="ir.sequence">
<field name="name">Picking POS</field>
@@ -52,5 +52,5 @@
<field name="image" type="base64" file="point_of_sale/static/img/product_product_49-image.jpg"/>
</record>
</data>
</openerp>
</odoo>
@@ -1,6 +1,5 @@
<?xml version="1.0"?>
<openerp>
<data>
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Partners with Barcodes -->
<record id='base.res_partner_1' model='res.partner'> <field name='barcode'>0420100000005</field> </record>
<record id='base.res_partner_2' model='res.partner'> <field name='barcode'>0420200000004</field> </record>
@@ -572,7 +571,4 @@
<record id="product.membership_1" model="product.product"> <field name="available_in_pos" eval="False"/> </record>
<record id="product.membership_2" model="product.product"> <field name="available_in_pos" eval="False"/> </record>
</data>
</openerp>
</odoo>
@@ -1,6 +1,5 @@
<?xml version="1.0"?>
<openerp>
<data>
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record model="workflow" id="wkf_pos">
<field name="name">Pos workflow</field>
<field name="osv">pos.order</field>
@@ -74,10 +73,6 @@
<field name="signal">cancel</field>
</record>
</data>
<data>
<record model="workflow" id="wkf_pos_session">
<field name="name">POS Session Workflow</field>
<field name="osv">pos.session</field>
@@ -168,8 +163,4 @@
<field name="act_to" ref="act_close" />
<field name="condition">cash_control == False</field>
</record>
</data>
</openerp>
</odoo>
+14
View File
@@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import account_bank_statement
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
@@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
# Copyright (C) 2004-2008 PC Solutions (<http://pcsol.be>). All Rights Reserved
from odoo import fields, models
class AccountBankStatement(models.Model):
_inherit = 'account.bank.statement'
pos_session_id = fields.Many2one('pos.session', string="Session", copy=False)
account_id = fields.Many2one('account.account', related='journal_id.default_debit_account_id', readonly=True)
class AccountBankStatementLine(models.Model):
_inherit = 'account.bank.statement.line'
pos_statement_id = fields.Many2one('pos.order', string="POS statement", ondelete='cascade')
@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
# Copyright (C) 2004-2008 PC Solutions (<http://pcsol.be>). All Rights Reserved
from odoo import fields, models, api
class AccountJournal(models.Model):
_inherit = 'account.journal'
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.")
@api.model
def search(self, args, offset=0, limit=None, order=None, count=False):
session_id = self.env.context.get('pos_session_id', False)
if session_id:
session = self.env['pos.session'].browse(session_id)
if session:
args += [('id', 'in', session.config_id.journal_ids.ids)]
return super(AccountJournal, self).search(args=args, offset=offset, limit=limit, order=order, count=count)
@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import sets
from odoo import models
from odoo.tools.translate import _
class BarcodeRule(models.Model):
_inherit = 'barcode.rule'
def _get_type_selection(self):
types = sets.Set(super(BarcodeRule, 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,59 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, tools, _
class PosCategory(models.Model):
_name = "pos.category"
_description = "Public Category"
_order = "sequence, name"
@api.constrains('parent_id')
def _check_category_recursion(self):
if not self._check_recursion():
raise ValueError(_('Error ! You cannot create recursive categories.'))
name = fields.Char(required=True, translate=True)
parent_id = fields.Many2one('pos.category', string='Parent Category', index=True)
child_id = fields.One2many('pos.category', 'parent_id', string='Children Categories')
sequence = fields.Integer(help="Gives the sequence order when displaying a list of product categories.")
# 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.
image = fields.Binary(attachment=True,
help="This field holds the image used as image for the cateogry, limited to 1024x1024px.")
image_medium = fields.Binary(string="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 = fields.Binary(string="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.")
@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)
@api.multi
def name_get(self):
def get_names(cat):
res = []
while cat:
res.append(cat.name)
cat = cat.parent_id
return res
return [(cat.id, " / ".join(reversed(get_names(cat)))) for cat in self]
+250
View File
@@ -0,0 +1,250 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import uuid
from odoo import api, fields, models, _
from odoo.exceptions import UserError
class PosConfig(models.Model):
_name = 'pos.config'
POS_CONFIG_STATE = [
('active', 'Active'),
('inactive', 'Inactive'),
('deprecated', 'Deprecated')
]
def _default_sale_journal(self):
return self.env['account.journal'].search([('type', '=', 'sale'), ('company_id', '=', self.env.user.company_id.id)], limit=1)
def _default_pricelist(self):
return self.env['product.pricelist'].search([], limit=1)
def _get_default_location(self):
return self.env['stock.warehouse'].search([('company_id', '=', self.env.user.company_id.id)], limit=1).lot_stock_id
def _get_default_nomenclature(self):
return self.env['barcode.nomenclature'].search([], limit=1)
def _get_group_pos_manager(self):
return self.env.ref('point_of_sale.group_pos_manager')
def _get_group_pos_user(self):
return self.env.ref('point_of_sale.group_pos_user')
name = fields.Char(string='Point of Sale Name', index=True, 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', string='Available Payment Methods',
domain="[('journal_user', '=', True ), ('type', 'in', ['bank', 'cash'])]",)
picking_type_id = fields.Many2one('stock.picking.type', string='Picking Type')
stock_location_id = fields.Many2one(
'stock.location', string='Stock Location',
domain=[('usage', '=', 'internal')], required=True, default=_get_default_location)
journal_id = fields.Many2one(
'account.journal', string='Sale Journal',
domain=[('type', '=', 'sale')],
help="Accounting journal used to post sales entries.",
default=_default_sale_journal)
currency_id = fields.Many2one('res.currency', compute='_compute_currency', string="Currency")
iface_cashdrawer = fields.Boolean(string='Cashdrawer', help="Automatically open the cashdrawer")
iface_payment_terminal = fields.Boolean(string='Payment Terminal', help="Enables Payment Terminal integration")
iface_electronic_scale = fields.Boolean(string='Electronic Scale', help="Enables Electronic Scale integration")
iface_vkeyboard = fields.Boolean(string='Virtual KeyBoard', help="Enables an integrated Virtual Keyboard")
iface_print_via_proxy = fields.Boolean(string='Print via Proxy', help="Bypass browser printing and prints via the hardware proxy")
iface_scan_via_proxy = fields.Boolean(string='Scan via Proxy', help="Enable barcode scanning with a remotely connected barcode scanner")
iface_invoicing = fields.Boolean(string='Invoicing', help='Enables invoice generation from the Point of Sale', default=True)
iface_big_scrollbars = fields.Boolean('Large Scrollbars', help='For imprecise industrial touchscreens')
iface_print_auto = fields.Boolean(string='Automatic Receipt Printing', default=False,
help='The receipt will automatically be p-rinted at the end of each order')
iface_print_skip_screen = fields.Boolean(string='Skip Receipt Screen', default=True,
help='The receipt screen will be skipped if the receipt can be printed automatically.')
iface_precompute_cash = fields.Boolean(string='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(string='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', string='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(string='Display Category Pictures',
help="The product categories will be displayed with pictures.")
cash_control = fields.Boolean(string='Cash Control', help="Check the amount of the cashbox at opening and closing.")
receipt_header = fields.Text(string='Receipt Header', help="A short text that will be inserted as a header in the printed receipt")
receipt_footer = fields.Text(string='Receipt Footer', help="A short text that will be inserted as a footer in the printed receipt")
proxy_ip = fields.Char(string='IP Address', size=45,
help='The hostname or ip address of the hardware proxy, Will be autodetected if left empty')
state = fields.Selection(POS_CONFIG_STATE, string='Status', required=True, readonly=True, copy=False, default=POS_CONFIG_STATE[0][0])
uuid = fields.Char(readonly=True, default=lambda self: str(uuid.uuid4()),
help='A globally unique identifier for this pos configuration, used to prevent conflicts in client-generated data')
sequence_id = fields.Many2one('ir.sequence', string='Order IDs Sequence', readonly=True,
help="This sequence is automatically created by Odoo but you can change it "
"to customize the reference numbers of your orders.", copy=False)
session_ids = fields.One2many('pos.session', 'config_id', string='Sessions')
current_session_id = fields.Many2one('pos.session', compute='_compute_current_session', string="Current Session")
current_session_state = fields.Char(compute='_compute_current_session')
last_session_closing_cash = fields.Float(compute='_compute_last_session')
last_session_closing_date = fields.Date(compute='_compute_last_session')
pos_session_username = fields.Char(compute='_compute_current_session_user')
group_by = fields.Boolean(string='Group Journal Items', default=True,
help="Check this if you want to group the Journal Items by Product while closing a Session")
pricelist_id = fields.Many2one('product.pricelist', string='Pricelist', required=True, default=_default_pricelist)
company_id = fields.Many2one('res.company', string='Company', required=True, default=lambda self: self.env.user.company_id)
barcode_nomenclature_id = fields.Many2one('barcode.nomenclature', string='Barcodes', required=True, default=_get_default_nomenclature,
help='Defines what kind of barcodes are available and how they are assigned to products, customers and cashiers')
group_pos_manager_id = fields.Many2one('res.groups', string='Point of Sale Manager Group', default=_get_group_pos_manager,
help='This field is there to pass the id of the pos manager group to the point of sale client')
group_pos_user_id = fields.Many2one('res.groups', string='Point of Sale User Group', default=_get_group_pos_user,
help='This field is there to pass the id of the pos user group to the point of sale client')
tip_product_id = fields.Many2one('product.product', string='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')
@api.depends('journal_id.currency_id', 'journal_id.company_id.currency_id')
def _compute_currency(self):
for pos_config in self:
if pos_config.journal_id:
pos_config.currency_id = pos_config.journal_id.currency_id.id or pos_config.journal_id.company_id.currency_id.id
else:
pos_config.currency_id = self.env.user.company_id.currency_id.id
@api.depends('session_ids')
def _compute_current_session(self):
for pos_config in self:
session = pos_config.session_ids.filtered(lambda r: r.user_id.id == self.env.uid and not r.state == 'closed')
pos_config.current_session_id = session
pos_config.current_session_state = session.state
@api.depends('session_ids')
def _compute_last_session(self):
PosSession = self.env['pos.session']
for pos_config in self:
session = PosSession.search_read(
[('config_id', '=', pos_config.id), ('state', '=', 'closed')],
['cash_register_balance_end_real', 'stop_at'],
order="stop_at desc", limit=1)
if session:
pos_config.last_session_closing_cash = session[0]['cash_register_balance_end_real']
pos_config.last_session_closing_date = session[0]['stop_at']
else:
pos_config.last_session_closing_cash = 0
pos_config.last_session_closing_date = False
@api.depends('session_ids')
def _compute_current_session_user(self):
for pos_config in self:
pos_config.pos_session_username = pos_config.session_ids.filtered(lambda s: s.state == 'opened').user_id.name
@api.constrains('company_id', 'stock_location_id')
def _check_company_location(self):
if self.stock_location_id.company_id and self.stock_location_id.company_id.id != self.company_id.id:
raise UserError(_("The company of the stock location is different than the one of point of sale"))
@api.constrains('company_id', 'journal_id')
def _check_company_journal(self):
if self.journal_id and self.journal_id.company_id.id != self.company_id.id:
raise UserError(_("The company of the sale journal is different than the one of point of sale"))
@api.constrains('company_id', 'journal_ids')
def _check_company_payment(self):
if self.env['account.journal'].search_count([('id', 'in', self.journal_ids.ids), ('company_id', '!=', self.company_id.id)]):
raise UserError(_("The company of a payment method is different than the one of point of sale"))
@api.onchange('iface_print_via_proxy')
def _onchange_iface_print_via_proxy(self):
self.iface_print_auto = self.iface_print_via_proxy
@api.onchange('picking_type_id')
def _onchange_picking_type_id(self):
if self.picking_type_id.default_location_src_id.usage == 'internal' and self.picking_type_id.default_location_dest_id.usage == 'customer':
self.stock_location_id = self.picking_type_id.default_location_src_id.id
@api.multi
def name_get(self):
result = []
for config in self:
if (not config.session_ids) or (config.session_ids[0].state == 'closed'):
result.append((config.id, config.name + ' (' + _('not used') + ')'))
continue
result.append((config.id, config.name + ' (' + config.session_ids[0].user_id.name + ')'))
return result
@api.model
def create(self, values):
IrSequence = self.env['ir.sequence']
val = {
'name': _('POS Order %s') % values['name'],
'padding': 4,
'prefix': "%s/" % values['name'],
'code': "pos.order",
'company_id': values.get('company_id', False),
}
# force sequence_id field to new pos.order sequence
values['sequence_id'] = IrSequence.create(val).id
# TODO master: add field sequence_line_id on model
# this make sure we always have one available per company
val.update(name=_('POS order line %s') % values['name'], code='pos.order.line')
IrSequence.create(val)
return super(PosConfig, self).create(values)
@api.multi
def unlink(self):
for pos_config in self.filtered(lambda pos_config: pos_config.sequence_id):
pos_config.sequence_id.unlink()
return super(PosConfig, self).unlink()
@api.multi
def set_active(self):
self.write({'state': 'active'})
@api.multi
def set_inactive(self):
self.write({'state': 'inactive'})
@api.multi
def set_deprecate(self):
self.write({'state': 'deprecated'})
# Methods to open the POS
@api.multi
def open_ui(self):
assert len(self.ids) == 1, "you can open only one session at a time"
return {
'type': 'ir.actions.act_url',
'url': '/pos/web/',
'target': 'self',
}
@api.multi
def open_existing_session_cb_close(self):
assert len(self.ids) == 1, "you can open only one session at a time"
self.current_session_id.signal_workflow('cashbox_control')
return self.open_session_cb()
@api.multi
def open_session_cb(self):
assert len(self.ids) == 1, "you can open only one session at a time"
if not self.current_session_id:
self.current_session_id = self.env['pos.session'].create({
'user_id': self.env.uid,
'config_id': self.id
})
if self.current_session_id.state == 'opened':
return self.open_ui()
return self._open_session(self.current_session_id.id)
return self._open_session(self.current_session_id.id)
@api.multi
def open_existing_session_cb(self):
assert len(self.ids) == 1, "you can open only one session at a time"
return self._open_session(self.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',
}
+696
View File
@@ -0,0 +1,696 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
from functools import partial
from odoo import api, fields, models, tools, _
from odoo.tools import float_is_zero
from odoo.exceptions import UserError
import odoo.addons.decimal_precision as dp
_logger = logging.getLogger(__name__)
class PosOrder(models.Model):
_name = "pos.order"
_description = "Point of Sale"
_order = "id desc"
def _amount_line_tax(self, cr, uid, line, fiscal_position_id, context=None):
taxes = line.tax_ids.filtered(lambda t: t.company_id.id == line.order_id.company_id.id)
if fiscal_position_id:
taxes = fiscal_position_id.map_tax(taxes)
price = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
taxes = taxes.compute_all(price, line.order_id.pricelist_id.currency_id, line.qty, product=line.product_id, partner=line.order_id.partner_id or False)['taxes']
return sum(tax.get('amount', 0.0) for tax in taxes)
@api.model
def _order_fields(self, ui_order):
process_line = partial(self.env['pos.order.line']._order_line_fields)
return {
'name': ui_order['name'],
'user_id': ui_order['user_id'] or False,
'session_id': ui_order['pos_session_id'],
'lines': [process_line(l) for l in ui_order['lines']] if ui_order['lines'] else False,
'pos_reference': ui_order['name'],
'partner_id': ui_order['partner_id'] or False,
'date_order': ui_order['creation_date'],
'fiscal_position_id': ui_order['fiscal_position_id']
}
def _payment_fields(self, ui_paymentline):
return {
'amount': ui_paymentline['amount'] or 0.0,
'payment_date': ui_paymentline['name'],
'statement_id': ui_paymentline['statement_id'],
'payment_name': ui_paymentline.get('note', False),
'journal': ui_paymentline['journal_id'],
}
# This deals with orders that belong to a closed session. In order
# to recover from this we:
# - assign the order to another compatible open session
# - if that doesn't exist, create a new one
def _get_valid_session(self, order):
PosSession = self.env['pos.session']
closed_session = PosSession.browse(order['pos_session_id'])
open_session = PosSession.search(
[('state', '=', 'opened'),
('config_id', '=', closed_session.config_id.id),
('user_id', '=', closed_session.user_id.id)],
limit=1, order="start_at DESC")
if open_session:
return open_session
else:
new_session = PosSession.create({'config_id': closed_session.config_id.id})
# bypass opening_control (necessary when using cash control)
new_session.signal_workflow('open')
return new_session
@api.model
def _process_order(self, pos_order):
pos_session = self.env['pos.session'].browse(pos_order['pos_session_id'])
if pos_session.state == 'closing_control' or pos_session.state == 'closed':
pos_order['pos_session_id'] = self._get_valid_session(pos_order).id
order = self.create(self._order_fields(pos_order))
journal_ids = set()
for payments in pos_order['statement_ids']:
order.add_payment(self._payment_fields(payments[2]))
journal_ids.add(payments[2]['journal_id'])
if pos_session.sequence_number <= pos_order['sequence_number']:
pos_session.write({'sequence_number': pos_order['sequence_number'] + 1})
pos_session.refresh()
if not float_is_zero(pos_order['amount_return'], self.env['decimal.precision'].precision_get('Account')):
cash_journal_id = pos_session.cash_journal_id.id
if not cash_journal_id:
# Select for change one of the cash journals used in this
# payment
cash_journal = self.env['account.journal'].search([
('type', '=', 'cash'),
('id', 'in', list(journal_ids)),
], limit=1)
if not cash_journal:
# If none, select for change one of the cash journals of the POS
# This is used for example when a customer pays by credit card
# an amount higher than total amount of the order and gets cash back
cash_journal = [statement.journal_id for statement in pos_session.statement_ids if statement.journal_id.type == 'cash']
if not cash_journal:
raise UserError(_("No cash statement found for this session. Unable to record returned cash."))
cash_journal_id = cash_journal[0].id
order.add_payment({
'amount': -pos_order['amount_return'],
'payment_date': fields.Datetime.now(),
'payment_name': _('return'),
'journal': cash_journal_id,
})
return order
def _prepare_analytic_account(self, line):
'''This method is designed to be inherited in a custom module'''
return False
def _create_account_move(self, dt, ref, journal_id, company_id):
date_tz_user = fields.Datetime.context_timestamp(self, fields.Datetime.from_string(dt))
date_tz_user = fields.Date.to_string(date_tz_user)
return self.env['account.move'].sudo().create({'ref': ref, 'journal_id': journal_id, 'date': date_tz_user})
def _prepare_invoice(self):
"""
Prepare the dict of values to create the new invoice for a pos order.
"""
return {
'name': self.name,
'origin': self.name,
'account_id': self.partner_id.property_account_receivable_id.id,
'journal_id': self.sale_journal.id,
'type': 'out_invoice',
'reference': self.name,
'partner_id': self.partner_id.id,
'comment': self.note or '',
# considering partner's sale pricelist's currency
'currency_id': self.pricelist_id.currency_id.id,
'user_id': self.env.uid,
}
def _action_create_invoice_line(self, line=False, invoice_id=False):
InvoiceLine = self.env['account.invoice.line']
inv_name = line.product_id.name_get()[0][1]
inv_line = {
'invoice_id': invoice_id,
'product_id': line.product_id.id,
'quantity': line.qty,
'account_analytic_id': self._prepare_analytic_account(line),
'name': inv_name,
}
# Oldlin trick
invoice_line = InvoiceLine.sudo().new(inv_line)
invoice_line._onchange_product_id()
invoice_line.invoice_line_tax_ids = invoice_line.invoice_line_tax_ids.filtered(lambda t: t.company_id.id == line.order_id.company_id.id).ids
fiscal_position_id = line.order_id.fiscal_position_id
if fiscal_position_id:
invoice_line.invoice_line_tax_ids = fiscal_position_id.map_tax(invoice_line.invoice_line_tax_ids)
invoice_line.invoice_line_tax_ids = invoice_line.invoice_line_tax_ids.ids
# We convert a new id object back to a dictionary to write to
# bridge between old and new api
inv_line = invoice_line._convert_to_write(invoice_line._cache)
inv_line.update(price_unit=line.price_unit, discount=line.discount)
return InvoiceLine.sudo().create(inv_line)
def _create_account_move_line(self, session=None, move=None):
# Tricky, via the workflow, we only have one id in the ids variable
"""Create a account move line of order grouped by products or not."""
IrProperty = self.env['ir.property']
ResPartner = self.env['res.partner']
if session and not all(session.id == order.session_id.id for order in self):
raise UserError(_('Selected orders do not have the same session!'))
grouped_data = {}
have_to_group_by = session and session.config_id.group_by or False
for order in self.filtered(lambda o: not o.account_move or order.state == 'paid'):
current_company = order.sale_journal.company_id
account_def = IrProperty.get(
'property_account_receivable_id', 'res.partner')
order_account = order.partner_id.property_account_receivable_id.id or account_def and account_def.id
partner_id = ResPartner._find_accounting_partner(order.partner_id).id or False
if move is None:
# Create an entry for the sale
move = self._create_account_move(
order.session_id.start_at, order.name, order.sale_journal.id, order.company_id.id)
def insert_data(data_type, values):
# if have_to_group_by:
values.update({
'ref': order.name,
'partner_id': partner_id,
'journal_id': order.sale_journal.id,
'date': fields.Date.context_today(self),
'move_id': move.id,
'company_id': current_company.id,
})
if data_type == 'product':
key = ('product', values['partner_id'], (values['product_id'], values['name']), values['analytic_account_id'], values['debit'] > 0)
elif data_type == 'tax':
key = ('tax', values['partner_id'], values['tax_line_id'], values['debit'] > 0)
elif data_type == 'counter_part':
key = ('counter_part', values['partner_id'], values['account_id'], values['debit'] > 0)
else:
return
grouped_data.setdefault(key, [])
if have_to_group_by:
if not grouped_data[key]:
grouped_data[key].append(values)
else:
for line in grouped_data[key]:
if line.get('tax_code_id') == values.get('tax_code_id'):
current_value = line
current_value['quantity'] = current_value.get(
'quantity', 0.0) + values.get('quantity', 0.0)
current_value['credit'] = current_value.get(
'credit', 0.0) + values.get('credit', 0.0)
current_value['debit'] = current_value.get(
'debit', 0.0) + values.get('debit', 0.0)
break
else:
grouped_data[key].append(values)
else:
grouped_data[key].append(values)
# because of the weird way the pos order is written, we need to make sure there is at least one line,
# because just after the 'for' loop there are references to 'line' and 'income_account' variables (that
# are set inside the for loop)
# TOFIX: a deep refactoring of this method (and class!) is needed
# in order to get rid of this stupid hack
assert order.lines, _('The POS order must have lines when calling this method')
# Create an move for each order line
cur = order.pricelist_id.currency_id
for line in order.lines:
amount = line.price_subtotal
# Search for the income account
if line.product_id.property_account_income_id.id:
income_account = line.product_id.property_account_income_id.id
elif line.product_id.categ_id.property_account_income_categ_id.id:
income_account = line.product_id.categ_id.property_account_income_categ_id.id
else:
raise UserError(_('Please define income '
'account for this product: "%s" (id:%d).')
% (line.product_id.name, line.product_id.id))
name = line.product_id.name
if line.notice:
# add discount reason in move
name = name + ' (' + line.notice + ')'
# Create a move for the line for the order line
insert_data('product', {
'name': name,
'quantity': line.qty,
'product_id': line.product_id.id,
'account_id': income_account,
'analytic_account_id': self._prepare_analytic_account(line),
'credit': ((amount > 0) and amount) or 0.0,
'debit': ((amount < 0) and -amount) or 0.0,
'partner_id': partner_id
})
# Create the tax lines
taxes = line.tax_ids_after_fiscal_position.filtered(lambda t: t.company_id.id == current_company.id)
if not taxes:
continue
for tax in taxes.compute_all(line.price_unit * (100.0 - line.discount) / 100.0, cur, line.qty)['taxes']:
insert_data('tax', {
'name': _('Tax') + ' ' + tax['name'],
'product_id': line.product_id.id,
'quantity': line.qty,
'account_id': tax['account_id'] or income_account,
'credit': ((tax['amount'] > 0) and tax['amount']) or 0.0,
'debit': ((tax['amount'] < 0) and -tax['amount']) or 0.0,
'tax_line_id': tax['id'],
'partner_id': partner_id
})
# counterpart
insert_data('counter_part', {
'name': _("Trade Receivables"), # order.name,
'account_id': order_account,
'credit': ((order.amount_total < 0) and -order.amount_total) or 0.0,
'debit': ((order.amount_total > 0) and order.amount_total) or 0.0,
'partner_id': partner_id
})
order.write({'state': 'done', 'account_move': move.id})
all_lines = []
for group_key, group_data in grouped_data.iteritems():
for value in group_data:
all_lines.append((0, 0, value),)
if move: # In case no order was changed
move.sudo().write({'line_ids': all_lines})
move.sudo().post()
return True
def _default_session(self):
return self.env['pos.session'].search([('state', '=', 'opened'), ('user_id', '=', self.env.uid)], limit=1)
def _default_pricelist(self):
return self._default_session().config_id.pricelist_id
name = fields.Char(string='Order Ref', required=True, readonly=True, copy=False, default='/')
company_id = fields.Many2one('res.company', string='Company', required=True, readonly=True, default=lambda self: self.env.user.company_id)
date_order = fields.Datetime(string='Order Date', readonly=True, index=True, default=fields.Datetime.now)
user_id = fields.Many2one('res.users', string='Salesman', help="Person who uses the cash register. It can be a reliever, a student or an interim employee.", default=lambda self: self.env.uid)
amount_tax = fields.Float(compute='_compute_amount_all', string='Taxes', digits=0)
amount_total = fields.Float(compute='_compute_amount_all', string='Total', digits=0)
amount_paid = fields.Float(compute='_compute_amount_all', string='Paid', states={'draft': [('readonly', False)]}, readonly=True, digits=0)
amount_return = fields.Float(compute='_compute_amount_all', string='Returned', digits=0)
lines = fields.One2many('pos.order.line', 'order_id', string='Order Lines', states={'draft': [('readonly', False)]}, readonly=True, copy=True)
statement_ids = fields.One2many('account.bank.statement.line', 'pos_statement_id', string='Payments', states={'draft': [('readonly', False)]}, readonly=True)
pricelist_id = fields.Many2one('product.pricelist', string='Pricelist', required=True, states={
'draft': [('readonly', False)]}, readonly=True, default=_default_pricelist)
partner_id = fields.Many2one('res.partner', string='Customer', change_default=True, index=True, states={'draft': [('readonly', False)], 'paid': [('readonly', False)]})
sequence_number = fields.Integer(string='Sequence Number', help='A session-unique sequence number for the order', default=1)
session_id = fields.Many2one(
'pos.session', string='Session', required=True, index=True,
domain="[('state', '=', 'opened')]", states={'draft': [('readonly', False)]},
readonly=True, default=_default_session)
config_id = fields.Many2one('pos.config', related='session_id.config_id', string="Point of Sale")
state = fields.Selection(
[('draft', 'New'), ('cancel', 'Cancelled'), ('paid', 'Paid'), ('done', 'Posted'), ('invoiced', 'Invoiced')],
'Status', readonly=True, copy=False, default='draft')
invoice_id = fields.Many2one('account.invoice', string='Invoice', copy=False)
account_move = fields.Many2one('account.move', string='Journal Entry', readonly=True, copy=False)
picking_id = fields.Many2one('stock.picking', string='Picking', readonly=True, copy=False)
picking_type_id = fields.Many2one('stock.picking.type', related='session_id.config_id.picking_type_id', string="Picking Type")
location_id = fields.Many2one('stock.location', related='session_id.config_id.stock_location_id', string="Location", store=True)
note = fields.Text(string='Internal Notes')
nb_print = fields.Integer(string='Number of Print', readonly=True, copy=False, default=0)
pos_reference = fields.Char(string='Receipt Ref', readonly=True, copy=False)
sale_journal = fields.Many2one('account.journal', related='session_id.config_id.journal_id', string='Sale Journal', store=True, readonly=True)
fiscal_position_id = fields.Many2one('account.fiscal.position', string='Fiscal Position')
@api.depends('statement_ids', 'lines.price_subtotal_incl', 'lines.discount')
def _compute_amount_all(self):
for order in self:
order.amount_paid = order.amount_return = order.amount_tax = 0.0
currency = order.pricelist_id.currency_id
order.amount_paid = sum(payment.amount for payment in order.statement_ids)
order.amount_return = sum(payment.amount < 0 and payment.amount or 0 for payment in order.statement_ids)
order.amount_tax = currency.round(sum(self._amount_line_tax(line, order.fiscal_position_id) for line in order.lines))
amount_untaxed = currency.round(sum(line.price_subtotal for line in order.lines))
order.amount_total = order.amount_tax + amount_untaxed
@api.onchange('partner_id')
def _onchange_partner_id(self):
if self.partner_id:
self.pricelist = self.partner_id.property_product_pricelist.id
@api.multi
def write(self, vals):
res = super(PosOrder, self).write(vals)
Partner = self.env['res.partner']
# If you change the partner of the PoS order, change also the partner of the associated bank statement lines
if 'partner_id' in vals:
for order in self:
partner_id = False
if order.invoice_id:
raise UserError(_("You cannot change the partner of a POS order for which an invoice has already been issued."))
if vals['partner_id']:
partner = Partner.browse(vals['partner_id'])
partner_id = Partner._find_accounting_partner(partner).id
order.statement_ids.write({'partner_id': partner_id})
return res
@api.multi
def unlink(self):
for pos_order in self.filtered(lambda pos_order: pos_order.state not in ['draft', 'cancel']):
raise UserError(_('In order to delete a sale, it must be new or cancelled.'))
return super(PosOrder, self).unlink()
@api.model
def create(self, values):
if values.get('session_id'):
# set name based on the sequence specified on the config
session = self.env['pos.session'].browse(values['session_id'])
values['name'] = session.config_id.sequence_id._next()
values.setdefault('session_id', session.config_id.pricelist_id.id)
else:
# fallback on any pos.order sequence
values['name'] = self.env['ir.sequence'].next_by_code('pos.order')
return super(PosOrder, self).create(values)
@api.multi
def action_invoice_state(self):
self.write({'state': 'invoiced'})
@api.multi
def action_invoice(self):
Invoice = self.env['account.invoice']
for order in self:
# Force company for all SUPERUSER_ID action
local_context = dict(self.env.context, force_company=order.company_id.id, company_id=order.company_id.id)
if order.invoice_id:
Invoice += order.invoice_id
continue
if not order.partner_id:
raise UserError(_('Please provide a partner for the sale.'))
invoice = Invoice.new(order._prepare_invoice())
invoice._onchange_partner_id()
inv = invoice._convert_to_write(invoice._cache)
new_invoice = Invoice.with_context(local_context).sudo().create(inv)
order.write({'invoice_id': new_invoice.id, 'state': 'invoiced'})
Invoice += new_invoice
for line in order.lines:
self.with_context(local_context)._action_create_invoice_line(line, new_invoice.id)
new_invoice.with_context(local_context).sudo().compute_taxes()
order.sudo().signal_workflow('invoice')
new_invoice.sudo().signal_workflow('validate')
if not Invoice:
return {}
return {
'name': _('Customer Invoice'),
'view_type': 'form',
'view_mode': 'form',
'view_id': self.env.ref('account.invoice_form').id,
'res_model': 'account.invoice',
'context': "{'type':'out_invoice'}",
'type': 'ir.actions.act_window',
'nodestroy': True,
'target': 'current',
'res_id': Invoice and Invoice.ids[0] or False,
}
@api.multi
def action_paid(self):
self.write({'state': 'paid'})
self.create_picking()
@api.multi
def action_cancel(self):
self.write({'state': 'cancel'})
@api.multi
def action_done(self):
self._create_account_move_line()
@api.model
def create_from_ui(self, orders):
# Keep only new orders
submitted_references = [o['data']['name'] for o in orders]
pos_order = self.search([('pos_reference', 'in', submitted_references)])
existing_orders = pos_order.read(['pos_reference'])
existing_references = set([o['pos_reference'] for o in existing_orders])
orders_to_save = [o for o in orders if o['data']['name'] not in existing_references]
order_ids = []
for tmp_order in orders_to_save:
to_invoice = tmp_order['to_invoice']
pos_order = self._process_order(tmp_order['data'])
order_ids.append(pos_order.id)
try:
pos_order.signal_workflow('paid')
except Exception as e:
_logger.error('Could not fully process the POS Order: %s', tools.ustr(e))
if to_invoice:
pos_order.action_invoice()
pos_order.invoice_id.sudo().signal_workflow('invoice_open')
return order_ids
def test_paid(self):
"""A Point of Sale is paid when the sum
@return: True
"""
for order in self:
if order.lines and not order.amount_total:
return True
if (not order.lines) or (not order.statement_ids) or (abs(order.amount_total - order.amount_paid) > 0.00001):
return False
return True
def create_picking(self):
"""Create a picking for each order and validate it."""
Picking = self.env['stock.picking']
Move = self.env['stock.move']
StockWarehouse = self.env['stock.warehouse']
for order in self:
address = order.partner_id.address_get(['delivery']) or {}
picking_type = order.picking_type_id
picking_id = False
location_id = order.location_id.id
if order.partner_id:
destination_id = order.partner_id.property_stock_customer.id
else:
if (not picking_type) or (not picking_type.default_location_dest_id):
customerloc, supplierloc = StockWarehouse._get_partner_locations([])
destination_id = customerloc.id
else:
destination_id = picking_type.default_location_dest_id.id
if picking_type:
pos_qty = all([x.qty >= 0 for x in order.lines])
picking_id = Picking.create({
'origin': order.name,
'partner_id': address.get('delivery', False),
'date_done': order.date_order,
'picking_type_id': picking_type.id,
'company_id': order.company_id.id,
'move_type': 'direct',
'note': order.note or "",
'location_id': location_id if pos_qty else destination_id,
'location_dest_id': destination_id if pos_qty else location_id,
})
order.write({'picking_id': picking_id.id})
for line in order.lines.filtered(lambda l: l.product_id.type in ['product', 'consu']):
Move += Move.create({
'name': line.name,
'product_uom': line.product_id.uom_id.id,
'picking_id': picking_id.id,
'picking_type_id': picking_type.id,
'product_id': line.product_id.id,
'product_uom_qty': abs(line.qty),
'state': 'draft',
'location_id': location_id if line.qty >= 0 else destination_id,
'location_dest_id': destination_id if line.qty >= 0 else location_id,
})
if picking_id:
picking_id.action_confirm()
picking_id.force_assign()
# Mark pack operations as done
for pack in picking_id.pack_operation_ids:
pack.write({'qty_done': pack.product_qty})
picking_id.action_done()
elif Move:
Move.action_confirm()
Move.force_assign()
Move.action_done()
return True
def add_payment(self, data):
"""Create a new payment for the order"""
args = {
'amount': data['amount'],
'date': data.get('payment_date', fields.Date.today()),
'name': self.name + ': ' + (data.get('payment_name', '') or ''),
'partner_id': self.env["res.partner"]._find_accounting_partner(self.partner_id).id or False,
}
journal_id = data.get('journal', False)
statement_id = data.get('statement_id', False)
assert journal_id or statement_id, "No statement_id or journal_id passed to the method!"
journal = self.env['account.journal'].browse(journal_id)
# use the company of the journal and not of the current user
company_cxt = dict(self.env.context, force_company=journal.company_id.id)
account_def = self.env['ir.property'].with_context(company_cxt).get('property_account_receivable_id', 'res.partner')
args['account_id'] = (self.partner_id.property_account_receivable_id.id) or (account_def and account_def.id) or False
if not args['account_id']:
if not args['partner_id']:
msg = _('There is no receivable account defined to make payment.')
else:
msg = _('There is no receivable account defined to make payment for the partner: "%s" (id:%d).') % (
self.partner_id.name, self.partner_id.id,)
raise UserError(msg)
context = dict(self.env.context)
context.pop('pos_session_id', False)
for statement in self.session_id.statement_ids:
if statement.id == statement_id:
journal_id = statement.journal_id.id
break
elif statement.journal_id.id == journal_id:
statement_id = statement.id
break
if not statement_id:
raise UserError(_('You have to open at least one cashbox.'))
args.update({
'statement_id': statement_id,
'pos_statement_id': self.id,
'journal_id': journal_id,
'ref': self.session_id.name,
})
self.env['account.bank.statement.line'].with_context(context).create(args)
return statement_id
@api.multi
def refund(self):
"""Create a copy of order for refund order"""
PosOrder = self.env['pos.order']
current_session = self.env['pos.session'].search([('state', '!=', 'closed'), ('user_id', '=', self.env.uid)], limit=1)
if not current_session:
raise UserError(_('To return product(s), you need to open a session that will be used to register the refund.'))
for order in self:
clone = order.copy({
# ot used, name forced by create
'name': order.name + _(' REFUND'),
'session_id': current_session.id,
'date_order': fields.Datetime.now(),
})
PosOrder += clone
for clone in PosOrder:
for order_line in clone.lines:
order_line.write({'qty': -order_line.qty})
return {
'name': _('Return Products'),
'view_type': 'form',
'view_mode': 'form',
'res_model': 'pos.order',
'res_id': PosOrder.ids[0],
'view_id': False,
'context': self.env.context,
'type': 'ir.actions.act_window',
'target': 'current',
}
class PosOrderLine(models.Model):
_name = "pos.order.line"
_description = "Lines of Point of Sale"
_rec_name = "product_id"
def _order_line_fields(self, line):
if line and 'tax_ids' not in line[2]:
product = self.env['product.product'].browse(line[2]['product_id'])
line[2]['tax_ids'] = [(6, 0, [x.id for x in product.taxes_id])]
return line
company_id = fields.Many2one('res.company', string='Company', required=True, default=lambda self: self.env.user.company_id)
name = fields.Char(string='Line No', required=True, copy=False, default=lambda self: self.env['ir.sequence'].next_by_code('pos.order.line'))
notice = fields.Char(string='Discount Notice')
product_id = fields.Many2one('product.product', string='Product', domain=[('sale_ok', '=', True)], required=True, change_default=True)
price_unit = fields.Float(string='Unit Price', digits=0)
qty = fields.Float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), default=1)
price_subtotal = fields.Float(compute='_compute_amount_line_all', digits=0, string='Subtotal w/o Tax')
price_subtotal_incl = fields.Float(compute='_compute_amount_line_all', digits=0, string='Subtotal')
discount = fields.Float(string='Discount (%)', digits=0, default=0.0)
order_id = fields.Many2one('pos.order', string='Order Ref', ondelete='cascade')
create_date = fields.Datetime(string='Creation Date', readonly=True)
tax_ids = fields.Many2many('account.tax', string='Taxes', readonly=True)
tax_ids_after_fiscal_position = fields.Many2many('account.tax', compute='_get_tax_ids_after_fiscal_position', string='Taxes')
@api.depends('price_unit', 'tax_ids', 'qty', 'discount', 'product_id')
def _compute_amount_line_all(self):
for line in self:
currency = line.order_id.pricelist_id.currency_id
taxes = line.tax_ids.filtered(lambda tax: tax.company_id.id == line.order_id.company_id.id)
fiscal_position_id = line.order_id.fiscal_position_id
if fiscal_position_id:
taxes = fiscal_position_id.map_tax(taxes)
price = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
line.price_subtotal = line.price_subtotal_incl = price * line.qty
if taxes:
taxes = taxes.compute_all(price, currency, line.qty, product=line.product_id, partner=line.order_id.partner_id or False)
line.price_subtotal = taxes['total_excluded']
line.price_subtotal_incl = taxes['total_included']
@api.onchange('product_id')
def _onchange_product_id(self):
if self.product_id:
if not self.order_id.pricelist_id:
raise UserError(
_('You have to select a pricelist in the sale form !\n'
'Please set one before choosing a product.'))
price = self.order_id.pricelist_id.price_get(
self.product_id.id, self.qty or 1.0, self.order_id.partner_id.id)[self.order_id.pricelist_id.id]
self.onchange_qty()
self.price_unit = price
self.tax_ids = self.product_id.taxes_id
@api.onchange('qty', 'discount', 'price_unit', 'tax_ids')
def _onchange_qty(self):
if self.product_id:
if not self.order_id.pricelist_id:
raise UserError(_('You have to select a pricelist in the sale form !'))
price = self.price_unit * (1 - (self.discount or 0.0) / 100.0)
self.price_subtotal = self.price_subtotal_incl = price * self.qty
if (self.product_id.taxes_id):
taxes = self.product_id.taxes_id.compute_all(price, self.order_id.pricelist_id.currency_id, self.qty, product=self.product_id, partner=False)
self.price_subtotal = taxes['total_excluded']
self.price_subtotal_incl = taxes['total_included']
@api.multi
def _get_tax_ids_after_fiscal_position(self):
for line in self:
line.tax_ids_after_fiscal_position = line.order_id.fiscal_position_id.map_tax(line.tax_ids)
+230
View File
@@ -0,0 +1,230 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
from odoo.exceptions import UserError
class PosSession(models.Model):
_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 _confirm_orders(self):
for session in self:
company_id = session.config_id.journal_id.company_id.id
orders = session.order_ids.filtered(lambda order: order.state == 'paid')
move = self.env['pos.order'].with_context(force_company=company_id)._create_account_move(session.start_at, session.name, session.config_id.journal_id.id, company_id)
orders.with_context(force_company=company_id)._create_account_move_line(session, move)
for order in session.order_ids.filtered(lambda o: o.state != 'done'):
if order.state not in ('paid', 'invoiced'):
raise UserError(_("You cannot confirm all orders of this session, because they have not the 'paid' status"))
order.signal_workflow('done')
config_id = fields.Many2one(
'pos.config', string='Point of Sale',
help="The physical point of sale you will use.",
required=True,
index=True,
domain="[('state', '=', 'active')]")
name = fields.Char(string='Session ID', required=True, readonly=True, default='/')
user_id = fields.Many2one(
'res.users', string='Responsible',
required=True,
index=True,
readonly=True,
states={'opening_control': [('readonly', False)]},
default=lambda self: self.env.uid)
currency_id = fields.Many2one('res.currency', related='config_id.currency_id', string="Currency")
start_at = fields.Datetime(string='Opening Date', readonly=True)
stop_at = fields.Datetime(string='Closing Date', readonly=True, copy=False)
state = fields.Selection(
POS_SESSION_STATE, string='Status',
required=True, readonly=True,
index=True, copy=False, default='opening_control')
sequence_number = fields.Integer(string='Order Sequence Number', help='A sequence number that is incremented with each order', default=1)
login_number = fields.Integer(string='Login Sequence Number', help='A sequence number that is incremented each time a user resumes the pos session', default=0)
cash_control = fields.Boolean(compute='_compute_cash_all', string='Has Cash Control')
cash_journal_id = fields.Many2one('account.journal', compute='_compute_cash_all', string='Cash Journal', store=True)
cash_register_id = fields.Many2one('account.bank.statement', compute='_compute_cash_all', string='Cash Register', store=True)
cash_register_balance_end_real = fields.Monetary(
related='cash_register_id.balance_end_real',
string="Ending Balance",
help="Total of closing cash control lines.",
readonly=True)
cash_register_balance_start = fields.Monetary(
related='cash_register_id.balance_start',
string="Starting Balance",
help="Total of opening cash control lines.",
readonly=True)
cash_register_total_entry_encoding = fields.Monetary(
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.Monetary(
related='cash_register_id.balance_end',
digits=0,
string="Theoretical Closing Balance",
help="Sum of opening balance and transactions.",
readonly=True)
cash_register_difference = fields.Monetary(
related='cash_register_id.difference',
string='Difference',
help="Difference between the theoretical closing balance and the real closing balance.",
readonly=True)
journal_ids = fields.Many2many(
'account.journal',
related='config_id.journal_ids',
readonly=True,
string='Available Payment Methods')
order_ids = fields.One2many('pos.order', 'session_id', string='Orders')
statement_ids = fields.One2many('account.bank.statement', 'pos_session_id', string='Bank Statement', readonly=True)
_sql_constraints = [('uniq_name', 'unique(name)', _("The name of this POS Session must be unique !"))]
@api.depends('cash_control', 'cash_journal_id', 'config_id.cash_control')
def _compute_cash_all(self):
for session in self:
session.cash_journal_id = session.cash_register_id = session.cash_control = False
if session.config_id.cash_control:
for statement in session.statement_ids:
if statement.journal_id.type == 'cash':
session.cash_control = True
session.cash_journal_id = statement.journal_id.id
session.cash_register_id = statement.id
if not session.cash_control:
raise UserError(_("Cash control can only be applied to cash journals."))
@api.constrains('user_id', 'state')
def _check_unicity(self):
# open if there is no session in 'opening_control', 'opened', 'closing_control' for one user
if self.search_count([('state', 'not in', ('closed', 'closing_control')), ('user_id', '=', self.user_id.id)]) > 1:
raise UserError(_("You cannot create two active sessions with the same responsible!"))
@api.constrains('config_id')
def _check_pos_config(self):
if self.search_count([('state', '!=', 'closed'), ('config_id', '=', self.config_id.id)]) > 1:
raise UserError(_("You cannot create two active sessions related to the same point of sale!"))
@api.model
def create(self, values):
config_id = values.get('config_id') or self.env.context.get('default_config_id')
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.
pos_config = self.env['pos.config'].browse(config_id)
ctx = dict(self.env.context, company_id=pos_config.company_id.id)
if not pos_config.journal_id:
jid = pos_config.with_context(ctx).default_get(['journal_id'])['journal_id']
if not jid:
raise UserError(_("Unable to open the session. You have to assign a sale journal to your point of sale."))
pos_config.with_context(ctx).sudo().write({'journal_id': jid})
# define some cash journal if no payment method exists
if not pos_config.journal_ids:
Journal = self.env['account.journal']
journals = Journal.with_context(ctx).search([('journal_user', '=', True), ('type', '=', 'cash')])
if not journals:
journals = Journal.with_context(ctx).search([('type', '=', 'cash')])
if not journals:
journals = Journal.with_context(ctx).search([('journal_user', '=', True)])
journals.sudo().write({'journal_user': True})
pos_config.sudo().write({'journal_ids': [(6, 0, journals.ids)]})
statements = [(0, 0, {
'journal_id': journal.id,
'user_id': self.env.uid,
'company_id': pos_config.company_id.id
}) for journal in pos_config.journal_ids]
values.update({
'name': self.env['ir.sequence'].with_context(ctx).next_by_code('pos.session'),
'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.filtered(lambda j: j.type == 'cash'):
ctx.update({'journal_id': journal.id})
return super(PosSession, self.with_context(ctx)).create(values)
@api.multi
def unlink(self):
for session in self.filtered(lambda s: s.statement_ids):
session.statement_ids.unlink()
return super(PosSession, self).unlink()
@api.multi
def login(self):
self.ensure_one()
self.write({
'login_number': self.login_number + 1,
})
def wkf_action_open(self):
# second browse because we need to refetch the data from the DB for cash_register_id
for session in self:
values = {}
if not session.start_at:
values['start_at'] = fields.Datetime.now()
values['state'] = 'opened'
session.write(values)
session.statement_ids.button_open()
return True
def wkf_action_opening_control(self):
return self.write({'state': 'opening_control'})
def wkf_action_closing_control(self):
for session in self:
for statement in session.statement_ids:
if (statement != session.cash_register_id) and (statement.balance_end != statement.balance_end_real):
statement.write({'balance_end_real': statement.balance_end})
session.write({'state': 'closing_control', 'stop_at': fields.Datetime.now()})
def wkf_action_close(self):
# Close CashBox
for session in self:
company_id = session.config_id.company_id.id
ctx = dict(self.env.context, force_company=company_id, company_id=company_id)
for st in session.statement_ids:
if abs(st.difference) > st.journal_id.amount_authorized_diff:
# The pos manager can close statements with maximums.
if not self.env['ir.model.access'].check_groups("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 "))
st.with_context(ctx).sudo().button_confirm_bank()
self.with_context(ctx)._confirm_orders()
self.write({'state': 'closed'})
return {
'type': 'ir.actions.client',
'name': 'Point of Sale Menu',
'tag': 'reload',
'params': {'menu_id': self.env.ref('point_of_sale.menu_point_root').id},
}
@api.multi
def open_frontend_cb(self):
if not self.ids:
return {}
for session in self.filtered(lambda s: s.user_id.id != self.env.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)
return {
'type': 'ir.actions.act_url',
'target': 'self',
'url': '/pos/web/',
}
+22
View File
@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
from odoo.exceptions import UserError
class ProductTemplate(models.Model):
_inherit = 'product.template'
available_in_pos = fields.Boolean(string='Available in the Point of Sale', help='Check if you want this product to appear in the Point of Sale', default=True)
to_weight = fields.Boolean(string='To Weigh With Scale', help="Check if the product should be weighted using the hardware scale integration")
pos_categ_id = fields.Many2one(
'pos.category', string='Point of Sale Category',
help="Those categories are used to group similar products for point of sale.")
@api.multi
def unlink(self):
product_ctx = dict(self.env.context or {}, active_test=False)
if self.with_context(product_ctx).search_count([('id', 'in', self.ids), ('available_in_pos', '=', True)]):
if self.env['pos.session'].search_count([('state', '!=', 'closed')]):
raise UserError(_('You cannot delete a product saleable in point of sale while a session is still opened.'))
return super(ProductTemplate, self).unlink()
+31
View File
@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class PosConfiguration(models.TransientModel):
_inherit = 'res.config.settings'
_name = 'pos.config.settings'
module_pos_restaurant = fields.Selection([
(0, "Point of sale for shops"),
(1, "Restaurant: activate table management")
], string="Restaurant",
help='This module adds several restaurant features to the Point of Sale: \n\n- Bill Printing: Allows you to print a receipt before the order is paid \n\n- Bill Splitting: Allows you to split an order into different orders \n\n- Kitchen Order Printing: allows you to print orders updates to kitchen or bar printers')
module_pos_loyalty = fields.Boolean(
string="Loyalty Program",
help='Allows you to define a loyalty program in the point of sale, where the customers earn loyalty points and get rewards')
module_pos_discount = fields.Selection([
(0, "Allow discounts on order lines only"),
(1, "Allow global discounts")
], string="Discount",
help='Allows the cashier to quickly give a percentage sale discount for all the sales order to a customer')
module_pos_mercury = fields.Selection([
(0, "No credit card"),
(1, "Allows customers to pay with credit cards.")
], string="Credit Cards",
help='The transactions are processed by MercuryPay')
module_pos_reprint = fields.Selection([
(0, "No reprint"),
(1, "Allow cashier to reprint receipts")
], string="Reprints")
@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
class ResPartner(models.Model):
_inherit = 'res.partner'
barcode = fields.Char(string='Barcode', help="BarCode", oldname='ean13')
@api.model
def create_from_ui(self, partner):
""" 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'):
partner['image'] = partner['image'].split(',')[1]
partner_id = partner.pop('id', False)
if partner_id: # Modifying existing partner
self.browse(partner_id).write(partner)
else:
partner_id = self.create(partner).id
return partner_id
+15
View File
@@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
from odoo.exceptions import UserError
class ResUsers(models.Model):
_inherit = 'res.users'
pos_security_pin = fields.Char(string='Security PIN', size=32, help='A Security PIN used to protect sensible functionality in the Point of Sale')
@api.constrains('pos_security_pin')
def _check_pin(self):
if self.pos_security_pin and not self.pos_security_pin.isdigit():
raise UserError(_("Security PIN can only contain digits"))
File diff suppressed because it is too large Load Diff
@@ -1,60 +0,0 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<openerp>
<data>
<report
id="report_user_label"
model="res.users"
string="User Labels"
report_type="qweb-pdf"
name="point_of_sale.report_userlabel"
file="point_of_sale.report_userlabel"
/>
<report
id="pos_invoice_report"
string="Invoice"
model="pos.order"
report_type="qweb-pdf"
name="point_of_sale.report_invoice"
file="point_of_sale.report_invoice"
/>
<report
id="pos_lines_report"
string="Sales Lines"
model="pos.order"
report_type="qweb-pdf"
name="point_of_sale.report_saleslines"
file="point_of_sale.report_saleslines"
auto="False"
/>
<report
id="pos_lines_detail"
string="Details of Sales"
model="pos.order"
report_type="qweb-pdf"
name="point_of_sale.report_detailsofsales"
file="point_of_sale.report_detailsofsales"
menu="False"
/>
<report
id="pos_payment_report"
string="Today's Payment"
model="pos.order"
report_type="qweb-pdf"
name="point_of_sale.report_payment"
file="point_of_sale.report_payment"
/>
<report
id="action_report_pos_session_summary"
string="Session Summary"
model="pos.session"
report_type="qweb-pdf"
name="point_of_sale.report_sessionsummary"
file="point_of_sale.report_sessionsummary"
/>
</data>
</openerp>
-998
View File
@@ -1,998 +0,0 @@
<?xml version="1.0"?>
<openerp>
<data>
<!-- Top menu item -->
<menuitem name="Point of Sale"
id="menu_point_root"
groups="group_pos_manager,group_pos_user"
icon="fa-suitcase"
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>
+2 -6
View File
@@ -1,12 +1,8 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import pos_users_product
import account_statement
import pos_receipt
import pos_invoice
import pos_lines
import pos_details
import pos_payment_report
import pos_report
import pos_order_report
import pos_receipt
import pos_users_product
-181
View File
@@ -1,181 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import time
from openerp.osv import osv
from openerp.report import report_sxw
class pos_details(report_sxw.rml_parse):
def _get_invoice(self, inv_id):
res={}
if inv_id:
self.cr.execute("select number from account_invoice as ac where id = %s", (inv_id,))
res = self.cr.fetchone()
return res[0] or 'Draft'
else:
return ''
def _get_all_users(self):
user_obj = self.pool.get('res.users')
return user_obj.search(self.cr, self.uid, [])
def _pos_sales_details(self, form):
pos_obj = self.pool.get('pos.order')
user_obj = self.pool.get('res.users')
data = []
result = {}
user_ids = form['user_ids'] or self._get_all_users()
company_id = user_obj.browse(self.cr, self.uid, self.uid).company_id.id
pos_ids = pos_obj.search(self.cr, self.uid, [('date_order','>=',form['date_start'] + ' 00:00:00'),('date_order','<=',form['date_end'] + ' 23:59:59'),('user_id','in',user_ids),('state','in',['done','paid','invoiced']),('company_id','=',company_id)])
for pos in pos_obj.browse(self.cr, self.uid, pos_ids, context=self.localcontext):
for pol in pos.lines:
result = {
'code': pol.product_id.default_code,
'name': pol.product_id.name,
'invoice_id': pos.invoice_id.id,
'price_unit': pol.price_unit,
'qty': pol.qty,
'discount': pol.discount,
'total': (pol.price_unit * pol.qty * (1 - (pol.discount) / 100.0)),
'date_order': pos.date_order,
'pos_name': pos.name,
'uom': pol.product_id.uom_id.name
}
data.append(result)
self.total += result['total']
self.qty += result['qty']
self.discount += result['discount']
if data:
return data
else:
return {}
def _get_qty_total_2(self):
return self.qty
def _get_sales_total_2(self):
return self.total
def _get_sum_invoice_2(self, form):
pos_obj = self.pool.get('pos.order')
user_obj = self.pool.get('res.users')
user_ids = form['user_ids'] or self._get_all_users()
company_id = user_obj.browse(self.cr, self.uid, self.uid).company_id.id
pos_ids = pos_obj.search(self.cr, self.uid, [('date_order','>=',form['date_start'] + ' 00:00:00'),('date_order','<=',form['date_end'] + ' 23:59:59'),('user_id','in',user_ids),('company_id','=',company_id),('invoice_id','<>',False)])
for pos in pos_obj.browse(self.cr, self.uid, pos_ids):
for pol in pos.lines:
self.total_invoiced += (pol.price_unit * pol.qty * (1 - (pol.discount) / 100.0))
return self.total_invoiced or False
def _paid_total_2(self):
return self.total or 0.0
def _get_sum_dis_2(self):
return self.discount or 0.0
def _get_sum_discount(self, form):
#code for the sum of discount value
pos_obj = self.pool.get('pos.order')
user_obj = self.pool.get('res.users')
user_ids = form['user_ids'] or self._get_all_users()
company_id = user_obj.browse(self.cr, self.uid, self.uid).company_id.id
pos_ids = pos_obj.search(self.cr, self.uid, [('date_order','>=',form['date_start'] + ' 00:00:00'),('date_order','<=',form['date_end'] + ' 23:59:59'),('user_id','in',user_ids),('company_id','=',company_id)])
for pos in pos_obj.browse(self.cr, self.uid, pos_ids):
for pol in pos.lines:
self.total_discount += ((pol.price_unit * pol.qty) * (pol.discount / 100))
return self.total_discount or False
def _get_payments(self, form):
statement_line_obj = self.pool.get("account.bank.statement.line")
pos_order_obj = self.pool.get("pos.order")
user_ids = form['user_ids'] or self._get_all_users()
company_id = self.pool['res.users'].browse(self.cr, self.uid, self.uid).company_id.id
pos_ids = pos_order_obj.search(self.cr, self.uid, [('date_order','>=',form['date_start'] + ' 00:00:00'),('date_order','<=',form['date_end'] + ' 23:59:59'),('state','in',['paid','invoiced','done']),('user_id','in',user_ids), ('company_id', '=', company_id)])
data={}
if pos_ids:
st_line_ids = statement_line_obj.search(self.cr, self.uid, [('pos_statement_id', 'in', pos_ids)])
if st_line_ids:
st_id = statement_line_obj.browse(self.cr, self.uid, st_line_ids)
a_l=[]
for r in st_id:
a_l.append(r['id'])
self.cr.execute("select aj.name,sum(amount) from account_bank_statement_line as absl,account_bank_statement as abs,account_journal as aj " \
"where absl.statement_id = abs.id and abs.journal_id = aj.id and absl.id IN %s " \
"group by aj.name ",(tuple(a_l),))
data = self.cr.dictfetchall()
return data
else:
return {}
def _total_of_the_day(self, objects):
return self.total or 0.00
def _sum_invoice(self, objects):
return reduce(lambda acc, obj:
acc + obj.invoice_id.amount_total,
[o for o in objects if o.invoice_id and o.invoice_id.number],
0.0)
def _ellipsis(self, orig_str, maxlen=100, ellipsis='...'):
maxlen = maxlen - len(ellipsis)
if maxlen <= 0:
maxlen = 1
new_str = orig_str[:maxlen]
return new_str
def _strip_name(self, name, maxlen=50):
return self._ellipsis(name, maxlen, ' ...')
def _get_tax_amount(self, form):
taxes = {}
account_tax_obj = self.pool.get('account.tax')
user_ids = form['user_ids'] or self._get_all_users()
pos_order_obj = self.pool.get('pos.order')
company_id = self.pool['res.users'].browse(self.cr, self.uid, self.uid).company_id.id
pos_ids = pos_order_obj.search(self.cr, self.uid, [('date_order','>=',form['date_start'] + ' 00:00:00'),('date_order','<=',form['date_end'] + ' 23:59:59'),('state','in',['paid','invoiced','done']),('user_id','in',user_ids), ('company_id', '=', company_id)])
for order in pos_order_obj.browse(self.cr, self.uid, pos_ids):
currency = order.session_id.currency_id
for line in order.lines:
if line.product_id.taxes_id:
line_taxes = line.product_id.taxes_id.compute_all(line.price_unit * (1-(line.discount or 0.0)/100.0), currency, line.qty, product=line.product_id, partner=line.order_id.partner_id or False)
for tax in line_taxes['taxes']:
taxes.setdefault(tax['id'], {'name': tax['name'], 'amount':0.0})
taxes[tax['id']]['amount'] += tax['amount']
return taxes.values()
def _get_user_names(self, user_ids):
user_obj = self.pool.get('res.users')
return ', '.join(map(lambda x: x.name, user_obj.browse(self.cr, self.uid, user_ids)))
def __init__(self, cr, uid, name, context):
super(pos_details, self).__init__(cr, uid, name, context=context)
self.total = 0.0
self.qty = 0.0
self.total_invoiced = 0.0
self.discount = 0.0
self.total_discount = 0.0
self.localcontext.update({
'time': time,
'strip_name': self._strip_name,
'getpayments': self._get_payments,
'getsumdisc': self._get_sum_discount,
'gettotaloftheday': self._total_of_the_day,
'gettaxamount': self._get_tax_amount,
'pos_sales_details':self._pos_sales_details,
'getqtytotal2': self._get_qty_total_2,
'getsalestotal2': self._get_sales_total_2,
'getsuminvoice2':self._get_sum_invoice_2,
'getpaidtotal2': self._paid_total_2,
'getinvoice':self._get_invoice,
'get_user_names': self._get_user_names,
})
class report_pos_details(osv.AbstractModel):
_name = 'report.point_of_sale.report_detailsofsales'
_inherit = 'report.abstract_report'
_template = 'point_of_sale.report_detailsofsales'
_wrapped_report_class = pos_details
+14 -21
View File
@@ -1,34 +1,27 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp import SUPERUSER_ID
from openerp.osv import osv
from openerp.tools.translate import _
from openerp.exceptions import UserError
from odoo import api, models, _
from odoo.exceptions import UserError
class PosInvoiceReport(osv.AbstractModel):
class PosInvoiceReport(models.AbstractModel):
_name = 'report.point_of_sale.report_invoice'
def render_html(self, cr, uid, ids, data=None, context=None):
report_obj = self.pool['report']
posorder_obj = self.pool['pos.order']
report = report_obj._get_report_from_name(cr, uid, 'account.report_invoice')
selected_orders = posorder_obj.browse(cr, uid, ids, context=context)
@api.multi
def render_html(self, data=None):
Report = self.env['report']
PosOrder = self.env['pos.order']
ids_to_print = []
invoiced_posorders_ids = []
for order in selected_orders:
if order.invoice_id:
ids_to_print.append(order.invoice_id.id)
invoiced_posorders_ids.append(order.id)
not_invoiced_orders_ids = list(set(ids) - set(invoiced_posorders_ids))
selected_orders = PosOrder.browse(self.ids)
for order in selected_orders.filtered(lambda o: o.invoice_id):
ids_to_print.append(order.invoice_id.id)
invoiced_posorders_ids.append(order.id)
not_invoiced_orders_ids = list(set(self.ids) - set(invoiced_posorders_ids))
if not_invoiced_orders_ids:
not_invoiced_posorders = posorder_obj.browse(cr, uid, not_invoiced_orders_ids, context=context)
not_invoiced_posorders = PosOrder.browse(not_invoiced_orders_ids)
not_invoiced_orders_names = list(map(lambda a: a.name, not_invoiced_posorders))
raise UserError(_('No link to an invoice for %s.') % ', '.join(not_invoiced_orders_names))
docargs = {
'docs': self.pool['account.invoice'].browse(cr, uid, ids_to_print, context=context)
}
return report_obj.render(cr, SUPERUSER_ID, ids, 'account.report_invoice', docargs, context=context)
return Report.sudo().render('account.report_invoice', {'docs': self.env['account.invoice'].browse(ids_to_print)})
-43
View File
@@ -1,43 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import time
from openerp.osv import osv
from openerp.report import report_sxw
class pos_lines(report_sxw.rml_parse):
def __init__(self, cr, uid, name, context):
super(pos_lines, self).__init__(cr, uid, name, context=context)
self.total = 0.0
self.localcontext.update({
'time': time,
'total_quantity': self.__total_quantity__,
'taxes':self.__taxes__,
})
def __total_quantity__(self, obj):
tot = 0
for line in obj.lines:
tot += line.qty
self.total = tot
return self.total
def __taxes__(self, obj):
self.cr.execute ( " Select acct.name from pos_order as po " \
" LEFT JOIN pos_order_line as pol ON po.id = pol.order_id " \
" LEFT JOIN product_product as pp ON pol.product_id = pp.id"
" LEFT JOIN product_taxes_rel as ptr ON pp.product_tmpl_id = ptr.prod_id " \
" LEFT JOIN account_tax as acct ON acct.id = ptr.tax_id " \
" WHERE pol.id = %s", (obj.id,))
res=self.cr.fetchone()[0]
return res
class report_pos_lines(osv.AbstractModel):
_name = 'report.point_of_sale.report_saleslines'
_inherit = 'report.abstract_report'
_template = 'point_of_sale.report_saleslines'
_wrapped_report_class = pos_lines
+70 -59
View File
@@ -1,78 +1,89 @@
# -*- 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
from odoo import fields, models, tools
class pos_order_report(osv.osv):
class PosOrderReport(models.Model):
_name = "report.pos.order"
_description = "Point of Sale Orders Statistics"
_auto = False
_columns = {
'date': fields.datetime('Date Order', readonly=True),
'partner_id':fields.many2one('res.partner', 'Partner', readonly=True),
'product_id':fields.many2one('product.product', 'Product', readonly=True),
'product_tmpl_id': fields.many2one('product.template', 'Product Template', readonly=True),
'state': fields.selection([('draft', 'New'), ('paid', 'Paid'), ('done', 'Posted'), ('invoiced', 'Invoiced'), ('cancel', 'Cancelled')],
'Status'),
'user_id':fields.many2one('res.users', 'Salesperson', readonly=True),
'price_total':fields.float('Total Price', readonly=True),
'price_sub_total':fields.float('Subtotal w/o discount', readonly=True),
'total_discount':fields.float('Total Discount', readonly=True),
'average_price': fields.float('Average Price', readonly=True,group_operator="avg"),
'location_id':fields.many2one('stock.location', 'Location', readonly=True),
'company_id':fields.many2one('res.company', 'Company', readonly=True),
'nbr':fields.integer('# of Lines', readonly=True), # TDE FIXME master: rename into nbr_lines
'product_qty':fields.integer('Product Quantity', readonly=True),
'journal_id': fields.many2one('account.journal', 'Journal'),
'delay_validation': fields.integer('Delay Validation'),
'product_categ_id': fields.many2one('product.category', 'Product Category', readonly=True),
'invoiced': fields.boolean('Invoiced', readonly=True),
'config_id' : fields.many2one('pos.config', 'Point of Sale', readonly=True),
'pos_categ_id': fields.many2one('pos.category','Public Category', readonly=True),
'stock_location_id': fields.many2one('stock.location', 'Warehouse', readonly=True),
'pricelist_id': fields.many2one('product.pricelist', 'Pricelist', readonly=True),
}
_order = 'date desc'
date = fields.Datetime(string='Date Order', readonly=True)
partner_id = fields.Many2one('res.partner', string='Partner', readonly=True)
product_id = fields.Many2one('product.product', string='Product', readonly=True)
product_tmpl_id = fields.Many2one('product.template', string='Product Template', readonly=True)
state = fields.Selection(
[('draft', 'New'), ('paid', 'Paid'), ('done', 'Posted'),
('invoiced', 'Invoiced'), ('cancel', 'Cancelled')],
string='Status')
user_id = fields.Many2one('res.users', string='Salesperson', readonly=True)
price_total = fields.Float(string='Total Price', readonly=True)
price_sub_total = fields.Float(string='Subtotal w/o discount', readonly=True)
total_discount = fields.Float(string='Total Discount', readonly=True)
average_price = fields.Float(string='Average Price', readonly=True, group_operator="avg")
location_id = fields.Many2one('stock.location', string='Location', readonly=True)
company_id = fields.Many2one('res.company', string='Company', readonly=True)
nbr_lines = fields.Integer(string='# of Lines', readonly=True, oldname='nbr')
product_qty = fields.Integer(string='Product Quantity', readonly=True)
journal_id = fields.Many2one('account.journal', string='Journal')
delay_validation = fields.Integer(string='Delay Validation')
product_categ_id = fields.Many2one('product.category', string='Product Category', readonly=True)
invoiced = fields.Boolean(readonly=True)
config_id = fields.Many2one('pos.config', string='Point of Sale', readonly=True)
pos_categ_id = fields.Many2one('pos.category', string='Public Category', readonly=True)
stock_location_id = fields.Many2one('stock.location', string='Warehouse', readonly=True)
pricelist_id = fields.Many2one('product.pricelist', string='Pricelist', readonly=True)
session_id = fields.Many2one('pos.session', string='Session', readonly=True)
def init(self, cr):
tools.drop_view_if_exists(cr, 'report_pos_order')
cr.execute("""
create or replace view report_pos_order as (
select
min(l.id) as id,
count(*) as nbr,
s.date_order as date,
sum(l.qty * u.factor) as product_qty,
sum(l.qty * l.price_unit) as price_sub_total,
sum((l.qty * l.price_unit) * (100 - l.discount) / 100) as price_total,
sum((l.qty * l.price_unit) * (l.discount / 100)) as total_discount,
(sum(l.qty*l.price_unit)/sum(l.qty * u.factor))::decimal as average_price,
sum(cast(to_char(date_trunc('day',s.date_order) - date_trunc('day',s.create_date),'DD') as int)) as delay_validation,
s.partner_id as partner_id,
s.state as state,
s.user_id as user_id,
s.location_id as location_id,
s.company_id as company_id,
s.sale_journal as journal_id,
l.product_id as product_id,
pt.categ_id as product_categ_id,
CREATE OR REPLACE VIEW report_pos_order AS (
SELECT
MIN(l.id) AS id,
COUNT(*) AS nbr_lines,
s.date_order AS date,
SUM(l.qty * u.factor) AS product_qty,
SUM(l.qty * l.price_unit) AS price_sub_total,
SUM((l.qty * l.price_unit) * (100 - l.discount) / 100) AS price_total,
SUM((l.qty * l.price_unit) * (l.discount / 100)) AS total_discount,
(SUM(l.qty*l.price_unit)/SUM(l.qty * u.factor))::decimal AS average_price,
SUM(cast(to_char(date_trunc('day',s.date_order) - date_trunc('day',s.create_date),'DD') AS INT)) AS delay_validation,
s.partner_id AS partner_id,
s.state AS state,
s.user_id AS user_id,
s.location_id AS location_id,
s.company_id AS company_id,
s.sale_journal AS journal_id,
l.product_id AS product_id,
pt.categ_id AS product_categ_id,
p.product_tmpl_id,
ps.config_id,
pt.pos_categ_id,
pc.stock_location_id,
s.pricelist_id,
s.session_id,
s.invoice_id IS NOT NULL AS invoiced
from pos_order_line as l
left join pos_order s on (s.id=l.order_id)
left join product_product p on (l.product_id=p.id)
left join product_template pt on (p.product_tmpl_id=pt.id)
left join product_uom u on (u.id=pt.uom_id)
left join pos_session ps on (s.session_id=ps.id)
left join pos_config pc on (ps.config_id=pc.id)
group by
FROM pos_order_line AS l
LEFT JOIN pos_order s ON (s.id=l.order_id)
LEFT JOIN product_product p ON (l.product_id=p.id)
LEFT JOIN product_template pt ON (p.product_tmpl_id=pt.id)
LEFT JOIN product_uom u ON (u.id=pt.uom_id)
LEFT JOIN pos_session ps ON (s.session_id=ps.id)
LEFT JOIN pos_config pc ON (ps.config_id=pc.id)
GROUP BY
s.date_order, s.partner_id,s.state, pt.categ_id,
s.user_id,s.location_id,s.company_id,s.sale_journal,s.pricelist_id,s.invoice_id,l.product_id,s.create_date,pt.categ_id,pt.pos_categ_id,p.product_tmpl_id,ps.config_id,pc.stock_location_id
having
sum(l.qty * u.factor) != 0)""")
s.user_id, s.location_id, s.company_id, s.sale_journal,
s.pricelist_id, s.invoice_id, s.create_date, s.session_id,
l.product_id,
pt.categ_id, pt.pos_categ_id,
p.product_tmpl_id,
ps.config_id,
pc.stock_location_id
HAVING
SUM(l.qty * u.factor) != 0
)
""")
@@ -1,52 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import time
from openerp.osv import osv
from openerp.report import report_sxw
class pos_payment_report(report_sxw.rml_parse):
def __init__(self, cr, uid, name, context):
super(pos_payment_report, self).__init__(cr, uid, name, context=context)
self.total = 0.0
self.localcontext.update({
'time': time,
'pos_payment': self._pos_payment,
'pos_payment_total':self._pos_payment_total,
})
def _pos_payment(self, obj):
self.total = 0
data={}
sql = """ select id from pos_order where id = %d"""%(obj.id)
self.cr.execute(sql)
if self.cr.fetchone():
self.cr.execute ("select pt.name,pp.default_code as code,pol.qty,pu.name as uom,pol.discount,pol.price_unit, " \
"(pol.price_unit * pol.qty * (1 - (pol.discount) / 100.0)) as total " \
"from pos_order as po,pos_order_line as pol,product_product as pp,product_template as pt, product_uom as pu " \
"where pt.id=pp.product_tmpl_id and pp.id=pol.product_id and po.id = pol.order_id and pu.id=pt.uom_id " \
"and po.state IN ('paid','invoiced') and to_char(date_trunc('day',po.date_order),'YYYY-MM-DD')::date = current_date and po.id=%d"%(obj.id))
data=self.cr.dictfetchall()
else:
self.cr.execute ("select pt.name,pp.default_code as code,pol.qty,pu.name as uom,pol.discount,pol.price_unit, " \
"(pol.price_unit * pol.qty * (1 - (pol.discount) / 100.0)) as total " \
"from pos_order as po,pos_order_line as pol,product_product as pp,product_template as pt, product_uom as pu " \
"where pt.id=pp.product_tmpl_id and pp.id=pol.product_id and po.id = pol.order_id and pu.id=pt.uom_id " \
"and po.state IN ('paid','invoiced') and to_char(date_trunc('day',po.date_order),'YYYY-MM-DD')::date = current_date")
data=self.cr.dictfetchall()
for d in data:
self.total += d['price_unit'] * d['qty']
return data
def _pos_payment_total(self, o):
return self.total
class report_pos_payment(osv.AbstractModel):
_name = 'report.point_of_sale.report_payment'
_inherit = 'report.abstract_report'
_template = 'point_of_sale.report_payment'
_wrapped_report_class = pos_payment_report
-121
View File
@@ -1,121 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp.osv import fields, osv
from openerp import tools
class report_transaction_pos(osv.osv):
_name = "report.transaction.pos"
_description = "transaction for the pos"
_auto = False
_columns = {
'date_create': fields.char('Date', size=16, readonly=True),
'journal_id': fields.many2one('account.journal', 'Sales Journal', readonly=True),
'jl_id': fields.many2one('account.journal', 'Cash Journals', readonly=True),
'user_id': fields.many2one('res.users', 'User', readonly=True),
'no_trans': fields.float('Number of Transaction', readonly=True),
'amount': fields.float('Amount', readonly=True),
'invoice_id': fields.float('Nbr Invoice', readonly=True),
'invoice_am': fields.float('Invoice Amount', readonly=True),
'product_nb': fields.float('Product Nb.', readonly=True),
'disc': fields.float('Disc.', readonly=True),
}
def init(self, cr):
tools.drop_view_if_exists(cr, 'report_transaction_pos')
cr.execute("""
create or replace view report_transaction_pos as (
select
min(absl.id) as id,
count(absl.id) as no_trans,
sum(absl.amount) as amount,
sum((100.0-line.discount) * line.price_unit * line.qty / 100.0) as disc,
to_char(date_trunc('day',absl.create_date),'YYYY-MM-DD')::text as date_create,
po.user_id as user_id,
po.sale_journal as journal_id,
abs.journal_id as jl_id,
count(po.invoice_id) as invoice_id,
count(p.id) as product_nb
from
account_bank_statement_line as absl,
account_bank_statement as abs,
product_product as p,
pos_order_line as line,
pos_order as po
where
absl.pos_statement_id = po.id and
line.order_id=po.id and
line.product_id=p.id and
absl.statement_id=abs.id
group by
po.user_id,po.sale_journal, abs.journal_id,
to_char(date_trunc('day',absl.create_date),'YYYY-MM-DD')::text
)
""")
#to_char(date_trunc('day',absl.create_date),'YYYY-MM-DD')
#to_char(date_trunc('day',absl.create_date),'YYYY-MM-DD')::text as date_create,
class report_sales_by_user_pos(osv.osv):
_name = "report.sales.by.user.pos"
_description = "Sales by user"
_auto = False
_columns = {
'date_order': fields.date('Order Date',required=True, select=True),
'amount': fields.float('Total', readonly=True, select=True),
'qty': fields.float('Quantity', readonly=True, select=True),
'user_id': fields.many2one('res.users', 'User', readonly=True, select=True),
}
def init(self, cr):
tools.drop_view_if_exists(cr, 'report_sales_by_user_pos')
cr.execute("""
create or replace view report_sales_by_user_pos as (
select
min(po.id) as id,
to_char(date_trunc('day',po.date_order),'YYYY-MM-DD')::text as date_order,
po.user_id as user_id,
sum(pol.qty)as qty,
sum((pol.price_unit * pol.qty * (1 - (pol.discount) / 100.0))) as amount
from
pos_order as po,pos_order_line as pol,product_product as pp,product_template as pt
where
pt.id=pp.product_tmpl_id and pp.id=pol.product_id and po.id = pol.order_id
group by
to_char(date_trunc('day',po.date_order),'YYYY-MM-DD')::text,
po.user_id
)
""")
class report_sales_by_user_pos_month(osv.osv):
_name = "report.sales.by.user.pos.month"
_description = "Sales by user monthly"
_auto = False
_columns = {
'date_order': fields.date('Order Date',required=True, select=True),
'amount': fields.float('Total', readonly=True, select=True),
'qty': fields.float('Quantity', readonly=True, select=True),
'user_id': fields.many2one('res.users', 'User', readonly=True, select=True),
}
def init(self, cr):
tools.drop_view_if_exists(cr, 'report_sales_by_user_pos_month')
cr.execute("""
create or replace view report_sales_by_user_pos_month as (
select
min(po.id) as id,
to_char(date_trunc('month',po.date_order),'YYYY-MM-DD')::text as date_order,
po.user_id as user_id,
sum(pol.qty)as qty,
sum((pol.price_unit * pol.qty * (1 - (pol.discount) / 100.0))) as amount
from
pos_order as po,pos_order_line as pol,product_product as pp,product_template as pt
where
pt.id=pp.product_tmpl_id and pp.id=pol.product_id and po.id = pol.order_id
group by
to_char(date_trunc('month',po.date_order),'YYYY-MM-DD')::text,
po.user_id
)
""")
-29
View File
@@ -1,29 +0,0 @@
from openerp.osv import fields, osv
class pos_configuration(osv.TransientModel):
_inherit = 'res.config.settings'
_name = 'pos.config.settings'
_columns = {
'module_pos_restaurant': fields.selection([
(0, "Point of sale for shops"),
(1, "Restaurant: activate table management")
], "Restaurant",
help='This module adds several restaurant features to the Point of Sale: \n\n- Bill Printing: Allows you to print a receipt before the order is paid \n\n- Bill Splitting: Allows you to split an order into different orders \n\n- Kitchen Order Printing: allows you to print orders updates to kitchen or bar printers'),
'module_pos_loyalty': fields.boolean("Loyalty Program",
help='Allows you to define a loyalty program in the point of sale, where the customers earn loyalty points and get rewards'),
'module_pos_discount': fields.selection([
(0, "Allow discounts on order lines only"),
(1, "Allow global discounts")
], "Discount",
help='Allows the cashier to quickly give a percentage sale discount for all the sales order to a customer'),
'module_pos_mercury': fields.selection([
(0, "No credit card"),
(1, "Allows customers to pay with credit cards.")
], "Credit Cards",
help='The transactions are processed by MercuryPay'),
'module_pos_reprint': fields.selection([
(0, "No reprint"),
(1, "Allow cashier to reprint receipts")
], "Reprints"),
}
-9
View File
@@ -1,9 +0,0 @@
from openerp.osv import osv, fields
class res_users(osv.osv):
_inherit = 'res.partner'
_columns = {
'barcode' : fields.char('Barcode', help="BarCode", oldname='ean13'),
}
-19
View File
@@ -1,19 +0,0 @@
from openerp.osv import osv, fields
class res_users(osv.osv):
_inherit = 'res.users'
_columns = {
'pos_security_pin': fields.char('Security PIN',size=32, help='A Security PIN used to protect sensible functionality in the Point of Sale'),
}
def _check_pin(self, cr, uid, ids, context=None):
for user in self.browse(cr, uid, ids, context=context):
if user.pos_security_pin and not user.pos_security_pin.isdigit():
return False
return True
_constraints = [
(_check_pin, "Security PIN can only contain digits",['pos_security_pin']),
]
@@ -1,13 +1,10 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_pos_order,pos.order,model_pos_order,group_pos_user,1,1,1,1
access_pos_order_line,pos.order.line,model_pos_order_line,group_pos_user,1,1,1,1
access_report_transaction_pos,report.transaction.pos,model_report_transaction_pos,group_pos_manager,1,1,1,1
access_stock_picking_pos_user,stock.picking pos_user,stock.model_stock_picking,group_pos_user,1,1,1,1
access_stock_warehouse_pos_user,stock.warehouse pos_user,stock.model_stock_warehouse,group_pos_user,1,0,0,0
access_pos_order_stock_worker,pos.order stock_worker,model_pos_order,stock.group_stock_user,1,0,0,0
access_stock_move_pos_user,stock.move pos_user,stock.model_stock_move,group_pos_user,1,1,1,1
access_report_sales_by_user_pos,report.sales.by.user.pos,model_report_sales_by_user_pos,group_pos_user,1,0,0,0
access_report_sales_by_user_pos_month,report.sales.by.user.pos.month,model_report_sales_by_user_pos_month,group_pos_user,1,0,0,0
access_report_pos_order,report.pos.order,model_report_pos_order,group_pos_user,1,1,1,1
access_account_journal_pos_user,account.journal pos_user,account.model_account_journal,group_pos_user,1,0,0,0
access_account_payment_method_pos_user,account.payment.method pos_user,account.model_account_payment_method,group_pos_manager,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_pos_order pos.order model_pos_order group_pos_user 1 1 1 1
3 access_pos_order_line pos.order.line model_pos_order_line group_pos_user 1 1 1 1
access_report_transaction_pos report.transaction.pos model_report_transaction_pos group_pos_manager 1 1 1 1
4 access_stock_picking_pos_user stock.picking pos_user stock.model_stock_picking group_pos_user 1 1 1 1
5 access_stock_warehouse_pos_user stock.warehouse pos_user stock.model_stock_warehouse group_pos_user 1 0 0 0
6 access_pos_order_stock_worker pos.order stock_worker model_pos_order stock.group_stock_user 1 0 0 0
7 access_stock_move_pos_user stock.move pos_user stock.model_stock_move group_pos_user 1 1 1 1
access_report_sales_by_user_pos report.sales.by.user.pos model_report_sales_by_user_pos group_pos_user 1 0 0 0
access_report_sales_by_user_pos_month report.sales.by.user.pos.month model_report_sales_by_user_pos_month group_pos_user 1 0 0 0
8 access_report_pos_order report.pos.order model_report_pos_order group_pos_user 1 1 1 1
9 access_account_journal_pos_user account.journal pos_user account.model_account_journal group_pos_user 1 0 0 0
10 access_account_payment_method_pos_user account.payment.method pos_user account.model_account_payment_method group_pos_manager 1 0 0 0
@@ -1,7 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="0">
<odoo>
<record id="group_pos_user" model="res.groups">
<field name="name">User</field>
<field name="category_id" ref="base.module_category_point_of_sale"/>
@@ -71,5 +69,4 @@
<field name="global" eval="True"/>
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
</record>
</data>
</openerp>
</odoo>
@@ -1,11 +0,0 @@
-
In order to test the Point of Sale module, I will open all cash registers through the wizard
-
I go to the window to open the cash registers as admin
-
!record {model: pos.open.statement, id: new_statement_open}:
-
I click on the button to open all statements/cash registers
-
!python {model: pos.open.statement}: |
self.open_statement(cr, uid, [ref('new_statement_open')], context={})
@@ -1,136 +0,0 @@
- |
In order to test the Point of Sale in module, I will do a full flow from the sale to the payment and invoicing.
I will use two products, one with price including a 10% tax, the other one with 5% tax excluded from the price.
- |
I create a VAT tax of 10%, included in the public price
-
!record {model: account.tax, id: account_tax_10_incl}:
name: VAT 10 perc Incl
amount_type: percent
amount: 10.0
price_include: 1
-
I assign this 10 percent tax on the [PCSC234] PC Assemble SC234 product as a sale tax
-
!record {model: product.product, id: product.product_product_3, view: False}:
taxes_id: [account_tax_10_incl]
- |
I create a VAT tax of 5%, which is added to the public price
-
!record {model: account.tax, id: account_tax_05_incl}:
name: VAT 05 perc Excl
amount_type: percent
amount: 5.0
price_include: 0
-
I will create a second VAT tax of 5% but this time for a child company, to
ensure that only product taxes of the current session's company are considered
(this tax should be ignore when computing order's taxes in following tests)
-
!record {model: account.tax, id: account_tax_05_incl_chicago}:
name: VAT 05 perc Excl (US)
amount_type: percent
amount: 5.0
price_include: 0
company_id: stock.res_company_1
-
I assign those 5 percent taxes on the PCSC349 product as a sale taxes
-
!record {model: product.product, id: product.product_product_4, view: False}:
taxes_id: [account_tax_05_incl, account_tax_05_incl_chicago]
-
I click on create a new session button
-
!python {model: pos.config}: |
self.open_session_cb(cr, uid, [ref('pos_config_main')])
-
I create a PoS order with 2 units of PCSC234 at 450 EUR (Tax Incl) and 3 units of PCSC349 at 300 EUR. (Tax Excl)
-
!record {model: pos.order, id: pos_order_pos0}:
company_id: base.main_company
lines:
- name: OL/0001
product_id: product.product_product_3
price_unit: 450
discount: 0.0
qty: 2.0
- name: OL/0002
product_id: product.product_product_4
price_unit: 300
discount: 0.0
qty: 3.0
-
I check that the total of the order is equal to 450*2 + 300*3*1.05 and the tax of the order is equal to 900 -(450 * 2 / 1.1) + 300*0.05*3
-
!python {model: pos.order}: |
order = self.browse(cr, uid, ref('pos_order_pos0'))
assert(abs(order.amount_total - (450*2 + 300*3*1.05)) < 0.01), "The order has a wrong amount, tax included"
assert(abs(order.amount_tax - (900-(450*2/1.1) + 300*0.05*3)) < 0.01), "The order has a wrong tax amount"
-
I want to add a global discount of 5 percent using the wizard
-
!record {model: pos.discount, id: pos_discount_0}:
discount: 5.0
-
I click the apply button to set the discount on all lines
-
!python {model: pos.discount}: |
self.apply_discount(cr, uid, [ref("pos_discount_0")], {"active_model": "pos.order",
"active_ids": [ref("pos_order_pos0")], "active_id": ref("pos_order_pos0"), })
-
I check that the total of the order is now equal to (450*2 + 300*3*1.05)*0.95
-
!python {model: pos.order}: |
order = self.browse(cr, uid, ref('pos_order_pos0'))
assert(abs(order.amount_total - (450*2 + 300*3*1.05)*0.95) < 0.01), "The order has a wrong total including tax and discounts"
-
I click on the "Make Payment" wizard to pay the PoS order with a partial amount of 100.0 EUR
-
!record {model: pos.make.payment, id: pos_make_payment_0, context: '{"active_id": ref("pos_order_pos0"), "active_ids": [ref("pos_order_pos0")]}' }:
amount: 100.0
-
I click on the validate button to register the payment.
-
!python {model: pos.make.payment}: |
self.check(cr, uid, [ref('pos_make_payment_0')], context={'active_id': ref('pos_order_pos0'), 'lang':'en_US', 'active_model': 'pos.order', 'tz': False, 'active_ids': [ref('pos_order_pos0')]} )
- |
I check that the order is not marked as paid yet
-
!assert {model: pos.order, id: pos_order_pos0}:
- state == 'draft'
- |
On the second payment proposition, I check that it proposes me the remaining balance which is 1790.0 EUR
-
!python {model: pos.make.payment}: |
defs = self.default_get(cr, uid, ['amount'], {'active_id': ref('pos_order_pos0')})
assert abs(defs['amount'] - ((450*2 + 300*3*1.05)*0.95-100.0)) < 0.01, "The remaining balance is incorrect"
-
I pay the remaining balance.
-
!record {model: pos.make.payment, id: pos_make_payment_1, context: '{"active_id": ref("pos_order_pos0"), "active_ids": [ref("pos_order_pos0")]}' }:
amount: !eval >
(450*2 + 300*3*1.05)*0.95-100.0
-
I click on the validate button to register the payment.
-
!python {model: pos.make.payment}: |
self.check(cr, uid, [ref('pos_make_payment_1')], context={'active_id': ref('pos_order_pos0')} )
- |
I check that the order is marked as paid
-
!assert {model: pos.order, id: pos_order_pos0}:
- state == 'paid'
-
I generate the journal entries
-
!python {model: pos.order}: |
self.create_account_move(cr, uid, [ref('pos_order_pos0')], {})
-
I test that the generated journal entry is attached to the PoS order
-
!assert {model: pos.order, id: pos_order_pos0}:
- bool(account_move)
@@ -1,53 +0,0 @@
-
I create a new PoS order with 2 units of PC1 at 450 EUR (Tax Incl) and 3 units of PCSC349 at 300 EUR. (Tax Excl)
-
!record {model: pos.order, id: pos_order_pos1}:
company_id: base.main_company
partner_id: base.res_partner_1
lines:
- name: OL/0001
product_id: product.product_product_3
price_unit: 450
discount: 5.0
qty: 2.0
- name: OL/0002
product_id: product.product_product_4
price_unit: 300
discount: 5.0
qty: 3.0
-
I click on the "Make Payment" wizard to pay the PoS order
-
!record {model: pos.make.payment, id: pos_make_payment_2, context: '{"active_id": ref("pos_order_pos1"), "active_ids": [ref("pos_order_pos1")]}' }:
amount: !eval >
(450*2 + 300*3*1.05)*0.95
-
I click on the validate button to register the payment.
-
!python {model: pos.make.payment}: |
self.check(cr, uid, [ref('pos_make_payment_2')], context={'active_id': ref('pos_order_pos1')} )
- |
I check that the order is marked as paid and there is no invoice attached to it
-
!python {model: pos.order}: |
order = self.browse(cr,uid,ref('pos_order_pos1'))
-
!assert {model: pos.order, id: pos_order_pos1, string: State not correct}:
- state == 'paid'
- not invoice_id
-
I set the order as invoiced
-
!workflow {model: pos.order, action: invoice, ref: pos_order_pos1}
-
I generate an invoice from the order
-
!python {model: pos.order}: |
invoice = self.action_invoice(cr, uid, [ref('pos_order_pos1')])
-
I test that the total of the attached invoice is correct
-
!python {model: pos.order}: |
from openerp.tools import float_compare
amount_total = self.browse(cr, uid, ref('pos_order_pos1')).amount_total
assert float_compare(amount_total, 1752.75, precision_digits=2) == 0, "Invoice not correct"
@@ -1,68 +0,0 @@
-
In order to test the reports on Bank Statement defined in point_of_sale module, I create a bank statement line, confirm it and print the reports
-
I select the period and journal for the bank statement
-
!python {model: account.bank.statement}: |
import time
journal = self._default_journal(cr, uid, {'journal_type': 'bank'})
assert journal, 'Journal has not been selected'
-
I create a bank statement with Opening and Closing balance 0.
-
!record {model: account.bank.statement, id: account_bank_statement_0}:
balance_end_real: 0.0
balance_start: 0.0
date: !eval time.strftime('%Y-%m-%d')
journal_id: bank_journal
-
I create bank statement line
-
!record {model: account.bank.statement.line, id: account_bank_statement_line_0}:
amount: 1000.0
partner_id: base.res_partner_4
statement_id: account_bank_statement_0
name: 'EXT001'
-
I modify the bank statement and set the Closing Balance.
-
!record {model: account.bank.statement, id: account_bank_statement_0}:
balance_end_real: 1000.0
-
I reconcile the bank statement.
-
!python {model: account.bank.statement.line}: |
new_aml_dicts = [{
'account_id': self.pool.get('res.partner').browse(cr, uid, ref('base.res_partner_4')).property_account_receivable_id.id,
'name': "EXT001",
'credit': 1000.0,
'debit': 0.0,
}]
self.process_reconciliations(cr, uid, [ref("account_bank_statement_line_0")], [{'new_aml_dicts': new_aml_dicts}])
-
I confirm the bank statement using Confirm button
-
!python {model: account.bank.statement}: |
self.button_confirm_bank(cr, uid, [ref("account_bank_statement_0")])
-
Print the account statement report
-
!python {model: account.bank.statement}: |
import os
import openerp.report
from openerp import tools
data, format = openerp.report.render_report(cr, uid, [ref('account_bank_statement_0')], 'point_of_sale.report_statement', {}, {})
if tools.config['test_report_directory']:
file(os.path.join(tools.config['test_report_directory'], 'point_of_sale-statement.'+format), 'wb+').write(data)
-
Printing the user s product report
-
!python {model: account.bank.statement}: |
import os
import openerp.report
from openerp import tools
data, format = openerp.report.render_report(cr, uid, [ref('account_bank_statement_0')], 'point_of_sale.report_usersproduct', {}, {})
if tools.config['test_report_directory']:
file(os.path.join(tools.config['test_report_directory'], 'point_of_sale-usersproduct.'+format), 'wb+').write(data)
@@ -1,43 +0,0 @@
-
In order to test the PDF reports defined on a Point Of Sale, we will print a POS Invoice Report
-
!python {model: pos.order}: |
import os
import openerp.report
from openerp import tools
data, format = openerp.report.render_report(cr, uid, [ref('pos_order_pos1')], 'point_of_sale.report_invoice', {}, {})
if tools.config['test_report_directory']:
file(os.path.join(tools.config['test_report_directory'], 'point_of_sale-invoice_report.'+format), 'wb+').write(data)
-
In order to test the PDF reports defined on a Point Of Sale, we will print a POS Lines Report
-
!python {model: pos.order}: |
import os
import openerp.report
from openerp import tools
data, format = openerp.report.render_report(cr, uid, [ref('pos_order_pos1')], 'point_of_sale.report_saleslines', {}, {})
if tools.config['test_report_directory']:
file(os.path.join(tools.config['test_report_directory'], 'point_of_sale-lines_report.'+format), 'wb+').write(data)
-
Print the POS Details Report through the wizard
-
!python {model: ir.ui.menu}: |
import time
ctx={}
ctx.update({'model': 'ir.ui.menu','active_ids': []})
data_dict = {'date_start': time.strftime('%Y-%m-%d'), 'date_end': time.strftime('%Y-%m-%d'), 'user_ids': [(6,0,[ref('base.user_root')])]}
from openerp.tools import test_reports
test_reports.try_report_action(cr, uid, 'action_report_pos_details',wiz_data=data_dict, context=ctx, our_module='point_of_sale')
-
In order to test the PDF reports defined on a Point of Sale, we will print a POS Payment Report
-
!python {model: pos.order}: |
import os, time
import openerp.report
from openerp import tools
data, format = openerp.report.render_report(cr, uid, [ref('pos_order_pos0')], 'point_of_sale.report_payment', {}, {})
if tools.config['test_report_directory']:
file(os.path.join(tools.config['test_report_directory'], 'point_of_sale-payment_report.'+format), 'wb+').write(data)
@@ -1,13 +0,0 @@
<?xml version="1.0"?>
<openerp>
<data>
<record id="bank_journal" model="account.journal">
<field eval="True" name="journal_user"/>
</record>
<record model="pos.config" id="pos_config_main">
<field name="journal_ids" eval="[(6, 0, [ref('bank_journal')])]" />
</record>
</data>
</openerp>
+4
View File
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import test_point_of_sale_flow
+60
View File
@@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
from odoo.tests import common
class TestPointOfSaleCommon(common.TransactionCase):
def setUp(self):
super(TestPointOfSaleCommon, self).setUp()
self.AccountBankStatement = self.env['account.bank.statement']
self.AccountBankStatementLine = self.env['account.bank.statement.line']
self.PosMakePayment = self.env['pos.make.payment']
self.PosOrder = self.env['pos.order']
self.company_id = self.ref('base.main_company')
self.product3 = self.env.ref('product.product_product_3')
self.product4 = self.env.ref('product.product_product_4')
self.partner1 = self.env.ref('base.res_partner_1')
self.partner4 = self.env.ref('base.res_partner_4')
self.pos_config = self.env.ref('point_of_sale.pos_config_main')
# create a new session
self.pos_order_session0 = self.env['pos.session'].create({
'user_id': 1,
'config_id': self.pos_config.id
})
# create a VAT tax of 10%, included in the public price
Tax = self.env['account.tax']
account_tax_10_incl = Tax.create({
'name': 'VAT 10 perc Incl',
'amount_type': 'percent',
'amount': 10.0,
'price_include': 1
})
# assign this 10 percent tax on the [PCSC234] PC Assemble SC234 product
# as a sale tax
self.product3.taxes_id = [(6, 0, [account_tax_10_incl.id])]
# create a VAT tax of 5%, which is added to the public price
account_tax_05_incl = Tax.create({
'name': 'VAT 5 perc Incl',
'amount_type': 'percent',
'amount': 5.0,
'price_include': 0
})
# create a second VAT tax of 5% but this time for a child company, to
# ensure that only product taxes of the current session's company are considered
#(this tax should be ignore when computing order's taxes in following tests)
account_tax_05_incl_chicago = Tax.create({
'name': 'VAT 05 perc Excl (US)',
'amount_type': 'percent',
'amount': 5.0,
'price_include': 0,
'company_id': self.ref('stock.res_company_1')
})
# I assign those 5 percent taxes on the PCSC349 product as a sale taxes
self.product4.write(
{'taxes_id': [(6, 0, [account_tax_05_incl.id, account_tax_05_incl_chicago.id])]})
@@ -0,0 +1,217 @@
# -*- coding: utf-8 -*-
import datetime
import time
import os
import odoo
from odoo import tools, report as odoo_report
from odoo.tools import float_compare, test_reports
from odoo.addons.point_of_sale.tests.common import TestPointOfSaleCommon
@odoo.tests.common.at_install(False)
@odoo.tests.common.post_install(True)
class TestPointOfSaleFlow(TestPointOfSaleCommon):
def test_register_open(self):
"""
In order to test the Point of Sale module, I will open all cash registers through the wizard
"""
# open all statements/cash registers
self.env['pos.open.statement'].create({}).open_statement()
def test_order_to_payment(self):
"""
In order to test the Point of Sale in module, I will do a full flow from the sale to the payment and invoicing.
I will use two products, one with price including a 10% tax, the other one with 5% tax excluded from the price.
"""
# I click on create a new session button
self.pos_config.open_session_cb()
# I create a PoS order with 2 units of PCSC234 at 450 EUR (Tax Incl)
# and 3 units of PCSC349 at 300 EUR. (Tax Excl)
self.pos_order_pos0 = self.PosOrder.create({
'company_id': self.company_id,
'pricelist_id': self.partner1.property_product_pricelist.id,
'partner_id': self.partner1.id,
'lines': [(0, 0, {
'name': "OL/0001",
'product_id': self.product3.id,
'price_unit': 450,
'discount': 0.0,
'qty': 2.0,
'tax_ids': [(6, 0, self.product3.taxes_id.ids)],
}), (0, 0, {
'name': "OL/0002",
'product_id': self.product4.id,
'price_unit': 300,
'discount': 0.0,
'qty': 3.0,
'tax_ids': [(6, 0, self.product4.taxes_id.ids)],
})]
})
# I check that the total of the order is equal to 450*2 + 300*3*1.05
# and the tax of the order is equal to 900 -(450 * 2 / 1.1) +
# 300*0.05*3
self.assertLess(
abs(self.pos_order_pos0.amount_total - (450 * 2 + 300 * 3 * 1.05)),
0.01, 'The order has a wrong amount, tax included.')
self.assertLess(
abs(self.pos_order_pos0.amount_tax - (900 - (450 * 2 / 1.1) + 300 * 0.05 * 3)),
0.01, 'The order has a wrong tax amount.')
# I want to add a global discount of 5 percent using the wizard
self.pos_discount_0 = self.env['pos.discount'].create({'discount': 5.0})
context = {"active_model": "pos.order", "active_ids": [self.pos_order_pos0.id], "active_id": self.pos_order_pos0.id}
# I click the apply button to set the discount on all lines
self.pos_discount_0.with_context(context).apply_discount()
# I check that the total of the order is now equal to (450*2 +
# 300*3*1.05)*0.95
self.assertLess(
abs(self.pos_order_pos0.amount_total - (450 * 2 + 300 * 3 * 1.05) * 0.95),
0.01, 'The order has a wrong total including tax and discounts')
# I click on the "Make Payment" wizard to pay the PoS order with a
# partial amount of 100.0 EUR
context_make_payment = {"active_ids": [self.pos_order_pos0.id], "active_id": self.pos_order_pos0.id}
self.pos_make_payment_0 = self.PosMakePayment.with_context(context_make_payment).create({
'amount': 100.0
})
# I click on the validate button to register the payment.
context_payment = {'active_id': self.pos_order_pos0.id}
self.pos_make_payment_0.with_context(context_payment).check()
# I check that the order is not marked as paid yet
self.assertEqual(self.pos_order_pos0.state, 'draft', 'Order should be in draft state.')
# On the second payment proposition, I check that it proposes me the
# remaining balance which is 1790.0 EUR
defs = self.pos_make_payment_0.with_context({'active_id': self.pos_order_pos0.id}).default_get(['amount'])
self.assertLess(
abs(defs['amount'] - ((450 * 2 + 300 * 3 * 1.05) * 0.95 - 100.0)), 0.01, "The remaining balance is incorrect.")
#'I pay the remaining balance.
context_make_payment = {
"active_ids": [self.pos_order_pos0.id], "active_id": self.pos_order_pos0.id}
self.pos_make_payment_1 = self.PosMakePayment.with_context(context_make_payment).create({
'amount': (450 * 2 + 300 * 3 * 1.05) * 0.95 - 100.0
})
# I click on the validate button to register the payment.
self.pos_make_payment_1.with_context(context_make_payment).check()
# I check that the order is marked as paid
self.assertEqual(self.pos_order_pos0.state, 'paid', 'Order should be in paid state.')
# I generate the journal entries
self.pos_order_pos0._create_account_move_line()
# I test that the generated journal entry is attached to the PoS order
self.assertTrue(self.pos_order_pos0.account_move, "Journal entry has not been attached to Pos order.")
def test_order_to_invoice(self):
#I create a new PoS order with 2 units of PC1 at 450 EUR (Tax Incl) and 3 units of PCSC349 at 300 EUR. (Tax Excl)
self.pos_order_pos1 = self.PosOrder.create({
'company_id': self.company_id,
'partner_id': self.partner1.id,
'pricelist_id': self.partner1.property_product_pricelist.id,
'lines': [(0, 0, {
'name': "OL/0001",
'product_id': self.product3.id,
'price_unit': 450,
'discount': 5.0,
'qty': 2.0,
'tax_ids': [(6, 0, self.product3.taxes_id.ids)],
}), (0, 0, {
'name': "OL/0002",
'product_id': self.product4.id,
'price_unit': 300,
'discount': 5.0,
'qty': 3.0,
'tax_ids': [(6, 0, self.product4.taxes_id.ids)],
})]
})
# I click on the "Make Payment" wizard to pay the PoS order
context_make_payment = {"active_ids": [self.pos_order_pos1.id], "active_id": self.pos_order_pos1.id}
self.pos_make_payment = self.PosMakePayment.with_context(context_make_payment).create({
'amount': (450 * 2 + 300 * 3 * 1.05) * 0.95,
})
# I click on the validate button to register the payment.
context_payment = {'active_id': self.pos_order_pos1.id}
self.pos_make_payment.with_context(context_payment).check()
# I check that the order is marked as paid and there is no invoice
# attached to it
self.assertEqual(self.pos_order_pos1.state, 'paid', "Order should be in paid state.")
self.assertFalse(self.pos_order_pos1.invoice_id, 'Invoice should not be attached to order.')
# I set the order as invoiced
self.pos_order_pos1.signal_workflow('invoice')
# I generate an invoice from the order
self.invoice = self.pos_order_pos1.action_invoice()
# I test that the total of the attached invoice is correct
self.amount_total = self.pos_order_pos1.amount_total
self.assertEqual(
float_compare(self.amount_total, 1752.75, precision_digits=2), 0, "Invoice not correct")
"""In order to test the reports on Bank Statement defined in point_of_sale module, I create a bank statement line, confirm it and print the reports"""
# I select the period and journal for the bank statement
context_journal = {'journal_type': 'bank'}
self.assertTrue(self.AccountBankStatement.with_context(
context_journal)._default_journal(), 'Journal has not been selected')
journal = self.env['account.journal'].create({
'name': 'Bank Test',
'code': 'BNKT',
'type': 'bank',
'company_id': self.company_id,
})
# I create a bank statement with Opening and Closing balance 0.
account_statement = self.AccountBankStatement.create({
'balance_start': 0.0,
'balance_end_real': 0.0,
'date': time.strftime('%Y-%m-%d'),
'journal_id': journal.id,
'company_id': self.company_id,
})
# I create bank statement line
account_statement_line = self.AccountBankStatementLine.create({
'amount': 1000,
'partner_id': self.partner4.id,
'statement_id': account_statement.id,
'name': 'EXT001'
})
# I modify the bank statement and set the Closing Balance.
account_statement.write({
'balance_end_real': 1000.0,
})
# I reconcile the bank statement.
new_aml_dicts = [{
'account_id': self.partner4.property_account_receivable_id.id,
'name': "EXT001",
'credit': 1000.0,
'debit': 0.0,
}]
self.AccountBankStatementLine._model.process_reconciliations(
self.env.cr, self.env.uid, [account_statement_line.id], [{'new_aml_dicts': new_aml_dicts}])
# I confirm the bank statement using Confirm button
self.AccountBankStatement.button_confirm_bank()
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<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 id="menu_action_account_journal_form_open"
action="action_account_journal_form"
parent="menu_point_config_product"
sequence="20"/>
</odoo>
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<openerp>
<data>
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<report
id="action_report_account_statement"
model="account.bank.statement"
@@ -17,5 +16,4 @@
file="point_of_sale.report_usersproduct"
report_type="qweb-pdf"
/>
</data>
</openerp>
</odoo>
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record model="ir.ui.view" id="view_account_journal_pos_user_form">
<field name="name">POS Journal</field>
<odoo>
<record id="view_account_journal_pos_user_form" model="ir.ui.view">
<field name="name">account.journal.pos.user.form.inherit</field>
<field name="model">account.journal</field>
<field name="inherit_id" ref="account.view_account_journal_form"/>
<field name="arch" type="xml">
@@ -16,6 +15,4 @@
</xpath>
</field>
</record>
</data>
</openerp>
</odoo>
+2 -4
View File
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<odoo>
<template id="assets" name="point_of_sale assets">
<link rel="stylesheet" href="/web/static/src/fonts/lato/stylesheet.css" />
<link rel="stylesheet" href="/web/static/lib/fontawesome/css/font-awesome.css" />
@@ -21,5 +20,4 @@
<script type="text/javascript" src="/point_of_sale/static/src/js/screens.js"></script>
<script type="text/javascript" src="/point_of_sale/static/src/js/tests.js"></script>
</template>
</data>
</openerp>
</odoo>
@@ -1,4 +1,4 @@
<?xml version="1.0"?>
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="action_pos_session_filtered" model="ir.actions.act_window">
@@ -105,7 +105,6 @@
<div class="o_kanban_card_manage_title">
<span>View</span>
</div>
<div>
<a name="%(action_pos_session_filtered)d" type="action">Sessions</a>
</div>
@@ -117,7 +116,6 @@
<div class="o_kanban_card_manage_title">
<span>Reports</span>
</div>
<div>
<a name="%(action_report_pos_order_all_filtered)d" type="action">Orders</a>
</div>
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<report
id="report_user_label"
model="res.users"
string="User Labels"
report_type="qweb-pdf"
name="point_of_sale.report_userlabel"
file="point_of_sale.report_userlabel"
/>
<!-- used from POS UI, no need to be in print menu -->
<record id="pos_invoice_report" model="ir.actions.report.xml">
<field name="name">Invoice</field>
<field name="model">pos.order</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">point_of_sale.report_invoice</field>
</record>
</odoo>
@@ -1,18 +1,12 @@
<?xml version="1.0"?>
<openerp>
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
#
# Sequences for pos.order
#
<record model="ir.sequence" id="seq_pos_session">
<record id="seq_pos_session" model="ir.sequence">
<field name="name">POS Session</field>
<field name="code">pos.session</field>
<field name="prefix">POS/%(year)s/%(month)s/%(day)s/</field>
<field name="padding">2</field>
<field name="company_id" eval="False" />
</record>
</data>
</openerp>
</odoo>
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Top menu item -->
<menuitem
id="menu_point_root"
name="Point of Sale"
groups="group_pos_manager,group_pos_user"
icon="fa-suitcase"
web_icon="point_of_sale,static/description/icon.png"
sequence="20"/>
<menuitem id="menu_point_config_product" name="Configuration" parent="menu_point_root"
sequence="100" groups="group_pos_manager,base.group_configuration"/>
<menuitem id="menu_point_of_sale" name="Orders" parent="menu_point_root" sequence="10"/>
<!-- Miscellaneous Operations/Reporting -->
<menuitem id="menu_point_rep" name="Reports" parent="menu_point_root" sequence="99" groups="group_pos_manager"/>
</odoo>
@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<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="display_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, the layout of the
touchscreen interface will automatically. We suggest not to put
a photo on categories for small (1024x768) screens.
</p>
</field>
</record>
<menuitem id="menu_product_pos_category" action="product_pos_category_action" parent="menu_point_config_product" sequence="20" />
</odoo>
@@ -0,0 +1,139 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_pos_config_form" model="ir.ui.view">
<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" />
<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"/>
<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 id="view_pos_config_tree" model="ir.ui.view">
<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 id="view_pos_config_search" model="ir.ui.view">
<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 id="action_pos_config_pos" model="ir.actions.act_window">
<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
id="menu_pos_config_pos"
parent="menu_point_config_product"
action="action_pos_config_pos"
groups="group_pos_manager"/>
<record id="action_pos_config_kanban" model="ir.actions.act_window">
<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 id="menu_pos_barcode_nomenclature_all" parent="menu_point_config_product" action="barcodes.action_barcode_nomenclature_form" sequence="30"/>
<menuitem id="menu_pos_dashboard" action="action_pos_config_kanban" parent="menu_point_root" name="Dashboard" sequence="1"/>
</odoo>
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<odoo>
<record id="view_report_pos_order_pivot" model="ir.ui.view">
<field name="name">report.pos.order.pivot</field>
<field name="model">report.pos.order</field>
@@ -53,6 +52,13 @@
</field>
</record>
<record id="filter_orders_per_session" model="ir.filters">
<field name="name">Per session</field>
<field name="model_id">report.pos.order</field>
<field name="user_id" eval="False"/>
<field name="context">{'group_by': ['date', 'session_id'], 'measures': ['price_total']}</field>
</record>
<record id="action_report_pos_order_all" model="ir.actions.act_window">
<field name="name">Orders Analysis</field>
<field name="res_model">report.pos.order</field>
@@ -62,6 +68,5 @@
<field name="context">{'group_by_no_leaf':1,'group_by':[]}</field>
</record>
<menuitem name="Orders" action="action_report_pos_order_all" id="menu_report_pos_order_all" parent="menu_point_rep" sequence="3"/>
</data>
</openerp>
<menuitem id="menu_report_pos_order_all" name="Orders" action="action_report_pos_order_all" parent="menu_point_rep" sequence="3"/>
</odoo>
@@ -0,0 +1,253 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_pos_pos_form" model="ir.ui.view">
<field name="name">pos.order.form</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" 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"/>
<field name="qty"/>
<field name="price_unit" widget="monetary"/>
<field name="discount" 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"/>
<field name="qty"/>
<field name="discount" widget="monetary"/>
<field name="price_unit" 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 id="action_pos_pos_form" model="ir.actions.act_window">
<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 id="view_pos_order_tree" model="ir.ui.view">
<field name="name">pos.order.tree</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_search" model="ir.ui.view">
<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 id="menu_point_ofsale" parent="menu_point_of_sale" action="action_pos_pos_form" sequence="1" groups="group_pos_manager,group_pos_user"/>
<record id="view_pos_order_line" model="ir.ui.view">
<field name="name">pos.order.line.tree</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 id="view_pos_order_line_form" model="ir.ui.view">
<field name="name">pos.order.line.form</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 id="action_pos_order_line" model="ir.actions.act_window">
<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 id="action_pos_order_line_form" model="ir.actions.act_window">
<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 id="action_pos_order_line_day" model="ir.actions.act_window">
<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 id="view_pos_order_tree_all_sales_lines" model="ir.ui.view">
<field name="name">pos.order.line.all.sales.tree</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 id="action_pos_all_sales_lines" model="ir.actions.act_window">
<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>
<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>
</odoo>
@@ -0,0 +1,191 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<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 id="view_pos_session_form" model="ir.ui.view">
<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 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 id="view_pos_session_tree" model="ir.ui.view">
<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 id="view_pos_session_search" model="ir.ui.view">
<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 id="action_pos_session" model="ir.actions.act_window">
<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
id="menu_pos_session_all"
parent="menu_point_of_sale"
action="action_pos_session"
sequence="2"
groups="group_pos_manager"/>
</odoo>
@@ -1,8 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<odoo>
<template id="index" name="POS Index">&lt;!DOCTYPE html&gt;
<html>
<head>
@@ -65,6 +62,4 @@
</body>
</html>
</template>
</data>
</openerp>
</odoo>
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<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
id="menu_pos_products"
action="product_template_action"
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>
</odoo>
@@ -1,140 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<template id="report_detailsofsales">
<t t-call="report.html_container">
<t t-call="report.internal_layout">
<div class="page">
<h2>Details of Sales</h2>
<div class="row mt32">
<div class="col-xs-3">
<strong>Company</strong>:<br/>
<span t-esc="res_company.name"/>
</div>
<div class="col-xs-3">
<strong>Users</strong>:<br/>
<span t-esc="get_user_names(data['form']['user_ids']) or 'All'"/>
</div>
<div class="col-xs-3">
<strong>Print Date</strong>:<br/>
<span t-esc="formatLang(time.strftime('%Y-%m-%d'),date=True)"/>
</div>
</div>
<div class="row mb32">
<div class="col-xs-3">
<strong>Start Period</strong>:<br/>
<span t-esc="formatLang(data['form']['date_start'],date=True)"/>
</div>
<div class="col-xs-3">
<strong>End Period</strong>:<br/>
<span t-esc="formatLang(data['form']['date_end'],date=True)"/>
</div>
</div>
<table class="table table-condensed">
<thead>
<th><strong>Date</strong></th>
<th><strong>Order</strong></th>
<th><strong>Product</strong></th>
<th class="text-right"><strong>Price</strong></th>
<th class="text-center"><strong>Qty</strong></th>
<th class="text-center"><strong>Disc(%)</strong></th>
<th class="text-center"><strong>Invoiced</strong></th>
</thead>
<tbody>
<tr t-foreach="pos_sales_details(data['form'])" t-as="line_ids">
<td>
<span t-esc="formatLang(line_ids['date_order'], date_time=True)"/>
</td>
<td>
<span t-esc="line_ids['pos_name']"/>
</td>
<td>
[<t t-if="line_ids['code']">
<span t-esc="line_ids['code']"/>
</t>]
<span t-esc="line_ids['name']"/>
</td>
<td class="text-right">
<span t-esc="formatLang(line_ids['price_unit'], dp='Sale Price', currency_obj=res_company.currency_id)"/>
</td>
<td class="text-center">
<span t-esc="formatLang(line_ids['qty'])"/>
<span t-esc="line_ids['uom']"/>
</td>
<td class="text-center">
<span t-esc="formatLang(line_ids['discount'], dp='Sale Price')"/>
</td>
<td class="text-center">
<t t-if="line_ids['invoice_id']">
<span t-esc="getinvoice(line_ids['invoice_id'])"/>
</t>
</td>
</tr>
</tbody>
</table>
<table class="table table-condensed">
<tr t-if="gettaxamount(data['form'])"><td colspan="2"><strong>Taxes</strong></td></tr>
<tr t-foreach="gettaxamount(data['form'])" t-as="tax">
<td><span t-esc="tax['name']"/></td>
<td class="text-right">
<strong t-esc="formatLang(tax['amount'], currency_obj = res_company.currency_id)"/>
</td>
</tr>
<tr>
<td colspan="2"><strong>Payment</strong></td>
</tr>
<tr t-foreach="getpayments(data['form'])" t-as="payment">
<td><span t-esc="payment['name']"/></td>
<td class="text-right">
<strong t-esc="formatLang(payment['sum'], dp='Account', currency_obj=res_company.currency_id)"/>
</td>
</tr>
<tr>
<td colspan="2"><strong>Summary</strong></td>
</tr>
<tr>
<td><strong>Sales total(Revenue)</strong></td>
<td class="text-right">
<strong t-esc="formatLang(getsalestotal2(), dp='Sale Price', currency_obj=res_company.currency_id)"/>
</td>
</tr>
<tr>
<td><strong>Qty of product</strong></td>
<td class="text-right">
<strong t-esc="formatLang(getqtytotal2())"/>
</td>
</tr>
<tr>
<td><strong>Total invoiced</strong></td>
<td class="text-right">
<strong t-esc="formatLang(getsuminvoice2(data['form']), dp='Sale Price', currency_obj=res_company.currency_id)"/>
</td>
</tr>
<tr>
<td><strong>Total discount</strong></td>
<td class="text-right">
<strong t-esc="formatLang(getsumdisc(data['form']), dp='Sale Price', currency_obj=res_company.currency_id)"/>
</td>
</tr>
<tr>
<td><strong>Total paid</strong></td>
<td class="text-right">
<strong t-esc="formatLang(getpaidtotal2(), dp='Sale Price', currency_obj=res_company.currency_id)"/>
</td>
</tr>
<tr>
<td><strong>Total of the day</strong></td>
<td class="text-right">
<strong t-esc="formatLang(gettotaloftheday(data['form']), dp='Sale Price', currency_obj = res_company.currency_id)"/>
</td>
</tr>
</table>
</div>
</t>
</t>
</template>
</data>
</openerp>
@@ -1,91 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<template id="report_payment">
<t t-call="report.html_container">
<t t-foreach="docs" t-as="o">
<t t-call="report.internal_layout">
<div class="page">
<h2>Today's Payments</h2>
<div class="row mt32 mb32">
<div class="col-xs-3">
<strong>Company</strong>:
<br/>
<span t-field="res_company.name"/>
</div>
<div class="col-xs-3">
<strong>Print date</strong>:
<br/>
<t t-esc="formatLang(time.strftime('%Y-%m-%d'), date='True')"/>
</div>
</div>
<t t-set="pos_payment" t-value="pos_payment(o)"/>
<table class="table table-condensed" t-if="pos_payment">
<thead>
<tr>
<td>
<strong>Product</strong>
</td>
<td class="text-center">
<strong>Qty</strong>
</td>
<td class="text-center">
<strong>Disc.(%)</strong>
</td>
<td class="text-right">
<strong>Unit Price</strong>
</td>
<td class="text-right">
<strong>Total</strong>
</td>
</tr>
</thead>
<tbody>
<tr t-foreach="pos_payment" t-as="line_ids">
<td>
<t t-if="line_ids['code']">
<span t-esc="line_ids['code']"/>
</t>
<t t-if="line_ids['name']">
<span t-esc="line_ids['name']"/>
</t>
</td>
<td class="text-right">
<span t-esc="formatLang(line_ids['qty'])"/>
<t t-if="line_ids['uom']">
<span t-esc="line_ids['uom']"/>
</t>
</td>
<td class="text-right">
<span t-esc="formatLang(line_ids['discount'])"/>
</td>
<td class="text-right">
<span t-esc="formatLang(line_ids['price_unit'])"/>
</td>
<td class="text-right">
<span t-esc="formatLang(line_ids['total'], currency_obj=res_company.currency_id)"/>
</td>
</tr>
</tbody>
</table>
<div class="row" t-if="pos_payment_total(o)">
<div class="col-xs-4 pull-right">
<table class="table table-condensed">
<tr class="border-black">
<td>
<strong>Net Total</strong>
</td>
<td class="text-right">
<span t-esc="formatLang(pos_payment_total(o), currency_obj=res_company.currency_id)"/>
</td>
</tr>
</table>
</div>
</div>
</div>
</t>
</t>
</t>
</template>
</data>
</openerp>
@@ -1,98 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<template id="report_saleslines">
<t t-call="report.html_container">
<t t-foreach="docs" t-as="o">
<t t-call="report.internal_layout">
<div class="page">
<h2>POS Lines</h2>
<div class="row mt32 mb32">
<div class="col-xs-3">
<strong>Company</strong>:<br/>
<span t-field="res_company.name"/>
</div>
<div class="col-xs-3">
<strong>Print date</strong>:<br/>
<t t-esc="formatLang(time.strftime('%Y-%m-%d'), date=True)"/>
</div>
<div class="col-xs-3">
<strong>No. Of Articles</strong>:<br/>
<t t-esc="total_quantity(o)"/>
</div>
</div>
<table class="table table-condensed">
<thead>
<tr>
<th><strong>Description</strong></th>
<th><strong>Tax</strong></th>
<th class="text-right"><strong>Quantity</strong></th>
<th class="text-right"><strong>Unit Price</strong></th>
<th class="text-right" groups="sale.group_discount_per_so_line"><strong>Disc. (%)</strong></th>
<th class="text-right"><strong>Price</strong></th>
</tr>
</thead>
<tbody>
<tr t-foreach="o.lines" t-as="l">
<td>
<t t-if="l.product_id and l.product_id.code">
[<span t-field="l.product_id.code"/>]
</t>
<span t-field="l.product_id.name"/>
</td>
<td>
<t t-esc="taxes(l)"/>
</td>
<td class="text-right">
<span t-field="l.qty"/>
<span t-field="l.product_id.uom_id.name" groups="product.group_uom"/>
</td>
<td class="text-right">
<span t-field="l.price_unit"/>
</td>
<td class="text-right" groups="sale.group_discount_per_so_line">
<span t-field="l.discount"/>
</td>
<td class="text-right">
<span t-field="l.price_subtotal" t-field-options='{"widget": "monetary", "display_currency": "o.pricelist_id.currency_id"}'/>
</td>
</tr>
</tbody>
</table>
<div class="row">
<div class="col-xs-4 pull-right">
<table class="table table-condensed">
<tr class="border-black">
<td><strong>Total Without Taxes</strong></td>
<td class="text-right">
<span t-esc="o.amount_total - o.amount_tax"
t-esc-options='{"widget": "monetary", "display_currency": "o.pricelist_id.currency_id"}'/>
</td>
</tr>
<tr>
<td>Taxes</td>
<td class="text-right">
<span t-field="o.amount_tax"
t-field-options='{"widget": "monetary", "display_currency": "o.pricelist_id.currency_id"}'/>
</td>
</tr>
<tr class="border-black">
<td><strong>Total</strong></td>
<td class="text-right">
<span t-field="o.amount_total"
t-field-options='{"widget": "monetary", "display_currency": "o.pricelist_id.currency_id"}'/>
</td>
</tr>
</table>
</div>
</div>
</div>
</t>
</t>
</t>
</template>
</data>
</openerp>
@@ -1,124 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<template id="report_sessionsummary">
<t t-call="report.html_container">
<t t-foreach="docs" t-as="o">
<t t-call="report.internal_layout">
<div class="page">
<h2>Session Summary: <span t-field="o.name"/></h2>
<div class="row mt32 mb32">
<div class="col-xs-3">
<strong>Responsible</strong>:<br/>
<span t-field="o.user_id"/>
</div>
<div class="col-xs-3">
<strong>Point of Sale</strong>:<br/>
<span t-field="o.config_id"/>
</div>
<div class="col-xs-3">
<strong>Opening Date</strong>:<br/>
<span t-field="o.start_at"/>
</div>
<div class="col-xs-3">
<strong>Closing Date</strong>:<br/>
<span t-field="o.stop_at"/>
</div>
</div>
<h4>Statement Summary</h4>
<table class="table table-condensed mb32">
<thead>
<tr>
<th>
<strong>Reference</strong>
</th>
<th>
<strong>Journal</strong>
</th>
<th class="text-right">
<strong>Starting Balance</strong>
</th>
<th class="text-right">
<strong>Total Transactions</strong>
</th>
<th class="text-right">
<strong>Ending Balance</strong>
</th>
<th class="text-right">
<strong>Difference</strong>
</th>
<th class="text-right" groups="base.group_multi_currency">
<strong>Currency</strong>
</th>
<th class="text-right">
<strong>Status</strong>
</th>
</tr>
</thead>
<tbody>
<tr t-foreach="o.statement_ids" t-as="statement">
<td><span t-field="statement.name"/></td>
<td><span t-field="statement.journal_id"/></td>
<td class="text-right">
<span t-field="statement.balance_start"
t-field-options='{"widget": "monetary", "display_currency": "statement.currency_id"}'/>
</td>
<td class="text-right">
<span t-field="statement.total_entry_encoding"
t-field-options='{"widget": "monetary", "display_currency": "statement.currency_id"}'/>
</td>
<td class="text-right">
<span t-field="statement.balance_end_real"
t-field-options='{"widget": "monetary", "display_currency": "statement.currency_id"}'/>
</td>
<td class="text-right">
<span t-field="statement.difference"
t-field-options='{"widget": "monetary", "display_currency": "statement.currency_id"}'/>
</td>
<td class="text-right" groups="base.group_multi_currency">
<span t-field="statement.currency_id"/>
</td>
<td class="text-right"><span t-field="statement.state"/></td>
</tr>
</tbody>
</table>
<t t-foreach="o.statement_ids" t-as="statement">
<h4 t-if="statement.line_ids">Statement Details: <span t-esc="statement.name"/></h4>
<table class="table table-condensed" t-if="statement.line_ids">
<thead>
<tr>
<th>Date</th>
<th>Description</th>
<th>Reference</th>
<th>Partner</th>
<th>Account</th>
<th class="text-right">Amount</th>
</tr>
</thead>
<tbody>
<tr t-foreach="statement.line_ids" t-as="line">
<td><span t-field="line.date"/></td>
<td><span t-field="line.name"/></td>
<td><span t-field="line.ref"/></td>
<td><span t-field="line.partner_id"/></td>
<td><span t-field="line.account_id"/></td>
<td class="text-right">
<span t-field="line.amount"
t-field-options='{"widget": "monetary", "display_currency": "statement.currency_id"}'/>
</td>
</tr>
</tbody>
</table>
</t>
</div>
</t>
</t>
</t>
</template>
</data>
</openerp>
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<odoo>
<template id="report_userlabel">
<t t-call="report.html_container">
<div class="page">
@@ -25,5 +24,4 @@
</div>
</t>
</template>
</data>
</openerp>
</odoo>
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<odoo>
<record id="view_sale_config_settings_form_pos" model="ir.ui.view">
<field name="name">pos.config.settings.form</field>
<field name="model">pos.config.settings</field>
@@ -33,7 +32,6 @@
<field name="target">inline</field>
</record>
<menuitem id="menu_pos_global_settings" name="Settings"
<menuitem id="menu_pos_global_settings" name="Settings"
parent="menu_point_config_product" sequence="7" action="action_pos_configuration"/>
</data>
</openerp>
</odoo>
@@ -1,7 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<odoo>
<record id="view_partner_property_form" model="ir.ui.view">
<field name="name">res.partner.pos.form.inherit</field>
<field name="model">res.partner</field>
@@ -14,6 +12,4 @@
</xpath>
</field>
</record>
</data>
</openerp>
</odoo>
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<odoo>
<record id="res_users_form_view" model="ir.ui.view">
<field name="name">res.users.form.view</field>
<field name="model">res.users</field>
@@ -16,5 +15,4 @@
</notebook>
</field>
</record>
</data>
</openerp>
</odoo>
-2
View File
@@ -2,8 +2,6 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import pos_box
import pos_confirm
import pos_details
import pos_discount
import pos_open_statement
import pos_payment
+26 -40
View File
@@ -1,62 +1,48 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp.osv import osv
from openerp.tools.translate import _
from openerp.exceptions import UserError
from odoo import api, _
from odoo.exceptions import UserError
from odoo.addons.account.wizard.pos_box import CashBox
from openerp.addons.account.wizard.pos_box import CashBox
class PosBox(CashBox):
_register = False
def run(self, cr, uid, ids, context=None):
if not context:
context = dict()
active_model = context.get('active_model', False) or False
active_ids = context.get('active_ids', []) or []
@api.multi
def run(self):
active_model = self.env.context.get('active_model', False)
active_ids = self.env.context.get('active_ids', [])
if active_model == 'pos.session':
records = self.pool[active_model].browse(cr, uid, active_ids, context=context)
bank_statements = [record.cash_register_id for record in records if record.cash_register_id]
bank_statements = [session.cash_register_id for session in self.env[active_model].browse(active_ids) if session.cash_register_id]
if not bank_statements:
raise UserError(_("There is no cash register for this PoS Session"))
return self._run(cr, uid, ids, bank_statements, context=context)
return self._run(bank_statements)
else:
return super(PosBox, self).run(cr, uid, ids, context=context)
return super(PosBox, self).run()
class PosBoxIn(PosBox):
_inherit = 'cash.box.in'
def _compute_values_for_statement_line(self, cr, uid, box, record, context=None):
if context is None:
context = {}
values = super(PosBoxIn, self)._compute_values_for_statement_line(cr, uid, box, record, context=context)
active_model = context.get('active_model', False) or False
active_ids = context.get('active_ids', []) or []
if active_model == 'pos.session':
session = self.pool[active_model].browse(cr, uid, active_ids, context=context)[0]
values['ref'] = session.name
def _compute_values_for_statement_line(self, box, record):
values = super(PosBoxIn, self)._compute_values_for_statement_line(box=box, record=record)
active_model = self.env.context.get('active_model', False)
active_ids = self.env.context.get('active_ids', [])
if active_model == 'pos.session' and active_ids:
values['ref'] = self.env[active_model].browse(active_ids)[0].name
return values
class PosBoxOut(PosBox):
_inherit = 'cash.box.out'
def _compute_values_for_statement_line(self, cr, uid, box, record, context=None):
values = super(PosBoxOut, self)._compute_values_for_statement_line(cr, uid, box, record, context=context)
active_model = context.get('active_model', False) or False
active_ids = context.get('active_ids', []) or []
if active_model == 'pos.session':
session = self.pool[active_model].browse(cr, uid, active_ids, context=context)[0]
values['ref'] = session.name
def _compute_values_for_statement_line(self, box, record):
values = super(PosBoxOut, self)._compute_values_for_statement_line(box=box, record=record)
active_model = self.env.context.get('active_model', False)
active_ids = self.env.context.get('active_ids', [])
if active_model == 'pos.session' and active_ids:
values['ref'] = self.env[active_model].browse(active_ids)[0].name
return values
+6 -8
View File
@@ -1,22 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<odoo>
<act_window
id="action_pos_box_in"
name="Put Money In"
res_model="cash.box.in"
src_model="pos.session"
view_mode="form"
target="new"
key2="client_action_multi"
id="action_pos_box_in" />
key2="client_action_multi"/>
<act_window
id="action_pos_box_out"
name="Take Money Out"
res_model="cash.box.out"
src_model="pos.session"
view_mode="form"
target="new"
key2="client_action_multi"
id="action_pos_box_out" />
</data>
</openerp>
key2="client_action_multi"/>
</odoo>
@@ -1,32 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp.osv import osv
class pos_confirm(osv.osv_memory):
_name = 'pos.confirm'
_description = 'Post POS Journal Entries'
def action_confirm(self, cr, uid, ids, context=None):
order_obj = self.pool.get('pos.order')
ids = order_obj.search(cr, uid, [('state','=','paid')], context=context)
for order in order_obj.browse(cr, uid, ids, context=context):
todo = True
for line in order.statement_ids:
if line.statement_id.state != 'confirm':
todo = False
break
if todo:
order.signal_workflow('done')
# Check if there is orders to reconcile their invoices
ids = order_obj.search(cr, uid, [('state','=','invoiced'),('invoice_id.state','=','open')], context=context)
for order in order_obj.browse(cr, uid, ids, context=context):
invoice = order.invoice_id
data_lines = [x.id for x in invoice.move_id.line_id if x.account_id.id == invoice.account_id.id]
for st in order.statement_ids:
for move in st.move_ids:
data_lines += [x.id for x in move.line_id if x.account_id.id == invoice.account_id.id]
self.pool.get('account.move.line').reconcile(cr, uid, data_lines, context=context)
return {}
@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- Point of Sale Confirm -->
<record id="view_pos_confirm" model="ir.ui.view">
<field name="name">Post Sale Journal Entries</field>
<field name="model">pos.confirm</field>
<field name="arch" type="xml">
<form string="Post All Orders">
<separator string="Generate Journal Entries"/>
<label string="Generate all sale journal entries for non invoiced orders linked to a closed cash register or statement."/>
<footer>
<button name="action_confirm" string="Generate Entries" type="object" class="btn-primary"/>
<button string="Cancel" class="btn-default" special="cancel" />
</footer>
</form>
</field>
</record>
</data>
</openerp>
@@ -1,39 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import time
from openerp.osv import osv, fields
class pos_details(osv.osv_memory):
_name = 'pos.details'
_description = 'Sales Details'
_columns = {
'date_start': fields.date('Date Start', required=True),
'date_end': fields.date('Date End', required=True),
'user_ids': fields.many2many('res.users', 'pos_details_report_user_rel', 'user_id', 'wizard_id', 'Salespeople'),
}
_defaults = {
'date_start': fields.date.context_today,
'date_end': fields.date.context_today,
}
def print_report(self, cr, uid, ids, context=None):
"""
To get the date and print the report
@param self: The object pointer.
@param cr: A database cursor
@param uid: ID of the user currently logged in
@param context: A standard dictionary
@return : retrun report
"""
if context is None:
context = {}
datas = {'ids': context.get('active_ids', [])}
res = self.read(cr, uid, ids, ['date_start', 'date_end', 'user_ids'], context=context)
res = res and res[0] or {}
datas['form'] = res
if res.get('id',False):
datas['ids']=[res['id']]
return self.pool['report'].get_action(cr, uid, [], 'point_of_sale.report_detailsofsales', data=datas, context=context)
@@ -1,36 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- pos.details -->
<record id="view_pos_details" model="ir.ui.view">
<field name="name">POS Details</field>
<field name="model">pos.details</field>
<field name="arch" type="xml">
<form string="POS Details">
<group col="4" string="Dates">
<field name="date_start"/>
<field name="date_end"/>
</group>
<field name="user_ids"/>
<footer>
<button name="print_report" type="object" string="Print Report" class="btn-primary"/>
<button string="Cancel" class="btn-default" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="action_report_pos_details" model="ir.actions.act_window">
<field name="name">Sale Details</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">pos.details</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</data>
</openerp>
+8 -28
View File
@@ -1,37 +1,17 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp.osv import osv, fields
from odoo import api, fields, models
class pos_discount(osv.osv_memory):
class PosDiscount(models.TransientModel):
_name = 'pos.discount'
_description = 'Add a Global Discount'
_columns = {
'discount': fields.float('Discount (%)', required=True, digits=(16,2)),
}
_defaults = {
'discount': 5,
}
def apply_discount(self, cr, uid, ids, context=None):
"""
To give the discount of product and check the.
discount = fields.Float(string='Discount (%)', required=True, digits=(16, 2), default=5)
@param self: The object pointer.
@param cr: A database cursor
@param uid: ID of the user currently logged in
@param context: A standard dictionary
@return : nothing
"""
order_ref = self.pool.get('pos.order')
order_line_ref = self.pool.get('pos.order.line')
if context is None:
context = {}
this = self.browse(cr, uid, ids[0], context=context)
record_id = context and context.get('active_id', False)
if isinstance(record_id, (int, long)):
record_id = [record_id]
for order in order_ref.browse(cr, uid, record_id, context=context):
order_line_ref.write(cr, uid, [x.id for x in order.lines], {'discount':this.discount}, context=context)
return {}
@api.multi
def apply_discount(self):
self.ensure_one()
for order in self.env['pos.order'].browse(self.env.context.get('active_id', False)):
order.lines.write({'discount': self.discount})
+3 -8
View File
@@ -1,10 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- Add Product -->
<odoo>
<record id="view_pos_discount" model="ir.ui.view">
<field name="name">Apply Discount</field>
<field name="name">pos.discount.form</field>
<field name="model">pos.discount</field>
<field name="arch" type="xml">
<form string="Apply Discount">
@@ -27,6 +24,4 @@
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</data>
</openerp>
</odoo>
@@ -1,58 +1,32 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp.osv import osv
from openerp.tools.translate import _
from openerp.exceptions import UserError
from odoo import api, models, _
from odoo.exceptions import UserError
class pos_open_statement(osv.osv_memory):
class PosOpenStatement(models.TransientModel):
_name = 'pos.open.statement'
_description = 'Open Statements'
def open_statement(self, cr, uid, ids, context=None):
"""
Open the statements
@param self: The object pointer.
@param cr: A database cursor
@param uid: ID of the user currently logged in
@param context: A standard dictionary
@return : Blank Directory
"""
data = {}
mod_obj = self.pool.get('ir.model.data')
statement_obj = self.pool.get('account.bank.statement')
sequence_obj = self.pool.get('ir.sequence')
journal_obj = self.pool.get('account.journal')
if context is None:
context = {}
st_ids = []
j_ids = journal_obj.search(cr, uid, [('journal_user','=',1)], context=context)
if not j_ids:
@api.multi
def open_statement(self):
self.ensure_one()
BankStatement = self.env['account.bank.statement']
journals = self.env['account.journal'].search([('journal_user', '=', True)])
if not journals:
raise UserError(_('You have to define which payment method must be available in the point of sale by reusing existing bank and cash through "Accounting / Configuration / Journals / Journals". Select a journal and check the field "PoS Payment Method" from the "Point of Sale" tab. You can also create new payment methods directly from menu "PoS Backend / Configuration / Payment Methods".'))
for journal in journal_obj.browse(cr, uid, j_ids, context=context):
ids = statement_obj.search(cr, uid, [('state', '!=', 'confirm'), ('user_id', '=', uid), ('journal_id', '=', journal.id)], context=context)
for journal in journals:
if journal.sequence_id:
number = sequence_obj.next_by_id(cr, uid, journal.sequence_id.id, context=context)
number = journal.sequence_id.next_by_id()
else:
raise UserError(_("No sequence defined on the journal"))
BankStatement += BankStatement.create({'journal_id': journal.id, 'user_id': self.env.uid, 'name': number})
data.update({
'journal_id': journal.id,
'user_id': uid,
'name': number
})
statement_id = statement_obj.create(cr, uid, data, context=context)
st_ids.append(int(statement_id))
tree_res = mod_obj.get_object_reference(cr, uid, 'account', 'view_bank_statement_tree')
tree_id = tree_res and tree_res[1] or False
form_res = mod_obj.get_object_reference(cr, uid, 'account', 'view_bank_statement_form')
form_id = form_res and form_res[1] or False
search_res = mod_obj.get_object_reference(cr, uid, 'account', 'view_bank_statement_search')
search_id = search_res and search_res[1] or False
tree_id = self.env.ref('account.view_bank_statement_tree').id
form_id = self.env.ref('account.view_bank_statement_form').id
search_id = self.env.ref('account.view_bank_statement_search').id
return {
'type': 'ir.actions.act_window',
@@ -60,7 +34,7 @@ class pos_open_statement(osv.osv_memory):
'view_type': 'form',
'view_mode': 'tree,form',
'res_model': 'account.bank.statement',
'domain': str([('id', 'in', st_ids)]),
'domain': str([('id', 'in', BankStatement.ids)]),
'views': [(tree_id, 'tree'), (form_id, 'form')],
'search_view_id': search_id,
}
@@ -1,10 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- Point of Sale Confirm -->
<odoo>
<record id="view_pos_open_statement" model="ir.ui.view">
<field name="name">Open Statements</field>
<field name="name">pos.open.statement.form</field>
<field name="model">pos.open.statement</field>
<field name="arch" type="xml">
<form string="Open Statements">
@@ -17,13 +14,14 @@
</form>
</field>
</record>
<act_window name="Open Statements"
<act_window
id="act_pos_open_statement"
name="Open Statements"
res_model="pos.open.statement"
src_model="account.bank.statement"
view_mode="form"
target="new"
key2="client_action_multi"
id="act_pos_open_statement"/>
key2="client_action_multi"/>
<record id="action_pos_open_statement" model="ir.actions.act_window">
<field name="name">Open Cash Register</field>
@@ -33,6 +31,4 @@
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</data>
</openerp>
</odoo>
+34 -82
View File
@@ -1,58 +1,52 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import time
from openerp.osv import osv, fields
from openerp.tools.translate import _
from odoo import api, fields, models, _
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):
class PosMakePayment(models.TransientModel):
_name = 'pos.make.payment'
_description = 'Point of Sale Payment'
def check(self, cr, uid, ids, context=None):
def _default_journal(self):
active_id = self.env.context.get('active_id')
if active_id:
session = self.env['pos.order'].browse(active_id).session_id
return session.config_id.journal_ids and session.config_id.journal_ids.ids[0] or False
return False
def _default_amount(self):
active_id = self.env.context.get('active_id')
if active_id:
order = self.env['pos.order'].browse(active_id)
return (order.amount_total - order.amount_paid)
return False
journal_id = fields.Many2one('account.journal', string='Payment Mode', required=True, default=_default_journal)
amount = fields.Float(digits=(16, 2), required=True, default=_default_amount)
payment_name = fields.Char(string='Payment Reference')
payment_date = fields.Date(string='Payment Date', required=True, default=lambda *a: fields.Datetime.now())
@api.multi
def check(self):
"""Check the order:
if the order is not paid: continue payment,
if the order is paid print ticket.
"""
context = context or {}
order_obj = self.pool.get('pos.order')
active_id = context and context.get('active_id', False)
order = order_obj.browse(cr, uid, active_id, context=context)
self.ensure_one()
order = self.env['pos.order'].browse(self.env.context.get('active_id', False))
amount = order.amount_total - order.amount_paid
data = self.read(cr, uid, ids, context=context)[0]
data = self.read()[0]
# this is probably a problem of osv_memory as it's not compatible with normal OSV's
data['journal'] = data['journal_id'][0]
if amount != 0.0:
order_obj.add_payment(cr, uid, active_id, data, context=context)
order.add_payment(data)
if order.test_paid():
order.signal_workflow('paid')
return {'type': 'ir.actions.act_window_close'}
return self.launch_payment()
if order_obj.test_paid(cr, uid, [active_id]):
order_obj.signal_workflow(cr, uid, [active_id], 'paid')
return {'type' : 'ir.actions.act_window_close' }
return self.launch_payment(cr, uid, ids, context=context)
def launch_payment(self, cr, uid, ids, context=None):
def launch_payment(self):
return {
'name': _('Payment'),
'view_type': 'form',
@@ -62,48 +56,6 @@ class pos_make_payment(osv.osv_memory):
'target': 'new',
'views': False,
'type': 'ir.actions.act_window',
'context': context,
'context': self.env.context,
}
def print_report(self, cr, uid, ids, context=None):
active_id = context.get('active_id', [])
datas = {'ids' : [active_id]}
return {
'type': 'ir.actions.report.xml',
'report_name': 'pos.receipt',
'datas': datas,
}
def _default_journal(self, cr, uid, context=None):
if not context:
context = {}
session = False
order_obj = self.pool.get('pos.order')
active_id = context and context.get('active_id', False)
if active_id:
order = order_obj.browse(cr, uid, active_id, context=context)
session = order.session_id
if session:
for journal in session.config_id.journal_ids:
return journal.id
return False
def _default_amount(self, cr, uid, context=None):
order_obj = self.pool.get('pos.order')
active_id = context and context.get('active_id', False)
if active_id:
order = order_obj.browse(cr, uid, active_id, context=context)
return order.amount_total - order.amount_paid
return False
_columns = {
'journal_id' : fields.many2one('account.journal', 'Payment Mode', required=True),
'amount': fields.float('Amount', digits=(16,2), required= True),
'payment_name': fields.char('Payment Reference'),
'payment_date': fields.date('Payment Date', required=True),
}
_defaults = {
'journal_id' : _default_journal,
'payment_date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
'amount': _default_amount,
}
+3 -7
View File
@@ -1,9 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<odoo>
<record id="view_pos_payment" model="ir.ui.view">
<field name="name">Pay Order</field>
<field name="name">pos.make.payment.form</field>
<field name="model">pos.make.payment</field>
<field name="arch" type="xml">
<form string="Pay Order">
@@ -27,6 +25,4 @@
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</data>
</openerp>
</odoo>
+3 -4
View File
@@ -68,11 +68,10 @@ class pos_order_card(models.Model):
return fields
@api.model
def add_payment(self, order_id, data):
statement_id = super(pos_order_card, self).add_payment(order_id, data)
def add_payment(self, data):
statement_id = super(pos_order_card, self).add_payment(data)
statement_lines = self.env['account.bank.statement.line'].search([('statement_id', '=', statement_id),
('pos_statement_id', '=', order_id),
('pos_statement_id', '=', self.id),
('journal_id', '=', data['journal']),
('amount', '=', data['amount'])])