Browse Source

[ADD] new module 'pos_order_load';

pull/42/head
Sylvain LE GAL 9 years ago
parent
commit
9e49d2ca07
  1. 38
      pos_order_load/README.rst
  2. 2
      pos_order_load/__init__.py
  3. 38
      pos_order_load/__openerp__.py
  4. 145
      pos_order_load/i18n/fr.po
  5. 2
      pos_order_load/models/__init__.py
  6. 97
      pos_order_load/models/pos_order.py
  7. 54
      pos_order_load/static/src/css/pos_order_load.css
  8. 363
      pos_order_load/static/src/js/pos_order_load.js
  9. 76
      pos_order_load/static/src/xml/pos_order_load.xml
  10. 18
      pos_order_load/view/pos_order_load.xml

38
pos_order_load/README.rst

@ -0,0 +1,38 @@
POS Order Load and Save
=======================
This module allows to load existing POS order.
In this version, when the loaded order
is validated in the POS, a new one is created.
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/pos/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed feedback
`here <https://github.com/OCA/pos/issues/new?body=module:%20pos_order_load%0Aversion:%208.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Credits
=======
Contributors
------------
* Sylvain Calador <sylvain.calador@akretion.com>
* Sylvain Le Gal (https://twitter.com/legalsylvain)
Maintainer
----------
.. image:: http://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: http://odoo-community.org
This module is maintained by the OCA.
OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.
To contribute to this module, please visit http://odoo-community.org.

2
pos_order_load/__init__.py

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
import models

38
pos_order_load/__openerp__.py

@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2014-TODAY Akretion (<http://www.akretion.com>).
# Copyright (C) 2014 Sylvain Calador (sylvain.calador@akretion.com).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
{
'name': 'POS Order Load and Save',
'version': '0.2',
'author': 'Akretion,GRAP,Odoo Community Association (OCA)',
'category': 'Point Of Sale',
'depends': [
'point_of_sale',
],
'website': 'https://www.akretion.com',
'data': [
'view/pos_order_load.xml',
],
'qweb': [
'static/src/xml/pos_order_load.xml',
],
}

145
pos_order_load/i18n/fr.po

@ -0,0 +1,145 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * pos_order_load
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 8.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-07-27 17:43+0000\n"
"PO-Revision-Date: 2015-07-27 17:43+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: pos_order_load
#. openerp-web
#: code:addons/pos_order_load/static/src/js/pos_order_load.js:291
#: code:addons/pos_order_load/static/src/js/pos_order_load.js:317
#, python-format
msgid "Can not execute this action because the POS is currently offline"
msgstr "Impossible d'exécuter cette action car le Point de vente est actuellement hors ligne"
#. module: pos_order_load
#. openerp-web
#: code:addons/pos_order_load/static/src/js/pos_order_load.js:182
#, python-format
msgid "Can not load the Selected Order because the POS is currently offline"
msgstr "Impossible de charger la vente sélectionnée car le Point de vente est actuellement hors ligne"
#. module: pos_order_load
#. openerp-web
#: code:addons/pos_order_load/static/src/xml/pos_order_load.xml:28
#, python-format
msgid "Cancel"
msgstr "Annuler"
#. module: pos_order_load
#. openerp-web
#: code:addons/pos_order_load/static/src/js/pos_order_load.js:181
#: code:addons/pos_order_load/static/src/js/pos_order_load.js:290
#: code:addons/pos_order_load/static/src/js/pos_order_load.js:316
#, python-format
msgid "Connection error"
msgstr "Erreur de connection"
#. module: pos_order_load
#. openerp-web
#: code:addons/pos_order_load/static/src/xml/pos_order_load.xml:60
#, python-format
msgid "Customer"
msgstr "Client"
#. module: pos_order_load
#. openerp-web
#: code:addons/pos_order_load/static/src/xml/pos_order_load.xml:5
#, python-format
msgid "Load Draft Order"
msgstr "Charger la vente en brouillon"
#. module: pos_order_load
#. openerp-web
#: code:addons/pos_order_load/static/src/xml/pos_order_load.xml:59
#, python-format
msgid "Order"
msgstr "Vente"
#. module: pos_order_load
#: model:ir.model,name:pos_order_load.model_pos_order
msgid "Point of Sale"
msgstr "Point de Vente"
#. module: pos_order_load
#. openerp-web
#: code:addons/pos_order_load/static/src/xml/pos_order_load.xml:10
#, python-format
msgid "Save Current Order"
msgstr "Sauvegarder la vente"
#. module: pos_order_load
#. openerp-web
#: code:addons/pos_order_load/static/src/js/pos_order_load.js:71
#, python-format
msgid "Save The current Order ?"
msgstr "Sauvegarder la vente en cours ?"
#. module: pos_order_load
#. openerp-web
#: code:addons/pos_order_load/static/src/xml/pos_order_load.xml:36
#, python-format
msgid "Search Orders"
msgstr "Chercher des ventes"
#. module: pos_order_load
#. openerp-web
#: code:addons/pos_order_load/static/src/xml/pos_order_load.xml:40
#, python-format
msgid "Select Order"
msgstr "Choisir une vente"
#. module: pos_order_load
#. openerp-web
#: code:addons/pos_order_load/static/src/js/pos_order_load.js:72
#, python-format
msgid "This operation will save the current order in a draft state. You'll have to mark it as paid after."
msgstr "Cette opération va sauvegarder la vente en cours à l'état de brouillon. Vous devrez la marquer comme payée ultérieurement"
#. module: pos_order_load
#. openerp-web
#: code:addons/pos_order_load/static/src/xml/pos_order_load.xml:61
#, python-format
msgid "Total Amount"
msgstr "Montant Total"
#. module: pos_order_load
#. openerp-web
#: code:addons/pos_order_load/static/src/js/pos_order_load.js:269
#, python-format
msgid "Unable to load some order lines because the products are not available in the POS cache.\n"
"\n"
"Please check that lines :\n"
"\n"
" * "
msgstr "Impossible de charger certaines lignes de ventes parce que les produits ne sont pas disponibles dans le cache du point de vente.\n"
"\n"
"Veuillez vérifier ces lignes :\n"
"\n"
" * "
#. module: pos_order_load
#. openerp-web
#: code:addons/pos_order_load/static/src/js/pos_order_load.js:268
#, python-format
msgid "Unknown Products"
msgstr "Produits Iconnu"
#. module: pos_order_load
#. openerp-web
#: code:addons/pos_order_load/static/src/xml/pos_order_load.xml:32
#, python-format
msgid "Validate"
msgstr "Valider"

2
pos_order_load/models/__init__.py

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
import pos_order

97
pos_order_load/models/pos_order.py

@ -0,0 +1,97 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2014 Akretion (<http://www.akretion.com>).
# @author Sylvain LE GAL (https://twitter.com/legalsylvain)
# @author Sylvain Calador (sylvain.calador@akretion.com).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp import models, api
class PosOrder(models.Model):
_inherit = 'pos.order'
# Overload Section
@api.model
def _order_fields(self, ui_order):
res = super(PosOrder, self)._order_fields(ui_order)
if 'order_id' in ui_order:
res['order_id'] = ui_order['order_id']
return res
@api.model
def create_from_ui(self, orders):
"""
Remove from the 'orders' list all orders where amount_return is < 0
(because that means they are not paid, but just in draft state).
* call a specific function for the draft orders list
* call the parent create_from_ui() for the remaining orders"""
draft_orders = []
for tmp_order in orders:
if tmp_order['data']['amount_return'] < 0\
and abs(tmp_order['data']['amount_return']) > 0.000001:
draft_orders.append(tmp_order)
orders.remove(tmp_order)
# Save Draft Orders
self._create_draft_order_from_ui(draft_orders)
# Save Paid Orders
return super(PosOrder, self).create_from_ui(orders)
# Custom Section
@api.model
def search_read_orders(self, query):
condition = [
('state', '=', 'draft'),
('statement_ids', '=', False),
'|',
('name', 'ilike', query),
('partner_id', 'ilike', query)
]
fields = ['name', 'partner_id', 'amount_total']
return self.search_read(condition, fields, limit=10)
@api.one
def load_order(self):
condition = [('order_id', '=', self.id)]
fields = ['product_id', 'price_unit', 'qty', 'discount']
orderlines = self.lines.search_read(condition, fields)
return {
'id': self.id,
'name': self.pos_reference,
'partner_id': self.partner_id and self.partner_id.id or False,
'orderlines': orderlines
}
@api.model
def _create_draft_order_from_ui(self, orders):
for order_tmp in orders:
order_data = order_tmp['data']
statements_data = order_data['statement_ids']
order_data.pop('statement_ids')
# create Order
order = self.create(self._order_fields(order_data))
# Create payment
for statement_data in statements_data:
self.add_payment(
order.id, self._payment_fields(statement_data[2]))

54
pos_order_load/static/src/css/pos_order_load.css

@ -0,0 +1,54 @@
.screen .top-content .button.validate {
left: 0px;
margin-left: 150px;
}
.pos .orderlist-screen .order-list{
font-size: 16px;
width: 100%;
line-height: 40px;
}
.pos .orderlist-screen .order-list th,
.pos .orderlist-screen .order-list td {
padding: 0px 8px;
}
.pos .orderlist-screen .order-list tr{
transition: all 150ms linear;
background: rgb(230,230,230);
cursor: pointer;
}
.pos .orderlist-screen .order-list thead > tr,
.pos .orderlist-screen .order-list tr:nth-child(even) {
background: rgb(247,247,247);
}
.pos .orderlist-screen .order-list tr.highlight{
transition: all 150ms linear;
background: rgb(110,200,155) !important;
color: white;
}
.pos .orderlist-screen .order-list tr.lowlight{
transition: all 150ms linear;
background: rgb(216, 238, 227);
}
.pos .orderlist-screen .order-list tr.lowlight:nth-child(even){
transition: all 150ms linear;
background: rgb(227, 246, 237);
}
.pos .orderlist-screen .order-details{
padding: 16px;
border-bottom: solid 5px rgb(110,200,155);
}
.pos .orderlist-screen .searchbox{
right: auto;
margin-left: -90px;
margin-top:8px;
left: 50%;
}
.pos .orderlist-screen .searchbox input{
width: 120px;
}
.pos .order-line .total {
text-align: right;
}

363
pos_order_load/static/src/js/pos_order_load.js

@ -0,0 +1,363 @@
/******************************************************************************
* Point Of Sale - Product Template module for Odoo
* Copyright (C) 2014-Today Akretion (http://www.akretion.com)
* @author Sylvain Calador (sylvain.calador@akretion.com)
* @author Sylvain Le Gal (https://twitter.com/legalsylvain)
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*****************************************************************************/
openerp.pos_order_load = function(instance, local) {
module = instance.point_of_sale;
var QWeb = instance.web.qweb;
var _t = instance.web._t;
var round_pr = instance.web.round_precision;
/*************************************************************************
Extend Model Order:
* Add getter and setter function for field 'order_id';
*/
module.Order = module.Order.extend({
set_order_id: function(id) {
this.set({
order_id: id,
});
},
get_order_id: function() {
return this.get('order_id');
},
});
/*************************************************************************
New Widget LoadButtonWidget:
* On click, display a new screen to select draft orders;
*/
module.LoadButtonWidget = module.PosBaseWidget.extend({
template: 'LoadButtonWidget',
renderElement: function() {
var self = this;
this._super();
this.$el.click(function(){
var ss = self.pos.pos_widget.screen_selector;
ss.set_current_screen('orderlist');
});
},
});
/*************************************************************************
New Widget SaveButtonWidget:
* On click, backup the current draft order;
*/
module.SaveButtonWidget = module.PosBaseWidget.extend({
template: 'SaveButtonWidget',
renderElement: function() {
var self = this;
this._super();
this.$el.click(function(){
self.pos.pos_widget.screen_selector.show_popup('confirm',{
message: _t('Save The current Order ?'),
comment: _t('This operation will save the current order in a draft state. You\'ll have to mark it as paid after.'),
confirm: function(){
var currentOrder = this.pos.get('selectedOrder');
this.pos.push_order(currentOrder);
self.pos.get('selectedOrder').destroy();
},
});
});
},
});
/*************************************************************************
Extend PosWidget:
* Create new screen;
* Add load and save button;
*/
module.PosWidget = module.PosWidget.extend({
build_widgets: function() {
this._super();
// New Screen to select Draft Orders
this.orderlist_screen = new module.OrderListScreenWidget(this, {});
this.orderlist_screen.appendTo(this.$('.screens'));
this.orderlist_screen.hide();
this.screen_selector.screen_set['orderlist'] =
this.orderlist_screen;
// Add buttons
this.load_button = new module.LoadButtonWidget(this,{});
this.load_button.appendTo(this.pos_widget.$('li.orderline.empty'));
this.save_button = new module.SaveButtonWidget(this,{});
},
});
/*************************************************************************
Extend OrderWidget:
*/
module.OrderWidget = module.OrderWidget.extend({
renderElement: function(scrollbottom){
this._super(scrollbottom);
if (this.pos_widget.load_button) {
this.pos_widget.load_button.appendTo(
this.pos_widget.$('li.orderline.empty')
);
}
if (this.pos_widget.save_button && (this.pos.get('selectedOrder').get('orderLines').length > 0)) {
this.pos_widget.save_button.appendTo(
this.pos_widget.$('div.summary')
);
}
}
});
/*************************************************************************
New ScreenWidget OrderListScreenWidget:
* On show, display all draft orders;
* on click on an order, display the content;
* on click on 'validate', allow to use this POS Order;
* on click on 'cancel', display the preview screen;
*/
module.OrderListScreenWidget = module.ScreenWidget.extend({
template: 'OrderListScreenWidget',
show_leftpane: true,
model: 'pos.order',
current_order_id: 0,
init: function(parent, options){
this._super(parent, options);
},
reset_order: function(order) {
order.set_client(undefined);
order.set_order_id(undefined);
order.get('orderLines').reset();
return order;
},
start: function() {
var self = this;
this._super();
this.$el.find('span.button.back').click(function(){
order = self.pos.get('selectedOrder');
self.reset_order(order);
self.pos_widget.order_widget.change_selected_order();
var ss = self.pos.pos_widget.screen_selector;
ss.set_current_screen('products');
});
this.$el.find('span.button.validate').click(function(){
var orderModel = new instance.web.Model('pos.order');
return orderModel.call('unlink', [[self.current_order_id]])
.then(function (result) {
var ss = self.pos.pos_widget.screen_selector;
ss.set_current_screen('products');
}).fail(function (error, event){
if (parseInt(error.code) === 200) {
// Business Logic Error, not a connection problem
self.pos_widget.screen_selector.show_popup(
'error-traceback', {
message: error.data.message,
comment: error.data.debug
});
}
else{
self.pos_widget.screen_selector.show_popup('error',{
message: _t('Connection error'),
comment: _t('Can not load the Selected Order because the POS is currently offline'),
});
}
event.preventDefault();
});
});
var search_timeout = null;
this.$('.searchbox input').on('keyup',function(event){
clearTimeout(search_timeout);
var query = this.value;
search_timeout = setTimeout(function(){
self.perform_search(query);
},70);
});
this.$('.searchbox .search-clear').click(function(){
self.clear_search();
});
},
// to override if necessary
add_product_attribute: function(product, key, orderline){
return product;
},
load_order_fields: function(order, fields) {
order.set_order_id(fields.id);
var partner = this.pos.db.get_partner_by_id(
fields.partner_id);
order.set_client(partner || undefined);
return order;
},
prepare_orderline_options: function(orderline) {
return {
quantity: orderline.qty,
price: orderline.price_unit,
discount: orderline.discount,
};
},
load_order: function(order_id) {
var self = this;
var orderModel = new instance.web.Model(this.model);
return orderModel.call('load_order', [order_id])
.then(function (result) {
var order = self.pos.get('selectedOrder');
var result = result[0];
order = self.load_order_fields(order, result);
order.get('orderLines').reset();
var orderlines = result.orderlines || [];
var unknown_products = [];
for (var i=0, len=orderlines.length; i<len; i++) {
var orderline = orderlines[i];
var product_id = orderline.product_id[0];
var product_name = orderline.product_id[1];
var product = self.pos.db.get_product_by_id(product_id);
if (_.isUndefined(product)) {
unknown_products.push(product_name);
continue;
}
for (key in orderline) {
if (!key.indexOf('product__')) {
product = self.add_product_attribute(
product, key, orderline
);
}
}
order.addProduct(product,
self.prepare_orderline_options(orderline)
);
last_orderline = order.getLastOrderline();
last_orderline = jQuery.extend(last_orderline, orderline);
}
// Forbid POS Order loading if some products are unknown
if (unknown_products.length > 0){
self.pos_widget.screen_selector.show_popup(
'error-traceback', {
message: _t('Unknown Products'),
comment: _t('Unable to load some order lines because the ' +
'products are not available in the POS cache.\n\n' +
'Please check that lines :\n\n * ') + unknown_products.join("; \n *")
});
self.$el.find('span.button.validate').hide();
}
else{
self.$el.find('span.button.validate').show();
}
}).fail(function (error, event){
if (parseInt(error.code) === 200) {
// Business Logic Error, not a connection problem
self.pos_widget.screen_selector.show_popup(
'error-traceback', {
message: error.data.message,
comment: error.data.debug
});
}
else{
self.pos_widget.screen_selector.show_popup('error',{
message: _t('Connection error'),
comment: _t('Can not execute this action because the POS is currently offline'),
});
}
event.preventDefault();
});
},
load_orders: function(query) {
var self = this;
var orderModel = new instance.web.Model(this.model);
return orderModel.call('search_read_orders', [query || ''])
.then(function (result) {
self.render_list(result);
}).fail(function (error, event){
if (parseInt(error.code) === 200) {
// Business Logic Error, not a connection problem
self.pos_widget.screen_selector.show_popup(
'error-traceback', {
message: error.data.message,
comment: error.data.debug
}
);
}
else{
self.pos_widget.screen_selector.show_popup('error',{
message: _t('Connection error'),
comment: _t('Can not execute this action because the POS is currently offline'),
});
}
event.preventDefault();
});
},
show: function() {
this._super();
var ss = this.pos.pos_widget.screen_selector;
if (ss.get_current_screen() == 'orderlist') {
this.load_orders();
}
},
render_list: function(orders){
var self = this;
var contents = this.$el[0].querySelector('.order-list-contents');
contents.innerHTML = "";
for (var i = 0, len = orders.length; i < len; i++){
var order = orders[i];
var orderline_html = QWeb.render('LoadOrderLine',
{widget: this, order:orders[i]});
var orderline = document.createElement('tbody');
orderline.innerHTML = orderline_html;
orderline = orderline.childNodes[1];
orderline.addEventListener('click', function() {
self.current_order_id = parseInt(this.dataset['orderId']);
self.load_order(self.current_order_id);
});
contents.appendChild(orderline);
}
},
perform_search: function(query){
this.load_orders(query)
},
clear_search: function(){
this.load_orders();
this.$('.searchbox input')[0].value = '';
this.$('.searchbox input').focus();
},
});
}

76
pos_order_load/static/src/xml/pos_order_load.xml

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<template>
<t t-name="LoadButtonWidget">
<button class="order-load">Load Draft Order</button>
</t>
<t t-name="SaveButtonWidget">
<li class="orderline">
<button class="order-save">Save Current Order</button>
</li>
</t>
<t t-name="LoadOrderLine">
<tr class="order-line" t-att-data-order-id="order.id">
<td><t t-esc="order.name"/></td>
<td t-if="order.partner_id"><t t-esc="order.partner_id[1]"/></td>
<td t-if="!order.partner_id"></td>
<td class="total"><t t-esc="widget.format_currency(order.amount_total)"/></td>
</tr>
</t>
<t t-name="OrderListScreenWidget">
<div class="orderlist-screen screen">
<div class="screen-content">
<section class="top-content">
<span class="button back">
<i class="fa fa-angle-double-left"></i>
Cancel
</span>
<span class="button validate">
<i class="fa fa-angle-double-left"></i>
Validate
</span>
<span class="searchbox">
<input placeholder="Search Orders" />
<span class="search-clear"></span>
</span>
<span class="searchbox"></span>
<span class="button next oe_hidden highlight">
Select Order
<i class="fa fa-angle-double-right"></i>
</span>
</section>
<section class="full-content">
<div class="window">
<section class="subwindow collapsed">
<div class="subwindow-container">
<div class="subwindow-container-fix order-details-contents">
</div>
</div>
</section>
<section class="subwindow">
<div class="subwindow-container">
<div class="subwindow-container-fix touch-scrollable scrollable-y">
<table class="order-list">
<thead>
<tr>
<th>Order</th>
<th>Customer</th>
<th>Total Amount</th>
</tr>
</thead>
<tbody class="order-list-contents">
</tbody>
</table>
</div>
</div>
</section>
</div>
</section>
</div>
</div>
</t>
</template>

18
pos_order_load/view/pos_order_load.xml

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- vim:fdn=3:
-->
<openerp>
<data>
<template id="assets_backend" name="pos_order_load assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<script type="text/javascript" src="/pos_order_load/static/src/js/pos_order_load.js"></script>
</xpath>
</template>
<template id="index_pos_order_load" name="POS Index Order Load" inherit_id="point_of_sale.index">
<xpath expr="//head" position="inside">
<link rel="stylesheet" href="/pos_order_load/static/src/css/pos_order_load.css"/>
</xpath>
</template>
</data>
</openerp>
Loading…
Cancel
Save