[MERGE] migration of point_of_sale to the new API
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
+3
-3
@@ -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>
|
||||
|
||||
+3
-7
@@ -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>
|
||||
+3
-12
@@ -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>
|
||||
@@ -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]
|
||||
@@ -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',
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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/',
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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','<>','invoiced')]}"/>
|
||||
<field name="account_move" readonly="1"
|
||||
attrs="{'invisible':[('state','<>','done')]}"/>
|
||||
</group>
|
||||
<group>
|
||||
<button name="%(pos_invoice_report)d" string="Re-Print"
|
||||
icon="gtk-print" type="action" attrs="{'invisible':[('state','<>','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','<>',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', '>=', time.strftime('%Y-%m-%d 00:00:00')),('create_date', '<=', 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 & Post Entries"
|
||||
attrs="{'invisible' : ['|', ('cash_control', '=', True),('state', '!=', 'opened')]}"
|
||||
class="oe_highlight" />
|
||||
<button name="close" type="workflow" string="Validate Closing & 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>
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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)})
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
)
|
||||
""")
|
||||
@@ -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"),
|
||||
}
|
||||
@@ -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'),
|
||||
}
|
||||
@@ -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,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>
|
||||
@@ -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
|
||||
@@ -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>
|
||||
+3
-5
@@ -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>
|
||||
+4
-7
@@ -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>
|
||||
@@ -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
-3
@@ -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>
|
||||
+4
-10
@@ -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>
|
||||
+10
-5
@@ -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','<>','invoiced')]}"/>
|
||||
<field name="account_move" readonly="1"
|
||||
attrs="{'invisible':[('state','<>','done')]}"/>
|
||||
</group>
|
||||
<group>
|
||||
<button name="%(pos_invoice_report)d" string="Re-Print"
|
||||
icon="gtk-print" type="action" attrs="{'invisible':[('state','<>','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', '>=', time.strftime('%Y-%m-%d 00:00:00')),('create_date', '<=', 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 & Post Entries"
|
||||
attrs="{'invisible' : ['|', ('cash_control', '=', True),('state', '!=', 'opened')]}"
|
||||
class="oe_highlight" />
|
||||
<button name="close" type="workflow" string="Validate Closing & 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>
|
||||
+2
-7
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
|
||||
<odoo>
|
||||
<template id="index" name="POS Index"><!DOCTYPE html>
|
||||
<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','<>',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>
|
||||
|
||||
+3
-5
@@ -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>
|
||||
+2
-6
@@ -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>
|
||||
+2
-4
@@ -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,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
@@ -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})
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'])])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user