[MERGE] forward port of branch 9.0 up to 87a8896
This commit is contained in:
@@ -390,7 +390,7 @@ class AccountJournal(models.Model):
|
||||
if vals.get('type') in ('bank', 'cash'):
|
||||
# For convenience, the name can be inferred from account number
|
||||
if not vals.get('name') and 'bank_acc_number' in vals:
|
||||
vals['name'] = _('Bank') + ' ' + vals['bank_acc_number']
|
||||
vals['name'] = vals['bank_acc_number']
|
||||
|
||||
# If no code provided, loop to find next available journal code
|
||||
if not vals.get('code'):
|
||||
|
||||
@@ -818,7 +818,7 @@ class AccountBankStatementLine(models.Model):
|
||||
ctx = dict(self._context, date=self.date)
|
||||
for aml_dict in to_create:
|
||||
aml_dict['move_id'] = move.id
|
||||
aml_dict['date'] = self.statement_id.date
|
||||
aml_dict['date'] = self.date
|
||||
aml_dict['partner_id'] = self.partner_id.id
|
||||
aml_dict['journal_id'] = self.journal_id.id
|
||||
aml_dict['company_id'] = self.company_id.id
|
||||
|
||||
@@ -120,8 +120,8 @@ class account_register_payments(models.TransientModel):
|
||||
invoices = self.env[active_model].browse(active_ids)
|
||||
if any(invoice.state != 'open' for invoice in invoices):
|
||||
raise UserError(_("You can only register payments for open invoices"))
|
||||
if any(inv.partner_id != invoices[0].partner_id for inv in invoices):
|
||||
raise UserError(_("In order to pay multiple invoices at once, they must belong to the same partner."))
|
||||
if any(inv.commercial_partner_id != invoices[0].commercial_partner_id for inv in invoices):
|
||||
raise UserError(_("In order to pay multiple invoices at once, they must belong to the same commercial partner."))
|
||||
if any(MAP_INVOICE_TYPE_PARTNER_TYPE[inv.type] != MAP_INVOICE_TYPE_PARTNER_TYPE[invoices[0].type] for inv in invoices):
|
||||
raise UserError(_("You cannot mix customer invoices and vendor bills in a single payment."))
|
||||
if any(inv.currency_id != invoices[0].currency_id for inv in invoices):
|
||||
@@ -132,7 +132,7 @@ class account_register_payments(models.TransientModel):
|
||||
'amount': abs(total_amount),
|
||||
'currency_id': invoices[0].currency_id.id,
|
||||
'payment_type': total_amount > 0 and 'inbound' or 'outbound',
|
||||
'partner_id': invoices[0].partner_id.id,
|
||||
'partner_id': invoices[0].commercial_partner_id.id,
|
||||
'partner_type': MAP_INVOICE_TYPE_PARTNER_TYPE[invoices[0].type],
|
||||
})
|
||||
return rec
|
||||
|
||||
@@ -516,7 +516,7 @@
|
||||
<separator/>
|
||||
<filter domain="[('user_id','=',uid)]" help="My Invoices"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter name="group_by_partner_id" string="Partner" context="{'group_by':'partner_id'}"/>
|
||||
<filter name="group_by_partner_id" string="Partner" context="{'group_by':'commercial_partner_id'}"/>
|
||||
<filter string="Salesperson" context="{'group_by':'user_id'}"/>
|
||||
<filter name="status" string="Status" context="{'group_by':'state'}"/>
|
||||
<separator/>
|
||||
|
||||
@@ -189,9 +189,9 @@
|
||||
<field name="payment_method_id" widget="radio" attrs="{'invisible': [('hide_payment_method', '=', True)]}"/>
|
||||
<field name="payment_method_code" invisible="1"/>
|
||||
<label for="amount"/>
|
||||
<div name="amount_div">
|
||||
<field name="amount" class="oe_inline"/>
|
||||
<field name="currency_id" options="{'no_create': True, 'no_open': True}" groups="base.group_multi_currency" class="oe_inline" invisible="1"/>
|
||||
<div name="amount_div" class="o_row">
|
||||
<field name="amount"/>
|
||||
<field name="currency_id" options="{'no_create': True, 'no_open': True}" groups="base.group_multi_currency"/>
|
||||
</div>
|
||||
</group>
|
||||
<group>
|
||||
|
||||
@@ -33,9 +33,7 @@ class AssetAssetReport(models.Model):
|
||||
dl.name as name,
|
||||
dl.depreciation_date as depreciation_date,
|
||||
a.date as date,
|
||||
(CASE WHEN (select min(d.id) from account_asset_depreciation_line as d
|
||||
left join account_asset_asset as ac ON (ac.id=d.asset_id)
|
||||
where a.id=ac.id) = min(dl.id)
|
||||
(CASE WHEN dlmin.id = min(dl.id)
|
||||
THEN a.value
|
||||
ELSE 0
|
||||
END) as gross_value,
|
||||
@@ -59,8 +57,9 @@ class AssetAssetReport(models.Model):
|
||||
a.company_id as company_id
|
||||
from account_asset_depreciation_line dl
|
||||
left join account_asset_asset a on (dl.asset_id=a.id)
|
||||
left join (select min(d.id) as id,ac.id as ac_id from account_asset_depreciation_line as d inner join account_asset_asset as ac ON (ac.id=d.asset_id) group by ac_id) as dlmin on dlmin.ac_id=a.id
|
||||
group by
|
||||
dl.amount,dl.asset_id,dl.depreciation_date,dl.name,
|
||||
a.date, dl.move_check, a.state, a.category_id, a.partner_id, a.company_id,
|
||||
a.value, a.id, a.salvage_value
|
||||
a.value, a.id, a.salvage_value, dlmin.id
|
||||
)""")
|
||||
|
||||
@@ -20,7 +20,7 @@ bus.Bus = Widget.extend({
|
||||
|
||||
// bus presence
|
||||
this.set("window_focus", true);
|
||||
this.on("change:window_focus", self, function(e) {
|
||||
this.on("change:window_focus", self, function() {
|
||||
self.options.im_presence = self.get("window_focus");
|
||||
});
|
||||
$(window).on("focus", _.bind(this.window_focus, this));
|
||||
@@ -40,9 +40,9 @@ bus.Bus = Widget.extend({
|
||||
poll: function() {
|
||||
var self = this;
|
||||
self.activated = true;
|
||||
var data = {'channels': self.channels, 'last': self.last, 'options' : self.options};
|
||||
var data = {channels: self.channels, last: self.last, options : self.options};
|
||||
session.rpc('/longpolling/poll', data, {shadow : true}).then(function(result) {
|
||||
_.each(result, _.bind(self.on_notification, self));
|
||||
self._notification_receive(result);
|
||||
if(!self.stop){
|
||||
self.poll();
|
||||
}
|
||||
@@ -53,6 +53,9 @@ bus.Bus = Widget.extend({
|
||||
setTimeout(_.bind(self.poll, self), bus.ERROR_DELAY + (Math.floor((Math.random()*20)+1)*1000));
|
||||
});
|
||||
},
|
||||
_notification_receive: function(notifications){
|
||||
_.each(notifications, this.on_notification);
|
||||
},
|
||||
on_notification: function(notification) {
|
||||
if (notification.id > this.last) {
|
||||
this.last = notification.id;
|
||||
@@ -60,9 +63,8 @@ bus.Bus = Widget.extend({
|
||||
this.trigger("notification", [notification.channel, notification.message]);
|
||||
},
|
||||
add_channel: function(channel){
|
||||
if(!_.contains(this.channels, channel)){
|
||||
this.channels.push(channel);
|
||||
}
|
||||
this.channels.push(channel);
|
||||
this.channels = _.uniq(this.channels);
|
||||
},
|
||||
delete_channel: function(channel){
|
||||
this.channels = _.without(this.channels, channel);
|
||||
@@ -74,10 +76,238 @@ bus.Bus = Widget.extend({
|
||||
window_blur: function() {
|
||||
this.set("window_focus", false);
|
||||
},
|
||||
update_option: function(key, value){
|
||||
this.options[key] = value;
|
||||
},
|
||||
delete_option: function(key){
|
||||
if(_.contains(_.keys(this.options), key)){
|
||||
delete this.options[key];
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// singleton
|
||||
bus.bus = new bus.Bus();
|
||||
|
||||
|
||||
/**
|
||||
* CrossTabBus Widget
|
||||
*
|
||||
* Manage the communication before browser tab to allow only one tab polling for the others (performance improvement)
|
||||
* When a tab is opened, and the start_polling method is called, the tab is signaling through the localStorage to the
|
||||
* others. When a tab is closed, it signals its removing. If he was the master tab (the polling one), he choose another
|
||||
* one in the list of open tabs. This one start polling for the other. When a notification is recieved from the poll, it
|
||||
* is signaling through the localStorage too.
|
||||
*
|
||||
* localStorage used keys are :
|
||||
* - bus.channels : shared public channel list to listen during the poll
|
||||
* - bus.options : shared options
|
||||
* - bus.notification : the received notifications from the last poll
|
||||
* - bus.tab_list : list of opened tab ids
|
||||
* - bus.tab_master : generated id of the master tab
|
||||
*/
|
||||
var CrossTabBus = bus.Bus.extend({
|
||||
init: function(){
|
||||
var self = this;
|
||||
this._super.apply(this, arguments);
|
||||
this.is_master = false;
|
||||
tab_manager.register_tab(function () {
|
||||
self.is_master = true;
|
||||
self.start_polling();
|
||||
});
|
||||
|
||||
on("storage", this.on_storage.bind(this));
|
||||
if (this.is_master) {
|
||||
setItem('bus.channels', this.channels);
|
||||
setItem('bus.options', this.options);
|
||||
} else {
|
||||
this.channels = getItem('bus.channels', this.channels);
|
||||
this.options = getItem('bus.options', this.options);
|
||||
}
|
||||
|
||||
},
|
||||
start_polling: function(){
|
||||
if (this.is_master) {
|
||||
this._super.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
_notification_receive: function(notifications){
|
||||
if(this.is_master) { // broadcast to other tabs
|
||||
setItem('bus.notification', notifications);
|
||||
}
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
on_storage: function (e) {
|
||||
// use the value of event to not read from
|
||||
// localStorage (avoid race condition)
|
||||
var value = e.newValue;
|
||||
// notifications changed
|
||||
if(e.key === 'bus.notification'){
|
||||
var notifs = JSON.parse(value);
|
||||
_.each(notifs, this.on_notification);
|
||||
}
|
||||
// update channels
|
||||
if(e.key === 'bus.channels'){
|
||||
this.channels = JSON.parse(value);
|
||||
}
|
||||
// update options
|
||||
if(e.key === 'bus.options'){
|
||||
this.options = JSON.parse(value);
|
||||
}
|
||||
},
|
||||
add_channel: function(){
|
||||
this._super.apply(this, arguments);
|
||||
setItem('bus.channels', this.channels);
|
||||
},
|
||||
delete_channel: function(){
|
||||
this._super.apply(this, arguments);
|
||||
setItem('bus.channels', this.channels);
|
||||
},
|
||||
update_option: function(){
|
||||
this._super.apply(this, arguments);
|
||||
setItem('bus.options', this.options);
|
||||
},
|
||||
delete_option: function(){
|
||||
this._super.apply(this, arguments);
|
||||
setItem('bus.options', this.options);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
//utility functions
|
||||
function on(type, listener) {
|
||||
if (window.addEventListener) {
|
||||
window.addEventListener(type, listener);
|
||||
} else { //IE8
|
||||
window.attachEvent('on' + type, listener);
|
||||
}
|
||||
}
|
||||
|
||||
function getItem(key, defaultValue) {
|
||||
var val = localStorage.getItem(key);
|
||||
return val ? JSON.parse(val) : defaultValue;
|
||||
}
|
||||
|
||||
function setItem(key, value) {
|
||||
localStorage.setItem(key, JSON.stringify(value));
|
||||
}
|
||||
|
||||
var tab_manager = {
|
||||
peersKey: 'bus.peers',
|
||||
masterKey: 'bus.master',
|
||||
heartbeatKey: 'bus.heartbeat',
|
||||
isMaster: false,
|
||||
id: new Date().getTime() + ':' + (Math.random() * 1000000000 | 0),
|
||||
|
||||
register_tab: function (callback) {
|
||||
this.callback = callback;
|
||||
|
||||
var peers = getItem(tab_manager.peersKey, {});
|
||||
peers[tab_manager.id] = new Date().getTime();
|
||||
setItem(tab_manager.peersKey, peers);
|
||||
|
||||
on('unload', function () {
|
||||
// unload peer
|
||||
var peers = getItem(tab_manager.peersKey, {});
|
||||
delete peers[tab_manager.id];
|
||||
setItem(tab_manager.peersKey, peers);
|
||||
|
||||
// unload master
|
||||
if (tab_manager.isMaster) {
|
||||
localStorage.removeItem(tab_manager.masterKey);
|
||||
}
|
||||
});
|
||||
|
||||
if (!localStorage[tab_manager.masterKey]) {
|
||||
tab_manager.start_election();
|
||||
}
|
||||
|
||||
on('storage', function(e) {
|
||||
if (!e) { e = window.event;}
|
||||
if (e.key !== tab_manager.masterKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.newValue === null) { //master was unloaded
|
||||
tab_manager.start_election();
|
||||
}
|
||||
|
||||
});
|
||||
tab_manager.heartbeat();
|
||||
},
|
||||
heartbeat: function () {
|
||||
var current = new Date().getTime();
|
||||
var pollPeriod = 10000;
|
||||
var heartbeatValue = localStorage[tab_manager.heartbeatKey] || 0;
|
||||
var peers = getItem(tab_manager.peersKey, {});
|
||||
|
||||
if ((parseInt(heartbeatValue) + 5000) < current) {
|
||||
// Heartbeat is out of date. Electing new master
|
||||
tab_manager.start_election();
|
||||
}
|
||||
if (tab_manager.isMaster) {
|
||||
localStorage[tab_manager.heartbeatKey] = current;
|
||||
//walk through all peers and kill old
|
||||
var cleanedPeers = {};
|
||||
for (var peerName in peers) {
|
||||
if (peers[peerName] + 15000 > current) {
|
||||
cleanedPeers[peerName] = peers[peerName];
|
||||
}
|
||||
}
|
||||
|
||||
setItem(tab_manager.peersKey, cleanedPeers);
|
||||
pollPeriod = 1500;
|
||||
} else {
|
||||
//update own heartbeat
|
||||
peers[tab_manager.id] = current;
|
||||
setItem(tab_manager.peersKey, peers);
|
||||
}
|
||||
|
||||
setTimeout(function(){
|
||||
tab_manager.heartbeat();
|
||||
}, pollPeriod);
|
||||
},
|
||||
start_election: function () {
|
||||
if (tab_manager.isMaster) {
|
||||
return;
|
||||
}
|
||||
//check who's next
|
||||
var peers = getItem(tab_manager.peersKey, {});
|
||||
var now = new Date().getTime();
|
||||
var newMaster;
|
||||
|
||||
for (var peerName in peers) {
|
||||
//check for dead peers
|
||||
if (peers[peerName] + 15000 < now) {
|
||||
continue;
|
||||
}
|
||||
|
||||
newMaster = peerName;
|
||||
break;
|
||||
}
|
||||
if (newMaster === tab_manager.id) {
|
||||
//we're next in queue. Electing as master
|
||||
setItem(tab_manager.masterKey, tab_manager.id);
|
||||
setItem(tab_manager.heartbeatKey, new Date().getTime());
|
||||
tab_manager.isMaster = true;
|
||||
tab_manager.callback();
|
||||
|
||||
//removing master peer from queue
|
||||
delete peers[newMaster];
|
||||
setItem(tab_manager.peersKey, peers);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
// bus singleton, depending of the browser :
|
||||
// if supporting LocalStorage, there will be only one tab polling
|
||||
if(typeof Storage !== "undefined"){
|
||||
bus.bus = new CrossTabBus();
|
||||
} else {
|
||||
bus.bus = new bus.Bus();
|
||||
}
|
||||
|
||||
return bus;
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -353,14 +353,7 @@
|
||||
<field name="create_date"/>
|
||||
<field name="country_id"/>
|
||||
<separator />
|
||||
<filter string="Leads"
|
||||
name="type_lead"
|
||||
domain="[('type', '=', 'lead')]"/>
|
||||
<filter string="Opportunities"
|
||||
name="type_opportunity"
|
||||
domain="[('type', '=', 'opportunity')]"/>
|
||||
<separator/>
|
||||
<filter string="My Opportunities"
|
||||
<filter string="My Leads"
|
||||
name="assigned_to_me"
|
||||
domain="[('user_id','=',uid)]"
|
||||
help="Leads that are assigned to me"/>
|
||||
@@ -371,9 +364,6 @@
|
||||
<filter string="Important Messages" name="message_needaction" domain="[('message_needaction','=',True)]"/>
|
||||
<filter string="Archived" name="archived" domain="[('active', '=', False)]"/>
|
||||
<separator />
|
||||
<filter string="Followup" name="activities"
|
||||
domain="[('date_action', '<=', datetime.datetime.now().replace(hour=23, minute=59, second=59))]"/>
|
||||
<separator />
|
||||
<filter string="Exclude Opt Out"
|
||||
name='not_opt_out' domain="[('opt_out', '=', False)]"
|
||||
help="Leads that did not ask not to be included in mass mailing campaigns"/>
|
||||
@@ -384,9 +374,8 @@
|
||||
<filter name="customer" string="Customer" help="Partner" domain="[]" context="{'group_by':'partner_id'}"/>
|
||||
<filter string="Campaign" name="groupby_campaign" domain="[]" context="{'group_by':'campaign_id'}"/>
|
||||
<filter string="Source" name="groupby_source" domain="[]" context="{'group_by':'source_id'}"/>
|
||||
<filter string="Channel" name="groupby_channel" domain="[]" context="{'group_by':'medium_id'}"/>
|
||||
<filter string="Medium" name="groupby_channel" domain="[]" context="{'group_by':'medium_id'}"/>
|
||||
<filter string="Creation Month" name="groupby_createmonth" domain="[]" context="{'group_by':'create_date'}"/>
|
||||
<filter string="Expected Closing" name="groupby_expectclosing" domain="[]" context="{'group_by':'date_deadline'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
@@ -576,7 +565,7 @@
|
||||
<field name="name">Opportunities Tree</field>
|
||||
<field name="model">crm.lead</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Opportunities" decoration-bf="message_needaction==True" decoration-muted="probability == 100" decoration-danger="date_deadline and (date_deadline < current_date)">
|
||||
<tree string="Opportunities" decoration-bf="message_needaction==True" decoration-muted="probability == 100" decoration-danger="date_action and (date_action < current_date)">
|
||||
<field name="date_deadline" invisible="1"/>
|
||||
<field name="create_date"/>
|
||||
<field name="name" string="Opportunity"/>
|
||||
@@ -651,7 +640,7 @@
|
||||
<filter string="Company" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
|
||||
<filter name="stage" string="Stage" context="{'group_by':'stage_id'}"/>
|
||||
<filter string="Campaign" domain="[]" context="{'group_by':'campaign_id'}"/>
|
||||
<filter string="Channel" domain="[]" context="{'group_by':'medium_id'}"/>
|
||||
<filter string="Medium" domain="[]" context="{'group_by':'medium_id'}"/>
|
||||
<filter string="Source" domain="[]" context="{'group_by':'source_id'}"/>
|
||||
<separator orientation="vertical" />
|
||||
<filter string="Creation Month" context="{'group_by':'create_date:month'}" name="month"/>
|
||||
|
||||
@@ -122,7 +122,7 @@ class crm_team(osv.Model):
|
||||
|
||||
def action_your_pipeline(self, cr, uid, context=None):
|
||||
IrModelData = self.pool['ir.model.data']
|
||||
action = IrModelData.xmlid_to_object(cr, uid, 'crm.crm_lead_opportunities_tree_view').read(['name', 'help', 'res_model', 'target', 'context', 'type'])
|
||||
action = IrModelData.xmlid_to_object(cr, uid, 'crm.crm_lead_opportunities_tree_view').read(['name', 'help', 'res_model', 'target', 'domain', 'context', 'type', 'search_view_id'])
|
||||
if not action:
|
||||
action = {}
|
||||
else:
|
||||
|
||||
@@ -29,6 +29,6 @@ class PriceRule(models.Model):
|
||||
operator = fields.Selection([('==', '='), ('<=', '<='), ('<', '<'), ('>=', '>='), ('>', '>')], 'Operator', required=True, default='<=')
|
||||
max_value = fields.Float('Maximum Value', required=True)
|
||||
variable_factor = fields.Selection([('weight', 'Weight'), ('volume', 'Volume'), ('wv', 'Weight * Volume'), ('price', 'Price'), ('quantity', 'Quantity')], 'Variable Factor', required=True, default='weight')
|
||||
list_base_price = fields.Float(string='Sale Base Price', digits_compute=dp.get_precision('Product Price'), required=True, default=0.0)
|
||||
list_price = fields.Float('Sale Price', digits_compute=dp.get_precision('Product Price'), required=True, default=0.0)
|
||||
standard_price = fields.Float('Cost Price', digits_compute=dp.get_precision('Product Price'), required=True, default=0.0)
|
||||
list_base_price = fields.Float(string='Sale Base Price', digits=dp.get_precision('Product Price'), required=True, default=0.0)
|
||||
list_price = fields.Float('Sale Price', digits=dp.get_precision('Product Price'), required=True, default=0.0)
|
||||
standard_price = fields.Float('Cost Price', digits=dp.get_precision('Product Price'), required=True, default=0.0)
|
||||
|
||||
@@ -13,7 +13,7 @@ class StockMove(models.Model):
|
||||
uom_categ_id = self.env.ref('product.product_uom_categ_kgm').id
|
||||
return self.env['product.uom'].search([('category_id', '=', uom_categ_id), ('factor', '=', 1)], limit=1)
|
||||
|
||||
weight = fields.Float(compute='_cal_move_weight', digits_compute=dp.get_precision('Stock Weight'), store=True)
|
||||
weight = fields.Float(compute='_cal_move_weight', digits=dp.get_precision('Stock Weight'), store=True)
|
||||
weight_uom_id = fields.Many2one('product.uom', string='Unit of Measure', required=True, readonly=True, help="Unit of Measure (Unit of Measure) is the unit of measurement for Weight", default=_default_uom)
|
||||
|
||||
@api.depends('product_id', 'product_uom_qty', 'product_uom')
|
||||
|
||||
@@ -56,7 +56,7 @@ class StockPicking(models.Model):
|
||||
delivery_type = fields.Selection(related='carrier_id.delivery_type', readonly=True)
|
||||
carrier_id = fields.Many2one("delivery.carrier", string="Carrier")
|
||||
volume = fields.Float(copy=False)
|
||||
weight = fields.Float(compute='_cal_weight', digits_compute=dp.get_precision('Stock Weight'), store=True)
|
||||
weight = fields.Float(compute='_cal_weight', digits=dp.get_precision('Stock Weight'), store=True)
|
||||
carrier_tracking_ref = fields.Char(string='Tracking Reference', copy=False)
|
||||
number_of_packages = fields.Integer(string='Number of Packages', copy=False)
|
||||
weight_uom_id = fields.Many2one('product.uom', string='Unit of Measure', required=True, readonly="1", help="Unit of measurement for Weight", default=_default_uom)
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="context">{}</field>
|
||||
<field name="domain">[('url', 'like', '/spreadsheet/')]</field>
|
||||
<field name="domain">[('url', '=ilike', '%google%/spreadsheet%')]</field>
|
||||
<field name="help">Google Spreadsheets</field>
|
||||
</record>
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ class hr_timesheet_report(models.Model):
|
||||
user_id = fields.Many2one('res.users', 'User', readonly=True)
|
||||
account_id = fields.Many2one('account.analytic.account', 'Analytic Account', readonly=True)
|
||||
company_id = fields.Many2one('res.company', 'Company', readonly=True)
|
||||
cost = fields.Float('Cost', readonly=True, digits_compute=dp.get_precision('Account'))
|
||||
cost = fields.Float('Cost', readonly=True, digits=dp.get_precision('Account'))
|
||||
quantity = fields.Float('Time', readonly=True)
|
||||
|
||||
def _select(self):
|
||||
|
||||
@@ -44,7 +44,8 @@ index_template = """
|
||||
</p>
|
||||
<p>
|
||||
For more information on how to setup the Point of Sale with
|
||||
the PosBox, please refer to <a href='/hw_proxy/static/doc/manual.pdf'>the manual</a>.
|
||||
the PosBox, please refer to
|
||||
<a href='https://www.odoo.com/documentation/user/point_of_sale/posbox/index.html'>the manual</a>.
|
||||
</p>
|
||||
<p>
|
||||
To see the status of the connected hardware, please refer
|
||||
|
||||
@@ -70,7 +70,8 @@ upgrade_template = """
|
||||
<p></p>
|
||||
However the preferred method to upgrade the posbox is to flash the sd-card with
|
||||
the <a href='http://nightly.odoo.com/trunk/posbox/'>latest image</a>. The upgrade
|
||||
procedure is explained into to the <a href='/hw_proxy/static/doc/manual.pdf'>PosBox manual</a>
|
||||
procedure is explained into to the
|
||||
<a href='https://www.odoo.com/documentation/user/point_of_sale/posbox/index.html'>PosBox manual</a>
|
||||
</p>
|
||||
<p>
|
||||
To upgrade the posbox, click on the upgrade button. The upgrade will take a few minutes. <b>Do not reboot</b> the PosBox during the upgrade.
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 66 KiB |
@@ -1,548 +0,0 @@
|
||||
<?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="1600px"
|
||||
height="1200px"
|
||||
id="svg2985"
|
||||
version="1.1"
|
||||
inkscape:version="0.48.3.1 r9886"
|
||||
sodipodi:docname="posbox_doc_schema.svg"
|
||||
inkscape:export-filename="/home/fva/Code/openerp/posbox_doc_schema.png"
|
||||
inkscape:export-xdpi="67.5"
|
||||
inkscape:export-ydpi="67.5">
|
||||
<defs
|
||||
id="defs2987">
|
||||
<marker
|
||||
inkscape:stockid="DotL"
|
||||
orient="auto"
|
||||
refY="0.0"
|
||||
refX="0.0"
|
||||
id="DotL"
|
||||
style="overflow:visible">
|
||||
<path
|
||||
id="path6730"
|
||||
d="M -2.5,-1.0 C -2.5,1.7600000 -4.7400000,4.0 -7.5,4.0 C -10.260000,4.0 -12.5,1.7600000 -12.5,-1.0 C -12.5,-3.7600000 -10.260000,-6.0 -7.5,-6.0 C -4.7400000,-6.0 -2.5,-3.7600000 -2.5,-1.0 z "
|
||||
style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt"
|
||||
transform="scale(0.8) translate(7.4, 1)" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:stockid="DotM"
|
||||
orient="auto"
|
||||
refY="0.0"
|
||||
refX="0.0"
|
||||
id="DotM"
|
||||
style="overflow:visible">
|
||||
<path
|
||||
id="path6733"
|
||||
d="M -2.5,-1.0 C -2.5,1.7600000 -4.7400000,4.0 -7.5,4.0 C -10.260000,4.0 -12.5,1.7600000 -12.5,-1.0 C -12.5,-3.7600000 -10.260000,-6.0 -7.5,-6.0 C -4.7400000,-6.0 -2.5,-3.7600000 -2.5,-1.0 z "
|
||||
style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt"
|
||||
transform="scale(0.4) translate(7.4, 1)" />
|
||||
</marker>
|
||||
<pattern
|
||||
inkscape:collect="always"
|
||||
xlink:href="#Strips1_3"
|
||||
id="pattern6512"
|
||||
patternTransform="matrix(1.4070053,-1.4070053,10.196067,10.196067,0,0)" />
|
||||
<pattern
|
||||
inkscape:stockid="Stripes 1:3"
|
||||
id="Strips1_3"
|
||||
patternTransform="translate(0,0) scale(10,10)"
|
||||
height="1"
|
||||
width="4"
|
||||
patternUnits="userSpaceOnUse"
|
||||
inkscape:collect="always">
|
||||
<rect
|
||||
id="rect4622"
|
||||
height="2"
|
||||
width="1"
|
||||
y="-0.5"
|
||||
x="0"
|
||||
style="fill:black;stroke:none" />
|
||||
</pattern>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="1"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.45254834"
|
||||
inkscape:cx="679.01909"
|
||||
inkscape:cy="813.9637"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:document-units="px"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="991"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata2990">
|
||||
<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
|
||||
id="layer1"
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer">
|
||||
<g
|
||||
id="g7313">
|
||||
<g
|
||||
transform="translate(0,35.355339)"
|
||||
id="g6566">
|
||||
<g
|
||||
id="g6514"
|
||||
transform="translate(-10.9375,-57.8125)">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path6489"
|
||||
d="m 905,179.59375 -34.8125,31.34375 -118.78125,0 c -5.54,0 -10,4.46 -10,10 l 0,102.5 -157.5,0 c -16.62,0 -30,13.38 -30,30 l 0,632.3125 -27.4375,24.9375 33.25,44.2813 c 0.10609,0.1442 0.2039,0.2952 0.3125,0.4374 l 2.125,2.8126 0.21875,-0.125 c 5.44486,5.6076 13.06148,9.0937 21.53125,9.0937 l 463.43755,0 c 16.6199,0 30,-13.38 30,-30 l 0,-683.75 c 0,-10.90688 -5.7475,-20.40588 -14.4063,-25.65625 l -41.125,-31 -27.3125,26.65625 -45.28125,0 0,-102.5 c 0,-2.89877 -1.22955,-5.48931 -3.1875,-7.3125 l 0.25,-0.375 L 905,179.59375 z"
|
||||
style="opacity:0.25;fill:url(#pattern6512);fill-opacity:1;stroke:none" />
|
||||
<g
|
||||
transform="translate(-2.2097087,75.130096)"
|
||||
id="g3823">
|
||||
<g
|
||||
id="g3847">
|
||||
<rect
|
||||
ry="5.274591"
|
||||
y="106.16312"
|
||||
x="711.8349"
|
||||
height="151.06096"
|
||||
width="194.27489"
|
||||
id="rect3843"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:8.48901749;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
rx="5.274591" />
|
||||
<rect
|
||||
rx="5.274591"
|
||||
style="fill:none;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
id="rect3845"
|
||||
width="175.41872"
|
||||
height="151.06096"
|
||||
x="721.263"
|
||||
y="106.16312"
|
||||
ry="5.274591" />
|
||||
</g>
|
||||
<rect
|
||||
rx="5.274591"
|
||||
ry="5.274591"
|
||||
y="869.83844"
|
||||
x="746.01172"
|
||||
height="110.99157"
|
||||
width="131.81378"
|
||||
id="rect3765"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:8.48901749;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:8.48901749;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
id="rect6618"
|
||||
width="125.18465"
|
||||
height="53.539146"
|
||||
x="938.25635"
|
||||
y="571.52777"
|
||||
ry="5.274591"
|
||||
rx="5.274591" />
|
||||
<rect
|
||||
rx="34.723557"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:8;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
id="rect2993"
|
||||
width="509.11688"
|
||||
height="730.677"
|
||||
x="525.61603"
|
||||
y="217.12157"
|
||||
ry="34.723557" />
|
||||
<rect
|
||||
rx="21.787001"
|
||||
ry="21.787001"
|
||||
y="235.37366"
|
||||
x="543.0672"
|
||||
height="694.12708"
|
||||
width="474.21454"
|
||||
id="rect3763"
|
||||
style="fill:none;stroke:#000000;stroke-width:1.99425566;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<rect
|
||||
style="fill:none;stroke:#000000;stroke-width:1.99425554;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
id="rect3767"
|
||||
width="160.64433"
|
||||
height="9.1270761"
|
||||
x="562.84052"
|
||||
y="740.37366"
|
||||
rx="5.3000002"
|
||||
ry="5.3000002" />
|
||||
<rect
|
||||
ry="5.3000002"
|
||||
rx="5.2999997"
|
||||
y="667.30597"
|
||||
x="828.00549"
|
||||
height="9.1270761"
|
||||
width="73.434502"
|
||||
id="rect3769"
|
||||
style="fill:none;stroke:#000000;stroke-width:1.99399996;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<rect
|
||||
ry="5.3000002"
|
||||
rx="5.3000002"
|
||||
y="313.75256"
|
||||
x="693.65527"
|
||||
height="9.1270761"
|
||||
width="160.64433"
|
||||
id="rect3771"
|
||||
style="fill:none;stroke:#000000;stroke-width:1.99425554;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<g
|
||||
id="g3783">
|
||||
<rect
|
||||
ry="5.3000002"
|
||||
rx="5.3000002"
|
||||
y="245.39903"
|
||||
x="962.35583"
|
||||
height="271.93509"
|
||||
width="43.971706"
|
||||
id="rect3773"
|
||||
style="fill:none;stroke:#000000;stroke-width:1.99425554;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<rect
|
||||
ry="5.3000002"
|
||||
rx="5.3000002"
|
||||
y="374.20874"
|
||||
x="952.03491"
|
||||
height="7.0710678"
|
||||
width="21.213203"
|
||||
id="rect3775"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
||||
id="rect3777"
|
||||
width="21.213203"
|
||||
height="7.0710678"
|
||||
x="992.86823"
|
||||
y="373.3754"
|
||||
rx="5.3000002"
|
||||
ry="5.3000002" />
|
||||
<rect
|
||||
transform="matrix(0,-1,1,0,0,0)"
|
||||
ry="5.3000002"
|
||||
rx="5.3000002"
|
||||
y="981.60596"
|
||||
x="-260.01752"
|
||||
height="7.0710678"
|
||||
width="21.213203"
|
||||
id="rect3779"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none"
|
||||
id="rect3781"
|
||||
width="21.213203"
|
||||
height="7.0710678"
|
||||
x="-526.6842"
|
||||
y="981.60596"
|
||||
rx="5.3000002"
|
||||
ry="5.3000002"
|
||||
transform="matrix(0,-1,1,0,0,0)" />
|
||||
</g>
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
d="m 958.1875,822.25 c -9.01707,3.59482 -15.375,12.41301 -15.375,22.71875 0,13.50563 10.93187,24.46875 24.4375,24.46875 13.50563,0 24.46875,-10.96312 24.46875,-24.46875 0,-10.17788 -6.2141,-18.91637 -15.0625,-22.59375 l 0,8.8125 c 4.63325,2.9517 7.6875,8.02222 7.6875,13.78125 0,9.11223 -7.65608,16.5 -17.09375,16.5 -9.43767,0 -17.0625,-7.38777 -17.0625,-16.5 0,-5.88681 3.20248,-11.04837 8,-13.96875 l 0,-8.75 z"
|
||||
id="path3792" />
|
||||
<g
|
||||
id="g3818"
|
||||
transform="translate(0,-1.767767)">
|
||||
<rect
|
||||
ry="5.3000002"
|
||||
rx="5.2999992"
|
||||
y="879.66095"
|
||||
x="943.13324"
|
||||
height="8.7558908"
|
||||
width="46.827415"
|
||||
id="rect3794"
|
||||
style="fill:none;stroke:#000000;stroke-width:1.99399996;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<rect
|
||||
style="fill:none;stroke:#000000;stroke-width:1.99399996;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
id="rect3798"
|
||||
width="46.827415"
|
||||
height="8.7558908"
|
||||
x="943.13324"
|
||||
y="895.68982"
|
||||
rx="5.2999992"
|
||||
ry="5.3000002" />
|
||||
<rect
|
||||
ry="5.3000002"
|
||||
rx="5.2999992"
|
||||
y="910.22754"
|
||||
x="943.13324"
|
||||
height="8.7558908"
|
||||
width="46.827415"
|
||||
id="rect3800"
|
||||
style="fill:none;stroke:#000000;stroke-width:1.99399996;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
</g>
|
||||
<rect
|
||||
ry="5.3000002"
|
||||
rx="5.2999992"
|
||||
y="963.05292"
|
||||
x="-845.58075"
|
||||
height="8.7558908"
|
||||
width="32.390652"
|
||||
id="rect3812"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.99400008;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
transform="matrix(0,-1,1,0,0,0)" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g6559"
|
||||
transform="translate(0,-7.7339804)">
|
||||
<path
|
||||
transform="matrix(1,0,0,0.85333333,0,117.03125)"
|
||||
d="m 1119.5313,856.64062 c 0,5.82488 -4.1974,10.54688 -9.375,10.54688 -5.1777,0 -9.375,-4.722 -9.375,-10.54688 0,-5.82487 4.1973,-10.54687 9.375,-10.54687 5.1776,0 9.375,4.722 9.375,10.54687 z"
|
||||
sodipodi:ry="10.546875"
|
||||
sodipodi:rx="9.375"
|
||||
sodipodi:cy="856.64062"
|
||||
sodipodi:cx="1110.1562"
|
||||
id="path6539"
|
||||
style="fill:#00fd28;fill-opacity:1;stroke:#000000;stroke-width:2.16506362;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
sodipodi:type="arc" />
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="fill:#ff0d0d;fill-opacity:1;stroke:#000000;stroke-width:2.16506362;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
id="path6541"
|
||||
sodipodi:cx="1110.1562"
|
||||
sodipodi:cy="856.64062"
|
||||
sodipodi:rx="9.375"
|
||||
sodipodi:ry="10.546875"
|
||||
d="m 1119.5313,856.64062 c 0,5.82488 -4.1974,10.54688 -9.375,10.54688 -5.1777,0 -9.375,-4.722 -9.375,-10.54688 0,-5.82487 4.1973,-10.54687 9.375,-10.54687 5.1776,0 9.375,4.722 9.375,10.54687 z"
|
||||
transform="matrix(1,0,0,0.85333333,0,141.44531)" />
|
||||
<path
|
||||
transform="matrix(1,0,0,0.85333333,0,165.85937)"
|
||||
d="m 1119.5313,856.64062 c 0,5.82488 -4.1974,10.54688 -9.375,10.54688 -5.1777,0 -9.375,-4.722 -9.375,-10.54688 0,-5.82487 4.1973,-10.54687 9.375,-10.54687 5.1776,0 9.375,4.722 9.375,10.54687 z"
|
||||
sodipodi:ry="10.546875"
|
||||
sodipodi:rx="9.375"
|
||||
sodipodi:cy="856.64062"
|
||||
sodipodi:cx="1110.1562"
|
||||
id="path6543"
|
||||
style="fill:#00fd28;fill-opacity:1;stroke:#000000;stroke-width:2.16506362;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
sodipodi:type="arc" />
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="fill:#00fd28;fill-opacity:1;stroke:#000000;stroke-width:2.16506362;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
id="path6545"
|
||||
sodipodi:cx="1110.1562"
|
||||
sodipodi:cy="856.64062"
|
||||
sodipodi:rx="9.375"
|
||||
sodipodi:ry="10.546875"
|
||||
d="m 1119.5313,856.64062 c 0,5.82488 -4.1974,10.54688 -9.375,10.54688 -5.1777,0 -9.375,-4.722 -9.375,-10.54688 0,-5.82487 4.1973,-10.54687 9.375,-10.54687 5.1776,0 9.375,4.722 9.375,10.54687 z"
|
||||
transform="matrix(1,0,0,0.85333333,0,190.27344)" />
|
||||
<path
|
||||
transform="matrix(1,0,0,0.85333333,0,214.6875)"
|
||||
d="m 1119.5313,856.64062 c 0,5.82488 -4.1974,10.54688 -9.375,10.54688 -5.1777,0 -9.375,-4.722 -9.375,-10.54688 0,-5.82487 4.1973,-10.54687 9.375,-10.54687 5.1776,0 9.375,4.722 9.375,10.54687 z"
|
||||
sodipodi:ry="10.546875"
|
||||
sodipodi:rx="9.375"
|
||||
sodipodi:cy="856.64062"
|
||||
sodipodi:cx="1110.1562"
|
||||
id="path6547"
|
||||
style="fill:#fff90d;fill-opacity:1;stroke:#000000;stroke-width:2.16506362;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
sodipodi:type="arc" />
|
||||
</g>
|
||||
</g>
|
||||
<text
|
||||
sodipodi:linespacing="125%"
|
||||
id="text6598"
|
||||
y="178.8759"
|
||||
x="514.01447"
|
||||
style="font-size:36.98903275px;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:normal;font-stretch:normal;font-family:Droid Sans;-inkscape-font-specification:Droid Sans"
|
||||
y="178.8759"
|
||||
x="514.01447"
|
||||
id="tspan6600"
|
||||
sodipodi:role="line">2A Power</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:36.98903275px;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="724.2699"
|
||||
y="127.21583"
|
||||
id="text6602"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6604"
|
||||
x="724.2699"
|
||||
y="127.21583"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Droid Sans;-inkscape-font-specification:Droid Sans">SD Card</tspan></text>
|
||||
<text
|
||||
sodipodi:linespacing="125%"
|
||||
id="text6606"
|
||||
y="626.60999"
|
||||
x="319.89322"
|
||||
style="font-size:36.98903275px;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:normal;font-stretch:normal;font-family:Droid Sans;-inkscape-font-specification:Droid Sans"
|
||||
y="626.60999"
|
||||
x="319.89322"
|
||||
id="tspan6608"
|
||||
sodipodi:role="line">HDMI</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:36.98903275px;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="761.83496"
|
||||
y="1097.278"
|
||||
id="text6610"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6612"
|
||||
x="761.83496"
|
||||
y="1097.278"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Droid Sans;-inkscape-font-specification:Droid Sans">USB</tspan></text>
|
||||
<text
|
||||
sodipodi:linespacing="125%"
|
||||
id="text6614"
|
||||
y="1097.278"
|
||||
x="596.10681"
|
||||
style="font-size:36.98903275px;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:normal;font-stretch:normal;font-family:Droid Sans;-inkscape-font-specification:Droid Sans"
|
||||
y="1097.278"
|
||||
x="596.10681"
|
||||
id="tspan6616"
|
||||
sodipodi:role="line">RJ45</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:36.98903275px;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="1135.8894"
|
||||
y="660.90717"
|
||||
id="text6620"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6622"
|
||||
x="1135.8894"
|
||||
y="660.90717"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Droid Sans;-inkscape-font-specification:Droid Sans">Composite</tspan></text>
|
||||
<text
|
||||
sodipodi:linespacing="125%"
|
||||
id="text6624"
|
||||
y="774.18842"
|
||||
x="1135.8894"
|
||||
style="font-size:36.98903275px;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:normal;font-stretch:normal;font-family:Droid Sans;-inkscape-font-specification:Droid Sans"
|
||||
y="774.18842"
|
||||
x="1135.8894"
|
||||
id="tspan6626"
|
||||
sodipodi:role="line">Audio</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:36.98903275px;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="1135.8894"
|
||||
y="774.18842"
|
||||
id="text6628"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6630"
|
||||
x="1135.8894"
|
||||
y="774.18842"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Droid Sans;-inkscape-font-specification:Droid Sans">Audio</tspan></text>
|
||||
<text
|
||||
sodipodi:linespacing="125%"
|
||||
id="text6632"
|
||||
y="891.37592"
|
||||
x="1212.4519"
|
||||
style="font-size:36.98903275px;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:normal;font-stretch:normal;font-family:Droid Sans;-inkscape-font-specification:Droid Sans"
|
||||
y="891.37592"
|
||||
x="1212.4519"
|
||||
id="tspan6634"
|
||||
sodipodi:role="line">Ready</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:36.98903275px;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="1212.4519"
|
||||
y="940.45667"
|
||||
id="text6636"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6638"
|
||||
x="1212.4519"
|
||||
y="940.45667"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Droid Sans;-inkscape-font-specification:Droid Sans">Power</tspan></text>
|
||||
<text
|
||||
sodipodi:linespacing="125%"
|
||||
id="text6640"
|
||||
y="986.97278"
|
||||
x="1212.4519"
|
||||
style="font-size:36.98903275px;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:normal;font-stretch:normal;font-family:Droid Sans;-inkscape-font-specification:Droid Sans"
|
||||
y="986.97278"
|
||||
x="1212.4519"
|
||||
id="tspan6642"
|
||||
sodipodi:role="line">Network</tspan></text>
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path6663"
|
||||
d="m 437.5,614.15728 54.6875,0"
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#DotL)" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#DotL)"
|
||||
d="m 601.86625,199.79103 0,54.6875"
|
||||
id="path7295"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path7297"
|
||||
d="m 1113.1075,650.87603 -54.6875,0"
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#DotL)" />
|
||||
<path
|
||||
sodipodi:nodetypes="cc"
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#DotL)"
|
||||
d="m 1113.1075,761.81353 -76.5625,0"
|
||||
id="path7299"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#DotL)"
|
||||
d="m 1192.657,876.26632 -54.6875,0"
|
||||
id="path7301"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
sodipodi:nodetypes="cc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path7303"
|
||||
d="m 1192.657,927.08963 -54.6875,-27.62136"
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#DotL)" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#DotL)"
|
||||
d="m 1192.657,974.04593 -54.6875,0"
|
||||
id="path7305"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
sodipodi:nodetypes="cc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path7307"
|
||||
d="m 1192.657,974.04593 -54.6875,-24.85922"
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#DotL)" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#DotL)"
|
||||
d="m 1192.657,974.04593 -54.6875,-49.16602"
|
||||
id="path7309"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
sodipodi:nodetypes="cc"
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#DotL)"
|
||||
d="m 630.03792,1053.883 0,-32.0313"
|
||||
id="path7311"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 24 KiB |
@@ -1,447 +0,0 @@
|
||||
====================
|
||||
PosBox Documentation
|
||||
====================
|
||||
|
||||
Posbox Setup Guide
|
||||
==================
|
||||
|
||||
Prerequisites
|
||||
-------------
|
||||
|
||||
Before you start setting up your PosPox make sure you have everything.
|
||||
You will need :
|
||||
|
||||
- The PosBox
|
||||
- A 2A Power adapter
|
||||
- A computer or tablet with an up to date Firefox, Chrome or Safari web
|
||||
browser (we recommend Chrome)
|
||||
- A running SaaS or V8.0 OpenERP Instance with the Point of Sale
|
||||
Installed.
|
||||
- A local network set up with DHCP ( this is the default setting )
|
||||
- A RJ45 Ethrnet Cable
|
||||
- An Epson USB TM-T20 Printer or another compatible printer.
|
||||
- A Honeywell Eclipse USB Barcode Scanner or another compatible scanner.
|
||||
- An Epson compatible cash drawer.
|
||||
|
||||
Step By Step Setup Guide
|
||||
------------------------
|
||||
|
||||
.. image:: _images/posbox_doc_schema.png
|
||||
:width: 100%
|
||||
:align: center
|
||||
|
||||
Power the PosBox
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Plug the PosBox to the 2A Power Adapter, a bright red status led should
|
||||
light up.
|
||||
|
||||
Connect it to the Local Network
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Connect the PosBox to the Local Network with the RJ45 Cable. Make sure
|
||||
You connect the PosBox to the same Network as your POS device. Once
|
||||
connected a bright yellow status led should light up, and a green status
|
||||
led should flash.
|
||||
|
||||
Connect the USB Printer
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Put a paper roll in the printer, power it up, then connect it to one of
|
||||
the PosBox's USB port
|
||||
|
||||
Connect the cash drawer
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The cash drawer should be connected to the printer with the RJ25 cable
|
||||
|
||||
Connect the USB Barcode Scanner
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Connect the usb barcode scanner to one of the PosBox's USB port. The
|
||||
Barcode Scanner should immediately light up and emit a loud beep.
|
||||
|
||||
Configure the USB Barcode Scanner
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The barcode scanner should be configured in QWERTY and emit a new line /
|
||||
return symbol after each scan. This is most likely the default
|
||||
configuration of your barcode scanner.
|
||||
|
||||
Make sure the PosBox is ready
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Once powered, The PosBox needs less than a minute to boot. Once the
|
||||
PosBox is ready, it should print a status receipt with its IP Adress.
|
||||
Also, the last status led, just after the red power status led, should
|
||||
be permanently lit green.
|
||||
|
||||
Setup the Point of Sale
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Make sure to activate the 'Print via Proxy', 'Remote Scanning' or
|
||||
'Cashdrawer' options in the Point of Sale configuration. If you are
|
||||
using firefox, you must manually specify the PosBox's IP address which
|
||||
was printed on the status receipt.
|
||||
|
||||
If you are running multiple Point of Sales on the same PosBox, make sure
|
||||
that only one of them has Remote Scanning activated.
|
||||
|
||||
If you manually specify the PosBox's IP address, you must configure your
|
||||
network to make sure the PosBox's IP address doesn't change. Please
|
||||
refer to your Router documentation.
|
||||
|
||||
Launch the Point of Sale.
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you didn't spcecify the PosBox's IP address in the config, the POS
|
||||
will need some time to perform a network scan to find the PosBox. This
|
||||
is only done once.
|
||||
|
||||
The Point of Sale is now connected to the PosBox and your Hardware
|
||||
should be ready to use.
|
||||
|
||||
Multi-Pos Configuration
|
||||
-----------------------
|
||||
The advised way to setup a multi Point-of-Sale shop is to have one PosBox
|
||||
per Point-of-Sale. In this case it is mandatory to manually specify the
|
||||
IP address of each PosBox in each Point of Sale. You must also configure
|
||||
your network to make sure the PosBox's IP address doesn't change. Please
|
||||
refer to your Router documentation.
|
||||
|
||||
PosBoxless Setup Guide
|
||||
======================
|
||||
|
||||
If you are running your Point of Sale on a debian-based linux
|
||||
distribution, you do not need the PosBox as you can run its software
|
||||
locally. However the installation process is not foolproof. You'll need
|
||||
at least to know how to install and run openerp. You may also run into
|
||||
issues specific to your distribution or to your particular setup and
|
||||
hardware configuration.
|
||||
|
||||
Prerequisites
|
||||
-------------
|
||||
|
||||
- A debian based linux distribution (Debian, Ubuntu, Mint, etc.)
|
||||
- A running OpenERP server (version trunk or saas-3 or 8.0 or later)
|
||||
- You must uninstall any esc-pos printer driver as it will conflict
|
||||
with OpenERP's built-in driver.
|
||||
|
||||
Step By Step Setup Guide
|
||||
------------------------
|
||||
|
||||
Extra dependencies
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Because Odoo is running on Python 2, you need to check which version of pip you need to use.
|
||||
|
||||
$ sudo pip --version
|
||||
|
||||
If it returns something like :
|
||||
pip 1.5.6 from /usr/local/lib/python3.3/dist-packages/pip-1.5.6-py3.3.egg (python 3.3)
|
||||
You need to try pip2 instead
|
||||
|
||||
If it returns something like :
|
||||
pip 1.4.1 from /usr/lib/python2.7/dist-packages (python 2.7)
|
||||
You can use pip
|
||||
|
||||
The driver modules requires the installation of new python modules::
|
||||
|
||||
$ sudo pip install pyserial
|
||||
$ sudo pip install --pre pyusb
|
||||
$ sudo pip install qrcode
|
||||
|
||||
Database Setup
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
You must create a database called ``posbox`` with the modules
|
||||
``hw_proxy``, ``hw_escpos``, and ``hw_scanner`` installed.
|
||||
|
||||
Access Rights
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
The drivers need raw access to the printer and barcode scanner devices.
|
||||
Doing so requires a bit system administration. First we are going to
|
||||
create a group that has haccess to usb devices::
|
||||
|
||||
$ sudo groupadd usbusers
|
||||
|
||||
Then we add the user who will run the OpenERP server to ``usbusers``::
|
||||
|
||||
$ sudo useradd -G usbusers USERNAME
|
||||
|
||||
Then we need to create a udev rule that will automatically allow members
|
||||
of ``usbusers`` to access raw usb devices. To do so create a file called
|
||||
``99-usbusers.rule`` in the ``/etc/udev/rules.d/`` directory with the
|
||||
following content::
|
||||
|
||||
SUBSYSTEM=="usb", GROUP="usbusers", MODE="0660"
|
||||
SUBSYSTEMS=="usb", GROUP="usbusers", MODE="0660"
|
||||
|
||||
Then you need to reboot your machine.
|
||||
|
||||
Start the local OpenERP Installl
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
We must launch the OpenERP server on the port ``8069`` with the correct
|
||||
database settings::
|
||||
|
||||
$ ./server/openerp-server --addons-path=addons,web/addons --db-filter='^posbox$' \
|
||||
--xmlrpc-port=8069 -d posbox
|
||||
|
||||
Check that everything works
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Plug all your hardware to your machine's USB ports, and go to
|
||||
``http://localhost:8069/hw_proxy/status`` refresh the page a few times and
|
||||
see if all your devices are indicated as *Connected*. Possible source of
|
||||
errors are: The paths on the distribution differ from the paths expected
|
||||
by the drivers, another process has grabbed exclusive access to the
|
||||
devices, the udev rules do not apply or a superceeded by others.
|
||||
|
||||
Automatically Start OpenERP
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You must now make sure that this OpenERP install is automatically
|
||||
started after boot. There are various ways to do so, and how to do it
|
||||
depends on your particular setup. We use *systemd* on the PosBox, but
|
||||
*upstart* or *sysvinit* are other options.
|
||||
|
||||
Setup the Point of Sale
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The IP Adress field in the POS Config must be either ``127.0.0.1``
|
||||
or ``localhost``. You can also leave it empty.
|
||||
|
||||
PosBox Technical Documentation
|
||||
==============================
|
||||
|
||||
Technical Overview
|
||||
------------------
|
||||
|
||||
The PosBox Hardware
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The PosBox's Hardware is based on a model B Raspberry Pi, a popular
|
||||
open-source micro-computer. The Raspberry Pi is powered with a 2A
|
||||
micro-usb power adapter. 2A is needed to give enough power to the
|
||||
barcode scanners. And we recommend Samsung power adapters for their
|
||||
availability and reliability ( but beware of counterfeits ). The
|
||||
Raspberry is protected by a ModMyPi Raspberry Pi Case. The Software is
|
||||
installed on a 8Gb Class 10 or Higher SD Card. The SD Card's class is
|
||||
important to ensure good performances. All this hardware is easily
|
||||
available worldwide from independant vendors.
|
||||
|
||||
Compatible Peripherals
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- Printers:
|
||||
|
||||
- Epson TM-T20
|
||||
- Support for Other Esc-Pos compatible printers can be easily added
|
||||
on a case-by-case basis, please see the *Unsupported Printers*
|
||||
chapter of this manual.
|
||||
|
||||
- Barcode Scannners:
|
||||
|
||||
- Metapace S61
|
||||
- Honeywell Eclipse or Voyager 95x0 Series.
|
||||
- Most other barcode scanners should work out of the box. Some
|
||||
barcode scanners need more power than the PosBox can provide and
|
||||
must be plugged in a self-powered USB HUB. This is the case for
|
||||
Datalogic Barcode Scanners.
|
||||
|
||||
- Cash Drawers:
|
||||
|
||||
- As the cash drawers are connected to the printer, All Epson
|
||||
compatible cash drawers should work out of the Box.
|
||||
|
||||
The PosBox Software
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The PosBox runs a Raspbian Linux distribution, a Debian derivative
|
||||
optimized for the Raspberry Pi. It also runs a barebones install of
|
||||
OpenERP which provides the webserver and the drivers. The printer &
|
||||
scanner drivers are implemented as openerp modules. Those modules are
|
||||
named ``hw_proxy``, ``hw_escpos``, ``hw_scanner`` and are the only
|
||||
modules installed and running. OpenERP is only used for the framework it
|
||||
provides. No business data is processed or stored on the PosBox. The
|
||||
OpenERP install is a full bazaar clone of the ``trunk`` branch, and can
|
||||
thus be updated trough the usual means.
|
||||
|
||||
We use systemd to manage the Openerp server. Systemd makes sure OpenERP
|
||||
starts up at boot and is always up and running. Its systemd unit file is
|
||||
called ``openerp.service``, and can be found in
|
||||
``/etc/systemd/system/openerp.service``. The systemd version used is
|
||||
quite old and thus ``journalctl`` is not available. All logs can be
|
||||
found in instead ``/var/logs/syslog``
|
||||
|
||||
We removed all graphical software from the default install to reduce to
|
||||
image size but nothing prevents you from reinstalling them.
|
||||
|
||||
Accessing the PosBox
|
||||
--------------------
|
||||
|
||||
Local Access
|
||||
~~~~~~~~~~~~
|
||||
|
||||
If you plug a QWERTY USB keyboard into one of the PosBox's USB ports,
|
||||
and if you connect a computer monitor to the *HDMI* port of the PosBox,
|
||||
you can use it as a small UNIX computer and perform various
|
||||
administration tasks.
|
||||
|
||||
Once the PosBox is ready press ``ALT-F2`` to access the login prompt.
|
||||
The login is ``pi`` and the password ``admin``. The OpenERP install is
|
||||
in the ``~/openerp`` directory along with a few scripts to help with
|
||||
debugging and administration.
|
||||
|
||||
Remote Access
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
If you have the PosBox's IP address and a SSH client you can access the
|
||||
PosBox's system remotely. The login / password are ``pi``/``admin``
|
||||
|
||||
Getting Unsupported Printers to Work
|
||||
------------------------------------
|
||||
|
||||
The PosBox should be able to print to any ESC-POS printer, not just the
|
||||
Epson TM-T20. If You have such a printer, you can activate it with the
|
||||
following steps:
|
||||
|
||||
- Get local or remote access to the PosBox.
|
||||
- Plug in your printer
|
||||
- type ``lsusb`` in a prompt
|
||||
- Find your printer in the list of connected USB devices
|
||||
- Find your printer's vendor id: It consists of two hexadecimal numbers
|
||||
separated by a colon.
|
||||
- Edit ``~/openerp/addons/hw_escpos/escpos/supported_devices.py`` and
|
||||
add an entry for your printer.
|
||||
- Restart the PosBox.
|
||||
- If everything works properly you can send your printer's name and
|
||||
vendor ID to ``support@openerp.com`` and we'll add it to the list of
|
||||
supported devices.
|
||||
|
||||
Updating The PosBox Software
|
||||
----------------------------
|
||||
|
||||
The best way to update the PosBox software is to download a new version
|
||||
of the image and flash the SD-Card with it. This operation is described
|
||||
in details on the following tutorial
|
||||
``http://elinux.org/RPi_Easy_SD_Card_Setup``, just replace the standard
|
||||
raspberry pi image to the latest one found at
|
||||
``http://nightly.openerp.com/trunk/posbox/``
|
||||
|
||||
Troubleshoot
|
||||
============
|
||||
|
||||
The POS cannot connect to the PosBox.
|
||||
-------------------------------------
|
||||
|
||||
- The easiest way to make sure the PosBox is properly set-up is to turn
|
||||
it on with the printer plugged in as it will print a receipt
|
||||
indicating any error if encountered or the PosBox's IP adress in case
|
||||
of success. If no receipt is printed, check the following steps:
|
||||
- Make sure the PosBox is powered on, indicated by a brightly lid red
|
||||
status LED.
|
||||
- Make sure the PosBox is ready, this is indicated by a brightly lid
|
||||
green status LED just above the red power status LED. The PosBox
|
||||
should be ready one minute after it is powered on.
|
||||
- Make sure the PosBox is connected to the Network. This is indicated
|
||||
by a brightly lid yellow status LED.
|
||||
- Make sure the PosBox is connected to the same network as your POS
|
||||
device. Both the device and the posbox should be visible in the list
|
||||
of connected devices on your network router.
|
||||
- Make sure that your LAN is set up with DHCP, and gives ip Address in
|
||||
the range 192.168.0.X, 192.168.1.X, 10.0.0.X. If you cannot setup
|
||||
your LAN that way, you must manually set up your PosBox's ip-address.
|
||||
See the relevant paragraph in the Setup chapter of this documentation
|
||||
- If you have specified the PosBox's IP address in the config, make
|
||||
sure it correspond to the printed on the PosBox's status receipt.
|
||||
- Make sure that the POS is not loaded over HTTPS.
|
||||
- A bug in Firefox's HTTP implementation prevents the autodiscovery
|
||||
from working reliably. When using Firefox you should manually set up
|
||||
the PosBox's ip address in the POS config.
|
||||
|
||||
The Barcode Scanner is not working
|
||||
----------------------------------
|
||||
|
||||
- The PosBox needs a 2A power supply to work with some barcode
|
||||
scanners. If you are not using the provided power supply, make sure
|
||||
the one you use has enough power.
|
||||
- Some barcode scanners will need more than 2A and will not work, or
|
||||
will work unreliably, even with the provided power supply. In those
|
||||
case you can plug the barcode scanner in a self-powered USB Hub.
|
||||
- Some poorly built barcode scanners do not advertise themselves as
|
||||
barcode scanners but as a usb keyboard instead, and will not be
|
||||
recognized by the PosBox.
|
||||
- The barcode scanner must be configured in US QWERTY and emit a linefeed
|
||||
after each codebar.
|
||||
|
||||
The Barcode Scanner is not working reliably
|
||||
-------------------------------------------
|
||||
|
||||
- Make sure that no more than one device with 'Scan via Proxy' enabled
|
||||
are connected to the PosBox at the same time.
|
||||
|
||||
Printing the receipt takes too much time.
|
||||
-----------------------------------------
|
||||
|
||||
- A small delay before the first print is expected, as the PosBox will
|
||||
do some preprocessing to speed up the next printings. If you suffer
|
||||
delays afterwards it is most likely due to poor network connection
|
||||
between the POS and the PosBox.
|
||||
|
||||
Some characters are not correctly printed on the receipt.
|
||||
---------------------------------------------------------
|
||||
|
||||
- The PosBox does not support all languages and characters. It
|
||||
currently supports latin and cyrillic based scripts, with basic
|
||||
japanese support.
|
||||
|
||||
The Printer is Offline
|
||||
----------------------
|
||||
|
||||
- The PosBox only supports EPSON TM-T20 printers. Make sure the printer
|
||||
is connected, powered, has enough paper and has its lid closed, and
|
||||
does is not in an error status. If the error persists, please contact
|
||||
support.
|
||||
|
||||
The Cashdrawer does not open.
|
||||
-----------------------------
|
||||
|
||||
- The cashdrawer should be connected to the printer and should be
|
||||
activated in the POS Configuration
|
||||
|
||||
Credits
|
||||
=======
|
||||
The PosBox project was developped by Frédéric van der Essen with the
|
||||
kind help of Gary Malherbe, Fabien Meghazi, Nicolas Wisniewsky,
|
||||
Dimitri Del Marmol and Antony Lesuisse.
|
||||
|
||||
This development would not have been possible without the Indiegogo
|
||||
campaign and those who contributed to it. Special thanks goes to the
|
||||
partners who backed the campaign with founding partner bundles:
|
||||
|
||||
- Camptocamp
|
||||
- BHC
|
||||
- openBig
|
||||
- Eeezee-IT
|
||||
- Solarsis LDA
|
||||
- ACSONE
|
||||
- Vauxoo
|
||||
- Ekomurz
|
||||
- Datalp
|
||||
- Dao Systems
|
||||
- Eggs Solutions
|
||||
- OpusVL
|
||||
|
||||
And also the partners who've backed the development with the Founding
|
||||
PosBox Bundle:
|
||||
|
||||
- Willow IT
|
||||
- E\. Akhalwaya & Sons
|
||||
- Multibase
|
||||
- Mindesa
|
||||
- bpso.biz
|
||||
- Shine IT.
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -142,7 +142,7 @@ class LunchOrderLine(models.Model):
|
||||
readonly=True, store=True)
|
||||
note = fields.Text('Note')
|
||||
price = fields.Float(related='product_id.price', readonly=True, store=True,
|
||||
digits_compute=dp.get_precision('Account'))
|
||||
digits=dp.get_precision('Account'))
|
||||
state = fields.Selection([('new', 'New'),
|
||||
('confirmed', 'Received'),
|
||||
('ordered', 'Ordered'),
|
||||
@@ -192,7 +192,7 @@ class LunchProduct(models.Model):
|
||||
name = fields.Char('Product', required=True)
|
||||
category_id = fields.Many2one('lunch.product.category', 'Category', required=True)
|
||||
description = fields.Text('Description')
|
||||
price = fields.Float('Price', digits_compute=dp.get_precision('Account'))
|
||||
price = fields.Float('Price', digits=dp.get_precision('Account'))
|
||||
supplier = fields.Many2one('res.partner', 'Vendor')
|
||||
|
||||
|
||||
|
||||
@@ -118,7 +118,7 @@ class MailController(http.Controller):
|
||||
if not record_sudo:
|
||||
# record does not seem to exist -> redirect to login
|
||||
return self._redirect_to_messaging()
|
||||
record_action = record_sudo.get_access_action()[0]
|
||||
record_action = record_sudo.get_access_action()
|
||||
|
||||
# the record has an URL redirection: use it directly
|
||||
if record_action['type'] == 'ir.actions.act_url':
|
||||
|
||||
@@ -121,13 +121,15 @@
|
||||
<td style="padding:0px 10px 5px 5px;font-size: 14px;">
|
||||
${object.body | safe}
|
||||
% if ctx.get('tracking'):
|
||||
<ul>
|
||||
% for tracking in ctx['tracking']
|
||||
${tracking[0]} : ${tracking[1]} -> ${tracking[2]}
|
||||
<li>${tracking[0]} : ${tracking[1]} -> ${tracking[2]}</li>
|
||||
% endfor
|
||||
</ul>
|
||||
% endif
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div style="padding:0px; width:600px; max-width:600px; margin:0 auto; background: #FFFFFF repeat top /100%;color:#777777">
|
||||
<table cellspacing="0" cellpadding="0" style="width:600px ;text-align:justify; margin:0 auto; border-collapse:collapse; background:inherit; color:inherit">
|
||||
|
||||
@@ -6,13 +6,7 @@
|
||||
<menuitem
|
||||
name='Payments'
|
||||
id='root_payment_menu'
|
||||
parent='account.menu_finance_reports'/>
|
||||
|
||||
<!-- Payment menu in invoicing -->
|
||||
<menuitem
|
||||
name='Payments'
|
||||
id='finance_payment_menu'
|
||||
parent='account.menu_finance_reports'/>
|
||||
parent='account.menu_finance_configuration'/>
|
||||
|
||||
<!-- Payment acquirers -->
|
||||
<record id="payment_transaction_action_child" model="ir.actions.act_window">
|
||||
@@ -231,12 +225,6 @@
|
||||
parent='root_payment_menu'
|
||||
sequence='20' />
|
||||
|
||||
<menuitem
|
||||
action='action_payment_transaction'
|
||||
id='finance_pay_tx_menu'
|
||||
parent='finance_payment_menu'
|
||||
sequence='20' />
|
||||
|
||||
<!-- Payment Methods -->
|
||||
<record model='ir.ui.view' id='payment_method_tree_view'>
|
||||
<field name='name'>payment.method.tree</field>
|
||||
@@ -308,10 +296,5 @@
|
||||
id='payment_method_menu'
|
||||
parent='root_payment_menu'/>
|
||||
|
||||
<menuitem
|
||||
action='payment_method_action'
|
||||
id='finance_pay_method_menu'
|
||||
parent='finance_payment_menu' />
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
@@ -15,7 +15,7 @@ class AdyenController(http.Controller):
|
||||
_return_url = '/payment/adyen/return/'
|
||||
|
||||
@http.route([
|
||||
'/payment/adyen/return/',
|
||||
'/payment/adyen/return',
|
||||
], type='http', auth='none')
|
||||
def adyen_return(self, **post):
|
||||
_logger.info('Beginning Adyen form_feedback with post data %s', pprint.pformat(post)) # debug
|
||||
@@ -26,3 +26,17 @@ class AdyenController(http.Controller):
|
||||
custom = json.loads(post.pop('merchantReturnData', '{}'))
|
||||
return_url = custom.pop('return_url', '/')
|
||||
return werkzeug.utils.redirect(return_url)
|
||||
|
||||
@http.route([
|
||||
'/payment/adyen/notification',
|
||||
], type='http', auth='none', methods=['POST'])
|
||||
def adyen_notification(self, **post):
|
||||
tx_id = post.get('merchantReference') and request.registry['payment.transaction'].search(request.cr, SUPERUSER_ID, [('reference', 'in', [post.get('merchantReference')])], limit=1, context=request.context)
|
||||
if post.get('eventCode') in ['AUTHORISATION'] and tx_id:
|
||||
tx = request.registry['payment.transaction'].browse(request.cr, SUPERUSER_ID, tx_id, context=request.context)
|
||||
states = (post.get('merchantReference'), post.get('success'), tx.state)
|
||||
if (post.get('success') == 'true' and tx.state == 'done') or (post.get('success') == 'false' and tx.state in ['cancel', 'error']):
|
||||
_logger.info('Notification from Adyen for the reference %s: received %s, state is %s', states)
|
||||
else:
|
||||
_logger.warning('Notification from Adyen for the reference %s: received %s but state is %s', states)
|
||||
return '[accepted]'
|
||||
|
||||
@@ -74,7 +74,7 @@ class pos_config(osv.osv):
|
||||
result = dict()
|
||||
|
||||
for record in self.browse(cr, uid, ids, context=context):
|
||||
result[record.id] = record.session_ids.filtered(lambda r: r.state == 'opened').user_id.name
|
||||
result[record.id] = record.session_ids.filtered(lambda r: r.state == 'opened' and not r.rescue).user_id.name
|
||||
return result
|
||||
|
||||
_columns = {
|
||||
@@ -222,7 +222,7 @@ class pos_config(osv.osv):
|
||||
'group_by' : True,
|
||||
'pricelist_id': _default_pricelist,
|
||||
'iface_invoicing': True,
|
||||
'iface_print_auto': True,
|
||||
'iface_print_auto': False,
|
||||
'iface_print_skip_screen': True,
|
||||
'stock_location_id': _get_default_location,
|
||||
'company_id': _get_default_company,
|
||||
@@ -238,6 +238,9 @@ class pos_config(osv.osv):
|
||||
return {'value': {'stock_location_id': p_type.default_location_src_id.id}}
|
||||
return False
|
||||
|
||||
def onchange_iface_print_via_proxy(self, cr, uid, ids, print_via_proxy, context=None):
|
||||
return {'value': {'iface_print_auto': print_via_proxy}}
|
||||
|
||||
def set_active(self, cr, uid, ids, context=None):
|
||||
return self.write(cr, uid, ids, {'state' : 'active'}, context=context)
|
||||
|
||||
|
||||
@@ -699,7 +699,7 @@
|
||||
</group>
|
||||
<group string="Hardware Proxy / PosBox" >
|
||||
<field name="proxy_ip" />
|
||||
<field name="iface_print_via_proxy" string="Receipt Printer"/>
|
||||
<field name="iface_print_via_proxy" string="Receipt Printer" on_change="onchange_iface_print_via_proxy(iface_print_via_proxy)"/>
|
||||
<field name="iface_scan_via_proxy" string="Barcode Scanner"/>
|
||||
<field name="iface_electronic_scale" />
|
||||
<field name="iface_cashdrawer" />
|
||||
|
||||
@@ -11,7 +11,7 @@ class ProductTemplate(models.Model):
|
||||
uos_id = fields.Many2one('product.uom', 'Unit of Sale',
|
||||
help='Specify a unit of measure here if invoicing is made in another'
|
||||
' unit of measure than inventory. Keep empty to use the default unit of measure.')
|
||||
uos_coeff = fields.Float('Unit of Measure -> UOS Coeff', digits_compute=dp.get_precision('Product Unit of Measure'),
|
||||
uos_coeff = fields.Float('Unit of Measure -> UOS Coeff', digits=dp.get_precision('Product Unit of Measure'),
|
||||
help='Coefficient to convert default Unit of Measure to Unit of Sale'
|
||||
' uos = uom * coeff')
|
||||
|
||||
@@ -29,7 +29,7 @@ class SaleOrderLine(models.Model):
|
||||
def _compute_uos(self):
|
||||
self.product_uos_qty = self.product_uom_qty * self.product_id.uos_coeff
|
||||
|
||||
product_uos_qty = fields.Float(string='Quantity', digits_compute=dp.get_precision('Product Unit of Measure'),
|
||||
product_uos_qty = fields.Float(string='Quantity', digits=dp.get_precision('Product Unit of Measure'),
|
||||
compute='_compute_uos', inverse='_set_uos', readonly=False)
|
||||
product_uos = fields.Many2one('product.uom', string='Unit of Measure', required=True,
|
||||
related='product_id.uos_id', readonly=True)
|
||||
|
||||
@@ -102,7 +102,7 @@ class PurchaseOrder(models.Model):
|
||||
"It's used to do the matching when you receive the "
|
||||
"products as this reference is usually written on the "
|
||||
"delivery order sent by your vendor.")
|
||||
date_order = fields.Datetime('Order Date', required=True, states=READONLY_STATES, select=True, copy=False, default=fields.Date.context_today,\
|
||||
date_order = fields.Datetime('Order Date', required=True, states=READONLY_STATES, select=True, copy=False, default=fields.Datetime.now(),\
|
||||
help="Depicts the date where the Quotation should be validated and converted into a purchase order.")
|
||||
date_approve = fields.Date('Approval Date', readonly=1, select=True, copy=False)
|
||||
partner_id = fields.Many2one('res.partner', string='Vendor', required=True, states=READONLY_STATES, change_default=True, track_visibility='always')
|
||||
@@ -441,13 +441,13 @@ class PurchaseOrderLine(models.Model):
|
||||
line.qty_received = total
|
||||
|
||||
name = fields.Text(string='Description', required=True)
|
||||
product_qty = fields.Float(string='Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True, default=1.0)
|
||||
product_qty = fields.Float(string='Quantity', digits=dp.get_precision('Product Unit of Measure'), required=True, default=1.0)
|
||||
date_planned = fields.Datetime(string='Scheduled Date', required=True, select=True)
|
||||
taxes_id = fields.Many2many('account.tax', string='Taxes')
|
||||
product_uom = fields.Many2one('product.uom', string='Product Unit of Measure', required=True)
|
||||
product_id = fields.Many2one('product.product', string='Product', domain=[('purchase_ok', '=', True)], change_default=True, required=True)
|
||||
move_ids = fields.One2many('stock.move', 'purchase_line_id', string='Reservation', readonly=True, ondelete='set null', copy=False)
|
||||
price_unit = fields.Float(string='Unit Price', required=True, digits_compute=dp.get_precision('Product Price'))
|
||||
price_unit = fields.Float(string='Unit Price', required=True, digits=dp.get_precision('Product Price'))
|
||||
|
||||
price_subtotal = fields.Monetary(compute='_compute_amount', string='Subtotal', store=True)
|
||||
price_total = fields.Monetary(compute='_compute_amount', string='Total', store=True)
|
||||
|
||||
@@ -200,7 +200,7 @@
|
||||
<field name="qty_invoiced" invisible="not context.get('show_purchase', False)"/>
|
||||
<field name="product_uom" groups="product.group_uom"/>
|
||||
<field name="price_unit"/>
|
||||
<field name="taxes_id" widget="many2many_tags" domain="[('type_tax_use','=','purchase')]"/>
|
||||
<field name="taxes_id" widget="many2many_tags" domain="[('type_tax_use','=','purchase')]" context="{'default_type_tax_use': 'purchase'}"/>
|
||||
<field name="price_subtotal" widget="monetary"/>
|
||||
</tree>
|
||||
<form string="Purchase Order Line">
|
||||
|
||||
+7
-6
@@ -161,6 +161,7 @@ class SaleOrder(models.Model):
|
||||
return 'sale.mt_order_sent'
|
||||
return super(SaleOrder, self)._track_subtype(init_values)
|
||||
|
||||
@api.multi
|
||||
@api.onchange('partner_shipping_id')
|
||||
def onchange_partner_shipping_id(self):
|
||||
"""
|
||||
@@ -597,7 +598,7 @@ class SaleOrderLine(models.Model):
|
||||
('to invoice', 'To Invoice'),
|
||||
('no', 'Nothing to Invoice')
|
||||
], string='Invoice Status', compute='_compute_invoice_status', store=True, readonly=True, default='no')
|
||||
price_unit = fields.Float('Unit Price', required=True, digits_compute=dp.get_precision('Product Price'), default=0.0)
|
||||
price_unit = fields.Float('Unit Price', required=True, digits=dp.get_precision('Product Price'), default=0.0)
|
||||
|
||||
price_subtotal = fields.Monetary(compute='_compute_amount', string='Subtotal', readonly=True, store=True)
|
||||
price_tax = fields.Monetary(compute='_compute_amount', string='Taxes', readonly=True, store=True)
|
||||
@@ -606,20 +607,20 @@ class SaleOrderLine(models.Model):
|
||||
price_reduce = fields.Monetary(compute='_get_price_reduce', string='Price Reduce', readonly=True, store=True)
|
||||
tax_id = fields.Many2many('account.tax', string='Taxes')
|
||||
|
||||
discount = fields.Float(string='Discount (%)', digits_compute=dp.get_precision('Discount'), default=0.0)
|
||||
discount = fields.Float(string='Discount (%)', digits=dp.get_precision('Discount'), default=0.0)
|
||||
|
||||
product_id = fields.Many2one('product.product', string='Product', domain=[('sale_ok', '=', True)], change_default=True, ondelete='restrict', required=True)
|
||||
product_uom_qty = fields.Float(string='Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True, default=1.0)
|
||||
product_uom_qty = fields.Float(string='Quantity', digits=dp.get_precision('Product Unit of Measure'), required=True, default=1.0)
|
||||
product_uom = fields.Many2one('product.uom', string='Unit of Measure', required=True)
|
||||
|
||||
qty_delivered_updateable = fields.Boolean(compute='_compute_qty_delivered_updateable', string='Can Edit Delivered', readonly=True, default=True)
|
||||
qty_delivered = fields.Float(string='Delivered', copy=False, digits_compute=dp.get_precision('Product Unit of Measure'), default=0.0)
|
||||
qty_delivered = fields.Float(string='Delivered', copy=False, digits=dp.get_precision('Product Unit of Measure'), default=0.0)
|
||||
qty_to_invoice = fields.Float(
|
||||
compute='_get_to_invoice_qty', string='To Invoice', store=True, readonly=True,
|
||||
digits_compute=dp.get_precision('Product Unit of Measure'), default=0.0)
|
||||
digits=dp.get_precision('Product Unit of Measure'), default=0.0)
|
||||
qty_invoiced = fields.Float(
|
||||
compute='_get_invoice_qty', string='Invoiced', store=True, readonly=True,
|
||||
digits_compute=dp.get_precision('Product Unit of Measure'), default=0.0)
|
||||
digits=dp.get_precision('Product Unit of Measure'), default=0.0)
|
||||
|
||||
salesman_id = fields.Many2one(related='order_id.user_id', store=True, string='Salesperson', readonly=True)
|
||||
currency_id = fields.Many2one(related='order_id.currency_id', store=True, string='Currency', readonly=True)
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
<th>Description</th>
|
||||
<th class="text-right">Quantity</th>
|
||||
<th class="text-right">Unit Price</th>
|
||||
<th t-if="display_discount" class="text-right"><span groups="sale.group_discount_per_so_line">Disc.(%)</span></th>
|
||||
<th t-if="display_discount" class="text-right" groups="sale.group_discount_per_so_line">Disc.(%)</th>
|
||||
<th class="text-right">Taxes</th>
|
||||
<th class="text-right">Price</th>
|
||||
</tr>
|
||||
|
||||
@@ -133,8 +133,7 @@ class stock_history(osv.osv):
|
||||
serial_number
|
||||
FROM
|
||||
((SELECT
|
||||
stock_move.id::text || '-' || quant.id::text AS id,
|
||||
quant.id AS quant_id,
|
||||
stock_move.id AS id,
|
||||
stock_move.id AS move_id,
|
||||
dest_location.id AS location_id,
|
||||
dest_location.company_id AS company_id,
|
||||
@@ -148,19 +147,19 @@ class stock_history(osv.osv):
|
||||
stock_production_lot.name AS serial_number
|
||||
FROM
|
||||
stock_quant as quant
|
||||
LEFT JOIN
|
||||
JOIN
|
||||
stock_quant_move_rel ON stock_quant_move_rel.quant_id = quant.id
|
||||
LEFT JOIN
|
||||
JOIN
|
||||
stock_move ON stock_move.id = stock_quant_move_rel.move_id
|
||||
LEFT JOIN
|
||||
stock_production_lot ON stock_production_lot.id = quant.lot_id
|
||||
LEFT JOIN
|
||||
JOIN
|
||||
stock_location dest_location ON stock_move.location_dest_id = dest_location.id
|
||||
LEFT JOIN
|
||||
JOIN
|
||||
stock_location source_location ON stock_move.location_id = source_location.id
|
||||
LEFT JOIN
|
||||
JOIN
|
||||
product_product ON product_product.id = stock_move.product_id
|
||||
LEFT JOIN
|
||||
JOIN
|
||||
product_template ON product_template.id = product_product.product_tmpl_id
|
||||
WHERE quant.qty>0 AND stock_move.state = 'done' AND dest_location.usage in ('internal', 'transit')
|
||||
AND (
|
||||
@@ -168,10 +167,9 @@ class stock_history(osv.osv):
|
||||
(source_location.company_id is not null and dest_location.company_id is null) or
|
||||
source_location.company_id != dest_location.company_id or
|
||||
source_location.usage not in ('internal', 'transit'))
|
||||
) UNION
|
||||
) UNION ALL
|
||||
(SELECT
|
||||
'-' || stock_move.id::text || '-' || quant.id::text AS id,
|
||||
quant.id AS quant_id,
|
||||
(-1) * stock_move.id AS id,
|
||||
stock_move.id AS move_id,
|
||||
source_location.id AS location_id,
|
||||
source_location.company_id AS company_id,
|
||||
@@ -185,19 +183,19 @@ class stock_history(osv.osv):
|
||||
stock_production_lot.name AS serial_number
|
||||
FROM
|
||||
stock_quant as quant
|
||||
LEFT JOIN
|
||||
JOIN
|
||||
stock_quant_move_rel ON stock_quant_move_rel.quant_id = quant.id
|
||||
LEFT JOIN
|
||||
JOIN
|
||||
stock_move ON stock_move.id = stock_quant_move_rel.move_id
|
||||
LEFT JOIN
|
||||
stock_production_lot ON stock_production_lot.id = quant.lot_id
|
||||
LEFT JOIN
|
||||
JOIN
|
||||
stock_location source_location ON stock_move.location_id = source_location.id
|
||||
LEFT JOIN
|
||||
JOIN
|
||||
stock_location dest_location ON stock_move.location_dest_id = dest_location.id
|
||||
LEFT JOIN
|
||||
JOIN
|
||||
product_product ON product_product.id = stock_move.product_id
|
||||
LEFT JOIN
|
||||
JOIN
|
||||
product_template ON product_template.id = product_product.product_tmpl_id
|
||||
WHERE quant.qty>0 AND stock_move.state = 'done' AND source_location.usage in ('internal', 'transit')
|
||||
AND (
|
||||
|
||||
@@ -50,12 +50,14 @@ if(!$('.js_surveyresult').length) {
|
||||
|
||||
//initialize discreteBar Chart
|
||||
function init_bar_chart(){
|
||||
return nv.models.discreteBarChart()
|
||||
var chart = nv.models.discreteBarChart()
|
||||
.x(function(d) { return d.text; })
|
||||
.y(function(d) { return d.count; })
|
||||
.staggerLabels(true)
|
||||
.tooltips(false)
|
||||
.showValues(true);
|
||||
|
||||
chart.tooltip.enabled(false);
|
||||
return chart;
|
||||
}
|
||||
|
||||
//initialize Pie Chart
|
||||
|
||||
@@ -954,24 +954,26 @@ var X2ManyListView = ListView.extend({
|
||||
if (_.isEmpty(this.records.records)){
|
||||
return true;
|
||||
}
|
||||
var fields = this.editor.form.fields;
|
||||
var current_values = {};
|
||||
_.each(this.editor.form.fields, function(field){
|
||||
_.each(fields, function(field){
|
||||
field._inhibit_on_change_flag = true;
|
||||
field.no_rerender = true;
|
||||
current_values[field.name] = field.get('value');
|
||||
});
|
||||
var valid = _.every(this.records.records, function(record){
|
||||
_.each(self.editor.form.fields, function(field){
|
||||
field.set_value(record.attributes[field.name]);
|
||||
var valid = _.every(this.dataset.cache, function(record){
|
||||
_.each(fields, function(field){
|
||||
var value = record.values[field.name];
|
||||
field.set_value(_.isArray(value) && _.isArray(value[0]) ? [COMMANDS.delete_all()].concat(value) : value);
|
||||
});
|
||||
return _.every(self.editor.form.fields, function(field){
|
||||
return _.every(fields, function(field){
|
||||
field.process_modifiers();
|
||||
field._check_css_flags();
|
||||
return field.is_valid();
|
||||
});
|
||||
});
|
||||
_.each(this.editor.form.fields, function(field){
|
||||
field.set('value', current_values[field.name]);
|
||||
_.each(fields, function(field){
|
||||
field.set('value', current_values[field.name], {silent: true});
|
||||
field._inhibit_on_change_flag = false;
|
||||
field.no_rerender = false;
|
||||
});
|
||||
|
||||
@@ -738,9 +738,10 @@ var FieldPercentPie = common.AbstractField.extend({
|
||||
.donut(true)
|
||||
.showLegend(false)
|
||||
.showLabels(false)
|
||||
.tooltips(false)
|
||||
.color(['#7C7BAD','#DDD'])
|
||||
.donutRatio(0.62);
|
||||
|
||||
chart.tooltip.enabled(false);
|
||||
|
||||
d3.select(svg)
|
||||
.datum([{'x': 'value', 'y': value}, {'x': 'complement', 'y': 100 - value}])
|
||||
@@ -778,12 +779,13 @@ var FieldBarChart = common.AbstractField.extend({
|
||||
.width(width)
|
||||
.height(height)
|
||||
.margin({top: 0, right: 0, bottom: 0, left: 0})
|
||||
.tooltips(false)
|
||||
.showValues(false)
|
||||
.transition(350)
|
||||
.showXAxis(false)
|
||||
.showYAxis(false);
|
||||
|
||||
|
||||
chart.tooltip.enabled(false);
|
||||
|
||||
d3.select(svg)
|
||||
.datum([{key: 'values', values: value}])
|
||||
.transition()
|
||||
|
||||
@@ -405,23 +405,27 @@
|
||||
}
|
||||
.oe_overlay .oe_overlay_options {
|
||||
position: absolute;
|
||||
left: 50% !important;
|
||||
text-align: center;
|
||||
top: -20px;
|
||||
width: 100%;
|
||||
top: -35px;
|
||||
z-index: 1002;
|
||||
}
|
||||
.oe_overlay .oe_overlay_options > .btn-group {
|
||||
left: -50%;
|
||||
white-space: nowrap;
|
||||
display: -ms-flexbox;
|
||||
display: -moz-box;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
-moz-justify-content: center;
|
||||
-webkit-justify-content: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.oe_overlay .oe_overlay_options > .btn-group > a {
|
||||
.oe_overlay .oe_overlay_options > .btn-group > * {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
float: none;
|
||||
margin: 0 -3px;
|
||||
}
|
||||
.oe_overlay .oe_overlay_options > .btn-group > div {
|
||||
float: left;
|
||||
-ms-flex: 0 0 auto;
|
||||
-moz-flex: 0 0 auto;
|
||||
-webkit-box-flex: 0;
|
||||
-webkit-flex: 0 0 auto;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.oe_overlay .oe_overlay_options .btn, .oe_overlay .oe_overlay_options a {
|
||||
cursor: pointer;
|
||||
|
||||
@@ -311,20 +311,25 @@
|
||||
|
||||
.oe_overlay_options
|
||||
position: absolute
|
||||
left: 50% !important
|
||||
text-align: center
|
||||
top: -20px
|
||||
width: 100%
|
||||
top: -35px
|
||||
z-index: 1002
|
||||
> .btn-group
|
||||
left: -50%
|
||||
white-space: nowrap
|
||||
> a
|
||||
display: -ms-flexbox
|
||||
display: -moz-box
|
||||
display: -webkit-box
|
||||
display: -webkit-flex
|
||||
display: flex
|
||||
-moz-justify-content: center
|
||||
-webkit-justify-content: center
|
||||
justify-content: center
|
||||
> *
|
||||
cursor: pointer
|
||||
display: inline-block
|
||||
float: none
|
||||
margin: 0 -3px
|
||||
> div
|
||||
float: left
|
||||
-ms-flex: 0 0 auto
|
||||
-moz-flex: 0 0 auto
|
||||
-webkit-box-flex: 0
|
||||
-webkit-flex: 0 0 auto
|
||||
flex: 0 0 auto
|
||||
.btn, a
|
||||
cursor: pointer
|
||||
.dropdown
|
||||
|
||||
@@ -33,18 +33,16 @@ class view(osv.osv):
|
||||
|
||||
def _view_obj(self, cr, uid, view_id, context=None):
|
||||
if isinstance(view_id, basestring):
|
||||
try:
|
||||
return self.pool['ir.model.data'].xmlid_to_object(
|
||||
cr, uid, view_id, raise_if_not_found=True, context=context
|
||||
)
|
||||
except:
|
||||
# Try to fallback on key instead of xml_id
|
||||
if 'website_id' in context:
|
||||
domain = [('key', '=', view_id), '|', ('website_id', '=', False), ('website_id', '=', context.get('website_id'))]
|
||||
rec_id = self.search(cr, uid, domain, order='website_id', context=context)
|
||||
else:
|
||||
rec_id = self.search(cr, uid, [('key', '=', view_id)], context=context)
|
||||
if rec_id:
|
||||
_logger.info("Could not find view with `xml_id' '%s', fallback on `key'" % (view_id))
|
||||
return self.browse(cr, uid, rec_id, context=context)[0]
|
||||
else:
|
||||
raise
|
||||
if rec_id:
|
||||
return self.browse(cr, uid, rec_id, context=context)[0]
|
||||
else:
|
||||
return self.pool['ir.model.data'].xmlid_to_object(
|
||||
cr, uid, view_id, raise_if_not_found=True, context=context)
|
||||
elif isinstance(view_id, (int, long)):
|
||||
return self.browse(cr, uid, view_id, context=context)
|
||||
|
||||
|
||||
@@ -228,7 +228,7 @@ options.registry.gallery = options.Class.extend({
|
||||
var $container = this.$target.find(".container:first");
|
||||
var editor = new widget.MediaDialog(this.$target.closest('.o_editable'), null, {select_images: true});
|
||||
editor.appendTo(document.body);
|
||||
var index = Math.max(_.map(this.$target.find("img").get(), function (img) { return img.dataset.index | 0; })) + 1;
|
||||
var index = Math.max(0, _.max(_.map(this.$target.find("img").get(), function (img) { return img.dataset.index | 0; })) + 1);
|
||||
editor.on('saved', this, function (attachments) {
|
||||
for (var i = 0 ; i < attachments.length; i++) {
|
||||
var img = $('<img class="img img-responsive mb8 mt8"/>')
|
||||
@@ -335,7 +335,6 @@ options.registry.gallery_img = options.Class.extend({
|
||||
if (type !== "click") return;
|
||||
|
||||
var $parent = this.$target.closest("section");
|
||||
this.buildingBlock.create_overlay($parent);
|
||||
var editor = $parent.data('snippet-editor').styles.gallery;
|
||||
var imgs = $parent.find('img').get();
|
||||
imgs.sort(function (a,b) { return $(a).data('index')-$(b).data('index'); });
|
||||
|
||||
@@ -68,8 +68,8 @@
|
||||
</a>
|
||||
</li>
|
||||
<li t-if="submenu.child_id" t-attf-class="dropdown #{
|
||||
(submenu.url and submenu.url != '/' and any([request.httprequest.path == child.url.replace('/page/website.', '/page/') for child in submenu.child_id]) or
|
||||
request.httprequest.path == submenu.url.replace('/page/website.', '/page/')) and 'active'
|
||||
(submenu.url and submenu.url != '/' and any([request.httprequest.path == child.url.replace('/page/website.', '/page/') for child in submenu.child_id if child.url]) or
|
||||
(submenu.url and request.httprequest.path == submenu.url.replace('/page/website.', '/page/'))) and 'active'
|
||||
}">
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<span t-field="submenu.name"/> <span class="caret" t-ignore="true"></span>
|
||||
|
||||
@@ -157,6 +157,7 @@ class WebsiteBlog(http.Controller):
|
||||
values = {
|
||||
'blog': blog,
|
||||
'blogs': blogs,
|
||||
'main_object': blog,
|
||||
'tags': all_tags,
|
||||
'active_tag_ids': active_tag_ids,
|
||||
'tags_list' : tags_list,
|
||||
|
||||
@@ -245,10 +245,10 @@ class BlogPost(osv.Model):
|
||||
self._check_for_publication(cr, uid, ids, vals, context=context)
|
||||
return result
|
||||
|
||||
def get_access_action(self, cr, uid, id, context=None):
|
||||
def get_access_action(self, cr, uid, ids, context=None):
|
||||
""" Override method that generated the link to access the document. Instead
|
||||
of the classic form view, redirect to the post on the website directly """
|
||||
post = self.browse(cr, uid, id, context=context)
|
||||
post = self.browse(cr, uid, ids[0], context=context)
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'url': '/blog/%s/post/%s' % (post.blog_id.id, post.id),
|
||||
|
||||
@@ -704,10 +704,11 @@ class Post(models.Model):
|
||||
self._cr.execute("""UPDATE forum_post SET views = views+1 WHERE id IN %s""", (self._ids,))
|
||||
return True
|
||||
|
||||
@api.one
|
||||
@api.multi
|
||||
def get_access_action(self):
|
||||
""" Override method that generated the link to access the document. Instead
|
||||
of the classic form view, redirect to the post on the website directly """
|
||||
self.ensure_one()
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'url': '/forum/%s/question/%s' % (self.forum_id.id, self.id),
|
||||
|
||||
@@ -7,10 +7,13 @@ from openerp import api, models
|
||||
class sale_order(models.Model):
|
||||
_inherit = 'sale.order'
|
||||
|
||||
@api.one
|
||||
@api.multi
|
||||
def get_access_action(self):
|
||||
""" Override method that generated the link to access the document. Instead
|
||||
of the classic form view, redirect to the online quote if exists. """
|
||||
self.ensure_one()
|
||||
if self.state in ['draft', 'cancel']:
|
||||
return super(sale_order, self).get_access_action()
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'url': '/my/orders/%s' % self.id,
|
||||
|
||||
@@ -8,10 +8,11 @@ class Issue(models.Model):
|
||||
_name = "project.issue"
|
||||
_inherit = ['project.issue']
|
||||
|
||||
@api.one
|
||||
@api.multi
|
||||
def get_access_action(self):
|
||||
""" Override method that generated the link to access the document. Instead
|
||||
of the classic form view, redirect to the post on the website directly """
|
||||
self.ensure_one()
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'url': '/my/issues/%s' % self.id,
|
||||
|
||||
@@ -196,6 +196,12 @@ class sale_quote(http.Controller):
|
||||
'callback_eval': "self.env['sale.order']._confirm_online_quote(self.sale_order_id.id, self)"
|
||||
}, context=context)
|
||||
tx = transaction_obj.browse(cr, SUPERUSER_ID, tx_id, context=context)
|
||||
# update quotation
|
||||
request.registry['sale.order'].write(
|
||||
cr, SUPERUSER_ID, [order.id], {
|
||||
'payment_acquirer_id': acquirer_id,
|
||||
'payment_tx_id': tx_id
|
||||
}, context=context)
|
||||
|
||||
# confirm the quotation
|
||||
if tx.acquirer_id.auto_confirm == 'at_pay_now':
|
||||
|
||||
@@ -253,17 +253,17 @@ class sale_order(osv.osv):
|
||||
products += line.product_id.product_tmpl_id.recommended_products(context=context)
|
||||
return products
|
||||
|
||||
def get_access_action(self, cr, uid, id, context=None):
|
||||
def get_access_action(self, cr, uid, ids, context=None):
|
||||
""" Override method that generated the link to access the document. Instead
|
||||
of the classic form view, redirect to the online quote if exists. """
|
||||
quote = self.browse(cr, uid, id, context=context)
|
||||
quote = self.browse(cr, uid, ids[0], context=context)
|
||||
if not quote.template_id:
|
||||
return super(sale_order, self).get_access_action(cr, uid, id, context=context)
|
||||
return super(sale_order, self).get_access_action(cr, uid, ids, context=context)
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'url': '/quote/%s' % id,
|
||||
'url': '/quote/%s' % quote.id,
|
||||
'target': 'self',
|
||||
'res_id': id,
|
||||
'res_id': quote.id,
|
||||
}
|
||||
|
||||
def _confirm_online_quote(self, cr, uid, order_id, tx, context=None):
|
||||
|
||||
@@ -772,15 +772,14 @@
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-center" id="td-price" name="price">
|
||||
<del t-attf-class="text-danger #{compute_currency(line.product_id.lst_price) - line.price_reduce < 1 and 'hidden' or ''}" style="text-decoration: line-through; white-space: nowrap;"
|
||||
<del t-attf-class="text-danger #{'' if (compute_currency(line.product_id.lst_price) - line.price_unit) > 0.1 else 'hidden'}" style="text-decoration: line-through; white-space: nowrap;"
|
||||
t-field="line.product_id.lst_price" t-field-options='{
|
||||
"widget": "monetary",
|
||||
"from_currency": "line.company_id.currency_id",
|
||||
"display_currency": "website.currency_id"
|
||||
}'/>
|
||||
<span t-field="line.price_reduce" style="white-space: nowrap;" t-field-options='{
|
||||
<span t-field="line.price_unit" style="white-space: nowrap;" t-field-options='{
|
||||
"widget": "monetary",
|
||||
"from_currency": "website_sale_order.pricelist_id.currency_id",
|
||||
"display_currency": "website.currency_id"
|
||||
}'/>
|
||||
</td>
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
<span class="badge" t-field="delivery.price"
|
||||
t-field-options='{
|
||||
"widget": "monetary",
|
||||
"from_currency": "website.currency_id",
|
||||
"from_currency": "delivery.product_id.company_id.currency_id",
|
||||
"display_currency": "website_sale_order.currency_id"
|
||||
}'/>
|
||||
<div t-field="delivery.website_description" class="text-muted"/>
|
||||
|
||||
@@ -149,7 +149,7 @@ class ir_attachment(osv.osv):
|
||||
r = os.path.getsize(full_path)
|
||||
else:
|
||||
r = open(full_path,'rb').read().encode('base64')
|
||||
except IOError:
|
||||
except (IOError, OSError):
|
||||
_logger.info("_read_file reading %s", full_path, exc_info=True)
|
||||
return r
|
||||
|
||||
|
||||
@@ -212,6 +212,11 @@ class lang(osv.osv):
|
||||
if isinstance(ids, (int, long)):
|
||||
ids = [ids]
|
||||
|
||||
if 'code' in vals:
|
||||
for rec in self.browse(cr, uid, ids, context):
|
||||
if rec.code != vals['code']:
|
||||
raise UserError(_("Language code cannot be modified."))
|
||||
|
||||
if vals.get('active') == False:
|
||||
users = self.pool.get('res.users')
|
||||
for current_id in ids:
|
||||
|
||||
@@ -263,7 +263,20 @@ class TestAPI(common.TransactionCase):
|
||||
@mute_logger('openerp.models')
|
||||
def test_60_cache(self):
|
||||
""" Check the record cache behavior """
|
||||
partners = self.env['res.partner'].search([('child_ids', '!=', False)])
|
||||
Partners = self.env['res.partner']
|
||||
pids = []
|
||||
data = {
|
||||
'partner One': ['Partner One - One', 'Partner One - Two'],
|
||||
'Partner Two': ['Partner Two - One'],
|
||||
'Partner Three': ['Partner Three - One'],
|
||||
}
|
||||
for p in data:
|
||||
pids.append(Partners.create({
|
||||
'name': p,
|
||||
'child_ids': [(0, 0, {'name': c}) for c in data[p]],
|
||||
}).id)
|
||||
|
||||
partners = Partners.search([('id', 'in', pids)])
|
||||
partner1, partner2 = partners[0], partners[1]
|
||||
children1, children2 = partner1.child_ids, partner2.child_ids
|
||||
self.assertTrue(children1)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
|
||||
access_test_inherit_mother,access_test_inherit_mother,model_test_inherit_mother,,1,1,1,1
|
||||
access_test_inherit_daughter,access_test_inherit_daughter,model_test_inherit_daughter,,1,1,1,1
|
||||
access_test_inherit_property,access_test_inherit_property,model_test_inherit_property,,1,1,1,1
|
||||
|
||||
|
@@ -78,3 +78,35 @@ class res_partner(models.Model):
|
||||
|
||||
# define a one2many field based on the inherited field partner_id
|
||||
daughter_ids = fields.One2many('test.inherit.daughter', 'partner_id')
|
||||
|
||||
|
||||
# Check the overriding of property fields by non-property fields.
|
||||
# Contribution by Adrien Peiffer (ACSONE).
|
||||
class test_inherit_property(models.Model):
|
||||
_name = 'test.inherit.property'
|
||||
|
||||
_columns = {
|
||||
'name': osv.fields.char('Name', required=True),
|
||||
'property_foo': osv.fields.property(string='Foo', type='integer'),
|
||||
'property_bar': osv.fields.property(string='Bar', type='integer'),
|
||||
}
|
||||
|
||||
class test_inherit_property(models.Model):
|
||||
_inherit = 'test.inherit.property'
|
||||
|
||||
@api.multi
|
||||
def _get_foo(self, name, arg):
|
||||
return dict.fromkeys(self.ids, 42)
|
||||
|
||||
_columns = {
|
||||
# override property_foo with an old-api function field
|
||||
'property_foo': osv.fields.function(_get_foo, type='integer', string='Foo'),
|
||||
}
|
||||
|
||||
# override property_bar with a new-api computed field
|
||||
property_bar = fields.Integer(compute='_compute_bar', company_dependent=False)
|
||||
|
||||
@api.multi
|
||||
def _compute_bar(self):
|
||||
for record in self:
|
||||
record.property_bar = 42
|
||||
|
||||
@@ -76,3 +76,20 @@ class test_inherits(common.TransactionCase):
|
||||
# search the partner from the daughter record
|
||||
partners = self.env['res.partner'].search([('daughter_ids', 'in', daughter.ids)])
|
||||
self.assertIn(partner_demo, partners)
|
||||
|
||||
|
||||
class test_override_property(common.TransactionCase):
|
||||
|
||||
def test_override_with_function_field(self):
|
||||
""" test overriding a property field by a function field """
|
||||
record = self.env['test.inherit.property'].create({'name': "Stuff"})
|
||||
# record.property_foo is not a property field
|
||||
self.assertEqual(record.property_foo, 42)
|
||||
self.assertFalse(type(record).property_foo.company_dependent)
|
||||
|
||||
def test_override_with_computed_field(self):
|
||||
""" test overriding a property field by a computed field """
|
||||
record = self.env['test.inherit.property'].create({'name': "Stuff"})
|
||||
# record.property_bar is not a property field
|
||||
self.assertEqual(record.property_bar, 42)
|
||||
self.assertFalse(type(record).property_bar.company_dependent)
|
||||
|
||||
+2
-2
@@ -1607,14 +1607,14 @@ class BaseModel(object):
|
||||
'context': context,
|
||||
}
|
||||
|
||||
def get_access_action(self, cr, uid, id, context=None):
|
||||
def get_access_action(self, cr, uid, ids, context=None):
|
||||
""" Return an action to open the document. This method is meant to be
|
||||
overridden in addons that want to give specific access to the document.
|
||||
By default it opens the formview of the document.
|
||||
|
||||
:param int id: id of the document to open
|
||||
"""
|
||||
return self.get_formview_action(cr, uid, id, context=context)
|
||||
return self.get_formview_action(cr, uid, ids[0], context=context)
|
||||
|
||||
def _view_look_dom_arch(self, cr, uid, node, view_id, context=None):
|
||||
return self.pool['ir.ui.view'].postprocess_and_fields(
|
||||
|
||||
@@ -1430,6 +1430,7 @@ class function(_column):
|
||||
def to_field_args(self):
|
||||
args = super(function, self).to_field_args()
|
||||
args['store'] = bool(self.store)
|
||||
args['company_dependent'] = False
|
||||
if self._type in ('float',):
|
||||
args['digits'] = self._digits_compute or self._digits
|
||||
elif self._type in ('binary',):
|
||||
|
||||
@@ -466,7 +466,7 @@ def html_keep_url(text):
|
||||
""" Transform the url into clickable link with <a/> tag """
|
||||
idx = 0
|
||||
final = ''
|
||||
link_tags = re.compile(r'(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?')
|
||||
link_tags = re.compile(r"""(?<!["'])((ftp|http|https):\/\/(\w+:{0,1}\w*@)?([^\s"':]+)(:[0-9]+)?(\/|\/([^\s"']))?)(?![^\s"']*["'])""")
|
||||
for item in re.finditer(link_tags, text):
|
||||
final += text[idx:item.start()]
|
||||
final += '<a href="%s" target="_blank">%s</a>' % (item.group(0), item.group(0))
|
||||
|
||||
@@ -52,7 +52,12 @@ class WorkflowInstance(object):
|
||||
cr = self.session.cr
|
||||
cr.execute("select * from wkf_workitem where inst_id=%s", (self.instance['id'],))
|
||||
stack = []
|
||||
for work_item_values in cr.dictfetchall():
|
||||
for i, work_item_values in enumerate(cr.dictfetchall()):
|
||||
if i > 0:
|
||||
# test if previous workitem has already processed this one
|
||||
cr.execute("select id from wkf_workitem where id=%s", (work_item_values['id'],))
|
||||
if not cr.fetchone():
|
||||
continue
|
||||
wi = WorkflowItem(self.session, self.record, work_item_values)
|
||||
wi.process(signal=signal, force_running=force_running, stack=stack)
|
||||
# An action is returned
|
||||
|
||||
Reference in New Issue
Block a user