[MERGE] latest trunk

bzr revid: abo@openerp.com-20120822175756-ypym270e6cklpbds
This commit is contained in:
Antonin Bourguignon 2012-08-22 19:57:56 +02:00
commit 121b351b59
183 changed files with 6911 additions and 59071 deletions

View File

@ -7,14 +7,14 @@ msgstr ""
"Project-Id-Version: OpenERP Server 6.0dev\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2012-02-08 00:35+0000\n"
"PO-Revision-Date: 2012-05-10 17:32+0000\n"
"Last-Translator: Michael Otcheskih <otma@mail.ru>\n"
"PO-Revision-Date: 2012-08-17 11:07+0000\n"
"Last-Translator: Chertykov Denis <chertykov@gmail.com>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-08-07 05:06+0000\n"
"X-Generator: Launchpad (build 15745)\n"
"X-Launchpad-Export-Date: 2012-08-18 04:58+0000\n"
"X-Generator: Launchpad (build 15810)\n"
#. module: account
#: view:account.invoice.report:0
@ -9872,7 +9872,7 @@ msgstr "Не определен счет доходов для ТМЦ: \"%s\" (i
#. module: account
#: constraint:account.move.line:0
msgid "You can not create journal items on closed account."
msgstr ""
msgstr "Нельзя создать элемент журнала по закрытому счету ."
#. module: account
#: field:account.account,unrealized_gain_loss:0

View File

@ -7,7 +7,7 @@
currency_id: base.EUR
invoice_line:
- account_id: account.a_sale
name: '[PC1] Basic PC'
name: '[PCSC234] PC Assemble SC234'
price_unit: 450.0
quantity: 1.0
product_id: product.product_product_3

View File

@ -7,7 +7,7 @@
currency_id: base.EUR
invoice_line:
- account_id: account.a_sale
name: '[PC1] Basic PC'
name: '[PCSC234] PC Assemble SC234'
price_unit: 450.0
quantity: 1.0
product_id: product.product_product_3

View File

@ -20,7 +20,7 @@
currency_id: base.EUR
invoice_line:
- account_id: account.a_sale
name: '[PC3] Medium PC'
name: '[PC-DEM] PC on Demand'
price_unit: 900.0
quantity: 10.0
product_id: product.product_product_5

View File

@ -28,7 +28,7 @@
currency_id: base.EUR
invoice_line:
- account_id: account.a_expense
name: '[PC1] Basic PC'
name: '[PCSC234] PC Assemble SC234'
price_unit: 300.0
product_id: product.product_product_3
quantity: 10.0

View File

@ -24,7 +24,7 @@
uos_id: 1
quantity: 5.0
price_unit: 100.0
name: 'Medium PC'
name: 'PC on Demand'
account_id: account.a_pay
tax_line:
- name: sale tax
@ -104,9 +104,9 @@
"__model": "account.invoice.line",
"__id": "account:b22acf7a-ddcd-11e0-a4db-701a04e25543.account_invoice_line-1RP3so",
"uos_id": ["product:b22acf7a-ddcd-11e0-a4db-701a04e25543.product_uom_unit", "Unit"],
"name": "Basic PC",
"name": "PC Assemble SC234",
"price_unit": 10.0,
"product_id": ["product:b22acf7a-ddcd-11e0-a4db-701a04e25543.product_product_3", "[PC1] Basic PC"],
"product_id": ["product:b22acf7a-ddcd-11e0-a4db-701a04e25543.product_product_3", "[PCSC234] PC Assemble SC234"],
"quantity": 1.0
},
{
@ -114,9 +114,9 @@
"__model": "account.invoice.line",
"__id": "account:b22acf7a-ddcd-11e0-a4db-701a04e25543.account_invoice_line-u2XV5",
"uos_id": ["product:b22acf7a-ddcd-11e0-a4db-701a04e25543.product_uom_unit", "Unit"],
"name": "Medium PC",
"name": "PC on Demand",
"price_unit": 100.0,
"product_id": ["product:b22acf7a-ddcd-11e0-a4db-701a04e25543.product_product_5", "[PC3] Medium PC"],
"product_id": ["product:b22acf7a-ddcd-11e0-a4db-701a04e25543.product_product_5", "[PC-DEM] PC on Demand"],
"quantity": 5.0
}]
}
@ -137,12 +137,12 @@
assert invoice_new.type == 'in_invoice', "Invoice type was not set properly"
assert len(invoice_new.invoice_line) == 2, "invoice lines are not same"
for inv_line in invoice_new.invoice_line:
if inv_line.name == 'Basic PC':
if inv_line.name == 'PC Assemble SC234':
assert inv_line.uos_id.name == "Unit" , "uom is not same"
assert inv_line.price_unit == 10 , "price unit is not same"
assert inv_line.quantity == 1 , "product qty is not same"
assert inv_line.price_subtotal == 10, "price sub total is not same"
elif inv_line.name == 'Medium PC':
elif inv_line.name == 'PC on Demand':
assert inv_line.uos_id.name == "Unit" , "uom is not same"
assert inv_line.price_unit == 100 , "price unit is not same"
assert inv_line.quantity == 5 , "product qty is not same"

View File

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- rename root menu "Accounting" -->
<record id="account.menu_finance" model="ir.ui.menu">
<field name="name">Accounting</field>
</record>
<!-- Rename root menu "Accounting" -->
<!-- Top menu item -->
<menuitem name="Accounting"
id="account.menu_finance"/>
</data>
<data noupdate="1">
@ -19,10 +19,8 @@
<field name="type">automatic</field>
<field name="sequence">100</field>
</record>
</data>
<data noupdate="1">
<!-- notify all employees of module installation -->
<!-- Notify all employees of module installation -->
<function model="mail.group" name="message_append_note">
<!-- ids, subject, body, parent_id=False, type='notification', content_subtype='html' -->
<value eval="[ref('mail.group_all_employees')]"/>

View File

@ -7,14 +7,14 @@ msgstr ""
"Project-Id-Version: OpenERP Server 6.0dev\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2012-02-08 00:35+0000\n"
"PO-Revision-Date: 2010-12-23 13:10+0000\n"
"PO-Revision-Date: 2012-08-17 10:54+0000\n"
"Last-Translator: Chertykov Denis <chertykov@gmail.com>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-08-07 05:17+0000\n"
"X-Generator: Launchpad (build 15745)\n"
"X-Launchpad-Export-Date: 2012-08-18 04:58+0000\n"
"X-Generator: Launchpad (build 15810)\n"
#. module: account_analytic_default
#: help:account.analytic.default,partner_id:0
@ -133,7 +133,7 @@ msgstr "Аналитика по умолчанию"
#. module: account_analytic_default
#: sql_constraint:stock.picking:0
msgid "Reference must be unique per Company!"
msgstr ""
msgstr "Ссылка должна быть уникальна для каждой компании!"
#. module: account_analytic_default
#: view:account.analytic.default:0

View File

@ -1106,7 +1106,8 @@ class account_voucher(osv.osv):
# otherwise we use the rates of the system (giving the voucher date in the context)
amount_currency = currency_obj.compute(cr, uid, company_currency, line.move_line_id.currency_id.id, move_line['debit']-move_line['credit'], context=ctx)
if line.amount == line.amount_unreconciled and line.move_line_id.currency_id.id == voucher_currency:
foreign_currency_diff = line.move_line_id.amount_residual_currency + amount_currency
sign = voucher_brw.type in ('payment', 'purchase') and -1 or 1
foreign_currency_diff = sign * line.move_line_id.amount_residual_currency + amount_currency
move_line['amount_currency'] = amount_currency
voucher_line = move_line_obj.create(cr, uid, move_line)

View File

@ -54,7 +54,7 @@
currency_id: base.EUR
journal_id: account.bank_journal
name: Voucher Axelor
narration: Basic PC
narration: PC Assemble SC234
line_dr_ids:
- account_id: account.cash
amount: 1000.0

View File

@ -6,7 +6,7 @@
company_id: base.main_company
journal_id: account.bank_journal
name: Voucher Axelor
narration: Basic PC
narration: PC Assemble SC234
amount: 1000.0
line_ids:
- account_id: account.cash

View File

@ -79,7 +79,7 @@
period_id: account.period_1
invoice_line:
- account_id: account.a_sale
name: '[PC1] Basic PC'
name: '[PCSC234] PC Assemble SC234'
price_unit: 200.0
quantity: 1.0
product_id: product.product_product_3
@ -112,7 +112,7 @@
period_id: account.period_2
invoice_line:
- account_id: account.a_sale
name: '[PC1] Basic PC'
name: '[PCSC234] PC Assemble SC234'
price_unit: 100.0
quantity: 1.0
product_id: product.product_product_3

View File

@ -50,7 +50,7 @@
period_id: account.period_1
invoice_line:
- account_id: account.a_expense
name: '[PC1] Basic PC'
name: '[PCSC234] PC Assemble SC234'
price_unit: 200.0
quantity: 1.0
product_id: product.product_product_3
@ -85,7 +85,7 @@
type : in_invoice
invoice_line:
- account_id: account.a_expense
name: '[PC1] Basic PC'
name: '[PCSC234] PC Assemble SC234'
price_unit: 100.0
quantity: 1.0
product_id: product.product_product_3

View File

@ -85,7 +85,7 @@
period_id: account.period_1
invoice_line:
- account_id: account.a_sale
name: '[PC1] Basic PC'
name: '[PCSC234] PC Assemble SC234'
price_unit: 200.0
quantity: 1.0
product_id: product.product_product_3
@ -118,7 +118,7 @@
period_id: account.period_2
invoice_line:
- account_id: account.a_sale
name: '[PC1] Basic PC'
name: '[PCSC234] PC Assemble SC234'
price_unit: 100.0
quantity: 1.0
product_id: product.product_product_3

View File

@ -85,7 +85,7 @@
period_id: account.period_1
invoice_line:
- account_id: account.a_sale
name: '[PC1] Basic PC'
name: '[PCSC234] PC Assemble SC234'
price_unit: 200.0
quantity: 1.0
product_id: product.product_product_3
@ -118,7 +118,7 @@
period_id: account.period_2
invoice_line:
- account_id: account.a_sale
name: '[PC1] Basic PC'
name: '[PCSC234] PC Assemble SC234'
price_unit: 100.0
quantity: 1.0
product_id: product.product_product_3

View File

@ -39,7 +39,7 @@
period_id: account.period_1
invoice_line:
- account_id: account.a_sale
name: '[PC1] Basic PC'
name: '[PCSC234] PC Assemble SC234'
price_unit: 150.0
quantity: 1.0
product_id: product.product_product_3
@ -72,7 +72,7 @@
period_id: account.period_1
invoice_line:
- account_id: account.a_sale
name: '[PC1] Basic PC'
name: '[PCSC234] PC Assemble SC234'
price_unit: 80.0
quantity: 1.0
product_id: product.product_product_3

View File

@ -73,7 +73,7 @@
period_id: account.period_1
invoice_line:
- account_id: account.a_sale
name: '[PC1] Basic PC'
name: '[PCSC234] PC Assemble SC234'
price_unit: 200.0
quantity: 1.0
product_id: product.product_product_3

View File

@ -52,7 +52,7 @@
period_id: account.period_1
invoice_line:
- account_id: account.a_sale
name: '[PC1] Basic PC'
name: '[PCSC234] PC Assemble SC234'
price_unit: 1000.0
quantity: 1.0
product_id: product.product_product_3

View File

@ -7,7 +7,7 @@
currency_id: base.EUR
invoice_line:
- account_id: account.a_sale
name: '[PC1] Basic PC'
name: '[PCSC234] PC Assemble SC234'
price_unit: 450.0
quantity: 1.0
product_id: product.product_product_3

View File

@ -1,3 +1,4 @@
import controllers
import auth_oauth
import res_users
import res_config

View File

@ -31,9 +31,10 @@ Allow users to login through Google OAuth2.
'author': 'Victor Tabuenca',
'maintainer': 'OpenERP s.a.',
'website': 'http://www.openerp.com',
'depends': ['base', 'web'],
'data': ['auth_oauth_data.xml',
'auth_oauth_view.xml'
'depends': ['base', 'web', 'base_setup'],
'data': [
'auth_oauth_data.xml',
'auth_oauth_view.xml'
],
'js': ['static/src/js/auth_oauth.js'],
'css': ['static/lib/zocial/css/zocial.css'],

View File

@ -1,6 +1,6 @@
from openerp.osv import osv, fields
class auth_oauth_providers(osv.osv):
class auth_oauth_provider(osv.osv):
"""Class defining the configuration values of an OAuth2 provider"""
_name = 'auth.oauth.provider'
@ -8,15 +8,17 @@ class auth_oauth_providers(osv.osv):
_order = 'name'
_columns = {
'name' : fields.char('Provider name', required=True), # Name of the OAuth2 entity, Google, LinkedIn, etc
'client_id' : fields.char('Client ID', required=True), # Our identifier
'auth_endpoint' : fields.char('Authentication URL', required=True), # OAuth provider URL to authenticate users
'name' : fields.char('Provider name'), # Name of the OAuth2 entity, Google, LinkedIn, etc
'client_id' : fields.char('Client ID'), # Our identifier
'auth_endpoint' : fields.char('Authentication URL'), # OAuth provider URL to authenticate users
'scope' : fields.char('Scope'), # OAUth user data desired to access
'validation_endpoint' : fields.char('Validation URL'), # OAuth provider URL to validate tokens
'data_endpoint' : fields.char('Data URL'),
'enabled' : fields.boolean('Allowed'),
'css_class' : fields.char('CSS class'),
'body' : fields.char('Body'),
'active' : fields.boolean('Active'),
'sequence' : fields.integer(),
}
_defaults = {
'enabled' : False,
}

View File

@ -1,38 +1,34 @@
<?xml version="1.0"?>
<openerp>
<data noupdate="1">
<data>
<record id="provider_facebook" model="auth.oauth.provider">
<field name="name">Facebook Graph</field>
<field name="client_id">facebook_client_id</field>
<field name="auth_endpoint">https://www.facebook.com/dialog/oauth</field>
<field name="scope"></field>
<field name="validation_endpoint">https://graph.facebook.com/me/permissions</field>
<field name="data_endpoint"></field>
<field name="css_class">zocial facebook</field>
<field name="body">Sign in with facebook</field>
<field name="active">True</field>
</record>
<record id="provider_google" model="auth.oauth.provider">
<field name="name">Google OAuth2</field>
<field name="client_id">108010644258-duuhmp6pu7li4tsmnqg7j9rvdeklg0ki.apps.googleusercontent.com</field>
<field name="auth_endpoint">https://accounts.google.com/o/oauth2/auth</field>
<field name="scope">https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile</field>
<field name="validation_endpoint">https://www.googleapis.com/oauth2/v1/tokeninfo</field>
<field name="data_endpoint">https://www.googleapis.com/oauth2/v1/userinfo</field>
<field name="css_class">zocial google</field>
<field name="body">Sign in with google</field>
<field name="active">True</field>
</record>
<record id="provider_twitter" model="auth.oauth.provider">
<field name="name">Twitter OAuth2</field>
<field name="client_id">108010644258-duuhmp6pu7li4tsmnqg7j9rvdeklg0ki.apps.twitterusercontent.com</field>
<!-- <record id="provider_twitter" model="auth.oauth.provider">
<field name="name">Twitter OAuth</field>
<field name="auth_endpoint">https://api.twitter.com/oauth/request_token</field>
<field name="scope"></field>
<field name="validation_endpoint">https://api.twitter.com/oauth/authorize</field>
<field name="data_endpoint"></field>
<field name="css_class">zocial twitter</field>
<field name="body">Sign in with twitter</field>
<field name="active">True</field>
</record>
</record> -->
</data>
</openerp>

View File

@ -11,7 +11,7 @@
<group>
<field name="name" />
<field name="client_id" />
<field name="active" />
<field name="enabled" />
</group>
<group>
<field name="auth_endpoint" />
@ -22,7 +22,7 @@
</sheet>
</form>
</field>
</record>
</record>
<record model="ir.ui.view" id="view_oauth_provider_list">
<field name="name">auth.oauth.provider.list</field>
<field name="model">auth.oauth.provider</field>
@ -31,7 +31,7 @@
<tree string="arch" version="7.0">
<field name="name" />
<field name="client_id" />
<field name="active" />
<field name="enabled" />
</tree>
</field>
</record>

View File

@ -18,7 +18,7 @@ class OAuthController(openerpweb.Controller):
registry = openerp.modules.registry.RegistryManager.get(dbname)
with registry.cursor() as cr:
providers = registry.get('auth.oauth.provider')
l = providers.read(cr, 1, providers.search(cr, 1, []))
l = providers.read(cr, 1, providers.search(cr, 1, [('enabled','=',True)]))
return l
@openerpweb.httprequest

View File

@ -0,0 +1,63 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2012-Today OpenERP SA (<http://www.openerp.com>)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
#
##############################################################################
from openerp.osv import osv, fields
import logging
_logger = logging.getLogger(__name__)
class base_config_settings(osv.TransientModel):
_inherit = 'base.config.settings'
_columns = {
'auth_oauth_google_enabled' : fields.boolean('Allow users to sign in with Google'),
'auth_oauth_google_client_id' : fields.char('Client ID'),
'auth_oauth_facebook_enabled' : fields.boolean('Allow users to sign in with Facebook'),
'auth_oauth_facebook_client_id' : fields.char('Client ID'),
}
def get_oauth_providers(self, cr, uid, fields, context=None):
google_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'auth_oauth', 'provider_google')[1]
facebook_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'auth_oauth', 'provider_facebook')[1]
rg = self.pool.get('auth.oauth.provider').read(cr, uid, [google_id], ['enabled','client_id'], context=context)
rf = self.pool.get('auth.oauth.provider').read(cr, uid, [facebook_id], ['enabled','client_id'], context=context)
return {
'auth_oauth_google_enabled': rg[0]['enabled'],
'auth_oauth_google_client_id': rg[0]['client_id'],
'auth_oauth_facebook_enabled': rf[0]['enabled'],
'auth_oauth_facebook_client_id': rf[0]['client_id'],
}
def set_oauth_providers(self, cr, uid, ids, context=None):
google_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'auth_oauth', 'provider_google')[1]
facebook_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'auth_oauth', 'provider_facebook')[1]
config = self.browse(cr, uid, ids[0], context=context)
rg = {
'enabled':config.auth_oauth_google_enabled,
'client_id':config.auth_oauth_google_client_id,
}
rf = {
'enabled':config.auth_oauth_facebook_enabled,
'client_id':config.auth_oauth_facebook_client_id,
}
self.pool.get('auth.oauth.provider').write(cr, uid, [google_id], rg)
self.pool.get('auth.oauth.provider').write(cr, uid, [facebook_id], rf)

View File

@ -0,0 +1,50 @@
<?xml version="1.0"?>
<openerp>
<data>
<record model="ir.ui.view" id="view_general_configuration">
<field name="name">base.config.settings.oauth</field>
<field name="model">base.config.settings</field>
<field name="inherit_id" ref="base_setup.view_general_configuration"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='module_auth_oauth']/.." position="after">
<div attrs="{'invisible':[('module_auth_oauth','=',False)]}">
<div name="google">
<div>
<field name="auth_oauth_google_enabled" class="oe_inline"/>
<label for="auth_oauth_google_enabled"/>
</div>
<div attrs="{'invisible':[('auth_oauth_google_enabled','=',False)]}">
<blockquote>
To setup the signin process with Google, first you have to perform the following steps:<br/>
<br/>
- Go to the <a href="https://code.google.com/apis/console/">Google APIs console</a><br/>
- Ceate a new project<br/>
- Go to Api Access<br/>
- Create an oauth client_id<br/>
- Edit settings and set both Authorized Redirect URIs and Authorized JavaScript Origins to your hostname.<br/>
<br/>
Now copy paste the client_id here: <field name="auth_oauth_google_client_id" class="oe_inline" placeholder="e.g. 1234-xyz.apps.googleusercontent.com"/>
</blockquote>
</div>
</div>
<div name="facebook">
<div>
<field name="auth_oauth_facebook_enabled" class="oe_inline"/>
<label for="auth_oauth_facebook_enabled"/>
</div>
<div attrs="{'invisible':[('auth_oauth_facebook_enabled','=',False)]}">
<blockquote>
To setup the signin process with Google, first you have to perform the following steps:<br/>
<br/>
Now copy paste the client_id here: <field name="auth_oauth_facebook_client_id" class="oe_inline" placeholder="e.g. 1234-xyz.apps.googleusercontent.com"/>
</blockquote>
</div>
</div>
</div>
</xpath>
</field>
</record>
</data>
</openerp>

View File

@ -21,9 +21,7 @@ openerp.auth_oauth = function(instance) {
},
on_oauth_loaded: function(result) {
this.oauth_providers = result;
console.log(result);
var buttons = QWeb.render("auth_oauth.Login.button",{"widget":this});
console.log(buttons);
this.$(".oe_login_pane form ul").after(buttons);
},
on_oauth_sign_in: function(ev) {

View File

@ -25,7 +25,8 @@ class base_config_settings(osv.TransientModel):
_inherit = 'base.config.settings'
_columns = {
'auth_signup_template_user_id': fields.many2one('res.users', 'Template user for new users created through signup')
'auth_signup_uninvited': fields.boolean('allow public users to sign up', help="If unchecked only invited users may sign up"),
'auth_signup_template_user_id': fields.many2one('res.users', 'Template user for new users created through signup'),
}
def get_default_signup(self, cr, uid, fields, context=None):

View File

@ -7,16 +7,15 @@
<field name="model">base.config.settings</field>
<field name="inherit_id" ref="base_setup.view_general_configuration"/>
<field name="arch" type="xml">
<xpath expr="//group[last()]" position="after">
<group>
<label for="id" string="External Users"/>
<div>
<div>
<label for="auth_signup_template_user_id"/>
<field name="auth_signup_template_user_id" class="oe_inline"/>
</div>
</div>
</group>
<xpath expr="//field[@name='module_auth_anonymous']/.." position="after">
<div>
<field name="auth_signup_uninvited" class="oe_inline"/>
<label for="auth_signup_uninvited"/>
</div>
<div>
<label for="auth_signup_template_user_id"/>
<field name="auth_signup_template_user_id" class="oe_inline"/>
</div>
</xpath>
</field>
</record>

View File

@ -16,6 +16,14 @@
<field name="name">Off-site Meeting</field>
</record>
<record model="crm.meeting.type" id="categ_meet4">
<field name="name">Open Discussion</field>
</record>
<record model="crm.meeting.type" id="categ_meet5">
<field name="name">Feedback Meeting</field>
</record>
<record model="res.request.link" id="request_link_meeting">
<field name="name">Meeting</field>
<field name="object">crm.meeting</field>

View File

@ -27,7 +27,7 @@
<field eval="time.strftime('%Y-%m-05 12:00:00')" name="date"/>
<field eval="time.strftime('%Y-%m-05 19:00:00')" name="date_deadline"/>
<field eval="7.0" name="duration"/>
<field name="state">draft</field>
<field name="state">open</field>
</record>
<record id="crm_meeting_3" model="crm.meeting">
@ -39,7 +39,7 @@
<field eval="time.strftime('%Y-%m-12 15:55:05')" name="date"/>
<field eval="time.strftime('%Y-%m-12 18:55:05')" name="date_deadline"/>
<field eval="3.0" name="duration"/>
<field name="state">done</field>
<field name="state">open</field>
</record>
<record id="crm_meeting_4" model="crm.meeting">
@ -61,7 +61,7 @@
<field eval="time.strftime('%Y-%m-22 11:05:00')" name="date"/>
<field eval="time.strftime('%Y-%m-22 16:05:00')" name="date_deadline"/>
<field eval="5" name="duration"/>
<field name="state">draft</field>
<field name="state">open</field>
</record>
<record id="crm_meeting_6" model="crm.meeting">
@ -72,7 +72,7 @@
<field eval="time.strftime('%Y-%m-18 2:00:00')" name="date"/>
<field eval="time.strftime('%Y-%m-18 10:30:00')" name="date_deadline"/>
<field eval="8.5" name="duration"/>
<field name="state">done</field>
<field name="state">open</field>
</record>
</data>
</openerp>

View File

@ -70,7 +70,7 @@
<field name="arch" type="xml">
<form string="Meetings" version="7.0">
<header>
<field name="state"/>
<field name="state" invisible="True"/>
</header>
<sheet>
<div class="oe_title">
@ -115,7 +115,7 @@
<label for="allday" string="All Day?"/>)
</div>
<field name="recurrency"
attrs="{'readonly': ['|', ('recurrent_uid','!=',False), ('state','=','done')]}"/>
attrs="{'readonly': [('recurrent_uid','!=',False)]}"/>
</group>
<group>
<field name="alarm_id" widget="selection" />
@ -171,7 +171,6 @@
<button string="Invite People"
name="%(base_calendar.action_view_calendar_invite_attendee_wizard)d"
icon="terp-partner" type="action"
attrs="{'readonly': [('state', '=', 'done')]}"
context="{'model' : 'crm.meeting', 'attendee_field':'attendee_ids'}" colspan="2"/>
<field name="attendee_ids" widget="one2many" mode="tree">
<tree string="Invitation details" editable="top">
@ -245,12 +244,11 @@
<field name="name">CRM - Meetings Tree</field>
<field name="model">crm.meeting</field>
<field name="arch" type="xml">
<tree string="Meetings" fonts="bold:needaction_pending==True"
colors="red:state=='open';black:state in ('draft', 'cancel','done','pending')">
<tree string="Meetings" fonts="bold:needaction_pending==True">
<field name="name" string="Subject" />
<field name="user_id"/>
<field name="date"/>
<field name="state"/>
<field name="state" invisible="True"/>
<field name="duration" />
<field name="needaction_pending" invisible="1"/>
</tree>

View File

@ -28,10 +28,13 @@ class base_config_settings(osv.osv_memory):
'module_multi_company': fields.boolean('manage multiple companies',
help="""Work in multi-company environments, with appropriate security access between companies.
This installs the module multi_company."""),
'module_portal': fields.boolean('activate customer portal',
help="""The portal will give access to a series of documents for your customers; his quotations, his invoices, his projects, etc."""),
'module_share': fields.boolean('allow documents sharing',
help="""As an example, you will be able to share a project or some tasks to your customers, or quotes/sales to several persons at your customer company, or your agenda availabilities to your contacts."""),
help="""Share or embbed any screen of openerp."""),
'module_portal': fields.boolean('activate the customer/supplier portal',
help="""Give access your customers and suppliers to their documents."""),
'module_auth_anonymous': fields.boolean('activate the public portal',
help="""Enable the public part of openerp, openerp becomes a public website."""),
'module_auth_oauth': fields.boolean('use external authentication providers, sign in with google, facebook, ...'),
}
def open_company(self, cr, uid, ids, context=None):

View File

@ -28,20 +28,34 @@
</div>
</group>
<group>
<label for="id" string="Share Data"/>
<div>
<label for="id" string="Email"/>
<div name="email">
<div>
<field name="module_share" class="oe_inline"/>
<label for="module_share"/>
<button type="action"
name="%(base.action_ir_mail_server_list)d"
string="Configure outgoing email servers" class="oe_link"/>
</div>
</div>
</group>
<group>
<label for="id" string="Portal access"/>
<div>
<div>
<field name="module_portal" class="oe_inline"/>
<label for="module_portal"/>
</div>
<div>
<button type="action"
name="%(base.action_ir_mail_server_list)d"
string="Configure outgoing email servers" class="oe_link"/>
<field name="module_auth_anonymous" class="oe_inline"/>
<label for="module_auth_anonymous"/>
</div>
</div>
</group>
<group>
<label for="id" string="Authentication"/>
<div>
<div>
<field name="module_auth_oauth" class="oe_inline"/>
<label for="module_auth_oauth"/>
</div>
</div>
</group>

View File

@ -49,7 +49,6 @@ Can you send details,</field>
<field name="section_id" ref="crm_case_section_3"/>
<field name="user_id" ref="base.user_admin"/>
<field name="stage_id" ref="stage_lead1"/>
<field name="description">Want to know features and benifits to use the new software.</field>
<field eval="1" name="active"/>
</record>
@ -550,53 +549,86 @@ Andrew</field>
<!-- Some messages linked to the previous opportunity -->
<record id="message_email0" model="mail.message">
<field name="subject">Kitchen design</field>
<field name="subject">Plan to buy a Laptop</field>
<field name="model">crm.lead</field>
<field name="res_id" ref="crm_case_15"/>
<field name="content_subtype">html</field>
<field name="body_html"><![CDATA[Email0 inquiry]]></field>
<field name="type">email</field>
<field name="state">sent</field>
<field name="user_id" ref="base.user_root"></field>
</record>
<record id="message_note0" model="mail.message">
<field name="subject">Reply</field>
<field name="model">crm.lead</field>
<field name="res_id" ref="crm_case_15"/>
<field name="content_subtype">html</field>
<field name="body_html"><![CDATA[Internal note0]]></field>
<field name="type">comment</field>
<field name="user_id" ref="base.user_root"></field>
</record>
<record id="message_note0_comment0" model="mail.message">
<field name="subject">Reply</field>
<field name="model">crm.lead</field>
<field name="res_id" ref="crm_case_15"/>
<field name="content_subtype">html</field>
<field name="body_html"><![CDATA[comment on note0]]></field>
<field name="parent_id" ref="message_note0"/>
<field name="type">comment</field>
<field name="user_id" ref="base.user_root"></field>
</record>
<record id="message_note0_comment1" model="mail.message">
<field name="subject">Reply</field>
<field name="model">crm.lead</field>
<field name="res_id" ref="crm_case_15"/>
<field name="content_subtype">html</field>
<field name="body_html"><![CDATA[comment1 on note0]]></field>
<field name="parent_id" ref="message_note0"/>
<field name="type">comment</field>
<field name="user_id" ref="base.user_root"></field>
</record>
<record id="message_email1" model="mail.message">
<field name="subject">Your inquiry</field>
<field name="model">crm.lead</field>
<field name="res_id" ref="crm_case_15"/>
<field name="content_subtype">html</field>
<field name="body_html"><![CDATA[hello, outgoing email]]></field>
<field name="body_html">&lt;![CDATA[Email0 inquiry]]&gt;&lt;div&gt;&lt;font size="2"&gt;Hello,&lt;/font&gt;&lt;/div&gt;&lt;div&gt;&lt;font size="2"&gt;&lt;br&gt;&lt;/font&gt;&lt;/div&gt;&lt;div&gt;&lt;font size="2"&gt;I am interested in your company's product and I plan to buy a new laptop having latest technologies and affordable price.&lt;/font&gt;&lt;/div&gt;&lt;div&gt;&lt;font size="2"&gt;Can you please send me product catalogue?&lt;/font&gt;&lt;/div&gt;</field>
<field name="type">email</field>
<field name="state">received</field>
<field name="user_id" ref="base.user_root"></field>
<field name="user_id" ref="base.user_demo"/>
</record>
<record id="message_note0" model="mail.message">
<field name="subject">Re: Plan to buy a Laptop</field>
<field name="model">crm.lead</field>
<field name="res_id" ref="crm_case_15"/>
<field name="parent_id" ref="message_email0"/>
<field name="content_subtype">plain</field>
<field name="body_text">Dear Customer,
Thanks for showing interest in our products.
We have attached the catalogue,
We would like to know your interests, Let us know if we can call you for more details.
Thanks</field>
<field name="type">email</field>
<field name="user_id" ref="base.user_root"/>
<field name="state">sent</field>
</record>
<record id="message_note0_comment0" model="mail.message">
<field name="subject">Re: Plan to buy a Laptop</field>
<field name="model">crm.lead</field>
<field name="res_id" ref="crm_case_15"/>
<field name="content_subtype">html</field>
<field name="body_html">&lt;div&gt;Thanks for the information,&lt;/div&gt;&lt;div&gt;I will visit the store soon.&lt;/div&gt;</field>
<field name="parent_id" ref="message_note0"/>
<field name="type">email</field>
<field name="user_id" ref="base.user_demo"/>
<field name="state">received</field>
</record>
<record id="message_note0_comment1" model="mail.message">
<field name="subject">Re: Plan to buy a Laptop</field>
<field name="model">crm.lead</field>
<field name="res_id" ref="crm_case_15"/>
<field name="content_subtype">html</field>
<field name="body_html">&lt;font color="#1f1f1f"&gt;Can you tell me if the store is open at 9:00 PM?&lt;/b&gt;&lt;/font&gt;</field>
<field name="parent_id" ref="message_note0"/>
<field name="type">email</field>
<field name="state">received</field>
<field name="user_id" ref="base.user_demo"/>
</record>
<record id="message_email1" model="mail.message">
<field name="subject">Re: Plan to buy a Laptop</field>
<field name="model">crm.lead</field>
<field name="res_id" ref="crm_case_15"/>
<field name="content_subtype">plain</field>
<field name="body_text">Yes, its open till 10:00 PM, you are welcome!</field>
<field name="type">email</field>
<field name="state">sent</field>
<field name="user_id" ref="base.user_root"/>
</record>
<record id="message_email_12" model="mail.message">
<field name="subject">Inquiry</field>
<field name="model">crm.lead</field>
<field name="res_id" ref="crm_case_1"/>
<field name="content_subtype">plain</field>
<field name="body_text">Hello,
I am Jason from Le Club SARL,
I am intertested to attend Training organized in your company,
Can you send details,</field>
<field name="type">email</field>
<field name="state">received</field>
<field name="user_id" ref="base.user_demo"/>
</record>
<record id="message_email_13" model="mail.message">
<field name="subject">Need Details</field>
<field name="model">crm.lead</field>
<field name="res_id" ref="crm_case_2"/>
<field name="content_subtype">plain</field>
<field name="body_text">Want to know features and benifits to use the new software.</field>
<field name="type">comment</field>
<field name="user_id" ref="base.user_demo"/>
</record>

View File

@ -2,9 +2,8 @@
<openerp>
<data>
<!-- Calendar Attendee Form View -->
<record id="attendee_form_view_inherit" model="ir.ui.view">
<!-- Calendar Attendee Form View -->
<record id="attendee_form_view_inherit" model="ir.ui.view">
<field name="name">calendar.attendee.form.inherit</field>
<field name="model">calendar.attendee</field>
<field name="inherit_id" ref="base_calendar.base_calendar_attendee_form_view"/>
@ -13,7 +12,7 @@
<field name="categ_id" string="Event Type"/>
</field>
</field>
</record>
</record>
</data>
</openerp>

File diff suppressed because it is too large Load Diff

View File

@ -35,8 +35,7 @@ The whole workflow is implemented:
* Draft expense
* Confirmation of the sheet by the employee
* Validation by his manager
* Validation by the accountant and invoice creation
* Payment of the invoice to the employee
* Validation by the accountant and receipt creation
This module also uses the analytic accounting and is compatible with
the invoice on timesheet module so that you will be able to automatically
@ -45,7 +44,7 @@ re-invoice your customer's expenses if your work by project.
'author': 'OpenERP SA',
'website': 'http://www.openerp.com',
'images': ['images/hr_expenses_analysis.jpeg', 'images/hr_expenses.jpeg'],
'depends': ['hr', 'account'],
'depends': ['hr', 'account_voucher'],
'data': [
'security/ir.model.access.csv',
'hr_expense_data.xml',

View File

@ -40,12 +40,16 @@ class hr_expense_expense(osv.osv):
if context is None:
context = {}
if not default: default = {}
default.update({'invoice_id': False, 'date_confirm': False, 'date_valid': False, 'user_valid': False})
default.update({'voucher_id': False, 'date_confirm': False, 'date_valid': False, 'user_valid': False})
return super(hr_expense_expense, self).copy(cr, uid, id, default, context=context)
def _amount(self, cr, uid, ids, field_name, arg, context=None):
cr.execute("SELECT s.id,COALESCE(SUM(l.unit_amount*l.unit_quantity),0) AS amount FROM hr_expense_expense s LEFT OUTER JOIN hr_expense_line l ON (s.id=l.expense_id) WHERE s.id IN %s GROUP BY s.id ", (tuple(ids),))
res = dict(cr.fetchall())
res= {}
for expense in self.browse(cr, uid, ids, context=context):
total = 0.0
for line in expense.line_ids:
total += line.unit_amount * line.unit_quantity
res[expense.id] = total
return res
def _get_currency(self, cr, uid, context=None):
@ -63,7 +67,7 @@ class hr_expense_expense(osv.osv):
'name': fields.char('Description', size=128, required=True),
'id': fields.integer('Sheet ID', readonly=True),
'date': fields.date('Date', select=True),
'journal_id': fields.many2one('account.journal', 'Force Journal', help = "The journal used when the expense is invoiced"),
'journal_id': fields.many2one('account.journal', 'Force Journal', help = "The journal used when the expense is done."),
'employee_id': fields.many2one('hr.employee', "Employee", required=True),
'user_id': fields.many2one('res.users', 'User', required=True),
'date_confirm': fields.date('Confirmation Date', select=True, help = "Date of the confirmation of the sheet expense. It's filled when the button Confirm is pressed."),
@ -73,7 +77,7 @@ class hr_expense_expense(osv.osv):
'line_ids': fields.one2many('hr.expense.line', 'expense_id', 'Expense Lines', readonly=True, states={'draft':[('readonly',False)]} ),
'note': fields.text('Note'),
'amount': fields.function(_amount, string='Total Amount', digits_compute= dp.get_precision('Account')),
'invoice_id': fields.many2one('account.invoice', "Employee's Invoice"),
'voucher_id': fields.many2one('account.voucher', "Employee's Receipt"),
'currency_id': fields.many2one('res.currency', 'Currency', required=True),
'department_id':fields.many2one('hr.department','Department'),
'company_id': fields.many2one('res.company', 'Company', required=True),
@ -82,11 +86,10 @@ class hr_expense_expense(osv.osv):
('cancelled', 'Refused'),
('confirm', 'Waiting Approval'),
('accepted', 'Approved'),
('invoiced', 'Invoiced'),
('paid', 'Reimbursed')
('done', 'Done'),
],
'Status', readonly=True, help='When the expense request is created the status is \'Draft\'.\n It is confirmed by the user and request is sent to admin, the status is \'Waiting Confirmation\'.\
\nIf the admin accepts it, the status is \'Accepted\'.\n If an invoice is made for the expense request, the status is \'Invoiced\'.\n If the expense is paid to user, the status is \'Reimbursed\'.'),
\nIf the admin accepts it, the status is \'Accepted\'.\n If a receipt is made for the expense request, the status is \'Done\'.'),
}
_defaults = {
'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'hr.employee', context=c),
@ -97,6 +100,13 @@ class hr_expense_expense(osv.osv):
'currency_id': _get_currency,
}
def onchange_currency_id(self, cr, uid, ids, currency_id=False, company_id=False, context=None):
res = {'value': {'journal_id': False}}
journal_ids = self.pool.get('account.journal').search(cr, uid, [('type','=','purchase'), ('currency','=',currency_id), ('company_id', '=', company_id)], context=context)
if journal_ids:
res['value']['journal_id'] = journal_ids[0]
return res
def onchange_employee_id(self, cr, uid, ids, employee_id, context=None):
emp_obj = self.pool.get('hr.employee')
department_id = False
@ -126,101 +136,94 @@ class hr_expense_expense(osv.osv):
self.write(cr, uid, ids, {'state':'cancelled'})
return True
def expense_paid(self, cr, uid, ids, *args):
self.write(cr, uid, ids, {'state':'paid'})
return True
def invoice(self, cr, uid, ids, context=None):
wf_service = netsvc.LocalService("workflow")
mod_obj = self.pool.get('ir.model.data')
res = mod_obj.get_object_reference(cr, uid, 'account', 'invoice_supplier_form')
inv_ids = []
for id in ids:
wf_service.trg_validate(uid, 'hr.expense.expense', id, 'invoice', cr)
inv_ids.append(self.browse(cr, uid, id).invoice_id.id)
return {
'name': _('Supplier Invoices'),
'view_type': 'form',
'view_mode': 'form',
'view_id': [res and res[1] or False],
'res_model': 'account.invoice',
'context': "{'type':'out_invoice', 'journal_type': 'purchase'}",
'type': 'ir.actions.act_window',
'nodestroy': True,
'target': 'current',
'res_id': inv_ids and inv_ids[0] or False,
}
def action_invoice_create(self, cr, uid, ids):
res = False
invoice_obj = self.pool.get('account.invoice')
def action_receipt_create(self, cr, uid, ids, context=None):
property_obj = self.pool.get('ir.property')
sequence_obj = self.pool.get('ir.sequence')
analytic_journal_obj = self.pool.get('account.analytic.journal')
account_journal = self.pool.get('account.journal')
for exp in self.browse(cr, uid, ids):
voucher_obj = self.pool.get('account.voucher')
currency_obj = self.pool.get('res.currency')
wkf_service = netsvc.LocalService("workflow")
if context is None:
context = {}
for exp in self.browse(cr, uid, ids, context=context):
company_id = exp.company_id.id
lines = []
for l in exp.line_ids:
tax_id = []
if l.product_id:
acc = l.product_id.product_tmpl_id.property_account_expense
total = 0.0
ctx = context.copy()
ctx.update({'date': exp.date})
journal = False
if exp.journal_id:
journal = exp.journal_id
else:
journal_id = voucher_obj._get_journal(cr, uid, context={'type': 'purchase', 'company_id': company_id})
if journal_id:
journal = account_journal.browse(cr, uid, journal_id, context=context)
for line in exp.line_ids:
if line.product_id:
acc = line.product_id.product_tmpl_id.property_account_expense
if not acc:
acc = l.product_id.categ_id.property_account_expense_categ
tax_id = [x.id for x in l.product_id.supplier_taxes_id]
acc = line.product_id.categ_id.property_account_expense_categ
else:
acc = property_obj.get(cr, uid, 'property_account_expense_categ', 'product.category', context={'force_company': company_id})
if not acc:
raise osv.except_osv(_('Error!'), _('Please configure Default Expense account for Product purchase: `property_account_expense_categ`.'))
total_amount = line.total_amount
if journal.currency:
if exp.currency_id != journal.currency:
total_amount = currency_obj.compute(cr, uid, exp.currency_id.id, journal.currency.id, total_amount, context=ctx)
elif exp.currency_id != exp.company_id.currency_id:
total_amount = currency_obj.compute(cr, uid, exp.currency_id.id, exp.company_id.currency_id.id, total_amount, context=ctx)
lines.append((0, False, {
'name': l.name,
'name': line.name,
'account_id': acc.id,
'price_unit': l.unit_amount,
'quantity': l.unit_quantity,
'uos_id': l.uom_id.id,
'product_id': l.product_id and l.product_id.id or False,
'invoice_line_tax_id': tax_id and [(6, 0, tax_id)] or False,
'account_analytic_id': l.analytic_account.id,
'account_analytic_id': line.analytic_account.id,
'amount': total_amount,
'type': 'dr'
}))
total += total_amount
if not exp.employee_id.address_home_id:
raise osv.except_osv(_('Error!'), _('The employee must have a home address.'))
acc = exp.employee_id.address_home_id.property_account_payable.id
payment_term_id = exp.employee_id.address_home_id.property_payment_term.id
inv = {
voucher = {
'name': exp.name,
'reference': sequence_obj.get(cr, uid, 'hr.expense.invoice'),
'account_id': acc,
'type': 'in_invoice',
'type': 'purchase',
'partner_id': exp.employee_id.address_home_id.id,
'company_id': company_id,
'origin': exp.name,
'invoice_line': lines,
'currency_id': exp.currency_id.id,
'payment_term': payment_term_id,
'fiscal_position': exp.employee_id.address_home_id.property_account_position.id
'line_ids': lines,
'amount': total,
'journal_id': journal.id,
}
if payment_term_id:
to_update = invoice_obj.onchange_payment_term_date_invoice(cr, uid, [], payment_term_id, None)
if to_update:
inv.update(to_update['value'])
journal = False
if exp.journal_id:
inv['journal_id']=exp.journal_id.id
journal = exp.journal_id
else:
journal_id = invoice_obj._get_journal(cr, uid, context={'type': 'in_invoice', 'company_id': company_id})
if journal_id:
inv['journal_id'] = journal_id
journal = account_journal.browse(cr, uid, journal_id)
if journal and not journal.analytic_journal_id:
analytic_journal_ids = analytic_journal_obj.search(cr, uid, [('type','=','purchase')])
analytic_journal_ids = analytic_journal_obj.search(cr, uid, [('type','=','purchase')], context=context)
if analytic_journal_ids:
account_journal.write(cr, uid, [journal.id],{'analytic_journal_id':analytic_journal_ids[0]})
inv_id = invoice_obj.create(cr, uid, inv, {'type': 'in_invoice'})
invoice_obj.button_compute(cr, uid, [inv_id], {'type': 'in_invoice'}, set_total=True)
self.write(cr, uid, [exp.id], {'invoice_id': inv_id, 'state': 'invoiced'})
res = inv_id
return res
account_journal.write(cr, uid, [journal.id], {'analytic_journal_id': analytic_journal_ids[0]}, context=context)
voucher_id = voucher_obj.create(cr, uid, voucher, context=context)
wkf_service.trg_validate(uid, 'account.voucher', voucher_id, 'proforma_voucher', cr)
self.write(cr, uid, [exp.id], {'voucher_id': voucher_id, 'state': 'done'}, context=context)
return True
def action_view_receipt(self, cr, uid, ids, context=None):
'''
This function returns an action that display existing receipt of given expense ids.
'''
assert len(ids) == 1, 'This option should only be used for a single id at a time'
voucher_id = self.browse(cr, uid, ids[0], context=context).voucher_id.id
res = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account_voucher', 'view_purchase_receipt_form')
result = {
'name': _('Expense Receipt'),
'view_type': 'form',
'view_mode': 'form',
'view_id': res and res[1] or False,
'res_model': 'account.voucher',
'type': 'ir.actions.act_window',
'nodestroy': True,
'target': 'current',
'res_id': voucher_id,
}
return result
hr_expense_expense()

View File

@ -31,7 +31,7 @@
<field name="date"/>
<field name="user_id" invisible="1"/>
<field name="name"/>
<field name="currency_id"/>
<field name="currency_id" groups="base.group_multi_currency"/>
<field name="amount"/>
<field name="state"/>
</tree>
@ -42,7 +42,7 @@
<field name="name">hr.expense.expense.tree</field>
<field name="model">hr.expense.expense</field>
<field name="arch" type="xml">
<tree colors="blue:state == 'draft';black:state in ('confirm','accepted','invoiced','paid');gray:state == 'cancelled'" string="Expenses" editable="top">
<tree colors="blue:state == 'draft';black:state in ('confirm','accepted','done');gray:state == 'cancelled'" string="Expenses" editable="top">
<field name="employee_id"/>
<field name="date"/>
<field name="department_id"/>
@ -64,9 +64,10 @@
<button name="confirm" states="draft" string="Submit to Manager" type="workflow" class="oe_highlight"/>
<button name="validate" states="confirm" string="Approve" type="workflow" groups="base.group_hr_user" class="oe_highlight"/>
<button name="draft" states="confirm,cancelled" string="Set to Draft" type="workflow" groups="base.group_hr_user" />
<button name="invoice" states="accepted" string="Invoice" type="object" groups="base.group_hr_user" class="oe_highlight"/>
<button name="done" states="accepted" string="Generate Accounting Entries" type="workflow" groups="account.group_account_invoice" class="oe_highlight"/>
<button name="action_view_receipt" states="done" string="Open Receipt" type="object"/>
<button name="refuse" states="confirm,accepted" string="Refuse" type="workflow" groups="base.group_hr_user" />
<field name="state" widget="statusbar" statusbar_visible="draft,confirm,accepted" statusbar_colors='{"confirm":"blue","cancelled":"red"}'/>
<field name="state" widget="statusbar" statusbar_visible="draft,confirm,accepted,done" statusbar_colors='{"confirm":"blue","cancelled":"red"}'/>
</header>
<sheet>
<group>
@ -79,7 +80,7 @@
<group>
<field name="name"/>
<field name="user_valid"/>
<field name="currency_id"/>
<field name="currency_id" groups="base.group_multi_currency" on_change="onchange_currency_id(currency_id, company_id)"/>
</group>
</group>
<notebook>
@ -105,14 +106,21 @@
</group>
</form>
</field>
<separator string="Notes"/>
<field name="note" placeholder="Free Notes"/>
<group>
<div>
<separator string="Notes"/>
<field name="note" placeholder="Free Notes"/>
</div>
<group class="oe_subtotal_footer">
<field name="amount"/>
</group>
</group>
</page>
<page string="Other Info">
<group>
<group string="Accounting Data">
<field name="journal_id"/>
<field name="invoice_id" context="{'type':'in_invoice', 'journal_type': 'purchase'}"/>
<field name="journal_id" widget="selection" domain="[('type', '=', 'purchase')]"/>
<field name="voucher_id" context="{'form_view_ref': 'account_voucher.view_purchase_receipt_form'}"/>
</group>
</group>
</page>

View File

@ -32,14 +32,6 @@
<field name="action">expense_accept()</field>
</record>
<record id="act_paid" model="workflow.activity">
<field name="wkf_id" ref="wkf_expenses"/>
<field name="name">paid</field>
<field name="kind">function</field>
<field name="action">expense_paid()</field>
<field name="flow_stop">True</field>
</record>
<record id="act_refused" model="workflow.activity">
<field name="wkf_id" ref="wkf_expenses"/>
<field name="name">refused</field>
@ -47,12 +39,11 @@
<field name="action">expense_canceled()</field>
</record>
<record id="act_invoice" model="workflow.activity">
<record id="act_done" model="workflow.activity">
<field name="wkf_id" ref="wkf_expenses"/>
<field name="name">invoice</field>
<field name="kind">subflow</field>
<field name="subflow_id" ref="account.wkf"/>
<field name="action">action_invoice_create()</field>
<field name="name">done</field>
<field name="kind">function</field>
<field name="action">action_receipt_create()</field>
</record>
<record id="t1" model="workflow.transition">
@ -91,15 +82,8 @@
<record id="t8" model="workflow.transition">
<field name="act_from" ref="act_accepted"/>
<field name="act_to" ref="act_invoice"/>
<field name="signal">invoice</field>
<field name="group_id" ref="base.group_hr_user"/>
</record>
<record id="t9" model="workflow.transition">
<field name="act_from" ref="act_invoice"/>
<field name="act_to" ref="act_paid"/>
<field name="signal">subflow.paid</field>
<field name="act_to" ref="act_done"/>
<field name="signal">done</field>
<field name="group_id" ref="base.group_hr_user"/>
</record>

View File

@ -39,11 +39,10 @@ class hr_expense_report(osv.osv):
'product_id':fields.many2one('product.product', 'Product', readonly=True),
'journal_id': fields.many2one('account.journal', 'Force Journal', readonly=True),
'product_qty':fields.float('Qty', readonly=True),
'invoiced':fields.integer('# of Invoiced Lines', readonly=True),
'employee_id': fields.many2one('hr.employee', "Employee's Name", readonly=True),
'date_confirm': fields.date('Confirmation Date', readonly=True),
'date_valid': fields.date('Validation Date', readonly=True),
'invoice_id': fields.many2one('account.invoice', 'Invoice', readonly=True),
'voucher_id': fields.many2one('account.voucher', 'Receipt', readonly=True),
'department_id':fields.many2one('hr.department','Department', readonly=True),
'company_id':fields.many2one('res.company', 'Company', readonly=True),
'user_id':fields.many2one('res.users', 'Validation User', readonly=True),
@ -60,8 +59,7 @@ class hr_expense_report(osv.osv):
('draft', 'Draft'),
('confirm', 'Waiting confirmation'),
('accepted', 'Accepted'),
('invoiced', 'Invoiced'),
('paid', 'Reimbursed'),
('done', 'Done'),
('cancelled', 'Cancelled')],
'Status', readonly=True),
}
@ -78,8 +76,7 @@ class hr_expense_report(osv.osv):
s.currency_id,
to_date(to_char(s.date_confirm, 'dd-MM-YYYY'),'dd-MM-YYYY') as date_confirm,
to_date(to_char(s.date_valid, 'dd-MM-YYYY'),'dd-MM-YYYY') as date_valid,
s.invoice_id,
count(s.invoice_id) as invoiced,
s.voucher_id,
s.user_valid as user_id,
s.department_id,
to_char(date_trunc('day',s.create_date), 'YYYY') as year,
@ -109,7 +106,7 @@ class hr_expense_report(osv.osv):
to_date(to_char(s.date_valid, 'dd-MM-YYYY'),'dd-MM-YYYY'),
l.product_id,
l.analytic_account,
s.invoice_id,
s.voucher_id,
s.currency_id,
s.user_valid,
s.department_id,

View File

@ -6,13 +6,13 @@
<field name="name">hr.expense.report.tree</field>
<field name="model">hr.expense.report</field>
<field name="arch" type="xml">
<tree colors="blue:state == 'draft';black:state in ('confirm','accepted','invoiced','paid');gray:state == 'cancelled'" string="Expenses Analysis">
<tree colors="blue:state == 'draft';black:state in ('confirm','accepted','done');gray:state == 'cancelled'" string="Expenses Analysis">
<field name="employee_id" invisible="1"/>
<field name="user_id" invisible="1"/>
<field name="year" invisible="1"/>
<field name="month" invisible="1"/>
<field name="day" invisible="1"/>
<field name="invoice_id" invisible="1"/>
<field name="voucher_id" invisible="1"/>
<field name="analytic_account" invisible="1" groups="analytic.group_analytic_accounting"/>
<field name="department_id" invisible="1"/>
<field name="company_id" invisible="1"/>
@ -22,7 +22,6 @@
<field name="state" invisible="1"/>
<field name="nbr" sum="# of Lines"/>
<field name="no_of_products" sum="# of Products"/>
<field name="invoiced" sum="Total Invoiced Lines"/>
<field name="price_average" avg="Average Price"/>
<field name="price_total" sum="Total Price"/>
<field name="delay_confirm"/>
@ -50,7 +49,7 @@
<search string="Expenses Analysis">
<filter string="Waiting" icon="terp-gtk-media-pause" domain="[('state', '=' ,'confirm')]" help = "Confirm Expenses"/>
<filter string="Approved" icon="terp-check" domain="[('state','=','accepted')]" help = "Approved Expenses"/>
<filter string="Invoiced" icon="terp-dolar" domain="[('state','in', ('invoiced', 'paid'))]" help = "Invoiced Expenses"/>
<filter string="Done" icon="terp-dolar" domain="[('state','=', 'done')]" help = "Done Expenses"/>
<field name="employee_id"/>
<field name="department_id"/>
<group expand="0" string="Extended Filters...">

View File

@ -17,33 +17,20 @@
!assert {model: hr.expense.expense, id: sep_expenses, severity: error, string: Expense should be in Approved state}:
- state == 'accepted'
-
I make Invoice for the expense.
I make Receipt for the expense.
-
!python {model: hr.expense.expense}: |
self.invoice(cr, uid, [ref('sep_expenses')])
!workflow {model: hr.expense.expense, action: done, ref: sep_expenses}
-
I check invoice details.
I check receipt details.
-
!python {model: hr.expense.expense}: |
sep_expenses = self.browse(cr, uid, ref("sep_expenses"), context=context)
assert sep_expenses.state == 'invoiced', "Expense should be in 'Invoiced' state."
assert sep_expenses.invoice_id, "Expense should have link of Invoice."
assert sep_expenses.invoice_id.currency_id == sep_expenses.currency_id,"Invoice currency is not correspond with supplier invoice currency"
assert sep_expenses.invoice_id.origin == sep_expenses.name,"Invoice origin is not correspond with supplier invoice"
assert sep_expenses.invoice_id.type == 'in_invoice', "Invoice type is not supplier invoice"
assert sep_expenses.invoice_id.amount_total == sep_expenses.amount,"Invoice total amount is not correspond with supplier invoice total"
assert len(sep_expenses.invoice_id.invoice_line) == len(sep_expenses.line_ids),"Lines of Invoice and supplier invoice Line are not correspond"
#TODO: check invoice line details with Expenses lines
-
I pay the expenses.
-
!python {model: hr.expense.expense}: |
self.expense_paid(cr, uid, [ref('sep_expenses')])
-
I check that state of expenses is 'Paid'.
-
!assert {model: hr.expense.expense, id: sep_expenses, severity: error, string: Expense should be in Paid state}:
- state == 'paid'
assert sep_expenses.state == 'done', "Expense should be in 'Done' state."
assert sep_expenses.voucher_id, "Expense should have link of Purchase Receipt."
assert sep_expenses.voucher_id.type == 'purchase', "Receipt type is not purchase receipt."
assert sep_expenses.voucher_id.amount == sep_expenses.amount,"Receipt total amount is not correspond with expense total."
assert len(sep_expenses.voucher_id.line_dr_ids) == len(sep_expenses.line_ids),"Lines of Receipt and expense line are not correspond."
-
I duplicate the expenses and cancel duplicated.
-

View File

@ -274,7 +274,7 @@ class hr_holidays(osv.osv):
'date': record.date_from,
'end_date': record.date_to,
'date_deadline': record.date_to,
'state': 'done', # to block that meeting date in the calendar
'state': 'open', # to block that meeting date in the calendar
}
meeting_id = meeting_obj.create(cr, uid, meeting_vals)
self._create_resource_leave(cr, uid, [record], context=context)

View File

@ -1,7 +1,8 @@
<?xml version="1.0" ?>
<openerp>
<data noupdate="1">
<!-- notify all employees of module installation -->
<!-- Notify all employees of module installation -->
<function model="mail.group" name="message_append_note">
<!-- ids, subject, body, parent_id=False, type='notification', content_subtype='html' -->
<value eval="[ref('mail.group_all_employees')]"/>
@ -20,13 +21,7 @@
<field eval="&quot;&quot;&quot;My Timesheet&quot;&quot;&quot;" name="name"/>
</record>
<record id="menu_act_hr_timesheet_sheet_form_my_current" model="ir.ui.menu">
<field name="name">My Current Timesheet</field>
<field eval="1" name="sequence"/>
<field name="parent_id" ref="hr_attendance.menu_hr_time_tracking"/>
<field name="icon">STOCK_JUSTIFY_FILL</field>
<field name="action" ref="ir_actions_server_timsheet_sheet"/>
</record>
<menuitem name="My Current Timesheet" id="menu_act_hr_timesheet_sheet_form_my_current" parent="hr_attendance.menu_hr_time_tracking" action="ir_actions_server_timsheet_sheet" sequence="1"/>
</data>
</openerp>

View File

@ -25,7 +25,7 @@
-
!record {model: account.invoice.line, id: dta_invoice_line, view: False}:
account_id: account.a_expense
name: '[PC1] Basic PC'
name: '[PCSC234] PC Assemble SC234'
price_unit: 700.0
quantity: 10.0
product_id: product.product_product_3

View File

@ -26,7 +26,7 @@
-
!record {model: account.invoice.line, id: v11_test_invoice_line, view: False}:
account_id: account.a_expense
name: '[PC1] Basic PC'
name: '[PCSC234] PC Assemble SC234'
price_unit: 888.00
quantity: 1.0
product_id: product.product_product_3

View File

@ -27,7 +27,7 @@
-
!record {model: account.invoice.line, id: v11_part_test_invoice_line, view: False}:
account_id: account.a_expense
name: '[PC1] Basic PC'
name: '[PCSC234] PC Assemble SC234'
price_unit: 250.00
quantity: 1.0
product_id: product.product_product_3

View File

@ -8,21 +8,11 @@
groups="base.group_user"
sequence="10"/>
<!-- left-side menu: Feeds !-->
<!-- Left-side menu: Feeds -->
<menuitem id="mail_feeds" name="Feeds" parent="mail_feeds_main" groups="base.group_user" sequence="10"/>
<record id="mail_wallfeeds" model="ir.ui.menu">
<field name="name">News Feed</field>
<field name="sequence" eval="10"/>
<field name="action" ref="action_mail_all_feeds"/>
<field name="parent_id" ref="mail_feeds"/>
</record>
<record id="mail_myfeeds" model="ir.ui.menu">
<field name="name">My Feeds</field>
<field name="sequence" eval="11"/>
<field name="action" ref="action_mail_my_feeds"/>
<field name="parent_id" ref="mail_feeds"/>
</record>
<menuitem id="mail_wallfeeds" name="News Feed" parent="mail_feeds" action="action_mail_all_feeds" sequence="10"/>
<menuitem id="mail_myfeeds" name="My Feeds" parent="mail_feeds" action="action_mail_my_feeds" sequence="11"/>
</data>
</openerp>

View File

@ -6,9 +6,10 @@
<field name="model">base.config.settings</field>
<field name="inherit_id" ref="base_setup.view_general_configuration"/>
<field name="arch" type="xml">
<xpath expr="/form/group[last()]/div[last()]/div[last()]" position='after' version="7.0">
<xpath expr="//div[@name='email']" position='inside'>
<div>
<label for="alias_domain" class="oe_inline"/><field name="alias_domain" placeholder="mycompany.my.openerp.com" class="oe_inline"/>
<label for="alias_domain" class="oe_inline"/>
<field name="alias_domain" placeholder="mycompany.my.openerp.com" class="oe_inline"/>
</div>
</xpath>
</field>

View File

@ -838,13 +838,13 @@ openerp.mail = function(session) {
init: function() {
this._super.apply(this, arguments);
this.params = this.get_definition_options();
this.params = this.options;
this.params.thread_level = this.params.thread_level || 0;
this.thread = null;
this.ds = new session.web.DataSet(this, this.view.model);
this.ds_users = new session.web.DataSet(this, 'res.users');
},
start: function() {
// NB: all the widget should be modified to check the actual_mode property on view, not use
// any other method to know if the view is in create mode anymore
@ -858,7 +858,6 @@ openerp.mail = function(session) {
this.$element.find('button.oe_mail_button_unfollow').click(function () { self.do_unfollow(); })
.mouseover(function () { $(this).html('Unfollow').removeClass('oe_mail_button_mouseout').addClass('oe_mail_button_mouseover'); })
.mouseleave(function () { $(this).html('Following').removeClass('oe_mail_button_mouseover').addClass('oe_mail_button_mouseout'); });
this.reinit();
},
_check_visibility: function() {

View File

@ -5,7 +5,7 @@
date_expected: !eval datetime.today().strftime("%Y-%m-%d %H:%M:%S")
location_dest_id: stock.stock_location_14
location_id: stock.stock_location_stock
name: '[PC1] Basic PC'
name: '[PCSC234] PC Assemble SC234'
product_id: product.product_product_3
product_qty: 1.0
product_uom: product.product_uom_unit
@ -23,7 +23,7 @@
operations:
- location_dest_id: stock.location_production
location_id: stock.stock_location_stock
name: '[HDD1] HDD Seagate 7200.8 80GB'
name: '[M-Las] Mouse, Laser'
price_unit: 50.0
product_id: product.product_product_11
product_uom: product.product_uom_unit
@ -32,7 +32,7 @@
to_invoice: 1
type: add
fees_lines:
- name: 'HDD1 Seagate repair fees'
- name: 'Mouse repair fees'
product_id: product.product_product_11
product_uom_qty: 1.0
product_uom: product.product_uom_unit
@ -46,7 +46,7 @@
date_expected: !eval datetime.today().strftime("%Y-%m-%d %H:%M:%S")
location_dest_id: stock.stock_location_14
location_id: stock.stock_location_stock
name: '[PC3] Medium PC'
name: '[PC-DEM] PC on Demand'
product_id: product.product_product_5
product_qty: 1.0
product_uom: product.product_uom_unit
@ -64,7 +64,7 @@
operations:
- location_dest_id: stock.location_production
location_id: stock.stock_location_stock
name: '[HDD2] HDD Seagate 7200.8 120GB'
name: '[M-Wir] Mouse, Wireless'
price_unit: 50.0
product_id: product.product_product_12
product_uom: product.product_uom_unit
@ -73,7 +73,7 @@
to_invoice: 1
type: add
fees_lines:
- name: 'HDD2 Seagate repair fees'
- name: 'Mouse Seagate repair fees'
product_id: product.product_product_12
product_uom_qty: 1.0
product_uom: product.product_uom_unit
@ -87,7 +87,7 @@
date_expected: !eval datetime.today().strftime("%Y-%m-%d %H:%M:%S")
location_dest_id: stock.stock_location_14
location_id: stock.stock_location_stock
name: '[PC4] Customizable PC'
name: '[LCD15] 15” LCD Monitor'
product_id: product.product_product_6
product_qty: 1.0
product_uom: product.product_uom_unit
@ -105,7 +105,7 @@
operations:
- location_dest_id: stock.location_production
location_id: stock.stock_location_stock
name: '[HDD3] HDD Seagate 7200.8 160GB'
name: '[RAM-SR5] RAM DDR SR5'
price_unit: 50.0
product_id: product.product_product_13
product_uom: product.product_uom_unit
@ -114,7 +114,7 @@
to_invoice: 1
type: add
fees_lines:
- name: 'HDD3 Seagate repair fees'
- name: 'RAM fees'
product_id: product.product_product_13
product_uom_qty: 1.0
product_uom: product.product_uom_unit

View File

@ -1,6 +1,6 @@
-
In order to test the cancel flow of mrp_repair module,
I start by creating new copy Repair order for "Basic PC" product.
I start by creating new copy Repair order for "PC Assemble SC234" product.
-
!python {model: mrp.repair}: |
copy_id = self.copy(cr, uid, ref("mrp_repair_rmrp1"))

View File

@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-08 00:36+0000\n"
"PO-Revision-Date: 2010-10-26 08:32+0000\n"
"PO-Revision-Date: 2012-08-17 11:09+0000\n"
"Last-Translator: Chertykov Denis <chertykov@gmail.com>\n"
"Language-Team: Russian <ru@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-08-07 05:25+0000\n"
"X-Generator: Launchpad (build 15745)\n"
"X-Launchpad-Export-Date: 2012-08-18 04:58+0000\n"
"X-Generator: Launchpad (build 15810)\n"
#. module: multi_company
#: model:res.company,overdue_msg:multi_company.res_company_odoo
@ -68,7 +68,7 @@ msgstr "Возвращаемое"
#. module: multi_company
#: model:ir.ui.menu,name:multi_company.menu_custom_multicompany
msgid "Multi-Companies"
msgstr ""
msgstr "Холдинги"
#. module: multi_company
#: view:multi_company.default:0

View File

@ -79,18 +79,17 @@ Main features:
],
'installable': True,
'application': True,
# Web client
'js': [
'static/lib/backbone/backbone-0.9.2.js',
'static/lib/mousewheel/jquery.mousewheel-3.0.6.js',
'static/src/js/pos_models.js',
'static/src/js/pos_basewidget.js',
'static/src/js/pos_keyboard_widget.js',
'static/src/js/pos_scrollbar_widget.js',
'static/src/js/pos_widgets.js',
'static/src/js/pos_devices.js',
'static/src/js/pos_screens.js',
'static/src/js/pos_main.js'
'static/src/js/db.js',
'static/src/js/models.js',
'static/src/js/widget_base.js',
'static/src/js/widget_keyboard.js',
'static/src/js/widget_scrollbar.js',
'static/src/js/widgets.js',
'static/src/js/devices.js',
'static/src/js/screens.js',
'static/src/js/main.js',
],
'css': [
'static/src/css/pos.css',
@ -99,4 +98,5 @@ Main features:
'qweb': ['static/src/xml/pos.xml'],
'auto_install': False,
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -34,8 +34,8 @@ class account_journal(osv.osv):
'self_checkout_payment_method' : fields.boolean('Self Checkout Payment Method'),
}
_defaults = {
'opening_control' : True,
'closing_control' : True,
'opening_control' : False,
'closing_control' : False,
'self_checkout_payment_method' : False,
}

View File

@ -0,0 +1,3 @@
import main
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,14 +1,71 @@
# -*- coding: utf-8 -*-
import logging
import simplejson
import os
import openerp
try:
import openerp.addons.web.common.http as openerpweb
from openerp.addons.web.controllers.main import manifest_list, module_boot, html_template
except ImportError:
import web.common.http as openerpweb
class PointOfSaleController(openerpweb.Controller):
_cp_path = '/pos'
@openerpweb.httprequest
def app(self, req, s_action=None, **kw):
js = "\n ".join('<script type="text/javascript" src="%s"></script>' % i for i in manifest_list(req, None, 'js'))
css = "\n ".join('<link rel="stylesheet" href="%s">' % i for i in manifest_list(req, None, 'css'))
cookie = req.httprequest.cookies.get("instance0|session_id")
session_id = cookie.replace("%22","")
template = html_template.replace('<html','<html manifest="/pos/manifest?session_id=%s"'%session_id)
r = template % {
'js': js,
'css': css,
'modules': simplejson.dumps(module_boot(req)),
'init': 'var wc = new s.web.WebClient();wc.appendTo($(document.body));'
}
return r
@openerpweb.httprequest
def manifest(self, req, **kwargs):
""" This generates a HTML5 cache manifest files that preloads the categories and products thumbnails
and other ressources necessary for the point of sale to work offline """
ml = ["CACHE MANIFEST"]
# loading all the images in the static/src/img/* directories
def load_css_img(srcdir,dstdir):
for f in os.listdir(srcdir):
path = os.path.join(srcdir,f)
dstpath = os.path.join(dstdir,f)
if os.path.isdir(path) :
load_css_img(path,dstpath)
elif f.endswith(('.png','.PNG','.jpg','.JPG','.jpeg','.JPEG','.gif','.GIF')):
ml.append(dstpath)
imgdir = openerp.modules.get_module_resource('point_of_sale','static/src/img');
load_css_img(imgdir,'/point_of_sale/static/src/img')
products = req.session.model('product.product')
for p in products.search_read([('pos_categ_id','!=',False)], ['name']):
product_id = p['id']
url = "/web/binary/image?session_id=%s&model=product.product&field=image&id=%s" % (req.session_id, product_id)
ml.append(url)
categories = req.session.model('pos.category')
for c in categories.search_read([],['name']):
category_id = c['id']
url = "/web/binary/image?session_id=%s&model=pos.category&field=image&id=%s" % (req.session_id, category_id)
ml.append(url)
ml += ["NETWORK:","*"]
m = "\n".join(ml)
return m
@openerpweb.jsonrequest
def dispatch(self, request, iface, **kwargs):
method = 'iface_%s' % iface
@ -109,3 +166,4 @@ class PointOfSaleController(openerpweb.Controller):
print 'print_receipt' + str(receipt)
return

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 75 KiB

View File

@ -0,0 +1,506 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="744.09448819"
height="1052.3622047"
id="svg2"
version="1.1"
inkscape:version="0.48.3.1 r9886"
sodipodi:docname="barcode_test_sheet.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="1"
inkscape:pageshadow="2"
inkscape:zoom="0.86706515"
inkscape:cx="381.55817"
inkscape:cy="597.57819"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1301"
inkscape:window-height="744"
inkscape:window-x="1985"
inkscape:window-y="24"
inkscape:window-maximized="1" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<g
id="g4158"
transform="matrix(0.92675002,0,0,0.92675002,27.597551,38.360312)">
<text
sodipodi:linespacing="125%"
id="text2985"
y="72.12545"
x="140.86679"
style="font-size:46.51613998px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
xml:space="preserve"><tspan
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium"
y="72.12545"
x="140.86679"
id="tspan2987"
sodipodi:role="line">BARCODE TEST SHEET</tspan></text>
<g
transform="translate(0,-5.3405762e-5)"
id="g3347">
<image
y="124.14642"
x="388.17426"
id="image3083"
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOYAAACYCAIAAAB/IAJdAAAAA3NCSVQICAjb4U/gAAADqUlEQVR4 nO3a226jMABFUYjy/7/MPCChDBfHARM4zVpP1dQY4u66JEw/DEO3S9/3XdeNh79+vRwz2hr5OmZr 5JH5t0YuX0X51W0dW3PNNa9xOWf5au8wsrxu9Ud95HHkYPg+yRJGsoSRLGEkSxjJEkayhJEsYSRL GMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJEkayhJEsYSRLGMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJ EkayhJEsYSRLGMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJEkayhJEsYSRLGMkSRrKEkSxhJEsYyRJG soSRLGEkSxjJEkayhJEsYSRLGMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJEkayhJEsYSRLGMkSRrKE kSxhJEsYyRJGsoSRLGEkSxjJEkayhJEsYSRLGMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJEkayhJEs YSRLGMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJEkayhJEsYSRLGMkSRrKEkSxhJEsYyRJGsoSRLGEk SxjJEkayhJEsYSRLGMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJEkayhJEsYSRLGMkSRrKEkSxh+mEY dh7Z913XjYe/fv3LrMlS8zWxyxJGsjuNGwbf91z911Z/1GY/18oJ689+cOTb7LZmPtJr/ZrcfOTu 1SucseaQlV221f6xnKdm5vqznzHy1Nn6vq9fk5SRrYzzry7RzHyXbd7r9HszXVP9b3bN/EdGlq/k 0y250uu0W2tSv3oXjvxo9d4ahmE6b/mP58ou2/BNbuUr3Hf2M0aOzthRVn8M5QurX71rR87sXr1h GGa/z6tT/Zds2y12+TpfP+nYOqp+/rYjZ4esXvzk0zkLlmtSv3rXjlx1/F3QMtzZgMfse1d9jvi1 t1zND2HScPUK2+2j+cl2uEmvl/Bh2Zat+4TH64gLruvzs599C3vSOqz+Yf1LvZ60estwn93ehdt9 ez5zk1vYJmrW5C9lWqNVJ5OnW4IjR31kudFWvqe5v/NWb/lE47n6jbOvo3xZhbOfMfLL7n97fRNb j9/WH9j+mgtTtsUW5hzNN6PVMx2/iMLO93ba798tfPp6d398tjyqfqFu+I9vv7XD1uY6Ofd/ci0/ o/5ZhR/2lvrVu3ZkK7PPX7ee2px1YzA9Mn77S/Nrlj/+1UdNlat37ciGCncCMyfuslsP/e7mazey y22j8Pi3fvWuHdlq9cYZap6H/wMqFm5yx9XfMwAAAABJRU5ErkJggg== "
height="107.96206"
width="163.36363" />
<rect
style="fill:none;stroke:#c3c3c3;stroke-width:1.07903957;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="rect3086"
width="417.54495"
height="126.4052"
x="141.08452"
y="114.51707"
rx="2.0839903"
ry="2.0839903" />
<text
xml:space="preserve"
style="font-size:27.53050041px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
x="201.46712"
y="147.73848"
id="text3856"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan3858"
x="201.46712"
y="147.73848"
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">CASHIER</tspan></text>
<text
sodipodi:linespacing="125%"
id="text3860"
y="182.10147"
x="154.5717"
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
xml:space="preserve"><tspan
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium"
y="182.10147"
x="154.5717"
id="tspan3862"
sodipodi:role="line">Prefix: 40</tspan></text>
<text
xml:space="preserve"
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
x="154.5717"
y="200.85837"
id="text3149"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan3151"
x="154.5717"
y="200.85837"
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">ID: 4447190000</tspan></text>
</g>
<g
transform="translate(0,0.97855377)"
id="g3335">
<g
id="g3173"
transform="translate(0,-3.2620699)">
<text
sodipodi:linespacing="125%"
id="text3153"
y="341.12738"
x="154.5717"
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
xml:space="preserve"><tspan
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium"
y="341.12738"
x="154.5717"
id="tspan3155"
sodipodi:role="line">Prefix: 42</tspan></text>
<text
xml:space="preserve"
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
x="154.5717"
y="359.88428"
id="text3157"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan3159"
x="154.5717"
y="359.88428"
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">ID: 1182180000</tspan></text>
</g>
<g
id="g3954"
transform="translate(0,-18.756902)">
<rect
style="fill:none;stroke:#c3c3c3;stroke-width:1.07903957;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="rect3876"
width="417.54495"
height="126.4052"
x="141.08452"
y="284.96024"
rx="2.0839903"
ry="2.0839903" />
<text
xml:space="preserve"
style="font-size:27.53050041px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
x="209.6223"
y="318.18164"
id="text3878"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan3880"
x="209.6223"
y="318.18164"
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">CLIENT</tspan></text>
<image
width="162.31204"
height="107.26709"
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOYAAACYCAIAAAB/IAJdAAAAA3NCSVQICAjb4U/gAAADyUlEQVR4 nO3a3Y6qShSFUTC+/yvXuSAxbCgQsPiZp8e46mgJq/ETxe6+lNId0vd913XDw8c/z+8d3zIYP2p8 y9LKLbcvbXN8y5Zt/jLn0jHZcnzm29z+m961cmn+vY/a5fXLg+F6kiWMZAkjWcJIljCSJYxkCSNZ wkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJI ljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYw kiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIl jGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxk CSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkj WcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nC 9KWUg4/s+67rhoePf/7LHJO55sfEWZYwj0t2eCG2XXmGe/f+l72rt7Z6U5s8r1832LbX+ZqlAc6b 85qpbln59SDs7afvN31MrZxlm5w/+r6fb6d644H9Huu1euOpcx6eqnrj0n7vXdnKsP31Iz+YnmWb TzZ+3Wx/wqoOrJzvfeml3HDO36ear1wa/saV62fovafYUspnv+tv8pWzbMPPA5NNjS8bL1Dd+9gt c36dqnrX+pNy78qJw8etlDJ53VY39U+ybZ+kjb9nGWm1cpfmc/5o6RwzfyHdu3LX8NvNw50seE3u a/J8XPC8bjH5bavXPdfP+XWqXG0TGm92fJTezXf2BJ8PRtsvzy/wzKkea3Je/yT6mqw41e0vjI27 vnjO/0evJx20+eeEd3f0vWnXx/OlC52TVA/f8P6yPsCPc64fk8NTRTt8GbfkffYZ5eJYu9ULiJU+ zp7z2FQpzqtocj4tpbyrdzSZY7zNJz8fKXP+NfNYhx/qf7BtuL+HR5Ay55OdcYpdOY+8Ss1n6Y9z pHTwqDmXvgGdl3HvypNMvtKa7/GU/+S695uB7Qf9yjkPpDD/hn/JvStbmcdaPSxnfTDoVn/PK783 +GXZBdcTS/utfoO7dPV218qGtl9RPO7/ZZuovkBv/5vcrqmqK5c2e+PKVu9Uwxa2PEf/AXnknj+O sxSLAAAAAElFTkSuQmCC "
id="image3951"
x="388.17426"
y="294.58957" />
</g>
</g>
<g
transform="translate(0,0.3261795)"
id="g3322">
<rect
style="fill:none;stroke:#c3c3c3;stroke-width:1.07903957;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="rect3964"
width="417.54495"
height="126.4052"
x="141.08452"
y="419.52063"
rx="2.0839903"
ry="2.0839903" />
<text
xml:space="preserve"
style="font-size:27.53050041px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
x="209.6223"
y="452.74203"
id="text3966"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan3968"
x="209.6223"
y="452.74203"
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">WEIGHT</tspan></text>
<image
width="162.12962"
height="107.14654"
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOYAAACYCAIAAAB/IAJdAAAAA3NCSVQICAjb4U/gAAAERElEQVR4 nO3a0ZKaQBRFUbX8/182D1QZInRzadDxTNZ6mjgNNrhBYHJ9PB6XIdfr9XK5TIvPf57/drJ8/ayR rVdaG9Wfc+Vd+vPc+3N/hv3XK68st2i5Xa09XNnS+sjKthfdjiwMnydZwkiWMJIljGQJI1nCSJYw kiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIl jGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxk CSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkj WcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nC SJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiW MJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCS Jcz18XgMLnm9Xi6XafH5z/8z+2Tp9H3iLEuYDyU7HV7njuT/dH/+9NLKid9o5/Zan+fYFnW+vFrT 27uv+hPb3AnLt1su0prSO/beWeVcr6XL1Ns0dLnNqy+OzeOskfV5Dm9RZ8BZe2N1YgfXWXlx11u/ Y2TftFTlM7rP/zFv/B378eDIp+I8V89enUO5MpMjXz7P9S/nP59V/3zWOiX317k6/9aw5TqPj9z0 eDyea+vfpd1Wt/ny7y3eN6jP88jX9wdu8Jfzn2wuWD84W1cOm8Navz1rZN/LfmidcW/H36nz9pUP oz7yUs6uuLa5dx+cB5/vvOmRWeuAb52ex0bunVL/W/Se8uDw+Dw3r1MHTnVjs5qvpP6m9QuG7/lu HNa5Trg3F/oVKrfSR05ge5dt3awMH5DPj7b+0KA/n+/xcrZ+7qhmsm/6JjpdfZ6dMZuLd+5yBuxa ydgHsbxsnS4N407Gy3BXkm3d6KyuqzLyTSrzXH5Cex+Cdt5ifoabBlT2SSWmutZGtVb4+UxP7+Sf v34979EGbl8+aWye9RuIuoMnv039Ge66AVruqy//lCcvzw3+XhjsvSH4Ke+YZ+dR/DfvijFBW9S6 071fylcCP644z19T2wc25GsvZzvnpnvrF1/rxHl2rvZWL3lbTxuK77V6ifmZaPpH+8vlZmeeYyPH ptqa8C3lnPTjj5OW6xz7dlr9Q93YrFoP7Vfz6s+kOM+DI/uWl63r095c0VnP8A8+M9/cF8XHN8Xn WfUT6nCvm2uo7Lf6lu6af32eu7aoo36X8jv/i/fqAXrwBrm1zr0rKa6heJzXt3TXE4P6PI/vk/lS lc/oD6RX6TSeNJuDAAAAAElFTkSuQmCC "
id="image4065"
x="387.35876"
y="429.14993" />
<g
transform="translate(0,140.26901)"
id="g3179">
<text
xml:space="preserve"
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
x="154.5717"
y="341.12738"
id="text3181"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan3183"
x="154.5717"
y="341.12738"
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">Prefix: 21</tspan></text>
<text
sodipodi:linespacing="125%"
id="text3185"
y="359.88428"
x="154.5717"
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
xml:space="preserve"><tspan
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium"
y="359.88428"
x="154.5717"
id="tspan3187"
sodipodi:role="line">ID: 12345</tspan></text>
<text
xml:space="preserve"
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
x="154.5717"
y="379.4567"
id="text3189"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan3191"
x="154.5717"
y="379.4567"
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">Value: 67.89Kg</tspan></text>
</g>
</g>
<g
transform="translate(0,-1.9572678)"
id="g3308">
<rect
ry="2.0839903"
rx="2.0839903"
y="574.46893"
x="141.08452"
height="126.4052"
width="417.54495"
id="rect4081"
style="fill:none;stroke:#c3c3c3;stroke-width:1.07903957;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<g
id="g3296">
<text
xml:space="preserve"
style="font-size:27.53050041px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
x="195.7585"
y="607.69037"
id="text4083"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan4085"
x="195.7585"
y="607.69037"
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">DISCOUNT</tspan></text>
<image
y="584.09827"
x="385.72772"
id="image4202"
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOYAAACYCAIAAAB/IAJdAAAAA3NCSVQICAjb4U/gAAADsElEQVR4 nO3aXXOiMACGUej4//8ye+EMZYHECBF96zlXHTd8iE8jYTtO0zQcMo7jMAz3zUs/b8ffLUcuXymN bHn92ZHtR3l2ZOnnpfazOvauS3toP3rLyPr5tGx1wM+ZjeF6kiWMZAkjWcJIljCSJYxkCSNZwkiW MJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCS JYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWM ZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJ I1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZ wkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJI ljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCjNM0 HdxyHIdhuG++/PmbuSZb3a+JWZYwH5fs/RfxL42kr9vuq6/4UmvZZ0qFZ3pdbVu5II0jSyezO777 0XdP4Fg549h0m7ozy75i/mjZZ0qFHXst7W0cx92RjZufPPrJkc+e1XKT3fe4sp5lr+81pb/zV2be wzyXzJ9TaXZZvl45gZbJqf3oZ0ZWBtdN0zTvrf6FvDPLvmKRa+E8W16K+nf36l+Xi+7XHf3kyIeD 6/tZpb/7Zv+bZd9yS9A4kQSN3FWaOe6zy3ZOumYhsT36s+fZ3eo3c3vE32TfteRi5cDlal8qvcKq qi4TX+U+4bb7ahd6fZfLrvwcVq+HBts9bMO9rUb0pdeOtiFWVj/v1fFz34Z7G46+yfrt+SdcuOu1 L1meUl+Qbc/h2Jr98IltHyMsD939mvy4Jfhk86p5tZquu+bKVxZqQ785a/Xc4PfGoPJA+OTfzfTd 5/dYfU5vPJN3Ka0p9//Dlvcq3Ql8j8pv7K3yNPvMM+Hu+0xXeq5ZuSwPr1Vp2931e+PRD5xnXw8f 2H3cX3L9ecuPpMu902on9Rn64dHbR5buWQ/Hvb1t3d2JG4PrlJ5itvzJ1e74px6Lth/9qfOsn2q7 9nt3s+ylSuvrMzts32f3kaWjH3hT901atv0Hm+wpnHOLKX0AAAAASUVORK5CYII= "
height="108.34499"
width="163.94308"
inkscape:transform-center-x="-24.4145"
inkscape:transform-center-y="6.3942733" />
<g
transform="translate(0,296.03285)"
id="g3210">
<text
xml:space="preserve"
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
x="154.5717"
y="341.12738"
id="text3212"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan3214"
x="154.5717"
y="341.12738"
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">Prefix: 44</tspan></text>
<text
sodipodi:linespacing="125%"
id="text3216"
y="359.88428"
x="154.5717"
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
xml:space="preserve"><tspan
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium"
y="359.88428"
x="154.5717"
id="tspan3218"
sodipodi:role="line">ID: 11111</tspan></text>
<text
xml:space="preserve"
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
x="154.5717"
y="379.4567"
id="text3220"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan3222"
x="154.5717"
y="379.4567"
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">Value: 25%</tspan></text>
</g>
</g>
</g>
<g
transform="translate(0,-0.97866058)"
id="g3283">
<rect
style="fill:none;stroke:#c3c3c3;stroke-width:1.07903957;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="rect4115"
width="417.54495"
height="126.4052"
x="141.08452"
y="726.15521"
rx="2.0839903"
ry="2.0839903" />
<text
xml:space="preserve"
style="font-size:27.53050041px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
x="212.88437"
y="759.37659"
id="text4117"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan4119"
x="212.88437"
y="759.37659"
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">PRICE</tspan></text>
<image
width="163.36363"
height="107.96206"
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOYAAACYCAIAAAB/IAJdAAAAA3NCSVQICAjb4U/gAAAD70lEQVR4 nO3Z3XKbOhiGUezJ/d8yPfBsbwaB+Phz/aZrHaWukDB+goE8xnEcDnk8HsMwvDaf/tz+7/SVl3bR drbEkWvHZG3O/gytyvxrq6yt265emWfvyMq7K3qe2Rg+T7KEkSxhJEsYyRJGsoSRLGEkSxjJEkay hJEsYSRLGMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJEkayhJEsYSRLGMkSRrKEkSxhJEsYyRJGsoSR LGEkSxjJEkayhJEsYSRLGMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJEkayhJEsYSRLGMkSRrKEkSxh JEsYyRJGsoSRLGEkSxjJEkayhJEsYSRLGMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJEkayhJEsYSRL GMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJEkayhJEsYSRLGMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJ EkayhJEsYSRLGMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJEkayhJEsYSRLGMkSRrKEkSxhJEsYyRJG soSRLGEkSxjJEkayhJEsYSRLGMkSRrKEkSxhJEsYyRJGsoSRLGEkSxjJEkayhJEsYSRLmMc4jge3 fDyGYXhtPv35X+aYtC4/Js6yhJHsX/M65bDXz/un2RG85ButPufvG9lX6fXYWp0v37VFK4M/c0wq 2z4XV118Ze/yi3PWX6zP+W0jN21uVT9Q9Zl37Wr9nV57TCrv8We6xrvx9/bnz7XTGTp70w5bWz1l ZGvvZzmbdnO5yvyVXa33cGE54zi+Z+vfpT2n2yz+fED7Tqb/nB7ZxZ3rfE99/8jzdn19HxhTVO/h qnLGcZydERYPxXOt6DavA3twctv6aeP7Rw7/fSSzD6YzrDLn21U3c/UebiqnDXc24Ge4gYeRd9i8 Tt31pf/ytZ9U5zrhlmTPq/+Opow8pp2/jaze69r83xnu7Gz93tWPPpdtD9Dil8hiBykj79O5Wths bnp3cezC429prxOOn2V3XXSv3ZC1AzZ988gLb2HbSRbPiPXL6MUXL3w0VFn6klVuP8u+7/vWfrPb 19NH3qR+A7R3zm82e24wjuON17KzlfqD68cuZeTHrP3hY/jKva1bu1O8K9nNK4Hi5r9mZN0vqO28 zvnu53VBs/aHjZMH7uQ17uLzuS8f+UmL63YuedeeNszmLPZwUzmbj+Gei0NPnjzqO9056KEjr9JO fv4ksvbEY/Pd9d/pVeW0l63LO7b4+Oa9zeG1+wMWT2Cbq6eMrOj31zmAxS+uk/t/x294X/3O5/+z +oV/F9mVbLvJxh6HjOyrnDKPfSK7fhk6c94xsq/4rO0Pc0/7XuEpWE8AAAAASUVORK5CYII= "
id="image4264"
x="386.54324"
y="734.96906" />
<g
transform="translate(0,442.82599)"
id="g3224">
<text
xml:space="preserve"
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
x="154.5717"
y="341.12738"
id="text3226"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan3228"
x="154.5717"
y="341.12738"
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">Prefix: 02</tspan></text>
<text
sodipodi:linespacing="125%"
id="text3230"
y="359.88428"
x="154.5717"
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
xml:space="preserve"><tspan
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium"
y="359.88428"
x="154.5717"
id="tspan3232"
sodipodi:role="line">ID: 99999</tspan></text>
<text
xml:space="preserve"
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
x="154.5717"
y="379.4567"
id="text3234"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan3236"
x="154.5717"
y="379.4567"
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">Value: 134.50€</tspan></text>
</g>
</g>
<g
id="g3269">
<g
id="g3061"
transform="translate(0,284.6156)">
<g
id="g3063"
transform="translate(0,154.94832)">
<rect
ry="2.0839903"
rx="2.0839903"
y="438.27753"
x="141.08452"
height="126.4052"
width="417.54495"
id="rect3065"
style="fill:none;stroke:#c3c3c3;stroke-width:1.07903957;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<text
sodipodi:linespacing="125%"
id="text3067"
y="471.49893"
x="216.61778"
style="font-size:27.53050041px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
xml:space="preserve"><tspan
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium"
y="471.49893"
x="216.61778"
sodipodi:role="line"
id="tspan3083">UNIT</tspan></text>
<g
transform="translate(0,156.57936)"
id="g3071" />
</g>
</g>
<image
y="887.47076"
x="386.54324"
id="image3146"
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOYAAACYCAIAAAB/IAJdAAAAA3NCSVQICAjb4U/gAAADz0lEQVR4 nO3Z0ZKaMACGUdnx/V+ZXjBjGWKyEQjyt+fcdIsBY/ZTgZ3meX7sMk3T4/FYdq/9XI5frB9db18/ Wh6nfJZyr/Yz9s+t/Yp65tw/22+N3Pe6asevhdQ/stPPkZ3hepIljGQJI1nCSJYwkiWMZAkjWcJI ljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYw kiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIl jGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxk CSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkj WcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nC SJYwkiWMZAkjWcJIljCSJYxkCSNZwkiWMJIljGQJI1nCSJYwkiWMZAkjWcJIljCSJYxkCSNZwkzz PO/cc5oej8ey+/rn/5k1KZ2+Jj5lCSNZwjyXf5ZP7NKJ32v9XwrtkZupNg743ZH3nNWgV3p8WZaD 9Oz4Uz7fCP1P0Rg5TVP5aG38d0fWfHFW/av36TofX5bXLm+PtvF8/TTuQuGUXl/W83y9zs3kX8d5 bb9yZM0dZtWzev0jy2f/9bA18zy/jtb+mr3oXLZ/9u3zgc2j7cOuH71+5K1m1b96B9d5+e+isUvt OJv0336EDU/23I/Y0vruyfo45ZJdM7LmnrM6MnLQXbwy3M2AZ+2xU6Zy1iUXN7eO55RfYuM84Vnb 53hD1/R6wbXjP+z4d2Dt2ut4uJvP9ddhf9Zn0LvPQtrPesrIt19heu3Uv3r71nlEPOsjr2fyrF0t /nrd1z7lH3cKe+dMj1+cjTbi91Jefi1XTsv209ekevl1w1OC8k084m39r+pfvY/WefT6b+4bzPNc PZc95clqGzevs3/k2y30O/FUbbTa/YCByV7jzucJ9zf6FuRujVsQb/56sdnnrHfbwbOF2nwag7+1 ccfrGr2xf/XOWudye6dfb7b+vB13eq/HvZ1J+61f3ve+cuStZtW/emet845yytPW95NZ37PdOLfX 49dkH83zst/cR6v0xVn1r96gdW7r/2PE33tYI/76Vc7p+G2ED15b9ysaMfKes9q3emetc1vjjura Hwkp3Xa7HcbMAAAAAElFTkSuQmCC "
height="108.34499"
width="163.94308" />
<g
id="g3255"
transform="translate(0,598.58983)">
<text
xml:space="preserve"
style="font-size:17.41555405px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
x="154.5717"
y="351.81107"
id="text3261"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan3263"
x="154.5717"
y="351.81107"
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium">ID: 5449000000996</tspan></text>
</g>
</g>
<g
transform="matrix(0,-1,1,0,261.82237,1622.2884)"
id="g4150">
<text
xml:space="preserve"
style="font-size:8px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Droid Sans;-inkscape-font-specification:Droid Sans"
x="618.97778"
y="320.0275"
id="text3357"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan3359"
x="618.97778"
y="320.0275">This is a test sheet intended to help you test the codebar aquisition in OpenERP's Point of Sale module. </tspan><tspan
sodipodi:role="line"
x="618.97778"
y="330.0275"
id="tspan3363">The codes provided in this list are randomly chosen and are encoded in the EAN13 format. Their codes</tspan><tspan
sodipodi:role="line"
x="618.97778"
y="340.0275"
id="tspan3367">and prefixes are not intended to match any specified standard. </tspan></text>
<text
sodipodi:linespacing="125%"
id="text3369"
y="349.54001"
x="618.99207"
style="font-size:6.53564215px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#b3b3b3;fill-opacity:1;stroke:none;font-family:Droid Sans;-inkscape-font-specification:Droid Sans"
xml:space="preserve"><tspan
id="tspan3375"
y="349.54001"
x="618.99207"
sodipodi:role="line"
style="fill:#b3b3b3;fill-opacity:1">This document is licensed by OpenERP S.A. under the Creative Commons CC BY 3.0 license htttp://www.creativecommons.org</tspan></text>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -22,6 +22,7 @@
import pdb
import openerp
import addons
import openerp.addons.product.product
import time
from datetime import datetime
@ -59,16 +60,9 @@ class pos_config(osv.osv):
help="Accounting journal used to post sales entries."),
'iface_self_checkout' : fields.boolean('Self Checkout Mode',
help="Check this if this point of sale should open by default in a self checkout mode. If unchecked, OpenERP uses the normal cashier mode by default."),
'iface_websql' : fields.boolean('WebSQL (Faster but Chrome Only)',
help="If have more than 200 products, it's highly suggested to use WebSQL "\
"to store the data in the browser, instead of localStore mechanism. "\
"It's more efficient but works on the Chrome browser only."
),
'iface_led' : fields.boolean('Help Notification'),
'iface_cashdrawer' : fields.boolean('Cashdrawer Interface'),
'iface_payment_terminal' : fields.boolean('Payment Terminal Interface'),
'iface_electronic_scale' : fields.boolean('Electronic Scale Interface'),
'iface_barscan' : fields.boolean('BarScan Interface'),
'iface_vkeyboard' : fields.boolean('Virtual KeyBoard Interface'),
'iface_print_via_proxy' : fields.boolean('Print via Proxy'),
@ -77,7 +71,7 @@ class pos_config(osv.osv):
help="This sequence is automatically created by OpenERP but you can change it "\
"to customize the reference numbers of your orders."),
'session_ids': fields.one2many('pos.session', 'config_id', 'Sessions'),
'group_by' : fields.boolean('Group By', help="Check this if you want to group the Journal Items by Product while closing a Session"),
'group_by' : fields.boolean('Group Journal Items', help="Check this if you want to group the Journal Items by Product while closing a Session"),
}
def name_get(self, cr, uid, ids, context=None):
@ -93,7 +87,7 @@ class pos_config(osv.osv):
result.append((record.id, record.name+' ('+_('not used')+')'))
continue
session = record.session_ids[0]
result.append((record.id, record.name + ' ('+session.user_id.name+', '+states[session.state]+')'))
result.append((record.id, record.name + ' ('+session.user_id.name+')')) #, '+states[session.state]+')'))
return result
@ -143,8 +137,6 @@ class pos_config(osv.osv):
obj.sequence_id.unlink()
return super(pos_config, self).unlink(cr, uid, ids, context=context)
pos_config()
class pos_session(osv.osv):
_name = 'pos.session'
_order = 'id desc'
@ -459,8 +451,6 @@ class pos_session(osv.osv):
'context' : context,
}
pos_session()
class pos_order(osv.osv):
_name = "pos.order"
_description = "Point of Sale"
@ -1079,8 +1069,6 @@ class pos_order(osv.osv):
self.create_account_move(cr, uid, ids, context=context)
return True
pos_order()
class account_bank_statement(osv.osv):
_inherit = 'account.bank.statement'
_columns= {
@ -1179,8 +1167,6 @@ class pos_order_line(osv.osv):
})
return super(pos_order_line, self).copy_data(cr, uid, id, default, context=context)
pos_order_line()
class pos_category(osv.osv):
_name = 'pos.category'
_description = "Point of Sale Category"
@ -1230,6 +1216,11 @@ class pos_category(osv.osv):
'parent_id': fields.many2one('pos.category','Parent Category', select=True),
'child_id': fields.one2many('pos.category', 'parent_id', string='Children Categories'),
'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of product categories."),
# NOTE: there is no 'default image', because by default we don't show thumbnails for categories. However if we have a thumbnail
# for at least one category, then we display a default image on the other, so that the buttons have consistent styling.
# In this case, the default image is set by the js code.
'image': fields.binary("Image",
help="This field holds the image used for the category. "\
"The image is base64 encoded, and PIL-supported. "\
@ -1252,21 +1243,40 @@ class pos_category(osv.osv):
"Use this field anywhere a small image is required."),
}
def _get_default_image(self, cr, uid, context=None):
image_path = openerp.modules.get_module_resource('point_of_sale', 'images', 'default_category_photo.png')
return tools.image_resize_image_big(open(image_path, 'rb').read().encode('base64'))
_defaults = {
'image': _get_default_image,
}
pos_category()
import io, StringIO
class ean_wizard(osv.osv_memory):
_name = 'pos.ean_wizard'
_columns = {
'ean13_pattern': fields.char('Ean13 Pattern', size=32, required=True, translate=True),
}
def sanitize_ean13(self, cr, uid, ids, context):
for r in self.browse(cr,uid,ids):
ean13 = openerp.addons.product.product.sanitize_ean13(r.ean13_pattern)
m = context.get('active_model')
m_id = context.get('active_id')
self.pool.get(m).write(cr,uid,[m_id],{'ean13':ean13})
return { 'type' : 'ir.actions.act_window_close' }
class product_product(osv.osv):
_inherit = 'product.product'
#def _get_small_image(self, cr, uid, ids, prop, unknow_none, context=None):
# result = {}
# for obj in self.browse(cr, uid, ids, context=context):
# if not obj.product_image:
# result[obj.id] = False
# continue
# image_stream = io.BytesIO(obj.product_image.decode('base64'))
# img = Image.open(image_stream)
# img.thumbnail((120, 100), Image.ANTIALIAS)
# img_stream = StringIO.StringIO()
# img.save(img_stream, "JPEG")
# result[obj.id] = img_stream.getvalue().encode('base64')
# return result
_columns = {
'income_pdt': fields.boolean('Point of Sale Cash In', help="This is a product you can use to put cash into a statement for the point of sale backend."),
'expense_pdt': fields.boolean('Point of Sale Cash Out', help="This is a product you can use to take cash from a statement for the point of sale backend, exemple: money lost, transfer to bank, etc."),
@ -1278,7 +1288,16 @@ class product_product(osv.osv):
'to_weight' : False,
}
product_product()
def edit_ean(self, cr, uid, ids, context):
return {
'name': "Edit Ean",
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'form',
'res_model': 'pos.ean_wizard',
'target' : 'new',
'view_id': False,
'context':context,
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,12 +1,40 @@
<?xml version="1.0"?>
<openerp>
<data>
<record id="base.user_root" model="res.users">
<field name="ean13">0410100000006</field>
</record>
<record id="base.user_demo" model="res.users">
<field name="groups_id" eval="[(4,ref('group_pos_user'))]"/>
<field name="ean13">0410200000005</field>
</record>
<record id="account.cash_journal" model="account.journal">
<record id="account.cash_journal" model="account.journal">
<field eval="True" name="journal_user"/>
</record>
<record id="base.user_jsmith" model="res.users">
<field name="name">John Smith (Cashier)</field>
<field name="login">jsmith</field>
<field name="ean13">0410300000004</field>
<field name="groups_id" eval="[(4,ref('group_pos_manager'))]"/>
</record>
<record id="base.user_jdoe" model="res.users">
<field name="name">John Doe (Cashier)</field>
<field name="login">jdoe</field>
<field name="ean13">0410400000003</field>
<field name="groups_id" eval="[(4,ref('group_pos_manager'))]"/>
</record>
<record id="base.user_jbloggs" model="res.users">
<field name="name">Joe Bloggs (Client)</field>
<field name="login">jbloggs</field>
<field name="ean13">0420100000005</field>
<field name="groups_id" eval="[(4,ref('group_pos_user'))]"/>
</record>
<record id="base.user_bsoap" model="res.users">
<field name="name">Bob Soap (Client)</field>
<field name="login">bsoap</field>
<field name="ean13">0420200000004</field>
<field name="groups_id" eval="[(4,ref('group_pos_user'))]"/>
</record>
<!-- Resource: pos.category -->
@ -154,6 +182,7 @@
<field name="list_price">1.98</field>
<field name="name">Boni Oranges</field>
<field name="to_weight">True</field>
<field name="ean13">2100002000003</field>
<field name="pos_categ_id" ref="oranges"/>
<field name="uom_id" ref="product.product_uom_kgm" />
<field name="uom_po_id" ref="product.product_uom_kgm" />
@ -248,6 +277,7 @@
<field name="list_price">5.10</field>
<field name="name">Fishing</field>
<field name="to_weight">True</field>
<field name="ean13">2300001000008</field>
<field name="pos_categ_id" ref="rouges_noyau_fruits"/>
<field name="uom_id" ref="product.product_uom_kgm" />
<field name="uom_po_id" ref="product.product_uom_kgm" />
@ -316,6 +346,7 @@
<field name="list_price">1.28</field>
<field name="name">Onions</field>
<field name="to_weight">True</field>
<field name="ean13">2100001000004</field>
<field name="pos_categ_id" ref="oignons_ail_echalotes"/>
<field name="uom_id" ref="product.product_uom_kgm" />
<field name="uom_po_id" ref="product.product_uom_kgm" />
@ -389,6 +420,7 @@
<record id="coca_regular_33cl" model="product.product">
<field name="list_price">0.51</field>
<field name="name">Coca-Cola Regular 33cl</field>
<field name="ean13">5449000000996</field>
<field name="pos_categ_id" ref="coke"/>
<field name="image">/9j/4AAQSkZJRgABAAEBLAEsAAD/2wCEAAoHBwgHBgoICAgLCgoLDhgQDg0NDh0VFhEYIx8lJCIfIiEmKzcvJik0KSEiMEExNDk7Pj4+JS5ESUM8SDc9PjsBCwoKDg0OHBYWHCgoKCgoOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O//AABEIAGQAOAMBEQACEQEDEQH/xAAfAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgv/xAC1EAACAQMDAgQDBQUEBAAAAX0BAgMABBEFEiExQQYTUWEHInEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj5OXm5+jp6vHy8/T19vf4+fr/xAAfAQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgv/xAC1EQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/APZSQoJJwByTQBCt1HIxVOSOvtQBBe36WkDyySLGqAse5wKB2M1fEqHDYkZCu7ds4xQFi/Zaql7brPCVdGzjBweDjvQFix9ujVgsmULdM9/oelAiwrB1DKcgjINAFTVL6DT9PluLggIqnI9amUlFXZrQoyrTUY7s5OLWpJbINIs1ozjO6ICRfy4IrGOIT3R3VsslGTUJJ/g/8jPkngaXzZ9Wif2nDrn8wRWqq031OZ4LEL7D+Wo0ahp6uP8AiYaf5f8Ac81cflt/pT9pDuifqtf+SX3MVby2kuPMg1aBPRYSzlfYYGKTq0+5SwWJ/kf3WNy4muLTTUkJkvGlQspYCNcYHXqT1/8A1UqlTlV0h4fC+1m1J2szY8NanHquiQTLtWRB5cqD+Fhwf8fxopT543FjsK8PWcenT0OU8dXUlx4gtNK8wJF5aytlsbiWI/p+PHpXPipNySPZyWlGGHnVtd3t+A6bakaqvQDArJhC7d2Ub1AkJ3gcrke4qZaG9F3locBfMr3blVGAalI+joxtBHVeFirWwIUD6CnHc8fMk1I76JkudJj3n/Uttb6dv6V2RtKHofNyTp13bqZWgXMVp4xbT7NgIZomeRAcjI6GppSSqWR242nKpg/aT3TsjG+IJ2+NLY562i/+hNWeJ+M7sj1wUv8AF+iOTGq3trd7VuJDGH5TOQRmsEe08NTnDZXsdHrtzLcaLbm2eJmMzCBVJHmxMMgjnOQwYEZJycdxW9SKcVY8bBJQry5r7a+TWmvk1Zp9vmZFjoNlqGhwXEt6LK7mkdUeUZicg8KT/C1KMFKCd7M66+ZVqOIlFR5opLbdefmixPpuqeH7Ge4VZ4kDKoUhXVSe+7HzAjoeOeo6ZU6TimzOljKOMqRi2np5p/d0f9INK1u9udL1qKe6dybVWUdMfOAcY/3qVOXuy9C8ThKcK1Fxj9r9H/kaPw+H/FXRe1q9PC/GYZ4/9jf+JFj4h/8AI52h/wCnVf8A0JqrE/GjPIv9yn/i/RHGSQNPfCNMbnfaM+5rnWrse4pqFO76I39X1TSdL1A6GbQ3OnQoI5+fmEo6uno3r64ronOMXy20PHw2GxFel7fm5Zt3Xa3Z9127Fy2tZlsprvSpIdas3wZrdx88o/2l7SD+8OuOmetxi7Nx1X9ficdWrH2ihVTpyWz6L0f8r7dPTavqlzYf8Ii8dn/aUQFwgNtOTtgP905/h7jryO1TUcfZO1zbCU6v11OfI/deq6+fr38jn9PJR7he0tuy/lhv/Za5oPf0PZrK6i+0l/l+p1Xw7GfFg9rVv5itsL8Z5We/7p/28S/Ec7PF1m3/AE7L/wChNV4n40TkCvhJr+9+hyDTJFeq8jFVWQEkdRzXNH4j2nFyptLsXte0uOXVJwkPkSPKQGVyyFzyA2c7d2chuhyOnONqsLs4MFipRoxu7pL52XVd7dVuvuvL4UiSVEax2LqFqWeSA5WS4XqArZGPQjp3welOgrrTdGGaScW1P4JWs+i9VZ+qf5Brl/4hutFf7S9wLRZQk0U8IV0bquWAG4e/HPXtkqSqOOuw8DQwdOuuVLmtdNO6a66X0fkZNm25tw/55t/6Ca51uepU0XzX5nWfDZt/it/a1b+a1vhfj+R5Gfq2FX+L/MsfEqPzPFOnrnG63A/8eNaYle+jPh+XLhaj8/0PPrknz5Of4j/Oudn0kPhXodLpUyeIrKKx+1my1i2TZbXAcqJ0/uMR3Hatov2itezPExcHg6jny81OTu1vZ90NstMaCJbGY2+n6rBIzYu0AW4B6bZOoI6cGiELK2zM6+JU5c6vODX2Xt6r/NEGq6vq2paNKj3Mnk28qpc28gBKNztO7GSuR3OQcdampOco7muEwuHo101FXabT799Nr+n4GXo5LX8cQ/jyv5g1lBXaPSxNlSb7HW/C47vFEv8A16v/AOhLW2G+P5HlcR6YVf4v0ZpfEhf+Km0pvWIj/wAerTEfGjkyF/7NV9Tzi5/4+JM/3j/OuZn1EPhXoafhq1mu5b6KGW3jLWpXNwPlyWULhv4WyQQfUVdKLd/Q87M6saag2m/e6b7O+nVW3RuJrerafbNZ+INMj1JIRl0mH7xF/vZIIYe4/E1sqk0rSVzyZYPD1Z8+HqOF+23p3T8vuMzVvEdpe6VJp+kaQllA7LJOwxlsHgce+KzqVU42irHbg8vqU6yqVajk9kUfC8XneI7KPrmT+hqKKvNHdmMuXCzfkdP8K+fFEx7C0b/0JK0w3x/I83iP/do/4v0Zp/EzP9u6QV67G7e4q8T8SOTh+3sK1/L9Tzi9QR3cqhg2G6iuaW59PSleCZo+GEuPt8skLwCOOL9+lxnZIhIBBwOnIOe2M1VK/McGaSh7NJp3b0tunvp/l12Oy0W4t5fM+yalA1rEGVrS+Ic2zYI+V8/Mvb0wetddNp7PTz6HzuMhONueD5nb3o6c3quj/U5e401IfAQvFaJXmvizYP3lG5QF9ccn8a55w/dX8z2KWIcsx5NbKH52eongGEzeLrQ44jDuf++SP5kVOGV6iNs7ny4Kfnb8zc+FA/4qS79rVv8A0Na0wy/efI5OJH/s8P8AF+jND4mNjxBpABx8jc/jV4j4kc+QL9xV+R59er5upugO3e4GW7Z71yvVn0dJ8tFPyNa10uXStUeay1FWktUdnjERMnysEZWjz05z16c1pGDjK6Z5lbExxFFKcNJNa301V00/61NaaDwta3gv5RcWnygSwxLuhcsuSmMZXr0OOlbctNO+x50amPqQ5FaXZvRqz38/VXOYvLoz+HrKGMkRW9xMAp9DtIz+ZrCbvBep69Cny4mbe7jH9U/0Oj+GNsDf318w4ghCA+7HP/staYRe82cHEdT91CHd/l/w5rfC3TZba/kvJAdtzbNs9MB1zVYaOt/Iw4hxMZpQX2WvyY74lRyHxLpUgU7PKOGxnkNk/pijEfGgyGUfq1VeZ57qY238w56965pbn0lB3pI6W1jm1+KLWNIl8vXLJQJ4sj9+AMBxnjJHBHf+eyTn70d0eHWccJJ0aqvSls+3l/kEGseG2upptV0y4srxlZLiGIHy5SeuVPQ559jzmqjOm3qrEVMJjVFKlNSj0b3XzOdtoTNpGpOisIYXikBPY5Kgfkx/KsbXiz1JS5a9NPdpr8L/AKHoHhi0/sXwBc3kg2yTwyTn6bcKP6/jXVQjyUmz57Mav1nMowWyaX46mv4Jkid7ZIoyPL06Pe/GCSE/wP5GrpWtH0OLMIy9rVb61P8AP/gHTappNnrFqba8i3qDlWBwyH1B7GrnBSVmcmHxFShLmgzzTxJ8OprWRri3vg6N084Yx9SOK5pYZ9GfQ4XiNRjacPu/yf8Amc1BpuoaTdJdW9zGssZyro+B/wCPYzUKjUi7o6amc4HEQcZ3s+6/yN3/AISa11CMJ4g0S2uZFGPNhljyfwzkfnW2sviieW1Sou9DEWXZ3/y/QjnuLXVUh0uysU0zS/NEk77wWkx9M/5xSlTcvdSsi6eMp0G6sqvPO1lo7I7prBPEejvp8BNvauFQyDBwoI4XqO2K3lFSjY8nD4mVGsqlrtfmbOi6HYaDZi1sItinlmJyzn1JpU6cYKyFisXVxM+abuaB6mrZziEBhggEHsaAM668PaPekm406Bye+3H8qVkO7M5vAPhlm3HTh9PMb/GiwcxetPDGh2JBt9MgUjoSu4/rmiyC7NQAKAFAAHQCmIUUIAAA/9k=</field>
</record>
@ -1022,3 +1054,4 @@
</data>
</openerp>

View File

@ -55,10 +55,14 @@
</group>
</form>
</field>
<group class="oe_subtotal_footer">
<group class="oe_subtotal_footer oe_right" colspan="2" name="order_total">
<field name="amount_tax"/>
<field name="amount_total"/>
<button name="button_dummy" string="Update" class="oe_link" states="draft"/>
<div class="oe_subtotal_footer_separator oe_inline">
<label for="amount_total" />
<button name="button_dummy"
states="draft" string="(update)" type="object" class="oe_edit_only oe_link"/>
</div>
<field name="amount_total" nolabel="1" class="oe_subtotal_footer_separator"/>
</group>
</page>
<page string="Payments">
@ -630,6 +634,9 @@
<field name="expense_pdt"/>
</group>
</group>
<field name="ean13" position="after">
<button name="edit_ean" type="object" string="Edit" />
</field>
</field>
</record>
@ -773,10 +780,10 @@
<sheet>
<group>
<field name="name"/>
<field name="shop_id" widget="selection" />
<field name="shop_id" widget="selection" groups="stock.group_locations" />
<field name="journal_id" widget="selection" />
<field name="sequence_id" readonly="1"/>
<field name="group_by" />
<field name="sequence_id" readonly="1" groups="base.group_no_one" />
<field name="group_by" groups="account.group_account_user" />
</group>
<separator string="Available Payment Methods" colspan="4"/>
<field name="journal_ids" colspan="4" nolabel="1">
@ -791,14 +798,11 @@
<group string="Material Interfaces" >
<group>
<field name="iface_self_checkout" />
<field name="iface_websql" />
<field name="iface_led" />
<field name="iface_cashdrawer" />
<field name="iface_payment_terminal" />
</group>
<group>
<field name="iface_electronic_scale" />
<field name="iface_barscan" />
<field name="iface_vkeyboard" />
<field name="iface_print_via_proxy" />
</group>
@ -877,7 +881,7 @@
class="oe_highlight"/>
<button name="close" type="workflow" string="Validate Closing &amp; Post Entries" states="closing_control"
class="oe_highlight"/>
<button name="open_frontend_cb" type="object" string="Start Selling" states="opened"/>
<button name="open_frontend_cb" type="object" string="Continue Selling" states="opened"/>
<div class="oe_right">
<field name="state" widget="statusbar" statusbar_visible="opening_control,opened,closing_control,closed" nolabel="1"/>
</div>
@ -888,7 +892,7 @@
<button name="%(action_pos_box_out)d" string="Take Money Out" type="action" states="opened,closing_control"/>
</div>
<h1 class="oe_title">
Point of Sale Session:
Session:
<field name="name" attrs="{'invisible': [('name','=','/')]}" class="oe_inline"/>
</h1>
<field name="has_opening_control" invisible="1" />
@ -1057,5 +1061,20 @@
parent="menu_point_of_sale"
id="menu_pos_session_opening" sequence="0"/>
<record model="ir.ui.view" id="pos_ean13_generator">
<field name="name">pos.ean_wizard</field>
<field name="model">pos.ean_wizard</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Ean13 Generator" version="7.0">
<field name="ean13_pattern"/>
<footer>
<button name="sanitize_ean13" type="object" string="Apply"/>
</footer>
</form>
</field>
</record>
</data>
</openerp>

View File

@ -37,14 +37,14 @@ class pos_sales_user(report_sxw.rml_parse):
dt1 = form['date_start'] + ' 00:00:00'
dt2 = form['date_end'] + ' 23:59:59'
data={}
self.cr.execute("select po.name as pos,po.date_order,ru.name as user,po.state,rc.name " \
"from pos_order as po,res_users as ru,res_company as rc " \
self.cr.execute("select po.name as pos,po.date_order,rp.name as user,po.state,rc.name " \
"from pos_order as po,res_users as ru,res_company as rc, res_partner as rp " \
"where po.date_order >= %s and po.date_order <= %s " \
"and po.company_id=rc.id and po.user_id=ru.id and po.user_id IN %s " \
"and po.company_id=rc.id and po.user_id=ru.id and po.user_id IN %s and ru.partner_id = rp.id" \
,(dt1,dt2,tuple(form['user_id'])))
return self.cr.dictfetchall()
report_sxw.report_sxw('report.pos.sales.user', 'pos.order', 'addons/point_of_sale/report/pos_sales_user.rml', parser=pos_sales_user,header='internal')
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,38 +1,8 @@
#!/usr/bin/env python
from osv import osv, fields
import math
import openerp.addons.product.product
def is_pair(x):
return not x%2
# This code is a duplicate of product#check_ean function
def check_ean(eancode):
if not eancode:
return True
if len(eancode) <> 13:
return False
try:
int(eancode)
except:
return False
oddsum=0
evensum=0
total=0
eanvalue=eancode
reversevalue = eanvalue[::-1]
finalean=reversevalue[1:]
for i in range(len(finalean)):
if is_pair(i):
oddsum += int(finalean[i])
else:
evensum += int(finalean[i])
total=(oddsum * 3) + evensum
check = int(10 - math.ceil(total % 10.0)) %10
if check != int(eancode[-1]):
return False
return True
class res_users(osv.osv):
_inherit = 'res.users'
@ -43,10 +13,22 @@ class res_users(osv.osv):
def _check_ean(self, cr, uid, ids, context=None):
return all(
check_ean(user.ean13) == True
openerp.addons.product.product.check_ean(user.ean13) == True
for user in self.browse(cr, uid, ids, context=context)
)
def edit_ean(self, cr, uid, ids, context):
return {
'name': "Edit Ean",
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'form',
'res_model': 'pos.ean_wizard',
'target' : 'new',
'view_id': False,
'context':context,
}
_constraints = [
(_check_ean, "Error: Invalid ean code", ['ean13'],),
]

View File

@ -20,8 +20,11 @@
<field name="arch" type="xml">
<notebook position="inside">
<page string="Point Of Sale">
<field name="ean13" />
<field name="pos_config" />
<group>
<field name="pos_config" />
<field name="ean13" />
<button name="edit_ean" type="object" string="Edit" />
</group>
</page>
</notebook>
</field>

View File

@ -60,7 +60,8 @@ access_product_uom_manager,product.uom manager,product.model_product_uom,group_p
access_res_partner_manager,res.partner manager,base.model_res_partner,group_pos_manager,1,0,0,0
access_product_category_manager,product.category manager,product.model_product_category,group_pos_manager,1,1,1,1
access_product_pricelist_manager,product.pricelist manager,product.model_product_pricelist,group_pos_manager,1,0,0,0
access_product_category_pos_manager,pos.category manager,model_pos_category,group_pos_manager,1,1,1,"1"""
access_product_category_pos_user,pos.category user,model_pos_category,group_pos_user,1,0,0,"0"""
access_product_category_pos_manager,pos.category manager,model_pos_category,group_pos_manager,1,1,1,1
access_product_category_pos_user,pos.category user,model_pos_category,group_pos_user,1,0,0,0
access_pos_session_user,pos.session user,model_pos_session,group_pos_user,1,1,1,0
access_pos_config_user,pos.config user,model_pos_config,group_pos_user,1,1,1,0
access_ir_sequence_manager,ir.sequence manager,base.model_ir_sequence,group_pos_manager,1,1,1,1

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
60 access_res_partner_manager res.partner manager base.model_res_partner group_pos_manager 1 0 0 0
61 access_product_category_manager product.category manager product.model_product_category group_pos_manager 1 1 1 1
62 access_product_pricelist_manager product.pricelist manager product.model_product_pricelist group_pos_manager 1 0 0 0
63 access_product_category_pos_manager pos.category manager model_pos_category group_pos_manager 1 1 1 1" 1
64 access_product_category_pos_user pos.category user model_pos_category group_pos_user 1 0 0 0" 0
65 access_pos_session_user pos.session user model_pos_session group_pos_user 1 1 1 0
66 access_pos_config_user pos.config user model_pos_config group_pos_user 1 1 1 0
67 access_ir_sequence_manager ir.sequence manager base.model_ir_sequence group_pos_manager 1 1 1 1

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -31,6 +31,7 @@
background: -ms-linear-gradient(top,rgba(0,0,0,0.09),rgba(0,0,0,0));
background: linear-gradient(top,rgba(0,0,0,0.09),rgba(0,0,0,0));
}
.point-of-sale .darker-shadow-top{
position: absolute;
top:0;
@ -42,6 +43,7 @@
background: -ms-linear-gradient(top,rgba(0,0,0,0.15),rgba(0,0,0,0));
background: linear-gradient(top,rgba(0,0,0,0.15),rgba(0,0,0,0));
}
/* ********* The black loading screen ********* */
.point-of-sale .loader{
@ -156,11 +158,11 @@
}
.point-of-sale #rightheader button {
color: black;
height:29px;
margin:2px;
color: #273072;
height:27px;
margin:3px;
margin-right:0px;
border: 1px solid black;
border: 1px solid #353A7E;
background: #7f82ac;
background: -webkit-gradient(linear, left top, left bottom, from(#b2b3d7), to(#7f82ac));
background: -moz-linear-gradient(#b2b3d7, #7f82ac);
@ -187,6 +189,33 @@
font-weight: 900;
}
/* c) The session buttons */
.point-of-sale #rightheader .header-button{
float:right;
height:32px;
padding-left:10px;
padding-right:10px;
border-right: 1px solid #3a3a3a;
border-left: 1px solid #3a3a3a;
color:#DDD;
line-height:32px;
text-align:center;
cursor: pointer;
-webkit-transition-property: background;
-webkit-transition-duration: 0.2s;
-webkit-transition-timing-function: ease-out;
}
.point-of-sale #rightheader .header-button:last-child{
border-left: 1px solid #3a3a3a;
}
.point-of-sale #rightheader .header-button:hover{
background: rgba(0,0,0,0.2);
text-shadow: #000 0px 0px 3px;
color:#EEE;
}
/* c) The notifications indicator */
.point-of-sale .oe_pos_synch-notification{
@ -234,7 +263,8 @@
left:0;
width:440px;
top:0px;
bottom:105px;
/*bottom:105px;*/
bottom:0;
border-right: solid 1px #CECBCB;
background-color: white;
}
@ -318,7 +348,8 @@
.point-of-sale #rightpane {
position: absolute;
top: 0;
bottom: 105px;
/*bottom: 105px;*/
bottom:0;
left: 440px;
right: 0;
vertical-align: top;
@ -454,6 +485,42 @@
cursor: pointer;
}
.point-of-sale .category-simple-button{
position: relative;
display: inline-block;
font-size: 14px;
margin-right:10px;
padding:5px;
cursor: pointer;
border: 1px solid #cacaca;
border-radius: 4px;
background: #e2e2e2;
background: -webkit-linear-gradient(#f0f0f0, #e2e2e2);
background: -moz-linear-gradient(#f0f0f0, #e2e2e2);
background: -ms-linear-gradient(#f0f0f0, #e2e2e2);
background: linear-gradient(#f0f0f0, #e2e2e2);
-webkit-box-shadow: 0px 2px 2px rgba(0,0,0, 0.1);
-moz-box-shadow: 0px 2px 2px rgba(0,0,0, 0.1);
box-shadow: 0px 2px 2px rgba(0,0,0, 0.1);
}
.point-of-sale .category-simple-button:hover {
color: white;
background: #7f82ac;
border: 1px solid #7f82ac;
background: -webkit-linear-gradient(#9d9fc5, #7f82ac);
background: -moz-linear-gradient(#9d9fc5, #7f82ac);
background: -ms-linear-gradient(#9d9fc5, #7f82ac);
background: linear-gradient(#9d9fc5, #7f82ac);
-webkit-transition-property: background, border;
-webkit-transition-duration: 0.2s;
-webkit-transition-timing-function: ease-out;
}
.point-of-sale .category-button .category-img {
position: relative;
width: 120px;
@ -463,7 +530,8 @@
}
.point-of-sale .category-button .category-img img {
height: 100px;
max-height: 100px;
max-width: 120px;
}
.point-of-sale .category-button .category-name {
@ -490,6 +558,7 @@
position:relative;
vertical-align: top;
display: inline-block;
line-height: 100px;
font-size: 11px;
margin: 5px;
width: 120px;
@ -497,9 +566,9 @@
background:#fff;
border: 1px solid #fff;
border-radius: 3px;
-webkit-box-shadow: 0px 1px 8px rgba(0,0,0,0.2);
-moz-box-shadow: 0px 1px 8px rgba(0,0,0,0.2);
box-shadow: 0px 1px 8px rgba(0,0,0,0.2);
-webkit-box-shadow: 0px 1px 8px rgba(127,130,172,0.4);
-moz-box-shadow: 0px 1px 8px rgba(127,130,172,0.4);
box-shadow: 0px 1px 8px rgba(127,130,172,0.4);
}
.point-of-sale .product .product-img {
@ -511,7 +580,8 @@
}
.point-of-sale .product .product-img img {
height: 100px;
max-height: 100px;
max-width: 120px;
}
.point-of-sale .product .price-tag {
@ -520,6 +590,7 @@
right: 2px;
vertical-align: top;
color: white;
line-height: 14px;
background: #7f82ac;
padding: 2px 5px;
border-radius: 3px;
@ -533,6 +604,7 @@
box-sizing: border-box;
bottom:0;
top:auto;
line-height: 14px;
width:100%;
background: -webkit-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1));
background: -moz-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1));
@ -598,8 +670,16 @@
display: inline-block;
font-size: 1.5em;
}
.point-of-sale .greyed-out{
color: #AAA;
}
.point-of-sale .pos-step-container input{
font-size: 1em;
}
.point-of-sale .pos-payment-container {
text-align: left;
min-width: 500px;
}
.point-of-sale .pos-payment-container .left-block{
display: inline-block;
@ -648,8 +728,17 @@
width: 300px;
background-color: white;
margin: 20px;
padding: 10px;
padding: 15px;
padding-bottom:30px;
display: inline-block;
font-family: "Inconsolata";
-webkit-box-shadow: 0px 5px 16px rgba(0,0,0, 0.3);
-moz-box-shadow: 0px 5px 16px rgba(0,0,0, 0.3);
box-shadow: 0px 5px 16px rgba(0,0,0, 0.3);
}
.point-of-sale .pos-sale-ticket .emph{
font-size: 20px;
margin:5px;
}
.point-of-sale .pos-sale-ticket table {
width: 100%;
@ -660,18 +749,18 @@
}
@media print {
#oe_header, #oe_menu, .point-of-sale #topheader, .point-of-sale #leftpane {
display: none;
.point-of-sale #topheader, .point-of-sale #leftpane {
display: none !important;
}
.point-of-sale #content {
top: 0px;
top: 0px !important;
}
.point-of-sale #rightpane {
left: 0px;
left: 0px !important;
background-color: white;
}
#receipt-screen header {
display: none;
display: none !important;
}
#receipt-screen {
text-align: left;
@ -761,6 +850,28 @@
font-family: "Inconsolata";
}
/* e) The Welcome Screen */
.point-of-sale .goodbye-message{
position: absolute;
left:50%;
top:30%;
width:500px;
height:400px;
margin-left: -250px;
margin-top: -200px;
padding:10px;
padding-top:20px;
text-align:center;
font-size:20px;
font-weight:bold;
background-color: #F0EEEE;
border: 1px solid #E0DDDD;
-webkit-box-shadow: 0px 10px 20px rgba(0,0,0, 0.3);
-moz-box-shadow: 0px 10px 20px rgba(0,0,0, 0.3);
-ms-box-shadow: 0px 10px 20px rgba(0,0,0, 0.3);
z-index:1150;
}
/* ********* The OrderWidget ********* */
.point-of-sale .order-container{
@ -846,6 +957,10 @@
-moz-transition: background 50ms ease-in-out;
transition: background 50ms ease-in-out;
}
.point-of-sale .order .orderline.empty:hover{
background: transparent;
cursor: default;
}
.point-of-sale .order .orderline.selected{
background: rgba(140,143,183,0.2);
@ -933,9 +1048,9 @@
background: -moz-linear-gradient(#f0f0f0, #e2e2e2);
background: -ms-linear-gradient(#f0f0f0, #e2e2e2);
background: linear-gradient(#f0f0f0, #e2e2e2);
-webkit-box-shadow: 0px 2px 2px rgba(0,0,0, 0.3);
-moz-box-shadow: 0px 2px 2px rgba(0,0,0, 0.3);
box-shadow: 0px 2px 2px rgba(0,0,0, 0.3);
-webkit-box-shadow: 0px 2px 2px rgba(0,0,0, 0.1);
-moz-box-shadow: 0px 2px 2px rgba(0,0,0, 0.1);
box-shadow: 0px 2px 2px rgba(0,0,0, 0.1);
}
.point-of-sale .pos-actionbar .button .label{
margin-top: 37px;
@ -957,6 +1072,23 @@
-webkit-transition-timing-function: ease-out;
}
.point-of-sale .pos-actionbar .button.disabled{
color:#AAA;
}
.point-of-sale .pos-actionbar .button.disabled:hover{
border: 1px solid #cacaca;
border-radius: 4px;
background: #e2e2e2;
background: -webkit-linear-gradient(#f0f0f0, #e2e2e2);
background: -moz-linear-gradient(#f0f0f0, #e2e2e2);
background: -ms-linear-gradient(#f0f0f0, #e2e2e2);
background: linear-gradient(#f0f0f0, #e2e2e2);
-webkit-box-shadow: 0px 2px 2px rgba(0,0,0, 0.1);
-moz-box-shadow: 0px 2px 2px rgba(0,0,0, 0.1);
box-shadow: 0px 2px 2px rgba(0,0,0, 0.1);
}
.point-of-sale .pos-actionbar .button.rightalign{
float:right;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 46 KiB

View File

@ -0,0 +1,984 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="48"
height="48"
overflow="visible"
enable-background="new 0 0 128 129.396"
xml:space="preserve"
id="svg2"
sodipodi:version="0.32"
inkscape:version="0.48.3.1 r9886"
sodipodi:docname="scale.svg"
version="1.0"
inkscape:export-filename="/home/fva/Code/openerp/src/addons/trunk-pos-fva/point_of_sale/static/src/img/scale.png"
inkscape:export-xdpi="600"
inkscape:export-ydpi="600"
inkscape:output_extension="org.inkscape.output.svg.inkscape"><metadata
id="metadata367"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><cc:license
rdf:resource="http://creativecommons.org/licenses/publicdomain/" /><dc:title></dc:title><dc:creator><cc:Agent><dc:title>Jakub Steiner</dc:title></cc:Agent></dc:creator><dc:source>http://jimmac.musichall.cz</dc:source><dc:subject><rdf:Bag><rdf:li>home</rdf:li><rdf:li>return</rdf:li><rdf:li>go</rdf:li><rdf:li>default</rdf:li><rdf:li>user</rdf:li><rdf:li>directory</rdf:li></rdf:Bag></dc:subject><dc:contributor><cc:Agent><dc:title>Tuomas Kuosmanen</dc:title></cc:Agent></dc:contributor></cc:Work><cc:License
rdf:about="http://creativecommons.org/licenses/publicdomain/"><cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" /><cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" /><cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" /></cc:License></rdf:RDF></metadata><defs
id="defs365"><linearGradient
inkscape:collect="always"
id="linearGradient4977"><stop
style="stop-color:#ff0000;stop-opacity:1;"
offset="0"
id="stop4979" /><stop
style="stop-color:#ff0000;stop-opacity:0"
offset="1"
id="stop4981" /></linearGradient><inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 24 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="48 : 24 : 1"
inkscape:persp3d-origin="24 : 16 : 1"
id="perspective92" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient5060"
id="radialGradient5031"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-2.774389,0,0,1.969706,112.7623,-872.8854)"
cx="605.71429"
cy="486.64789"
fx="605.71429"
fy="486.64789"
r="117.14286" /><linearGradient
inkscape:collect="always"
id="linearGradient5060"><stop
style="stop-color:black;stop-opacity:1;"
offset="0"
id="stop5062" /><stop
style="stop-color:black;stop-opacity:0;"
offset="1"
id="stop5064" /></linearGradient><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient5060"
id="radialGradient5029"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.774389,0,0,1.969706,-1891.633,-872.8854)"
cx="605.71429"
cy="486.64789"
fx="605.71429"
fy="486.64789"
r="117.14286" /><linearGradient
id="linearGradient5048"><stop
style="stop-color:black;stop-opacity:0;"
offset="0"
id="stop5050" /><stop
id="stop5056"
offset="0.5"
style="stop-color:black;stop-opacity:1;" /><stop
style="stop-color:black;stop-opacity:0;"
offset="1"
id="stop5052" /></linearGradient><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5048"
id="linearGradient5027"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.774389,0,0,1.969706,-1892.179,-872.8854)"
x1="302.85715"
y1="366.64789"
x2="302.85715"
y2="609.50507" /><linearGradient
id="linearGradient2406"><stop
style="stop-color:#7c7e79;stop-opacity:1;"
offset="0"
id="stop2408" /><stop
id="stop2414"
offset="0.1724138"
style="stop-color:#848681;stop-opacity:1;" /><stop
style="stop-color:#898c86;stop-opacity:1;"
offset="1"
id="stop2410" /></linearGradient><linearGradient
inkscape:collect="always"
id="linearGradient2390"><stop
style="stop-color:#919191;stop-opacity:1;"
offset="0"
id="stop2392" /><stop
style="stop-color:#919191;stop-opacity:0;"
offset="1"
id="stop2394" /></linearGradient><linearGradient
inkscape:collect="always"
id="linearGradient2378"><stop
style="stop-color:#575757;stop-opacity:1;"
offset="0"
id="stop2380" /><stop
style="stop-color:#575757;stop-opacity:0;"
offset="1"
id="stop2382" /></linearGradient><linearGradient
inkscape:collect="always"
id="linearGradient2368"><stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop2370" /><stop
style="stop-color:#ffffff;stop-opacity:0;"
offset="1"
id="stop2372" /></linearGradient><linearGradient
inkscape:collect="always"
id="linearGradient2349"><stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop2351" /><stop
style="stop-color:#000000;stop-opacity:0;"
offset="1"
id="stop2353" /></linearGradient><linearGradient
id="linearGradient2341"><stop
id="stop2343"
offset="0"
style="stop-color:#000000;stop-opacity:1;" /><stop
id="stop2345"
offset="1"
style="stop-color:#000000;stop-opacity:0;" /></linearGradient><linearGradient
id="linearGradient2329"><stop
style="stop-color:#000000;stop-opacity:0.18556701;"
offset="0"
id="stop2331" /><stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="1"
id="stop2333" /></linearGradient><linearGradient
inkscape:collect="always"
id="linearGradient2319"><stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop2321" /><stop
style="stop-color:#000000;stop-opacity:0;"
offset="1"
id="stop2323" /></linearGradient><linearGradient
id="linearGradient2307"><stop
style="stop-color:#edd400;stop-opacity:1;"
offset="0"
id="stop2309" /><stop
style="stop-color:#998800;stop-opacity:1;"
offset="1"
id="stop2311" /></linearGradient><linearGradient
inkscape:collect="always"
id="linearGradient2299"><stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop2301" /><stop
style="stop-color:#ffffff;stop-opacity:0;"
offset="1"
id="stop2303" /></linearGradient><linearGradient
id="XMLID_2_"
gradientUnits="userSpaceOnUse"
x1="80.223602"
y1="117.5205"
x2="48.046001"
y2="59.7995"
gradientTransform="matrix(0.314683,0.000000,0.000000,0.314683,4.128264,3.742874)">
<stop
offset="0"
style="stop-color:#CCCCCC"
id="stop17" />
<stop
offset="0.9831"
style="stop-color:#FFFFFF"
id="stop19" />
<midPointStop
offset="0"
style="stop-color:#CCCCCC"
id="midPointStop48" />
<midPointStop
offset="0.5"
style="stop-color:#CCCCCC"
id="midPointStop50" />
<midPointStop
offset="0.9831"
style="stop-color:#FFFFFF"
id="midPointStop52" />
</linearGradient><linearGradient
inkscape:collect="always"
xlink:href="#XMLID_2_"
id="linearGradient1514"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.336922,0,0,0.166888,143.49433,15.46151)"
x1="52.006104"
y1="166.1331"
x2="14.049017"
y2="-42.218513" /><linearGradient
id="XMLID_39_"
gradientUnits="userSpaceOnUse"
x1="64.387703"
y1="65.124001"
x2="64.387703"
y2="35.569"
gradientTransform="matrix(0.354101,0,0,0.354101,127.15013,-0.08364921)">
<stop
offset="0"
style="stop-color:#FFFFFF"
id="stop336" />
<stop
offset="0.8539"
style="stop-color:#FF6200"
id="stop338" />
<stop
offset="1"
style="stop-color:#F25D00"
id="stop340" />
<midPointStop
offset="0"
style="stop-color:#FFFFFF"
id="midPointStop335" />
<midPointStop
offset="0.5"
style="stop-color:#FFFFFF"
id="midPointStop337" />
<midPointStop
offset="0.8539"
style="stop-color:#FF6200"
id="midPointStop339" />
<midPointStop
offset="0.5"
style="stop-color:#FF6200"
id="midPointStop341" />
<midPointStop
offset="1"
style="stop-color:#F25D00"
id="midPointStop343" />
</linearGradient><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient2299"
id="radialGradient2305"
cx="7.5326638"
cy="24.202574"
fx="7.5326638"
fy="24.202574"
r="8.2452128"
gradientTransform="matrix(4.100086,0,0,4.201322,100.09639,-78.53967)"
gradientUnits="userSpaceOnUse" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient2307"
id="radialGradient2313"
cx="19.985598"
cy="36.77816"
fx="19.985598"
fy="36.77816"
r="1.0821035"
gradientTransform="matrix(1.125263,0,0,0.982744,122.08278,0.565787)"
gradientUnits="userSpaceOnUse" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient2319"
id="radialGradient2325"
cx="20.443665"
cy="37.425829"
fx="20.443665"
fy="37.425829"
r="1.0821035"
gradientTransform="matrix(1.125263,0,0,0.982744,122.08278,0.731106)"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2329"
id="linearGradient2335"
x1="17.602522"
y1="26.057423"
x2="17.682528"
y2="32.654099"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.898789,0,0,1.071914,125.98948,-2.080838)" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient2341"
id="radialGradient2339"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(4.100086,0,0,-4.201322,120.31334,105.3535)"
cx="11.68129"
cy="19.554111"
fx="11.68129"
fy="19.554111"
r="8.2452126" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient2349"
id="radialGradient2355"
cx="24.023088"
cy="40.56913"
fx="24.023088"
fy="40.56913"
r="16.28684"
gradientTransform="matrix(1.000000,0.000000,0.000000,0.431250,1.157278e-15,23.07369)"
gradientUnits="userSpaceOnUse" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient2368"
id="radialGradient2374"
cx="29.913452"
cy="30.442923"
fx="29.913452"
fy="30.442923"
r="4.0018832"
gradientTransform="matrix(3.751495,0,0,3.147818,43.502383,-65.70704)"
gradientUnits="userSpaceOnUse" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient2378"
id="radialGradient2384"
cx="24.195112"
cy="10.577631"
fx="24.195112"
fy="10.577631"
r="15.242914"
gradientTransform="matrix(1.125263,-3.585417e-8,4.269819e-8,1.340059,122.50475,1.355395)"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2390"
id="linearGradient2396"
x1="30.603519"
y1="37.337803"
x2="30.603519"
y2="36.112415"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.263867,0,0,0.859794,-6.499556,8.390924)" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2406"
id="linearGradient2412"
x1="17.850183"
y1="28.939463"
x2="19.040216"
y2="41.03223"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.888785,0,0,1.08932,127.92244,-1.524336)" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5048"
id="linearGradient4689"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.774389,0,0,1.969706,-1892.179,-872.8854)"
x1="302.85715"
y1="366.64789"
x2="302.85715"
y2="609.50507" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient5060"
id="radialGradient4691"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.774389,0,0,1.969706,-1891.633,-872.8854)"
cx="605.71429"
cy="486.64789"
fx="605.71429"
fy="486.64789"
r="117.14286" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient5060"
id="radialGradient4693"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-2.774389,0,0,1.969706,112.7623,-872.8854)"
cx="605.71429"
cy="486.64789"
fx="605.71429"
fy="486.64789"
r="117.14286" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5048"
id="linearGradient4894"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.774389,0,0,1.969706,-1892.179,-872.8854)"
x1="302.85715"
y1="366.64789"
x2="302.85715"
y2="609.50507" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient5060"
id="radialGradient4896"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.774389,0,0,1.969706,-1891.633,-872.8854)"
cx="605.71429"
cy="486.64789"
fx="605.71429"
fy="486.64789"
r="117.14286" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient5060"
id="radialGradient4898"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-2.774389,0,0,1.969706,112.7623,-872.8854)"
cx="605.71429"
cy="486.64789"
fx="605.71429"
fy="486.64789"
r="117.14286" /><filter
inkscape:collect="always"
id="filter4971"
x="-0.064853556"
width="1.1297071"
y="-1.9456067"
height="4.8912134"><feGaussianBlur
inkscape:collect="always"
stdDeviation="0.81066946"
id="feGaussianBlur4973" /></filter><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4977"
id="linearGradient4983"
x1="23.6875"
y1="32"
x2="23.75"
y2="45.25"
gradientUnits="userSpaceOnUse" /></defs><sodipodi:namedview
inkscape:cy="-2.746436"
inkscape:cx="31.130127"
inkscape:zoom="8"
inkscape:window-height="1176"
inkscape:window-width="1855"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
borderopacity="0.21568627"
bordercolor="#666666"
pagecolor="#ffffff"
id="base"
inkscape:showpageshadow="false"
inkscape:window-x="65"
inkscape:window-y="24"
inkscape:current-layer="svg2"
fill="#555753"
showgrid="true"
stroke="#a40000"
showguides="true"
inkscape:guide-bbox="true"
inkscape:snap-global="false"
inkscape:window-maximized="1"><inkscape:grid
type="xygrid"
id="grid3818" /></sodipodi:namedview>
<g
style="display:inline"
id="g5022"
transform="matrix(0.02158196,0,0,0.01859457,168.63396,41.63767)"><rect
y="-150.69685"
x="-1559.2523"
height="478.35718"
width="1339.6335"
id="rect4173"
style="opacity:0.40206185;color:#000000;fill:url(#linearGradient5027);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible" /><path
sodipodi:nodetypes="cccc"
id="path5058"
d="m -219.61876,-150.68038 c 0,0 0,478.33079 0,478.33079 142.874166,0.90045 345.40022,-107.16966 345.40014,-239.196175 0,-132.026537 -159.436816,-239.134595 -345.40014,-239.134615 z"
style="opacity:0.40206185;color:#000000;fill:url(#radialGradient5029);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
inkscape:connector-curvature="0" /><path
style="opacity:0.40206185;color:#000000;fill:url(#radialGradient5031);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
d="m -1559.2523,-150.68038 c 0,0 0,478.33079 0,478.33079 -142.8742,0.90045 -345.4002,-107.16966 -345.4002,-239.196175 0,-132.026537 159.4368,-239.134595 345.4002,-239.134615 z"
id="path5018"
sodipodi:nodetypes="cccc"
inkscape:connector-curvature="0" /></g><path
style="color:#000000;fill:url(#linearGradient1514);fill-opacity:1;fill-rule:nonzero;stroke:#757575;stroke-width:1.0000006;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
d="m 147.13103,8.1833733 5.95746,0 c 0.83973,0 13.88647,15.4353277 13.88647,16.3406587 l -0.44352,18.496745 c 0,0.905333 -0.67603,1.634177 -1.51576,1.634177 l -31.45728,0 c -0.83973,0 -1.51576,-0.728844 -1.51576,-1.634177 l 0.0565,-18.496745 c 0,-0.905331 14.19218,-16.3406587 15.03191,-16.3406587 z"
id="rect1512"
sodipodi:nodetypes="ccccccccc"
inkscape:connector-curvature="0" /><path
style="fill:none"
id="path5"
d="m 172.47503,45.735573 -45.3249,0 0,-45.32489746 45.3249,0 0,45.32489746 z"
inkscape:connector-curvature="0" /><path
style="fill:url(#linearGradient2335);fill-opacity:1;fill-rule:evenodd"
id="path2327"
d="m 148.51145,29 -0.0457,15.090942 -11.84279,0 L 136.51145,29 l 12,0 z"
clip-rule="evenodd"
sodipodi:nodetypes="ccccc"
inkscape:connector-curvature="0" /><path
sodipodi:nodetypes="ccccccccc"
id="path2357"
d="m 147.29191,9.405584 5.5591,0 c 0.78358,0 13.00087,14.399588 13.00087,15.244172 l -0.34716,18.212311 c 0,0.459259 -0.14374,0.653465 -0.51237,0.653465 l -31.38721,0.01428 c -0.36863,0 -0.58396,-0.07992 -0.58396,-0.45355 l 0.21534,-18.426506 c 0,-0.844584 13.27181,-15.244172 14.05539,-15.244172 z"
style="opacity:0.3125;color:#000000;fill:none;stroke:#ffffff;stroke-width:1.00000012;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
inkscape:connector-curvature="0" /><path
clip-rule="evenodd"
d="m 132.71898,27.943053 -0.0543,2.595194 18.36817,-13.179254 15.28639,11.154428 0.0713,-0.311714 -16.37045,-15.904131 -17.30116,15.645477 z"
id="path23"
style="opacity:0.2;fill:url(#radialGradient2384);fill-opacity:1;fill-rule:evenodd"
sodipodi:nodetypes="ccccccc"
inkscape:connector-curvature="0" /><path
clip-rule="evenodd"
d="m 147.51145,30 0,14.090942 -9.81103,0 L 137.51145,30 l 10,0 z"
id="path188"
style="fill:url(#linearGradient2412);fill-opacity:1;fill-rule:evenodd"
sodipodi:nodetypes="ccccc"
inkscape:connector-curvature="0" /><path
style="opacity:0.40909089;fill:url(#radialGradient2325);fill-opacity:1;fill-rule:evenodd"
id="path2315"
d="m 145.08831,36.44767 c 0.67279,0 1.21662,0.474605 1.21662,1.058507 0,0.589811 -0.54383,1.068355 -1.21662,1.068355 -0.67227,0 -1.21869,-0.478544 -1.21869,-1.068355 5.2e-4,-0.583902 0.54642,-1.058507 1.21869,-1.058507 z"
clip-rule="evenodd"
inkscape:connector-curvature="0" /><path
clip-rule="evenodd"
d="m 144.97377,35.932229 c 0.67279,0 1.21661,0.474605 1.21661,1.058507 0,0.589809 -0.54382,1.068353 -1.21661,1.068353 -0.67228,0 -1.21869,-0.478544 -1.21869,-1.068353 5.2e-4,-0.583902 0.54641,-1.058507 1.21869,-1.058507 z"
id="path217"
style="fill:url(#radialGradient2313);fill-opacity:1;fill-rule:evenodd"
inkscape:connector-curvature="0" /><path
d="m 149.9592,11.559337 18.92706,17.169868 0.49468,0.391991 0.40368,-0.171385 -0.37287,-0.761673 -0.27762,-0.223436 -19.17493,-15.572306 -19.38951,15.743335 -0.23761,0.14412 -0.21671,0.706786 0.43342,0.129248 0.38456,-0.308423 19.02585,-17.248125 z"
id="path342"
style="fill:url(#XMLID_39_)"
sodipodi:nodetypes="ccccccccccccc"
inkscape:connector-curvature="0" /><path
style="fill:#ef2929;stroke:#a40000"
id="path362"
d="m 149.84162,2.2713382 -21.88174,18.1013368 -0.62473,7.165928 1.99994,2.064323 c 0,0 20.40738,-17.157285 20.62409,-17.327963 l 19.63254,17.54326 1.89843,-2.323997 -1.61579,-7.111374 -19.91518,-18.2159732 -0.11756,0.1044594 z"
sodipodi:nodetypes="cccccccccc"
inkscape:connector-curvature="0" />
<path
style="opacity:0.40909089;color:#000000;fill:url(#radialGradient2305);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
d="M 128.3528,20.613129 128.06124,27.236494 149.88067,8.980075 149.81034,3.0867443 128.3528,20.613129 z"
id="path1536"
sodipodi:nodetypes="ccccc"
inkscape:connector-curvature="0" /><path
sodipodi:nodetypes="ccccc"
id="path2337"
d="m 149.99522,8.7509884 0.0995,-5.8411017 19.32896,17.6519533 1.49181,6.500812 -20.92023,-18.3116636 z"
style="opacity:0.13636367;color:#000000;fill:url(#radialGradient2339);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
inkscape:connector-curvature="0" /><path
style="opacity:0.31818183;color:#000000;fill:none;stroke:#ffffff;stroke-width:0.99999934;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
d="m 152.61368,27.719824 9.04,0 c 0.77059,0 1.39096,0.62037 1.39096,1.390967 l -0.008,9.079221 c 0,0.770596 -0.59632,1.265969 -1.36691,1.265969 l -9.05609,0 c -0.77059,0 -1.39096,-0.620373 -1.39096,-1.390969 l 0,-8.954221 c 0,-0.770597 0.62037,-1.390967 1.39096,-1.390967 z"
id="rect2361"
sodipodi:nodetypes="ccccccccc"
inkscape:connector-curvature="0" /><rect
style="color:#000000;fill:#3465a4;fill-opacity:1;fill-rule:nonzero;stroke:#757575;stroke-width:0.9999994;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
id="rect3263"
width="10.001333"
height="9.9624557"
x="152.01923"
y="28.514256"
rx="0.38128215"
ry="0.38128215" /><path
style="opacity:0.39772728;color:#000000;fill:url(#radialGradient2374);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.99999958;marker:none;visibility:visible;display:inline;overflow:visible"
d="m 152.61857,34.408261 c 3.61798,0.331177 5.52773,-1.445704 8.86815,-1.55274 l 0.0247,-3.849491 -8.91161,-0.006 0.0187,5.408261 z"
id="rect2363"
sodipodi:nodetypes="ccccc"
inkscape:connector-curvature="0" /><g
transform="translate(65.937707,2.0108349)"
id="g4739"><path
sodipodi:type="arc"
style="fill:#6adb4d;fill-opacity:1;stroke:#34781d;stroke-width:1.05264175;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="path4741"
sodipodi:cx="25.367456"
sodipodi:cy="8.2694378"
sodipodi:rx="5.3033009"
sodipodi:ry="5.3033009"
d="m 30.670757,8.2694378 c 0,2.9289322 -2.374368,5.3033012 -5.303301,5.3033012 -2.928932,0 -5.3033,-2.374369 -5.3033,-5.3033012 0,-2.9289322 2.374368,-5.3033009 5.3033,-5.3033009 2.928933,0 5.303301,2.3743687 5.303301,5.3033009 z"
transform="matrix(0.95416667,0,0,0.94583333,1.405743,0.73519001)" /><path
transform="matrix(0.77083333,0,0,0.77916666,6.0564434,2.1576238)"
d="m 30.670757,8.2694378 c 0,2.9289322 -2.374368,5.3033012 -5.303301,5.3033012 -2.928932,0 -5.3033,-2.374369 -5.3033,-5.3033012 0,-2.9289322 2.374368,-5.3033009 5.3033,-5.3033009 2.928933,0 5.303301,2.3743687 5.303301,5.3033009 z"
sodipodi:ry="5.3033009"
sodipodi:rx="5.3033009"
sodipodi:cy="8.2694378"
sodipodi:cx="25.367456"
id="path4743"
style="fill:none;stroke:#b3ff9a;stroke-width:1.29034114;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
sodipodi:type="arc" /></g><g
id="g4733"
transform="translate(76.897863,5.0160386)"><path
transform="matrix(0.95416667,0,0,0.94583333,1.405743,0.73519001)"
d="m 30.670757,8.2694378 c 0,2.9289322 -2.374368,5.3033012 -5.303301,5.3033012 -2.928932,0 -5.3033,-2.374369 -5.3033,-5.3033012 0,-2.9289322 2.374368,-5.3033009 5.3033,-5.3033009 2.928933,0 5.303301,2.3743687 5.303301,5.3033009 z"
sodipodi:ry="5.3033009"
sodipodi:rx="5.3033009"
sodipodi:cy="8.2694378"
sodipodi:cx="25.367456"
id="path4735"
style="fill:#6adb4d;fill-opacity:1;stroke:#34781d;stroke-width:1.05264175;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
sodipodi:type="arc" /><path
sodipodi:type="arc"
style="fill:none;stroke:#b3ff9a;stroke-width:1.29034114;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="path4737"
sodipodi:cx="25.367456"
sodipodi:cy="8.2694378"
sodipodi:rx="5.3033009"
sodipodi:ry="5.3033009"
d="m 30.670757,8.2694378 c 0,2.9289322 -2.374368,5.3033012 -5.303301,5.3033012 -2.928932,0 -5.3033,-2.374369 -5.3033,-5.3033012 0,-2.9289322 2.374368,-5.3033009 5.3033,-5.3033009 2.928933,0 5.303301,2.3743687 5.303301,5.3033009 z"
transform="matrix(0.77083333,0,0,0.77916666,6.0564434,2.1576238)" /></g><g
transform="translate(70.887455,7.0268736)"
id="g4727"><path
sodipodi:type="arc"
style="fill:#6adb4d;fill-opacity:1;stroke:#34781d;stroke-width:1.05264175;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="path4729"
sodipodi:cx="25.367456"
sodipodi:cy="8.2694378"
sodipodi:rx="5.3033009"
sodipodi:ry="5.3033009"
d="m 30.670757,8.2694378 c 0,2.9289322 -2.374368,5.3033012 -5.303301,5.3033012 -2.928932,0 -5.3033,-2.374369 -5.3033,-5.3033012 0,-2.9289322 2.374368,-5.3033009 5.3033,-5.3033009 2.928933,0 5.303301,2.3743687 5.303301,5.3033009 z"
transform="matrix(0.95416667,0,0,0.94583333,1.405743,0.73519001)" /><path
transform="matrix(0.77083333,0,0,0.77916666,6.0564434,2.1576238)"
d="m 30.670757,8.2694378 c 0,2.9289322 -2.374368,5.3033012 -5.303301,5.3033012 -2.928932,0 -5.3033,-2.374369 -5.3033,-5.3033012 0,-2.9289322 2.374368,-5.3033009 5.3033,-5.3033009 2.928933,0 5.303301,2.3743687 5.303301,5.3033009 z"
sodipodi:ry="5.3033009"
sodipodi:rx="5.3033009"
sodipodi:cy="8.2694378"
sodipodi:cx="25.367456"
id="path4731"
style="fill:none;stroke:#b3ff9a;stroke-width:1.29034114;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
sodipodi:type="arc" /></g><g
id="g4723"
transform="translate(59.662134,5.9662134)"><path
transform="matrix(0.95416667,0,0,0.94583333,1.405743,0.73519001)"
d="m 30.670757,8.2694378 c 0,2.9289322 -2.374368,5.3033012 -5.303301,5.3033012 -2.928932,0 -5.3033,-2.374369 -5.3033,-5.3033012 0,-2.9289322 2.374368,-5.3033009 5.3033,-5.3033009 2.928933,0 5.303301,2.3743687 5.303301,5.3033009 z"
sodipodi:ry="5.3033009"
sodipodi:rx="5.3033009"
sodipodi:cy="8.2694378"
sodipodi:cx="25.367456"
id="path4719"
style="fill:#6adb4d;fill-opacity:1;stroke:#34781d;stroke-width:1.05264175;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
sodipodi:type="arc" /><path
sodipodi:type="arc"
style="fill:none;stroke:#b3ff9a;stroke-width:1.29034114;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="path4721"
sodipodi:cx="25.367456"
sodipodi:cy="8.2694378"
sodipodi:rx="5.3033009"
sodipodi:ry="5.3033009"
d="m 30.670757,8.2694378 c 0,2.9289322 -2.374368,5.3033012 -5.303301,5.3033012 -2.928932,0 -5.3033,-2.374369 -5.3033,-5.3033012 0,-2.9289322 2.374368,-5.3033009 5.3033,-5.3033009 2.928933,0 5.303301,2.3743687 5.303301,5.3033009 z"
transform="matrix(0.77083333,0,0,0.77916666,6.0564434,2.1576238)" /></g><g
id="g4695"
transform="matrix(1.1326331,0,0,0.44340824,65.580189,2.4215088)"><rect
style="fill:#8a8a8a;fill-opacity:1;stroke:none"
id="rect4697"
width="29.256544"
height="18.119614"
x="10.297243"
y="27.449707"
rx="1.7190553"
ry="4.3911204" /><g
id="g4699"><g
id="g4701"><rect
rx="1.7051755"
y="27.517153"
x="10.510214"
height="17.982044"
width="29.020325"
id="rect4703"
style="fill:none;stroke:#646464;stroke-width:1.41005611;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
ry="4.3556657" /></g><rect
rx="0.88575488"
y="29.624025"
x="11.337327"
height="13.83156"
width="27.319036"
id="rect4705"
style="fill:none;stroke:#b3b3b3;stroke-width:1.40048993;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
ry="2.2625546" /></g></g><g
transform="matrix(0.01936185,0,0,0.01859457,111.41659,40.621203)"
id="g4681"
style="display:inline"><rect
style="opacity:0.40206185;color:#000000;fill:url(#linearGradient4689);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
id="rect4683"
width="1339.6335"
height="478.35718"
x="-1559.2523"
y="-150.69685" /><path
inkscape:connector-curvature="0"
style="opacity:0.40206185;color:#000000;fill:url(#radialGradient4691);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
d="m -219.61876,-150.68038 c 0,0 0,478.33079 0,478.33079 142.874166,0.90045 345.40022,-107.16966 345.40014,-239.196175 0,-132.026537 -159.436816,-239.134595 -345.40014,-239.134615 z"
id="path4685"
sodipodi:nodetypes="cccc" /><path
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc"
id="path4687"
d="m -1559.2523,-150.68038 c 0,0 0,478.33079 0,478.33079 -142.8742,0.90045 -345.4002,-107.16966 -345.4002,-239.196175 0,-132.026537 159.4368,-239.134595 345.4002,-239.134615 z"
style="opacity:0.40206185;color:#000000;fill:url(#radialGradient4693);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible" /></g><g
id="g4672"
transform="translate(68.942911,-2.9168155)"><rect
ry="1.9470588"
rx="1.9470588"
y="27.449707"
x="10.297243"
height="18.119614"
width="29.256544"
id="rect4658"
style="fill:#dddddd;fill-opacity:1;stroke:none" /><g
id="g4667"><g
id="g4664"><rect
ry="1.9313381"
style="fill:none;stroke:#646464;stroke-width:0.99927008;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="rect4660"
width="29.020325"
height="17.982044"
x="10.510214"
y="27.517153"
rx="1.9313381" /></g><rect
ry="1.0032353"
style="fill:none;stroke:#f6f6f6;stroke-width:0.99249077;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="rect4662"
width="26.967867"
height="16.005489"
x="11.532422"
y="28.489801"
rx="1.0032353" /></g></g><path
transform="matrix(1.0023678,0,0,0.95596394,68.883716,-0.0273148)"
d="m 35,33 a 10,10 0 1 1 -20,0 10,10 0 1 1 20,0 z"
sodipodi:ry="10"
sodipodi:rx="10"
sodipodi:cy="33"
sodipodi:cx="25"
id="path4679"
style="fill:#b8b8b8;fill-opacity:1;fill-rule:evenodd;stroke:none"
sodipodi:type="arc" /><path
sodipodi:type="arc"
style="fill:#b8b8b8;fill-opacity:1;fill-rule:evenodd;stroke:#646464;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="path3820"
sodipodi:cx="25"
sodipodi:cy="33"
sodipodi:rx="10"
sodipodi:ry="10"
d="m 35,33 a 10,10 0 1 1 -20,0 10,10 0 1 1 20,0 z"
transform="matrix(0.94933484,0,0,0.95596394,70.209541,-1.4857225)" /><path
transform="matrix(0.6928006,0,0,0.69727163,76.6008,8.0344445)"
d="m 35,33 a 10,10 0 1 1 -20,0 10,10 0 1 1 20,0 z"
sodipodi:ry="10"
sodipodi:rx="10"
sodipodi:cy="33"
sodipodi:cx="25"
id="path4590"
style="fill:#f4f4f4;fill-opacity:1;fill-rule:evenodd;stroke:none"
sodipodi:type="arc" /><g
id="g4644"
transform="matrix(0.70710678,0.70710678,-0.70710678,0.70710678,99.594592,-10.924363)"><rect
style="fill:#232323;fill-opacity:1;stroke:none"
id="rect4646"
width="0.68500972"
height="1.1490486"
x="24.638252"
y="26.853086" /><rect
y="37.990021"
x="24.660349"
height="1.1490486"
width="0.68500972"
id="rect4648"
style="fill:#232323;fill-opacity:1;stroke:none" /></g><g
id="g4600"
style="fill:#a2a2a2;fill-opacity:1"
transform="translate(69.009202,-2.3643883)"><rect
style="fill:#a2a2a2;fill-opacity:1;stroke:none"
id="rect4602"
width="1"
height="5.9375"
x="40.742825"
y="-1.6325631"
transform="matrix(0.67898035,0.73415645,-0.73415645,0.67898035,0,0)" /><path
sodipodi:type="arc"
style="fill:#a2a2a2;fill-opacity:1;stroke:none"
id="path4604"
sodipodi:cx="25.03125"
sodipodi:cy="33.03125"
sodipodi:rx="1.03125"
sodipodi:ry="0.96875"
d="M 26.0625,33.03125 C 26.0625,33.566276 25.600794,34 25.03125,34 24.461706,34 24,33.566276 24,33.03125 c 0,-0.535026 0.461706,-0.96875 1.03125,-0.96875 0.569544,0 1.03125,0.433724 1.03125,0.96875 z"
transform="matrix(0.95714504,0,0,1.0228099,1.028519,-0.77553647)" /></g><g
id="g4596"
transform="translate(68.942911,-2.9168155)"><rect
transform="matrix(0.67898035,0.73415645,-0.73415645,0.67898035,0,0)"
y="-1.6325631"
x="40.742825"
height="5.9375"
width="1"
id="rect4592"
style="fill:#ff2121;fill-opacity:1;stroke:none" /><path
transform="matrix(0.95714504,0,0,1.0228099,1.028519,-0.77553647)"
d="M 26.0625,33.03125 C 26.0625,33.566276 25.600794,34 25.03125,34 24.461706,34 24,33.566276 24,33.03125 c 0,-0.535026 0.461706,-0.96875 1.03125,-0.96875 0.569544,0 1.03125,0.433724 1.03125,0.96875 z"
sodipodi:ry="0.96875"
sodipodi:rx="1.03125"
sodipodi:cy="33.03125"
sodipodi:cx="25.03125"
id="path4594"
style="fill:#ff2121;fill-opacity:1;stroke:none"
sodipodi:type="arc" /></g><path
sodipodi:type="arc"
style="fill:none;stroke:#646464;stroke-width:1.37966764;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="path4608"
sodipodi:cx="25"
sodipodi:cy="33"
sodipodi:rx="10"
sodipodi:ry="10"
d="m 35,33 a 10,10 0 1 1 -20,0 10,10 0 1 1 20,0 z"
transform="matrix(0.74693846,0,0,0.74919978,75.258402,5.3595919)" /><g
id="g4628"
transform="translate(68.942911,-2.9168155)"><rect
y="26.853086"
x="24.638252"
height="1.1490486"
width="0.68500972"
id="rect4624"
style="fill:#232323;fill-opacity:1;stroke:none" /><rect
style="fill:#232323;fill-opacity:1;stroke:none"
id="rect4626"
width="0.68500972"
height="1.1490486"
x="24.660349"
y="37.990021" /></g><g
id="g4632"
transform="matrix(0,1,-1,0,126.93079,5.087457)"><rect
style="fill:#232323;fill-opacity:1;stroke:none"
id="rect4634"
width="0.68500972"
height="1.1490486"
x="24.638252"
y="26.853086" /><rect
y="37.990021"
x="24.660349"
height="1.1490486"
width="0.68500972"
id="rect4636"
style="fill:#232323;fill-opacity:1;stroke:none" /></g><rect
style="fill:#232323;fill-opacity:1;stroke:none"
id="rect4640"
width="0.68500972"
height="1.1490486"
x="-45.50618"
y="-93.834114"
transform="matrix(-0.70710678,0.70710678,-0.70710678,-0.70710678,0,0)" /><rect
y="-82.697182"
x="-45.484081"
height="1.1490486"
width="0.68500972"
id="rect4642"
style="fill:#232323;fill-opacity:1;stroke:none"
transform="matrix(-0.70710678,0.70710678,-0.70710678,-0.70710678,0,0)" /><g
transform="matrix(1.2021267,0,0,0.11694557,63.863529,11.401153)"
id="g4707"><rect
ry="16.649275"
rx="1.6196786"
y="27.449707"
x="10.297243"
height="18.119614"
width="29.256544"
id="rect4709"
style="fill:#d4d4d4;fill-opacity:1;stroke:none" /><g
id="g4711"><g
id="g4713"><rect
ry="16.514845"
style="fill:none;stroke:#646464;stroke-width:2.66511464;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="rect4715"
width="29.020325"
height="17.982044"
x="10.510214"
y="27.517153"
rx="1" /></g></g></g><g
id="g4985"
transform="translate(0,-11.125)"><g
transform="matrix(0.01936185,0,0,0.01859457,41.505849,40.621203)"
id="g4874"
style="display:inline"><rect
style="opacity:0.40206185;color:#000000;fill:url(#linearGradient4894);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
id="rect4876"
width="1339.6335"
height="478.35718"
x="-1559.2523"
y="-150.69685" /><path
inkscape:connector-curvature="0"
style="opacity:0.40206185;color:#000000;fill:url(#radialGradient4896);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
d="m -219.61876,-150.68038 c 0,0 0,478.33079 0,478.33079 142.874166,0.90045 345.40022,-107.16966 345.40014,-239.196175 0,-132.026537 -159.436816,-239.134595 -345.40014,-239.134615 z"
id="path4878"
sodipodi:nodetypes="cccc" /><path
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc"
id="path4880"
d="m -1559.2523,-150.68038 c 0,0 0,478.33079 0,478.33079 -142.8742,0.90045 -345.4002,-107.16966 -345.4002,-239.196175 0,-132.026537 159.4368,-239.134595 345.4002,-239.134615 z"
style="opacity:0.40206185;color:#000000;fill:url(#radialGradient4898);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible" /></g><g
id="g4882"
transform="translate(-0.96783034,-2.9168155)"><rect
ry="1.9470588"
rx="1.9470588"
y="27.449707"
x="10.297243"
height="18.119614"
width="29.256544"
id="rect4884"
style="fill:#eaeaea;fill-opacity:1;stroke:none" /><g
id="g4886"><g
id="g4888"><rect
ry="1.9313381"
style="fill:none;stroke:#646464;stroke-width:0.99927008;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="rect4890"
width="29.020325"
height="17.982044"
x="10.510214"
y="27.517153"
rx="1.9313381" /></g><rect
ry="1.0032353"
style="fill:none;stroke:#ffffff;stroke-width:0.99249077;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="rect4892"
width="26.967867"
height="16.005489"
x="11.532422"
y="28.489801"
rx="1.0032353" /></g></g><g
transform="matrix(1,0,0,0.9039548,0,2.5932203)"
id="g4922"><rect
style="fill:#000000;fill-opacity:1;stroke:none"
id="rect4900"
width="1"
height="11.0625"
x="13.0625"
y="27" /><rect
y="27"
x="15.0625"
height="11.0625"
width="1"
id="rect4902"
style="fill:#000000;fill-opacity:1;stroke:none" /><rect
style="fill:#000000;fill-opacity:1;stroke:none"
id="rect4904"
width="1"
height="11.0625"
x="18"
y="27" /><rect
y="27"
x="19.9375"
height="11.0625"
width="2"
id="rect4906"
style="fill:#000000;fill-opacity:1;stroke:none" /><rect
y="27"
x="22.9375"
height="11.0625"
width="1"
id="rect4908"
style="fill:#000000;fill-opacity:1;stroke:none" /><rect
style="fill:#000000;fill-opacity:1;stroke:none"
id="rect4910"
width="2.0625"
height="11.0625"
x="25.9375"
y="27" /><rect
y="27"
x="28.9375"
height="11.0625"
width="1.125"
id="rect4912"
style="fill:#000000;fill-opacity:1;stroke:none" /><rect
style="fill:#000000;fill-opacity:1;stroke:none"
id="rect4914"
width="1.125"
height="11.0625"
x="30.9375"
y="27" /><rect
y="27"
x="33.9375"
height="11.0625"
width="1.125"
id="rect4916"
style="fill:#000000;fill-opacity:1;stroke:none" /></g><text
sodipodi:linespacing="125%"
id="text4918"
y="40.195068"
x="12.981453"
style="font-size:36.59571838px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
xml:space="preserve"><tspan
style="font-size:3.65957189px;font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-family:Inconsolata;-inkscape-font-specification:Inconsolata Medium"
y="40.195068"
x="12.981453"
id="tspan4920"
sodipodi:role="line">5 92102431 8</tspan></text>
<rect
y="31.0625"
x="9.0625"
height="1"
width="30"
id="rect4933"
style="fill:#ff0000;fill-opacity:1;stroke:none" /><rect
style="fill:#ff0000;fill-opacity:1;stroke:none;filter:url(#filter4971)"
id="rect4957"
width="30"
height="1"
x="9.0625"
y="31.0625" /><path
inkscape:connector-curvature="0"
id="path4975"
d="M 9.0625,32.0625 20,47.875 28.9375,47.9375 39,32.0625 z"
style="opacity:0.29787233;fill:url(#linearGradient4983);fill-opacity:1;stroke:none" /></g></svg>

After

Width:  |  Height:  |  Size: 41 KiB

View File

@ -0,0 +1,62 @@
v Affichage Catégories doivent respecter le poids
v Affichage Catégories par poids doivent avoir une hiérarchie plate
v Le Onscreen keyboard ne fonctionne plus
v Scrolling dans la sélection produit et dans
la liste de courses
v Réductions
v Redesign liste de courses
v bugs scans produits par prix par poids
v si pas photo photo par defaut
v WebSQL
v Redesign header, bouton retour aux backend en mode caissière
v bouton exit en mode caissière doit retourner au backend.
v posting orders
v bug ajout ligne payment à zero + curseur dedans + suppression + design plus grand
v si case print via proxy cochée, alors on skip l'ecran receipt
v bouton exit self checkout
v demarrage en mode self-checkout si self-checkout
v produits poid code barre non reconnus
v activer popup client aux écrans self-checkout
v discount pas plus grand que 100
v numpad dans l'écran payment pour pouvoir entrer le montant
v différence de total. Methode d'arrondis
- login alternatif
- user preferences : désactiver la balance etc.
- numpad state parfois sans state
- numpad state en mode pesée ?? le cacher ?
- Redesign du receipt
- retirer le receipt screen
- le numpad en mode paymenet marche pas
- l'écran de payement est hyper laid
- l'écran de pesée est hyper laid
x générer les données de printing
x popups d'erreur certainement buggés
TODO AUG 20
-----------
* GREEN STATUS
- make it green !
* TRUNK
- Connection status tooltip
v Remove receipt screen
- Finish the receipt JSON generation
- Modifie le widget de liste course
v supprimer l'écran
v bloquer sur l'impression
v L'impression est foireuse
* CLIENT
- create a new branch
- Self-checkout welcome screen
- removal of products for the root category
- Terminal de payement
- Code à barres
- Vidanges
- Design
- Backport
- CSS pour écran résistif
- Test sur matos

View File

@ -0,0 +1,235 @@
function openerp_pos_db(instance, module){
/* PosLS is a LocalStorage based implementation of the point of sale database,
it performs better for few products, but does not scale beyond 500 products.
*/
module.PosLS = instance.web.Class.extend({
name: 'openerp_pos_ls', //the prefix of the localstorage data
limit: 100, // the maximum number of results returned by a search
init: function(options){
options = options || {};
this.name = options.name || this.name;
this.limit = options.limit || this.limit;
//cache the data in memory to avoid roundtrips to the localstorage
this.cache = {};
this.category_by_id = {};
this.root_category_id = 0;
this.category_products = {};
this.category_ancestors = {};
this.category_childs = {};
this.category_parent = {};
this.category_search_string = {};
},
/* returns the category object from its id. If you pass a list of id as parameters, you get
* a list of category objects.
*/
get_category_by_id: function(categ_id){
if(categ_id instanceof Array){
var list = [];
for(var i = 0, len = categ_id.length; i < len; i++){
var cat = this.category_by_id[categ_id[i]];
if(cat){
list.push(cat);
}else{
console.error("get_category_by_id: no category has id:",categ_id[i]);
}
}
return list;
}else{
return this.category_by_id[categ_id];
}
},
/* returns a list of the category's child categories ids, or an empty list
* if a category has no childs */
get_category_childs_ids: function(categ_id){
return this.category_childs[categ_id] || [];
},
/* returns a list of all ancestors (parent, grand-parent, etc) categories ids
* starting from the root category to the direct parent */
get_category_ancestors_ids: function(categ_id){
return this.category_ancestors[categ_id] || [];
},
/* returns the parent category's id of a category, or the root_category_id if no parent.
* the root category is parent of itself. */
get_category_parent_id: function(categ_id){
return this.category_parent[categ_id] || this.root_category_id;
},
/* adds categories definitions to the database. categories is a list of categories objects as
* returned by the openerp server. Categories must be inserted before the products or the
* product/ categories association may (will) not work properly */
add_categories: function(categories){
var self = this;
if(!this.category_by_id[this.root_category_id]){
this.category_by_id[this.root_category_id] = {
id : this.root_category_id,
name : 'Root',
};
}
for(var i=0, len = categories.length; i < len; i++){
this.category_by_id[categories[i].id] = categories[i];
}
for(var i=0, len = categories.length; i < len; i++){
var cat = categories[i];
var parent_id = cat.parent_id[0] || this.root_category_id;
this.category_parent[cat.id] = cat.parent_id[0];
if(!this.category_childs[parent_id]){
this.category_childs[parent_id] = [];
}
this.category_childs[parent_id].push(cat.id);
}
function make_ancestors(cat_id, ancestors){
self.category_ancestors[cat_id] = ancestors;
ancestors = ancestors.slice(0);
ancestors.push(cat_id);
var childs = self.category_childs[cat_id] || [];
for(var i=0, len = childs.length; i < len; i++){
make_ancestors(childs[i], ancestors);
}
}
make_ancestors(this.root_category_id, []);
},
/* loads a record store from the database. returns default if nothing is found */
load: function(store,deft){
if(this.cache[store] !== undefined){
return this.cache[store];
}
var data = localStorage[this.name + '_' + store];
if(data !== undefined){
data = JSON.parse(data);
this.cache[store] = data;
return data;
}else{
return deft;
}
},
/* saves a record store to the database */
save: function(store,data){
localStorage[this.name + '_' + store] = JSON.stringify(data);
this.cache[store] = data;
},
_product_search_string: function(product){
var str = '' + product.id + ':' + product.name;
if(product.ean13){
str += '|' + product.ean13;
}
return str + '\n';
},
add_products: function(products){
var stored_products = this.load('products',{});
var stored_categories = this.load('categories',{});
if(!products instanceof Array){
products = [products];
}
for(var i = 0, len = products.length; i < len; i++){
var product = products[i];
var search_string = this._product_search_string(product);
var categ_id = product.pos_categ_id[0];
if(!stored_categories[categ_id]){
stored_categories[categ_id] = [];
}
stored_categories[categ_id].push(product.id);
if(this.category_search_string[categ_id] === undefined){
this.category_search_string[categ_id] = '';
}
this.category_search_string[categ_id] += search_string;
var ancestors = this.get_category_ancestors_ids(categ_id) || [];
for(var j = 0; j < ancestors.length; j++){
var ancestor = ancestors[j];
if(! stored_categories[ancestor]){
stored_categories[ancestor] = [];
}
stored_categories[ancestor].push(product.id);
if( this.category_search_string[ancestor] === undefined){
this.category_search_string[ancestor] = '';
}
this.category_search_string[ancestor] += search_string;
}
stored_products[product.id] = product;
}
this.save('products',stored_products);
this.save('categories',stored_categories);
},
/* removes all the data from the database. TODO : being able to selectively remove data */
clear: function(stores){
for(var i = 0, len = arguments.length; i < len; i++){
localStorage.removeItem(this.name + '_' + arguments[i]);
}
},
/* this internal methods returns the count of properties in an object. */
_count_props : function(obj){
var count = 0;
for(var prop in obj){
if(obj.hasOwnProperty(prop)){
count++;
}
}
return count;
},
get_product_by_id: function(id){
return this.load('products',{})[id];
},
get_product_by_ean13: function(ean13){
var products = this.load('products',{});
for(var i in products){
if( products[i] && products[i].ean13 === ean13){
return products[i];
}
}
return undefined;
},
get_product_by_category: function(category_id){
var stored_categories = this.load('categories',{});
var stored_products = this.load('products',{});
var product_ids = stored_categories[category_id];
var list = [];
for(var i = 0, len = Math.min(product_ids.length,this.limit); i < len; i++){
list.push(stored_products[product_ids[i]]);
}
return list;
},
/* returns a list of products with :
* - a category that is or is a child of category_id,
* - a name, package or ean13 containing the query (case insensitive)
*/
search_product_in_category: function(category_id, query){
var re = RegExp("([0-9]+):.*?"+query,"gi");
var results = [];
for(var i = 0; i < this.limit; i++){
r = re.exec(this.category_search_string[category_id]);
if(r){
var id = Number(r[1]);
results.push(this.get_product_by_id(id));
}else{
break;
}
}
return results;
},
add_order: function(order){
var last_id = this.load('last_order_id',0);
var orders = this.load('orders',[]);
orders.push({id: last_id + 1, data: order});
this.save('last_order_id',last_id+1);
this.save('orders',orders);
},
remove_order: function(order_id){
var orders = this.load('orders',[]);
orders = _.filter(orders, function(order){
return order.id !== order_id;
});
this.save('orders',orders);
},
get_orders: function(){
return this.load('orders',[]);
},
});
}

View File

@ -17,7 +17,9 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
delay_payment: function(){ this.activate(); this.payment_status = 'waiting_for_payment'; },
}))();
//window.debug_devices = debug_devices;
if(jQuery.deparam(jQuery.param.querystring()).debug !== undefined){
window.debug_devices = debug_devices;
}
// this object interfaces with the local proxy to communicate to the various hardware devices
// connected to the Point of Sale. As the communication only goes from the POS to the proxy,
@ -42,21 +44,26 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
success_callback = success_callback || function(){};
error_callback = error_callback || function(){};
if(debug_devices && debug_devices.active){
if(jQuery.deparam(jQuery.param.querystring()).debug !== undefined){
console.log('PROXY:',name,params);
}else{
}
if(!(debug_devices && debug_devices.active)){
this.connection.rpc('/pos/'+name, params || {}, success_callback, error_callback);
}
},
//a product has been scanned and recognized with success
scan_item_success: function(){
this.message('scan_item_success');
// ean is a parsed ean object
scan_item_success: function(ean){
this.message('scan_item_success',ean);
},
//a product has been scanned but not recognized
scan_item_error_unrecognized: function(){
this.message('scan_item_error_unrecognized');
// a product has been scanned but not recognized
// ean is a parsed ean object
scan_item_error_unrecognized: function(ean){
this.message('scan_item_error_unrecognized',ean);
},
//the client is asking for help
@ -222,11 +229,11 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
this.action_callback_stack = [];
this.price_prefix_set = attributes.price_prefix_set || {'02':'', '22':'', '24':'', '26':'', '28':''};
this.weight_prefix_set = attributes.weight_prefix_set || {'21':'','23':'','27':'','29':'','25':''};
this.client_prefix_set = attributes.weight_prefix_set || {'42':''};
this.cashier_prefix_set = attributes.weight_prefix_set || {'40':''};
this.discount_prefix_set = attributes.weight_prefix_set || {'44':''};
this.weight_prefix_set = attributes.weight_prefix_set || {'21':''};
this.discount_prefix_set = attributes.discount_prefix_set || {'22':''};
this.price_prefix_set = attributes.price_prefix_set || {'23':''};
this.cashier_prefix_set = attributes.cashier_prefix_set || {'041':''};
this.client_prefix_set = attributes.client_prefix_set || {'042':''};
},
save_callbacks: function(){
var callbacks = {};
@ -270,41 +277,37 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
this.action_callback[action] = undefined;
}
},
// returns the checksum of the ean, or -1 if the ean has not the correct length, ean must be a string
ean_checksum: function(ean){
var code = ean.split('');
if(code.length !== 13){
return -1;
}
var oddsum = 0, evensum = 0, total = 0;
code = code.reverse().splice(1);
for(var i = 0; i < code.length; i++){
if(i % 2 == 0){
oddsum += Number(code[i]);
}else{
evensum += Number(code[i]);
}
}
total = oddsum * 3 + evensum;
return Number((10 - total % 10) % 10);
},
// returns true if the ean is a valid EAN codebar number by checking the control digit.
// ean must be a string
check_ean: function(ean){
var code = ean.split('');
for(var i = 0; i < code.length; i++){
code[i] = Number(code[i]);
return this.ean_checksum(ean) === Number(ean[ean.length-1]);
},
// returns a valid zero padded ean13 from an ean prefix. the ean prefix must be a string.
sanitize_ean:function(ean){
ean = ean.substr(0,13);
for(var n = 0, count = (13 - ean.length); n < count; n++){
ean = ean + '0';
}
var st1 = code.slice();
var st2 = st1.slice(0,st1.length-1).reverse();
// some EAN13 barcodes have a length of 12, as they start by 0
while (st2.length < 12) {
st2.push(0);
}
var countSt3 = 1;
var st3 = 0;
$.each(st2, function() {
if (countSt3%2 === 1) {
st3 += this;
}
countSt3 ++;
});
st3 *= 3;
var st4 = 0;
var countSt4 = 1;
$.each(st2, function() {
if (countSt4%2 === 0) {
st4 += this;
}
countSt4 ++;
});
var st5 = st3 + st4;
var cd = (10 - (st5%10)) % 10;
return code[code.length-1] === cd;
return ean.substr(0,12) + this.ean_checksum(ean);
},
// attempts to interpret an ean (string encoding an ean)
@ -327,41 +330,47 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
type:'unknown', //
prefix:'',
ean:ean,
base_ean: ean,
id:'',
value: 0,
unit: 'none',
};
var prefix2 = ean.substring(0,2);
console.log('ean',ean);
if(!this.check_ean(ean)){
function match_prefix(prefix_set, type){
for(prefix in prefix_set){
if(ean.substring(0,prefix.length) === prefix){
parse_result.prefix = prefix;
parse_result.type = type;
return true;
}
}
return false;
}
if (!this.check_ean(ean)){
parse_result.type = 'error';
}else if (prefix2 in this.price_prefix_set){
parse_result.type = 'price';
parse_result.prefix = prefix2;
} else if( match_prefix(this.price_prefix_set,'price')){
parse_result.id = ean.substring(0,7);
parse_result.base_ean = this.sanitize_ean(ean.substring(0,7));
parse_result.value = Number(ean.substring(7,12))/100.0;
parse_result.unit = 'euro';
} else if (prefix2 in this.weight_prefix_set){
parse_result.type = 'weight';
parse_result.prefix = prefix2;
} else if( match_prefix(this.weight_prefix_set,'weight')){
parse_result.id = ean.substring(0,7);
parse_result.value = Number(ean.substring(7,12))/1000.0;
parse_result.base_ean = this.sanitize_ean(ean.substring(0,7));
parse_result.unit = 'Kg';
}else if (prefix2 in this.client_prefix_set){
parse_result.type = 'client';
parse_result.prefix = prefix2;
} else if( match_prefix(this.client_prefix_set,'client')){
parse_result.id = ean.substring(0,7);
}else if (prefix2 in this.cashier_prefix_set){
parse_result.type = 'cashier';
parse_result.prefix = prefix2;
parse_result.unit = 'Kg';
} else if( match_prefix(this.cashier_prefix_set,'cashier')){
parse_result.id = ean.substring(0,7);
}else if (prefix2 in this.discount_prefix_set){
parse_result.type = 'discount';
parse_result.prefix = prefix2;
} else if( match_prefix(this.discount_prefix_set,'discount')){
parse_result.id = ean.substring(0,7);
parse_result.base_ean = this.sanitize_ean(ean.substring(0,7));
parse_result.value = Number(ean.substring(7,12))/100.0;
parse_result.unit = '%';
}else{
} else {
parse_result.type = 'unit';
parse_result.prefix = '';
parse_result.id = ean;
@ -402,7 +411,7 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
var parse_result = self.parse_ean(codeNumbers.join(''));
if (parse_result.type === 'error') { //most likely a checksum error, raise warning
console.error('ERROR: barcode checksum error:',parse_result);
console.warn('WARNING: barcode checksum error:',parse_result);
}else if(parse_result.type in {'unit':'', 'weight':'', 'price':''}){ //ean is associated to a product
if(self.action_callback['product']){
self.action_callback['product'](parse_result);

View File

@ -5,6 +5,8 @@ openerp.point_of_sale = function(instance) {
var module = instance.point_of_sale;
openerp_pos_db(instance,module); // import db.js
openerp_pos_models(instance,module); // import pos_models.js
openerp_pos_basewidget(instance,module); // import pos_basewidget.js

View File

@ -1,51 +1,6 @@
function openerp_pos_models(instance, module){ //module is instance.point_of_sale
var QWeb = instance.web.qweb;
module.LocalStorageDAO = instance.web.Class.extend({
add_operation: function(operation) {
var self = this;
return $.async_when().pipe(function() {
var tmp = self._get('oe_pos_operations', []);
var last_id = self._get('oe_pos_operations_sequence', 1);
tmp.push({'id': last_id, 'data': operation});
self._set('oe_pos_operations', tmp);
self._set('oe_pos_operations_sequence', last_id + 1);
});
},
remove_operation: function(id) {
var self = this;
return $.async_when().pipe(function() {
var tmp = self._get('oe_pos_operations', []);
tmp = _.filter(tmp, function(el) {
return el.id !== id;
});
self._set('oe_pos_operations', tmp);
});
},
get_operations: function() {
var self = this;
return $.async_when().pipe(function() {
return self._get('oe_pos_operations', []);
});
},
_get: function(key, default_) {
var txt = localStorage['oe_pos_dao_'+key];
if (! txt)
return default_;
return JSON.parse(txt);
},
_set: function(key, value) {
localStorage['oe_pos_dao_'+key] = JSON.stringify(value);
},
reset_stored_data: function(){
for(key in localStorage){
if(key.indexOf('oe_pos_dao_') === 0){
delete localStorage[key];
}
}
},
});
var fetch = function(model, fields, domain, ctx){
return new instance.web.Model(model).query(fields).filter(domain).context(ctx).all()
};
@ -58,29 +13,20 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
// this is done asynchronously, a ready deferred alows the GUI to wait interactively
// for the loading to be completed
// There is a single instance of the PosModel for each Front-End instance, it is usually called
// 'pos' and is available to almost all widgets.
// 'pos' and is available to all widgets extending PosWidget.
module.PosModel = Backbone.Model.extend({
initialize: function(session, attributes) {
Backbone.Model.prototype.initialize.call(this, attributes);
var self = this;
this.dao = new module.LocalStorageDAO(); // used to store the order's data on the Hard Drive
this.session = session;
this.ready = $.Deferred(); // used to notify the GUI that the PosModel has loaded all resources
this.flush_mutex = new $.Mutex(); // used to make sure the orders are sent to the server once at time
//this.build_tree = _.bind(this.build_tree, this); // ???
this.session = session;
this.categories = {};
this.root_category = null;
this.weightable_categories = []; // a flat list of all categories that directly contain weightable products
this.barcode_reader = new module.BarcodeReader({'pos': this}); // used to read barcodes
this.proxy = new module.ProxyDevice(); // used to communicate to the hardware devices via a local proxy
// pos settings
this.use_scale = false;
this.use_proxy_printer = false;
this.use_virtual_keyboard = false;
this.use_websql = false;
this.use_barcode_scanner = false;
this.barcode_reader = new module.BarcodeReader({'pos': this}); // used to read barcodes
this.proxy = new module.ProxyDevice(); // used to communicate to the hardware devices via a local proxy
this.db = new module.PosLS(); // a database used to store the products and categories
this.db.clear('products','categories');
// default attributes values. If null, it will be loaded below.
this.set({
@ -98,12 +44,12 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
'products': new module.ProductCollection(),
'cashRegisters': null,
'product_list': null, // the list of all products, does not change.
'bank_statements': null,
'taxes': null,
'pos_session': null,
'pos_config': null,
'categories': null,
'units': null,
'units_by_id': null,
'selectedOrder': undefined,
});
@ -113,10 +59,9 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
// We fetch the backend data on the server asynchronously. this is done only when the pos user interface is launched,
// Any change on this data made on the server is thus not reflected on the point of sale until it is relaunched.
var user_def = fetch('res.users',['name','company_id'],[['id','=',this.session.uid]])
var loaded = fetch('res.users',['name','company_id'],[['id','=',this.session.uid]])
.pipe(function(users){
var user = users[0];
self.set('user',user);
self.set('user',users[0]);
return fetch('res.company',
[
@ -124,171 +69,114 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
'email',
'website',
'company_registry',
//TODO contact_address
'vat',
'name',
'phone'
'phone',
'partner_id',
],
[['id','=',user.company_id[0]]])
[['id','=',users[0].company_id[0]]]);
}).pipe(function(companies){
var company = companies[0];
self.set('company',company);
self.set('company',companies[0]);
return fetch('res.currency',['symbol','position'],[['id','=',company.currency_id[0]]]);
}).pipe(function (currencies){
return fetch('res.partner',['contact_address'],[['id','=',companies[0].partner_id[0]]]);
}).pipe(function(company_partners){
self.get('company').contact_address = company_partners[0].contact_address;
return fetch('res.currency',['symbol','position'],[['id','=',self.get('company').currency_id[0]]]);
}).pipe(function(currencies){
self.set('currency',currencies[0]);
});
var cat_def = fetch('pos.category', ['id','name', 'parent_id', 'child_id', 'image_medium'])
.pipe(function(result){
return self.set({'categories': result});
});
var uom_def = fetch( //unit of measure
'product.uom',
null,
null
).then(function(result){
self.set({'units': result});
return fetch('product.uom', null, null);
}).pipe(function(units){
self.set('units',units);
var units_by_id = {};
for(var i = 0, len = result.length; i < len; i++){
units_by_id[result[i].id] = result[i];
for(var i = 0, len = units.length; i < len; i++){
units_by_id[units[i].id] = units[i];
}
self.set({'units_by_id':units_by_id});
});
var pack_def = fetch(
'product.packaging',
null,
null
).then(function(packaging){
self.set('product.packaging',packaging);
});
var users_def = fetch(
'res.users',
['name','ean13'],
[['ean13', '!=', false]]
).then(function(result){
self.set({'user_list':result});
});
var tax_def = fetch('account.tax', ['amount','price_include','type'])
.then(function(result){
self.set({'taxes': result});
});
var session_def = fetch( // loading the PoS Session.
'pos.session',
['id', 'journal_ids','name','user_id','config_id','start_at','stop_at'],
[['state', '=', 'opened'], ['user_id', '=', this.session.uid]]
).pipe(function(result) {
// some data are associated with the pos session, like the pos config and bank statements.
// we must have a valid session before we can read those.
self.set('units_by_id',units_by_id);
var session_data_def = new $.Deferred();
return fetch('product.packaging', null, null);
}).pipe(function(packagings){
self.set('product.packaging',packagings);
if( result.length !== 0 ) {
var pos_session = result[0];
return fetch('res.users', ['name','ean13'], [['ean13', '!=', false]]);
}).pipe(function(users){
self.set('user_list',users);
self.set({'pos_session': pos_session});
return fetch('account.tax', ['amount', 'price_include', 'type']);
}).pipe(function(taxes){
self.set('taxes', taxes);
var pos_config_def = fetch(
'pos.config',
['name','journal_ids','shop_id','journal_id',
'iface_self_checkout', 'iface_websql', 'iface_led', 'iface_cashdrawer',
'iface_payment_terminal', 'iface_electronic_scale', 'iface_barscan', 'iface_vkeyboard',
'iface_print_via_proxy','iface_cashdrawer','state','sequence_id','session_ids'],
[['id','=', pos_session.config_id[0]]]
).pipe(function(result){
var pos_config = result[0]
self.set({'pos_config': pos_config});
self.use_scale = pos_config.iface_electronic_scale || false;
self.use_proxy_printer = pos_config.iface_print_via_proxy || false;
self.use_virtual_keyboard = pos_config.iface_vkeyboard || false;
self.use_websql = pos_config.iface_websql || false;
self.use_barcode_scanner = pos_config.iface_barscan || false;
self.use_selfcheckout = pos_config.iface_self_checkout || false;
self.use_cashbox = pos_config.iface_cashdrawer || false;
return fetch(
'pos.session',
['id', 'journal_ids','name','user_id','config_id','start_at','stop_at'],
[['state', '=', 'opened'], ['user_id', '=', self.session.uid]]
);
}).pipe(function(sessions){
self.set('pos_session', sessions[0]);
return shop_def = fetch('sale.shop',[], [['id','=',pos_config.shop_id[0]]])
}).pipe(function(shops){
self.set('shop',shops[0]);
return fetch(
'product.product',
//context {pricelist: shop.pricelist_id[0]}
['name', 'list_price','price','pos_categ_id', 'taxes_id','image_medium', 'ean13', 'to_weight', 'uom_id', 'uos_id', 'uos_coeff', 'mes_type'],
[['pos_categ_id','!=', false]],
{pricelist: shops[0].pricelist_id[0]} // context for price
);
}).pipe( function(product_list){
self.set({'product_list': product_list});
});
return fetch(
'pos.config',
['name','journal_ids','shop_id','journal_id',
'iface_self_checkout', 'iface_led', 'iface_cashdrawer',
'iface_payment_terminal', 'iface_electronic_scale', 'iface_barscan', 'iface_vkeyboard',
'iface_print_via_proxy','iface_cashdrawer','state','sequence_id','session_ids'],
[['id','=', self.get('pos_session').config_id[0]]]
);
}).pipe(function(configs){
var pos_config = configs[0];
self.set('pos_config', pos_config);
self.iface_electronic_scale = !!pos_config.iface_electronic_scale;
self.iface_print_via_proxy = !!pos_config.iface_print_via_proxy;
self.iface_vkeyboard = !!pos_config.iface_vkeyboard;
self.iface_self_checkout = !!pos_config.iface_self_checkout;
self.iface_cashdrawer = !!pos_config.iface_cashdrawer;
var bank_def = fetch(
'account.bank.statement',
['account_id','currency','journal_id','state','name','user_id','pos_session_id'],
[['state','=','open'],['pos_session_id', '=', pos_session.id]]
).then(function(result){
self.set({'bank_statements':result});
});
return fetch('sale.shop',[],[['id','=',pos_config.shop_id[0]]]);
}).pipe(function(shops){
self.set('shop',shops[0]);
var journal_def = fetch(
'account.journal',
undefined,
[['user_id','=',pos_session.user_id[0]]]
).then(function(result){
self.set({'journals':result});
});
return fetch('pos.category', ['id','name','parent_id','child_id','image'])
}).pipe(function(categories){
self.db.add_categories(categories);
// associate the bank statements with their journals.
var bank_process_def = $.when(bank_def, journal_def)
.then(function(){
var bank_statements = self.get('bank_statements');
var journals = self.get('journals');
for(var i = 0, ilen = bank_statements.length; i < ilen; i++){
for(var j = 0, jlen = journals.length; j < jlen; j++){
if(bank_statements[i].journal_id[0] === journals[j].id){
bank_statements[i].journal = journals[j];
bank_statements[i].self_checkout_payment_method = journals[j].self_checkout_payment_method;
}
}
}
});
return fetch(
'product.product',
['name', 'list_price','price','pos_categ_id', 'taxes_id', 'ean13', 'to_weight', 'uom_id', 'uos_id', 'uos_coeff', 'mes_type'],
[['pos_categ_id','!=', false]],
{pricelist: self.get('shop').pricelist_id[0]} // context for price
);
}).pipe(function(products){
self.db.add_products(products);
session_data_def = $.when(pos_config_def,bank_def,journal_def,bank_process_def);
return fetch(
'account.bank.statement',
['account_id','currency','journal_id','state','name','user_id','pos_session_id'],
[['state','=','open'],['pos_session_id', '=', self.get('pos_session').id]]
);
}).pipe(function(bank_statements){
self.set('bank_statements', bank_statements);
}else{
session_data_def.reject();
}
return session_data_def;
});
// associate the products with their categories
var prod_process_def = $.when(cat_def, session_def)
.pipe(function(){
var product_list = self.get('product_list');
var categories = self.get('categories');
var cat_by_id = {};
for(var i = 0; i < categories.length; i++){
cat_by_id[categories[i].id] = categories[i];
}
//set the parent in the category
for(var i = 0; i < categories.length; i++){
categories[i].parent_category = cat_by_id[categories[i].parent_id[0]];
}
for(var i = 0; i < product_list.length; i++){
product_list[i].pos_category = cat_by_id[product_list[i].pos_categ_id[0]];
return fetch('account.journal', undefined, [['user_id','=', self.get('pos_session').user_id[0]]]);
}).pipe(function(journals){
self.set('journals',journals);
// associate the bank statements with their journals.
var bank_statements = self.get('bank_statements');
for(var i = 0, ilen = bank_statements.length; i < ilen; i++){
for(var j = 0, jlen = journals.length; j < jlen; j++){
if(bank_statements[i].journal_id[0] === journals[j].id){
bank_statements[i].journal = journals[j];
bank_statements[i].self_checkout_payment_method = journals[j].self_checkout_payment_method;
}
}
}
self.set({'cashRegisters' : new module.CashRegisterCollection(self.get('bank_statements'))});
});
// when all the data has loaded, we compute some stuff, and declare the Pos ready to be used.
$.when(pack_def, cat_def, user_def, users_def, uom_def, session_def, tax_def, prod_process_def, user_def, this.flush())
$.when(loaded)
.then(function(){
self.build_categories();
self.set({'cashRegisters' : new module.CashRegisterCollection(self.get('bank_statements'))});
//self.log_loaded_data(); //Uncomment if you want to log the data to the console for easier debugging
self.ready.resolve();
},function(){
@ -302,7 +190,6 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
log_loaded_data: function(){
console.log('PosModel data has been loaded:');
console.log('PosModel: categories:',this.get('categories'));
console.log('PosModel: product_list:',this.get('product_list'));
console.log('PosModel: units:',this.get('units'));
console.log('PosModel: bank_statements:',this.get('bank_statements'));
console.log('PosModel: journals:',this.get('journals'));
@ -316,7 +203,6 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
console.log('PosModel: user_list:',this.get('user_list'));
console.log('PosModel: user:',this.get('user'));
console.log('PosModel.session:',this.session);
console.log('PosModel.categories:',this.categories);
console.log('PosModel end of data log.');
},
@ -324,7 +210,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
// order and a valid selected order
on_removed_order: function(removed_order){
if( this.get('orders').isEmpty()){
this.add_and_select_order(new module.Order({ pos: this }));
this.add_new_order();
}
if( this.get('selectedOrder') === removed_order){
this.set({ selectedOrder: this.get('orders').last() });
@ -333,175 +219,70 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
// saves the order locally and try to send it to the backend. 'record' is a bizzarely defined JSON version of the Order
push_order: function(record) {
var self = this;
return this.dao.add_operation(record).pipe(function(){
return self.flush();
});
this.db.add_order(record);
this.flush();
},
add_and_select_order: function(newOrder) {
(this.get('orders')).add(newOrder);
return this.set({
selectedOrder: newOrder
});
//creates a new empty order and sets it as the current order
add_new_order: function(){
var order = new module.Order({pos:this});
this.get('orders').add(order);
this.set('selectedOrder', order);
},
// attemps to send all pending orders ( stored in the DAO ) to the server.
// it will do it one by one, and remove the successfully sent ones from the DAO once
// it has been confirmed that they have been received.
// attemps to send all pending orders ( stored in the pos_db ) to the server,
// and remove the successfully sent ones from the db once
// it has been confirmed that they have been sent correctly.
flush: function() {
//this makes sure only one _int_flush is called at the same time
return this.flush_mutex.exec(_.bind(function() {
return this._int_flush();
return this._flush(0);
}, this));
},
_int_flush : function() {
// attempts to send an order of index 'index' in the list of order to send. The index
// is used to skip orders that failed. do not call this method outside the mutex provided
// by flush()
_flush: function(index){
var self = this;
var orders = this.db.get_orders();
self.set('nbr_pending_operations',orders.length);
this.dao.get_operations().pipe(function(operations) {
// operations are really Orders that are converted to json.
// they are saved to disk and then we attempt to send them to the backend so that they can
// be applied.
// since the network is not reliable we potentially have many 'pending operations' that have not been sent.
self.set( {'nbr_pending_operations':operations.length} );
if(operations.length === 0){
return $.when();
}
var order = operations[0];
// we prevent the default error handler and assume errors
// are a normal use case, except we stop the current iteration
return (new instance.web.Model('pos.order')).get_func('create_from_ui')([order])
.fail(function(unused, event){
// wtf ask niv
event.preventDefault();
})
.pipe(function(){
// success: remove the successfully sent operation, and try to send the next one
self.dao.remove_operation(operations[0].id).pipe(function(){
return self._int_flush();
});
}, function(){
// in case of error we just sit there and do nothing. wtf ask niv
return $.when();
});
});
var order = orders[index];
if(!order){
return;
}
//try to push an order to the server
(new instance.web.Model('pos.order')).get_func('create_from_ui')([order])
.fail(function(unused, event){
//don't show error popup if it fails
event.preventDefault();
console.error('Failed to send order:',order);
self._flush(index+1);
})
.done(function(){
//remove from db if success
self.db.remove_order(order.id);
self._flush(index);
});
},
// this adds several properties to the categories in order to make it easier to diplay them
// fields added include the list of product relevant to each category, list of child categories,
// list of ancestors, etc.
build_categories : function(){
var categories = this.get('categories');
var products = this.get('product_list');
scan_product: function(parsed_ean){
var self = this;
var product = this.db.get_product_by_ean13(parsed_ean.base_ean);
var selectedOrder = this.get('selectedOrder');
//append the content of array2 into array1
function append(array1, array2){
for(var i = 0, len = array2.length; i < len; i++){
array1.push(array2[i]);
}
if(!product){
return false;
}
function appendSet(set1, set2){
for(key in set2){
set1[key] = set2[key];
}
if(parsed_ean.type === 'price'){
selectedOrder.addProduct(new module.Product(product), {price:parsed_ean.value});
}else if(parsed_ean.type === 'weight'){
selectedOrder.addProduct(new module.Product(product), {quantity:parsed_ean.value, merge:false});
}else{
selectedOrder.addProduct(new module.Product(product));
}
var categories_by_id = {};
for(var i = 0; i < categories.length; i++){
categories_by_id[categories[i].id] = categories[i];
}
this.categories_by_id = categories_by_id;
var root_category = {
name : 'Root',
id : 0,
parent : null,
childrens : [],
};
// add parent and childrens field to categories, find root_categories
for(var i = 0; i < categories.length; i++){
var cat = categories[i];
cat.parent = categories_by_id[cat.parent_id[0]];
if(!cat.parent){
root_category.childrens.push(cat);
cat.parent = root_category;
}
cat.childrens = [];
for(var j = 0; j < cat.child_id.length; j++){
cat.childrens.push(categories_by_id[ cat.child_id[j] ]);
}
}
categories.push(root_category);
// set some default fields for next steps
for(var i = 0; i < categories.length; i++){
var cat = categories[i];
cat.product_list = []; //list of all products in the category
cat.product_set = {}; // [product.id] === true if product is in category
cat.weightable_product_list = [];
cat.weightable_product_set = {};
cat.weightable = false; //true if directly contains weightable products
}
this.root_category = root_category;
//we add the products to the categories.
for(var i = 0, len = products.length; i < len; i++){
var product = products[i];
var cat = categories_by_id[product.pos_categ_id[0]];
if(cat){
cat.product_list.push(product);
cat.product_set[product.id] = true;
if(product.to_weight){
cat.weightable_product_list.push(product);
cat.weightable_product_set[product.id] = true;
cat.weightable = true;
}
}
}
// we build a flat list of all categories that directly contains weightable products
this.weightable_categories = [];
for(var i = 0, len = categories.length; i < len; i++){
var cat = categories[i];
if(cat.weightable){
this.weightable_categories.push(cat);
}
}
// add ancestor field to categories, contains the list of parents of parents, from root to parent
function make_ancestors(cat, ancestors){
cat.ancestors = ancestors.slice(0);
ancestors.push(cat);
for(var i = 0; i < cat.childrens.length; i++){
make_ancestors(cat.childrens[i], ancestors.slice(0));
}
}
//add the products of the subcategories to the parent categories
function make_products(cat){
for(var i = 0; i < cat.childrens.length; i++){
make_products(cat.childrens[i]);
append(cat.product_list, cat.childrens[i].product_list);
append(cat.weightable_product_list, cat.childrens[i].weightable_product_list);
appendSet(cat.product_set, cat.childrens[i].product_set);
appendSet(cat.weightable_product_set, cat.childrens[i].weightable_product_set);
}
}
make_ancestors(root_category,[]);
make_products(root_category);
return true;
},
});
@ -608,15 +389,10 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
return false;
}else if(this.get_discount() > 0){ // we don't merge discounted orderlines
return false;
}else if(this.get_product_type() === 'unit'){
return true;
}else if(this.get_product_type() === 'weight'){
return true;
}else if(this.get_product_type() === 'price'){
return this.get_product().get('list_price') === orderline.get_product().get('list_price');
}else{
console.error('point_of_sale/pos_models.js/Orderline.can_be_merged_with() : unknown product type:',this.get('product_type'));
}else if(this.price !== orderline.price){
return false;
}else{
return true;
}
},
merge: function(orderline){
@ -882,6 +658,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
change: this.getChange(),
name : this.getName(),
client: client ? client.name : null ,
invoice_id: null, //TODO
cashier: cashier ? cashier.name : null,
date: {
year: date.getFullYear(),
@ -895,7 +672,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
email: company.email,
website: company.website,
company_registry: company.company_registry,
contact_address: null, //TODO
contact_address: company.contact_address,
vat: company.vat,
name: company.name,
phone: company.phone,

View File

@ -152,14 +152,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this.pos_widget.screen_selector.show_popup('help');
},
logout_button_action: function(){
this.pos_widget.screen_selector.set_user_mode('client');
},
close_button_action: function(){
this.pos_widget.try_close();
},
barcode_product_screen: 'products', //if defined, this screen will be loaded when a product is scanned
barcode_product_error_popup: 'error', //if defined, this popup will be loaded when there's an error in the popup
@ -167,14 +159,16 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
// it will add the product to the order and go to barcode_product_screen. Or show barcode_product_error_popup if
// there's an error.
barcode_product_action: function(ean){
if(this.pos_widget.scan_product(ean)){
this.pos.proxy.scan_item_success();
if(this.barcode_product_screen){
this.pos_widget.screen_selector.set_current_screen(this.barcode_product_screen);
var self = this;
if(self.pos.scan_product(ean)){
self.pos.proxy.scan_item_success(ean);
if(self.barcode_product_screen){
self.pos_widget.screen_selector.set_current_screen(self.barcode_product_screen);
}
}else{
if(this.barcode_product_error_popup){
this.pos_widget.screen_selector.show_popup(this.barcode_product_error_popup);
self.pos.proxy.scan_item_error_unrecognized(ean);
if(self.barcode_product_error_popup && self.pos_widget.screen_selector.get_user_mode() !== 'cashier'){
self.pos_widget.screen_selector.show_popup(self.barcode_product_error_popup);
}
}
},
@ -194,6 +188,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
return true;
}
}
this.pos.proxy.scan_item_unrecognized(ean);
return false;
},
@ -207,9 +202,11 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
if(users[i].ean13 === ean.ean){
this.pos.get('selectedOrder').set_client(users[i]);
this.pos_widget.username.refresh();
this.pos.proxy.scan_item_success(ean);
return true;
}
}
this.pos.proxy.scan_item_unrecognized(ean);
return false;
//TODO start the transaction
},
@ -217,29 +214,78 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
// what happens when a discount barcode is scanned : the default behavior
// is to set the discount on the last order.
barcode_discount_action: function(ean){
this.pos.proxy.scan_item_success(ean);
var last_orderline = this.pos.get('selectedOrder').getLastOrderline();
if(last_orderline){
last_orderline.set_discount(ean.value)
}
},
// shows an action bar on the screen. The actionbar is automatically shown when you add a button
// with add_action_button()
show_action_bar: function(){
this.pos_widget.action_bar.show();
this.$element.css({'bottom':'105px'});
},
// hides the action bar. The actionbar is automatically hidden when it is empty
hide_action_bar: function(){
this.pos_widget.action_bar.hide();
this.$element.css({'bottom':'0px'});
},
// adds a new button to the action bar. The button definition takes three parameters, all optional :
// - label: the text below the button
// - icon: a small icon that will be shown
// - click: a callback that will be executed when the button is clicked.
// the method returns a reference to the button widget, and automatically show the actionbar.
add_action_button: function(button_def){
this.show_action_bar();
return this.pos_widget.action_bar.add_new_button(button_def);
},
// this method shows the screen and sets up all the widget related to this screen. Extend this method
// if you want to alter the behavior of the screen.
show: function(){
var self = this;
this.hidden = false;
if(this.$element){
this.$element.show();
}
if(this.pos_widget.action_bar.get_button_count() > 0){
this.show_action_bar();
}else{
this.hide_action_bar();
}
// we add the help button by default. we do this because the buttons are cleared on each refresh so that
// the button stay local to each screen
this.pos_widget.left_action_bar.add_new_button({
label: 'help',
icon: '/point_of_sale/static/src/img/icons/png48/help.png',
click: function(){ self.help_button_action(); },
});
var self = this;
var cashier_mode = this.pos_widget.screen_selector.get_user_mode() === 'cashier';
this.pos_widget.set_numpad_visible(this.show_numpad && cashier_mode);
this.pos_widget.set_leftpane_visible(this.show_leftpane);
this.pos_widget.set_left_action_bar_visible(this.show_leftpane && !cashier_mode);
this.pos_widget.set_cashier_controls_visible(cashier_mode);
this.pos_widget.action_bar.set_element_visible('help-button', !cashier_mode, function(){ self.help_button_action(); });
this.pos_widget.action_bar.set_element_visible('logout-button', cashier_mode && this.pos.use_selfcheckout, function(){ self.logout_button_action(); });
this.pos_widget.action_bar.set_element_visible('close-button', cashier_mode, function(){ self.close_button_action(); });
if(cashier_mode && this.pos.iface_self_checkout){
this.pos_widget.client_button.show();
}else{
this.pos_widget.client_button.hide();
}
if(cashier_mode){
this.pos_widget.close_button.show();
}else{
this.pos_widget.close_button.hide();
}
this.pos_widget.username.set_user_mode(this.pos_widget.screen_selector.get_user_mode());
@ -251,16 +297,14 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
});
},
// this method is called when the screen is closed to make place for a new screen. this is a good place
// to put your cleanup stuff as it is guaranteed that for each show() there is one and only one close()
close: function(){
if(this.pos.barcode_reader){
this.pos.barcode_reader.reset_action_callbacks();
}
if(this.pos_widget.action_bar){
this.pos_widget.action_bar.destroy_buttons();
}
this.pos_widget.action_bar.destroy_buttons();
this.pos_widget.left_action_bar.destroy_buttons();
},
// this methods hides the screen. It's not a good place to put your cleanup stuff as it is called on the
@ -349,6 +393,9 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
module.ScaleInviteScreenWidget = module.ScreenWidget.extend({
template:'ScaleInviteScreenWidget',
next_screen:'scale',
previous_screen:'products',
show: function(){
this._super();
var self = this;
@ -359,21 +406,19 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
var weight = self.pos.proxy.weighting_read_kg();
if(weight > 0.001){
clearInterval(this.intervalID);
self.pos_widget.screen_selector.set_current_screen('scale');
self.pos_widget.screen_selector.set_current_screen(self.next_screen);
}
},500);
this.pos_widget.action_bar.add_new_button(
{
this.add_action_button({
label: 'back',
icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
click: function(){
clearInterval(this.intervalID);
self.pos.proxy.weighting_end();
self.pos_widget.screen_selector.set_current_screen('products');
self.pos_widget.screen_selector.set_current_screen(self.previous_screen);
}
}
);
});
},
close: function(){
this._super();
@ -383,25 +428,30 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
module.ScaleScreenWidget = module.ScreenWidget.extend({
template:'ScaleScreenWidget',
next_screen: 'products',
previous_screen: 'products',
show: function(){
this._super();
this.renderElement();
var self = this;
this.pos_widget.action_bar.add_new_button({
this.add_action_button({
label: 'back',
icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
click: function(){
self.pos_widget.screen_selector.set_current_screen('products');
self.pos_widget.screen_selector.set_current_screen(self.previous_screen);
}
});
this.validate_button = this.pos_widget.action_bar.add_new_button({
this.validate_button = this.add_action_button({
label: 'Validate',
icon: '/point_of_sale/static/src/img/icons/png48/validate.png',
click: function(){
self.order_product();
self.pos_widget.screen_selector.set_current_screen('products');
self.pos_widget.screen_selector.set_current_screen(self.next_screen);
},
});
@ -419,7 +469,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this._super();
this.$('.product-picture').click(function(){
self.order_product();
self.pos_widget.screen_selector.set_current_screen('products');
self.pos_widget.screen_selector.set_current_screen(self.next_screen);
});
},
get_product: function(){
@ -444,7 +494,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
},
get_product_image: function(){
var product = this.get_product();
return product ? product.get('image_medium') : undefined;
return product ? product.get('image') : undefined;
},
get_product_weight: function(){
return this.weight || 0;
@ -458,6 +508,10 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
module.ClientPaymentScreenWidget = module.ScreenWidget.extend({
template:'ClientPaymentScreenWidget',
next_screen: 'welcome',
previous_screen: 'products',
show: function(){
this._super();
var self = this;
@ -486,7 +540,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
self.pos.push_order(currentOrder.exportAsJSON()).then(function() {
currentOrder.destroy();
self.pos.proxy.transaction_end();
self.pos_widget.screen_selector.set_current_screen('welcome');
self.pos_widget.screen_selector.set_current_screen(self.next_screen);
});
}else if(payment === 'payment_rejected'){
clearInterval(this.intervalID);
@ -494,17 +548,15 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
}
},500);
this.pos_widget.action_bar.add_new_button(
{
this.add_action_button({
label: 'back',
icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
click: function(){ //TODO Go to ask for weighting screen
click: function(){
clearInterval(this.intervalID);
self.pos.proxy.payment_canceled();
self.pos_widget.screen_selector.set_current_screen('products');
self.pos_widget.screen_selector.set_current_screen(self.previous_screen);
}
}
);
});
},
close: function(){
this._super();
@ -515,23 +567,32 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
module.WelcomeScreenWidget = module.ScreenWidget.extend({
template:'WelcomeScreenWidget',
next_screen: 'products',
show_numpad: false,
show_leftpane: false,
barcode_client_action: function(ean){
this._super(ean);
this.pos_widget.screen_selector.set_current_screen('products');
this.pos_widget.screen_selector.set_current_screen(this.next_screen);
},
show: function(){
this._super();
var self = this;
$('.goodbye-message').css({opacity:1}).show();
setTimeout(function(){
$('.goodbye-message').animate({opacity:0},500,'swing',function(){$('.goodbye-message').hide();});
},3000);
},
});
module.ProductScreenWidget = module.ScreenWidget.extend({
template:'ProductScreenWidget',
scale_screen: 'scale_invite',
client_next_screen: 'client_payment',
show_numpad: true,
show_leftpane: true,
@ -542,8 +603,8 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this.product_list_widget = new module.ProductListWidget(this,{
click_product_action: function(product){
if(product.get('to_weight') && self.pos.use_scale){
self.pos_widget.screen_selector.set_current_screen('scale_invite', {product: product});
if(product.get('to_weight') && self.pos.iface_electronic_scale){
self.pos_widget.screen_selector.set_current_screen(self.scale_screen, {product: product});
}else{
self.pos.get('selectedOrder').addProduct(product);
}
@ -559,16 +620,16 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this.product_categories_widget.reset_category();
this.pos_widget.order_widget.set_numpad_state(this.pos_widget.numpad.state);
if(this.pos.use_virtual_keyboard){
if(this.pos.iface_vkeyboard){
this.pos_widget.onscreen_keyboard.connect();
}
if(this.pos_widget.screen_selector.current_mode === 'client'){
this.pos_widget.action_bar.add_new_button({
this.add_action_button({
label: 'pay',
icon: '/point_of_sale/static/src/img/icons/png48/go-next.png',
click: function(){
self.pos_widget.screen_selector.set_current_screen('client_payment');
self.pos_widget.screen_selector.set_current_screen(self.client_next_screen);
}
});
}
@ -604,17 +665,19 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this._super();
var self = this;
this.pos_widget.action_bar.add_new_button({
this.add_action_button({
label: 'Print',
icon: '/point_of_sale/static/src/img/icons/png48/printer.png',
click: function(){ self.print(); },
});
this.pos_widget.action_bar.add_new_button({
this.add_action_button({
label: 'Next Order',
icon: '/point_of_sale/static/src/img/icons/png48/go-next.png',
click: function() { self.finishOrder(); },
});
window.print();
},
print: function() {
window.print();
@ -643,6 +706,8 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
module.PaymentScreenWidget = module.ScreenWidget.extend({
template: 'PaymentScreenWidget',
back_screen: 'products',
next_screen: 'receipt',
init: function(parent, options) {
this._super(parent,options);
this.model = options.model;
@ -654,21 +719,21 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this._super();
var self = this;
if(this.pos.use_cashbox){
if(this.pos.iface_cashdrawer){
this.pos.proxy.open_cashbox();
}
this.set_numpad_state(this.pos_widget.numpad.state);
this.back_button = this.pos_widget.action_bar.add_new_button({
this.back_button = this.add_action_button({
label: 'Back',
icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
click: function(){
self.pos_widget.screen_selector.set_current_screen('products');
self.pos_widget.screen_selector.set_current_screen(self.back_screen);
},
});
this.validate_button = this.pos_widget.action_bar.add_new_button({
this.validate_button = this.add_action_button({
label: 'Validate',
icon: '/point_of_sale/static/src/img/icons/png48/validate.png',
click: function(){
@ -682,24 +747,18 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this.pos_widget.payment_screen.set_numpad_state(null);
},
back: function() {
this.pos_widget.screen_selector.set_current_screen('products');
this.pos_widget.screen_selector.set_current_screen(self.back_screen);
},
validateCurrentOrder: function() {
var self = this;
var currentOrder = this.pos.get('selectedOrder');
this.validate_button.$element.attr('disabled','disabled'); //FIXME is the css actually using this attr ?
this.pos.push_order(currentOrder.exportAsJSON())
.then(function() {
self.validate_button.$element.removeAttr('disabled');
if(self.pos.use_proxy_printer){
self.pos.proxy.print_receipt(currentOrder.export_for_printing());
self.pos.get('selectedOrder').destroy(); //finish order and go back to scan screen
}else{
self.pos_widget.screen_selector.set_current_screen('receipt');
}
});
if(this.pos.iface_print_via_proxy){
this.pos.proxy.print_receipt(currentOrder.export_for_printing());
this.pos.get('selectedOrder').destroy(); //finish order and go back to scan screen
}else{
this.pos_widget.screen_selector.set_current_screen(this.next_screen);
}
},
bindPaymentLineEvents: function() {
this.currentPaymentLines = (this.pos.get('selectedOrder')).get('paymentLines');

View File

@ -65,6 +65,7 @@ function openerp_pos_scrollbar(instance, module){ //module is instance.point_of_
this.auto_hide(false);
this.$element.bind('mousewheel',function(event,delta){
self.scroll(delta*self.wheel_step);
return false;
});
this.$element.bind('click',function(event){
var vpos = event.pageY - self.$element.offset().top;

View File

@ -1,6 +1,57 @@
function openerp_pos_widgets(instance, module){ //module is instance.point_of_sale
var QWeb = instance.web.qweb;
// The ImageCache is used to hide the latency of the application cache on-disk access in chrome
// that causes annoying flickering on product pictures. Why the hell a simple access to
// the application cache involves such latency is beyond me, hopefully one day this can be
// removed.
module.ImageCache = instance.web.Class.extend({
init: function(options){
options = options || {};
this.max_size = options.max_size || 500;
this.cache = {};
this.access_time = {};
this.size = 0;
},
get_image_uncached: function(url){
var img = new Image();
img.src = url;
return img;
},
// returns a DOM Image object from an url, and cache the last 500 (by default) results
get_image: function(url){
var cached = this.cache[url];
if(cached){
this.access_time[url] = (new Date()).getTime();
return cached;
}else{
var img = new Image();
img.src = url;
while(this.size >= this.max_size){
var oldestUrl = null;
var oldestTime = (new Date()).getTime();
for(var url in this.cache){
var time = this.access_time[url];
if(time <= oldestTime){
oldestTime = time;
oldestUrl = url;
}
}
if(oldestUrl){
delete this.cache[oldestUrl];
delete this.access_time[oldestUrl];
}
this.size--;
}
this.cache[url] = img;
this.access_time[url] = (new Date()).getTime();
this.size++;
return img;
}
},
});
module.NumpadWidget = module.PosBaseWidget.extend({
template:'NumpadWidget',
init: function(parent, options) {
@ -112,7 +163,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
template:'OrderWidget',
init: function(parent, options) {
this._super(parent,options);
this.compact = false;
this.display_mode = options.display_mode || 'numpad'; // 'maximized' | 'actionbar' | 'numpad'
this.set_numpad_state(options.numpadState);
this.pos.bind('change:selectedOrder', this.change_selected_order, this);
this.bind_orderline_events();
@ -165,8 +216,12 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
var self = this;
this._super();
if(!this.compact){
if(this.display_mode === 'maximized'){
$('.point-of-sale .order-container').css({'bottom':'0px'});
}else if(this.display_mode === 'actionbar'){
$('.point-of-sale .order-container').css({'bottom':'105px'});
}else if(this.display_mode !== 'numpad'){
console.error('ERROR: OrderWidget renderElement(): wrong display_mode:',this.display_mode);
}
var $content = this.$('.orderlines');
@ -211,9 +266,9 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
var total = order ? order.getTotal() : 0;
this.$('.summary .value.total').html(this.format_currency(total));
},
set_compact: function(compact){
if(this.compact !== compact){
this.compact = compact;
set_display_mode: function(mode){
if(this.display_mode !== mode){
this.display_mode = mode;
this.renderElement();
}
},
@ -225,20 +280,16 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
this._super(parent,options);
this.model = options.model;
this.model.attributes.weight = options.weight;
this.next_screen = options.next_screen;
this.click_product_action = options.click_product_action;
this.next_screen = options.next_screen; //when a product is clicked, this screen is set
this.click_product_action = options.click_product_action;
},
add_to_order: function(event) {
/* Preserve the category URL */
event.preventDefault();
return (this.pos.get('selectedOrder')).addProduct(this.model);
},
set_weight: function(weight){
this.model.attributes.weight = weight;
this.renderElement();
// returns the url of the product thumbnail
get_image_url: function() {
return '/web/binary/image?session_id='+instance.session.session_id+'&model=product.product&field=image&id='+this.model.get('id');
},
renderElement: function() {
this._super();
this.$('img').replaceWith(this.pos_widget.image_cache.get_image(this.get_image_url()));
var self = this;
$("a", this.$element).click(function(e){
if(self.click_product_action){
@ -313,6 +364,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
module.ActionButtonWidget = instance.web.Widget.extend({
template:'ActionButtonWidget',
icon_template:'ActionButtonWidgetWithIcon',
init: function(parent, options){
this._super(parent, options);
this.label = options.label || 'button';
@ -320,7 +372,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
this.click_action = options.click;
if(options.icon){
this.icon = options.icon;
this.template = 'ActionButtonWidgetWithIcon';
this.template = this.icon_template;
}
},
renderElement: function(){
@ -359,21 +411,25 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
this.button_list = [];
return this;
},
get_button_count: function(){
return this.button_list.length;
},
add_new_button: function(button_options){
if(arguments.length == 1){
var button = new module.ActionButtonWidget(this,button_options);
this.button_list.push(button);
button.appendTo($('.pos-actionbar-button-list'));
return button;
}else{
for(var i = 0; i < arguments.length; i++){
this.add_new_button(arguments[i]);
}
}
return undefined;
var button = new module.ActionButtonWidget(this,button_options);
this.button_list.push(button);
button.appendTo(this.$('.pos-actionbar-button-list'));
return button;
},
show:function(){
this.$element.show();
},
hide:function(){
this.$element.hide();
},
});
module.CategoryButton = module.PosBaseWidget.extend({
});
module.ProductCategoriesWidget = module.PosBaseWidget.extend({
template: 'ProductCategoriesWidget',
init: function(parent, options){
@ -389,41 +445,53 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
// changes the category. if undefined, sets to root category
set_category : function(category){
var db = this.pos.db;
if(!category){
this.category = this.pos.root_category;
this.category = db.get_category_by_id(db.root_category_id);
}else{
this.category = category;
}
this.breadcrumb = [];
for(var i = 1; i < this.category.ancestors.length; i++){
this.breadcrumb.push(this.category.ancestors[i]);
var ancestors_ids = db.get_category_ancestors_ids(this.category.id);
for(var i = 1; i < ancestors_ids.length; i++){
this.breadcrumb.push(db.get_category_by_id(ancestors_ids[i]));
}
if(this.category !== this.pos.root_category){
if(this.category.id !== db.root_category_id){
this.breadcrumb.push(this.category);
}
if(this.product_type === 'weightable'){
this.subcategories = [];
for(var i = 0; i < this.category.childrens.length; i++){
if(this.category.childrens[i].weightable_product_list.length > 0){
this.subcategories.push( this.category.childrens[i]);
}
}
}else{
this.subcategories = this.category.childrens || [];
}
this.subcategories = db.get_category_by_id(db.get_category_childs_ids(this.category.id));
},
get_image_url: function(category){
return '/web/binary/image?session_id='+instance.session.session_id+'&model=pos.category&field=image&id='+category.id;
},
renderElement: function(){
var self = this;
this._super();
_.each(this.subcategories, function(category){
var button = QWeb.render('CategoryButton',{category:category});
button = _.str.trim(button);
$(button).appendTo(this.$('.category-list')).click(function(event){
var hasimages = false; //if none of the subcategories have images, we don't display buttons with icons
_.each(this.subcategories, function(category){
if(category.image){
hasimages = true;
}
});
_.each(this.subcategories, function(category){
if(hasimages){
var button = QWeb.render('CategoryButton',{category:category});
var button = _.str.trim(button);
var button = $(button);
button.find('img').replaceWith(self.pos_widget.image_cache.get_image(self.get_image_url(category)));
}else{
var button = QWeb.render('CategorySimpleButton',{category:category});
button = _.str.trim(button); // we remove whitespace between buttons to fix spacing
var button = $(button);
}
button.appendTo(this.$('.category-list')).click(function(event){
var id = category.id;
var cat = self.pos.categories_by_id[id];
var cat = self.pos.db.get_category_by_id(id);
self.set_category(cat);
self.renderElement();
self.search_and_categories(cat);
@ -432,7 +500,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
// breadcrumb click actions
this.$(".oe-pos-categories-list a").click(function(event){
var id = $(event.target).data("category-id");
var category = self.pos.categories_by_id[id];
var category = self.pos.db.get_category_by_id(id);
self.set_category(category);
self.renderElement();
self.search_and_categories(category);
@ -455,47 +523,30 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
// filters the products, and sets up the search callbacks
search_and_categories: function(category){
var self = this;
var all_products = this.pos.get('product_list');
var all_packages = this.pos.get('product.packaging');
// find all products belonging to the current category
var products = [];
if(this.product_type === 'weightable'){
products = all_products.filter( function(product){
return self.category.weightable_product_set[product.id];
});
}else{
products = all_products.filter( function(product){
return self.category.product_set[product.id];
});
}
var products = this.pos.db.get_product_by_category(this.category.id);
self.pos.get('products').reset(products);
// product lists watch for reset events on 'products' to re-render.
// FIXME that means all productlist widget re-render... even the hidden ones !
this.pos.get('products').reset(products);
// find all the products whose name match the query in the searchbox
// filter the products according to the search string
this.$('.searchbox input').keyup(function(){
var results, search_str;
search_str = $(this).val().toLowerCase();
if(search_str){
results = products.filter( function(p){
return p.name.toLowerCase().indexOf(search_str) != -1 ||
(p.ean13 && p.ean13.indexOf(search_str) != -1);
});
self.$element.find('.search-clear').fadeIn();
query = $(this).val().toLowerCase();
if(query){
var products = self.pos.db.search_product_in_category(self.category.id, query);
self.pos.get('products').reset(products);
self.$('.search-clear').fadeIn();
}else{
results = products;
self.$element.find('.search-clear').fadeOut();
var products = self.pos.db.get_product_by_category(self.category.id);
self.pos.get('products').reset(products);
self.$('.search-clear').fadeOut();
}
self.pos.get('products').reset(results);
});
this.$('.searchbox input').click(function(){
});
this.$('.searchbox input').click(function(){}); //Why ???
//reset the search when clicking on reset
this.$('.search-clear').click(function(){
var products = self.pos.db.get_product_by_category(self.category.id);
self.pos.get('products').reset(products);
self.$('.searchbox input').val('').focus();
self.$('.search-clear').fadeOut();
@ -519,11 +570,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
self.renderElement();
});
},
set_weight: function(weight){
for(var i = 0; i < this.product_list.length; i++){
this.product_list[i].set_weight(weight);
}
},
renderElement: function() {
var self = this;
this._super();
@ -586,14 +632,35 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
},
});
module.HeaderButtonWidget = module.PosBaseWidget.extend({
template: 'HeaderButtonWidget',
init: function(parent, options){
options = options || {};
this._super(parent, options);
this.action = options.action;
this.label = options.label;
},
renderElement: function(){
var self = this;
this._super();
if(this.action){
this.$element.click(function(){ self.action(); });
}
},
show: function(){ this.$element.show(); },
hide: function(){ this.$element.hide(); },
});
// ---------- Main Point of Sale Widget ----------
// this is used to notify the user that data is being synchronized on the network
module.SynchNotificationWidget = module.PosBaseWidget.extend({
template: "SynchNotificationWidget",
init: function(parent,options) {
init: function(parent, options){
options = options || {};
this._super(parent,options);
this._super(parent, options);
},
renderElement: function() {
var self = this;
@ -632,9 +699,19 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
this.pos_widget = this; //So that pos_widget's childs have pos_widget set automatically
this.numpad_visible = true;
this.left_action_bar_visible = true;
this.leftpane_visible = true;
this.leftpane_width = '440px';
this.cashier_controls_visible = true;
this.image_cache = new module.ImageCache(); // for faster products image display
/*
//Epileptic mode
setInterval(function(){
$('body').css({'-webkit-filter':'hue-rotate('+Math.random()*360+'deg)' });
},100);
*/
},
start: function() {
@ -643,7 +720,9 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
self.build_currency_template();
self.renderElement();
self.$('.neworder-button').click(_.bind(self.create_new_order, self));
self.$('.neworder-button').click(function(){
self.pos.add_new_order();
});
//when a new order is created, add an order button widget
self.pos.get('orders').bind('add', function(new_order){
@ -659,6 +738,10 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
self.build_widgets();
self.screen_selector.set_default_screen();
self.pos.barcode_reader.connect();
instance.webclient.set_content_full_screen(true);
if (!self.pos.get('pos_session')) {
@ -667,9 +750,14 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
self.screen_selector.show_popup('error', 'Sorry, we could not find any PoS Configuration for this session');
}
self.$('.loader').animate({opacity:0},3000,'swing',function(){$('.loader').hide();});
self.$('.loader').animate({opacity:0},1500,'swing',function(){self.$('.loader').hide();});
self.$('.loader img').hide();
if(jQuery.deparam(jQuery.param.querystring()).debug !== undefined){
window.pos = self.pos;
window.pos_widget = self.pos_widget;
}
},function(){ // error when loading models data from the backend
self.$('.loader img').hide();
return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_pos_session_opening']], ['res_id'])
@ -682,8 +770,11 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
}, self));
});
},
// This method instantiates all the screens, widgets, etc. If you want to add new screens change the
// startup screen, etc, override this method.
build_widgets: function() {
var self = this;
// -------- Screens ---------
@ -725,13 +816,16 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
// -------- Misc ---------
this.notification = new module.SynchNotificationWidget(this,{});
this.notification.replace(this.$('.placeholder-SynchNotificationWidget'));
this.notification.appendTo(this.$('#rightheader'));
this.username = new module.UsernameWidget(this,{});
this.username.replace(this.$('.placeholder-UsernameWidget'));
this.action_bar = new module.ActionBarWidget(this);
this.action_bar.appendTo($(".point-of-sale #content"));
this.action_bar.appendTo($(".point-of-sale #rightpane"));
this.left_action_bar = new module.ActionBarWidget(this);
this.left_action_bar.appendTo($(".point-of-sale #leftpane"));
this.paypad = new module.PaypadWidget(this, {});
this.paypad.replace($('#placeholder-PaypadWidget'));
@ -746,6 +840,19 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
'keyboard_model': 'simple'
});
this.onscreen_keyboard.appendTo($(".point-of-sale #content"));
this.close_button = new module.HeaderButtonWidget(this,{
label:'Close',
action: function(){ self.try_close(); },
});
this.close_button.appendTo(this.$('#rightheader'));
this.client_button = new module.HeaderButtonWidget(this,{
label:'Self-Checkout',
action: function(){ self.screen_selector.set_user_mode('client'); },
});
this.client_button.appendTo(this.$('#rightheader'));
// -------- Screen Selector ---------
@ -768,59 +875,11 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
},
default_client_screen: 'welcome',
default_cashier_screen: 'products',
default_mode: this.pos.use_selfcheckout ? 'client' : 'cashier',
default_mode: this.pos.iface_self_checkout ? 'client' : 'cashier',
});
this.screen_selector.set_default_screen();
this.pos.barcode_reader.connect();
},
//FIXME this method is probably not at the right place ...
scan_product: function(parsed_ean){
var selectedOrder = this.pos.get('selectedOrder');
var scannedProductModel = this.get_product_by_ean(parsed_ean);
if (!scannedProductModel){
return false;
} else {
if(parsed_ean.type === 'price'){
selectedOrder.addProduct(new module.Product(scannedProductModel), { price:parsed_ean.value});
}else if(parsed_ean.type === 'weight'){
selectedOrder.addProduct(new module.Product(scannedProductModel), { quantity:parsed_ean.value, merge:false});
}else{
selectedOrder.addProduct(new module.Product(scannedProductModel));
}
return true;
}
},
get_product_by_ean: function(parsed_ean) {
var allProducts = this.pos.get('product_list');
var allPackages = this.pos.get('product.packaging');
var scannedProductModel = undefined;
if (parsed_ean.type === 'price' || parsed_ean.type === 'weight') {
var itemCode = parsed_ean.id;
var scannedPackaging = _.detect(allPackages, function(pack) {
return pack.ean && pack.ean.substring(0,7) === itemCode;
});
if (scannedPackaging) {
scannedProductModel = _.detect(allProducts, function(pc) { return pc.id === scannedPackaging.product_id[0];});
}else{
scannedProductModel = _.detect(allProducts, function(pc) { return pc.ean13 && (pc.ean13.substring(0,7) === parsed_ean.id);});
}
} else if(parsed_ean.type === 'unit'){
scannedProductModel = _.detect(allProducts, function(pc) { return pc.ean13 === parsed_ean.ean;}); //TODO DOES NOT SCALE
}
return scannedProductModel;
},
// creates a new order, and add it to the list of orders.
create_new_order: function() {
var new_order;
new_order = new module.Order({ pos: this.pos });
this.pos.get('orders').add(new_order);
this.pos.set({ selectedOrder: new_order });
},
changed_pending_operations: function () {
var self = this;
this.synch_notification.on_change_nbr_pending(self.pos.get('nbr_pending_operations').length);
@ -830,16 +889,35 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
if(visible !== this.numpad_visible){
this.numpad_visible = visible;
if(visible){
this.set_left_action_bar_visible(false);
this.numpad.show();
this.paypad.show();
this.order_widget.set_compact(true);
this.order_widget.set_display_mode('numpad');
}else{
this.numpad.hide();
this.paypad.hide();
this.order_widget.set_compact(false);
if(this.order_widget.display_mode === 'numpad'){
this.order_widget.set_display_mode('maximized');
}
}
}
},
set_left_action_bar_visible: function(visible){
if(visible !== this.left_action_bar_visible){
this.left_action_bar_visible = visible;
if(visible){
this.set_numpad_visible(false);
this.left_action_bar.show();
this.order_widget.set_display_mode('actionbar');
}else{
this.left_action_bar.hide();
if(this.order_widget.display_mode === 'actionbar'){
this.order_widget.set_display_mode('maximized');
}
}
}
},
//shows or hide the leftpane (contains the list of orderlines, the numpad, the paypad, etc.)
set_leftpane_visible: function(visible){
if(visible !== this.leftpane_visible){
@ -874,12 +952,14 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
});
},
close: function() {
var self = this;
this.pos.barcode_reader.disconnect();
return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_client_pos_menu']], ['res_id']).pipe(
_.bind(function(res) {
return this.rpc('/web/action/load', {'action_id': res[0]['res_id']}).pipe(_.bind(function(result) {
var action = result.result;
action.context = _.extend(action.context || {}, {'cancel_action': {type: 'ir.actions.client', tag: 'reload'}});
//self.destroy();
this.do_action(action);
}, this));
}, this));

View File

@ -15,7 +15,7 @@
<button class="neworder-button">+</button>
<ol id="orders"></ol>
</div>
<span class="placeholder-SynchNotificationWidget"></span>
<!-- here goes header buttons -->
</div>
</div>
<div id="content">
@ -44,6 +44,12 @@
</div>
</t>
<t t-name="HeaderButtonWidget">
<div class="header-button">
<t t-esc="widget.label" />
</div>
</t>
<t t-name="PosCloseWarning">
<div>There are pending operations that could not be saved into the database, are you sure you want to exit?</div>
</t>
@ -83,7 +89,7 @@
<t t-name="CategoryButton">
<li class='category-button'>
<div class="category-img">
<img t-att-src="'data:image/gif;base64,'+ category.image_medium" />
<img src="" />
</div>
<div class="category-name">
<t t-esc="category.name"/>
@ -91,6 +97,12 @@
</li>
</t>
<t t-name="CategorySimpleButton">
<li class='category-simple-button'>
<t t-esc="category.name"/>
</li>
</t>
<t t-name="ProductCategoriesWidget">
<header>
<ol class="breadcrumb">
@ -220,7 +232,7 @@
<t t-if="widget.currency.position == 'after'" t-esc="widget.currency.symbol"/>
</span>
</div>
<div class="infoline">
<div class="infoline" >
<span class='left-block'>
Change:
</span>
@ -252,6 +264,9 @@
<img src="/point_of_sale/static/src/img/scan.png" />
<p> Please scan an item or your member card </p>
</div>
<div class="goodbye-message">
<p>Thank you for shopping with us.</p>
</div>
</t>
@ -342,7 +357,7 @@
<li class='product'>
<a href="#">
<div class="product-img">
<img t-att-src="'data:image/gif;base64,'+ widget.model.get('image_medium')" />
<img src='' /> <!-- the product thumbnail -->
<t t-if="!widget.model.get('to_weight')">
<span class="price-tag">
<t t-esc="widget.format_currency(widget.model.get('list_price'))"/>
@ -493,7 +508,7 @@
<tr><td>Tax:</td><td class="pos-right-align">
<t t-esc="widget.format_currency(widget.currentOrder.getTax().toFixed(2))"/>
</td></tr>
<tr><td>Total:</td><td class="pos-right-align">
<tr class="emph"><td>Total:</td><td class="pos-right-align">
<t t-esc="widget.format_currency(widget.currentOrder.getTotal().toFixed(2))"/>
</td></tr>
</table>
@ -519,30 +534,7 @@
<t t-name="ActionBarWidget">
<div class="pos-actionbar">
<div class="pos-actionbar-left-pane">
<div class="button help-button">
<div class='icon'>
<img src="/point_of_sale/static/src/img/icons/png48/help.png" />
<div class='iconlabel'>Help</div>
</div>
</div>
<div class="button close-button">
<div class='icon'>
<img src="/point_of_sale/static/src/img/icons/png48/shut-down.png" />
<div class='iconlabel'>Close</div>
</div>
</div>
<div class="button logout-button">
<div class='icon'>
<img src="/point_of_sale/static/src/img/icons/png48/system-log-out.png" />
<div class='iconlabel'>Client Mode</div>
</div>
</div>
</div>
<ul class="pos-actionbar-button-list">
<!-- <li class="button">BUTTOOON</li>
<li class="button">JEEENKIINS</li>
<li class="button rightalign">ARGH</li> -->
</ul>
</div>
</t>

View File

@ -1,22 +0,0 @@
<!doctype html>
<html>
<head>
<title>JSON</title>
<script type="text/javascript" src="http://localhost:8069/web/webclient/js"></script>
</head>
<body>
<h1>JSON</h1>
<p id="response"></p>
<script type="text/javascript">
var c = new openerp.init(['web', 'point_of_sale']);
c.connection.session_bind('http://localhost:8069').then(function() {
var w = new c.point_of_sale.test();
w.appendTo($("#response"));
});
</script>
</body>
</html>

View File

@ -9,9 +9,3 @@
-
!python {model: pos.open.statement}: |
self.open_statement(cr, uid, [ref('new_statement_open')], context={})
-
I check that I have some bank statements open for the admin user
-
!python {model: account.bank.statement}: |
ids = self.search(cr, uid, [('state', 'in', ('open','new')), ('user_id', '=', 1)])
assert (len(ids)>0), 'No statement open for the admin user!'

View File

@ -12,7 +12,7 @@
account_collected_id: account.iva
price_include: 1
-
I assign this 10 percent tax on the PC1 product as a sale tax
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}:
taxes_id: [account_tax_10_incl]
@ -27,7 +27,7 @@
account_collected_id: account.iva
price_include: 0
-
I assign this 5 percent tax on the PC2 product as a sale tax
I assign this 5 percent tax on the PCSC349 product as a sale tax
-
!record {model: product.product, id: product.product_product_4}:
taxes_id: [account_tax_05_incl]
@ -52,7 +52,7 @@
-
!workflow {model: pos.session, action: open, ref: pos_order_session0}
-
I create a PoS order with 2 units of PC1 at 450 EUR (Tax Incl) and 3 units of PC2 at 300 EUR. (Tax Excl)
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

View File

@ -1,5 +1,5 @@
-
I create a new PoS order with 2 units of PC1 at 450 EUR (Tax Incl) and 3 units of PC2 at 300 EUR. (Tax Excl)
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

View File

@ -0,0 +1,56 @@
#!/usr/bin/python
import sys
import re
import math
def ean_checksum(eancode):
"""returns the checksum of an ean string of length 13, returns -1 if the string has the wrong length"""
if len(eancode) <> 13:
return -1
oddsum=0
evensum=0
total=0
eanvalue=eancode
reversevalue = eanvalue[::-1]
finalean=reversevalue[1:]
for i in range(len(finalean)):
if i % 2 == 0:
oddsum += int(finalean[i])
else:
evensum += int(finalean[i])
total=(oddsum * 3) + evensum
check = int(10 - math.ceil(total % 10.0)) %10
return check
def check_ean(eancode):
"""returns True if eancode is a valid ean13 string, or null"""
if not eancode:
return True
if len(eancode) <> 13:
return False
try:
int(eancode)
except:
return False
return ean_checksum(eancode) == int(eancode[-1])
def sanitize_ean13(ean13):
"""Creates and returns a valid ean13 from an invalid one"""
if not ean13:
return "0000000000000"
ean13 = re.sub("[A-Za-z]","0",ean13);
ean13 = re.sub("[^0-9]","",ean13);
ean13 = ean13[:13]
if len(ean13) < 13:
ean13 = ean13 + '0' * (13-len(ean13))
return ean13[:-1] + str(ean_checksum(ean13))
def main():
for arg in sys.argv[1:]:
print sanitize_ean13(arg)
if __name__ == '__main__':
main()

View File

@ -4,13 +4,17 @@ from osv import osv, fields
from tools.translate import _
import netsvc
from openerp.addons.point_of_sale.point_of_sale import pos_session
class pos_session_opening(osv.osv_memory):
_name = 'pos.session.opening'
_columns = {
'pos_config_id' : fields.many2one('pos.config', 'Point of Sale', required=True),
'pos_session_id' : fields.many2one('pos.session', 'PoS Session'),
'pos_state' : fields.char('Session State'),
'pos_state' : fields.selection(pos_session.POS_SESSION_STATE,
'Session State', readonly=True),
'show_config' : fields.boolean('Show Config', readonly=True),
}
def open_ui(self, cr, uid, ids, context=None):
@ -19,7 +23,7 @@ class pos_session_opening(osv.osv_memory):
context['active_id'] = data.pos_session_id.id
return {
'type' : 'ir.actions.client',
'name' : 'Start Point Of Sale',
'name' : _('Start Point Of Sale'),
'tag' : 'pos.ui',
'context' : context
}
@ -87,7 +91,11 @@ class pos_session_opening(osv.osv_memory):
if not result:
r = self.pool.get('pos.config').search(cr, uid, [], context=context)
result = r and r[0] or False
count = self.pool.get('pos.config').search_count(cr, uid, [('state', '=', 'active')], context=context)
show_config = bool(count > 1)
return {
'pos_config_id' : result
'pos_config_id' : result,
'show_config' : show_config,
}
pos_session_opening()

View File

@ -6,14 +6,15 @@
<field name="model">pos.session.opening</field>
<field name="arch" type="xml">
<form string="PoS Session Opening" version="7.0">
<separator string="Select your Point of Sale" colspan="4" />
<group>
<field name="show_config" invisible="1" />
<separator string="Select your Point of Sale" colspan="4" attrs="{'invisible' : [('show_config', '=', False)]}" />
<group attrs="{'invisible' : [('show_config', '=', False)]}">
<field name="pos_config_id" on_change="on_change_config(pos_config_id)"
widget="selection" domain="[('state','=','active')]"
class="oe_inline"/>
<field name="pos_state" class="oe_inline" attrs="{'invisible' : [('pos_state', '=', False)]}" />
</group>
<field name="pos_session_id" invisible="1"/>
<field name="pos_state" invisible="1"/>
<button name="open_ui" type="object" string="Start Selling"
attrs="{'invisible' : [('pos_state', 'not in', ('opened',))]}"
class="oe_highlight"

View File

@ -25,7 +25,8 @@
'depends': [
'base',
'share',
'auth_anonymous'
'auth_anonymous',
'auth_signup',
],
'author': 'OpenERP SA',
'category': 'Portal',
@ -45,8 +46,8 @@ very handy when used in combination with the module 'share'.
'data': [
'security/portal_security.xml',
'security/ir.model.access.csv',
'portal_view.xml',
'portal_data.xml',
'portal_view.xml',
'wizard/portal_wizard_view.xml',
'wizard/share_wizard_view.xml',
],

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<data noupdate="1">
<record id="portal" model="res.portal">
<field name="name">Portal</field>
@ -10,7 +10,7 @@
<!-- Mail group for the company's news -->
<record id="company_news_feed" model="mail.group">
<field name="name">Company's news feed</field>
<field name="name">Company's news</field>
</record>
<record id="action_news" model="ir.actions.act_window">
@ -32,24 +32,5 @@
<field name="view_mode">form</field>
</record>
<!-- Top menu item -->
<menuitem name="Portal"
id="portal_menu"
groups="base.group_no_one,portal.group_portal_member,auth_anonymous.group_anonymous"
sequence="20"/>
<menuitem name="Our company" id="portal_company" parent="portal_menu" sequence="10"/>
<menuitem name="News" id="portal_company_news" parent="portal_company" sequence="10" action="action_news"/>
<menuitem name="Jobs" id="portal_jobs" parent="portal_company" sequence="20" action="action_jobs"/>
<!--
Create menu items that we'll leave empty for now - they'll be
filled up by other portal modules.
-->
<menuitem name="Orders" id="portal_orders" parent="portal_menu" sequence="20"/>
<menuitem name="Invoices and Payments" id="portal_invoices_payements" parent="portal_menu" sequence="30"/>
<menuitem name="Projects" id="portal_projects" parent="portal_menu" sequence="40"/>
<menuitem name="After Sale Services" id="portal_after_sales" parent="portal_menu" sequence="50"/>
</data>
</openerp>

View File

@ -2,6 +2,25 @@
<openerp>
<data>
<!-- Top menu item -->
<menuitem name="Portal"
id="portal_menu"
groups="base.group_no_one,portal.group_portal_member,auth_anonymous.group_anonymous"
sequence="20"/>
<menuitem name="Our company" id="portal_company" parent="portal_menu" sequence="10"/>
<menuitem name="News" id="portal_company_news" parent="portal_company" sequence="10" action="action_news"/>
<menuitem name="Jobs" id="portal_jobs" parent="portal_company" sequence="20" action="action_jobs"/>
<!--
Create menu items that we'll leave empty for now - they'll be
filled up by other portal modules.
-->
<menuitem name="Orders" id="portal_orders" parent="portal_menu" sequence="20"/>
<menuitem name="Invoices and Payments" id="portal_invoices_payements" parent="portal_menu" sequence="30"/>
<menuitem name="Projects" id="portal_projects" parent="portal_menu" sequence="40"/>
<menuitem name="After Sale Services" id="portal_after_sales" parent="portal_menu" sequence="50"/>
<!-- portal tree view -->
<record id="portal_list_view" model="ir.ui.view">
<field name="name">Portal List</field>

View File

@ -28,18 +28,10 @@ import re
import tools
from tools.translate import _
def is_pair(x):
return not x%2
def check_ean(eancode):
if not eancode:
return True
def ean_checksum(eancode):
"""returns the checksum of an ean string of length 13, returns -1 if the string has the wrong length"""
if len(eancode) <> 13:
return False
try:
int(eancode)
except:
return False
return -1
oddsum=0
evensum=0
total=0
@ -48,17 +40,38 @@ def check_ean(eancode):
finalean=reversevalue[1:]
for i in range(len(finalean)):
if is_pair(i):
if i % 2 == 0:
oddsum += int(finalean[i])
else:
evensum += int(finalean[i])
total=(oddsum * 3) + evensum
check = int(10 - math.ceil(total % 10.0)) %10
return check
if check != int(eancode[-1]):
def check_ean(eancode):
"""returns True if eancode is a valid ean13 string, or null"""
if not eancode:
return True
if len(eancode) <> 13:
return False
return True
try:
int(eancode)
except:
return False
return ean_checksum(eancode) == int(eancode[-1])
def sanitize_ean13(ean13):
"""Creates and returns a valid ean13 from an invalid one"""
if not ean13:
return "0000000000000"
ean13 = re.sub("[A-Za-z]","0",ean13);
ean13 = re.sub("[^0-9]","",ean13);
ean13 = ean13[:13]
if len(ean13) < 13:
ean13 = ean13 + '0' * (13-len(ean13))
return ean13[:-1] + str(ean_checksum(ean13))
#----------------------------------------------------------
# UOM
#----------------------------------------------------------
@ -195,8 +208,10 @@ product_ul()
class product_category(osv.osv):
def name_get(self, cr, uid, ids, context=None):
if not len(ids):
if isinstance(ids, (list, tuple)) and not len(ids):
return []
if isinstance(ids, (long, int)):
ids = [ids]
reads = self.read(cr, uid, ids, ['name','parent_id'], context=context)
res = []
for record in reads:
@ -594,6 +609,7 @@ class product_product(osv.osv):
res = check_ean(product['ean13'])
return res
_constraints = [(_check_ean_key, 'Error: Invalid ean code', ['ean13'])]
def on_order(self, cr, uid, ids, orderline, quantity):

View File

@ -6,7 +6,7 @@
to_date: !eval "'%s-12-31' %(datetime.now().year)"
invoice_state: open_paid
-
I open margin for PC3
I open margin for PC-DEM
-
!python {model: product.margin}: |
self.action_open_window(cr, uid, [ref("product_margin_wiz0")], {"lang": 'en_US',

Some files were not shown because too many files have changed in this diff Show More